Files
simple-private-codecollection/docs/02-generation-rules-only-pattern.md
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

125 lines
4.6 KiB
Markdown

# 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`](../codebundles/gcp-bucket-crossplane-health/.runwhen/templates/gcp-bucket-crossplane-health-sli.yaml):
```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:
```mermaid
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](03-crossplane-crd-discovery.md).