openova/platform/crossplane
e3mrah 8592d20919
feat(bp-crossplane): 6 XRDs + Compositions for Day-2 CRUD (RegionClaim/ClusterClaim/NodePoolClaim/LoadBalancerClaim/PeeringClaim/NodeActionClaim) (#236)
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>
2026-04-30 09:33:38 +02:00
..
chart feat(bp-crossplane): 6 XRDs + Compositions for Day-2 CRUD (RegionClaim/ClusterClaim/NodePoolClaim/LoadBalancerClaim/PeeringClaim/NodeActionClaim) (#236) 2026-04-30 09:33:38 +02:00
compositions ci(pdm)+platform(crossplane): build workflow + XDynadotPoolAllocation composition (Phase 3+4 of #163) 2026-04-29 06:46:11 +02:00
blueprint.yaml feat(bp-crossplane): 6 XRDs + Compositions for Day-2 CRUD (RegionClaim/ClusterClaim/NodePoolClaim/LoadBalancerClaim/PeeringClaim/NodeActionClaim) (#236) 2026-04-30 09:33:38 +02:00
README.md docs(pass-48): crossplane OpenTofu/XRD group drift; PERSONAS clean 2026-04-28 00:10:48 +02:00

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) and docs/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