Files
development-gitops-argo-cd-…/references/adding-a-resource.md
oleg-lukasonokandClaude Opus 5 50d746d03f Add gitops-argo-cd agent skill
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>
2026-07-29 23:48:32 +03:00

196 lines
6.0 KiB
Markdown

# 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
```text
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:
```bash
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`
```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`
```yaml
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.
```jinja
{% 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:
```yaml
<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
```bash
# 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.yaml` with `type: bool` and `default: false`.
- [ ] The ArgoCD `metadata.name` matches the directory name exactly.
- [ ] `spec.source.path` matches the `cluster/resources/` directory name exactly.
- [ ] The conditional expressions in `cluster/argocd/` and `cluster/resources/` are identical.
- [ ] `repoURL` and `targetRevision` use Jinja2 variables, not literals.
- [ ] `resources-finalizer.argocd.argoproj.io` is present in `metadata.finalizers`.
- [ ] `values.yaml.jinja` uses the `render()` 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.