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>
This commit is contained in:
2026-07-29 23:48:32 +03:00
co-authored by Claude Opus 5
parent 89a9cf9320
commit 50d746d03f
11 changed files with 1615 additions and 1 deletions
+252
View File
@@ -0,0 +1,252 @@
---
name: 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.