Adds the 6 CompositeResourceDefinitions and matching Compositions that back the catalyst-api Day-2 CRUD endpoints. catalyst-api writes XRCs of these kinds; Crossplane materialises them into provider-hcloud (and a small number of provider-kubernetes) managed resources. Per docs/INVIOLABLE-PRINCIPLES.md #3, every cloud-side op flows through provider-hcloud — never bespoke hcloud-go calls or shell-outs to the hcloud CLI. XRDs (canonical group: compose.openova.io/v1alpha1): - RegionClaim → composes the Phase-0 quartet via provider-hcloud: Network + NetworkSubnet + Firewall + Server (cp1) + LoadBalancer + LoadBalancerNetwork + LoadBalancerService×2 + LoadBalancerTarget. Mirrors infra/hetzner/main.tf 1:1 so deletion of a RegionClaim cascades the whole slice. - ClusterClaim → composes a provider-kubernetes Object that materialises a cluster-identity ConfigMap. The catalyst-environment-controller reads the CM to template per-server cloud-init. - NodePoolClaim → composes up to 100 provider-hcloud Server resources. UPDATE flow: patching replicas n→m flips the per-index Required-policy gate so Crossplane creates/deletes Server CRs. - LoadBalancerClaim → composes provider-hcloud LoadBalancer + LoadBalancerNetwork + up to 50 LoadBalancerService entries (per listener) + up to 50 LoadBalancerTarget entries. UPDATE: patch listeners[]/targets[] → composite controller adds/removes services/targets. - PeeringClaim → composes 1 or 2 provider-hcloud Route resources (bidirectional flag toggles the second one through a Required-policy gate). - NodeActionClaim → composes a provider-kubernetes Object that creates a batch/v1 Job running kubectl cordon/drain (k8s-side op, not a cloud op, per the task spec). action=replace additionally composes a provider-hcloud Server for the replacement node. UPDATE/DELETE summary: - UPDATE: every mutable schema field is patched onto the underlying managed resource; Crossplane's composite controller drives the diff and provider-hcloud reconciles to the new state. - DELETE: every composed resource has deletionPolicy: Delete, so a cascade delete of the composite tears down the whole resource graph in dependency-safe order (Crossplane retries until deps unblock). New tests: - tests/composition-validate.sh — 7 gates: helm renders cleanly, exactly 6 XRDs, ≥ 6 Compositions, all 6 expected claim kinds present, every rendered doc is valid YAML, every fixture references a real XRD, and (when KUBECONFIG + Crossplane CRDs available) server-side dry-run for every fixture. - tests/fixtures/<kind>-sample.yaml — one XRC fixture per kind. Version bump: - platform/crossplane/chart/Chart.yaml 1.1.1 → 1.1.2 - platform/crossplane/blueprint.yaml 1.1.1 → 1.1.2 - clusters/_template/bootstrap-kit/04-crossplane.yaml → 1.1.2 - clusters/otech.omani.works/bootstrap-kit/04-crossplane.yaml → 1.1.2 Hard rules respected: - provider-hcloud only for cloud ops (never hcloud-go, never CLI). - provider-kubernetes Object for k8s-side ops (never raw kubectl). - No bespoke kubectl manifests for cloud resources. - Frontend + catalyst-api Go code untouched (sibling-owned). - Target state, no MVP framing — all 6 Compositions ship. Co-authored-by: hatiyildiz <hatice.yildiz@openova.io> Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|---|---|---|
| .. | ||
| chart | ||
| compositions | ||
| blueprint.yaml | ||
| README.md | ||
Crossplane
Day-2 cloud resource provisioning for Catalyst. Per-Sovereign on the management cluster (see docs/PLATFORM-TECH-STACK.md §3.2) — manages all non-Kubernetes resources for the entire Sovereign (host clusters, VPCs, DNS records, S3 buckets, third-party SaaS).
Crossplane is platform plumbing, never a user-facing surface. Users see "needs a database, pick existing or new" in the Catalyst console; Blueprint authors write Compositions; advanced users (sovereign-admins, OpenOva engineers) contribute Compositions upstream as Blueprints. End users do NOT write Crossplane Compositions in their Application configs. See
docs/ARCHITECTURE.md§4 / §7 (the "no fourth surface" rule) anddocs/BLUEPRINT-AUTHORING.md§8.
Status: Accepted | Updated: 2026-04-27
Overview
Crossplane provides Kubernetes-native cloud resource provisioning for day-2 operations. Terraform handles initial bootstrap; Crossplane manages ongoing infrastructure.
Architecture
flowchart TB
subgraph K8s["Kubernetes"]
subgraph Crossplane
Controller[Crossplane Controller]
Provider[Cloud Provider]
end
XR[Composite Resources]
Claim[Claims]
end
subgraph Cloud["Cloud Provider"]
Resources[Cloud Resources]
end
Claim --> XR
XR --> Controller
Controller --> Provider
Provider --> Resources
OpenTofu vs Crossplane
Catalyst uses OpenTofu (the open-source Terraform fork) for bootstrap IaC, not Terraform. See docs/PLATFORM-TECH-STACK.md §3.2 and platform/opentofu/.
| Aspect | OpenTofu | Crossplane |
|---|---|---|
| Phase | Bootstrap (day-0/1) — Phase 0 of Sovereign provisioning, then archived | Day-2+ operations |
| State | External state file | Kubernetes CRDs |
| Drift | Manual detection | Continuous reconciliation |
| Access | CI/CD pipeline (catalyst-provisioner) | K8s RBAC |
| Lifecycle | Point-in-time | GitOps continuous |
Decision: Use OpenTofu for initial cluster bootstrap only (Phase 0). All subsequent infrastructure managed via Crossplane.
Supported Providers
| Provider | Status | Crossplane Provider |
|---|---|---|
| Hetzner Cloud | Available | hcloud |
| Huawei Cloud | Coming | huaweicloud |
| Oracle Cloud | Coming | oci |
| AWS | Coming | aws |
| GCP | Coming | gcp |
| Azure | Coming | azure |
Configuration
Provider Configuration
apiVersion: pkg.crossplane.io/v1
kind: Provider
metadata:
name: provider-hcloud
spec:
package: xpkg.upbound.io/crossplane-contrib/provider-hcloud:v0.4.0
---
apiVersion: hcloud.crossplane.io/v1alpha1
kind: ProviderConfig
metadata:
name: default
spec:
credentials:
source: Secret
secretRef:
namespace: crossplane-system
name: hcloud-credentials
key: token
Composite Resource Definition
apiVersion: apiextensions.crossplane.io/v1
kind: CompositeResourceDefinition
metadata:
name: xdatabases.compose.openova.io
spec:
group: compose.openova.io # canonical XRD group per BLUEPRINT-AUTHORING §8
names:
kind: XDatabase
plural: xdatabases
versions:
- name: v1alpha1
served: true
referenceable: true
schema:
openAPIV3Schema:
type: object
properties:
spec:
type: object
properties:
size:
type: string
enum: [small, medium, large]
Composition
apiVersion: apiextensions.crossplane.io/v1
kind: Composition
metadata:
name: database.hcloud.compose.openova.io
spec:
compositeTypeRef:
apiVersion: compose.openova.io/v1alpha1 # canonical XRD group per BLUEPRINT-AUTHORING §8
kind: XDatabase
resources:
- name: server
base:
apiVersion: hcloud.crossplane.io/v1alpha1
kind: Server
spec:
forProvider:
serverType: cx21
image: ubuntu-22.04
GitOps Integration
Crossplane resources are managed via Flux:
apiVersion: kustomize.toolkit.fluxcd.io/v1
kind: Kustomization
metadata:
name: crossplane
namespace: flux-system
spec:
interval: 10m
sourceRef:
kind: GitRepository
name: crossplane
path: ./deploy/prod
prune: true
Catalyst Integration
Crossplane Compositions are referenced by Blueprints when an Application requires non-Kubernetes resources (cloud DBs, DNS records, S3 buckets, etc.). End users never see Crossplane directly — they see "needs a database" in the Blueprint's configSchema, rendered as a form in the Catalyst console. Advanced users author Crossplane Compositions and contribute them upstream as Blueprints. See docs/BLUEPRINT-AUTHORING.md §8.
Part of OpenOva