Skip to content

Add a helm_charts extension that stores looked-up OCI chart digests as facts - #310

Open
rdark wants to merge 1 commit into
periareon:mainfrom
rdark:richard/helm-charts-extension-facts
Open

rdark wants to merge 1 commit into
periareon:mainfrom
rdark:richard/helm-charts-extension-facts

Conversation

@rdark

@rdark rdark commented Oct 30, 2026

Copy link
Copy Markdown
Contributor

#307 let helm_import_repository fetch a pinned oci:// chart from the repository cache without contacting the registry. A review comment there asked whether the digest could be saved to facts when sha256 is not set. facts exists only for module extensions, and helm_import_repository is a repository rule, so the rule cannot write them. This PR adds an extension that does.

Change

helm_charts is a new extension in helm/extensions.bzl with one tag:

helm_charts = use_extension("@rules_helm//helm:extensions.bzl", "helm_charts")
helm_charts.chart(
    name = "karpenter",
    url = "oci://public.ecr.aws/karpenter/karpenter:1.9.0",
)
use_repo(helm_charts, "karpenter")

For each tag the extension takes the digest from sha256, else from the stored facts, else from the registry manifest. It declares helm_import_repository with that digest, so the cached-blob path from #307 applies.

  • On Bazel 8.5 and newer, a looked-up digest is saved as a fact in MODULE.bazel.lock, keyed by the chart url. Later evaluations make no registry request for it.
  • Older Bazel has no facts. When any tag has no sha256, the extension is marked non-reproducible there, so the lockfile records the repository together with its digest.

The extension is separate from helm so that an unreachable registry cannot stop toolchain resolution.

Supporting changes:

  • The registry code in import.bzl moved to helm/private/oci_chart.bzl and runs with repository_ctx or module_ctx. The rule makes the same requests in the same order with the same error messages.
  • import_authn.bzl called repository_ctx.delete, which module_ctx lacks. It now blanks the file there, so a token does not stay on disk.
  • The digest choice, the url and digest checks and the chart file name split are in helm/private/chart_utils.bzl, so unit tests can reach them.

Behaviour change in helm_import_repository

The rule recovered the chart name and version by splitting the file name <name>-<version>.tgz at the last dash. A version that contains a dash, such as 1.21.0-beta.0, gave the target cert-manager-1.21.0 instead of cert-manager, and a wrong version in attrs_for_reproducibility.

An oci:// url now supplies the name and version exactly. An http file name is split at the first dash that is followed by a version number. Names without such a version split at the last dash, as before.

Testing

  • Unit tests in tests/helm_charts/chart_utils_test.bzl cover the file name split, the url and digest checks, and the digest choice. Making the helper drop the lookup result fails the digest test, and restoring the old split fails the split test.
  • Four chart tests in tests/helm_charts: a looked-up chart on quay.io, a second looked-up chart on ghcr.io, a pinned chart, and a pinned pre-release chart whose target must be named cert-manager.
  • bazel test //... and --config=strict on macOS with Bazel 9.3.1: 70 pass, 3 skipped (the Linux-only with_image_deps_img tests). I excluded tests/with_image_deps_oci locally, because the Docker credential helper on my machine fails to pull its base image; the base branch fails the same way. On 7.7.1, //tests/helm_charts, //tests/with_external_deps and //helm/private pass.
  • .bazelrc sets --lockfile_mode=off, so CI never stores a fact. I checked the lockfile behaviour by hand:
    • Bazel 9.3.1 and 8.5.0 write the facts. 8.4.2 and 7.7.1 write none and record the repositories with their digests instead.
    • A fresh output base with --lockfile_mode=error and downloads disabled fetches the chart from the lockfile without a lookup.
    • A malformed fact is looked up again. A well-formed but wrong fact fails with a checksum error, and deleting the entry recovers.
    • Three unpinned tags with one url make one lookup, down from three.
  • Not run locally: Windows, and a registry that needs credentials.

Limits

  • All tags resolve in one evaluation. A failing lookup (an unknown version, an unreachable registry) fails every repository from the extension. Setting sha256 skips the lookup.
  • Without sha256, the digest comes from the same registry as the chart, so it is trusted on first use.
  • On a cold repository cache the rule repeats the token and manifest requests that the extension made. ghcr.io prints a 401 warning for the first blob request.
  • @<name> does not resolve to the chart; use @<name>//:<chart>. The alias is named with the canonical repository name, as it was before this change.
  • With the lockfile off, each CI job looks up two charts on quay.io and ghcr.io. If the first run shows 429, pin sha256 on the second chart.

* Add the `helm_charts` module extension with a `chart(name, url, sha256)`
  tag. It takes the digest from the tag, the stored facts or the registry
  manifest, then declares `helm_import_repository` with it. On Bazel 8.5+
  a looked-up digest is stored as a fact in `MODULE.bazel.lock`. Older
  Bazel records the repository and its digest in the lockfile instead.
* Move the registry code from `import.bzl` to `oci_chart.bzl` so it runs
  with `repository_ctx` or `module_ctx`. `import_authn.bzl` blanks a file
  where `module_ctx` has no `delete`.
* Take the chart name and version from an `oci://` url, and split an http
  file name at the first dash followed by a version number. A version such
  as `1.21.0-beta.0` no longer gives the target `cert-manager-1.21.0`.
* Add unit tests for the helpers, and chart tests for a looked-up, a pinned
  and a pre-release chart.

@abrisco abrisco left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Sorry, for the delay here, I'll take a look this weekend!

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants