Skip to content

Sync the operator ClusterRole into the helm chart automatically — it is hand-maintained and drifts (cross-operator convention) #371

Description

@whg517

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:

  1. 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.
  2. Only .rules is generated content; everything else is chart-owned and must survive.
  3. It should add no new tool dependencyyq/jq are not pinned in these repos' Makefiles.
  4. 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.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions