Skip to content

refactor!: migrate to operator-go Gen 3 GenericReconciler - #92

Open
whg517 wants to merge 6 commits into
zncdatadev:mainfrom
whg517:refactor/gen3-generic-reconciler
Open

whg517 wants to merge 6 commits into
zncdatadev:mainfrom
whg517:refactor/gen3-generic-reconciler

Conversation

@whg517

@whg517 whg517 commented Aug 2, 2026

Copy link
Copy Markdown
Member

What

Migrates nifi-operator from the Gen 2 BaseCluster architecture (operator-go v0.12.6) to the Gen 3 GenericReconciler framework, pinned to the released operator-go v0.13.0 — including the declare/fold/derive (#632) and workload-RBAC (#616) APIs that landed on the way there.

Full design, parity method, and decision log: docs/gen3-migration-design.md.

How

  • CRD unchanged: NifiCluster.GetSpec() bridges the typed spec.nodes into the generic Roles["node"] map at runtime (trino/zookeeper pattern). Status embeds GenericClusterStatus (additive).
  • RoleProvider.DeclareRoles declares the node role once per pass: ports, the bash entrypoint as Command, TLS-dependent startup/liveness probes, valueFrom env (POD_NAME/STACKLET_NAME/ZooKeeper/OIDC), listener class, and the 30s graceful-shutdown fallback as a declared config default folded beneath the CR's levels.
  • RoleGroupResolver derives nifi.properties/bootstrap.conf (rendered by a plain non-escaping marshaler via RegisterFormat for byte parity with Gen 2) plus the authenticator's keys; configOverrides/envOverrides/jvmArgumentOverrides — dead fields in Gen 2 — become live. Image policy lives in ImageResolution; spec.image.pullSecretName is rendered again (dead since the commons ImageSpec migration).
  • BuildResources override adds what no seam can model: the gomplate prepare init container and git-sync inits (via sidecar.NewStaticContainerProvider), git-sync sidecars, product volumes, the document-style XML config files, and strips the generated readiness probe (Gen 2 parity; the declaration cannot say "none").
  • Workload RBAC is framework-owned: WorkloadRBACRules declares leases/configmaps for Kubernetes-native clustering; the framework derives the ServiceAccount (nificluster-<cluster>) and maintains the Role/RoleBinding — upstream's implementation of the operator-go#597 issue this migration filed.
  • ClusterExtensions: generate-once secrets via reconciler.EnsureGeneratedSecret (sensitive-props key incl. the autoGenerate: false fail-fast contract; OIDC admin password), and the NiFi 1.x reporting-task Job/Service (2.x gate unchanged, create-only ensure).
  • clusterConfig.listenerClass is honored (was ignored); the ZooKeeper discovery ConfigMap is a fail-fast Dependency; status conditions + orphan cleanup are active.

Verification

  • e2e is the acceptance gate and is unmodified: both chainsaw suites (git-sync, reporting-task) pass on kind v1.35.0 with NiFi 2.4.0 — locally at each adaptation round and in CI.
  • Rendered-resource parity: before/after snapshots of the git-sync CR (live Gen 2 baseline vs this branch), normalized diff — every residual line maps to the reviewed intentional-diff list (design doc §6). Byte parity of nifi.properties / bootstrap.conf / state-management.xml is locked by unit tests against fixtures captured from the Gen 2 baseline.
  • make lint && make test green; CRD spec schema byte-identical except commons-type upstream evolution (the #573 folded-defaults fix class); helm chart ClusterRole synced with generated RBAC; zero drift on the v0.13.0 pin.

Breaking / upgrade impact

Three rendered changes are irreconcilable with Gen 2 and need the documented manual migration for live clusters (design doc §8):

  • StatefulSet selector labels and serviceName change (framework-owned, immutable fields) — kubectl delete sts <cluster>-node-<group> --cascade=orphan, then let the operator recreate it (pods adopted; Gen 2 created no PVCs, so no data risk).
  • The workload ServiceAccount/Role/RoleBinding are renamed to the framework-derived nificluster-<cluster>; the Gen 2 <cluster>-nifi objects are left behind and should be deleted manually.

Bug fixes riding along

nil panic when authentication unset · reporting-task Service selector (managed-by mismatch — matched zero pods) and targetPort (always https) · OIDC admin secret registered for LDAP instead of OIDC · LDAP scope service=service=<name> double prefix · duplicate AuthenticationClass GET per reconcile · nondeterministic git-sync args.

Framework feedback loop

This migration filed operator-go#596/#597/#598; all three were answered upstream before v0.13.0 shipped — #597 implemented as #616 (adopted here, deleting internal/extensions/rbac.go), #596/#598 answered with docs and pinned specs (the vendored bash helpers and the create-only Job ensure remain, annotated with upstream's reasoning). No blockers — every gap had a supported workaround.

🤖 Generated with Claude Code

whg517 and others added 4 commits August 2, 2026 23:52
Records the parity strategy (rendered-resource parity against a live Gen 2
baseline), the component mapping onto operator-go's GenericReconciler +
BaseRoleGroupHandler, the reviewed intentional-diff list, the upgrade path
for existing clusters, and the operator-go feedback ledger (issues #596,
#597, #598 filed upstream).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Replace the Gen 2 BaseCluster reconciler tree with the Gen 3 framework
(operator-go pinned to main @ 0ec90d7):

- NifiCluster implements common.ClusterInterface: GetSpec() bridges the
  typed spec.nodes into the generic Roles map at runtime; the CRD spec
  schema is unchanged. Status embeds GenericClusterStatus (additive).
- NifiRoleGroupHandler embeds BaseRoleGroupHandler: the framework builds
  the ConfigMap/Services/StatefulSet/role PDB skeleton; the override adds
  auth wiring, the gomplate prepare init container (via the sidecar
  channel's StaticContainerProvider), git-sync containers, and the
  document-style XML config files.
- nifi.properties/bootstrap.conf flow through ProductConfig (lowest merge
  layer, rendered by a plain non-escaping marshaler for byte parity with
  Gen 2); configOverrides/envOverrides/jvmArgumentOverrides become live.
- Cluster-scope resources move to ClusterExtensions: generate-once secrets
  via reconciler.EnsureGeneratedSecret (sensitive key, OIDC admin
  password), per-CR pod RBAC, and the NiFi 1.x reporting-task Job/Service.
- clusterConfig.listenerClass is honored (was ignored); the ZooKeeper
  discovery ConfigMap is declared as a fail-fast dependency; status
  conditions and orphaned role-group cleanup become active.

Rendered-resource parity was verified against a live Gen 2 baseline with a
normalized diff; every residual difference is on the reviewed
intentional-diff list in docs/gen3-migration-design.md (notably the STS
selector labels and serviceName, which are immutable — in-place upgrades
of live clusters need the documented --cascade=orphan migration).

Bug fixes riding along: nil panic when authentication is unset, the
reporting-task Service selector/targetPort that matched nothing, the OIDC
admin secret registered for LDAP instead of OIDC, the LDAP scope
double-prefix, duplicate AuthenticationClass reads, and nondeterministic
git-sync args.

Both chainsaw e2e suites pass unmodified (kind 1.35.0, NiFi 2.4.0).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Track operator-go main from 0ec90d7 to e8a9495, adapting to the two
breaking changes and adopting what they implement:

- #616 (workload ServiceAccount + RBAC): ServiceAccountNameFunc is gone;
  the framework derives 'nificluster-<cluster>' and, via the new
  WorkloadRBACRules hook, maintains the leases/configmaps Role/RoleBinding
  NiFi's pods need for Kubernetes-native clustering. The hand-rolled
  RbacExtension is deleted — this is upstream's implementation of the
  issue #597 this migration filed. Upgrade note: the Gen 2
  '<cluster>-nifi' SA/Role/RoleBinding are left behind and should be
  removed manually.
- #632 (declare, fold, derive): the handler now declares the 'node' role
  once per pass via RoleProvider.DeclareRoles (ports, bash entrypoint as
  Command, TLS-dependent probes, valueFrom env, listener class, and the
  '30s' gracefulShutdownTimeout as a declared config default folded
  beneath the CR's levels), derives nifi.properties/bootstrap.conf plus
  the authenticator's keys via RoleGroupResolver, and moves image policy
  to GenericReconcilerConfig.ImageResolution. The generated readiness
  probe is still stripped post-build (Gen 2 parity; the declaration has
  no way to say 'none').
- #622: spec.image.pullSecretName is forwarded through the bridge and
  rendered by the framework — the field had been dead since the commons
  ImageSpec migration.
- Upstream answered this migration's other two issues without new API:
  #596 -> #617 (vendored bash helpers stay product-side; native sidecars
  supersede the vector shutdown handshake long-term), #598 -> #620 (the
  reporting-task extension keeps its create-only ensure).

Config byte parity is locked by the existing fixture tests; both chainsaw
suites pass unmodified on kind 1.35.0 / NiFi 2.4.0, and the rendered
StatefulSet was re-verified point-by-point (entrypoint moved from args
into command, SA rename, container order, probes, selector unchanged).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@whg517

whg517 commented Aug 23, 2026

Copy link
Copy Markdown
Member Author

Rebased onto the latest operator-go main (0ec90d7e8a9495, 19 commits) in e14b589:

  • #616 implemented the pod-RBAC channel this migration requested in operator-go#597: WorkloadRBACRules now maintains the leases/configmaps Role/RoleBinding, and the hand-rolled RbacExtension is deleted. The workload ServiceAccount is framework-derived (nificluster-<cluster>) — upgrade note added to the design doc (the Gen 2 <cluster>-nifi objects are left behind).
  • #632 (declare/fold/derive): the handler now declares the node role via RoleProvider.DeclareRoles (entrypoint as Command, TLS-dependent probes, valueFrom env, the 30s graceful-shutdown default as a declared config default) and derives config via RoleGroupResolver; image policy moved to ImageResolution.
  • #622: spec.image.pullSecretName is live again (forwarded through the bridge, rendered by the framework).
  • operator-go#596/#598 were answered upstream with docs (no new API) — the vendored bash helpers and the extension's create-only Job ensure remain, now annotated with upstream's reasoning.

Gates re-run on the new pin: make lint && make test green, config-byte parity fixtures green, both chainsaw suites pass unmodified (kind 1.35.0, NiFi 2.4.0), rendered StatefulSet re-verified point-by-point.

whg517 and others added 2 commits August 23, 2026 21:03
Line length 80, compact-style lists instead of the wide mapping table,
ordered lists numbered from 1 — and fold the second adoption round into
the §3/§4 prose so the document describes the current wiring instead of
the superseded first-round shape.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The tag is our previous pin (e8a9495) plus only the release CHANGELOG
commit — zero API delta, and the regenerated CRDs/manifests confirm zero
drift. The PR no longer depends on a pseudo-version.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant