16 KiB
name, description, license, metadata, compatibility
| name | description | license | metadata | compatibility | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|
| development-gitops-argo-cd-3darch | Create, debug, and maintain GitOps repositories where ArgoCD syncs cluster state from a Copier-templated Git repo — app-of-apps Application manifests under cluster/argocd/, Helm values.yaml.jinja under cluster/resources/, Jinja2 conditional directory names used as feature flags, copier.yaml schemas, per-environment copier-answers.yaml — and connect the rendered repo to a live cluster. Use when adding an operator, database, or workload; editing sync policies, finalizers, or destination namespaces; wiring image address/tag through Copier variables into Helm values; toggling a feature flag for one environment; running copier update; registering the repository Secret, applying the AppProject and app-of-apps bootstrap, trusting the git host key, or granting SSO read access; or debugging an app stuck OutOfSync, a failed render, or blank lines that break rendered YAML — even when the user only names their repository (e.g. CogArchHubGitOps) instead of saying "GitOps" or "ArgoCD". | Proprietary |
|
Designed for Cline, Claude Code, GitHub Copilot, OpenAI Codex, and other compatible agent environments |
ArgoCD GitOps on a Copier Template Repository
3D Architecture Wizzard Project Adoption
This is the primary project-adopted skill for 3D Architecture Wizzard (3darch).
- Central source: https://gitea.lego-cloud.eu/home-v1-skills-code-agent/development-gitops-argo-cd
- Source branch:
test - Source commit:
510b2bb590118362804d6036cbbf37db72dc21d0 - Adopted repository: https://gitea.lego-cloud.eu/corp-v1-3darch-skills-code-agent/development-gitops-argo-cd-3darch
- Application: https://gitea.lego-cloud.eu/corp-v1-3darch/corp-v1-3darch
- Documentation: https://gitea.lego-cloud.eu/corp-v1-3darch/corp-v1-3darch-documentation
- Environment namespace:
CORP_V1_3DARCH_* - Global diagram skill: https://gitea.lego-cloud.eu/home-v1-skills-code-agent/diagrams-drawio
- Global glossary skill: https://gitea.lego-cloud.eu/home-v1-skills-code-agent/corp-v1-glossary
- Project automation: none; no scheduler job is authorized.
Project engineering peers:
development-branching-strategy-3darch— https://gitea.lego-cloud.eu/corp-v1-3darch-skills-code-agent/development-branching-strategy-3darchdevelopment-gitops-argo-cd-3darch— https://gitea.lego-cloud.eu/corp-v1-3darch-skills-code-agent/development-gitops-argo-cd-3darchdevelopment-monorepo-pnpm-3darch— https://gitea.lego-cloud.eu/corp-v1-3darch-skills-code-agent/development-monorepo-pnpm-3darchdevelopment-scripts-3darch— https://gitea.lego-cloud.eu/corp-v1-3darch-skills-code-agent/development-scripts-3darchdevsecops-ci-cd-gitea-3darch— https://gitea.lego-cloud.eu/corp-v1-3darch-skills-code-agent/devsecops-ci-cd-gitea-3darchdocumentation-docusaurus-3darch— https://gitea.lego-cloud.eu/corp-v1-3darch-skills-code-agent/documentation-docusaurus-3darchtemplate-engine-copier-3darch— https://gitea.lego-cloud.eu/corp-v1-3darch-skills-code-agent/template-engine-copier-3darch
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.
Two distinct jobs live in this skill. Authoring — sections 1–10 — produces the repository:
templates, flags, values, all of it files in git. Operating — section 11 — hands a
rendered repository to a live cluster: repository credentials, bootstrap manifests, RBAC. The
first never touches a cluster; the second never edits a template. If you are reaching for
kubectl, you are in section 11.
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 |
| Connecting a rendered repo to a cluster — repo Secret, bootstrap apply, host key, SSO RBAC | references/connecting-a-repo.md |
| Variables and flags for the connect operations | references/connect-env-vars.md |
| CogArchHubGitOps specifically — bands in use, flag names, global settings | references/cogarchhubgitops-reference.md |
1. Canonical repository shape
<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:
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:
{{ '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
repoURLortargetRevision— 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.jinjauses 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 — seereferences/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 raisesUndefinedErrorand fails the render for every environment. - New feature flags default to
falseincopier.yaml— resources are opt-in per environment. - No secrets in Git. Not in
.jinjatemplates, not invalues.yaml.jinja, not incopier-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 — seereferences/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/.
- Pick the band, the next free number, the kebab-case name, and the flag variable name.
- Declare the flag (
type: bool,default: false) and its config variables incopier.yaml. - Create
cluster/argocd/{{ 'NNN-name' if <flag> }}/application.yaml.jinja. - Create
cluster/resources/{{ 'NNN-name' if <flag> }}/values.yaml.jinja— identical expression. - Enable the flag and supply image/config values in the answer file of each target environment.
- 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.
# 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):
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:
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.yamlwithtype: boolanddefault: false. - The ArgoCD
metadata.nameequals the directory name exactly. spec.source.pathequals thecluster/resources/directory name exactly.- The conditional expressions in
cluster/argocd/andcluster/resources/are identical. repoURLandtargetRevisionuse Jinja2 variables.resources-finalizer.argocd.argoproj.iois present inmetadata.finalizers.values.yaml.jinjacarries therender()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.yamlinstead ofapplication.yaml.jinja. - Omitting the
resources-finalizer.argocd.argoproj.iofinalizer — deleting the app leaves orphaned cluster resources behind. - Disabling
selfHealorprunewithout 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_pathor_commitby hand in an answer file — both are Copier metadata and wrong values breakcopier update. - Skipping the render-and-diff step after editing an answer file — drift between answers and rendered output produces unexplained cluster state.
11. Connecting the repo to a cluster
Authoring stops at a rendered repository. Getting ArgoCD to read it is a separate,
cluster-side job, driven by the .scripts/argo-cd/ shell module:
task argo-cd:0000-trust-repo-host # once per git host — SSH known-hosts
task argo-cd:0100-connect-one # preflight → repository Secret → bootstrap apply
task argo-cd:0200-grant-sso-readonly # SSO users can see Applications
Full walkthrough, decision tables, and verification commands:
references/connecting-a-repo.md. Variables and flags: references/connect-env-vars.md.
Three things about this half are easy to get wrong:
- It assumes ArgoCD is already installed. Preflight only checks that the namespace exists; nothing here installs ArgoCD.
- The repo must be pushed before connecting. The bootstrap manifests are applied from
local disk, but they point ArgoCD at a
repoURLandpathin Git. An unpushed render produces an app-of-apps that cannot find its own path. - Cluster folder names are a contract. The connect scripts reference the rendered
bootstrap directory by name. Renaming cluster folders in the template without updating the
step scripts breaks onboarding — see
references/connecting-a-repo.md§7, which also maps the numberedcluster/0000-bootstraplayout onto thecluster/argocd/layout used above.
Configuration for these operations lives in .env layers, which are tooling inputs.
Never let their values reach the deployed cluster/** manifests.