The helm chart's operator ClusterRole is a hand-maintained copy of a generated file, and nothing verifies it. This has already caused two production-visible defects in this repo, and one sibling repo is drifted right now. This issue carries a tested design and implementation so every operator repo can align on the same fix.
Problem
config/rbac/role.yaml is generated from kubebuilder markers by make manifests. The chart's deploy/helm/<operator>/templates/clusterrole.yaml is a hand-written duplicate of its rules. Only CRDs have a sync target:
helm-crd-sync: manifests kustomize
$(KUSTOMIZE) build config/crd > deploy/helm/<operator>/crds/crds.yaml
There is no equivalent for RBAC, so adding a kubebuilder marker updates the kustomize deployment and silently leaves the chart behind — and the chart is what chart-e2e, the published release and every user actually deploy.
The existing CI gate does not cover it either. check-crds-sync runs make manifests then git diff --exit-code, which only proves the files under config/ are current. Neither chart copy — crds/crds.yaml nor templates/clusterrole.yaml — is regenerated in that job, so neither is verified.
Evidence this is real, not hypothetical
- This repo has fixed the same drift twice:
2ea40dd ("fix(helm): sync chart ClusterRole with generated RBAC rules"), and again in 867fd76, where a missing core/events rule meant every event the framework emitted was rejected in the helm deployment. The second one was invisible for the operator's entire Gen-3 life because event emission is best-effort — one line in the operator log, reconcile reports success, kubectl describe shows nothing.
- Fixing the same hole twice is the signal: what is missing is automation, not diligence.
- Across the org today: 0 of 14 operator repos have any RBAC sync target. 13 charts happen to match;
nifi-operator is currently missing batch/jobs and coordination.k8s.io/leases that its own generated role has.
Design
Constraints that shape the solution:
- The chart file is a helm template, not plain YAML —
metadata.name/labels come from include helpers, and the whole document is wrapped in {{- if .Values.serviceAccount.create -}} … {{- end }}. So it cannot be replaced wholesale from config/, and YAML-aware tools (yq) cannot parse it in place.
- Only
.rules is generated content; everything else is chart-owned and must survive.
- It should add no new tool dependency —
yq/jq are not pinned in these repos' Makefiles.
- The layout is identical in every operator repo I checked (hive, trino, nifi, hdfs, zookeeper):
{{- if … -}} on line 1, templated metadata, rules: as the last top-level key, {{- end }} on the last line. So one implementation ports unchanged.
The generated config/rbac/role.yaml ends with rules: and its list, so the whole rules block is simply "from ^rules: to EOF". That makes the splice three line-range operations and no parser.
Implementation
HELM_CHART_DIR ?= deploy/helm/$(PROJECT_NAME)
.PHONY: helm-rbac-sync
helm-rbac-sync: manifests ## Sync the generated operator ClusterRole rules into the helm chart
@tmp=$$(mktemp); \
{ \
sed -n '1,/^rules:/p' $(HELM_CHART_DIR)/templates/clusterrole.yaml | sed '$$d'; \
sed -n '/^rules:/,$$p' config/rbac/role.yaml; \
echo '{{- end }}'; \
} > $$tmp; \
mv $$tmp $(HELM_CHART_DIR)/templates/clusterrole.yaml
- line 1 keeps everything above
rules: — the {{- if }} guard and the templated metadata;
- line 2 substitutes the generated rules;
- line 3 restores the closing
{{- end }}.
Then extend the existing gate rather than adding a new one, so chart CRDs get covered at the same time:
- name: Check generated files are in sync
run: |
make manifests helm-crd-sync helm-rbac-sync
if ! git diff --exit-code; then
echo "Generated files are out of sync."
echo "Run 'make manifests helm-crd-sync helm-rbac-sync' and commit the result."
exit 1
fi
Optional stronger assertion, for repos willing to depend on helm+yq+jq — it validates the rendered result rather than the file, so it keeps holding if the template is restructured:
diff <(yq -o=json '.rules' config/rbac/role.yaml | jq -S .) \
<(helm template x $(HELM_CHART_DIR) | yq -o=json 'select(.kind=="ClusterRole") | .rules' | jq -S .)
Validation performed
- Idempotent: run against this repo (currently in sync), the target reproduces
templates/clusterrole.yaml byte for byte.
- Corrective: run against
nifi-operator (currently drifted), it adds exactly the missing batch/jobs and coordination.k8s.io/leases rules, after which the rendered chart ClusterRole equals the generated role. (Verified in a scratch copy; that repo's working tree was restored.)
Rollout
Same three edits in each repo: add the target, extend the CI step, commit the regenerated chart file. Current status:
| repo |
chart ClusterRole vs generated |
| nifi-operator |
drifted — missing batch/jobs, coordination.k8s.io/leases |
| airflow, commons, dolphinscheduler, hdfs, hive, kafka, listener, secret, spark-k8s, superset, trino, zookeeper |
in sync today, unguarded |
| doris, hbase |
no chart ClusterRole found — check separately |
I will implement it here first; this issue is meant to be the reference other repos copy.
The helm chart's operator
ClusterRoleis a hand-maintained copy of a generated file, and nothing verifies it. This has already caused two production-visible defects in this repo, and one sibling repo is drifted right now. This issue carries a tested design and implementation so every operator repo can align on the same fix.Problem
config/rbac/role.yamlis generated from kubebuilder markers bymake manifests. The chart'sdeploy/helm/<operator>/templates/clusterrole.yamlis a hand-written duplicate of itsrules. Only CRDs have a sync target:There is no equivalent for RBAC, so adding a kubebuilder marker updates the kustomize deployment and silently leaves the chart behind — and the chart is what
chart-e2e, the published release and every user actually deploy.The existing CI gate does not cover it either.
check-crds-syncrunsmake manifeststhengit diff --exit-code, which only proves the files underconfig/are current. Neither chart copy —crds/crds.yamlnortemplates/clusterrole.yaml— is regenerated in that job, so neither is verified.Evidence this is real, not hypothetical
2ea40dd("fix(helm): sync chart ClusterRole with generated RBAC rules"), and again in867fd76, where a missingcore/eventsrule meant every event the framework emitted was rejected in the helm deployment. The second one was invisible for the operator's entire Gen-3 life because event emission is best-effort — one line in the operator log, reconcile reports success,kubectl describeshows nothing.nifi-operatoris currently missingbatch/jobsandcoordination.k8s.io/leasesthat its own generated role has.Design
Constraints that shape the solution:
metadata.name/labelscome fromincludehelpers, and the whole document is wrapped in{{- if .Values.serviceAccount.create -}} … {{- end }}. So it cannot be replaced wholesale fromconfig/, and YAML-aware tools (yq) cannot parse it in place..rulesis generated content; everything else is chart-owned and must survive.yq/jqare not pinned in these repos' Makefiles.{{- if … -}}on line 1, templated metadata,rules:as the last top-level key,{{- end }}on the last line. So one implementation ports unchanged.The generated
config/rbac/role.yamlends withrules:and its list, so the whole rules block is simply "from^rules:to EOF". That makes the splice three line-range operations and no parser.Implementation
rules:— the{{- if }}guard and the templated metadata;{{- end }}.Then extend the existing gate rather than adding a new one, so chart CRDs get covered at the same time:
Optional stronger assertion, for repos willing to depend on
helm+yq+jq— it validates the rendered result rather than the file, so it keeps holding if the template is restructured:Validation performed
templates/clusterrole.yamlbyte for byte.nifi-operator(currently drifted), it adds exactly the missingbatch/jobsandcoordination.k8s.io/leasesrules, after which the rendered chart ClusterRole equals the generated role. (Verified in a scratch copy; that repo's working tree was restored.)Rollout
Same three edits in each repo: add the target, extend the CI step, commit the regenerated chart file. Current status:
batch/jobs,coordination.k8s.io/leasesI will implement it here first; this issue is meant to be the reference other repos copy.