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>
5.7 KiB
ArgoCD Application Manifests
Everything under cluster/argocd/: the root app-of-apps, numbered child applications, sync
policies, finalizers, and destinations.
Read this when creating a child Application, changing sync behaviour, moving a destination
namespace, or debugging an app that will not sync.
1. Template anatomy
Every cluster/argocd/**/application.yaml.jinja follows this shape:
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
| Field | Rule |
|---|---|
metadata.name |
Exactly equal to the parent directory name (210-eso-operator-group) |
metadata.namespace |
Always the ArgoCD install namespace — openshift-gitops on OpenShift GitOps |
metadata.finalizers |
Must include resources-finalizer.argocd.argoproj.io |
spec.project |
default unless the cluster defines scoped AppProjects |
spec.source.repoURL |
Always {{ gitops.repo_url }} — never a literal URL |
spec.source.targetRevision |
Always {{ gitops.branch }} — never a literal branch |
spec.source.path |
Exactly the mirrored cluster/resources/ path |
spec.source.helm.valueFiles |
Usually values.yaml, the rendered values.yaml.jinja |
spec.destination.server |
https://kubernetes.default.svc for in-cluster |
spec.destination.namespace |
The workload's target namespace |
Note the asymmetry in spec.source.path: the path value is the plain rendered directory
name (cluster/resources/210-eso-operator-group), while the directory on disk is the
Jinja2 conditional expression. Copier renders the expression away, so the two agree in the
output — but only if the literal you type in path matches what the expression evaluates to.
2. Sync policy rules
automated.prune: true— removes resources no longer present in Git. Required; without it a disabled feature flag leaves its resources running in the cluster forever.automated.selfHeal: true— reverts manual cluster changes. Required; disabling it meanskubectl editsilently wins over Git, which defeats the whole contract.- Any exception to either must be justified in a YAML comment directly above
syncPolicy. syncOptions: [CreateNamespace=true]— set for any app whose destination namespace is not guaranteed to pre-exist. Harmless when the namespace already exists.- Retry settings are optional; add
syncPolicy.retryonly for apps with known flaky dependencies, and document the reason.
3. Naming rules
- Three-digit prefix from the correct band (see
SKILL.md§4), next free number. - Descriptive part in lowercase kebab-case:
210-eso-operator-group. metadata.name== directory name == the value inside the conditional expression.- Wrap the directory in
{{ 'NNN-name' if <feature_flag> }}— no exceptions; an unconditional directory renders in every environment whether or not it is wanted.
4. Adding a child application
-
Choose the band and the next free number in it.
-
Create the conditional directory:
cluster/argocd/{{ '<NNN-name>' if <feature_flag> }}/application.yaml.jinja -
Create the mirror:
cluster/resources/{{ '<NNN-name>' if <feature_flag> }}/values.yaml.jinja -
Write the manifest from the anatomy in section 1.
-
Declare
<feature_flag>incopier.yamlwithtype: bool,default: false. -
Add the resource's configuration variables alongside the flag.
-
Render and validate (
SKILL.md§8).
No edit to the root application.yaml.jinja is needed — the app-of-apps discovers the new
child automatically.
5. Troubleshooting
| Symptom | Likely cause |
|---|---|
| App missing entirely from ArgoCD | Feature flag false in that environment's answer file, or the conditional directory expression is malformed and evaluated falsy |
path does not exist / ComparisonError |
spec.source.path does not match the rendered cluster/resources/ directory, or the mirror directory's conditional expression differs from the argocd one |
Literal {{ gitops.repo_url }} in the live manifest |
File was committed as application.yaml instead of application.yaml.jinja, so Copier copied it through unrendered |
Permanently OutOfSync after a manual cluster edit |
selfHeal disabled — the cluster and Git disagree and nothing reconciles them |
| Resources survive after deleting the app | Missing resources-finalizer.argocd.argoproj.io |
| Resources vanish after a rename | Renaming a directory prunes the old app; expected — confirm the new app synced before assuming breakage |
| Sync fails on namespace not found | Missing CreateNamespace=true in syncOptions |
Inspect the live state:
argocd app list
argocd app get <NNN-name>
argocd app diff <NNN-name>
kubectl -n openshift-gitops get applications.argoproj.io
6. Mistakes to avoid
- Hardcoding
repoURLortargetRevisioninstead of the Jinja2 variables. - Omitting the finalizer.
- Creating the argocd side without the mirrored resources side.
- Reusing a numeric prefix across domains (a
3xxnumber for a database app). - Writing
application.yamlwithout the.jinjaextension. - Disabling
selfHealorprunesilently. - Letting
metadata.namedrift from the directory name after a rename.