Files
development-gitops-argo-cd-…/SKILL.md
T
oleg-lukasonokandClaude Opus 5 fd003960c0 Rename skill to development-gitops-argo-cd
Aligns the skill name with the renamed repository and the development-*
naming used by development-scripts.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-30 22:29:06 +03:00

253 lines
12 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
---
name: development-gitops-argo-cd
description: >
Create, review, refactor, debug, and maintain GitOps repositories where ArgoCD syncs
cluster state from a Copier-templated Git repo — app-of-apps ArgoCD Application manifests
under cluster/argocd/, Helm values.yaml.jinja templates under cluster/resources/, Jinja2
conditional directory names used as feature flags, copier.yaml variable schemas, and
per-environment copier-answers.yaml files. Use when adding a new operator, database, or
workload to a cluster; editing sync policies, prune/self-heal, finalizers, or destination
namespaces; wiring container image address/tag through Copier variables into Helm values;
enabling or disabling a feature flag for one environment; comparing environment drift or
running copier update to re-render a deployed environment; or debugging an app stuck
OutOfSync, a template that fails to render, or blank lines that break rendered YAML — even
when the user only names their repository (for example CogArchHubGitOps) instead of saying
"GitOps" or "ArgoCD".
license: Proprietary
metadata:
author: workspace-skills-code-agent
version: "1.0"
spec: agentskills.io/specification
reference-implementation: CogArchHubGitOps
compatibility: Designed for Cline, Claude Code, GitHub Copilot, OpenAI Codex, and other compatible agent environments
---
# ArgoCD GitOps on a Copier Template Repository
Work rules for GitOps repositories that combine three layers: **Copier** renders the
repository from per-environment answer files, **ArgoCD** reads the rendered output and syncs
it, and **Helm** consumes the rendered `values.yaml` at sync time.
The rules here are repo-agnostic. `references/cogarchhubgitops-reference.md` records a
complete, concrete instance of every pattern (bands in use, flag names, global settings) —
read it when working in that repository, or as a worked example when standing up a new one.
## Scope routing
Pick the reference file that matches the change; do not load all of them.
| The change touches | Read |
|---|---|
| An `application.yaml.jinja` — sync policy, finalizers, destination, source path | `references/argocd-applications.md` |
| `copier.yaml`, any `.jinja` syntax, conditional directory names, the `render()` macro | `references/copier-templates.md` |
| A `values.yaml.jinja` — image wiring, `global` block, env vars, probes, replicas | `references/helm-values.md` |
| A `<env>/copier-answers.yaml` — feature flags, image tags, drift, `copier update` | `references/environments.md` |
| Adding a whole new resource across all three layers at once | `references/adding-a-resource.md` |
| CogArchHubGitOps specifically — bands in use, flag names, global settings | `references/cogarchhubgitops-reference.md` |
## 1. Canonical repository shape
```text
<repo>/
├── copier.yaml # variable schema + defaults
├── cluster/
│ ├── argocd/
│ │ ├── application.yaml.jinja # root app-of-apps (000-root)
│ │ └── {{ 'NNN-name' if <flag> }}/
│ │ └── application.yaml.jinja # one child Application per resource
│ └── resources/
│ └── {{ 'NNN-name' if <flag> }}/
│ └── values.yaml.jinja # Helm values for that Application
└── <env>/
└── copier-answers.yaml # per-environment answers
```
Every file under `cluster/` is a Copier template. There are no raw `.yaml` files there — a
manifest without the `.jinja` extension will be copied through unrendered and will reach
ArgoCD with literal `{{ … }}` in it.
## 2. The three-layer contract
Every managed resource spans three layers that must stay in sync:
```text
Layer 1 — copier.yaml
declares the feature flag + every configuration variable
Layer 2 — cluster/argocd/{{ 'NNN-name' if flag }}/application.yaml.jinja
tells ArgoCD where the chart and values live
Layer 3 — cluster/resources/{{ 'NNN-name' if flag }}/values.yaml.jinja
supplies the rendered values.yaml to Helm at sync time
```
All three share the **same feature flag variable** and the **same conditional directory
expression**. Breaking that symmetry is the single most common failure in this repo shape:
if the two directory expressions differ, one side renders and the other is omitted, and
ArgoCD fails with a path-not-found error.
**Mirroring rule** — every `cluster/argocd/<dir>/application.yaml.jinja` must have a
`cluster/resources/<same-dir>/` sibling containing at least a `values.yaml.jinja` (for Helm
apps) or raw manifests. Same expression, character for character.
## 3. App-of-apps bootstrap
`cluster/argocd/application.yaml.jinja` is the **root application**, conventionally named
`000-root`. It watches `cluster/argocd/` recursively inside the rendered repository. When
ArgoCD reconciles the root, it discovers every `application.yaml` beneath it and creates
those child `Application` resources automatically. Adding a child app therefore requires no
change to the root — only a new conditional directory.
## 4. Numeric band convention
Every child directory and its ArgoCD `Application` name carry a three-digit prefix:
| Band | Domain | Examples |
|---|---|---|
| 000 | Root app-of-apps | `000-root` |
| 200–299 | Operators | ESO, CNPG, Redis operator |
| 300–399 | Cluster configuration | ClusterSecretStore, namespaces |
| 400–499 | Databases | PostgreSQL cluster, Redis cluster |
| 700–799 | Advantage / MCP workloads | MCP servers, AI agents |
| 800–899 | Platform applications | notification, integration, agent-service |
Sub-groups inside a band take the next free number in that band — e.g. `210-eso-operator-group`,
`213-eso-subscription`, `215-eso-configuration`. Choose the band **before** writing any file
and scan existing directories to confirm the number is free. Never reuse a number across
domains.
## 5. Conditional directories are the feature-flag mechanism
Directory names inside `cluster/` are Jinja2 expressions:
```text
{{ '210-eso-operator-group' if operators.eso_is_enabled }}
```
Copier evaluates the expression at render time. Truthy → the directory renders under that
name. Falsy → **the entire directory tree is omitted**, so neither the ArgoCD app nor its
resources are generated.
This is why manifests in this repo contain almost no Jinja2 logic. Do not use `{% if %}`
blocks to conditionally include whole YAML sections — gate the directory instead. Templates
substitute values; directories decide what exists.
## 6. Non-negotiable rules
These apply to every change regardless of which layer it touches:
- **Never hardcode `repoURL` or `targetRevision`** — always `{{ gitops.repo_url }}` and
`{{ gitops.branch }}`.
- **Never hardcode image address or tag** in a values template — environments must deploy
different versions of the same template.
- **Every `values.yaml.jinja` uses the `{% macro render() %}` blank-line-stripping footer.**
YAML is whitespace-sensitive and unexpanded conditionals leave blank lines that break the
parse. The pattern is mandatory, not stylistic — see `references/copier-templates.md`.
- **Use `.get()` chains with defaults** for any nested variable that may be absent in some
environment: `{{ applications.get('svc', {}).get('replicas', 1) }}`. Direct dot access on
an optional key raises `UndefinedError` and fails the render for every environment.
- **New feature flags default to `false`** in `copier.yaml` — resources are opt-in per
environment.
- **No secrets in Git.** Not in `.jinja` templates, not in `values.yaml.jinja`, not in
`copier-answers.yaml`. Use placeholders and inject at runtime via ExternalSecret /
ClusterSecretStore.
- **Feature flags are YAML booleans** (`true`/`false`) in answer files. Helm string-booleans
(`is_internal`, `maintenance`) are quoted strings (`"true"`/`"false"`). These are different
things — see `references/environments.md`.
## 7. Adding a new resource — the short path
Full walkthrough with copy-paste templates: `references/adding-a-resource.md`.
Starting templates: `assets/example-cluster-resource/`.
1. Pick the band, the next free number, the kebab-case name, and the flag variable name.
2. Declare the flag (`type: bool`, `default: false`) and its config variables in `copier.yaml`.
3. Create `cluster/argocd/{{ 'NNN-name' if <flag> }}/application.yaml.jinja`.
4. Create `cluster/resources/{{ 'NNN-name' if <flag> }}/values.yaml.jinja` — identical expression.
5. Enable the flag and supply image/config values in the answer file of each target environment.
6. Render with the flag off **and** on, and validate both (section 8).
## 8. Validation
Never commit a template change without rendering it. A broken template blocks every
environment that renders from the repo, not just the one you were working on.
```bash
# Render with defaults — new resource must NOT appear (flag defaults to false)
copier copy . /tmp/gitops-off --overwrite --defaults
ls /tmp/gitops-off/cluster/argocd/ | grep "<NNN>" # expect no output
# Render with the resource enabled
copier copy . /tmp/gitops-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-on/cluster/argocd/ | grep "<NNN>" # expect the directory
# Render a real environment
copier copy . /tmp/gitops-env --overwrite --answers-file <env>/copier-answers.yaml
# No .jinja may leak into rendered output
find /tmp/gitops-env -name "*.jinja" | wc -l # must be 0
# Every rendered YAML must parse
find /tmp/gitops-env -name "*.yaml" -exec python3 -c \
"import yaml, sys; yaml.safe_load(open(sys.argv[1]))" {} \;
# Every Application's source.path must exist under the rendered tree
for app in $(find /tmp/gitops-env/cluster/argocd -name "application.yaml" -exec \
python3 -c "import yaml,sys; print(yaml.safe_load(open(sys.argv[1]))['spec']['source']['path'])" {} \;); do
[ -d "/tmp/gitops-env/$app" ] || echo "MISSING resources dir: $app"
done
```
Jinja2 syntax check without rendering (fast feedback while editing):
```bash
python3 -c "
from jinja2 import Environment
import pathlib
env = Environment()
for f in pathlib.Path('cluster').rglob('*.jinja'):
env.parse(f.read_text()); print('OK:', f)
"
```
Helm-lint the rendered values against the chart when a chart path is available:
```bash
helm lint <chart-path> -f /tmp/gitops-on/cluster/resources/<NNN-name>/values.yaml
```
## 9. Consistency checklist
Before committing any resource addition or rename:
- [ ] 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` equals the directory name exactly.
- [ ] `spec.source.path` equals the `cluster/resources/` directory name exactly.
- [ ] The conditional expressions in `cluster/argocd/` and `cluster/resources/` are identical.
- [ ] `repoURL` and `targetRevision` use Jinja2 variables.
- [ ] `resources-finalizer.argocd.argoproj.io` is present in `metadata.finalizers`.
- [ ] `values.yaml.jinja` carries the `render()` macro and stripping footer.
- [ ] All optional variable references use `.get()` with a default.
- [ ] No secrets in any new or edited file.
- [ ] At least one environment's answer file enables and fully configures the resource.
- [ ] Rendered flag-off and flag-on, both validated.
## 10. Top mistakes to avoid
- Creating the ArgoCD app without the mirrored `cluster/resources/` directory — ArgoCD errors
with path-not-found.
- Using different conditional expressions on the two sides — one renders, the other vanishes.
- Writing a raw `application.yaml` instead of `application.yaml.jinja`.
- Omitting the `resources-finalizer.argocd.argoproj.io` finalizer — deleting the app leaves
orphaned cluster resources behind.
- Disabling `selfHeal` or `prune` without documenting why — it silently defeats the GitOps
contract.
- Enabling a flag in an answer file without supplying the required image/config variables —
the rendered values file has empty fields and Helm fails at sync.
- Editing `_src_path` or `_commit` by hand in an answer file — both are Copier metadata and
wrong values break `copier update`.
- Skipping the render-and-diff step after editing an answer file — drift between answers and
rendered output produces unexplained cluster state.