helm: add helm-unittest harness for the charts

The charts have no unit tests. CI runs ct lint plus a helm template and
kustomize build, which prove the charts render but never assert what
they render, so conditional template logic goes unverified.

Add helm-unittest, pinned and installed as a standalone binary, a
test.helm make target, and a Helm Unittests workflow that runs it. The
first suite covers the toolbox deployment: the image composed from the
toolbox and cephImage settings, the precedence of both over the older
toolbox.image setting, the hostNetwork branch driven by the cluster
network provider, and the revisionHistoryLimit guard.

The new job is a required check in .mergify.yml, alongside the existing
chart linting.

Suites live under tests/ in a chart and are excluded from the packaged
chart via .helmignore. The runner is invoked with --strict so that a
misspelled key fails the suite instead of silently rendering defaults.

Signed-off-by: Joshua Hoblitt <josh@hoblitt.com>
This commit is contained in:
Joshua Hoblitt committed 2026-07-29 10:40:43 -07:00
1 parent ac0c3b45bc
commit fa89038264
9 files changed
+202 -1

No files matched your search

+1 -1
View File
@@ -32,4 +32,4 @@ jobs:
- name: Lint the helm charts
run: make lint.helm
run: make lint.helm
+31
View File
@@ -0,0 +1,31 @@
name: Helm Unittests
on:
push:
tags:
- v*
branches:
- master
- release-*
pull_request:
branches:
- master
- release-*
# cancel the in-progress workflow when PR is refreshed.
concurrency:
group: ${{ github.workflow }}-${{ github.event_name == 'pull_request' && github.head_ref || github.sha }}
cancel-in-progress: true
permissions:
contents: read
jobs:
helm-unittests:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- name: Unit test the helm charts
run: make test.helm
+4
View File
@@ -29,6 +29,7 @@ pull_request_rules:
- "check-success=gen-rbac"
- "check-success=golangci-lint"
- "check-success=govulncheck"
- "check-success=helm-unittests"
- "check-success=lint"
- "check-success=lint-test"
- "check-success=linux-build-all (1.23)"
@@ -89,6 +90,7 @@ pull_request_rules:
- "check-success=gen-rbac"
- "check-success=golangci-lint"
- "check-success=govulncheck"
- "check-success=helm-unittests"
- "check-success=lint"
- "check-success=lint-test"
- "check-success=linux-build-all (1.24)"
@@ -148,6 +150,7 @@ pull_request_rules:
- "check-success=gen-rbac"
- "check-success=golangci-lint"
- "check-success=govulncheck"
- "check-success=helm-unittests"
- "check-success=lint"
- "check-success=lint-test"
- "check-success=linux-build-all (1.25)"
@@ -207,6 +210,7 @@ pull_request_rules:
- "check-success=gen-rbac"
- "check-success=golangci-lint"
- "check-success=govulncheck"
- "check-success=helm-unittests"
- "check-success=lint"
- "check-success=lint-test"
- "check-success=linux-build-all (1.25)"
@@ -235,6 +235,15 @@ go test -coverprofile=coverage.out
go tool cover -html=coverage.out -o coverage.html
```
The Helm charts have their own unit tests, which render the chart templates and assert on the result:
```console
make test.helm
```
Suites live in `tests/*_test.yaml` under each chart and are written in the
[helm-unittest](https://github.com/helm-unittest/helm-unittest) YAML format.
### Writing unit tests
Good unit tests start with easily testable code. Small chunks ("units") of code can be easily tested
+4
View File
@@ -222,6 +222,10 @@ lint.helm: $(HELM) $(KUSTOMIZE) ## Check the helm charts
$(KUSTOMIZE) build >/dev/null
rm templated.yaml kustomization.yaml
.PHONY: test.helm
test.helm: $(HELM_UNITTEST) ## Run the helm chart unit tests
$(HELM_UNITTEST) --strict $(addprefix $(HELM_CHARTS_DIR)/,$(HELM_CHARTS))
.PHONY: lint.quick
lint.quick: lint.yaml lint.shell lint.make lint.go lint.helm lint.markdown ## run some (faster) linters
.PHONY: lint
+18
View File
@@ -131,3 +131,21 @@ $(HELM_DOCS): | $(TOOLS_HOST_DIR) ## Installs helm-docs
@GOBIN=$(TOOLS_HOST_DIR)/tmp GO111MODULE=on go install $(HELM_DOCS_REPO)@$(HELM_DOCS_VERSION)
@mv $(TOOLS_HOST_DIR)/tmp/helm-docs $(HELM_DOCS)
@rm -fr $(TOOLS_HOST_DIR)/tmp
# ====================================================================================
# Makefile helper functions for helm-unittest: https://github.com/helm-unittest/helm-unittest
#
HELM_UNITTEST_VERSION := v1.1.2
HELM_UNITTEST := $(TOOLS_HOST_DIR)/helm-unittest-$(HELM_UNITTEST_VERSION)
# the release artifacts label macOS "macos" where go calls it "darwin"
HELM_UNITTEST_PLATFORM := $(subst darwin,macos,$(shell go env GOHOSTOS))-$(GOHOSTARCH)
# helm-unittest ships as a helm plugin, but the binary it wraps runs standalone,
# so it is installed and invoked directly rather than through `helm plugin install`.
$(HELM_UNITTEST): | $(TOOLS_HOST_DIR) ## Installs helm-unittest
@echo === installing helm-unittest
@mkdir -p $(TOOLS_HOST_DIR)/tmp
@curl -fsSL https://github.com/helm-unittest/helm-unittest/releases/download/$(HELM_UNITTEST_VERSION)/helm-unittest-$(HELM_UNITTEST_PLATFORM)-$(patsubst v%,%,$(HELM_UNITTEST_VERSION)).tgz | tar -xz -C $(TOOLS_HOST_DIR)/tmp untt-$(HELM_UNITTEST_PLATFORM)
@mv $(TOOLS_HOST_DIR)/tmp/untt-$(HELM_UNITTEST_PLATFORM) $(HELM_UNITTEST)
@rm -fr $(TOOLS_HOST_DIR)/tmp
@@ -20,3 +20,5 @@
.idea/
*.tmproj
*.tmpl
# helm-unittest suites
tests/
@@ -0,0 +1,131 @@
suite: toolbox deployment
templates:
- deployment.yaml
tests:
- it: renders nothing when the toolbox is disabled
set:
toolbox.enabled: false
asserts:
- hasDocuments:
count: 0
- it: composes the image from the cephImage settings
set:
toolbox.enabled: true
cephImage.repository: quay.io/ceph/ceph
cephImage.tag: v20.2.2
asserts:
- equal:
path: spec.template.spec.containers[0].image
value: quay.io/ceph/ceph:v20.2.2
- it: tracks a custom cephImage repository and tag
set:
toolbox.enabled: true
cephImage.repository: registry.example.com/ceph/ceph
cephImage.tag: v19.2.3
asserts:
- equal:
path: spec.template.spec.containers[0].image
value: registry.example.com/ceph/ceph:v19.2.3
- it: prefers toolbox.image over the cephImage settings
set:
toolbox.enabled: true
cephImage.repository: quay.io/ceph/ceph
cephImage.tag: v20.2.2
toolbox.image: registry.example.com/rook/ceph:v1.18.0
asserts:
- equal:
path: spec.template.spec.containers[0].image
value: registry.example.com/rook/ceph:v1.18.0
- it: takes the tag from cephImage when only toolbox.repository is set
set:
toolbox.enabled: true
cephImage.repository: quay.io/ceph/ceph
cephImage.tag: v20.2.2
toolbox.repository: registry.example.com/rook/ceph
asserts:
- equal:
path: spec.template.spec.containers[0].image
value: registry.example.com/rook/ceph:v20.2.2
- it: takes the repository from cephImage when only toolbox.tag is set
set:
toolbox.enabled: true
cephImage.repository: quay.io/ceph/ceph
cephImage.tag: v20.2.2
toolbox.tag: v19.2.3
asserts:
- equal:
path: spec.template.spec.containers[0].image
value: quay.io/ceph/ceph:v19.2.3
- it: composes toolbox.repository and toolbox.tag when both are set
set:
toolbox.enabled: true
cephImage.repository: quay.io/ceph/ceph
cephImage.tag: v20.2.2
toolbox.repository: registry.example.com/rook/ceph
toolbox.tag: v1.18.0
asserts:
- equal:
path: spec.template.spec.containers[0].image
value: registry.example.com/rook/ceph:v1.18.0
- it: prefers toolbox.repository over toolbox.image
set:
toolbox.enabled: true
cephImage.repository: quay.io/ceph/ceph
cephImage.tag: v20.2.2
toolbox.image: registry.example.com/legacy/ceph:v18.2.0
toolbox.repository: registry.example.com/rook/ceph
asserts:
- equal:
path: spec.template.spec.containers[0].image
value: registry.example.com/rook/ceph:v20.2.2
- it: prefers toolbox.tag over toolbox.image
set:
toolbox.enabled: true
cephImage.repository: quay.io/ceph/ceph
cephImage.tag: v20.2.2
toolbox.image: registry.example.com/legacy/ceph:v18.2.0
toolbox.tag: v19.2.3
asserts:
- equal:
path: spec.template.spec.containers[0].image
value: quay.io/ceph/ceph:v19.2.3
- it: leaves hostNetwork unset unless the cluster uses the host network
set:
toolbox.enabled: true
asserts:
- notExists:
path: spec.template.spec.hostNetwork
- it: enables hostNetwork for a host network provider
set:
toolbox.enabled: true
cephClusterSpec.network.provider: host
asserts:
- equal:
path: spec.template.spec.hostNetwork
value: true
- it: omits revisionHistoryLimit unless it is set
set:
toolbox.enabled: true
asserts:
- notExists:
path: spec.revisionHistoryLimit
- it: applies revisionHistoryLimit when set
set:
toolbox.enabled: true
revisionHistoryLimit: 3
asserts:
- equal:
path: spec.revisionHistoryLimit
value: 3
+2
View File
@@ -20,3 +20,5 @@
.idea/
*.tmproj
*.tmpl
# helm-unittest suites
tests/