Modified the generation rules for Crossplane GCP Buckets to omit the version in resource type definitions, allowing the use of the API server's preferred version (currently v1beta2). Updated documentation to reflect this change and clarify version pinning for schema stability. Changes include: - Updated YAML generation rules to remove explicit versioning. - Revised documentation to explain the implications of omitting versioning and the preferred version usage. This enhances resilience across CRD upgrades and simplifies the configuration for users.
5.4 KiB
03 — Crossplane CRD discovery
What you'll do
Understand how RunWhen's Kubernetes indexer discovers custom resources — specifically the Crossplane GCP Bucket CRD — driven entirely by what your generation rules ask for.
Step 1 — Confirm the CRD exists
kubectl api-resources | grep storage.gcp.upbound.io
Expected output (trimmed):
buckets storage.gcp.upbound.io/v1beta1 false Bucket
bucketiammembers storage.gcp.upbound.io/v1beta1 false BucketIAMMember
hmackeys storage.gcp.upbound.io/v1beta1 false HMACKey
Three things to note:
buckets— this is the plural resource name. It is what you put in the generation rule, not the singularBucket.storage.gcp.upbound.io/v1beta2— the API group and preferred version (olderv1beta1may also be served for compatibility).falsein the "NAMESPACED" column — Crossplane managed resources are cluster-scoped. This is why our generation rule usesqualifiers: ["resource", "cluster"]and notnamespace.
Version pinning: our rule uses just
buckets.storage.gcp.upbound.io(no/version), which tells the indexer to use whatever the API server reports as preferred. That's the most resilient choice across CRD upgrades. Pin/v1beta1or/v1beta2only when you deliberately need a specific schema.
Screenshot placeholder: images/03-kubectl-api-resources.png — output of
the above command.
Step 2 — Confirm buckets exist to discover
kubectl get buckets.storage.gcp.upbound.io -o wide
Sample output on the airgap cluster:
NAME READY SYNCED EXTERNAL-NAME
runwhen-nonprod-shared-litellm-logging True True runwhen-nonprod-shared-litellm-logging
runwhen-nonprod-shared-loki True True runwhen-nonprod-shared-loki
runwhen-nonprod-shared-mimir True True runwhen-nonprod-shared-mimir
runwhen-nonprod-shared-tempo True True runwhen-nonprod-shared-tempo
Screenshot placeholder: images/03-kubectl-buckets.png.
Step 3 — Look at the .status.conditions[] we care about
This is what the SLI and TaskSet will read. Pick any bucket and inspect it:
kubectl get buckets.storage.gcp.upbound.io <name> -o json | jq .status.conditions
Expected shape:
[
{
"lastTransitionTime": "2025-11-14T10:15:11Z",
"reason": "Available",
"status": "True",
"type": "Ready"
},
{
"lastTransitionTime": "2025-11-14T10:15:10Z",
"reason": "ReconcileSuccess",
"status": "True",
"type": "Synced"
}
]
Every Crossplane managed resource carries these two conditions:
Ready— the external resource (the actual GCS bucket in GCP) exists and is available. WhenReady=False, something in GCP is wrong (missing IAM, quota, deleted out-of-band, etc.).Synced— the last reconcile between the Crossplane spec and the provider succeeded. WhenSynced=False, Crossplane could not talk to the provider or hit a validation error.
Our SLI computes (#True conditions) / (#total conditions), so a healthy
bucket returns 1.0 and any single failing condition drops it to 0.5
or 0.0.
Step 4 — Understand how workspace-builder gets there
RunWhen Local's Kubernetes indexer performs selective discovery: it
only lists custom resource types that appear in loaded generation rules.
That's why simply declaring buckets.storage.gcp.upbound.io/v1beta1 in
our rule is enough — no extra customResourceTypes: list in
workspaceInfo.yaml.
The parsing (in
runwhen-local/src/indexers/kubetypes.py)
takes the string plural.group/version and splits it into
(plural='buckets', group='storage.gcp.upbound.io', version='v1beta1').
It then calls CustomObjectsApi.list_cluster_custom_object(...).
You can also specify custom resources with a dict form, useful when you need to be explicit about a particular version:
resourceTypes:
- platform: kubernetes
resourceType: custom
kind: buckets # plural, NOT PascalCase
group: storage.gcp.upbound.io
version: v1beta1
Common pitfalls
- Wrong plural — the biggest source of "nothing gets discovered". Always
copy the plural from
kubectl api-resources, never guess it from theKind. - Version omission — leaving off
/v1beta1uses the API server's preferred version. Usually fine but pin it explicitly if you rely on a specific schema. - RBAC — workspace-builder needs
get/list/watchon the CRD. The airgap runner already bindsworkspace-builderSA toviewat the cluster scope viarbac/workspace-builder-view-rbac.yaml, which covers CRDs installed after the binding was created. - Namespace scope — some CRDs are namespaced. If you had chosen a
namespaced CRD you would need to include the CRD's namespaces in your
cloudConfig.kubernetes.namespaceslist or leave that list empty.
Next
Continue to 04 — Generation rule walkthrough.