rules_helm
Chart-as-a-module: build, lint, and publish Helm charts in Bazel (the Helm analog of rules_oci).
| Latest | 0.2.0 |
|---|---|
| Versions | 2 |
| Category | Bazel rules |
| Maintainers | Matt Marshall |
| Registry | https://registry.tbzl.dev/modules/rules_helm/ |
| Source | github.com/tomato-bazel/rules_helm |
bazel_dep(name = "rules_helm", version = "0.2.0")
View source & releases on GitHub ↗
Chart-as-a-module: build, lint, and publish Helm charts in Bazel — the Helm
analog of how this org already does container images (rules_oci) and protos.
Helm itself is fetched hermetically (a pinned per-platform binary from
get.helm.sh), so bazel build-ing a chart needs nothing installed on the host.
helm_template
Render a chart someone else published into manifests you can read, review, diff and gate:
load("@rules_helm//helm:defs.bzl", "helm_template")
helm_template(
name = "argocd_manifests",
chart = "@argo_cd_chart//file", # http_file, pinned by sha256
release_name = "argocd",
namespace = "argocd",
values = ["values.yaml"],
)
helm install renders and applies in one motion, so what actually reached the
cluster is knowable only afterwards, by asking the cluster. Rendering to a file
makes it reviewable before anything moves, byte-stable across rebuilds, and
diffable against a live cluster.
The action has no network, so chart must be a self-contained .tgz — a chart
with unvendored subcharts fails here rather than silently fetching them. Pin it
with http_file + sha256 so the render is a pure function of committed inputs.
Rules
| Rule | Kind | What it does |
|---|---|---|
helm_chart | build action | Packages a chart directory into <name>.tgz. |
helm_lint | build action | helm lint (fails the build); cacheable marker output. |
helm_push | bazel run | helm push <tgz> oci://<repository>. |
Use
MODULE.bazel:
bazel_dep(name = "rules_helm", version = "0.1.0")
BUILD.bazel next to a chart:
load("@rules_helm//helm:defs.bzl", "helm_chart", "helm_lint", "helm_push")
_SRCS = ["values.yaml"] + glob(["templates/**", ".helmignore"])
helm_chart(
name = "chart",
chart_yaml = "Chart.yaml",
srcs = _SRCS,
version = "1.2.3", # optional; defaults to Chart.yaml
app_version = "1.2.3", # optional; defaults to Chart.yaml
)
helm_lint(name = "lint", chart_yaml = "Chart.yaml", srcs = _SRCS)
helm_push(name = "push", chart = ":chart", repository = "ghcr.io/fastverk/charts")
bazel build //path/to:chart # -> bazel-bin/path/to/chart.tgz
bazel build //path/to:lint # fails if the chart doesn't lint
helm registry login ghcr.io ... # auth once (CI uses GITHUB_TOKEN)
bazel run //path/to:push # publishes the .tgz
srcs must list every file helm should package — the action runs in a
sandbox containing only the declared inputs. helm_chart is for leaf charts;
a chart with subchart dependencies must have its charts/ vendored
(helm dependency build) and the vendored archives passed in srcs.
Bumping helm
Two edits in helm/repositories.bzl: HELM_VERSION and
the four HELM_SHA256 lines. Refresh a checksum with:
curl -fsSL https://get.helm.sh/helm-v<ver>-<platform>.tar.gz.sha256sum
This v0.1.0 downloads only the host platform’s binary (chart packaging always runs host-native). A future revision can graduate this to a full multi-platform Bazel toolchain for exec-platform selection (RBE).
Publishing this module
Tag a release and run the registry tool (same flow as the other fastverk
rules_* modules):
git tag v0.1.0 && git push origin v0.1.0
# in repos/bazel-registry:
bazel run //tools/rels -- release --repo fastverk/rules_helm --version 0.1.0 Usage#
Real usage, taken from the module’s examples/.
examples/smoke/BUILD.bazel
load("@bazel_skylib//rules:diff_test.bzl", "diff_test")
load("@rules_helm//helm:defs.bzl", "helm_chart", "helm_lint", "helm_push", "helm_template")
# All chart files the action needs (the sandbox sees only declared inputs).
_SRCS = [
"chart/values.yaml",
"chart/templates/deployment.yaml",
]
helm_chart(
name = "smoke",
chart_yaml = "chart/Chart.yaml",
srcs = _SRCS,
version = "0.1.0",
app_version = "0.0.0",
)
helm_lint(
name = "lint",
chart_yaml = "chart/Chart.yaml",
srcs = _SRCS,
)
helm_push(
name = "push",
chart = ":smoke",
repository = "ghcr.io/fastverk/charts",
)
# ── helm_template ────────────────────────────────────────────────────────────
# Round-trips the chart this package already builds: helm_chart packages it, then
# helm_template renders it back to manifests. Self-contained, so the test proves
# the rule works without depending on any third-party chart staying published.
helm_template(
name = "smoke_rendered",
chart = ":smoke",
namespace = "smoke-ns",
release_name = "smoke-release",
values = ["chart/values.yaml"],
)
# The whole point of rendering to a file is that the output is reviewable and
# STABLE. If a helm upgrade or a rule change perturbs it, that must surface as a
# failing test rather than as a silent difference in what reaches a cluster.
diff_test(
name = "smoke_rendered_stable",
file1 = "expected_smoke_rendered.yaml",
file2 = ":smoke_rendered",
)Conformance#
No gate findings. 8 contested atoms. See how gating works or the full report.
Contested atoms
Third-party modules where this module resolves a different version than others do. Not a violation of anything this module did — it is the actionable form of a registry-level convergence finding, and the sentence a maintainer can act on.
| Atom | Resolved here | Elsewhere |
|---|---|---|
apple_support | 1.24.2 | 2.2.0 ×1 |
bazel_skylib | 1.8.2 | 1.9.0 ×2 |
nlohmann_json | 3.6.1 | 3.12.0.bcr.1 ×1 |
protobuf | 33.4 | 34.0.bcr.1 ×2 |
rules_jvm_external | 6.7 | 6.8 ×4 |
rules_python | 1.7.0 | 2.0.1 ×1 |
rules_swift | 3.1.2 | 3.6.1 ×1 |
upb | 0.0.0-20220923-a547704 | 0.0.0-20230516-61a97ef ×1 |
Dependencies#
Depends on
Used by (1 in the registry)
Versions#
2 published versions, newest first. Each resolves to an immutable, integrity-checked archive.
| Version | Integrity (sha256) | Source archive |
|---|---|---|
0.2.0 latest | nfhDMEqg4nHz/xF+… | tag archive ↗ |
0.1.0 | cpmWWy6NMci5bDFJ… | tag archive ↗ |
Changelog#
0.2.0 — helm_template
helm_template— render a packaged chart to a single manifest file, as a build action.helm_chartpackages a chart WE author; this renders a chart SOMEONE ELSE published into something we can read, review, diff and gate. Opposite directions, and the second is what a consumer of a third-party chart actually needs.- The point is that the output becomes an artifact rather than an event.
helm installrenders and applies in one motion, so what reached the cluster is knowable only afterwards by asking the cluster. Rendering to a file makes it reviewable BEFORE anything moves, byte-stable across rebuilds, and diffable against a live cluster withrules_k8s’sk8s_diff. - Fully offline: the action has no network, so
chartmust be a self-contained.tgz. A chart with unvendored subchart dependencies fails loudly here rather than silently fetching them. Pin it withhttp_file+ sha256 so the render is a pure function of committed inputs. setuses--set-stringdeliberately. Plain--setapplies YAML type inference, so an image tag like1.34renders as a float andtruebecomes a bool. A value that must keep its type belongs in a values file.- Attrs:
chart,values,set,release_name,namespace,include_crds,kube_version,api_versions. - Smoke test round-trips the example chart through
helm_chartthenhelm_templateand diff-tests the result, so a helm upgrade that perturbs the output fails a test rather than silently changing what would reach a cluster. The example template now reads.Release.Name/.Release.Namespaceso the golden file actually proves those flags are plumbed — it did not before.
All notable changes to this module are documented here; this project adheres to Keep a Changelog and SemVer.