From 9c8be026d050b639edf0796375e43fad99e00eb7 Mon Sep 17 00:00:00 2001 From: mikkeldamsgaard Date: Tue, 24 Feb 2026 21:32:55 +0100 Subject: [PATCH 1/2] feat: add Helm chart unit tests and CI validation. Add 8 helm-unittest tests covering deployment rendering, securityContext enforcement, disabled sampleDeployment producing no resources, multiple initContainers, extraVolumes/extraVolumeMounts, image configuration, workdir mount, and labels. Add helm-unittest plugin install and run step to CI helm-lint job. Closes #8 --- .github/workflows/ci.yml | 2 + CHANGELOG.md | 2 + charts/initium/tests/deployment_test.yaml | 265 ++++++++++++++++++++++ 3 files changed, 269 insertions(+) create mode 100644 charts/initium/tests/deployment_test.yaml diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index ca4ca42..1f43179 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -41,5 +41,7 @@ jobs: steps: - uses: actions/checkout@v4 - uses: azure/setup-helm@v4 + - run: helm plugin install https://github.com/helm-unittest/helm-unittest.git - run: helm lint charts/initium - run: helm template test-release charts/initium --set sampleDeployment.enabled=true --set 'initContainers[0].name=wait' --set 'initContainers[0].command[0]=wait-for' --set 'initContainers[0].args[0]=--target' --set 'initContainers[0].args[1]=tcp://localhost:5432' + - run: helm unittest charts/initium diff --git a/CHANGELOG.md b/CHANGELOG.md index acbe61e..3f42f4c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] ### Added +- Helm chart unit tests using helm-unittest plugin (`charts/initium/tests/deployment_test.yaml`) covering deployment rendering, securityContext enforcement, disabled sampleDeployment, multiple initContainers, extraVolumes/extraVolumeMounts, image configuration, workdir mount, and labels +- `helm unittest` step added to CI helm-lint job with automatic plugin installation - Duration unit support for all time parameters (`--timeout`, `--initial-delay`, `--max-delay`, seed phase `timeout`, seed wait-for `timeout`): accepts `ms`, `s`, `m`, `h` suffixes with decimal values (e.g. `1.5m`, `2.7s`) and combined units (e.g. `1m30s`, `2s700ms`, `18h36m4s200ms`); bare numbers default to seconds - `src/duration.rs` module with `parse_duration` and `format_duration` utilities - Environment variable support for all CLI flags via `INITIUM_*` prefix (e.g., `--json` → `INITIUM_JSON`, `--timeout` → `INITIUM_TIMEOUT`); flag values take precedence over env vars diff --git a/charts/initium/tests/deployment_test.yaml b/charts/initium/tests/deployment_test.yaml new file mode 100644 index 0000000..13d0403 --- /dev/null +++ b/charts/initium/tests/deployment_test.yaml @@ -0,0 +1,265 @@ +suite: deployment template tests +templates: + - templates/deployment.yaml +tests: + - it: should not render any resources when sampleDeployment is disabled + set: + sampleDeployment: + enabled: false + asserts: + - hasDocuments: + count: 0 + + - it: should render a Deployment when sampleDeployment is enabled + set: + sampleDeployment: + enabled: true + name: test-app + replicas: 2 + mainContainer: + image: nginx:1.27-alpine + port: 80 + initContainers: + - name: wait-for-db + command: + - wait-for + args: + - --target + - tcp://postgres:5432 + asserts: + - isKind: + of: Deployment + - equal: + path: metadata.name + value: test-app + - equal: + path: spec.replicas + value: 2 + - equal: + path: spec.template.spec.initContainers[0].name + value: wait-for-db + - equal: + path: spec.template.spec.initContainers[0].command + value: + - wait-for + - equal: + path: spec.template.spec.initContainers[0].args + value: + - --target + - tcp://postgres:5432 + - equal: + path: spec.template.spec.containers[0].image + value: nginx:1.27-alpine + + - it: should always apply securityContext to initContainers + set: + sampleDeployment: + enabled: true + securityContext: + runAsNonRoot: true + runAsUser: 65534 + runAsGroup: 65534 + readOnlyRootFilesystem: true + allowPrivilegeEscalation: false + capabilities: + drop: + - ALL + initContainers: + - name: init-one + command: + - wait-for + args: + - --target + - tcp://db:5432 + asserts: + - equal: + path: spec.template.spec.initContainers[0].securityContext.runAsNonRoot + value: true + - equal: + path: spec.template.spec.initContainers[0].securityContext.runAsUser + value: 65534 + - equal: + path: spec.template.spec.initContainers[0].securityContext.readOnlyRootFilesystem + value: true + - equal: + path: spec.template.spec.initContainers[0].securityContext.allowPrivilegeEscalation + value: false + - equal: + path: spec.template.spec.initContainers[0].securityContext.capabilities.drop + value: + - ALL + + - it: should render multiple initContainers correctly + set: + sampleDeployment: + enabled: true + initContainers: + - name: wait-for-db + command: + - wait-for + args: + - --target + - tcp://postgres:5432 + - name: run-migration + command: + - migrate + args: + - -- + - flyway + - migrate + env: + - name: FLYWAY_URL + value: "jdbc:postgresql://postgres:5432/mydb" + - name: seed-data + command: + - seed + args: + - --spec + - /seeds/seed.yaml + asserts: + - equal: + path: spec.template.spec.initContainers[0].name + value: wait-for-db + - equal: + path: spec.template.spec.initContainers[1].name + value: run-migration + - equal: + path: spec.template.spec.initContainers[1].env[0].name + value: FLYWAY_URL + - equal: + path: spec.template.spec.initContainers[2].name + value: seed-data + - equal: + path: spec.template.spec.initContainers[2].args + value: + - --spec + - /seeds/seed.yaml + + - it: should include extraVolumes and extraVolumeMounts + set: + sampleDeployment: + enabled: true + initContainers: + - name: render-config + command: + - render + args: + - --template + - /templates/app.conf.tmpl + - --output + - app.conf + extraVolumes: + - name: templates + configMap: + name: app-templates + - name: secrets + secret: + secretName: app-secrets + extraVolumeMounts: + - name: templates + mountPath: /templates + readOnly: true + - name: secrets + mountPath: /secrets + readOnly: true + asserts: + - contains: + path: spec.template.spec.volumes + content: + name: templates + configMap: + name: app-templates + - contains: + path: spec.template.spec.volumes + content: + name: secrets + secret: + secretName: app-secrets + - contains: + path: spec.template.spec.initContainers[0].volumeMounts + content: + name: templates + mountPath: /templates + readOnly: true + - contains: + path: spec.template.spec.initContainers[0].volumeMounts + content: + name: secrets + mountPath: /secrets + readOnly: true + + - it: should use the correct image repository and tag + set: + sampleDeployment: + enabled: true + image: + repository: ghcr.io/kitstream/initium + tag: "1.2.3" + pullPolicy: Always + initContainers: + - name: wait + command: + - wait-for + args: + - --target + - tcp://db:5432 + asserts: + - equal: + path: spec.template.spec.initContainers[0].image + value: "ghcr.io/kitstream/initium:1.2.3" + - equal: + path: spec.template.spec.initContainers[0].imagePullPolicy + value: Always + + - it: should include workdir volume mount on all initContainers + set: + sampleDeployment: + enabled: true + workdir: /custom-work + initContainers: + - name: init-one + command: + - wait-for + args: + - --target + - tcp://db:5432 + - name: init-two + command: + - exec + args: + - -- + - echo + - hello + asserts: + - contains: + path: spec.template.spec.initContainers[0].volumeMounts + content: + name: workdir + mountPath: /custom-work + - contains: + path: spec.template.spec.initContainers[1].volumeMounts + content: + name: workdir + mountPath: /custom-work + + - it: should include standard labels on the deployment + set: + sampleDeployment: + enabled: true + name: labeled-app + initContainers: + - name: wait + command: + - wait-for + args: + - --target + - tcp://db:5432 + asserts: + - equal: + path: metadata.labels["app.kubernetes.io/name"] + value: labeled-app + - equal: + path: metadata.labels["app.kubernetes.io/managed-by"] + value: Helm + - isNotEmpty: + path: metadata.labels["helm.sh/chart"] From 74c22c5d96c9ce5d99ebc8955ae8e45326690ec6 Mon Sep 17 00:00:00 2001 From: mikkeldamsgaard Date: Tue, 24 Feb 2026 21:35:51 +0100 Subject: [PATCH 2/2] fix: add --verify=false to helm plugin install for CI compatibility and added --verion --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 1f43179..e3799c9 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -41,7 +41,7 @@ jobs: steps: - uses: actions/checkout@v4 - uses: azure/setup-helm@v4 - - run: helm plugin install https://github.com/helm-unittest/helm-unittest.git + - run: helm plugin install --verify=false https://github.com/helm-unittest/helm-unittest.git --version v0.5.1 - run: helm lint charts/initium - run: helm template test-release charts/initium --set sampleDeployment.enabled=true --set 'initContainers[0].name=wait' --set 'initContainers[0].command[0]=wait-for' --set 'initContainers[0].args[0]=--target' --set 'initContainers[0].args[1]=tcp://localhost:5432' - run: helm unittest charts/initium