Motivation
We manage ClusterbookCluster CRs (clusterbook.stuttgart-things.com/v1alpha1) as raw YAML committed under ArgoCD app paths, e.g. clusters/labul/vsphere/platform-sthings/argocd/crossplane-dev1/cluster.yaml. That CR is an operator-backed abstraction handling IP allocation, DNS, ArgoCD cluster registration, kubeconfig secret wiring, and platform bootstrap (driven by the labels: toggle matrix consumed by ApplicationSets).
Our Configurations today cover VM provisioning (machinery/*) and k8s primitives (namespace, volume-claim, cloud-config), but nothing represents a cluster as a composable unit. A cluster-registration Configuration is the bridge between "we provisioned a node/VM" and "the platform is registered and bootstrapping in GitOps."
The hard part is already done: kcl/kubernetes/clusterbook-cluster-gen v0.3.0 is a mature module (schema validation for kind vs default clusterType, mutually-exclusive secret refs) that emits exactly this CR and is published to OCI. The wrapping pattern is also proven by cicd/ansible-run: namespaced XR → function-kcl (source: oci://…kcl-module) → provider-kubernetes Object → a derive-status KCL step that mirrors live CR status back onto the XR.
Value-add over raw YAML in argocd
- Typed XR API. The flat
labels: toggles (cicd-platform/crossplane: 'true', storage-platform/openebs, …) become a structured spec.platform.{cicd,network,storage,security} of booleans — defaultable/validatable in the XRD.
- Status surfacing. XR reflects the operator's reconcile result (allocated IP / FQDN / registered), like
ansible-run surfaces PipelineRun status.
- Composability. Becomes a building block for cluster provisioning + registration + bootstrap as one XR (see roadmap below).
Design decision to confirm
This file is currently pure GitOps (ArgoCD → ClusterbookCluster). A Configuration changes the chain to ArgoCD → XR → Crossplane → ClusterbookCluster, inserting Crossplane + provider-kubernetes as a dependency of cluster registration. On the management cluster this is fine (same in-cluster targeting as ansible-run). If we only ever want param-templating, an ApplicationSet/plugin over the existing KCL module is lighter — so this Configuration only pays off if we want cluster registration to be a composable Crossplane primitive.
Proposed shape (step 1, standalone)
- New Configuration
k8s/cluster-registration (or a new cluster/ category) — XRD apiextensions.crossplane.io/v2, scope: Namespaced.
- Composition:
function-kcl → clusterbook-cluster-gen OCI → provider-kubernetes Object (kubernetes.m.crossplane.io/v1alpha1, managementPolicies: [Observe, Create], no Delete) → derive-status → function-auto-ready.
- Shared inputs (Vault address, network pool) default from an
EnvironmentConfig; XR spec holds only the per-cluster delta.
xr-min / xr / xr-max examples, with crossplane-dev1 mirrored 1:1 as the realistic xr case.
Roadmap — compose with ESO + Rancher (later)
Mirrors how machinery grew (standalone configs → vm-provision router). Don't build the mega-Configuration first.
Cluster XR (namespaced)
├─ 1. PROVISION OpenTofu Workspace (rancher2 TF) → kube_config → connection Secret
├─ 2. SECRET ESO ClusterSecretStore + ExternalSecret (Vault)
└─ 3. REGISTER function-kcl → clusterbook-cluster-gen → provider-kubernetes Object → ClusterbookCluster
Building blocks that already exist:
- ESO + Vault:
kcl/crossplane/xplane-vault-config (eso-secret-store / eso-external-secret / eso-push-secret); machinery/virtual-machine already wires ClusterSecretStore + ExternalSecret.
- OpenTofu route: machinery provisions via
opentofu.m.upbound.io/v1beta1 Workspace — Rancher would follow the same pattern with the rancher2 TF provider (kube_config output → connection secret). Avoids an immature crossplane provider-rancher.
- Rancher is the only genuinely new piece.
The module's existing kubeconfigSecretRef vs existingSecretRef split already models the two kubeconfig provenances with no module change:
- Rancher-provisioned →
kubeconfigSecretRef (managed mode).
- Imported/external (kubeconfig in Vault via ESO) →
existingSecretRef (enrich mode).
Build order
cluster-registration (this issue) — standalone, closes the immediate gap.
rancher-cluster (opentofu/rancher2) — standalone provisioning, the new piece.
cluster router XR — composes registration + rancher + ESO, like vm-provision.
Gotchas to bank now
- Ordering isn't sequential. Crossplane reconciles composed resources concurrently; registration must tolerate the kubeconfig Secret being absent initially (operator retries). Gate readiness, don't assume sequence.
kubernetes.m.crossplane.io/v1alpha1 Object has no deletionPolicy — control delete behavior via managementPolicies only.
Acceptance criteria (step 1)
Motivation
We manage
ClusterbookClusterCRs (clusterbook.stuttgart-things.com/v1alpha1) as raw YAML committed under ArgoCD app paths, e.g.clusters/labul/vsphere/platform-sthings/argocd/crossplane-dev1/cluster.yaml. That CR is an operator-backed abstraction handling IP allocation, DNS, ArgoCD cluster registration, kubeconfig secret wiring, and platform bootstrap (driven by thelabels:toggle matrix consumed by ApplicationSets).Our Configurations today cover VM provisioning (
machinery/*) and k8s primitives (namespace,volume-claim,cloud-config), but nothing represents a cluster as a composable unit. Acluster-registrationConfiguration is the bridge between "we provisioned a node/VM" and "the platform is registered and bootstrapping in GitOps."The hard part is already done:
kcl/kubernetes/clusterbook-cluster-genv0.3.0 is a mature module (schema validation forkindvsdefaultclusterType, mutually-exclusive secret refs) that emits exactly this CR and is published to OCI. The wrapping pattern is also proven bycicd/ansible-run: namespaced XR →function-kcl(source: oci://…kcl-module) →provider-kubernetesObject → aderive-statusKCL step that mirrors live CR status back onto the XR.Value-add over raw YAML in argocd
labels:toggles (cicd-platform/crossplane: 'true',storage-platform/openebs, …) become a structuredspec.platform.{cicd,network,storage,security}of booleans — defaultable/validatable in the XRD.ansible-runsurfaces PipelineRun status.Design decision to confirm
This file is currently pure GitOps (
ArgoCD → ClusterbookCluster). A Configuration changes the chain toArgoCD → XR → Crossplane → ClusterbookCluster, inserting Crossplane + provider-kubernetes as a dependency of cluster registration. On the management cluster this is fine (same in-cluster targeting asansible-run). If we only ever want param-templating, an ApplicationSet/plugin over the existing KCL module is lighter — so this Configuration only pays off if we want cluster registration to be a composable Crossplane primitive.Proposed shape (step 1, standalone)
k8s/cluster-registration(or a newcluster/category) — XRDapiextensions.crossplane.io/v2,scope: Namespaced.function-kcl→clusterbook-cluster-genOCI →provider-kubernetesObject (kubernetes.m.crossplane.io/v1alpha1,managementPolicies: [Observe, Create], noDelete) →derive-status→function-auto-ready.EnvironmentConfig; XR spec holds only the per-cluster delta.xr-min/xr/xr-maxexamples, withcrossplane-dev1mirrored 1:1 as the realisticxrcase.Roadmap — compose with ESO + Rancher (later)
Mirrors how
machinerygrew (standalone configs →vm-provisionrouter). Don't build the mega-Configuration first.Building blocks that already exist:
kcl/crossplane/xplane-vault-config(eso-secret-store / eso-external-secret / eso-push-secret);machinery/virtual-machinealready wiresClusterSecretStore+ExternalSecret.opentofu.m.upbound.io/v1beta1Workspace — Rancher would follow the same pattern with therancher2TF provider (kube_configoutput → connection secret). Avoids an immature crossplane provider-rancher.The module's existing
kubeconfigSecretRefvsexistingSecretRefsplit already models the two kubeconfig provenances with no module change:kubeconfigSecretRef(managed mode).existingSecretRef(enrich mode).Build order
cluster-registration(this issue) — standalone, closes the immediate gap.rancher-cluster(opentofu/rancher2) — standalone provisioning, the new piece.clusterrouter XR — composes registration + rancher + ESO, likevm-provision.Gotchas to bank now
kubernetes.m.crossplane.io/v1alpha1Object has nodeletionPolicy— control delete behavior viamanagementPoliciesonly.Acceptance criteria (step 1)
k8s/cluster-registrationConfiguration renders locally (task render) for all three example XRs.xr.yamlreproduces the existingcrossplane-dev1ClusterbookCluster.ClusterProviderConfigviaspec.providerConfigRef) and the GitOps-vs-Crossplane ownership decision.README.mdupdated.