eb8160e659
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>
125 lines
4.6 KiB
Markdown
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).
|