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:
+50
@@ -0,0 +1,50 @@
|
||||
apiVersion: runwhen.com/v1
|
||||
kind: ServiceLevelIndicator
|
||||
metadata:
|
||||
name: {{slx_name}}
|
||||
labels:
|
||||
{% include "common-labels.yaml" %}
|
||||
annotations:
|
||||
{% include "common-annotations.yaml" %}
|
||||
spec:
|
||||
displayUnitsLong: Percentage
|
||||
displayUnitsShort: '%'
|
||||
locations:
|
||||
- {{default_location}}
|
||||
description: >-
|
||||
Fraction of Crossplane .status.conditions[] that are True for
|
||||
GCP Bucket {{match_resource.resource.metadata.name}}. Healthy = 1.0.
|
||||
# NOTE: This SLI runs from a *different* codecollection than the one
|
||||
# that owns this generation rule. The generic runtime (`k8s-kubectl-cmd`)
|
||||
# lives in `rw-generic-codecollection` — it is already loaded by the
|
||||
# airgap runner. If you move this example to another environment,
|
||||
# change `repoUrl` to wherever `rw-generic-codecollection` is served
|
||||
# (public GitHub, an internal proxy, a Gitea mirror, etc.).
|
||||
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
|
||||
intervalStrategy: intermezzo
|
||||
intervalSeconds: 300
|
||||
configProvided:
|
||||
- name: TASK_TITLE
|
||||
value: 'Crossplane GCP Bucket {{match_resource.resource.metadata.name}} condition health'
|
||||
# jq computes truthyConditions / totalConditions. Returns 0 when the
|
||||
# resource has never reconciled (no conditions yet), which correctly
|
||||
# signals "unhealthy" to the SLO layer.
|
||||
- name: KUBECTL_COMMAND
|
||||
value: |
|
||||
kubectl get buckets.storage.gcp.upbound.io {{match_resource.resource.metadata.name}} -o json | jq -r '(.status.conditions // []) as $c | if ($c|length)==0 then 0 else ([$c[]|select(.status=="True")]|length)/($c|length) end'
|
||||
- name: TIMEOUT_SECONDS
|
||||
value: '120'
|
||||
secretsProvided:
|
||||
{% if wb_version %}
|
||||
{% include "kubernetes-auth.yaml" ignore missing %}
|
||||
{% else %}
|
||||
- name: kubeconfig
|
||||
workspaceKey: {{custom.kubeconfig_secret_name}}
|
||||
{% endif %}
|
||||
alertConfig:
|
||||
tasks:
|
||||
persona: eager-edgar
|
||||
sessionTTL: 10m
|
||||
+36
@@ -0,0 +1,36 @@
|
||||
apiVersion: runwhen.com/v1
|
||||
kind: ServiceLevelX
|
||||
metadata:
|
||||
name: {{slx_name}}
|
||||
labels:
|
||||
{% include "common-labels.yaml" %}
|
||||
annotations:
|
||||
{% include "common-annotations.yaml" %}
|
||||
spec:
|
||||
imageURL: https://storage.googleapis.com/runwhen-nonprod-shared-images/icons/kubernetes.svg
|
||||
alias: Crossplane GCP Bucket {{match_resource.resource.metadata.name}} Health
|
||||
asMeasuredBy: Fraction of Crossplane .status.conditions[] that are True (Ready + Synced).
|
||||
configProvided:
|
||||
- name: OBJECT_NAME
|
||||
value: {{match_resource.resource.metadata.name}}
|
||||
- name: API_GROUP
|
||||
value: storage.gcp.upbound.io
|
||||
- name: KIND
|
||||
value: Bucket
|
||||
owners:
|
||||
- {{workspace.owner_email}}
|
||||
statement: >-
|
||||
Crossplane GCP Bucket {{match_resource.resource.metadata.name}} should have
|
||||
every status condition (Ready, Synced) in state True 99.5% of the time.
|
||||
additionalContext:
|
||||
{% include "kubernetes-hierarchy.yaml" ignore missing %}
|
||||
qualified_name: "{{ match_resource.qualified_name }}"
|
||||
tags:
|
||||
{% include "kubernetes-tags.yaml" ignore missing %}
|
||||
|
||||
- name: access
|
||||
value: read-only
|
||||
- name: managed-by
|
||||
value: crossplane
|
||||
- name: provider
|
||||
value: upbound-gcp
|
||||
+47
@@ -0,0 +1,47 @@
|
||||
apiVersion: runwhen.com/v1
|
||||
kind: Runbook
|
||||
metadata:
|
||||
name: {{slx_name}}
|
||||
labels:
|
||||
{% include "common-labels.yaml" %}
|
||||
annotations:
|
||||
{% include "common-annotations.yaml" %}
|
||||
spec:
|
||||
location: {{default_location}}
|
||||
# See the SLI template for the rationale on hardcoding the generic
|
||||
# codecollection URL here.
|
||||
codeBundle:
|
||||
repoUrl: http://rw-airgap-cc-catalog-svc.runwhen-env-airgap:8080/git/rw-generic-codecollection.git
|
||||
ref: main
|
||||
pathToRobot: codebundles/k8s-kubectl-cmd/runbook.robot
|
||||
configProvided:
|
||||
- name: TASK_TITLE
|
||||
value: 'Inspect Crossplane GCP Bucket {{match_resource.resource.metadata.name}} conditions'
|
||||
- name: TIMEOUT_SECONDS
|
||||
value: '300'
|
||||
# Enable JSON-issue mode in the generic runbook: it will parse the
|
||||
# stdout JSON, check the trigger key, and emit one RunWhen issue per
|
||||
# element in the issues array.
|
||||
- name: ISSUE_JSON_QUERY_ENABLED
|
||||
value: 'true'
|
||||
- name: ISSUE_JSON_TRIGGER_KEY
|
||||
value: issuesIdentified
|
||||
- name: ISSUE_JSON_TRIGGER_VALUE
|
||||
value: 'true'
|
||||
- name: ISSUE_JSON_ISSUES_KEY
|
||||
value: issues
|
||||
# The `jq` expression below emits the JSON envelope that the generic
|
||||
# runbook expects:
|
||||
# { issuesIdentified: <bool>, issues: [ {title,severity,...}, ... ] }
|
||||
# One issue per condition whose status is not "True". Severity 2 for
|
||||
# Ready failures (data-plane visible), 3 for others (e.g. Synced).
|
||||
- name: KUBECTL_COMMAND
|
||||
value: |
|
||||
kubectl get buckets.storage.gcp.upbound.io {{match_resource.resource.metadata.name}} -o json | jq -c '(.status.conditions // []) as $c | { issuesIdentified: (($c|length)==0 or ([$c[]|select(.status!="True")]|length > 0)), issues: ( if ($c|length)==0 then [ { title: "Crossplane Bucket {{match_resource.resource.metadata.name}} has no status conditions yet", severity: 3, expected: "Bucket should have Ready and Synced conditions populated by the Crossplane provider", actual: "status.conditions is empty or missing", reproduce_hint: "kubectl get buckets.storage.gcp.upbound.io {{match_resource.resource.metadata.name}} -o yaml", next_steps: "1. Check that the crossplane-system provider pod for provider-gcp-storage is Running.\n2. kubectl describe buckets.storage.gcp.upbound.io {{match_resource.resource.metadata.name}} to see reconciliation events.\n3. Verify the referenced ProviderConfig exists and is Healthy.\n4. Wait one reconcile interval and re-check.", details: (.status | tostring) } ] else [ $c[] | select(.status!="True") | { title: ("Crossplane Bucket " + .type + " condition is not True: {{match_resource.resource.metadata.name}}"), severity: (if .type=="Ready" then 2 else 3 end), expected: (.type + " condition should be True"), actual: (.type + "=" + .status + " reason=" + (.reason // "n/a") + " message=" + (.message // "n/a")), reproduce_hint: "kubectl get buckets.storage.gcp.upbound.io {{match_resource.resource.metadata.name}} -o yaml", next_steps: "1. kubectl describe buckets.storage.gcp.upbound.io {{match_resource.resource.metadata.name}}\n2. Inspect the crossplane-system provider-gcp-storage pod logs for reconcile errors.\n3. Verify the ProviderConfig references a Secret with valid GCP credentials.\n4. Check GCP IAM permissions on the target bucket / project.\n5. Look for spec drift and wait for the next reconcile.", details: (. | tostring) } ] end ) }'
|
||||
secretsProvided:
|
||||
{% if wb_version %}
|
||||
{% include "kubernetes-auth.yaml" ignore missing %}
|
||||
{% else %}
|
||||
- name: kubeconfig
|
||||
workspaceKey: {{custom.kubeconfig_secret_name}}
|
||||
{% endif %}
|
||||
Reference in New Issue
Block a user