Skip to content

Commit 82cca09

Browse files
authored
Merge pull request #421 from mrlunchbox777/docs-adding-expose-previous-chart-hip
HIP: adding expose deployed chart
2 parents 06581a7 + d3703a3 commit 82cca09

2 files changed

Lines changed: 307 additions & 0 deletions

File tree

hips/README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,3 +37,4 @@ restricted markdown format and can be found in the
3737
- [hip-0025: Better Support for Resource Creation Sequencing](hip-0025.md)
3838
- [hip-0026: H4HIP: Wasm plugin system](hip-0026.md)
3939
- [hip-0027: Bring .helmignore to parity with .gitignore file targeting syntax](hip-0027.md)
40+
- [hip-0029: Expose Release History During Template Rendering](hip-0029.md)

hips/hip-0029.md

Lines changed: 306 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,306 @@
1+
---
2+
hip: 0029
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 (including `--dry-run=server`) 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 During Upgrades/Rollbacks (and Server Dry-Runs)
60+
61+
`.Release.History` contains release metadata during `helm upgrade` and `helm rollback` when deployed releases exist and `--release-history-max` is greater than 0. During rollback, history follows Helm's normal revision behavior: rollback creates a new revision and prior revisions remain in history.
62+
63+
For `--dry-run=server`, behavior matches the corresponding live command because cluster context is available (for example, `helm upgrade --dry-run=server --release-history-max N` can populate `.Release.History`). It's empty for:
64+
65+
- `helm install` - No deployed release
66+
- `helm template` and `--dry-run=client` - No cluster context
67+
- When `--release-history-max 0` is used (default)
68+
69+
### Filtered Release Data
70+
71+
This proposal exposes a filtered subset of release data to balance utility with performance and security:
72+
73+
**Included by default:**
74+
75+
- Chart metadata (Name, Version, AppVersion, and other Chart.yaml fields)
76+
- Release metadata (Name, Namespace, Revision, Status)
77+
- Timestamps (FirstDeployed, LastDeployed)
78+
79+
**Excluded by default (opt-in via flag):**
80+
81+
- Values (may contain secrets; use `--include-history-values` to include)
82+
- Manifests (can be very large; future consideration)
83+
- Templates (can be very large; no clear use case - templates are static per chart version)
84+
- Hooks (implementation detail)
85+
86+
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.
87+
88+
**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.
89+
90+
### Max Control Flag
91+
92+
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.
93+
94+
`--release-history-max` is a retrieval limit, not a guarantee that many revisions exist. Template checks should rely on actual available data (`len .Release.History`).
95+
96+
### Design Decisions
97+
98+
- **Different Chart Names:** Still populates `.Release.History` even if chart names differ—templates can detect and handle this
99+
- **Helm's Record:** Reflects Helm's stored release record, not actual cluster state (use `lookup()` for that)
100+
- **Relationship to `helm history`:** `.Release.History` is derived from the same release records used by `helm history <release>`, but exposed as a reverse-chronological, template-friendly projection with field filtering and `--release-history-max` limits.
101+
- **Dry-Run Behavior:** `helm template` and `--dry-run=client` always return an empty array (cluster-agnostic). `--dry-run=server` follows live command behavior and can populate history when available.
102+
- **Opt-In by Default:** Default max of 0 requires explicit user choice, preventing accidental performance impact
103+
104+
## Specification
105+
106+
### New Template Objects
107+
108+
**`.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).
109+
110+
**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.
111+
112+
**Note:** Template logic should rely on actual availability (`len .Release.History`), not requested max. Requested max may be larger or smaller than existing revisions.
113+
114+
**Note:** `.Release.Revision` provides an upper bound on possible prior history (`revision - 1`). Chart maintainers can combine `.Release.Revision` with `len .Release.History` for stricter migration checks.
115+
116+
**Note:** `HistoricalRelease` is a runtime projection created for template rendering from Helm's stored release records. It is not a new persisted release schema.
117+
118+
**Release Object Structure:** Each release object contains:
119+
120+
```go
121+
type HistoricalRelease struct {
122+
Name string // Release name
123+
Namespace string // Release namespace
124+
Revision int // Revision number
125+
Status string // Release status (deployed, superseded, failed, etc.)
126+
Chart Chart // Chart metadata (same structure as .Chart)
127+
Values map[string]interface{} // Optional: only included when --include-history-values is set
128+
FirstDeployed time.Time // When this release was first deployed
129+
LastDeployed time.Time // When this release was last deployed
130+
}
131+
```
132+
133+
**Usage Examples:**
134+
135+
```yaml
136+
# Check if upgrading from a version that needs migration
137+
{{- if gt (len .Release.History) 0 }}
138+
{{- $lastRelease := index .Release.History 0 }}
139+
{{- if and (semverCompare ">=2.0.0" .Chart.Version) (semverCompare "<2.0.0" $lastRelease.Chart.Version) }}
140+
apiVersion: batch/v1
141+
kind: Job
142+
metadata:
143+
name: {{ include "mychart.fullname" . }}-migration
144+
annotations:
145+
"helm.sh/hook": pre-upgrade
146+
spec:
147+
template:
148+
spec:
149+
containers:
150+
- name: migrate
151+
image: myapp/migrator:{{ .Chart.AppVersion }}
152+
command: ["migrate", "v1-to-v2"]
153+
{{- end }}
154+
{{- end }}
155+
156+
# Require minimum history for safe upgrades
157+
{{- if and (gt .Release.Revision 1) (lt (len .Release.History) 3) }}
158+
{{- fail "This chart requires --release-history-max=3 for safe upgrades from v2.x" }}
159+
{{- end }}
160+
161+
# Multi-version migration: handle complex upgrade paths
162+
{{- range .Release.History }}
163+
{{- if and (eq .Status "deployed") (semverCompare "<1.5.0" .Chart.Version) }}
164+
# Run migration for successfully deployed versions before 1.5.0
165+
{{- end }}
166+
{{- end }}
167+
168+
# Check if last deployment was successful
169+
{{- if gt (len .Release.History) 0 }}
170+
{{- $lastRelease := index .Release.History 0 }}
171+
{{- if ne $lastRelease.Status "deployed" }}
172+
{{- fail (printf "Previous release was %s - manual intervention required" $lastRelease.Status) }}
173+
{{- end }}
174+
{{- end }}
175+
```
176+
177+
### Command-Line Flag
178+
179+
```bash
180+
# Default: no history retrieved (max 0)
181+
helm upgrade myrelease mychart
182+
183+
# Retrieve latest deployed release
184+
helm upgrade myrelease mychart --release-history-max 1
185+
186+
# Retrieve last 3 releases
187+
helm upgrade myrelease mychart --release-history-max 3
188+
189+
# Explicitly disable (same as default)
190+
helm upgrade myrelease mychart --release-history-max 0
191+
```
192+
193+
### Behavior Matrix
194+
195+
The following table shows what values are available in template context for different operations:
196+
197+
| Operation | `.Release.History` | `.Release.Revision` |
198+
| -------------------------------------------------------- | ------------------------------------- | ------------------- |
199+
| `helm install` | `[]` | 1 |
200+
| `helm upgrade` (first) | `[]` (default, no flag) | 2 |
201+
| `helm upgrade --release-history-max 1` (first) | Populated with 1 release | 2 |
202+
| `helm upgrade --release-history-max N` | Up to N releases | varies |
203+
| `helm rollback --release-history-max N` | Populated with releases before target | varies |
204+
| `helm upgrade --dry-run=server --release-history-max N` | Same as live `upgrade` | varies |
205+
| `helm rollback --dry-run=server --release-history-max N` | Same as live `rollback` | varies |
206+
| `helm install --dry-run=server` | `[]` | 1 |
207+
| `helm template` / `--dry-run=client` | `[]` | 1 |
208+
209+
**Note:** Use `.Release.Revision` to distinguish installs (revision=1) from upgrades (revision>1).
210+
211+
**Note:** `.Release.History` and `helm history <release>` both come from Helm's stored release records. For template ergonomics, `.Release.History` is reverse chronological (most recent first), filtered to HIP-defined fields, and capped by `--release-history-max`. During rollback, Helm creates a new revision; prior revisions remain in history.
212+
213+
## Backwards Compatibility
214+
215+
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.
216+
217+
## Security Implications
218+
219+
**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).
220+
221+
**Considerations:** Chart authors should not store sensitive data in Chart.yaml. The default `--release-history-max 0` provides opt-out by default. Higher max 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.
222+
223+
## How to Teach This
224+
225+
### Documentation Additions
226+
227+
1. **Template Objects Reference:** Add `.Release.History` to built-in objects documentation with availability details and usage patterns with `.Release.Revision`
228+
2. **Upgrade Guide:** "Implementing Version-Aware Upgrades" covering empty array checks, version comparisons, status checking, and best practices
229+
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)
230+
4. **Performance Note:** Document that `--release-history-max` should be kept minimal; opt-in by default protects users
231+
5. **Chart Linting:** Update `helm lint` to warn on `.Release.History` usage without empty array checks, and suggest using `.Release.Revision` for upgrade detection
232+
6. **Security Guide:** Document the opt-in flag `--include-history-values` with clear warnings about security implications
233+
234+
### Key Example Pattern
235+
236+
```yaml
237+
# Pattern 1: Defensive check before accessing history
238+
{{- if gt (len .Release.History) 0 }}
239+
{{- $lastRelease := index .Release.History 0 }}
240+
{{- if semverCompare "<3.0.0" $lastRelease.Chart.Version }}
241+
# Handle breaking change from versions < 3.0.0
242+
{{- end }}
243+
{{- end }}
244+
245+
# Pattern 2: Check only last successful deployment
246+
{{- $lastSuccessful := dict }}
247+
{{- range .Release.History }}
248+
{{- if eq .Status "deployed" }}
249+
{{- $lastSuccessful = . }}
250+
{{- break }}
251+
{{- end }}
252+
{{- end }}
253+
{{- if $lastSuccessful }}
254+
# Use $lastSuccessful for migration logic
255+
{{- end }}
256+
257+
# Pattern 3: Require history for upgrades (not installs)
258+
{{- if gt .Release.Revision 1 }}
259+
{{- if eq (len .Release.History) 0 }}
260+
{{- fail "Upgrades require --release-history-max=1 for continuity checks" }}
261+
{{- end }}
262+
{{- end }}
263+
264+
# Pattern 4: Check for sufficient history depth (less common)
265+
{{- if and (gt .Release.Revision 1) (lt (len .Release.History) 2) }}
266+
{{- fail "This complex migration requires --release-history-max=2 for full validation" }}
267+
{{- end }}
268+
```
269+
270+
**Guidance:** Treat `--release-history-max` as a retrieval cap, not a guarantee. Use `.Release.Revision` to detect install vs upgrade and `len .Release.History` to validate actual available history.
271+
272+
## Reference Implementation
273+
274+
A future pull request will:
275+
276+
1. Extend template rendering context to include `.Release.History`
277+
2. Populate `.Release.History` from release records during upgrade/rollback, including `--dry-run=server` parity (reverse chronological order)
278+
3. Add `--release-history-max` flag (default: 0)
279+
4. Add opt-in flag: `--include-history-values`
280+
5. Filter release objects by default to include: Chart, Name, Namespace, Revision, Status, FirstDeployed, LastDeployed
281+
6. When opt-in flag is used, include Values in historical releases
282+
7. Include comprehensive unit and integration tests covering flag behavior, filtering, and edge cases
283+
284+
## Rejected Ideas
285+
286+
- **Full Release Object:** Security/performance concerns; filtered release metadata sufficient
287+
- **Chart Metadata Only:** Missing release status/revision limits utility for migration logic
288+
- **Only Version Strings:** Inconsistent with `.Chart`; prevents access to other metadata
289+
- **`.DeployedChart`/`.DeployedCharts` Naming:** Less discoverable than extending `.Release` object
290+
- **Default Max of 1:** Opt-in by default (max 0) is more conservative and safer
291+
- **Environment Variable Control:** Less explicit than CLI flag
292+
- **Cluster Query During `helm template`:** Violates cluster-agnostic design principle
293+
- **Mutable Objects:** Violates read-only template model; no clear use case
294+
- **Separate `--disable-release-history` Flag:** Unified `--release-history-max` with 0 value is cleaner
295+
- **Unlimited History:** Performance implications; requiring explicit max prevents accidental overhead
296+
- **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.
297+
- **Including Templates:** Templates are static per chart version; if you need old templates, retrieve the old chart version. No flag needed.
298+
- **`.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.
299+
300+
## References
301+
302+
- [Helm Built-in Objects](https://helm.sh/docs/chart_template_guide/builtin_objects/)
303+
- [Helm Chart.yaml](https://helm.sh/docs/topics/charts/#the-chartyaml-file)
304+
- [Go Templates](https://pkg.go.dev/text/template)
305+
- [Semantic Versioning](https://semver.org/)
306+
- [Example of current workaround](https://github.com/helm/community/pull/421#issuecomment-3662769874)

0 commit comments

Comments
 (0)