Consolidates the five cogarchhubgitops-* source skills into one skill with scope routing: ArgoCD Applications, Copier templates, Helm values, environment answer files, and the end-to-end resource-addition walkthrough. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
6.0 KiB
Adding a Cluster Resource End-to-End
The integration walkthrough: one atomic change touching all three layers — Copier schema, ArgoCD Application, Helm values — plus the environment answer file.
Read this when onboarding a brand-new operator, database, MCP component, or workload. For an
isolated edit to one layer, read that layer's reference instead
(argocd-applications.md, copier-templates.md, helm-values.md, environments.md).
Starting templates: assets/example-cluster-resource/.
Step 1 — Choose number, name, and flag
Band: from the table in SKILL.md §4
Number: next available in that band
Name: <NNN>-<kebab-case-descriptor>
Flag: <domain>.<descriptor>_is_enabled (type: bool, default: false)
Confirm the number is free before writing anything:
ls cluster/argocd/ cluster/resources/ | grep -o '[0-9]\{3\}' | sort -u
The directory names are Jinja2 expressions, so the numbers are still visible in a plain listing — no rendering needed for this check.
Step 2 — Declare variables in copier.yaml
<domain>:
<descriptor>_is_enabled:
type: bool
default: false
help: "Enable <NNN>-<name>"
<descriptor>:
image:
address:
type: str
default: ""
help: "Container image for <name>"
tag:
type: str
default: ""
help: "Container image tag for <name>"
# additional variables as the resource requires
The flag defaults to false so existing environments are unaffected until they opt in.
Step 3 — Create the ArgoCD Application
cluster/argocd/{{ '<NNN>-<name>' if <domain>.<descriptor>_is_enabled }}/application.yaml.jinja
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: <NNN>-<name>
namespace: openshift-gitops
finalizers:
- resources-finalizer.argocd.argoproj.io
spec:
project: default
source:
repoURL: {{ gitops.repo_url }}
targetRevision: {{ gitops.branch }}
path: cluster/resources/<NNN>-<name>
helm:
valueFiles:
- values.yaml
destination:
server: https://kubernetes.default.svc
namespace: <target-namespace>
syncPolicy:
automated:
prune: true
selfHeal: true
syncOptions:
- CreateNamespace=true
Step 4 — Create the Helm values template
cluster/resources/{{ '<NNN>-<name>' if <domain>.<descriptor>_is_enabled }}/values.yaml.jinja
The conditional expression must be character-identical to step 3.
{% macro render() %}
# values for <NNN>-<name>
replicaCount: 1
image:
repository: {{ <domain>.get('<descriptor>', {}).get('image', {}).get('address', '') }}
tag: {{ <domain>.get('<descriptor>', {}).get('image', {}).get('tag', '') }}
pullPolicy: Always
# ... remaining values
{% endmacro %}
{%- set result = render() -%}
{%- for line in result.split('\n') -%}
{% if line | trim | length != 0 -%}
{{ line }}
{% endif -%}
{%- endfor %}
Step 5 — Update environment answer files
For each environment that should run this resource:
<domain>:
<descriptor>_is_enabled: true
<descriptor>:
image:
address: <registry>/<image>
tag: <version>
Enable it in one environment first — typically the lowest — and promote after it syncs cleanly. Enabling everywhere in the same commit means a template bug reaches production in the same sync cycle.
Step 6 — Validate both states
# Flag off (defaults): the directories must NOT appear
copier copy . /tmp/gitops-render-off --overwrite --defaults
ls /tmp/gitops-render-off/cluster/argocd/ | grep "<NNN>" # expect no output
# Flag on: the directories must appear
copier copy . /tmp/gitops-render-on --overwrite --defaults \
--data '<domain>.<descriptor>_is_enabled=true' \
--data '<domain>.<descriptor>.image.address=myregistry/myimage' \
--data '<domain>.<descriptor>.image.tag=1.0.0'
ls /tmp/gitops-render-on/cluster/argocd/ | grep "<NNN>"
# Both rendered files must parse
python3 -c "import yaml; yaml.safe_load(open('/tmp/gitops-render-on/cluster/argocd/<NNN>-<name>/application.yaml'))"
python3 -c "import yaml; yaml.safe_load(open('/tmp/gitops-render-on/cluster/resources/<NNN>-<name>/values.yaml'))"
# Helm lint, when a chart path is available
helm lint <chart-path> -f /tmp/gitops-render-on/cluster/resources/<NNN>-<name>/values.yaml
Testing the off state matters as much as the on state: a malformed conditional expression can render the directory unconditionally, silently deploying the new resource to every environment on the next sync.
Step 7 — Consistency checklist
- The three-digit number is unique and in the correct band.
- The flag is declared in
copier.yamlwithtype: boolanddefault: false. - The ArgoCD
metadata.namematches the directory name exactly. spec.source.pathmatches thecluster/resources/directory name exactly.- The conditional expressions in
cluster/argocd/andcluster/resources/are identical. repoURLandtargetRevisionuse Jinja2 variables, not literals.resources-finalizer.argocd.argoproj.iois present inmetadata.finalizers.values.yaml.jinjauses therender()macro and stripping footer.- All variable references use
.get()with a type-appropriate default. - No secrets or credentials in any new file.
- At least one environment's answer file enables and fully configures the resource.
- Rendered and validated with the flag both off and on.
Mistakes to avoid
- Creating the ArgoCD Application without the matching
cluster/resources/directory — ArgoCD fails with path-not-found. - Different conditional expressions on the two sides — one renders, the other is omitted.
- Referencing a variable that was never declared in
copier.yaml— the render fails for every environment, not just yours. - Hardcoding image address or tag in
values.yaml.jinja. - Setting the new flag
default: true— new resources are opt-in. - Committing without rendering; a broken template blocks every environment.