Skip to content
199 changes: 199 additions & 0 deletions hips/hip-0027.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,199 @@
---
hip: 0027
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

title: "Expose Previously Installed Chart Metadata During Template Rendering"
authors: ["Andrew Shoell <mrlunchbox777@gmail.com>"]
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
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

my main thought is, would you not also want to expose the values of helm history to check things like whether a previous release was successful?

Some thoughts about this: What about adding "History" key to the existing .Release built-in: .Release.History when a --release-history-depth flag is passed?

This could be a filtered version of the Release object that's currently stored in helm release secrets by default.

Agree that the full release object could cause performance issues. perhaps this could be mitigated by:

  • any history might be best to opt-into with a flag (default history level 0 rather than 1 as currently proposed)
  • including only a filtered subset of the release object, omitting potentially very large data like templates, manifest etc.

Not yet sure what security concerns adding the Release object would add that other built-ins don't already have (eg, .Values, .Files, etc). But good to consider here.

- **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)