Skip to content

Commit cd01bdc

Browse files
committed
docs: adding HIP-28 to expose release history during template rendering
Signed-off-by: Andrew Shoell <mrlunchbox777@gmail.com>
1 parent 222bdf8 commit cd01bdc

1 file changed

Lines changed: 287 additions & 0 deletions

File tree

hips/hip-0028.md

Lines changed: 287 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,287 @@
1+
---
2+
hip: 9999
3+
title: "Expose Release History During Template Rendering"
4+
authors: ["Andrew Shoell <mrlunchbox777@gmail.com>"]
5+
created: "2025-11-12"
6+
type: "feature"
7+
status: "draft"
8+
---
9+
10+
## Abstract
11+
12+
This HIP proposes exposing release history metadata during template rendering. Currently, Helm templates have access to `.Chart` for the chart being installed but no equivalent access to deployed release history. This forces chart authors to use complex workarounds like post-renderers, pre-upgrade hooks, or manual values conventions to implement version-aware upgrade logic.
13+
14+
The proposal introduces `.Release.History` (array of historical releases) available in template contexts, populated during `helm upgrade` and `helm rollback` operations when the `--release-history-max` flag is provided. The flag controls how many historical releases to retrieve (default: 0), requiring explicit opt-in. Chart authors can check `len .Release.History` to determine if sufficient historical data is available and use `.Release.Revision` (or compare `len .Release.History` to expected minimum) to detect upgrade scenarios requiring migration logic.
15+
16+
## Motivation
17+
18+
### Current Limitations
19+
20+
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:
21+
22+
**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.
23+
24+
**Pre-Upgrade Hooks:** Store version metadata in ConfigMaps via hooks, creating ordering dependencies and potential failure points.
25+
26+
**Manual Values:** Require users to specify previous versions in values files—error-prone and defeats Helm's release tracking.
27+
28+
### Real-World Impact
29+
30+
This limitation prevents or complicates legitimate use cases:
31+
32+
- **Breaking Changes:** No clean migration path for renamed resources or changed structures
33+
- **Conditional Resources:** Cannot create migration Jobs based on version deltas
34+
- **Smart Defaults:** Cannot distinguish fresh installs from upgrades for intelligent defaults
35+
- **Advanced Deployments:** Blue-green and similar strategies require external orchestration
36+
37+
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.
38+
39+
## Rationale
40+
41+
### Naming: `.Release.History`
42+
43+
`.Release.History` extends the existing `.Release` built-in object with a history array, making it immediately intuitive and discoverable for Helm users. This follows the established pattern of `.Release.Name`, `.Release.Namespace`, `.Release.Revision`, etc., and feels like a natural part of Helm's API.
44+
45+
The history array is ordered in reverse chronological order (index 0 is most recent deployed release). This provides ergonomic access to the most recent release while enabling multi-version migrations when needed.
46+
47+
Alternatives considered and rejected:
48+
49+
- `.PreviousChart` - Ambiguous during rollbacks
50+
- `.DeployedChart`/`.DeployedCharts` - Less discoverable, doesn't extend existing objects
51+
- `.InstalledChart` - Confusing with current installation
52+
- `.CurrentChart` - Ambiguous which is "current"
53+
- `.Release.Deployed.Chart` - Unnecessarily nested
54+
55+
### Always Available as Template Object
56+
57+
`.Release.History` (empty array or populated) is always present to ensure consistent template behavior, prevent undefined variable errors, and enable testing with `helm template`.
58+
59+
### Populated Only During Upgrades/Rollbacks
60+
61+
`.Release.History` contains release metadata only during `helm upgrade` and `helm rollback` when deployed releases exist and `--release-history-max` is greater than 0. During rollback, the history reflects releases deployed before the rollback target. It's empty for:
62+
63+
- `helm install` - No deployed release
64+
- `helm template` / dry-runs - No cluster context
65+
- When `--release-history-max 0` is used (default)
66+
67+
### Filtered Release Data
68+
69+
This proposal exposes a filtered subset of release data to balance utility with performance and security:
70+
71+
**Included by default:**
72+
73+
- Chart metadata (Name, Version, AppVersion, and other Chart.yaml fields)
74+
- Release metadata (Name, Namespace, Revision, Status)
75+
- Timestamps (FirstDeployed, LastDeployed)
76+
77+
**Excluded by default (opt-in via flag):**
78+
79+
- Values (may contain secrets; use `--include-history-values` to include)
80+
- Manifests (can be very large; future consideration)
81+
- Templates (can be very large; no clear use case - templates are static per chart version)
82+
- Hooks (implementation detail)
83+
84+
The conservative default excludes potentially sensitive or large data. Users who need historical values for complex migration scenarios can opt-in explicitly with `--include-history-values`, accepting the security and performance tradeoffs. See Security Implications section for detailed rationale.
85+
86+
**Future consideration:** Historical manifests could be made available via `--include-history-manifests` if demand exists, though manifests can be quite large and increase memory/performance overhead significantly.
87+
88+
### Max Control Flag
89+
90+
The `--release-history-max` flag controls how many historical releases to retrieve (default: 0, requiring explicit opt-in). This conservative default protects users from accidental performance impact. Setting `--release-history-max 0` explicitly disables the feature (though this is already the default). Higher max values may impact performance and should only be used for specific multi-version migration scenarios.
91+
92+
### Design Decisions
93+
94+
- **Different Chart Names:** Still populates `.Release.History` even if chart names differ—templates can detect and handle this
95+
- **Helm's Record:** Reflects Helm's stored release record, not actual cluster state (use `lookup()` for that)
96+
- **Dry-Run/Template:** Always empty array to maintain cluster-agnostic, deterministic behavior
97+
- **Opt-In by Default:** Default max of 0 requires explicit user choice, preventing accidental performance impact
98+
99+
## Specification
100+
101+
### New Template Objects
102+
103+
**`.Release.History`**: Array of release objects in reverse chronological order (most recent first). Empty array if no deployed releases exist or `--release-history-max 0` is used (default).
104+
105+
**Note:** Use `.Release.Revision` to detect if this is an upgrade (revision > 1) and `len .Release.History` to check how much historical data is available.
106+
107+
**Release Object Structure:** Each release object contains:
108+
109+
```go
110+
type HistoricalRelease struct {
111+
Name string // Release name
112+
Namespace string // Release namespace
113+
Revision int // Revision number
114+
Status string // Release status (deployed, superseded, failed, etc.)
115+
Chart Chart // Chart metadata (same structure as .Chart)
116+
FirstDeployed time.Time // When this release was first deployed
117+
LastDeployed time.Time // When this release was last deployed
118+
}
119+
```
120+
121+
**Usage Examples:**
122+
123+
```yaml
124+
# Check if upgrading from a version that needs migration
125+
{{- if gt (len .Release.History) 0 }}
126+
{{- $lastRelease := index .Release.History 0 }}
127+
{{- if and (semverCompare ">=2.0.0" .Chart.Version) (semverCompare "<2.0.0" $lastRelease.Chart.Version) }}
128+
apiVersion: batch/v1
129+
kind: Job
130+
metadata:
131+
name: {{ include "mychart.fullname" . }}-migration
132+
annotations:
133+
"helm.sh/hook": pre-upgrade
134+
spec:
135+
template:
136+
spec:
137+
containers:
138+
- name: migrate
139+
image: myapp/migrator:{{ .Chart.AppVersion }}
140+
command: ["migrate", "v1-to-v2"]
141+
{{- end }}
142+
{{- end }}
143+
144+
# Require minimum depth for safe upgrades
145+
{{- if lt .Release.HistoryDepth 3 }}
146+
{{- fail "This chart requires --release-history-depth=3 for safe upgrades from v2.x" }}
147+
{{- end }}
148+
149+
# Multi-version migration: handle complex upgrade paths
150+
{{- range .Release.History }}
151+
{{- if and (eq .Status "deployed") (semverCompare "<1.5.0" .Chart.Version) }}
152+
# Run migration for successfully deployed versions before 1.5.0
153+
{{- end }}
154+
{{- end }}
155+
156+
# Check if last deployment was successful
157+
{{- if gt (len .Release.History) 0 }}
158+
{{- $lastRelease := index .Release.History 0 }}
159+
{{- if ne $lastRelease.Status "deployed" }}
160+
{{- fail (printf "Previous release was %s - manual intervention required" $lastRelease.Status) }}
161+
{{- end }}
162+
{{- end }}
163+
```
164+
165+
### Command-Line Flag
166+
167+
```bash
168+
# Default: no history retrieved (max 0)
169+
helm upgrade myrelease mychart
170+
171+
# Retrieve latest deployed release
172+
helm upgrade myrelease mychart --release-history-max 1
173+
174+
# Retrieve last 3 releases
175+
helm upgrade myrelease mychart --release-history-max 3
176+
177+
# Explicitly disable (same as default)
178+
helm upgrade myrelease mychart --release-history-max 0
179+
```
180+
181+
### Behavior Matrix
182+
183+
The following table shows what values are available in template context for different operations:
184+
185+
| Operation | `.Release.History` | `.Release.Revision` |
186+
| ------------------------------------------------- | ------------------------------------- | ------------------- |
187+
| `helm install` | `[]` | 1 |
188+
| `helm upgrade` (first) | `[]` (default, no flag) | 2 |
189+
| `helm upgrade --release-history-max 1` (first) | Populated with 1 release | 2 |
190+
| `helm upgrade --release-history-max N` | Up to N releases | varies |
191+
| `helm rollback --release-history-max N` | Populated with releases before target | varies |
192+
| `helm template` / dry-runs | `[]` | 1 |
193+
194+
**Note:** Use `.Release.Revision` to distinguish installs (revision=1) from upgrades (revision>1).
195+
196+
## Backwards Compatibility
197+
198+
Fully backwards compatible. The `.Release.History` field is purely additive—existing charts work unchanged. Go templates handle empty arrays safely; the recommended `{{ if gt (len .Release.History) 0 }}` pattern works in all scenarios. Default max of 0 means existing behavior is unchanged unless users explicitly opt in.
199+
200+
## Security Implications
201+
202+
**Not Exposed:** Previous values (may contain secrets) or previous manifests (may contain sensitive data). Only filtered release metadata is exposed (chart metadata, release status, timestamps, revision numbers).
203+
204+
**Considerations:** Chart authors should not store sensitive data in Chart.yaml. The default `--release-history-depth 0` provides opt-out by default. Higher depth values increase data exposure; use the minimum required. Release status and metadata are already stored in cluster secrets by Helm, so this doesn't expose data that isn't already persisted.
205+
206+
## How to Teach This
207+
208+
### Documentation Additions
209+
210+
1. **Template Objects Reference:** Add `.Release.History` to built-in objects documentation with availability details and usage patterns with `.Release.Revision`
211+
2. **Upgrade Guide:** "Implementing Version-Aware Upgrades" covering empty array checks, version comparisons, status checking, and best practices
212+
3. **Migration Examples:** Show replacement of post-renderers and pre-upgrade hooks, including use of opt-in flag for values when needed, with practical patterns (check last release, check last successful)
213+
4. **Performance Note:** Document that `--release-history-max` should be kept minimal; opt-in by default protects users
214+
5. **Chart Linting:** Update `helm lint` to warn on `.Release.History` usage without empty array checks, and suggest using `.Release.Revision` for upgrade detection
215+
6. **Security Guide:** Document the opt-in flag `--include-history-values` with clear warnings about security implications
216+
217+
### Key Example Pattern
218+
219+
```yaml
220+
# Pattern 1: Defensive check before accessing history
221+
{{- if gt (len .Release.History) 0 }}
222+
{{- $lastRelease := index .Release.History 0 }}
223+
{{- if semverCompare "<3.0.0" $lastRelease.Chart.Version }}
224+
# Handle breaking change from versions < 3.0.0
225+
{{- end }}
226+
{{- end }}
227+
228+
# Pattern 2: Check only last successful deployment
229+
{{- $lastSuccessful := dict }}
230+
{{- range .Release.History }}
231+
{{- if eq .Status "deployed" }}
232+
{{- $lastSuccessful = . }}
233+
{{- break }}
234+
{{- end }}
235+
{{- end }}
236+
{{- if $lastSuccessful }}
237+
# Use $lastSuccessful for migration logic
238+
{{- end }}
239+
240+
# Pattern 3: Require history for upgrades (not installs)
241+
{{- if gt .Release.Revision 1 }}
242+
{{- if eq (len .Release.History) 0 }}
243+
{{- fail "Upgrades require --release-history-max=1 for continuity checks" }}
244+
{{- end }}
245+
{{- end }}
246+
247+
# Pattern 4: Check for sufficient history depth (less common)
248+
{{- if and (gt .Release.Revision 1) (lt (len .Release.History) 2) }}
249+
{{- fail "This complex migration requires --release-history-max=2 for full validation" }}
250+
{{- end }}
251+
```
252+
253+
## Reference Implementation
254+
255+
A future pull request will:
256+
257+
1. Extend template rendering context to include `.Release.History`
258+
2. Populate `.Release.History` from release records during upgrade/rollback (reverse chronological order)
259+
3. Add `--release-history-max` flag (default: 0)
260+
4. Add opt-in flag: `--include-history-values`
261+
5. Filter release objects by default to include: Chart, Name, Namespace, Revision, Status, FirstDeployed, LastDeployed
262+
6. When opt-in flag is used, include Values in historical releases
263+
7. Include comprehensive unit and integration tests covering flag behavior, filtering, and edge cases
264+
265+
## Rejected Ideas
266+
267+
- **Full Release Object:** Security/performance concerns; filtered release metadata sufficient
268+
- **Chart Metadata Only:** Missing release status/revision limits utility for migration logic
269+
- **Only Version Strings:** Inconsistent with `.Chart`; prevents access to other metadata
270+
- **`.DeployedChart`/`.DeployedCharts` Naming:** Less discoverable than extending `.Release` object
271+
- **Default Max of 1:** Opt-in by default (max 0) is more conservative and safer
272+
- **Environment Variable Control:** Less explicit than CLI flag
273+
- **Cluster Query During `helm template`:** Violates cluster-agnostic design principle
274+
- **Mutable Objects:** Violates read-only template model; no clear use case
275+
- **Separate `--disable-release-history` Flag:** Unified `--release-history-max` with 0 value is cleaner
276+
- **Unlimited History:** Performance implications; requiring explicit max prevents accidental overhead
277+
- **Including Values/Manifests by Default:** While historical values could be useful for migration scenarios, and users already have access via `helm get values --revision N` or `lookup()`, making them automatically available in templates creates additional surface area for accidental exposure. The filtered metadata approach with opt-in flag (`--include-history-values`) serves both conservative defaults and advanced use cases. Manifests moved to future consideration (`--include-history-manifests`) as they are very large and less commonly needed.
278+
- **Including Templates:** Templates are static per chart version; if you need old templates, retrieve the old chart version. No flag needed.
279+
- **`.Release.HistoryMax` Field:** Redundant with `len .Release.History` and `.Release.Revision`. Chart authors can use `.Release.Revision > 1` to detect upgrades and `len .Release.History` to check available data.
280+
281+
## References
282+
283+
- [Helm Built-in Objects](https://helm.sh/docs/chart_template_guide/builtin_objects/)
284+
- [Helm Chart.yaml](https://helm.sh/docs/topics/charts/#the-chartyaml-file)
285+
- [Go Templates](https://pkg.go.dev/text/template)
286+
- [Semantic Versioning](https://semver.org/)
287+
- [Example of current workaround](https://github.com/helm/community/pull/421#issuecomment-3662769874)

0 commit comments

Comments
 (0)