diff --git a/hips/hip-9999.md b/hips/hip-9999.md new file mode 100644 index 00000000..cdb6fb6a --- /dev/null +++ b/hips/hip-9999.md @@ -0,0 +1,218 @@ +--- +hip: "9999" +title: "Separate OCI pull identity from chart identity for dependencies" +authors: [ "BeganovR <446110@niuitmo.ru>", "Enin Kaduk" ] +created: "2026-08-10" +type: "feature" +status: "draft" +--- + +## Abstract + +For OCI dependencies, `Chart.yaml`'s `dependencies[].name` currently has to +serve two roles at once: it is the chart's identity (compared by `helm lint` +against the vendored subchart's own `Chart.yaml` name, and used to wire +`alias`/values), and it is the basename Helm appends to the OCI repository to +build the pull reference (`oci:///:`). When a chart is +published or mirrored under a registry path whose basename differs from its +own `name` — common with registry mirroring or organization naming prefixes — +there is no way to satisfy both roles at once. This HIP proposes an optional +`artifactName` field on `dependencies[]` entries that carries the OCI pull +basename separately from `name`, so `name` can always represent chart +identity. + +## Motivation + +`helm dependency update` resolves and downloads each dependency, then vendors +it under `charts/`. For OCI repositories, the pull reference is built as +`oci:///:`, so `name` doubles as the registry +basename. `helm lint` separately checks that `dependencies[].name` in the +parent `Chart.yaml` matches the vendored subchart's own declared `name`. + +These two uses of `name` conflict whenever a chart is republished or mirrored +under a different OCI basename than its own identity, e.g. +`oci://example.com/org/dhi-metrics-server-chart:3.13.1` for a chart whose own +`Chart.yaml` declares `name: metrics-server-chart`. Setting `name` to the OCI +basename lets `helm dependency update` pull the artifact, but `helm lint` then +fails with a paired error and warning, since the vendored chart's real +identity no longer matches the parent's `dependencies[].name`: + +```text +[ERROR] chart metadata is missing these dependencies: metrics-server-chart +[WARNING] chart directory is missing these dependencies: dhi-metrics-server-chart +``` + +There is currently no supported way to express both identities in +`Chart.yaml`. Users hitting this today have to choose between two broken +options: set `dependencies[].name` to the OCI basename, so +`helm dependency update` works but `helm lint` fails; or set it to the chart +identity, so lint can pass only by abandoning `repository` and vendoring the +chart into `charts/` by hand. Neither is workable for charts pulled through a +registry mirror or organization naming convention the user doesn't control +(e.g. third-party or vendor-mirrored charts prefixed with `dhi-`). + +This is compounded by Helm's own OCI strict naming policy, which today +requires "the basename of the registry reference [to] always match the +chart's name" for anything pushed with `helm push` — so the split described +here can currently only arise from registries populated by other tools (e.g. +mirroring), not from `helm push` itself. That said, this HIP does not depend +on relaxing strict-mode `helm push`; it only addresses *consuming* a +dependency that already lives under a different basename. + +This is **not** a request to weaken `helm lint`. The current lint output is +correctly describing a genuinely underspecified model: today there is only +one field to hold two different identities, so lint has no way to tell a real +mismatch apart from a mirrored basename. Once pull identity and chart +identity are separate fields, lint keeps comparing chart identity to the +vendored chart's own name exactly as it does today, and continues to catch +real mismatches — it simply stops being fed the OCI basename as if it were +the chart's name. + +This was reported and discussed in +[helm/helm#32529](https://github.com/helm/helm/issues/32529), which also +links prior discussions of the same coupling: #9627, #9386, #9278, and +#11127. + +## Rationale + +The fix keeps `name` as the sole source of chart identity everywhere it is +used today (lint, `charts/` vendoring, alias/values wiring), and adds a +second, purely OCI-facing field for the pull basename. This is additive and +opt-in: dependencies that don't set the new field keep behaving exactly as +before, since the pull basename still falls back to `name`. + +Alternatives considered and rejected: + +- **Allow `repository` to be a full OCI reference including the artifact + basename.** This overloads a field that today is documented as a registry + path, and would need every OCI-consuming code path to detect and strip a + trailing name segment, which is more invasive than adding one field. +- **Add a chart-level override instead of a per-dependency field.** A chart + can have multiple OCI dependencies pulled from differently-named artifacts, + so the override has to be per-dependency. +- **Reuse `alias`.** `alias` already has an established meaning (the values + key a subchart is addressed by) and is not tied to OCI at all; conflating + it with the pull basename would change existing, unrelated behavior. + +## Specification + +A new optional string field, `artifactName`, is added to `dependencies[]` +entries in `Chart.yaml`: + +```yaml +dependencies: + - name: metrics-server-chart + artifactName: dhi-metrics-server-chart + version: 3.13.1 + repository: oci://example.com/org +``` + +- `name` keeps its existing meaning: the chart's identity, matched against + the vendored subchart's own `Chart.yaml` `name` by `helm lint`, and used to + wire `alias`/values. +- `artifactName`, when set, is the basename used to build the OCI pull + reference (`oci:///:`) instead of `name`. + It only affects OCI repositories; it has no effect for `http(s)` chart + repositories or local (`file://`/subfolder) dependencies. +- If `artifactName` is empty or unset, the pull reference is built from `name` + as it is today — fully backwards compatible. +- `helm dependency update` builds the pull reference from `artifactName` + (falling back to `name`), downloads and vendors the chart under `charts/` + using the vendored chart's own declared name (unaffected by this change), + and `Chart.lock` records `artifactName` alongside `name` so that + `helm dependency build` (which reads the lock, not `Chart.yaml`) continues + to pull correctly without re-resolving. +- `helm lint` requires no changes: it already compares `dependencies[].name` + against the vendored subchart's own name, with no OCI-specific logic. Once + `name` is no longer forced to double as the OCI basename, the two values + it's compared against naturally agree. + +In short: + +| `artifactName` set? | OCI pull basename | `charts/` / lint identity | +|---|---|---| +| No (default) | `name` | `name` | +| Yes | `artifactName` | `name` (unchanged) | + +## Backwards compatibility + +This is a fully additive, opt-in change. Existing `Chart.yaml` files have no +`artifactName` field, so every dependency continues to resolve its OCI pull +basename from `name`, exactly as today. `Chart.lock` gains one new optional +field per dependency; older Helm versions reading a lock file produced by a +newer Helm simply ignore the unknown field. There is no change to the +on-disk layout of vendored charts, to `helm lint`'s behavior for charts that +don't use this field, or to non-OCI repositories. + +## Security implications + +None identified. `artifactName` is sanitized the same way other +`dependencies[]` string fields already are (control characters stripped), +and is only ever used to construct an OCI reference the same way `name` is +today — it does not introduce a new class of input reaching the registry +client. + +## How to teach this + +This is an opt-in field that only needs documentation where it applies: +users who publish or consume OCI charts under a registry path whose basename +differs from the chart's own name. The `Chart.yaml` reference documentation +and the OCI dependencies guide should note `artifactName` alongside `name`, +`version`, and `repository`, with the mirrored-registry scenario above as the +motivating example. No changes are needed to the documentation for +non-OCI/HTTP repositories, since the field has no effect there. + +## Reference implementation + +A reference implementation exists as a local branch against `helm/helm` +(`oci-dependency-artifact-name`), covering: + +- `pkg/chart/v2/dependency.go`: adds `ArtifactName` to the `Dependency` + struct, with the same sanitization applied to other string fields in + `Validate()`. +- `pkg/downloader/manager.go`: `downloadAll()` builds the OCI pull reference + from `ArtifactName` when set (falling back to `Name`), instead of always + using `Name`. +- `internal/resolver/resolver.go`: `Resolve()` uses `ArtifactName` for OCI + tag listing when no exact version is pinned, and persists `ArtifactName` + into the resulting `Chart.lock` entries. +- Unit tests covering sanitization and lock resolution, and an end-to-end + test (`pkg/cmd/dependency_update_test.go`) that publishes a fixture chart + under a differing OCI basename, runs `helm dependency update`, and asserts + both that the artifact was pulled by its OCI basename and that + `helm lint` no longer reports a dependency mismatch. + +This branch will be opened as a pull request against `helm/helm` once this +HIP has been reviewed. + +## Rejected ideas + +- **Overloading `repository`, a chart-level override, reusing `alias`.** See + [Rationale](#rationale) for why each was set aside in favor of a + per-dependency `artifactName` field. +- **Relaxing `helm lint` to stop reporting the name mismatch, instead of + adding a field.** This was considered and rejected: the mismatch lint + reports today is a real signal, not a false positive — it means the parent + chart's declared dependency and the actually-vendored chart disagree on + identity. The right fix is to stop forcing `name` to carry two meanings, not + to make lint quieter about a case it's correctly catching. + +## Open issues + +- **Field name.** This HIP uses `artifactName`; the originating issue also + considered `ociArtifact`. `artifactName` was picked because it doesn't + imply OCI-only scope in the name itself, but this is open to maintainer + preference and doesn't affect the rest of the design. + +## References + +- [helm/helm#32529](https://github.com/helm/helm/issues/32529) — original bug + report and discussion this HIP addresses. +- [helm/helm#9627](https://github.com/helm/helm/issues/9627), + [helm/helm#9386](https://github.com/helm/helm/issues/9386), + [helm/helm#9278](https://github.com/helm/helm/issues/9278), + [helm/helm#11127](https://github.com/helm/helm/issues/11127) — earlier + discussions of the same OCI basename / chart name coupling. +- [Use OCI-based registries — Specifying dependencies](https://helm.sh/docs/topics/registries/#specifying-dependencies) + and the [strict naming policy](https://helm.sh/docs/topics/registries/) it + documents.