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>
This commit is contained in:
@@ -0,0 +1,124 @@
|
||||
# 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).
|
||||
Reference in New Issue
Block a user