# 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: ```yaml apiVersion: argoproj.io/v1alpha1 kind: Application metadata: name: namespace: openshift-gitops finalizers: - resources-finalizer.argocd.argoproj.io spec: project: default source: repoURL: {{ gitops.repo_url }} targetRevision: {{ gitops.branch }} path: cluster/resources/ helm: valueFiles: - values.yaml destination: server: https://kubernetes.default.svc 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 means `kubectl edit` silently 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.retry` only 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 }}` — no exceptions; an unconditional directory renders in every environment whether or not it is wanted. --- ## 4. Adding a child application 1. Choose the band and the next free number in it. 2. Create the conditional directory: ```text cluster/argocd/{{ '' if }}/application.yaml.jinja ``` 3. Create the mirror: ```text cluster/resources/{{ '' if }}/values.yaml.jinja ``` 4. Write the manifest from the anatomy in section 1. 5. Declare `` in `copier.yaml` with `type: bool`, `default: false`. 6. Add the resource's configuration variables alongside the flag. 7. 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: ```bash argocd app list argocd app get argocd app diff kubectl -n openshift-gitops get applications.argoproj.io ``` --- ## 6. Mistakes to avoid - Hardcoding `repoURL` or `targetRevision` instead of the Jinja2 variables. - Omitting the finalizer. - Creating the argocd side without the mirrored resources side. - Reusing a numeric prefix across domains (a `3xx` number for a database app). - Writing `application.yaml` without the `.jinja` extension. - Disabling `selfHeal` or `prune` silently. - Letting `metadata.name` drift from the directory name after a rename.