diff --git a/hips/hip-0027.md b/hips/hip-0027.md new file mode 100644 index 00000000..0708ed1a --- /dev/null +++ b/hips/hip-0027.md @@ -0,0 +1,199 @@ +--- +hip: 0027 +title: "Expose Previously Installed Chart Metadata During Template Rendering" +authors: ["Andrew Shoell "] +created: "2025-11-12" +type: "feature" +status: "draft" +--- + +## Abstract + +This HIP proposes exposing metadata from currently deployed chart versions during template rendering. Currently, Helm templates have access to `.Chart` for the chart being installed but no equivalent access to deployed releases. This forces chart authors to use complex workarounds like post-renderers, pre-upgrade hooks, or manual values conventions to implement version-aware upgrade logic. + +The proposal introduces `.DeployedChart` (singular, latest deployed) and `.DeployedCharts` (array, multiple versions) objects available in all template contexts, populated during `helm upgrade` and `helm rollback` operations. The `--deployed-depth` flag controls how many deployed versions to retrieve (default: 1), with `--deployed-depth 0` disabling the feature. + +## Motivation + +### Current Limitations + +Helm provides comprehensive chart metadata through `.Chart` but offers no native way to access deployed release metadata during template evaluation. Chart developers must resort to problematic workarounds: + +**Post-Renderers:** External tools that query the cluster, parse manifests, and make version-aware modifications. This moves upgrade logic outside the chart, requires additional tooling, and breaks Helm's self-contained design. + +**Pre-Upgrade Hooks:** Store version metadata in ConfigMaps via hooks, creating ordering dependencies and potential failure points. + +**Manual Values:** Require users to specify previous versions in values files—error-prone and defeats Helm's release tracking. + +### Real-World Impact + +This limitation prevents or complicates legitimate use cases: + +- **Breaking Changes:** No clean migration path for renamed resources or changed structures +- **Conditional Resources:** Cannot create migration Jobs based on version deltas +- **Smart Defaults:** Cannot distinguish fresh installs from upgrades for intelligent defaults +- **Advanced Deployments:** Blue-green and similar strategies require external orchestration + +Post-rendering solutions violate Helm's design philosophy that template rendering should be deterministic and self-contained. Making deployed chart metadata available at template time keeps upgrade logic in the chart itself, maintaining Helm's portability, testability, and transparency. + +## Rationale + +### Naming: `.DeployedChart` and `.DeployedCharts` + +`.DeployedChart` (singular) provides ergonomic access to the most recent deployed version. `.DeployedCharts` (array) provides access to historical deployed versions in reverse chronological order (index 0 is most recent). `.DeployedChart` is syntactic sugar for `.DeployedCharts[0]`. + +Alternatives considered and rejected: + +- `.PreviousChart` - Ambiguous during rollbacks +- `.InstalledChart` - Confusing with current installation +- `.CurrentChart` - Ambiguous which is "current" +- `.Release.Deployed.Chart` - Unnecessarily nested + +### Always Available as Template Objects + +`.DeployedChart` (nil or chart metadata) and `.DeployedCharts` (empty array or populated) are always present to ensure consistent template behavior, prevent undefined variable errors, and enable testing with `helm template`. + +### Populated Only During Upgrades/Rollbacks + +`.DeployedChart`/`.DeployedCharts` contain chart metadata only during `helm upgrade` and `helm rollback` when deployed releases exist. During rollback, they reflect the currently deployed version (being rolled back _from_). They're nil/empty for: + +- `helm install` - No deployed release +- `helm template` / dry-runs - No cluster context +- When `--deployed-depth 0` is used + +### Chart Metadata Only + +This proposal exposes only Chart.yaml metadata (same structure as `.Chart`), not values, manifests, or release metadata. This maintains security (values may contain secrets), performance (manifests can be large), and simplicity while solving 90% of use cases. Future HIPs could extend this if needed. + +### Depth Control Flag + +The `--deployed-depth` flag controls how many deployed chart versions to retrieve (default: 1). Setting `--deployed-depth 0` disables the feature for security or determinism requirements. Higher depths may impact performance and should only be used for specific multi-version migration scenarios. + +### Design Decisions + +- **Different Chart Names:** Still populates `.DeployedChart` even if chart names differ—templates can detect and handle this +- **Helm's Record:** Reflects Helm's stored release record, not actual cluster state (use `lookup()` for that) +- **Dry-Run/Template:** Always nil to maintain cluster-agnostic, deterministic behavior + +## Specification + +### New Template Objects + +**`.DeployedChart`**: Singular object containing the most recent deployed chart metadata (nil if none exists). Syntactic sugar for `.DeployedCharts[0]`. + +**`.DeployedCharts`**: Array of deployed chart metadata objects in reverse chronological order (most recent first). Empty array if no deployed releases exist. + +**Metadata Structure:** Each chart object is identical to `.Chart`, including `Name`, `Version`, `AppVersion`, and other [Chart.yaml fields](https://helm.sh/docs/topics/charts/#the-chartyaml-file). + +**Usage Examples:** + +```yaml +# Simple case: check latest deployed version +{{- if .DeployedChart }} +{{- if and (semverCompare ">=2.0.0" .Chart.Version) (semverCompare "<2.0.0" .DeployedChart.Version) }} +apiVersion: batch/v1 +kind: Job +metadata: + name: {{ include "mychart.fullname" . }}-migration + annotations: + "helm.sh/hook": pre-upgrade +spec: + template: + spec: + containers: + - name: migrate + image: myapp/migrator:{{ .Chart.AppVersion }} + command: ["migrate", "v1-to-v2"] +{{- end }} +{{- end }} + +# Multi-version migration: handle complex upgrade paths +{{- if gt (len .DeployedCharts) 0 }} +{{- range .DeployedCharts }} +{{- if semverCompare "<1.5.0" .Version }} +# Run migration for versions before 1.5.0 +{{- end }} +{{- end }} +{{- end }} +``` + +### Command-Line Flag + +```bash +# Default: retrieve latest deployed chart +helm upgrade myrelease mychart + +# Retrieve last 3 deployed versions +helm upgrade myrelease mychart --deployed-depth 3 + +# Disable feature +helm upgrade myrelease mychart --deployed-depth 0 +``` + +### Behavior Matrix + +| Operation | `.DeployedChart` / `.DeployedCharts` | +| -------------------------- | --------------------------------------------------------------- | +| `helm install` | `nil` / `[]` (no deployed release) | +| `helm upgrade` (new) | `nil` / `[]` (no deployed release) | +| `helm upgrade` (existing) | Populated with deployed metadata (most recent first) | +| `helm rollback` | Populated with currently deployed version (rolling back _from_) | +| `helm template` / dry-runs | `nil` / `[]` (no cluster context) | +| `--deployed-depth 0` | `nil` / `[]` (explicitly disabled) | +| `--deployed-depth N` | Up to N most recent deployed versions | + +## Backwards Compatibility + +Fully backwards compatible. The `.DeployedChart` and `.DeployedCharts` objects are purely additive—existing charts work unchanged. Go templates handle nil and empty arrays safely; the recommended `{{ if .DeployedChart }}` pattern works in all scenarios. + +## Security Implications + +**Not Exposed:** Previous values (may contain secrets) or previous manifest (may contain sensitive data). Only Chart.yaml metadata is exposed. + +**Considerations:** Chart authors should not store sensitive data in Chart.yaml. The `--deployed-depth 0` flag provides opt-out for security-sensitive environments. Higher depth values increase data exposure; use the minimum required. + +## How to Teach This + +### Documentation Additions + +1. **Template Objects Reference:** Add `.DeployedChart` and `.DeployedCharts` to built-in objects with availability details +2. **Upgrade Guide:** "Implementing Version-Aware Upgrades" covering nil/empty checks, version comparisons, and best practices +3. **Migration Examples:** Show replacement of post-renderers and pre-upgrade hooks +4. **Performance Note:** Document that `--deployed-depth` should be kept minimal; default of 1 is recommended +5. **Chart Linting:** Update `helm lint` to warn on usage without nil/empty checks + +### Key Example Pattern + +```yaml +{{- if and .DeployedChart (semverCompare "<3.0.0" .DeployedChart.Version) }} +# Handle breaking change from versions < 3.0.0 +{{- end }} +``` + +## Reference Implementation + +A future pull request will: + +1. Extend template rendering context to include `.DeployedChart` and `.DeployedCharts` +2. Populate from release records during upgrade/rollback (reverse chronological order) +3. Add `--deployed-depth` flag (default: 1) +4. Implement `.DeployedChart` as alias to `.DeployedCharts[0]` +5. Include comprehensive unit and integration tests covering depth behavior + +## Rejected Ideas + +- **Full Release Object:** Security/performance concerns; chart metadata sufficient +- **Only Version Strings:** Inconsistent with `.Chart`; prevents access to other metadata +- **Environment Variable Control:** Less explicit than CLI flag +- **Cluster Query During `helm template`:** Violates cluster-agnostic design principle +- **Mutable Objects:** Violates read-only template model; no clear use case +- **Separate `--disable-deployed-chart` Flag:** Unified `--deployed-depth` with 0 value is cleaner +- **Unlimited History:** Performance implications; requiring explicit depth prevents accidental overhead + +## References + +- [Helm Built-in Objects](https://helm.sh/docs/chart_template_guide/builtin_objects/) +- [Helm Chart.yaml](https://helm.sh/docs/topics/charts/#the-chartyaml-file) +- [Go Templates](https://pkg.go.dev/text/template) +- [Semantic Versioning](https://semver.org/) +- [Example of current workaround](https://github.com/helm/community/pull/421#issuecomment-3662769874)