Files
simple-private-codecollection/docs/02-generation-rules-only-pattern.md
T
stewartshea eb8160e659 Initial scaffold: generation-rule-only Crossplane Bucket example
Educational RunWhen CodeCollection that discovers Crossplane GCP
Bucket CRDs (storage.gcp.upbound.io/v1beta1) and generates one SLX
per bucket. Ships only generation rules and Jinja templates; the
runtime lives in rw-generic-codecollection/k8s-kubectl-cmd (already
loaded by the airgap runner).

Includes:
- codebundles/gcp-bucket-crossplane-health with generation rule + 3 templates
- docs/01..07 numbered training chapters with screenshot placeholders
- README, .gitignore

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-30 21:51:04 -04:00

4.6 KiB

02 — The generation-rule-only pattern

What you'll do

Understand the split of responsibilities between this private code collection (rules + templates only) and the shared rw-generic-codecollection (the actual Robot runtime).

Traditional codebundle vs. rules-only codebundle

A traditional codebundle looks like this:

codebundles/k8s-my-thing/
  meta.yaml
  README.md
  runbook.robot           # ← runtime
  sli.robot               # ← runtime
  .runwhen/
    generation-rules/*.yaml
    templates/*.yaml      # templates reference this bundle's own robots

A rules-only codebundle looks like this — this is the pattern we are teaching:

codebundles/gcp-bucket-crossplane-health/
  README.md
  .runwhen/
    generation-rules/gcp-bucket-crossplane-health.yaml
    templates/
      gcp-bucket-crossplane-health-slx.yaml
      gcp-bucket-crossplane-health-sli.yaml
      gcp-bucket-crossplane-health-taskset.yaml

Notice: no meta.yaml, no runbook.robot, no sli.robot. The templates still reference pathToRobot, but they point at a different code collection.

The delegation trick

Look at gcp-bucket-crossplane-health-sli.yaml:

codeBundle:
  repoUrl: http://rw-airgap-cc-catalog-svc.runwhen-env-airgap:8080/git/rw-generic-codecollection.git
  ref: main
  pathToRobot: codebundles/k8s-kubectl-cmd/sli.robot
configProvided:
  - name: KUBECTL_COMMAND
    value: |
      kubectl get buckets.storage.gcp.upbound.io {{match_resource.resource.metadata.name}} ...

repoUrl points at the airgap cc-catalog proxy for rw-generic-codecollection. pathToRobot picks the generic k8s-kubectl-cmd/sli.robot. configProvided.KUBECTL_COMMAND is the actual command we want that generic robot to run — parameterized by the discovered resource's name via the match_resource Jinja variable.

At runtime, the workflow is:

sequenceDiagram
  participant WB as workspace-builder
  participant CR as Cluster (kubeapi)
  participant Repo as private CC (Gitea)
  participant PAPI as RunWhen Platform
  participant Worker as Runner Worker
  participant Generic as rw-generic-codecollection

  WB->>Repo: git clone (with token)
  WB->>CR: list buckets.storage.gcp.upbound.io
  CR-->>WB: [ Bucket A, Bucket B, ... ]
  WB->>WB: match rule; render templates
  WB->>PAPI: upload N SLXs (each with codeBundle.repoUrl = rw-generic)
  PAPI->>Worker: schedule SLI + TaskSet runs
  Worker->>Generic: clone at ref=main; load k8s-kubectl-cmd
  Worker->>CR: KUBECTL_COMMAND (kubectl + jq)
  CR-->>Worker: JSON status
  Worker->>PAPI: push metric / issues

Why not just put the robot files in the private CC?

Because then every consumer of the private CC would need to run the runner-side clone and image handling for those robots. The airgap runner's runner.codeCollections list would have to include this repo (with the same token concerns). By keeping the private CC as rules and templates only, you never have to update the runner-side collections list — the Robot code always resolves to the same shared, cached, image-baked generic collection that the runner already knows how to load.

The template variables you have

At render time, workspace-builder passes each template a rich context. The most useful entries for our use case:

Variable Value in our case
slx_name Generated SLX name, e.g. shared-cluster-runwhen-nonprod-shared-litellm-logging-xp-bkt-hlth
match_resource.resource The full Kubernetes object as returned by the API
match_resource.resource.metadata.name The Bucket's name
match_resource.kind Bucket (for CRDs, filled by the indexer)
match_resource.qualified_name The composite ID used by the resource registry
qualifiers The dict populated per your qualifiers list in the rule (resource, cluster)
cluster.name The Kubernetes cluster name (e.g. shared-cluster)
default_location Workspace default runner location
workspace.owner_email From workspaceInfo.workspaceOwnerEmail
custom.kubeconfig_secret_name From the custom: block in workspaceInfo
wb_version Set only in newer workspace-builders (used to gate the newer kubernetes-auth.yaml include)

Screenshot placeholders

  • images/02-side-by-side-file-tree.png — screenshot of the two directory trees (traditional vs. rules-only) side by side.
  • images/02-render-sequence.png — annotated version of the sequence diagram above (optional).

Next

Continue to 03 — Crossplane CRD discovery.