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:
@@ -0,0 +1,195 @@
|
||||
# Adding a Cluster Resource End-to-End
|
||||
|
||||
The integration walkthrough: one atomic change touching all three layers — Copier schema,
|
||||
ArgoCD Application, Helm values — plus the environment answer file.
|
||||
|
||||
Read this when onboarding a brand-new operator, database, MCP component, or workload. For an
|
||||
isolated edit to one layer, read that layer's reference instead
|
||||
(`argocd-applications.md`, `copier-templates.md`, `helm-values.md`, `environments.md`).
|
||||
|
||||
Starting templates: `assets/example-cluster-resource/`.
|
||||
|
||||
---
|
||||
|
||||
## Step 1 — Choose number, name, and flag
|
||||
|
||||
```text
|
||||
Band: from the table in SKILL.md §4
|
||||
Number: next available in that band
|
||||
Name: <NNN>-<kebab-case-descriptor>
|
||||
Flag: <domain>.<descriptor>_is_enabled (type: bool, default: false)
|
||||
```
|
||||
|
||||
Confirm the number is free before writing anything:
|
||||
|
||||
```bash
|
||||
ls cluster/argocd/ cluster/resources/ | grep -o '[0-9]\{3\}' | sort -u
|
||||
```
|
||||
|
||||
The directory names are Jinja2 expressions, so the numbers are still visible in a plain
|
||||
listing — no rendering needed for this check.
|
||||
|
||||
---
|
||||
|
||||
## Step 2 — Declare variables in `copier.yaml`
|
||||
|
||||
```yaml
|
||||
<domain>:
|
||||
<descriptor>_is_enabled:
|
||||
type: bool
|
||||
default: false
|
||||
help: "Enable <NNN>-<name>"
|
||||
|
||||
<descriptor>:
|
||||
image:
|
||||
address:
|
||||
type: str
|
||||
default: ""
|
||||
help: "Container image for <name>"
|
||||
tag:
|
||||
type: str
|
||||
default: ""
|
||||
help: "Container image tag for <name>"
|
||||
# additional variables as the resource requires
|
||||
```
|
||||
|
||||
The flag defaults to `false` so existing environments are unaffected until they opt in.
|
||||
|
||||
---
|
||||
|
||||
## Step 3 — Create the ArgoCD Application
|
||||
|
||||
`cluster/argocd/{{ '<NNN>-<name>' if <domain>.<descriptor>_is_enabled }}/application.yaml.jinja`
|
||||
|
||||
```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
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Step 4 — Create the Helm values template
|
||||
|
||||
`cluster/resources/{{ '<NNN>-<name>' if <domain>.<descriptor>_is_enabled }}/values.yaml.jinja`
|
||||
|
||||
The conditional expression must be **character-identical** to step 3.
|
||||
|
||||
```jinja
|
||||
{% macro render() %}
|
||||
# values for <NNN>-<name>
|
||||
replicaCount: 1
|
||||
|
||||
image:
|
||||
repository: {{ <domain>.get('<descriptor>', {}).get('image', {}).get('address', '') }}
|
||||
tag: {{ <domain>.get('<descriptor>', {}).get('image', {}).get('tag', '') }}
|
||||
pullPolicy: Always
|
||||
|
||||
# ... remaining values
|
||||
{% endmacro %}
|
||||
{%- set result = render() -%}
|
||||
{%- for line in result.split('\n') -%}
|
||||
{% if line | trim | length != 0 -%}
|
||||
{{ line }}
|
||||
{% endif -%}
|
||||
{%- endfor %}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Step 5 — Update environment answer files
|
||||
|
||||
For each environment that should run this resource:
|
||||
|
||||
```yaml
|
||||
<domain>:
|
||||
<descriptor>_is_enabled: true
|
||||
<descriptor>:
|
||||
image:
|
||||
address: <registry>/<image>
|
||||
tag: <version>
|
||||
```
|
||||
|
||||
Enable it in one environment first — typically the lowest — and promote after it syncs
|
||||
cleanly. Enabling everywhere in the same commit means a template bug reaches production in
|
||||
the same sync cycle.
|
||||
|
||||
---
|
||||
|
||||
## Step 6 — Validate both states
|
||||
|
||||
```bash
|
||||
# Flag off (defaults): the directories must NOT appear
|
||||
copier copy . /tmp/gitops-render-off --overwrite --defaults
|
||||
ls /tmp/gitops-render-off/cluster/argocd/ | grep "<NNN>" # expect no output
|
||||
|
||||
# Flag on: the directories must appear
|
||||
copier copy . /tmp/gitops-render-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-render-on/cluster/argocd/ | grep "<NNN>"
|
||||
|
||||
# Both rendered files must parse
|
||||
python3 -c "import yaml; yaml.safe_load(open('/tmp/gitops-render-on/cluster/argocd/<NNN>-<name>/application.yaml'))"
|
||||
python3 -c "import yaml; yaml.safe_load(open('/tmp/gitops-render-on/cluster/resources/<NNN>-<name>/values.yaml'))"
|
||||
|
||||
# Helm lint, when a chart path is available
|
||||
helm lint <chart-path> -f /tmp/gitops-render-on/cluster/resources/<NNN>-<name>/values.yaml
|
||||
```
|
||||
|
||||
Testing the **off** state matters as much as the on state: a malformed conditional
|
||||
expression can render the directory unconditionally, silently deploying the new resource to
|
||||
every environment on the next sync.
|
||||
|
||||
---
|
||||
|
||||
## Step 7 — Consistency checklist
|
||||
|
||||
- [ ] 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` matches the directory name exactly.
|
||||
- [ ] `spec.source.path` matches the `cluster/resources/` directory name exactly.
|
||||
- [ ] The conditional expressions in `cluster/argocd/` and `cluster/resources/` are identical.
|
||||
- [ ] `repoURL` and `targetRevision` use Jinja2 variables, not literals.
|
||||
- [ ] `resources-finalizer.argocd.argoproj.io` is present in `metadata.finalizers`.
|
||||
- [ ] `values.yaml.jinja` uses the `render()` macro and stripping footer.
|
||||
- [ ] All variable references use `.get()` with a type-appropriate default.
|
||||
- [ ] No secrets or credentials in any new file.
|
||||
- [ ] At least one environment's answer file enables and fully configures the resource.
|
||||
- [ ] Rendered and validated with the flag both off and on.
|
||||
|
||||
---
|
||||
|
||||
## Mistakes to avoid
|
||||
|
||||
- Creating the ArgoCD Application without the matching `cluster/resources/` directory —
|
||||
ArgoCD fails with path-not-found.
|
||||
- Different conditional expressions on the two sides — one renders, the other is omitted.
|
||||
- Referencing a variable that was never declared in `copier.yaml` — the render fails for
|
||||
every environment, not just yours.
|
||||
- Hardcoding image address or tag in `values.yaml.jinja`.
|
||||
- Setting the new flag `default: true` — new resources are opt-in.
|
||||
- Committing without rendering; a broken template blocks every environment.
|
||||
Reference in New Issue
Block a user