This is an automated email from the ASF dual-hosted git repository.
villebro pushed a commit to branch main
in repository
https://gitbox.apache.org/repos/asf/superset-kubernetes-operator.git
The following commit(s) were added to refs/heads/main by this push:
new 02640b4 test(helm): add chart unit tests with value-coverage gate
(#217)
02640b4 is described below
commit 02640b4598d1b16cdceca1f8f9ad826f8f137d20
Author: Ville Brofeldt <[email protected]>
AuthorDate: Thu Jul 23 13:27:26 2026 -0700
test(helm): add chart unit tests with value-coverage gate (#217)
* test(helm): add chart unit tests with value-coverage gat
* bump to Helm 4
* split releasing helm commands
* test(helm): slim suites to defaults + comprehensive, cover new knobs
Consolidate the chart unit tests into two broad suites — a stripped-down
defaults render and a comprehensive full-options render — per the testing
guideline favoring broad happy-path tests over many granular ones. Start
slim and add targeted tests later if regressions surface. Drops the
separate helpers suite.
Cover the new revisionHistoryLimit and topologySpreadConstraints knobs in
the comprehensive values file, and fix the Rat license exclude to match
the generated .snap files by basename (.*\.snap).
* refactor(make): make `lint` aggregate all linters
Mirror the codegen/test aggregator pattern so a single command covers a
whole category. `make lint` now runs the Go, Markdown, and Helm-chart
linters; `make lint-fix` runs every auto-fixer. Rename the granular
targets for symmetry: lint-go / lint-go-fix / lint-go-config (were
lint / lint-fix / lint-config) and lint-md-fix (was format-md).
CI and the pre-commit hook call the granular lint-go (Markdown and Helm
run in their own CI steps/jobs); release-rc.sh and the release checklist
drop the now-redundant helm-lint. Helm is documented as a dev
prerequisite since the lint aggregator shells out to `helm lint`.
* ci: match .asf.yaml required check to renamed Helm job
The Helm CI job is now "Helm lint & test"; update the branch-protection
required_status_checks context to match so the check is satisfied once
this lands on main.
* test(helm): snapshot the full render, not a hand-picked subset
Drop the per-template matchSnapshot picking (and the redundant granular
asserts) in favour of a single full-chart snapshot per suite. With no
`templates:` restriction the whole chart renders, so each snapshot mirrors
exactly what that values file deploys — the minimal suite now captures the
manager ClusterRole, leader-election RBAC, and metrics RBAC that a default
install creates, and templates added to the chart later are covered
automatically. A regression in any rendered resource (e.g. default
ClusterRole rendering) now surfaces as a snapshot diff.
---
.asf.yaml | 1 +
.githooks/pre-commit | 2 +-
.github/workflows/ci.yaml | 12 +-
.rat-excludes | 3 +
Makefile | 28 +-
.../superset-operator/.helmignore | 30 +-
.../tests/__snapshot__/full_options_test.yaml.snap | 672 +++++++++++++++++++++
.../tests/__snapshot__/minimal_test.yaml.snap | 400 ++++++++++++
.../superset-operator/tests/full_options_test.yaml | 36 ++
charts/superset-operator/tests/minimal_test.yaml | 34 ++
.../tests/values/full-options.yaml | 155 +++++
docs/contributing/development-guidelines.md | 23 +
docs/contributing/development-setup.md | 19 +-
docs/contributing/releasing.md | 5 +-
docs/getting-started.md | 2 +-
docs/user-guide/installation.md | 2 +-
renovate.json | 8 +
scripts/check-chart-values-covered.sh | 73 +++
scripts/install-helm-unittest.sh | 46 ++
scripts/install-helm.sh | 5 +-
scripts/{install-helm.sh => install-yq.sh} | 22 +-
scripts/release-rc.sh | 10 +-
22 files changed, 1538 insertions(+), 50 deletions(-)
diff --git a/.asf.yaml b/.asf.yaml
index d7c44da..8b78e44 100644
--- a/.asf.yaml
+++ b/.asf.yaml
@@ -62,6 +62,7 @@ github:
- Verify codegen
- Verify supported-versions table
- Docker build
+ - Helm lint & test
- Unit & Integration
- Apache Rat
required_signatures: false
diff --git a/.githooks/pre-commit b/.githooks/pre-commit
index b47c313..3bdfec7 100755
--- a/.githooks/pre-commit
+++ b/.githooks/pre-commit
@@ -17,7 +17,7 @@
set -e
# Run golangci-lint (includes gofmt, goimports, govet, and all configured
linters)
-make lint
+make lint-go
# Reformat staged Markdown with rumdl. `check --fix` exits non-zero (aborting
# the commit via `set -e`) if any unfixable issues remain. If it only applied
diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml
index e79fd01..ed2dc9c 100644
--- a/.github/workflows/ci.yaml
+++ b/.github/workflows/ci.yaml
@@ -49,7 +49,7 @@ jobs:
with:
go-version-file: go.mod
- name: Lint
- run: make lint
+ run: make lint-go
- name: Lint Markdown
run: make lint-md
- name: Vulnerability scan (govulncheck)
@@ -128,7 +128,7 @@ jobs:
run: ./scripts/validate-actions.sh
helm-lint:
- name: Helm lint
+ name: Helm lint & test
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 #
v7.0.0
@@ -139,5 +139,13 @@ jobs:
go-version-file: go.mod
- name: Install Helm
run: bash scripts/install-helm.sh
+ - name: Install helm-unittest plugin
+ run: bash scripts/install-helm-unittest.sh
+ - name: Install yq
+ run: bash scripts/install-yq.sh
- name: Lint Helm chart
run: make helm-lint
+ - name: Run Helm chart unit tests
+ run: make helm-test
+ - name: Verify chart test value coverage
+ run: make helm-values-covered
diff --git a/.rat-excludes b/.rat-excludes
index 2ecd819..7c6976d 100644
--- a/.rat-excludes
+++ b/.rat-excludes
@@ -38,6 +38,9 @@
.*values\.yaml
.*values\.schema\.json
+# Helm chart test snapshots (generated by helm-unittest)
+.*\.snap
+
# Documentation
.*api-reference\.md
.*docs-requirements\.txt
diff --git a/Makefile b/Makefile
index 934de82..2f9a210 100644
--- a/Makefile
+++ b/Makefile
@@ -178,6 +178,14 @@ helm: manifests helm-sync-crds ## Sync CRDs into Helm
chart and package it. The
helm-lint: helm-sync-crds ## Lint the Helm chart (syncs CRDs first).
helm lint $(HELM_CHART_DIR)
+.PHONY: helm-test
+helm-test: ## Run the Helm chart unit tests (requires the helm-unittest
plugin; see scripts/install-helm-unittest.sh).
+ helm unittest $(HELM_CHART_DIR)
+
+.PHONY: helm-values-covered
+helm-values-covered: ## Verify every values.yaml knob is exercised by the
comprehensive chart test.
+ CHART_DIR=$(HELM_CHART_DIR) ./scripts/check-chart-values-covered.sh
+
##@ Development
.PHONY: codegen
@@ -261,23 +269,29 @@ cleanup-test-e2e: ## Tear down the Kind cluster used for
e2e tests
@$(KIND) delete cluster --name $(KIND_CLUSTER)
.PHONY: lint
-lint: golangci-lint ## Run golangci-lint linter
- $(GOLANGCI_LINT) run
+lint: lint-go lint-md helm-lint ## Run all linters (Go, Markdown, Helm chart).
.PHONY: lint-fix
-lint-fix: golangci-lint ## Run golangci-lint linter and perform fixes
+lint-fix: lint-go-fix lint-md-fix ## Auto-fix all linters that support it (Go,
Markdown).
+
+.PHONY: lint-go
+lint-go: golangci-lint ## Run the Go linter (golangci-lint).
+ $(GOLANGCI_LINT) run
+
+.PHONY: lint-go-fix
+lint-go-fix: golangci-lint ## Run the Go linter and apply fixes.
$(GOLANGCI_LINT) run --fix
-.PHONY: lint-config
-lint-config: golangci-lint ## Verify golangci-lint linter configuration
+.PHONY: lint-go-config
+lint-go-config: golangci-lint ## Verify the golangci-lint configuration.
$(GOLANGCI_LINT) config verify
.PHONY: lint-md
lint-md: rumdl ## Lint Markdown files (check only).
$(RUMDL) check .
-.PHONY: format-md
-format-md: rumdl ## Auto-fix Markdown formatting in place.
+.PHONY: lint-md-fix
+lint-md-fix: rumdl ## Auto-fix Markdown formatting in place.
$(RUMDL) check --fix .
.PHONY: hooks
diff --git a/scripts/install-helm.sh b/charts/superset-operator/.helmignore
similarity index 56%
copy from scripts/install-helm.sh
copy to charts/superset-operator/.helmignore
index e8740fb..c54c744 100644
--- a/scripts/install-helm.sh
+++ b/charts/superset-operator/.helmignore
@@ -1,4 +1,3 @@
-#!/usr/bin/env bash
# Licensed to the Apache Software Foundation (ASF) under one or more
# contributor license agreements. See the NOTICE file distributed with
# this work for additional information regarding copyright ownership.
@@ -14,18 +13,19 @@
# See the License for the specific language governing permissions and
# limitations under the License.
-set -euo pipefail
+# Patterns to ignore when building packages.
+# Chart unit tests and their snapshots are development-only and must not ship
+# in the released chart artifact.
+tests/
+README.md.gotmpl
-HELM_VERSION="${HELM_VERSION:-v3.19.2}"
-HELM_PLATFORM="${HELM_PLATFORM:-linux-amd64}"
-HELM_SHA256="${HELM_SHA256:-2114c9dea2844dce6d0ee2d792a9aae846be8cf53d5b19dc2988b5a0e8fec26e}"
-
-archive="helm-${HELM_VERSION}-${HELM_PLATFORM}.tar.gz"
-url="https://get.helm.sh/${archive}"
-tmpdir="$(mktemp -d)"
-trap 'rm -rf "${tmpdir}"' EXIT
-
-curl -fsSL "${url}" -o "${tmpdir}/${archive}"
-printf '%s %s\n' "${HELM_SHA256}" "${tmpdir}/${archive}" | sha256sum -c -
-tar -xzf "${tmpdir}/${archive}" -C "${tmpdir}"
-sudo install -m 0755 "${tmpdir}/${HELM_PLATFORM}/helm" /usr/local/bin/helm
+# Common VCS / editor / OS cruft.
+.git/
+.gitignore
+*.tmp
+*.bak
+*.swp
+*.orig
+.DS_Store
+.idea/
+.vscode/
diff --git
a/charts/superset-operator/tests/__snapshot__/full_options_test.yaml.snap
b/charts/superset-operator/tests/__snapshot__/full_options_test.yaml.snap
new file mode 100644
index 0000000..77bf103
--- /dev/null
+++ b/charts/superset-operator/tests/__snapshot__/full_options_test.yaml.snap
@@ -0,0 +1,672 @@
+renders the full install manifests:
+ 1: |
+ apiVersion: apps/v1
+ kind: Deployment
+ metadata:
+ labels:
+ app.kubernetes.io/instance: my-release
+ app.kubernetes.io/managed-by: Helm
+ app.kubernetes.io/name: superset-operator
+ app.kubernetes.io/version: 1.0.0
+ control-plane: controller-manager
+ helm.sh/chart: superset-operator-1.0.0
+ name: my-release-superset-operator
+ namespace: my-namespace
+ spec:
+ replicas: 2
+ revisionHistoryLimit: 5
+ selector:
+ matchLabels:
+ app.kubernetes.io/instance: my-release
+ app.kubernetes.io/name: superset-operator
+ control-plane: controller-manager
+ template:
+ metadata:
+ annotations:
+ kubectl.kubernetes.io/default-container: manager
+ prometheus.io/scrape: "false"
+ labels:
+ app.kubernetes.io/instance: my-release
+ app.kubernetes.io/name: superset-operator
+ control-plane: controller-manager
+ team: platform
+ spec:
+ affinity:
+ nodeAffinity:
+ requiredDuringSchedulingIgnoredDuringExecution:
+ nodeSelectorTerms:
+ - matchExpressions:
+ - key: kubernetes.io/os
+ operator: In
+ values:
+ - linux
+ containers:
+ - args:
+ - --leader-elect=true
+ - --health-probe-bind-address=:8081
+ - --zap-log-level=debug
+ - --metrics-bind-address=:9443
+ - --metrics-cert-path=/tmp/k8s-metrics-server/metrics-certs
+ - --metrics-cert-name=tls.crt
+ - --metrics-cert-key=tls.key
+ command:
+ - /manager
+ env:
+ - name: WATCH_NAMESPACE
+ value: team-a,team-b
+ image: example.com/superset/superset-operator:v9.9.9-test
+ imagePullPolicy: Always
+ livenessProbe:
+ httpGet:
+ path: /healthz
+ port: 8081
+ initialDelaySeconds: 30
+ periodSeconds: 30
+ name: manager
+ readinessProbe:
+ httpGet:
+ path: /readyz
+ port: 8081
+ initialDelaySeconds: 10
+ periodSeconds: 15
+ resizePolicy:
+ - resourceName: cpu
+ restartPolicy: NotRequired
+ - resourceName: memory
+ restartPolicy: RestartContainer
+ resources:
+ limits:
+ cpu: "1"
+ memory: 256Mi
+ requests:
+ cpu: 100m
+ memory: 128Mi
+ securityContext:
+ allowPrivilegeEscalation: false
+ capabilities:
+ drop:
+ - ALL
+ readOnlyRootFilesystem: true
+ volumeMounts:
+ - mountPath: /tmp/k8s-metrics-server/metrics-certs
+ name: metrics-certs
+ readOnly: true
+ nodeSelector:
+ kubernetes.io/os: linux
+ securityContext:
+ runAsNonRoot: true
+ runAsUser: 65532
+ seccompProfile:
+ type: RuntimeDefault
+ serviceAccountName: superset-operator-custom-sa
+ terminationGracePeriodSeconds: 10
+ tolerations:
+ - effect: NoSchedule
+ key: node-role.kubernetes.io/control-plane
+ operator: Exists
+ topologySpreadConstraints:
+ - labelSelector:
+ matchLabels:
+ control-plane: controller-manager
+ maxSkew: 1
+ topologyKey: kubernetes.io/hostname
+ whenUnsatisfiable: DoNotSchedule
+ volumes:
+ - name: metrics-certs
+ secret:
+ items:
+ - key: ca.crt
+ path: ca.crt
+ - key: tls.crt
+ path: tls.crt
+ - key: tls.key
+ path: tls.key
+ optional: false
+ secretName: superset-operator-metrics-cert
+ 2: |
+ apiVersion: v1
+ data:
+ hello: world
+ kind: ConfigMap
+ metadata:
+ name: superset-operator-extra
+ namespace: my-namespace
+ 3: |
+ apiVersion: policy/v1
+ kind: PodDisruptionBudget
+ metadata:
+ name: superset-operator-extra-pdb
+ spec:
+ minAvailable: 1
+ selector:
+ matchLabels:
+ app.kubernetes.io/name: superset-operator
+ 4: |
+ apiVersion: rbac.authorization.k8s.io/v1
+ kind: Role
+ metadata:
+ labels:
+ app.kubernetes.io/instance: my-release
+ app.kubernetes.io/managed-by: Helm
+ app.kubernetes.io/name: superset-operator
+ app.kubernetes.io/version: 1.0.0
+ control-plane: controller-manager
+ helm.sh/chart: superset-operator-1.0.0
+ name: my-release-superset-operator-leader-election
+ namespace: my-namespace
+ rules:
+ - apiGroups:
+ - ""
+ resources:
+ - configmaps
+ verbs:
+ - get
+ - list
+ - watch
+ - create
+ - update
+ - patch
+ - delete
+ - apiGroups:
+ - coordination.k8s.io
+ resources:
+ - leases
+ verbs:
+ - get
+ - list
+ - watch
+ - create
+ - update
+ - patch
+ - delete
+ - apiGroups:
+ - ""
+ resources:
+ - events
+ verbs:
+ - create
+ - patch
+ 5: |
+ apiVersion: rbac.authorization.k8s.io/v1
+ kind: RoleBinding
+ metadata:
+ labels:
+ app.kubernetes.io/instance: my-release
+ app.kubernetes.io/managed-by: Helm
+ app.kubernetes.io/name: superset-operator
+ app.kubernetes.io/version: 1.0.0
+ control-plane: controller-manager
+ helm.sh/chart: superset-operator-1.0.0
+ name: my-release-superset-operator-leader-election
+ namespace: my-namespace
+ roleRef:
+ apiGroup: rbac.authorization.k8s.io
+ kind: Role
+ name: my-release-superset-operator-leader-election
+ subjects:
+ - kind: ServiceAccount
+ name: superset-operator-custom-sa
+ namespace: my-namespace
+ 6: |
+ apiVersion: rbac.authorization.k8s.io/v1
+ kind: ClusterRole
+ metadata:
+ labels:
+ app.kubernetes.io/instance: my-release
+ app.kubernetes.io/managed-by: Helm
+ app.kubernetes.io/name: superset-operator
+ app.kubernetes.io/version: 1.0.0
+ control-plane: controller-manager
+ helm.sh/chart: superset-operator-1.0.0
+ name: my-release-superset-operator-metrics-auth
+ rules:
+ - apiGroups:
+ - authentication.k8s.io
+ resources:
+ - tokenreviews
+ verbs:
+ - create
+ - apiGroups:
+ - authorization.k8s.io
+ resources:
+ - subjectaccessreviews
+ verbs:
+ - create
+ 7: |
+ apiVersion: rbac.authorization.k8s.io/v1
+ kind: ClusterRoleBinding
+ metadata:
+ labels:
+ app.kubernetes.io/instance: my-release
+ app.kubernetes.io/managed-by: Helm
+ app.kubernetes.io/name: superset-operator
+ app.kubernetes.io/version: 1.0.0
+ control-plane: controller-manager
+ helm.sh/chart: superset-operator-1.0.0
+ name: my-release-superset-operator-metrics-auth
+ roleRef:
+ apiGroup: rbac.authorization.k8s.io
+ kind: ClusterRole
+ name: my-release-superset-operator-metrics-auth
+ subjects:
+ - kind: ServiceAccount
+ name: superset-operator-custom-sa
+ namespace: my-namespace
+ 8: |
+ apiVersion: rbac.authorization.k8s.io/v1
+ kind: ClusterRole
+ metadata:
+ labels:
+ app.kubernetes.io/instance: my-release
+ app.kubernetes.io/managed-by: Helm
+ app.kubernetes.io/name: superset-operator
+ app.kubernetes.io/version: 1.0.0
+ control-plane: controller-manager
+ helm.sh/chart: superset-operator-1.0.0
+ name: my-release-superset-operator-metrics-reader
+ rules:
+ - nonResourceURLs:
+ - /metrics
+ verbs:
+ - get
+ 9: |
+ apiVersion: v1
+ kind: Service
+ metadata:
+ labels:
+ app.kubernetes.io/instance: my-release
+ app.kubernetes.io/managed-by: Helm
+ app.kubernetes.io/name: superset-operator
+ app.kubernetes.io/version: 1.0.0
+ control-plane: controller-manager
+ helm.sh/chart: superset-operator-1.0.0
+ name: my-release-superset-operator-metrics
+ namespace: my-namespace
+ spec:
+ ports:
+ - name: https
+ port: 9443
+ protocol: TCP
+ targetPort: 9443
+ selector:
+ app.kubernetes.io/instance: my-release
+ app.kubernetes.io/name: superset-operator
+ control-plane: controller-manager
+ 10: |
+ apiVersion: monitoring.coreos.com/v1
+ kind: ServiceMonitor
+ metadata:
+ labels:
+ app.kubernetes.io/instance: my-release
+ app.kubernetes.io/managed-by: Helm
+ app.kubernetes.io/name: superset-operator
+ app.kubernetes.io/version: 1.0.0
+ control-plane: controller-manager
+ helm.sh/chart: superset-operator-1.0.0
+ release: kube-prometheus-stack
+ name: my-release-superset-operator-metrics
+ namespace: my-namespace
+ spec:
+ endpoints:
+ - bearerTokenFile: /var/run/secrets/kubernetes.io/serviceaccount/token
+ interval: 15s
+ path: /metrics
+ port: https
+ scheme: https
+ scrapeTimeout: 10s
+ tlsConfig:
+ insecureSkipVerify: false
+ selector:
+ matchLabels:
+ app.kubernetes.io/instance: my-release
+ app.kubernetes.io/name: superset-operator
+ control-plane: controller-manager
+ 11: |
+ apiVersion: rbac.authorization.k8s.io/v1
+ kind: Role
+ metadata:
+ labels:
+ app.kubernetes.io/instance: my-release
+ app.kubernetes.io/managed-by: Helm
+ app.kubernetes.io/name: superset-operator
+ app.kubernetes.io/version: 1.0.0
+ control-plane: controller-manager
+ helm.sh/chart: superset-operator-1.0.0
+ name: my-release-superset-operator-manager
+ namespace: team-a
+ rules:
+ - apiGroups:
+ - ""
+ resources:
+ - configmaps
+ - serviceaccounts
+ - services
+ verbs:
+ - create
+ - delete
+ - get
+ - list
+ - patch
+ - update
+ - watch
+ - apiGroups:
+ - ""
+ resources:
+ - pods
+ verbs:
+ - get
+ - list
+ - watch
+ - apiGroups:
+ - apps
+ resources:
+ - deployments
+ verbs:
+ - create
+ - delete
+ - get
+ - list
+ - patch
+ - update
+ - watch
+ - apiGroups:
+ - autoscaling
+ resources:
+ - horizontalpodautoscalers
+ verbs:
+ - create
+ - delete
+ - get
+ - list
+ - patch
+ - update
+ - watch
+ - apiGroups:
+ - batch
+ resources:
+ - jobs
+ verbs:
+ - create
+ - delete
+ - get
+ - list
+ - patch
+ - update
+ - watch
+ - apiGroups:
+ - events.k8s.io
+ resources:
+ - events
+ verbs:
+ - create
+ - patch
+ - update
+ - apiGroups:
+ - gateway.networking.k8s.io
+ resources:
+ - httproutes
+ verbs:
+ - create
+ - delete
+ - get
+ - list
+ - patch
+ - update
+ - watch
+ - apiGroups:
+ - monitoring.coreos.com
+ resources:
+ - servicemonitors
+ verbs:
+ - create
+ - delete
+ - get
+ - list
+ - patch
+ - update
+ - watch
+ - apiGroups:
+ - networking.k8s.io
+ resources:
+ - ingresses
+ - networkpolicies
+ verbs:
+ - create
+ - delete
+ - get
+ - list
+ - patch
+ - update
+ - watch
+ - apiGroups:
+ - policy
+ resources:
+ - poddisruptionbudgets
+ verbs:
+ - create
+ - delete
+ - get
+ - list
+ - patch
+ - update
+ - watch
+ - apiGroups:
+ - superset.apache.org
+ resources:
+ - supersets/status
+ verbs:
+ - get
+ - patch
+ - update
+ - apiGroups:
+ - superset.apache.org
+ resources:
+ - supersets
+ verbs:
+ - get
+ - list
+ - patch
+ - watch
+ 12: |
+ apiVersion: rbac.authorization.k8s.io/v1
+ kind: Role
+ metadata:
+ labels:
+ app.kubernetes.io/instance: my-release
+ app.kubernetes.io/managed-by: Helm
+ app.kubernetes.io/name: superset-operator
+ app.kubernetes.io/version: 1.0.0
+ control-plane: controller-manager
+ helm.sh/chart: superset-operator-1.0.0
+ name: my-release-superset-operator-manager
+ namespace: team-b
+ rules:
+ - apiGroups:
+ - ""
+ resources:
+ - configmaps
+ - serviceaccounts
+ - services
+ verbs:
+ - create
+ - delete
+ - get
+ - list
+ - patch
+ - update
+ - watch
+ - apiGroups:
+ - ""
+ resources:
+ - pods
+ verbs:
+ - get
+ - list
+ - watch
+ - apiGroups:
+ - apps
+ resources:
+ - deployments
+ verbs:
+ - create
+ - delete
+ - get
+ - list
+ - patch
+ - update
+ - watch
+ - apiGroups:
+ - autoscaling
+ resources:
+ - horizontalpodautoscalers
+ verbs:
+ - create
+ - delete
+ - get
+ - list
+ - patch
+ - update
+ - watch
+ - apiGroups:
+ - batch
+ resources:
+ - jobs
+ verbs:
+ - create
+ - delete
+ - get
+ - list
+ - patch
+ - update
+ - watch
+ - apiGroups:
+ - events.k8s.io
+ resources:
+ - events
+ verbs:
+ - create
+ - patch
+ - update
+ - apiGroups:
+ - gateway.networking.k8s.io
+ resources:
+ - httproutes
+ verbs:
+ - create
+ - delete
+ - get
+ - list
+ - patch
+ - update
+ - watch
+ - apiGroups:
+ - monitoring.coreos.com
+ resources:
+ - servicemonitors
+ verbs:
+ - create
+ - delete
+ - get
+ - list
+ - patch
+ - update
+ - watch
+ - apiGroups:
+ - networking.k8s.io
+ resources:
+ - ingresses
+ - networkpolicies
+ verbs:
+ - create
+ - delete
+ - get
+ - list
+ - patch
+ - update
+ - watch
+ - apiGroups:
+ - policy
+ resources:
+ - poddisruptionbudgets
+ verbs:
+ - create
+ - delete
+ - get
+ - list
+ - patch
+ - update
+ - watch
+ - apiGroups:
+ - superset.apache.org
+ resources:
+ - supersets/status
+ verbs:
+ - get
+ - patch
+ - update
+ - apiGroups:
+ - superset.apache.org
+ resources:
+ - supersets
+ verbs:
+ - get
+ - list
+ - patch
+ - watch
+ 13: |
+ apiVersion: rbac.authorization.k8s.io/v1
+ kind: RoleBinding
+ metadata:
+ labels:
+ app.kubernetes.io/instance: my-release
+ app.kubernetes.io/managed-by: Helm
+ app.kubernetes.io/name: superset-operator
+ app.kubernetes.io/version: 1.0.0
+ control-plane: controller-manager
+ helm.sh/chart: superset-operator-1.0.0
+ name: my-release-superset-operator-manager
+ namespace: team-a
+ roleRef:
+ apiGroup: rbac.authorization.k8s.io
+ kind: Role
+ name: my-release-superset-operator-manager
+ subjects:
+ - kind: ServiceAccount
+ name: superset-operator-custom-sa
+ namespace: my-namespace
+ 14: |
+ apiVersion: rbac.authorization.k8s.io/v1
+ kind: RoleBinding
+ metadata:
+ labels:
+ app.kubernetes.io/instance: my-release
+ app.kubernetes.io/managed-by: Helm
+ app.kubernetes.io/name: superset-operator
+ app.kubernetes.io/version: 1.0.0
+ control-plane: controller-manager
+ helm.sh/chart: superset-operator-1.0.0
+ name: my-release-superset-operator-manager
+ namespace: team-b
+ roleRef:
+ apiGroup: rbac.authorization.k8s.io
+ kind: Role
+ name: my-release-superset-operator-manager
+ subjects:
+ - kind: ServiceAccount
+ name: superset-operator-custom-sa
+ namespace: my-namespace
+ 15: |
+ apiVersion: v1
+ kind: ServiceAccount
+ metadata:
+ annotations:
+ eks.amazonaws.com/role-arn:
arn:aws:iam::123456789012:role/superset-operator
+ labels:
+ app.kubernetes.io/instance: my-release
+ app.kubernetes.io/managed-by: Helm
+ app.kubernetes.io/name: superset-operator
+ app.kubernetes.io/version: 1.0.0
+ control-plane: controller-manager
+ helm.sh/chart: superset-operator-1.0.0
+ name: superset-operator-custom-sa
+ namespace: my-namespace
diff --git a/charts/superset-operator/tests/__snapshot__/minimal_test.yaml.snap
b/charts/superset-operator/tests/__snapshot__/minimal_test.yaml.snap
new file mode 100644
index 0000000..06cc34c
--- /dev/null
+++ b/charts/superset-operator/tests/__snapshot__/minimal_test.yaml.snap
@@ -0,0 +1,400 @@
+renders the default install manifests:
+ 1: |
+ apiVersion: rbac.authorization.k8s.io/v1
+ kind: ClusterRole
+ metadata:
+ labels:
+ app.kubernetes.io/instance: my-release
+ app.kubernetes.io/managed-by: Helm
+ app.kubernetes.io/name: superset-operator
+ app.kubernetes.io/version: 1.0.0
+ control-plane: controller-manager
+ helm.sh/chart: superset-operator-1.0.0
+ name: my-release-superset-operator-manager
+ rules:
+ - apiGroups:
+ - ""
+ resources:
+ - configmaps
+ - serviceaccounts
+ - services
+ verbs:
+ - create
+ - delete
+ - get
+ - list
+ - patch
+ - update
+ - watch
+ - apiGroups:
+ - ""
+ resources:
+ - pods
+ verbs:
+ - get
+ - list
+ - watch
+ - apiGroups:
+ - apps
+ resources:
+ - deployments
+ verbs:
+ - create
+ - delete
+ - get
+ - list
+ - patch
+ - update
+ - watch
+ - apiGroups:
+ - autoscaling
+ resources:
+ - horizontalpodautoscalers
+ verbs:
+ - create
+ - delete
+ - get
+ - list
+ - patch
+ - update
+ - watch
+ - apiGroups:
+ - batch
+ resources:
+ - jobs
+ verbs:
+ - create
+ - delete
+ - get
+ - list
+ - patch
+ - update
+ - watch
+ - apiGroups:
+ - events.k8s.io
+ resources:
+ - events
+ verbs:
+ - create
+ - patch
+ - update
+ - apiGroups:
+ - gateway.networking.k8s.io
+ resources:
+ - httproutes
+ verbs:
+ - create
+ - delete
+ - get
+ - list
+ - patch
+ - update
+ - watch
+ - apiGroups:
+ - monitoring.coreos.com
+ resources:
+ - servicemonitors
+ verbs:
+ - create
+ - delete
+ - get
+ - list
+ - patch
+ - update
+ - watch
+ - apiGroups:
+ - networking.k8s.io
+ resources:
+ - ingresses
+ - networkpolicies
+ verbs:
+ - create
+ - delete
+ - get
+ - list
+ - patch
+ - update
+ - watch
+ - apiGroups:
+ - policy
+ resources:
+ - poddisruptionbudgets
+ verbs:
+ - create
+ - delete
+ - get
+ - list
+ - patch
+ - update
+ - watch
+ - apiGroups:
+ - superset.apache.org
+ resources:
+ - supersets/status
+ verbs:
+ - get
+ - patch
+ - update
+ - apiGroups:
+ - superset.apache.org
+ resources:
+ - supersets
+ verbs:
+ - get
+ - list
+ - patch
+ - watch
+ 2: |
+ apiVersion: rbac.authorization.k8s.io/v1
+ kind: ClusterRoleBinding
+ metadata:
+ labels:
+ app.kubernetes.io/instance: my-release
+ app.kubernetes.io/managed-by: Helm
+ app.kubernetes.io/name: superset-operator
+ app.kubernetes.io/version: 1.0.0
+ control-plane: controller-manager
+ helm.sh/chart: superset-operator-1.0.0
+ name: my-release-superset-operator-manager
+ roleRef:
+ apiGroup: rbac.authorization.k8s.io
+ kind: ClusterRole
+ name: my-release-superset-operator-manager
+ subjects:
+ - kind: ServiceAccount
+ name: my-release-superset-operator
+ namespace: my-namespace
+ 3: |
+ apiVersion: apps/v1
+ kind: Deployment
+ metadata:
+ labels:
+ app.kubernetes.io/instance: my-release
+ app.kubernetes.io/managed-by: Helm
+ app.kubernetes.io/name: superset-operator
+ app.kubernetes.io/version: 1.0.0
+ control-plane: controller-manager
+ helm.sh/chart: superset-operator-1.0.0
+ name: my-release-superset-operator
+ namespace: my-namespace
+ spec:
+ replicas: 1
+ selector:
+ matchLabels:
+ app.kubernetes.io/instance: my-release
+ app.kubernetes.io/name: superset-operator
+ control-plane: controller-manager
+ template:
+ metadata:
+ annotations:
+ kubectl.kubernetes.io/default-container: manager
+ labels:
+ app.kubernetes.io/instance: my-release
+ app.kubernetes.io/name: superset-operator
+ control-plane: controller-manager
+ spec:
+ containers:
+ - args:
+ - --leader-elect=true
+ - --health-probe-bind-address=:8081
+ - --metrics-bind-address=:8443
+ command:
+ - /manager
+ image: ghcr.io/apache/superset-kubernetes-operator:1.0.0
+ imagePullPolicy: IfNotPresent
+ livenessProbe:
+ httpGet:
+ path: /healthz
+ port: 8081
+ initialDelaySeconds: 15
+ periodSeconds: 20
+ name: manager
+ readinessProbe:
+ httpGet:
+ path: /readyz
+ port: 8081
+ initialDelaySeconds: 5
+ periodSeconds: 10
+ resources:
+ limits:
+ cpu: 500m
+ memory: 128Mi
+ requests:
+ cpu: 10m
+ memory: 64Mi
+ securityContext:
+ allowPrivilegeEscalation: false
+ capabilities:
+ drop:
+ - ALL
+ readOnlyRootFilesystem: true
+ securityContext:
+ runAsNonRoot: true
+ seccompProfile:
+ type: RuntimeDefault
+ serviceAccountName: my-release-superset-operator
+ terminationGracePeriodSeconds: 10
+ 4: |
+ apiVersion: rbac.authorization.k8s.io/v1
+ kind: Role
+ metadata:
+ labels:
+ app.kubernetes.io/instance: my-release
+ app.kubernetes.io/managed-by: Helm
+ app.kubernetes.io/name: superset-operator
+ app.kubernetes.io/version: 1.0.0
+ control-plane: controller-manager
+ helm.sh/chart: superset-operator-1.0.0
+ name: my-release-superset-operator-leader-election
+ namespace: my-namespace
+ rules:
+ - apiGroups:
+ - ""
+ resources:
+ - configmaps
+ verbs:
+ - get
+ - list
+ - watch
+ - create
+ - update
+ - patch
+ - delete
+ - apiGroups:
+ - coordination.k8s.io
+ resources:
+ - leases
+ verbs:
+ - get
+ - list
+ - watch
+ - create
+ - update
+ - patch
+ - delete
+ - apiGroups:
+ - ""
+ resources:
+ - events
+ verbs:
+ - create
+ - patch
+ 5: |
+ apiVersion: rbac.authorization.k8s.io/v1
+ kind: RoleBinding
+ metadata:
+ labels:
+ app.kubernetes.io/instance: my-release
+ app.kubernetes.io/managed-by: Helm
+ app.kubernetes.io/name: superset-operator
+ app.kubernetes.io/version: 1.0.0
+ control-plane: controller-manager
+ helm.sh/chart: superset-operator-1.0.0
+ name: my-release-superset-operator-leader-election
+ namespace: my-namespace
+ roleRef:
+ apiGroup: rbac.authorization.k8s.io
+ kind: Role
+ name: my-release-superset-operator-leader-election
+ subjects:
+ - kind: ServiceAccount
+ name: my-release-superset-operator
+ namespace: my-namespace
+ 6: |
+ apiVersion: rbac.authorization.k8s.io/v1
+ kind: ClusterRole
+ metadata:
+ labels:
+ app.kubernetes.io/instance: my-release
+ app.kubernetes.io/managed-by: Helm
+ app.kubernetes.io/name: superset-operator
+ app.kubernetes.io/version: 1.0.0
+ control-plane: controller-manager
+ helm.sh/chart: superset-operator-1.0.0
+ name: my-release-superset-operator-metrics-auth
+ rules:
+ - apiGroups:
+ - authentication.k8s.io
+ resources:
+ - tokenreviews
+ verbs:
+ - create
+ - apiGroups:
+ - authorization.k8s.io
+ resources:
+ - subjectaccessreviews
+ verbs:
+ - create
+ 7: |
+ apiVersion: rbac.authorization.k8s.io/v1
+ kind: ClusterRoleBinding
+ metadata:
+ labels:
+ app.kubernetes.io/instance: my-release
+ app.kubernetes.io/managed-by: Helm
+ app.kubernetes.io/name: superset-operator
+ app.kubernetes.io/version: 1.0.0
+ control-plane: controller-manager
+ helm.sh/chart: superset-operator-1.0.0
+ name: my-release-superset-operator-metrics-auth
+ roleRef:
+ apiGroup: rbac.authorization.k8s.io
+ kind: ClusterRole
+ name: my-release-superset-operator-metrics-auth
+ subjects:
+ - kind: ServiceAccount
+ name: my-release-superset-operator
+ namespace: my-namespace
+ 8: |
+ apiVersion: rbac.authorization.k8s.io/v1
+ kind: ClusterRole
+ metadata:
+ labels:
+ app.kubernetes.io/instance: my-release
+ app.kubernetes.io/managed-by: Helm
+ app.kubernetes.io/name: superset-operator
+ app.kubernetes.io/version: 1.0.0
+ control-plane: controller-manager
+ helm.sh/chart: superset-operator-1.0.0
+ name: my-release-superset-operator-metrics-reader
+ rules:
+ - nonResourceURLs:
+ - /metrics
+ verbs:
+ - get
+ 9: |
+ apiVersion: v1
+ kind: Service
+ metadata:
+ labels:
+ app.kubernetes.io/instance: my-release
+ app.kubernetes.io/managed-by: Helm
+ app.kubernetes.io/name: superset-operator
+ app.kubernetes.io/version: 1.0.0
+ control-plane: controller-manager
+ helm.sh/chart: superset-operator-1.0.0
+ name: my-release-superset-operator-metrics
+ namespace: my-namespace
+ spec:
+ ports:
+ - name: https
+ port: 8443
+ protocol: TCP
+ targetPort: 8443
+ selector:
+ app.kubernetes.io/instance: my-release
+ app.kubernetes.io/name: superset-operator
+ control-plane: controller-manager
+ 10: |
+ apiVersion: v1
+ kind: ServiceAccount
+ metadata:
+ labels:
+ app.kubernetes.io/instance: my-release
+ app.kubernetes.io/managed-by: Helm
+ app.kubernetes.io/name: superset-operator
+ app.kubernetes.io/version: 1.0.0
+ control-plane: controller-manager
+ helm.sh/chart: superset-operator-1.0.0
+ name: my-release-superset-operator
+ namespace: my-namespace
diff --git a/charts/superset-operator/tests/full_options_test.yaml
b/charts/superset-operator/tests/full_options_test.yaml
new file mode 100644
index 0000000..7109208
--- /dev/null
+++ b/charts/superset-operator/tests/full_options_test.yaml
@@ -0,0 +1,36 @@
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements. See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+# Comprehensive happy path: a full-render golden snapshot of an install with
+# every knob set (tests/values/full-options.yaml; coverage that every knob is
+# exercised is enforced by scripts/check-chart-values-covered.sh). With no
+# `templates:` restriction, the whole chart is rendered, so the snapshot
captures
+# everything this configuration deploys — namespace-scoped RBAC,
ServiceMonitor,
+# cert volumes, extra manifests, and the rest. Regenerate intended changes with
+# `helm unittest -u`.
+suite: full options
+values:
+ - values/full-options.yaml
+# Pin chart/release identity so snapshots are stable across chart version
bumps.
+chart:
+ version: 1.0.0
+ appVersion: 1.0.0
+release:
+ name: my-release
+ namespace: my-namespace
+tests:
+ - it: renders the full install manifests
+ asserts:
+ - matchSnapshot: {}
diff --git a/charts/superset-operator/tests/minimal_test.yaml
b/charts/superset-operator/tests/minimal_test.yaml
new file mode 100644
index 0000000..1cd03b3
--- /dev/null
+++ b/charts/superset-operator/tests/minimal_test.yaml
@@ -0,0 +1,34 @@
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements. See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+# Stripped-down happy path: a full-render golden snapshot of a default
+# `helm install`. With no `templates:` restriction, the whole chart is
rendered,
+# so the snapshot captures every manifest a minimal install deploys
(Deployment,
+# ServiceAccount, cluster-scoped manager RBAC, leader-election RBAC, metrics
+# Service + auth RBAC). Any change to any rendered resource — including ones
this
+# file never mentions, and templates added to the chart later — shows up as a
+# snapshot diff. Regenerate intended changes with `helm unittest -u`.
+suite: minimal (default values)
+# Pin chart/release identity so snapshots are stable across chart version
bumps.
+chart:
+ version: 1.0.0
+ appVersion: 1.0.0
+release:
+ name: my-release
+ namespace: my-namespace
+tests:
+ - it: renders the default install manifests
+ asserts:
+ - matchSnapshot: {}
diff --git a/charts/superset-operator/tests/values/full-options.yaml
b/charts/superset-operator/tests/values/full-options.yaml
new file mode 100644
index 0000000..60d6d4c
--- /dev/null
+++ b/charts/superset-operator/tests/values/full-options.yaml
@@ -0,0 +1,155 @@
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements. See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+# Comprehensive chart-test values: every knob documented in values.yaml is set
+# to a non-default, rendering value. This is the single source of truth for the
+# "full options" test suite and is enforced by
scripts/check-chart-values-covered.sh
+# (CI target `make helm-values-covered`) — any new knob added to values.yaml
must
+# be exercised here.
+
+image:
+ repository: example.com/superset/superset-operator
+ tag: v9.9.9-test
+ pullPolicy: Always
+
+replicas: 2
+
+revisionHistoryLimit: 5
+
+resources:
+ limits:
+ cpu: "1"
+ memory: 256Mi
+ requests:
+ cpu: 100m
+ memory: 128Mi
+
+resizePolicy:
+ - resourceName: cpu
+ restartPolicy: NotRequired
+ - resourceName: memory
+ restartPolicy: RestartContainer
+
+serviceAccount:
+ create: true
+ name: superset-operator-custom-sa
+ annotations:
+ eks.amazonaws.com/role-arn:
arn:aws:iam::123456789012:role/superset-operator
+
+leaderElection:
+ enabled: true
+
+logLevel: debug
+
+metrics:
+ enabled: true
+ service:
+ port: 9443
+ certSecretName: superset-operator-metrics-cert
+ serviceMonitor:
+ enabled: true
+ interval: 15s
+ scrapeTimeout: 10s
+ labels:
+ release: kube-prometheus-stack
+ tlsConfig:
+ insecureSkipVerify: false
+ bearerTokenFile: /var/run/secrets/kubernetes.io/serviceaccount/token
+
+healthProbes:
+ livenessProbe:
+ httpGet:
+ path: /healthz
+ port: 8081
+ initialDelaySeconds: 30
+ periodSeconds: 30
+ readinessProbe:
+ httpGet:
+ path: /readyz
+ port: 8081
+ initialDelaySeconds: 10
+ periodSeconds: 15
+
+podSecurityContext:
+ runAsNonRoot: true
+ runAsUser: 65532
+ seccompProfile:
+ type: RuntimeDefault
+
+securityContext:
+ allowPrivilegeEscalation: false
+ readOnlyRootFilesystem: true
+ capabilities:
+ drop:
+ - ALL
+
+nodeSelector:
+ kubernetes.io/os: linux
+
+tolerations:
+ - key: node-role.kubernetes.io/control-plane
+ operator: Exists
+ effect: NoSchedule
+
+affinity:
+ nodeAffinity:
+ requiredDuringSchedulingIgnoredDuringExecution:
+ nodeSelectorTerms:
+ - matchExpressions:
+ - key: kubernetes.io/os
+ operator: In
+ values:
+ - linux
+
+topologySpreadConstraints:
+ - maxSkew: 1
+ topologyKey: kubernetes.io/hostname
+ whenUnsatisfiable: DoNotSchedule
+ labelSelector:
+ matchLabels:
+ control-plane: controller-manager
+
+podAnnotations:
+ prometheus.io/scrape: "false"
+
+podLabels:
+ team: platform
+
+watch:
+ scope: namespaces
+ namespaces:
+ - team-a
+ - team-b
+
+extraManifests:
+ # String form, passed through tpl so template expressions resolve.
+ - |
+ apiVersion: v1
+ kind: ConfigMap
+ metadata:
+ name: superset-operator-extra
+ namespace: {{ .Release.Namespace }}
+ data:
+ hello: world
+ # Object form.
+ - apiVersion: policy/v1
+ kind: PodDisruptionBudget
+ metadata:
+ name: superset-operator-extra-pdb
+ spec:
+ minAvailable: 1
+ selector:
+ matchLabels:
+ app.kubernetes.io/name: superset-operator
diff --git a/docs/contributing/development-guidelines.md
b/docs/contributing/development-guidelines.md
index a73765c..b813ac7 100644
--- a/docs/contributing/development-guidelines.md
+++ b/docs/contributing/development-guidelines.md
@@ -196,6 +196,29 @@ Each target seeds a corpus with `f.Add(...)` cases and
asserts an invariant beyo
**On a finding:** `go test` writes a reproducer to
`testdata/fuzz/<target>/<id>`. Commit it as a permanent regression seed, then
fix the bug.
+### Helm chart tests
+
+The Helm chart (`charts/superset-operator/`) has its own
[helm-unittest](https://github.com/helm-unittest/helm-unittest) suite under
`charts/superset-operator/tests/`. It renders the chart with different values
and asserts on the output — the same "assert on observable outputs" principle
as the Go tests, applied to rendered manifests. CI runs it in the `Helm lint &
test` job.
+
+Install the tooling once (the plugin binary is pinned; `yq` is only needed for
the coverage gate):
+
+```sh
+bash scripts/install-helm-unittest.sh
+bash scripts/install-yq.sh # or: brew install yq
+```
+
+Run locally:
+
+```sh
+make helm-test # run the chart unit tests
+helm unittest -u charts/superset-operator # update snapshots after an
intended change
+make helm-values-covered # verify every values.yaml knob is exercised
+```
+
+Two scenarios anchor the suite: `minimal_test.yaml` renders the chart with
default `values.yaml`, and `full_options_test.yaml` renders it with every knob
set via `tests/values/full-options.yaml`. Both capture a full-render
**snapshot** (stored in `tests/__snapshot__/`) plus targeted assertions for
invariants that must never silently change (watch-scope RBAC,
`WATCH_NAMESPACE`, ServiceAccount create-vs-BYO, metrics wiring). Chart/release
identity is pinned in each suite so snapshots are s [...]
+
+**Any change to the chart — especially a new values knob or feature — must be
exercised in `tests/values/full-options.yaml` and its rendered effect captured
in the snapshot (`helm unittest -u`).** This is enforced: `make
helm-values-covered` (`scripts/check-chart-values-covered.sh`) fails CI when a
`values.yaml` key is not set in the comprehensive values file. It closes the
gap a snapshot alone cannot — a knob that defaults to null and renders nothing
under defaults would otherwise leave [...]
+
---
## License Headers
diff --git a/docs/contributing/development-setup.md
b/docs/contributing/development-setup.md
index 4a763e3..6725692 100644
--- a/docs/contributing/development-setup.md
+++ b/docs/contributing/development-setup.md
@@ -29,6 +29,7 @@ This guide walks through setting up a local development
environment with Kind, P
| kubectl | 1.27+ | [kubernetes.io](https://kubernetes.io/docs/tasks/tools/) |
| Kind | 0.30+ | `brew install kind` or `go install sigs.k8s.io/kind@latest` |
| Go | 1.26+ | `brew install go` or [go.dev](https://go.dev/dl/) |
+| Helm | 4+ | `brew install helm` or
[helm.sh](https://helm.sh/docs/intro/install/) — used by `make lint` (Helm
chart lint) and the chart tests |
### macOS notes
@@ -157,6 +158,8 @@ kind delete cluster --name superset
| `make helm-docs` | Generate Helm chart README from values.yaml annotations. |
| `make helm` | Sync CRDs into Helm chart and package it. The chart
`appVersion` is derived from VERSION at package time. |
| `make helm-lint` | Lint the Helm chart (syncs CRDs first). |
+| `make helm-test` | Run the Helm chart unit tests (requires the helm-unittest
plugin; see scripts/install-helm-unittest.sh). |
+| `make helm-values-covered` | Verify every values.yaml knob is exercised by
the comprehensive chart test. |
### Development
@@ -175,11 +178,13 @@ kind delete cluster --name superset
| `make setup-test-e2e` | Set up a Kind cluster for e2e tests if it does not
exist |
| `make test-e2e` | Run the e2e tests. Expected an isolated environment using
Kind. |
| `make cleanup-test-e2e` | Tear down the Kind cluster used for e2e tests |
-| `make lint` | Run golangci-lint linter |
-| `make lint-fix` | Run golangci-lint linter and perform fixes |
-| `make lint-config` | Verify golangci-lint linter configuration |
+| `make lint` | Run all linters (Go, Markdown, Helm chart). |
+| `make lint-fix` | Auto-fix all linters that support it (Go, Markdown). |
+| `make lint-go` | Run the Go linter (golangci-lint). |
+| `make lint-go-fix` | Run the Go linter and apply fixes. |
+| `make lint-go-config` | Verify the golangci-lint configuration. |
| `make lint-md` | Lint Markdown files (check only). |
-| `make format-md` | Auto-fix Markdown formatting in place. |
+| `make lint-md-fix` | Auto-fix Markdown formatting in place. |
| `make hooks` | Configure git to use .githooks/ for pre-commit hooks |
### Build
@@ -207,13 +212,13 @@ kind delete cluster --name superset
## Pre-Commit Hooks
-The project includes a lightweight git hook at `.githooks/pre-commit` that
runs before each commit. It runs `make lint` (`golangci-lint`, which covers
`gofmt`, `goimports`, `go vet`, and all configured linters) and reformats any
staged Markdown with [rumdl](https://rumdl.dev/). If rumdl changes a file, the
commit is aborted so you can review the changes and re-stage them — nothing is
silently amended. rumdl is a pinned binary downloaded on demand into `bin/`.
+The project includes a lightweight git hook at `.githooks/pre-commit` that
runs before each commit. It runs `make lint-go` (`golangci-lint`, which covers
`gofmt`, `goimports`, `go vet`, and all configured linters) and reformats any
staged Markdown with [rumdl](https://rumdl.dev/). If rumdl changes a file, the
commit is aborted so you can review the changes and re-stage them — nothing is
silently amended. rumdl is a pinned binary downloaded on demand into `bin/`.
Markdown formatting is also enforced in CI. To check or fix Markdown by hand:
```sh
-make lint-md # check only (what CI runs)
-make format-md # auto-fix in place
+make lint-md # check only (what CI runs)
+make lint-md-fix # auto-fix in place
```
Formatting rules — including single-line-per-paragraph prose (no hard
wrapping) — live in `.rumdl.toml`.
diff --git a/docs/contributing/releasing.md b/docs/contributing/releasing.md
index 169c13f..50cd7cc 100644
--- a/docs/contributing/releasing.md
+++ b/docs/contributing/releasing.md
@@ -92,7 +92,8 @@ Before creating the first RC for a minor release, run or
verify:
- `make codegen` leaves no diff
- `make lint`
- `make test`
-- `make helm-lint`
+- `make helm-test`
+- `make helm-values-covered`
- `make docs-build`
- `make check-license`
- `make test-e2e` on a working Kind or equivalent Kubernetes cluster
@@ -115,7 +116,7 @@ Two flows depending on whether the minor release branch
already exists:
## Creating a Release Candidate
-The `scripts/release-rc.sh` script automates the full RC preparation: creates
the minor release branch (first RC only), bumps the operator version and Helm
chart `version`/`appVersion` metadata to the target release version,
regenerates manifests, runs the lint/license/test/docs/helm-lint checks,
commits, and tags. The tag-triggered release workflow packages and publishes
the RC image and Helm chart with the RC version suffix. The script also
verifies that `docs/reference/releases.md` co [...]
+The `scripts/release-rc.sh` script automates the full RC preparation: creates
the minor release branch (first RC only), bumps the operator version and Helm
chart `version`/`appVersion` metadata to the target release version,
regenerates manifests, runs the lint/license/test/docs/helm checks (lint, unit
tests, and value-coverage), commits, and tags. The tag-triggered release
workflow packages and publishes the RC image and Helm chart with the RC version
suffix. The script also verifies th [...]
```sh
# First RC for 0.2.0 — create the 0.2 branch first, then tag v0.2.0-rc1.
diff --git a/docs/getting-started.md b/docs/getting-started.md
index 6380e3e..57cba14 100644
--- a/docs/getting-started.md
+++ b/docs/getting-started.md
@@ -24,7 +24,7 @@ This guide walks through installing the operator and
deploying a minimal Superse
## Prerequisites
- Kubernetes 1.25+ (<!-- BEGIN SUPPORTED-K8S-INLINE -->1.36, 1.35<!-- END
SUPPORTED-K8S-INLINE --> officially tested; 1.29+ recommended so CEL validation
is GA — see [supported
versions](user-guide/installation.md#supported-kubernetes-versions))
-- Helm 3
+- Helm 4
- A PostgreSQL database accessible from the cluster
## 1. Install the operator
diff --git a/docs/user-guide/installation.md b/docs/user-guide/installation.md
index 8cf1741..2d1f72c 100644
--- a/docs/user-guide/installation.md
+++ b/docs/user-guide/installation.md
@@ -24,7 +24,7 @@ This guide covers installing the operator and deploying a
Superset instance in p
## Prerequisites
- Kubernetes cluster (see [Supported versions](#supported-kubernetes-versions))
-- Helm 3 (for Helm-based installation) or `kubectl` + `kustomize`
+- Helm 4 (for Helm-based installation) or `kubectl` + `kustomize`
- PostgreSQL or MySQL database
- (Optional) [Valkey](https://valkey.io/) (or Redis) — required when enabling
caching, Celery task broker, or result backend
- (Optional) [Gateway API
CRDs](https://gateway-api.sigs.k8s.io/guides/#installing-gateway-api) for
HTTPRoute support — not included in Kubernetes, must be installed separately
diff --git a/renovate.json b/renovate.json
index af31b0d..2c33fd7 100644
--- a/renovate.json
+++ b/renovate.json
@@ -67,6 +67,14 @@
"# renovate: datasource=(?<datasource>\\S+) depName=(?<depName>\\S+)(
versioning=(?<versioning>\\S+))?\\s+\\S+_VERSION=\"\\$\\{\\S+_VERSION:-(?<currentValue>[^\\\"]+)\\}\"[\\s\\S]*?\\S+_SHA256=\"\\$\\{\\S+_SHA256:-(?<currentDigest>[a-f0-9]+)\\}\""
],
"versioningTemplate": "{{#if
versioning}}{{{versioning}}}{{else}}semver-coerced{{/if}}"
+ },
+ {
+ "customType": "regex",
+ "description": "Track the version-only helm-unittest plugin pin (no
checksum; installed via helm plugin install)",
+ "managerFilePatterns": ["/^scripts\\/install-helm-unittest\\.sh$/"],
+ "matchStrings": [
+ "# renovate: datasource=(?<datasource>\\S+)
depName=(?<depName>\\S+)\\s+\\S+_VERSION=\"\\$\\{\\S+_VERSION:-(?<currentValue>[^\\\"]+)\\}\""
+ ]
}
]
}
diff --git a/scripts/check-chart-values-covered.sh
b/scripts/check-chart-values-covered.sh
new file mode 100755
index 0000000..a0e89fd
--- /dev/null
+++ b/scripts/check-chart-values-covered.sh
@@ -0,0 +1,73 @@
+#!/usr/bin/env bash
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements. See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+# Chart-test coverage gate.
+#
+# Fails when a knob documented in the chart's values.yaml is not exercised by
+# the comprehensive chart test values (tests/values/full-options.yaml). This
+# catches the case a snapshot cannot: a new value that defaults to null and is
+# consumed via a guarded block ({{- with .Values.newKnob }}) renders nothing
+# under defaults, so both the minimal and an under-specified comprehensive test
+# would stay green while the knob ships untested.
+#
+# Limitation: coverage is measured against values.yaml, so a template that
+# references an undocumented .Values.x is not caught here. That already
violates
+# the chart's documented-values contract (README is generated from values.yaml
+# via helm-docs) and is expected to be caught in review.
+
+set -euo pipefail
+
+CHART_DIR="${CHART_DIR:-charts/superset-operator}"
+VALUES="${CHART_DIR}/values.yaml"
+FULL="${CHART_DIR}/tests/values/full-options.yaml"
+
+for f in "${VALUES}" "${FULL}"; do
+ [ -f "${f}" ] || { echo "error: ${f} not found" >&2; exit 2; }
+done
+
+# Emit the dotted key paths of every "knob" in a values file: scalars plus
empty
+# maps/seqs (e.g. `affinity: {}`), which are leaves an author is expected to
set.
+# Numeric (array-index) path segments are stripped so coverage is
index-agnostic.
+leaf_paths() {
+ yq '.. | select(
+ (tag == "!!map" and length == 0) or
+ (tag == "!!seq" and length == 0) or
+ (tag != "!!map" and tag != "!!seq")
+ ) | path | join(".")' "$1" |
+ sed -E 's/\.[0-9]+//g' | grep -v '^$' | sort -u
+}
+
+# Emit every path in a values file (intermediate nodes included),
index-stripped.
+# A values.yaml leaf counts as covered when it appears anywhere in this set.
+all_paths() {
+ yq '.. | path | join(".")' "$1" |
+ sed -E 's/\.[0-9]+//g' | grep -v '^$' | sort -u
+}
+
+uncovered="$(comm -23 <(leaf_paths "${VALUES}") <(all_paths "${FULL}"))"
+
+if [ -n "${uncovered}" ]; then
+ {
+ echo "ERROR: values.yaml keys not exercised by ${FULL}:"
+ echo "${uncovered}" | awk '{ print " - " $0 }'
+ echo
+ echo "Add them to the comprehensive chart test values so the chart tests"
+ echo "cover every documented knob."
+ } >&2
+ exit 1
+fi
+
+echo "OK: every values.yaml knob is exercised by ${FULL}"
diff --git a/scripts/install-helm-unittest.sh b/scripts/install-helm-unittest.sh
new file mode 100755
index 0000000..35dcf2b
--- /dev/null
+++ b/scripts/install-helm-unittest.sh
@@ -0,0 +1,46 @@
+#!/usr/bin/env bash
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements. See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+# Installs the helm-unittest plugin, pinned to a known version. Idempotent:
+# skips the install when the pinned version is already present.
+
+set -euo pipefail
+
+# renovate: datasource=github-releases depName=helm-unittest/helm-unittest
+HELM_UNITTEST_VERSION="${HELM_UNITTEST_VERSION:-v1.1.1}"
+
+installed="$(helm plugin list 2>/dev/null | awk '$1 == "unittest" { print $2
}')"
+want="${HELM_UNITTEST_VERSION#v}"
+if [ "${installed}" = "${want}" ]; then
+ echo "helm-unittest ${HELM_UNITTEST_VERSION} already installed"
+ exit 0
+fi
+
+# helm 4 verifies plugin provenance by default and the upstream plugin does not
+# ship the required metadata yet, so skip verification there. helm 3 has no
such
+# flag, so only pass it when running helm >= 4.
+verify_flag=""
+helm_major="$(helm version --short 2>/dev/null | sed -E
's/^v?([0-9]+)\..*/\1/')"
+if [ -n "${helm_major}" ] && [ "${helm_major}" -ge 4 ]; then
+ verify_flag="--verify=false"
+fi
+
+# A stale/partial plugin dir breaks reinstall; remove it first.
+helm plugin uninstall unittest >/dev/null 2>&1 || true
+
+# shellcheck disable=SC2086
+helm plugin install https://github.com/helm-unittest/helm-unittest \
+ --version "${HELM_UNITTEST_VERSION}" ${verify_flag}
diff --git a/scripts/install-helm.sh b/scripts/install-helm.sh
index e8740fb..827df38 100644
--- a/scripts/install-helm.sh
+++ b/scripts/install-helm.sh
@@ -16,9 +16,10 @@
set -euo pipefail
-HELM_VERSION="${HELM_VERSION:-v3.19.2}"
+# renovate: datasource=github-releases depName=helm/helm
+HELM_VERSION="${HELM_VERSION:-v4.2.3}"
HELM_PLATFORM="${HELM_PLATFORM:-linux-amd64}"
-HELM_SHA256="${HELM_SHA256:-2114c9dea2844dce6d0ee2d792a9aae846be8cf53d5b19dc2988b5a0e8fec26e}"
+HELM_SHA256="${HELM_SHA256:-e9b88b4ee95b18c706839c28d3a0220e5bc470e9cd9262410c90793c45ff8b7c}"
archive="helm-${HELM_VERSION}-${HELM_PLATFORM}.tar.gz"
url="https://get.helm.sh/${archive}"
diff --git a/scripts/install-helm.sh b/scripts/install-yq.sh
old mode 100644
new mode 100755
similarity index 55%
copy from scripts/install-helm.sh
copy to scripts/install-yq.sh
index e8740fb..9004964
--- a/scripts/install-helm.sh
+++ b/scripts/install-yq.sh
@@ -14,18 +14,22 @@
# See the License for the specific language governing permissions and
# limitations under the License.
+# Installs the mikefarah/yq YAML processor, pinned by version and checksum.
+# The pinned SHA256 is for the default linux_amd64 asset (the CI platform);
+# override YQ_PLATFORM and YQ_SHA256 together to install elsewhere.
+
set -euo pipefail
-HELM_VERSION="${HELM_VERSION:-v3.19.2}"
-HELM_PLATFORM="${HELM_PLATFORM:-linux-amd64}"
-HELM_SHA256="${HELM_SHA256:-2114c9dea2844dce6d0ee2d792a9aae846be8cf53d5b19dc2988b5a0e8fec26e}"
+# renovate: datasource=github-releases depName=mikefarah/yq
+YQ_VERSION="${YQ_VERSION:-v4.53.3}"
+YQ_SHA256="${YQ_SHA256:-fa52a4e758c63d38299163fbdd1edfb4c4963247918bf9c1c5d31d84789eded4}"
+YQ_PLATFORM="${YQ_PLATFORM:-linux_amd64}"
-archive="helm-${HELM_VERSION}-${HELM_PLATFORM}.tar.gz"
-url="https://get.helm.sh/${archive}"
+asset="yq_${YQ_PLATFORM}"
+url="https://github.com/mikefarah/yq/releases/download/${YQ_VERSION}/${asset}"
tmpdir="$(mktemp -d)"
trap 'rm -rf "${tmpdir}"' EXIT
-curl -fsSL "${url}" -o "${tmpdir}/${archive}"
-printf '%s %s\n' "${HELM_SHA256}" "${tmpdir}/${archive}" | sha256sum -c -
-tar -xzf "${tmpdir}/${archive}" -C "${tmpdir}"
-sudo install -m 0755 "${tmpdir}/${HELM_PLATFORM}/helm" /usr/local/bin/helm
+curl -fsSL "${url}" -o "${tmpdir}/yq"
+printf '%s %s\n' "${YQ_SHA256}" "${tmpdir}/yq" | sha256sum -c -
+sudo install -m 0755 "${tmpdir}/yq" /usr/local/bin/yq
diff --git a/scripts/release-rc.sh b/scripts/release-rc.sh
index 1a79889..5af35ad 100755
--- a/scripts/release-rc.sh
+++ b/scripts/release-rc.sh
@@ -140,14 +140,18 @@ make codegen
info "Verifying Apache license headers"
make check-license
-info "Running golangci-lint"
+info "Running all linters (Go, Markdown, Helm chart)"
make lint
info "Running unit and integration tests"
make test-unit test-integration
-info "Linting Helm chart"
-make helm-lint
+info "Running Helm chart unit tests"
+bash scripts/install-helm-unittest.sh
+make helm-test
+
+info "Verifying Helm chart test value coverage"
+make helm-values-covered
info "Building docs"
make docs-build