diff --git a/.cspell.json b/.cspell.json
index 4023775f..ee99af6a 100644
--- a/.cspell.json
+++ b/.cspell.json
@@ -17,8 +17,11 @@
"buildplan",
"cainjector",
"CAROOT",
+ "certificaterequests",
+ "certificatesigningrequests",
"clsx",
"clusterissuer",
+ "clusterissuers",
"clusterrole",
"clusterrolebinding",
"configmap",
@@ -71,6 +74,7 @@
"Kustomizations",
"kustomize",
"ldflags",
+ "leaderelection",
"libnss",
"loadbalancer",
"mattn",
@@ -117,6 +121,7 @@
"startupapicheck",
"stefanprodan",
"structpb",
+ "subjectaccessreviews",
"svclb",
"systemconnect",
"tablewriter",
diff --git a/doc/md/guides/expose-a-service.mdx b/doc/md/guides/expose-a-service.mdx
index 9377a31a..cf6bf923 100644
--- a/doc/md/guides/expose-a-service.mdx
+++ b/doc/md/guides/expose-a-service.mdx
@@ -24,8 +24,9 @@ You'll need the following tools installed to complete this guide.
2. [helm](https://helm.sh/docs/intro/install/) - to render Helm Components.
3. [kubectl](https://kubernetes.io/docs/tasks/tools/) - to render Kustomize Components.
-Optionally, if you'd like to apply the rendered manifests to a real Cluster,
-first complete the [localhost Guide](../local-cluster).
+As an optional, but recommended step, complete the [Local Cluster] guide if you'd like to apply
+the rendered manifests to a cluster. This will smooth out the friction of
+managing certificates.
## Create a Git Repository
@@ -52,8 +53,8 @@ repository unless stated otherwise.
## Generate the Platform {#Generate-Platform}
-Start by generating a platform with one workload Cluster. The `guide` Platform
-is intended as a starting point for all of our guides.
+Generate a platform with one workload cluster. The `guide` Platform is intended
+as a starting point for all of our guides.
@@ -63,7 +64,7 @@ holos generate component workload-cluster
```
-```txt showLineNumbers
+```txt
generated component
```
@@ -87,68 +88,75 @@ git commit -m "holos generate platform guide - $(holos --version)"
-## Gateway API
+## Namespaces
-The Gateway API is an official Kubernetes project focused on L4 and L7 routing .
-You'll use the custom resources defined by the Gateway API to expose the httpbin
-service outside of the cluster. The Kubernetes Gateway API does not come
-installed by default on most Kubernetes clusters, so we need to manage the
-custom resource definitions (CRDs).
+We often need to manage namespaces prior to workloads being deployed. This is
+necessary because a namespace is a security boundary. Holos makes it easier,
+safer, and more consistent to manage service accounts, role bindings, and
+secrets prior to deploying workloads into a namespace.
-Run the following command to generate a Component to manage the Gateway API.
+We'll see how this works with the namespaces component, which offers a mechanism
+for other components to register their namespaces. The namespaces component
+initializes each registered namespace, optionally mixing in resources
+consistently.
-
+Run the following command to generate the namespaces component.
+
+
```bash
-holos generate component gateway-api
+holos generate component namespaces
```
-```txt showLineNumbers
+```txt
generated component
```
-The command generates two main configuration files, one at the leaf, and another
-at the root of the tree. At the leaf, the config produces a Kustomize build
-plan for Holos to render. At the root, the config adds the Component to all
-Clusters in the Platform.
-
-Notice the `kustomization.yaml` file at the leaf. This is an unmodified
-upstream copy of the standard way to install the Gateway API.
+The command generates two main configuration files like we've seen with other
+components. One file at the leaf, and another at the root. The leaf uses a
+Kubernetes build plan to produce resources directly from CUE.
-
-
- `components/gateway-api/gateway-api.cue`
+
+
+ `components/namespaces/namespaces.cue`
```cue showLineNumbers
package holos
-// Produce a kubectl kustomize build plan.
-(#Kustomize & {Name: "gateway-api"}).Output
-```
-
-
- `components/gateway-api/kustomization.yaml`
-```yaml showLineNumbers
-resources:
-- standard/gateway.networking.k8s.io_gatewayclasses.yaml
-- standard/gateway.networking.k8s.io_gateways.yaml
-- standard/gateway.networking.k8s.io_grpcroutes.yaml
-- standard/gateway.networking.k8s.io_httproutes.yaml
-- standard/gateway.networking.k8s.io_referencegrants.yaml
+let Objects = {
+ Name: "namespaces"
+ // highlight-next-line
+ Resources: Namespace: #Namespaces
+}
+
+// Produce a kubernetes objects build plan.
+(#Kubernetes & Objects).Output
```
-
- `gateway-api.gen.cue`
+
+ `namespaces.gen.cue`
```cue showLineNumbers
package holos
-// Manage on every Cluster in the Platform
+import corev1 "k8s.io/api/core/v1"
+
+// #Namespaces defines all managed namespaces in the Platform.
+// Holos adopts the sig-multicluster position of namespace sameness.
+#Namespaces: {
+ // Validate against v1 of the kubernetes core api
+ // highlight-next-line
+ [Name=string]: corev1.#Namespace & {
+ metadata: name: Name
+ }
+}
+
+// Manage the Component on every Cluster in the Platform
for Fleet in #Fleets {
for Cluster in Fleet.clusters {
- #Platform: Components: "\(Cluster.name)/gateway-api": {
- path: "components/gateway-api"
+ #Platform: Components: "\(Cluster.name)/namespaces": {
+ path: "components/namespaces"
cluster: Cluster.name
}
}
@@ -157,7 +165,20 @@ for Fleet in #Fleets {
-Render the Platform to render the Component for the workload clusters.
+Notice the highlighted line in the leaf file. Resources are managed directly in
+CUE at the leaf using the Kubernetes component. This is the same mechanism used
+to mix-in resources to Helm and Kustomize components. The leaf refers to
+`#Namespaces` defined at the root. At the root `#Namespaces` enforces a
+constraint: each Namespace must conform to the `k8s.io/api/core/v1`
+specification.
+
+- At the **leaf** Holos tailors the component to your platform, mixing
+in resources and customizing the rendered output.
+- At the **root** Holos integrates a component with the rest of your platform.
+
+You'll see this pattern again and again as you build your platform.
+
+Render the platform to render the component for the workload clusters.
@@ -167,131 +188,338 @@ holos render platform ./platform
```txt showLineNumbers
-rendered components/gateway-api for cluster workload in 279.312292ms
+rendered components/namespaces for cluster workload in 72.675292ms
+```
+
+
+
+Add and commit the configuration and rendered manifests.
+
+
+
+```bash
+git add .
+git commit -m "add namespaces component"
+```
+
+
+```txt showLineNumbers
+[main 1bf0d61] add namespaces component
+ 3 files changed, 30 insertions(+)
+ create mode 100644 components/namespaces/namespaces.cue
+ create mode 100644 deploy/clusters/workload/components/namespaces/namespaces.gen.yaml
+ create mode 100644 namespaces.gen.cue
```
+`#Namespaces` is currently empty, so the rendered output of
+`namespaces.gen.yaml` is also empty.
+
:::tip
-This example is equivalent to running `kubectl kustomize
-./components/gateway-api` and saving the output to a file. Holos simplifies
-this task and makes it consistent with Helm and other tools.
+Namespaces will be automatically managed as we add more components to the
+platform over time.
:::
-Add and commit the Component and rendered Platform.
+## Cert Manager
-
+We'll need a valid certificate to browse to httpbin. We'll manage cert-manager
+in our cluster to issue valid tls certificates.
+
+Run the following command to generate the cert-manager component.
+
+
+
+```bash
+holos generate component cert-manager
+```
+
+
+```txt
+generated component
+```
+
+
+
+This command generates a configuration file at the leaf and the root. At the
+leaf two helm values configure the behavior of the upstream cert-manager chart.
+At the root cert-manager is managed on all clusters in the platform.
+
+1. The leaf references the version and namespace fields defined in
+`#CertManager` at the root.
+2. The leaf defines two Helm values to manage. Holos makes it easier and safer
+to focus on how software is integrated into our platform.
+3. The root registers cert-manager for the namespaces component to manage
+consistently across all clusters in the platform.
+4. The root manages the component on all clusters in the platform.
+
+
+
+ `components/cert-manager/cert-manager.cue`
+```cue showLineNumbers
+package holos
+
+// Produce a helm chart build plan.
+(#Helm & Chart).Output
+
+let Chart = {
+ Name: "cert-manager"
+ // highlight-next-line
+ Version: #CertManager.Version
+ // highlight-next-line
+ Namespace: #CertManager.Namespace
+
+ Repo: name: "jetstack"
+ Repo: url: "https://charts.jetstack.io"
+
+ // highlight-next-line
+ Values: installCRDs: true
+ // highlight-next-line
+ Values: startupapicheck: enabled: false
+}
+```
+
+
+ `cert-manager.gen.cue`
+```cue showLineNumbers
+package holos
+
+// Platform wide configuration
+#CertManager: {
+ Version: "1.15.3"
+ Namespace: "cert-manager"
+}
+
+// Register the namespace
+// highlight-next-line
+#Namespaces: (#CertManager.Namespace): _
+
+// Manage the component on every cluster in the platform
+for Fleet in #Fleets {
+ for Cluster in Fleet.clusters {
+ #Platform: Components: "\(Cluster.name)/cert-manager": {
+ path: "components/cert-manager"
+ cluster: Cluster.name
+ }
+ }
+}
+```
+
+
+
+Render the platform to render manifests into the deploy directory.
+
+
+
+```bash
+holos render platform ./platform
+```
+
+
+```txt showLineNumbers
+rendered components/namespaces for cluster workload in 65.184791ms
+rendered components/cert-manager for cluster workload in 467.379292ms
+```
+
+
+
+Add and commit the configuration and rendered manifests.
+
+
```bash
git add .
-git commit -m "add gateway-api component"
+git commit -m "integrate cert-manager into the platform"
```
```txt showLineNumbers
-[main 88575a5] add gateway-api component
- 9 files changed, 26907 insertions(+)
- create mode 100644 components/gateway-api/gateway-api.cue
- create mode 100644 components/gateway-api/kustomization.yaml
- create mode 100644 components/gateway-api/standard/gateway.networking.k8s.io_gatewayclasses.yaml
- create mode 100644 components/gateway-api/standard/gateway.networking.k8s.io_gateways.yaml
- create mode 100644 components/gateway-api/standard/gateway.networking.k8s.io_grpcroutes.yaml
- create mode 100644 components/gateway-api/standard/gateway.networking.k8s.io_httproutes.yaml
- create mode 100644 components/gateway-api/standard/gateway.networking.k8s.io_referencegrants.yaml
- create mode 100644 deploy/clusters/workload/components/gateway-api/gateway-api.gen.yaml
- create mode 100644 gateway-api.gen.cue
+[main c6ab5f4] integrate cert-manager into the platform
+ 3 files changed, 9434 insertions(+)
+ create mode 100644 cert-manager.gen.cue
+ create mode 100644 components/cert-manager/cert-manager.cue
+ create mode 100644 deploy/clusters/workload/components/cert-manager/cert-manager.gen.yaml
```
-Optionally apply the rendered component to your cluster.
+:::tip
+We often need to understand how a change affects the platform as a whole. Holos
+offers the ability to use your preferred tooling to understand platform wide
+changes.
+:::
-
+For example, `git` summarizes all of the components and clusters affected by
+adding cert-manager. The output shows both the namespaces and cert-manager
+components have changed on the workload cluster.
+
+
```bash
-kubectl apply --server-side=true -f deploy/clusters/workload/components/gateway-api
+git show --stat deploy
```
```txt showLineNumbers
-customresourcedefinition.apiextensions.k8s.io/gatewayclasses.gateway.networking.k8s.io serverside-applied
-customresourcedefinition.apiextensions.k8s.io/gateways.gateway.networking.k8s.io serverside-applied
-customresourcedefinition.apiextensions.k8s.io/grpcroutes.gateway.networking.k8s.io serverside-applied
-customresourcedefinition.apiextensions.k8s.io/httproutes.gateway.networking.k8s.io serverside-applied
-customresourcedefinition.apiextensions.k8s.io/referencegrants.gateway.networking.k8s.io serverside-applied
+deploy/clusters/workload/components/cert-manager/cert-manager.gen.yaml | 9407
+deploy/clusters/workload/components/namespaces/namespaces.gen.yaml | 8
+2 files changed, 9415 insertions(+)
```
-## Namespaces
+As an optional step, apply the changes.
-We often need to manage namespaces prior to workloads being deployed. This is
-necessary because a namespace is a security boundary. Holos makes it easier,
-safer, and more consistent to manage service accounts, role bindings, and
-secrets prior to deploying workloads into a namespace.
+
+
+```bash
+kubectl apply --server-side=true -f deploy/clusters/workload/components/namespaces
+```
+
+
+```txt showLineNumbers
+namespace/cert-manager serverside-applied
+```
+
+
-We'll see how this works with the namespaces component, which offers a mechanism
-for other components to register their namespaces. The namespaces component
-initializes each registered namespace, optionally mixing in resources
-consistently.
+
+
+```bash
+kubectl apply --server-side=true -f deploy/clusters/workload/components/cert-manager
+```
+
+
+```txt showLineNumbers
+serviceaccount/cert-manager-cainjector serverside-applied
+serviceaccount/cert-manager serverside-applied
+serviceaccount/cert-manager-webhook serverside-applied
+customresourcedefinition.apiextensions.k8s.io/certificaterequests.cert-manager.io serverside-applied
+customresourcedefinition.apiextensions.k8s.io/certificates.cert-manager.io serverside-applied
+customresourcedefinition.apiextensions.k8s.io/challenges.acme.cert-manager.io serverside-applied
+customresourcedefinition.apiextensions.k8s.io/clusterissuers.cert-manager.io serverside-applied
+customresourcedefinition.apiextensions.k8s.io/issuers.cert-manager.io serverside-applied
+customresourcedefinition.apiextensions.k8s.io/orders.acme.cert-manager.io serverside-applied
+clusterrole.rbac.authorization.k8s.io/cert-manager-cainjector serverside-applied
+clusterrole.rbac.authorization.k8s.io/cert-manager-controller-issuers serverside-applied
+clusterrole.rbac.authorization.k8s.io/cert-manager-controller-clusterissuers serverside-applied
+clusterrole.rbac.authorization.k8s.io/cert-manager-controller-certificates serverside-applied
+clusterrole.rbac.authorization.k8s.io/cert-manager-controller-orders serverside-applied
+clusterrole.rbac.authorization.k8s.io/cert-manager-controller-challenges serverside-applied
+clusterrole.rbac.authorization.k8s.io/cert-manager-controller-ingress-shim serverside-applied
+clusterrole.rbac.authorization.k8s.io/cert-manager-cluster-view serverside-applied
+clusterrole.rbac.authorization.k8s.io/cert-manager-view serverside-applied
+clusterrole.rbac.authorization.k8s.io/cert-manager-edit serverside-applied
+clusterrole.rbac.authorization.k8s.io/cert-manager-controller-approve:cert-manager-io serverside-applied
+clusterrole.rbac.authorization.k8s.io/cert-manager-controller-certificatesigningrequests serverside-applied
+clusterrole.rbac.authorization.k8s.io/cert-manager-webhook:subjectaccessreviews serverside-applied
+clusterrolebinding.rbac.authorization.k8s.io/cert-manager-cainjector serverside-applied
+clusterrolebinding.rbac.authorization.k8s.io/cert-manager-controller-issuers serverside-applied
+clusterrolebinding.rbac.authorization.k8s.io/cert-manager-controller-clusterissuers serverside-applied
+clusterrolebinding.rbac.authorization.k8s.io/cert-manager-controller-certificates serverside-applied
+clusterrolebinding.rbac.authorization.k8s.io/cert-manager-controller-orders serverside-applied
+clusterrolebinding.rbac.authorization.k8s.io/cert-manager-controller-challenges serverside-applied
+clusterrolebinding.rbac.authorization.k8s.io/cert-manager-controller-ingress-shim serverside-applied
+clusterrolebinding.rbac.authorization.k8s.io/cert-manager-controller-approve:cert-manager-io serverside-applied
+clusterrolebinding.rbac.authorization.k8s.io/cert-manager-controller-certificatesigningrequests serverside-applied
+clusterrolebinding.rbac.authorization.k8s.io/cert-manager-webhook:subjectaccessreviews serverside-applied
+role.rbac.authorization.k8s.io/cert-manager-cainjector:leaderelection serverside-applied
+role.rbac.authorization.k8s.io/cert-manager:leaderelection serverside-applied
+role.rbac.authorization.k8s.io/cert-manager-webhook:dynamic-serving serverside-applied
+rolebinding.rbac.authorization.k8s.io/cert-manager-cainjector:leaderelection serverside-applied
+rolebinding.rbac.authorization.k8s.io/cert-manager:leaderelection serverside-applied
+rolebinding.rbac.authorization.k8s.io/cert-manager-webhook:dynamic-serving serverside-applied
+service/cert-manager serverside-applied
+service/cert-manager-webhook serverside-applied
+deployment.apps/cert-manager-cainjector serverside-applied
+deployment.apps/cert-manager serverside-applied
+deployment.apps/cert-manager-webhook serverside-applied
+mutatingwebhookconfiguration.admissionregistration.k8s.io/cert-manager-webhook serverside-applied
+validatingwebhookconfiguration.admissionregistration.k8s.io/cert-manager-webhook serverside-applied
+```
+
+
-Run the following command to generate the namespaces component.
+Check the pods become ready
-
+
```bash
-holos generate component namespaces
+kubectl get pods -n cert-manager
```
```txt showLineNumbers
+NAME READY STATUS RESTARTS AGE
+cert-manager-9647b459d-d5q8f 1/1 Running 0 9s
+cert-manager-cainjector-5d8798687c-kv8bs 1/1 Running 0 9s
+cert-manager-webhook-c77744d75-j6hv8 1/1 Running 0 9s
+```
+
+
+
+## Gateway API
+
+The Gateway API is an official Kubernetes project focused on L4 and L7 routing .
+You'll use the custom resources defined by the Gateway API to expose the httpbin
+service outside of the cluster. The Kubernetes Gateway API does not come
+installed by default on most Kubernetes clusters, so we need to manage the
+custom resource definitions (CRDs).
+
+Run the following command to generate a Component to manage the Gateway API.
+
+
+
+```bash
+holos generate component gateway-api
+```
+
+
+```txt
generated component
```
-The command generates two main configuration files like we've seen with other
-components. One file at the leaf, and another at the root. The leaf uses a
-Kubernetes build plan to produce resources directly from CUE.
+The command generates two main configuration files, one at the leaf, and another
+at the root of the tree. At the leaf, the config produces a Kustomize build
+plan for Holos to render. At the root, the config adds the Component to all
+Clusters in the Platform.
-
-
- `components/namespaces/namespaces.cue`
+Notice the `kustomization.yaml` file at the leaf. This is an unmodified
+upstream copy of the standard way to install the Gateway API.
+
+
+
+ `components/gateway-api/gateway-api.cue`
```cue showLineNumbers
package holos
-let Objects = {
- Name: "namespaces"
- // highlight-next-line
- Resources: Namespace: #Namespaces
-}
-
-// Produce a kubernetes objects build plan.
-(#Kubernetes & Objects).Output
+// Produce a kubectl kustomize build plan.
+(#Kustomize & {Name: "gateway-api"}).Output
```
-
- `namespaces.gen.cue`
+
+ `components/gateway-api/kustomization.yaml`
+```yaml showLineNumbers
+resources:
+- standard/gateway.networking.k8s.io_gatewayclasses.yaml
+- standard/gateway.networking.k8s.io_gateways.yaml
+- standard/gateway.networking.k8s.io_grpcroutes.yaml
+- standard/gateway.networking.k8s.io_httproutes.yaml
+- standard/gateway.networking.k8s.io_referencegrants.yaml
+```
+
+
+ `gateway-api.gen.cue`
```cue showLineNumbers
package holos
-import corev1 "k8s.io/api/core/v1"
-
-// #Namespaces defines all managed namespaces in the Platform.
-// Holos adopts the sig-multicluster position of namespace sameness.
-#Namespaces: {
- // Validate against v1 of the kubernetes core api
- // highlight-next-line
- [Name=string]: corev1.#Namespace & {
- metadata: name: Name
- }
-}
-
-// Manage the Component on every Cluster in the Platform
+// Manage on every Cluster in the Platform
for Fleet in #Fleets {
for Cluster in Fleet.clusters {
- #Platform: Components: "\(Cluster.name)/namespaces": {
- path: "components/namespaces"
+ #Platform: Components: "\(Cluster.name)/gateway-api": {
+ path: "components/gateway-api"
cluster: Cluster.name
}
}
@@ -300,28 +528,14 @@ for Fleet in #Fleets {
-Notice the highlighted line in the leaf file. Resources are managed directly in
-CUE at the leaf using the Kubernetes component. This is the same mechanism used
-to mix-in resources to Helm and Kustomize components. The leaf refers to
-`#Namespaces` defined at the root. At the root `#Namespaces` enforces a
-constraint: each Namespace must conform to the `k8s.io/api/core/v1`
-specification.
-
:::important
-We've covered three kinds of components so far. The [Quickstart] guide
-introduced Helm. We've used Kustomize and Kubernetes in this guide.
+We've covered three kinds of components so far: Kubernetes, Helm, and Kustomize.
Holos offers a consistent way to manage these different kinds of packaging
safely and easily.
:::
-- At the **leaf** Holos tailors the component to your platform, mixing
-in resources and customizing the rendered output.
-- At the **root** Holos integrates a component with the rest of your platform.
-
-You'll see this pattern again and again as you build your platform.
-
-Render the platform to render the component for the workload clusters.
+Render the Platform to render the Component for the workload clusters.
@@ -331,41 +545,63 @@ holos render platform ./platform
```txt showLineNumbers
-rendered components/namespaces for cluster workload in 72.675292ms
-rendered components/gateway-api for cluster workload in 259.174583ms
+rendered components/gateway-api for cluster workload in 279.312292ms
```
-Add and commit the configuration and rendered manifests.
+:::tip
+This example is equivalent to running `kubectl kustomize
+./components/gateway-api` and saving the output to a file. Holos simplifies
+this task and makes it consistent with Helm and other tools.
+:::
+
+Add and commit the Component and rendered Platform.
```bash
git add .
-git commit -m "add namespaces component"
+git commit -m "add gateway-api component"
```
```txt showLineNumbers
-[main 1bf0d61] add namespaces component
- 3 files changed, 30 insertions(+)
- create mode 100644 components/namespaces/namespaces.cue
- create mode 100644 deploy/clusters/workload/components/namespaces/namespaces.gen.yaml
- create mode 100644 namespaces.gen.cue
+[main 88575a5] add gateway-api component
+ 9 files changed, 26907 insertions(+)
+ create mode 100644 components/gateway-api/gateway-api.cue
+ create mode 100644 components/gateway-api/kustomization.yaml
+ create mode 100644 components/gateway-api/standard/gateway.networking.k8s.io_gatewayclasses.yaml
+ create mode 100644 components/gateway-api/standard/gateway.networking.k8s.io_gateways.yaml
+ create mode 100644 components/gateway-api/standard/gateway.networking.k8s.io_grpcroutes.yaml
+ create mode 100644 components/gateway-api/standard/gateway.networking.k8s.io_httproutes.yaml
+ create mode 100644 components/gateway-api/standard/gateway.networking.k8s.io_referencegrants.yaml
+ create mode 100644 deploy/clusters/workload/components/gateway-api/gateway-api.gen.yaml
+ create mode 100644 gateway-api.gen.cue
```
-`#Namespaces` is currently empty, so the rendered output of
-`namespaces.gen.yaml` is also empty.
+As an optional step, apply the rendered component to your cluster.
-:::tip
-Namespaces will be automatically managed as we add more components to the
-platform over time.
-:::
+
+
+```bash
+kubectl apply --server-side=true -f deploy/clusters/workload/components/gateway-api
+```
+
+
+```txt showLineNumbers
+customresourcedefinition.apiextensions.k8s.io/gatewayclasses.gateway.networking.k8s.io serverside-applied
+customresourcedefinition.apiextensions.k8s.io/gateways.gateway.networking.k8s.io serverside-applied
+customresourcedefinition.apiextensions.k8s.io/grpcroutes.gateway.networking.k8s.io serverside-applied
+customresourcedefinition.apiextensions.k8s.io/httproutes.gateway.networking.k8s.io serverside-applied
+customresourcedefinition.apiextensions.k8s.io/referencegrants.gateway.networking.k8s.io serverside-applied
+```
+
+
-## Istio
+## Istio Service Mesh
We'll manage Istio to implement the Gateway API so we can expose the httpbin
service outside of the cluster.
@@ -379,7 +615,7 @@ holos generate component istio
```
-```txt showLineNumbers
+```txt
generated component
```
@@ -400,7 +636,7 @@ holos generate component istio-k3d
```
-```txt showLineNumbers
+```txt
generated component
```
@@ -790,8 +1026,122 @@ kube-system svclb-gateway-5d311af0-fp5mk 3/3 Running 0
-Once all pods are ready, we're ready to manage httpbin so we can route http
-traffic to it.
+Once all pods are ready, we're ready for the next step toward exposing httpbin.
+
+## Certificate Issuer
+
+We need to issue certificates our browser trusts so we can browse httpbin.
+We'll do this by configuring a ClusterIssuer using the ca private key we created
+in the [Local Cluster] guide.
+
+Run the following command to generate the local-ca component.
+
+
+
+```bash
+holos generate component local-ca
+```
+
+
+```txt
+generated component
+```
+
+
+
+At the leaf, the configuration refers to the `#CertManager.Namespace` value
+defined previously at the root by the cert-manager component.
+
+
+
+```cue showLineNumbers
+package holos
+
+import ci "cert-manager.io/clusterissuer/v1"
+
+// Produce a kubernetes objects build plan.
+(#Kubernetes & Objects).Output
+
+let Objects = {
+ Name: "local-ca"
+ // highlight-next-line
+ Namespace: #CertManager.Namespace
+
+ Resources: ClusterIssuer: LocalCA: ci.#ClusterIssuer & {
+ metadata: name: "local-ca"
+ // highlight-next-line
+ metadata: namespace: #CertManager.Namespace
+
+ // The secret name must align with the local cluster guide at
+ // https://holos.run/docs/guides/local-cluster/
+ spec: ca: secretName: "local-ca"
+ }
+}
+```
+
+
+
+Render the platform to render manifests into the deploy directory.
+
+
+
+```bash
+holos render platform ./platform
+```
+
+
+```txt showLineNumbers
+TODO
+```
+
+
+
+Add and commit the configuration and rendered manifests.
+
+
+
+```bash
+git add .
+git commit -m "integrate local-ca into the platform"
+```
+
+
+```txt showLineNumbers
+TODO
+```
+
+
+
+As an optional step, apply the configuration to the cluster.
+
+
+
+```bash
+kubectl apply --server-side=true -f deploy/clusters/workload/components/local-ca
+```
+
+
+```txt showLineNumbers
+clusterissuer.cert-manager.io/local-ca serverside-applied
+```
+
+
+
+Verify the local-ca ClusterIssuer is ready:
+
+
+
+```bash
+kubectl get clusterissuers.cert-manager.io
+```
+
+
+```txt showLineNumbers
+NAME READY AGE
+local-ca True 12s
+```
+
+
## httpbin
diff --git a/doc/md/guides/local-cluster.mdx b/doc/md/guides/local-cluster.mdx
index 2897ffa8..5efdae82 100644
--- a/doc/md/guides/local-cluster.mdx
+++ b/doc/md/guides/local-cluster.mdx
@@ -88,7 +88,7 @@ Finally, add your trusted certificate authority.
```bash
kubectl apply --server-side=true -f "$(mkcert -CAROOT)/namespace.yaml"
-kubectl apply --server-side=true -f "$(mkcert -CAROOT)/local-ca.yaml"
+kubectl apply --server-side=true -n cert-manager -f "$(mkcert -CAROOT)/local-ca.yaml"
```
diff --git a/internal/generate/components/v1alpha3/cert-manager/cert-manager.gen.cue b/internal/generate/components/v1alpha3/cert-manager/cert-manager.gen.cue
new file mode 100644
index 00000000..09677b7b
--- /dev/null
+++ b/internal/generate/components/v1alpha3/cert-manager/cert-manager.gen.cue
@@ -0,0 +1,20 @@
+package holos
+
+// Platform wide configuration
+#CertManager: {
+ Version: "{{ .Version }}"
+ Namespace: "{{ .Namespace }}"
+}
+
+// Register the namespace
+#Namespaces: (#CertManager.Namespace): _
+
+// Manage the component on every cluster in the platform
+for Fleet in #Fleets {
+ for Cluster in Fleet.clusters {
+ #Platform: Components: "\(Cluster.name)/{{ .Name }}": {
+ path: "components/cert-manager"
+ cluster: Cluster.name
+ }
+ }
+}
diff --git a/internal/generate/components/v1alpha3/cert-manager/components/cert-manager/cert-manager.cue b/internal/generate/components/v1alpha3/cert-manager/components/cert-manager/cert-manager.cue
new file mode 100644
index 00000000..2960b227
--- /dev/null
+++ b/internal/generate/components/v1alpha3/cert-manager/components/cert-manager/cert-manager.cue
@@ -0,0 +1,16 @@
+package holos
+
+// Produce a helm chart build plan.
+(#Helm & Chart).Output
+
+let Chart = {
+ Name: "{{ .Name }}"
+ Version: #CertManager.Version
+ Namespace: #CertManager.Namespace
+
+ Repo: name: "{{ .RepoName }}"
+ Repo: url: "{{ .RepoURL }}"
+
+ Values: installCRDs: true
+ Values: startupapicheck: enabled: false
+}
diff --git a/internal/generate/components/v1alpha3/cert-manager/schematic.json b/internal/generate/components/v1alpha3/cert-manager/schematic.json
new file mode 100644
index 00000000..58008650
--- /dev/null
+++ b/internal/generate/components/v1alpha3/cert-manager/schematic.json
@@ -0,0 +1,10 @@
+{
+ "name": "cert-manager",
+ "short": "cloud native X.509 certificate management for kubernetes",
+ "long": "cert-manager creates tls certificates for workloads in your kubernetes cluster and renews the certificates before they expire.",
+ "chart": "",
+ "reponame": "jetstack",
+ "repourl": "https://charts.jetstack.io",
+ "version": "1.15.3",
+ "namespace": "cert-manager"
+}
diff --git a/internal/generate/components/v1alpha3/local-ca/components/local-ca/local-ca.cue b/internal/generate/components/v1alpha3/local-ca/components/local-ca/local-ca.cue
new file mode 100644
index 00000000..a69d3905
--- /dev/null
+++ b/internal/generate/components/v1alpha3/local-ca/components/local-ca/local-ca.cue
@@ -0,0 +1,20 @@
+package holos
+
+import ci "cert-manager.io/clusterissuer/v1"
+
+// Produce a kubernetes objects build plan.
+(#Kubernetes & Objects).Output
+
+let Objects = {
+ Name: "local-ca"
+ Namespace: #CertManager.Namespace
+
+ Resources: ClusterIssuer: LocalCA: ci.#ClusterIssuer & {
+ metadata: name: "local-ca"
+ metadata: namespace: #CertManager.Namespace
+
+ // The secret name must align with the local cluster guide at
+ // https://holos.run/docs/guides/local-cluster/
+ spec: ca: secretName: "local-ca"
+ }
+}
diff --git a/internal/generate/components/v1alpha3/local-ca/local-ca.gen.cue b/internal/generate/components/v1alpha3/local-ca/local-ca.gen.cue
new file mode 100644
index 00000000..2528af9f
--- /dev/null
+++ b/internal/generate/components/v1alpha3/local-ca/local-ca.gen.cue
@@ -0,0 +1,11 @@
+package holos
+
+// Manage the component on every cluster in the platform
+for Fleet in #Fleets {
+ for Cluster in Fleet.clusters {
+ #Platform: Components: "\(Cluster.name)/{{ .Name }}": {
+ path: "components/local-ca"
+ cluster: Cluster.name
+ }
+ }
+}
diff --git a/internal/generate/components/v1alpha3/local-ca/schematic.json b/internal/generate/components/v1alpha3/local-ca/schematic.json
new file mode 100644
index 00000000..87c0ed9e
--- /dev/null
+++ b/internal/generate/components/v1alpha3/local-ca/schematic.json
@@ -0,0 +1,6 @@
+{
+ "name": "local-ca",
+ "short": "manages a cluster issuer for use with our guides",
+ "long": "manages a cluster issuer that uses the mkcert ca private key to issue certs",
+ "namespace": "cert-manager"
+}
diff --git a/internal/generate/platforms/cue.mod/pkg/github.com/holos-run/holos/api/schema/v1alpha3/definitions.cue b/internal/generate/platforms/cue.mod/pkg/github.com/holos-run/holos/api/schema/v1alpha3/definitions.cue
index fc84db62..9d456981 100644
--- a/internal/generate/platforms/cue.mod/pkg/github.com/holos-run/holos/api/schema/v1alpha3/definitions.cue
+++ b/internal/generate/platforms/cue.mod/pkg/github.com/holos-run/holos/api/schema/v1alpha3/definitions.cue
@@ -11,6 +11,7 @@ import (
batchv1 "k8s.io/api/batch/v1"
app "argoproj.io/application/v1alpha1"
+ ci "cert-manager.io/clusterissuer/v1"
)
#Resources: {
@@ -19,6 +20,7 @@ import (
metadata: name: string | *InternalLabel
}
+ ClusterIssuer: [_]: ci.#ClusterIssuer
ClusterRole: [_]: rbacv1.#ClusterRole
ClusterRoleBinding: [_]: rbacv1.#ClusterRoleBinding
ConfigMap: [_]: corev1.#ConfigMap