New skill files: - clean-code.instructions.md — naming, functions, classes, error handling, formatting, DRY/KISS/SOLID, Helm YAML conventions - clean-architecture.instructions.md — dependency rule, layers, boundaries, SOLID foundation, Helm as outermost layer - helm.instructions.md — resource ownership, values hygiene, required vs defaults, umbrella chart pattern, two-file values layering, KISS principle, hook ordering, config files pattern, dependency caching, template testing Register all three in ai-root-instructions.md skills list and routing table. Remove .ai from .gitignore — .ai/ is the product in this repo and must be tracked; the apply.sh skip-by-marker mechanism prevents changes to other repos.
219 lines
6.7 KiB
Markdown
219 lines
6.7 KiB
Markdown
# Helm Chart Development Guidelines
|
||
|
||
## 🔍 Before Creating Anything New
|
||
|
||
**ALWAYS search the workspace for existing implementations first.**
|
||
|
||
Before writing a new template, config, or resource:
|
||
1. Search the workspace for similar existing files
|
||
2. If found, migrate/adapt it — don't invent a new structure
|
||
3. Only create from scratch if nothing exists
|
||
|
||
---
|
||
|
||
## 🏠 Resource Ownership
|
||
|
||
**Every Kubernetes resource must have exactly one owner. Never duplicate.**
|
||
|
||
- If a chart creates a resource (e.g. Traefik creates IngressClass), don't create it elsewhere
|
||
- Before adding a template for a resource, verify no other chart/subchart already manages it
|
||
|
||
```
|
||
❌ Wrong: subchart A creates IngressClass AND subchart B creates IngressClass
|
||
✅ Right: one chart owns IngressClass, others do not touch it
|
||
```
|
||
|
||
---
|
||
|
||
## 🧹 values.yaml Hygiene
|
||
|
||
**values.yaml must only contain values that templates actually use.**
|
||
|
||
Before adding a value:
|
||
- Verify at least one template references `.Values.<key>`
|
||
- If no template uses it → don't add it
|
||
- Empty string defaults (`key: ""`) that only serve as documentation are forbidden — use README instead
|
||
|
||
**Umbrella chart values.yaml:**
|
||
- Contains only subchart defaults that override upstream chart defaults
|
||
- All environment-specific values belong in the deployment repo
|
||
- Must NOT contain empty placeholders
|
||
|
||
**Subchart values.yaml:**
|
||
- Contains only values the subchart's own templates reference
|
||
- Required values (validated with `required`) must NOT have empty defaults — absence should trigger the error
|
||
|
||
---
|
||
|
||
## ✅ required vs defaults
|
||
|
||
Use `required` for values that MUST come from the deployer. Do NOT put empty defaults in values.yaml for these.
|
||
|
||
```yaml
|
||
# Good: fails loudly at install if not provided
|
||
name: {{ required "ingress.className is required" .Values.ingress.className }}
|
||
|
||
# Bad: silently passes empty string, fails later in obscure way
|
||
name: {{ .Values.ingress.className | default "" }}
|
||
```
|
||
|
||
---
|
||
|
||
## 📦 Subchart Dependency Conditions
|
||
|
||
Use `condition:` in `Chart.yaml` to enable/disable components. Use flat booleans, not nested `.enabled`:
|
||
|
||
```yaml
|
||
# Chart.yaml
|
||
dependencies:
|
||
- name: keycloak
|
||
repository: "file://../components/keycloak"
|
||
version: "0.2.0"
|
||
condition: components.keycloak
|
||
|
||
# values (Good)
|
||
components:
|
||
keycloak: true
|
||
traefik: false
|
||
|
||
# values (Avoid)
|
||
keycloak:
|
||
enabled: true
|
||
```
|
||
|
||
---
|
||
|
||
## ⏱️ Hook Ordering
|
||
|
||
Resources that depend on other resources must use `helm.sh/hook` with `hook-weight` to ensure correct install order.
|
||
|
||
Established ordering convention:
|
||
- `hook-weight: "10"` — infrastructure prerequisites (e.g. ClusterIssuer, IngressClass)
|
||
- `hook-weight: "15"` — resources that depend on prerequisites (e.g. Certificate)
|
||
|
||
```yaml
|
||
metadata:
|
||
annotations:
|
||
"helm.sh/hook": post-install,post-upgrade
|
||
"helm.sh/hook-weight": "10"
|
||
```
|
||
|
||
---
|
||
|
||
## 📄 Configuration Files over values.yaml
|
||
|
||
**Prefer `files/` + ConfigMap over defining configuration in values.yaml.**
|
||
|
||
When a component needs configuration (CASC, realm config, ini files, etc.):
|
||
1. Put the config file in `files/` directory
|
||
2. Create a ConfigMap that reads it with `.AsConfig` + `tpl`
|
||
3. Use `{{ .Values.global.xxx }}` expressions inside the config file for dynamic values
|
||
|
||
```yaml
|
||
# templates/configmap.yaml
|
||
kind: ConfigMap
|
||
apiVersion: v1
|
||
metadata:
|
||
name: my-component-config
|
||
data: {{ (tpl (.Files.Glob "files/*").AsConfig . ) | nindent 2 }}
|
||
```
|
||
|
||
```yaml
|
||
# files/config.yaml — contains Helm template expressions
|
||
server:
|
||
url: https://{{ .Values.global.domain }}
|
||
env: {{ .Values.global.environment }}
|
||
```
|
||
|
||
**Why:** Config files are readable, diffable, and version-controlled as real files.
|
||
Large configs in values.yaml become unmaintainable and hard to review.
|
||
|
||
**When `.AsConfig` + `tpl` is required:**
|
||
- Config file contains `{{ }}` template expressions → always use `.AsConfig` + `tpl`
|
||
- Config file contains special characters (`*`, `{`, `}`) → `.AsConfig` handles escaping safely
|
||
|
||
```yaml
|
||
# Never use plain Files.Get on files with template expressions or special chars:
|
||
{{ tpl (.Files.Get "files/config.json") . }} # ❌ breaks on * characters
|
||
|
||
# Always use AsConfig:
|
||
{{ tpl (.Files.Glob "files/config.json").AsConfig . }} # ✅
|
||
```
|
||
|
||
---
|
||
|
||
## 🔄 Dependency Caching
|
||
|
||
After editing any subchart template or values, cached charts in `charts/` will be stale:
|
||
|
||
```bash
|
||
rm -rf charts/ Chart.lock
|
||
helm dependency update .
|
||
```
|
||
|
||
Always rebuild before `helm template` testing when subchart files have changed.
|
||
|
||
---
|
||
|
||
## 🎂 Umbrella Chart Pattern
|
||
|
||
The standard structure is an umbrella chart that integrates multiple component charts.
|
||
|
||
```
|
||
my-umbrella/
|
||
├── Chart.yaml ← declares component charts as dependencies
|
||
├── values.yaml ← static baseline: shared across all installations
|
||
├── templates/ ← only cross-cutting resources that no subchart owns
|
||
└── charts/ ← built by helm dependency update
|
||
```
|
||
|
||
**Two-file values layering:**
|
||
|
||
| File | Where it lives | Purpose |
|
||
|---|---|---|
|
||
| `values.yaml` | umbrella chart repo | static baseline, same across all IAC installations |
|
||
| `values.<installation>.yaml` | IAC / deployment repo | installation-specific overrides and required parameters |
|
||
|
||
- `values.yaml` contains defaults valid for every installation; it never contains installation-specific data
|
||
- `values.<installation>.yaml` provides what that specific installation requires — credentials, hostnames, sizing, feature flags
|
||
- Required values (no sensible default) use `required` in templates and have **no entry** in `values.yaml` — their absence triggers a clear error at install time
|
||
|
||
---
|
||
|
||
## ⚡ KISS — Start Minimal, Add Only When Asked
|
||
|
||
**Do not add knobs, flags, or options that aren’t needed right now.**
|
||
|
||
- Build the minimum that meets the stated functional requirement
|
||
- Do not anticipate future needs with extra values, conditions, or template logic
|
||
- Complexity is easy to add; it is hard to remove once deployed
|
||
- AI may **suggest** what might be needed later — but must not implement it without explicit instruction
|
||
|
||
```yaml
|
||
# ❌ Wrong: adding a flag “just in case”
|
||
features:
|
||
enableFutureThing: false # not needed yet, adding anyway
|
||
|
||
# ✅ Right: don’t add it. If it’s needed, the developer will ask.
|
||
```
|
||
|
||
**When in doubt, leave it out.** A value that exists but is never used is noise. A template condition that is never toggled is dead code. Raise the question with the developer instead of silently implementing it.
|
||
|
||
---
|
||
|
||
## 🧪 Testing Templates
|
||
|
||
Standard verification command:
|
||
|
||
```bash
|
||
helm template <release-name> . \
|
||
--set global.key=value \
|
||
... \
|
||
2>&1 | grep "^kind:" | sort | uniq -c
|
||
```
|
||
|
||
Verify:
|
||
- Expected resource kinds appear
|
||
- No `Error:` lines
|
||
- `required` validation fires when values are missing
|