fe0946a146
Revert the earlier typo that changed the resourceTypes entry from
`buckets.storage.gcp.upbound.io` to `bucket.storage.gcp.upbound.io`
under the mistaken rationale of "consistency with preferred API
version". Kubernetes resource type specs in generation rules use the
API plural name (as reported by `kubectl api-resources`), which for
this CRD is `buckets`.
Confirmed against the live cluster:
kubectl api-resources --api-group=storage.gcp.upbound.io
NAME=buckets NAMESPACED=false KIND=Bucket
Symptom of the typo (from workspace-builder logs, image
`806-merge-343c86e3`):
Trying custom resource bucket.storage.gcp.upbound.io
Error scanning for custom resource instances; error: (403)
The 403 was misleading — it isn't an RBAC problem (the ClusterRole
correctly grants get/list/watch on `buckets`); it's the API server
rejecting a call for a resource type that doesn't exist. The
scope-aware discovery from runwhen-local PR #806 is working; it
just had the wrong plural to look up.
Co-authored-by: Cursor <cursoragent@cursor.com>
gcp-bucket-crossplane-health
A generation-rule-only codebundle. It has no sli.robot, no
runbook.robot, and no meta.yaml. It ships:
| File | Role |
|---|---|
.runwhen/generation-rules/gcp-bucket-crossplane-health.yaml |
Tells workspace-builder to discover every Crossplane Bucket (storage.gcp.upbound.io/v1beta1) in the cluster and generate one SLX per bucket. |
.runwhen/templates/gcp-bucket-crossplane-health-slx.yaml |
Jinja template for the ServiceLevelX resource. |
.runwhen/templates/gcp-bucket-crossplane-health-sli.yaml |
Jinja template for the ServiceLevelIndicator. Points at rw-generic-codecollection/k8s-kubectl-cmd/sli.robot. |
.runwhen/templates/gcp-bucket-crossplane-health-taskset.yaml |
Jinja template for the Runbook (TaskSet). Points at rw-generic-codecollection/k8s-kubectl-cmd/runbook.robot in JSON-issue mode. |
How it works
workspace-builderloads this codecollection and reads the generation rule.- The rule declares
resourceTypes: [buckets.storage.gcp.upbound.io/v1beta1]. RunWhen Local's Kubernetes indexer expands that spec intoplural=buckets, group=storage.gcp.upbound.io, version=v1beta1, callslist_cluster_custom_object, and stores each Bucket in the registry. - The
matchRulespredicate matches every Bucket by name (regex.+). - For each match, the rule emits an SLX with three output items (
slx,sli,runbook). Each output item is rendered from the corresponding Jinja template. - The SLI and Runbook templates set
codeBundle.repoUrlto the internal airgap URL forrw-generic-codecollection, andpathToRobottok8s-kubectl-cmd/{sli,runbook}.robot. That's where the actual runtime lives — this codecollection ships only the discovery and templating logic.
Health scoring
- SLI — the
KUBECTL_COMMANDrunskubectl get bucket ... -o json, pipes throughjqto compute the fraction of.status.conditions[]whosestatus == "True", and pushes that number (0.0 to 1.0) as the metric. A healthy Crossplane Bucket hasReady=TrueandSynced=True, producing an SLI of1.0. - TaskSet — the same generic bundle in JSON-issue mode. The
jqexpression emits{ issuesIdentified: true|false, issues: [...] }. Each condition whose status is notTruebecomes one issue with a severity (higher forReady), areproduce_hint, and human-readablenext_steps.
Why this pattern is useful
- Reuse over rewrite: no need to fork or maintain another robot file.
- Small blast radius: a private repo like this one holds just the organization-specific discovery logic and messaging.
- Fast iteration: change the
jqfilter and the workspace-builder picks it up on the next reconcile — no runner image rebuild required.