Skip to content

feat: rewrite the Cluster function in Python behind a stable platform API - #140

Open
jboero wants to merge 1 commit into
mainfrom
johnnyb_clean
Open

jboero wants to merge 1 commit into
mainfrom
johnnyb_clean

Conversation

@jboero

@jboero jboero commented Oct 14, 2026

Copy link
Copy Markdown

Summary

Rewrite the Cluster composition function and tests from KCL to Python, and reshape the Cluster into a stable platform API following Javid Habibi's "Can Your Platform API Survive Its Own Implementation?". The XRD changes are additive only, and existing Clusters keep their composed resources.

Python, split like the other Python functions

  • function/core.py builds the composed resources and status as typed models, without talking to Crossplane. function/fn.py only handles the request, and function/serialize.py dumps with by_alias and exclude_none (passing models straight to resource.update() drops aliases and defaults).
  • pytest unit tests in functions/cluster/tests cover what composition tests can't, mainly that a resource is not composed (nothing in-cluster before GKE is ready, no Usage for an App on another cluster). The shared workflow change that runs them in CI is upbound/sa-up#599.
  • Composition tests are rewritten in Python and run with schema validation on.

The Cluster as a platform API

  • Intent, not implementation: new nodes.size (small, medium, large) maps to a machine type in the function; medium is the old default n1-standard-4. nodes.instanceType stays as a documented override.
  • Implementation choices out of the schema: the Flux and Prometheus chart-version defaults move from the XRD into the function, so new Clusters follow platform updates. operators.*.version stays as an override.
  • Status as the interface: the Cluster publishes the network ID for SQLInstances, the ProviderConfig Apps deploy with, the kubeconfig Secret, and readiness for each part.
  • XRD categories and a description, for the catalog.

Fixes

  • Composed resources had fixed names, so two Clusters couldn't share a namespace. New ones are named after the Cluster; Crossplane keeps existing resources' names, so running Clusters are untouched (tested).
  • The deletion guard for Apps selected helm.crossplane.io Releases, but configuration-app creates namespaced helm.m.crossplane.io ones, so it never matched and was forced Ready. Apps are now found through the App API, one Usage per App on this cluster.
  • deletionPolicy: Orphan produced an Oss with LateInitialize, which the Oss API rejects; it now uses Oss's documented orphan policies.
  • Removed v1-era fields with no effect on namespaced XRs (writeConnectionSecretsToNamespace, defaultCompositeDeletePolicy).
  • Examples and README updated for Crossplane v2: namespaced gcp.m.upbound.io ProviderConfigs, the database connection Secret name, the postgres network ID, no claims.

Upgrading existing Clusters

  • The old App guard Usage (<id>-gke-by-labeled-release) is removed; it never protected anything.
  • Only Apps whose providerConfigName is the Cluster's id, and that carry the platform.upbound.io/deletion-ordering: enabled label, are protected.
  • Children gain a gcp.platformref.upbound.io/cluster-id label.

Testing

  • up project build, 5 composition tests and 14 unit tests pass; a deliberately broken gate makes a unit test fail.
  • Both Cluster examples render.
  • The e2e test is ported; it has not been run against a live control plane.

Follow-up

The domain packages are moving to provider-gcp v3 with typed status and an optional networkRef: upbound/configuration-gcp-network#91, upbound/configuration-gcp-gke#103, upbound/configuration-gcp-database#96. Once they're released, this repo bumps to them, which also resolves today's provider-family version conflict, and wires GKE to its Network by status.

🤖 Generated with Claude Code

… API

Rewrite the composition function and tests from KCL to Python, split
into core.py, which builds the composed resources and status without
talking to Crossplane, and a thin fn.py, with pytest unit tests on
core.py. Serialize with by_alias and exclude_none rather than passing
models to resource.update().

Apply the layered composition guidance: the Cluster schema describes
intent and changes additively only.
- Add nodes.size (small, medium, large), mapped to a machine type in the
  function; nodes.instanceType remains as an explicit override.
- Move the operator chart-version defaults out of the XRD into the
  function, so they follow platform updates; operators.*.version remain
  as overrides.
- Publish status for consumers: network ID, the ProviderConfig Apps
  deploy with, the kubeconfig Secret and per-part readiness.
- Add XRD categories and a description.

Fix composition bugs:
- Name composed resources after the Cluster so Clusters can share a
  namespace; existing resources keep their names.
- Protect Apps through the App API, one Usage per App on this cluster,
  instead of a helm.crossplane.io Release selector that never matched.
- Map deletionPolicy: Orphan to the Oss API's accepted policies.
- Only compose the Oss and Flux Usages alongside Oss and Flux.
- Drop v1-era fields with no effect on namespaced XRs.

Fix the examples and README for Crossplane v2: namespaced
ProviderConfigs, the database connection Secret name, the postgres
network ID, and no claims.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@jboero
jboero requested a review from a team as a code owner October 14, 2026 21:27
@Upbound-CLA

Upbound-CLA commented Oct 14, 2026 •

Copy link
Copy Markdown

CLA assistant check
All committers have signed the CLA.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants