Files
oleg-lukasonokandClaude Opus 5 50d746d03f 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>
2026-07-29 23:48:32 +03:00

144 lines
5.7 KiB
Markdown

# 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: <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 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 <feature_flag> }}` — 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/{{ '<NNN-name>' if <feature_flag> }}/application.yaml.jinja
```
3. Create the mirror:
```text
cluster/resources/{{ '<NNN-name>' if <feature_flag> }}/values.yaml.jinja
```
4. Write the manifest from the anatomy in section 1.
5. Declare `<feature_flag>` 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 <NNN-name>
argocd app diff <NNN-name>
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.