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,213 @@
|
||||
# Helm Values Templates
|
||||
|
||||
Everything under `cluster/resources/**/values.yaml.jinja`: image wiring, the `global` block,
|
||||
per-service sections, environment variables, probes, and replica counts.
|
||||
|
||||
Read this when creating or editing a values template, or when rendered `values.yaml` produces
|
||||
invalid YAML or unexpected Helm behaviour.
|
||||
|
||||
---
|
||||
|
||||
## 1. Dual-layer rendering
|
||||
|
||||
Every `values.yaml.jinja` is rendered **twice**:
|
||||
|
||||
1. **By Copier** — substitutes `{{ variable }}` from the answer file, producing `values.yaml`.
|
||||
2. **By Helm** — ArgoCD passes that `values.yaml` to `helm template` at sync time.
|
||||
|
||||
The file must therefore be valid Jinja2 *and* produce valid Helm values afterwards. A
|
||||
mistake in layer 1 fails the render; a mistake in layer 2 fails the sync, potentially hours
|
||||
later. Validate both (section 6).
|
||||
|
||||
---
|
||||
|
||||
## 2. Two categories of values file
|
||||
|
||||
| Category | Shape | Used for |
|
||||
|---|---|---|
|
||||
| Operator / config resources | Simple YAML, minimal templating | CRD instances — OperatorGroup, Subscription, ClusterSecretStore |
|
||||
| Application workloads | Full Helm values with `global` + per-service sections | Platform services, MCP servers, notification, integration |
|
||||
|
||||
Both use the same `render()` macro pattern. Do not mix the `global` shape and the standalone
|
||||
shape inside one file — pick the one matching the chart and follow it consistently.
|
||||
|
||||
---
|
||||
|
||||
## 3. Minimal structure (every file)
|
||||
|
||||
```jinja
|
||||
{% macro render() %}
|
||||
# --- values content here ---
|
||||
key: {{ domain.get('sub', {}).get('field', '') }}
|
||||
{% endmacro %}
|
||||
{%- set result = render() -%}
|
||||
{%- for line in result.split('\n') -%}
|
||||
{% if line | trim | length != 0 -%}
|
||||
{{ line }}
|
||||
{% endif -%}
|
||||
{%- endfor %}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 4. Shared-chart workload structure
|
||||
|
||||
Services that share one Helm release read cluster-wide settings from a `global` block:
|
||||
|
||||
```yaml
|
||||
global:
|
||||
is_internal: "{{ applications.get('global', {}).get('is_internal', '') }}"
|
||||
ca_env: {{ applications.get('global', {}).get('ca_env', '') }}
|
||||
ca_secret_name: {{ applications.get('global', {}).get('ca_secret_name', '') }}
|
||||
host: {{ applications.get('global', {}).get('host', '') }}
|
||||
ca_host: {{ applications.get('global', {}).get('ca_host', '') }}
|
||||
suffix_url: {{ applications.get('global', {}).get('suffix_url', '') }}
|
||||
maintenance: "{{ applications.get('global', {}).get('maintenance', 'false') }}"
|
||||
node_selector_app: "{{ applications.get('global', {}).get('node_selector_app', '') }}"
|
||||
mongo:
|
||||
host: {{ applications.get('global', {}).get('mongo', {}).get('host', '') }}
|
||||
port: {{ applications.get('global', {}).get('mongo', {}).get('port', '') }}
|
||||
user: {{ applications.get('global', {}).get('mongo', {}).get('user', '') }}
|
||||
|
||||
network:
|
||||
routes: []
|
||||
|
||||
<service-name>:
|
||||
enabled: true
|
||||
image:
|
||||
name: {{ applications.get('<service-name>', {}).get('image', {}).get('address', '') }}
|
||||
tag: {{ applications.get('<service-name>', {}).get('image', {}).get('tag', '') }}
|
||||
replicas: {{ applications.get('<service-name>', {}).get('replicas', 1) }}
|
||||
```
|
||||
|
||||
`global` goes at the top of the file. All `global` sub-keys use the established names — do
|
||||
not introduce synonyms (`hostname` for `host`, `env` for `ca_env`); the chart templates read
|
||||
the exact keys.
|
||||
|
||||
---
|
||||
|
||||
## 5. Standalone workload structure
|
||||
|
||||
Charts that do not share a `global` block use the standard Helm shape:
|
||||
|
||||
```yaml
|
||||
replicaCount: 1
|
||||
|
||||
image:
|
||||
repository: {{ domain.get('service', {}).get('image', {}).get('address', '') }}
|
||||
tag: {{ domain.get('service', {}).get('image', {}).get('tag', '') }}
|
||||
pullPolicy: Always
|
||||
|
||||
imagePullSecrets:
|
||||
- name: "storage-images"
|
||||
|
||||
service:
|
||||
type: ClusterIP
|
||||
port: 80
|
||||
|
||||
livenessProbe:
|
||||
httpGet:
|
||||
path: /health
|
||||
port: <app-port>
|
||||
periodSeconds: 60
|
||||
|
||||
readinessProbe:
|
||||
httpGet:
|
||||
path: /health
|
||||
port: <app-port>
|
||||
periodSeconds: 60
|
||||
|
||||
env:
|
||||
targetPort: <app-port>
|
||||
<env_var>: {{ domain.get('service', {}).get('env', {}).get('<env_var>', '') }}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 6. Design rules
|
||||
|
||||
### Images
|
||||
|
||||
- Always wire address and tag from Copier variables; never hardcode image coordinates.
|
||||
- Use `.get()` with an empty-string default for every image field.
|
||||
- `pullPolicy: Always` for mutable tags (`latest`, environment-specific). `IfNotPresent`
|
||||
only for immutable digest-pinned images.
|
||||
- Private registries need `imagePullSecrets` referencing the pre-existing secret — commonly
|
||||
`storage-images`.
|
||||
|
||||
### Booleans
|
||||
|
||||
Booleans passed into `global` (`is_internal`, `maintenance`, `enable_instana`) are **quoted
|
||||
strings** because the chart templates treat them as strings. This is deliberate and differs
|
||||
from Copier feature flags, which are real YAML booleans. Keep the two straight: flags gate
|
||||
rendering, these gate chart behaviour.
|
||||
|
||||
### Environment variables
|
||||
|
||||
- Map every env var from a Copier variable via `.get()`; never hardcode.
|
||||
- Group them under an `env:` key in the service's section.
|
||||
- Document *which* env vars are required, never their values — secrets come from an external
|
||||
secret store, not from a values file.
|
||||
|
||||
### Probes
|
||||
|
||||
- Every application workload configures both `livenessProbe` and `readinessProbe`.
|
||||
- Prefer `httpGet` on `/health` where the app exposes it.
|
||||
- `periodSeconds: 60` is the default; change only when startup or health cadence demands it.
|
||||
|
||||
### Replicas
|
||||
|
||||
- Default to `1` via the Copier variable: `replicas: {{ domain.get('svc', {}).get('replicas', 1) }}`.
|
||||
- Never hardcode — environments must scale independently.
|
||||
|
||||
### Blank-line stripping
|
||||
|
||||
- Wrap the full body in `{% macro render() %}` … `{% endmacro %}`.
|
||||
- Always append the four-line stripping footer.
|
||||
- Do not rely on `-` whitespace control inside the macro body for the same purpose.
|
||||
|
||||
---
|
||||
|
||||
## 7. Adding a new values file
|
||||
|
||||
1. Create it at `cluster/resources/{{ '<NNN-name>' if <flag> }}/values.yaml.jinja` — the
|
||||
expression identical to the `cluster/argocd/` side.
|
||||
2. Open with `{% macro render() %}`.
|
||||
3. Write the values body, sourcing every variable through a `.get()` chain.
|
||||
4. Close with `{% endmacro %}` plus the stripping footer.
|
||||
5. Confirm every referenced variable is declared in `copier.yaml`.
|
||||
6. Render and validate.
|
||||
|
||||
A starting template is in `assets/example-cluster-resource/resources/values.yaml.jinja`.
|
||||
|
||||
---
|
||||
|
||||
## 8. Validation
|
||||
|
||||
```bash
|
||||
# Render (Jinja2 layer)
|
||||
copier copy . /tmp/gitops-render --overwrite --defaults
|
||||
|
||||
# Parse the rendered values (YAML layer)
|
||||
python3 -c "import yaml; yaml.safe_load(open('/tmp/gitops-render/cluster/resources/<NNN-name>/values.yaml'))"
|
||||
|
||||
# Lint against the chart (Helm layer)
|
||||
helm lint <path-to-chart> -f /tmp/gitops-render/cluster/resources/<NNN-name>/values.yaml
|
||||
|
||||
# See what Helm will actually produce
|
||||
helm template <release> <path-to-chart> \
|
||||
-f /tmp/gitops-render/cluster/resources/<NNN-name>/values.yaml
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 9. Mistakes to avoid
|
||||
|
||||
- Omitting the `render()` wrapper — blank lines from unexpanded blocks produce invalid YAML.
|
||||
- Hardcoding image tags, so every environment deploys the same version.
|
||||
- Putting secrets in a values file instead of an ExternalSecret / ClusterSecretStore.
|
||||
- Quoting numerics (replicas, ports) unless the chart explicitly expects a string.
|
||||
- Mixing the `global` pattern and the standalone pattern in one file.
|
||||
- Forgetting `imagePullSecrets` for a private-registry image — pods stall on `ImagePullBackOff`.
|
||||
- Renaming a `global` sub-key to a synonym the chart does not read — the value silently
|
||||
becomes empty rather than erroring.
|
||||
Reference in New Issue
Block a user