Skip to content

chore: remove flux from kubernetes controller#2068

Merged
jakobmoellerdev merged 6 commits into
open-component-model:mainfrom
jakobmoellerdev:remove-flux-x
Mar 26, 2026
Merged

chore: remove flux from kubernetes controller#2068
jakobmoellerdev merged 6 commits into
open-component-model:mainfrom
jakobmoellerdev:remove-flux-x

Conversation

@jakobmoellerdev

@jakobmoellerdev jakobmoellerdev commented Mar 25, 2026

Copy link
Copy Markdown
Member

What this PR does / why we need it

this drops flux from the controller dependencies completely.

Which issue(s) this PR fixes

resolves status update issues we had due to the status patcher and drops our dependency in code to flux

this also fixes up a bug where the status was not patched in one go, and where the status was patched after the conditions were set.

…ling

Replace FluxCD runtime/conditions helpers with standard
k8s.io/apimachinery/pkg/api/meta for condition management across all
four controllers (Repository, Component, Resource, Deployer).

- Add own condition type and reason constants (ReadyCondition,
  ReconcilingCondition, StalledCondition, SucceededReason,
  ProgressingWithRetryReason) with identical string values for
  backward compatibility
- Create thin wrappers around apimeta in status/conditions.go
- Add semantic requeue helper (status.RequeueResult) that derives
  ctrl.Result from condition state
- Set EffectiveOCMConfig immediately after GetEffectiveConfig()
  succeeds so the deferred patch always persists correct status data
- Remove all fluxcd/pkg/runtime/conditions imports from Go sources

Signed-off-by: Jakob Möller <contact@jakob-moeller.com>
@coderabbitai

coderabbitai Bot commented Mar 25, 2026

Copy link
Copy Markdown
Contributor

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 8154f87f-8aca-41b3-aeae-a40da99c7b31

📥 Commits

Reviewing files that changed from the base of the PR and between 754b83b and ec594fd.

⛔ Files ignored due to path filters (1)
  • kubernetes/controller/go.sum is excluded by !**/*.sum
📒 Files selected for processing (1)
  • kubernetes/controller/go.mod
💤 Files with no reviewable changes (1)
  • kubernetes/controller/go.mod

📝 Walkthrough

Walkthrough

Replaces FluxCD condition/patch/event helpers with local types and Kubernetes apimachinery equivalents across controllers, tests, and utilities. Adds local NamespacedObjectKindReference and condition helpers, changes status patching to deep-copy + conditional Status().Patch, removes manager.events.address config and Flux event/meta/runtime deps.

Changes

Cohort / File(s) Summary
API types & interfaces
kubernetes/controller/api/v1alpha1/common_types.go, kubernetes/controller/api/v1alpha1/condition_types.go, kubernetes/controller/api/v1alpha1/interfaces.go
Added local NamespacedObjectKindReference, new condition/severity constants, and replaced embedded Flux conditions.Setter with client.Object + ConditionAccessor in OCMK8SObject.
Autogen deepcopy
kubernetes/controller/api/v1alpha1/zz_generated.deepcopy.go
Added autogenerated DeepCopy/DeepCopyInto for NamespacedObjectKindReference.
Helm chart & schema
kubernetes/controller/chart/README.md, kubernetes/controller/chart/templates/manager/manager.yaml, kubernetes/controller/chart/values.schema.json, kubernetes/controller/chart/values.yaml
Removed manager.events.address from values, template args, README and JSON schema.
Module & manager startup
kubernetes/controller/go.mod, kubernetes/controller/cmd/main.go
Dropped Flux event/meta/runtime deps; removed --events-addr flag and custom recorder creation; now use mgr.GetEventRecorderFor(...).
Status package & contracts
kubernetes/controller/internal/status/conditions.go, .../identifiable_contract.go, .../mutate_condition_status.go, .../register_status_defer.go, .../requeue.go
New ConditionObject and helpers (SetCondition, RemoveCondition, FindCondition, IsReady, etc.). Replaced Flux patch/update flow with UpdateBeforePatch and added RequeueResult. Condition mutation functions now use metav1.Condition and local helpers.
Event handling
kubernetes/controller/internal/event/event.go, kubernetes/controller/internal/event/event_test.go
Introduced exported EventObject interface; derive reason via apimachinery condition lookup; use v1alpha1 severity constants.
Controller reconcile changes
kubernetes/controller/internal/controller/component/.../component_controller.go, .../deployer_controller.go, .../repository_controller.go, .../resource/resource_controller.go, .../resource/predicates.go
Removed Flux serial patcher; use deep-copy + conditional Status().Patch(...) on semantic inequality; set Status.EffectiveOCMConfig early and return error to ensure persistence; switched event severities to v1alpha1.*; use status.RequeueResult for requeue semantics.
Tests & test utilities
kubernetes/controller/internal/.../*_test.go, kubernetes/controller/internal/test/*, kubernetes/controller/internal/ocm/ocm_test.go, .../resolution/service_test.go, .../setup/integration_test.go, internal/util/k8s.go
Replaced Flux meta/conditions with v1alpha1 types and apimachinery condition helpers; updated many test fixtures, assertions, and status update flows to direct Status().Patch/.Update.
Unit/integration helpers
kubernetes/controller/internal/test/*, kubernetes/controller/internal/test/k8s.go
Reworked readiness polling and mock status updates to deep-copy + status patch; use apimachinery condition helpers for readiness detection.

Sequence Diagram(s)

sequenceDiagram
    participant Reconciler
    participant StatusPkg as status (helpers)
    participant K8sAPI as Kubernetes API (client.Status())
    participant ControllerMgr as Manager (event recorder)

    Reconciler->>StatusPkg: compute EffectiveOCMConfig
    alt EffectiveOCMConfig differs from Status
        Reconciler->>Reconciler: set Status.EffectiveOCMConfig
        Reconciler-->>Reconciler: return error (force status persist)
    end
    Reconciler->>StatusPkg: UpdateBeforePatch(obj, recorder, requeue, err)
    Note right of Reconciler: deferred execution before final patch
    Reconciler->>K8sAPI: Status().Patch(ctx, obj, client.MergeFrom(old))  when status changed
    K8sAPI-->>Reconciler: patch result
    Reconciler->>ControllerMgr: mgr.GetEventRecorderFor(...) used for events
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

Suggested labels

!BREAKING-CHANGE!

Suggested reviewers

  • Skarlso
  • frewilhelm

"🐰
A hop of code, a tidy seam,
I replaced the old with a local dream.
Conditions neat and patches small,
Events reported, no more sprawl.
Hop on green—tests pass, we all beam!"

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 45.45% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately summarizes the main change: removing Flux as a dependency from the Kubernetes controller codebase.
Description check ✅ Passed The description clearly explains the motivation (drops flux dependencies, resolves status update issues) and benefits (fixes status patching bug), directly relating to the changeset.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@github-actions github-actions Bot added the size/l Large label Mar 25, 2026
@jakobmoellerdev jakobmoellerdev changed the title remove flux from kubernetes controller chore: remove flux from kubernetes controller Mar 25, 2026
@github-actions github-actions Bot added the kind/chore chore, maintenance, etc. label Mar 25, 2026
Replace the three remaining FluxCD packages with local implementations:

- Replace meta.NamespacedObjectKindReference with identical local struct
  in api/v1alpha1/common_types.go (CRD-compatible, same JSON tags)
- Replace eventv1.EventSeverityInfo/Error with own constants
- Replace patch.SerialPatcher with local StatusPatcher using
  client.MergeFrom + client.Status().Patch()
- Replace events.Recorder with standard mgr.GetEventRecorderFor()
  (drops Flux-specific --events-addr webhook forwarding)
- Run go mod tidy to remove fluxcd from go.mod entirely

BREAKING CHANGE: OCMConfiguration.NamespacedObjectKindReference type
changed from github.com/fluxcd/pkg/apis/meta to local v1alpha1 type.
The serialized JSON format is unchanged.

Signed-off-by: Jakob Möller <contact@jakob-moeller.com>
- Replace StatusPatcher with equality.Semantic.DeepEqual checks and client.MergeFrom patches
- Simplify status update logic in controllers by handling observed generation and status patching directly

BREAKING CHANGE: StatusPatcher utility was removed, affecting custom patching logic.

Signed-off-by: Jakob Möller <contact@jakob-moeller.com>
Signed-off-by: Jakob Möller <contact@jakob-moeller.com>
@jakobmoellerdev jakobmoellerdev marked this pull request as ready for review March 25, 2026 16:57
@jakobmoellerdev jakobmoellerdev requested a review from a team as a code owner March 25, 2026 16:57

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
kubernetes/controller/internal/controller/deployer/deployer_controller_test.go (1)

997-1018: ⚠️ Potential issue | 🟠 Major

Refresh the MergeFrom base inside each Eventually retry.

Lines 997 and 1274 capture old before the retry loop. If another reconcile updates the resource first, every retry reuses the same stale object/base pair, so Status().Patch(...) repeats the conflict instead of recovering. Fetch the current resource inside the retry closure and use that as the merge base.

Safer pattern
Eventually(func(ctx context.Context) error {
	current := &v1alpha1.Resource{}
	if err := k8sClient.Get(ctx, client.ObjectKeyFromObject(resourceObj), current); err != nil {
		return err
	}

	old := current.DeepCopy()
	current.Status.Component = /* ... */
	current.Status.Resource = /* ... */
	status.MarkReady(recorder, current, "applied mock resource")
	current.SetObservedGeneration(current.GetGeneration())

	return k8sClient.Status().Patch(ctx, current, client.MergeFrom(old))
}).WithContext(ctx).Should(Succeed())

Also applies to: 1274-1296

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@kubernetes/controller/internal/controller/deployer/deployer_controller_test.go`
around lines 997 - 1018, The retry closure captures a stale "old" snapshot (old/
resourceObj) before Eventually, causing MergeFrom conflicts; update the closure
used by Eventually to fetch the latest object via k8sClient.Get (into a local
current variable), set current.Status.Component/Resource, call
status.MarkReady(recorder, current, ...), take old := current.DeepCopy() and
then call k8sClient.Status().Patch(ctx, current, client.MergeFrom(old)); apply
the same change to the other Eventually block that currently reuses the
precomputed old.
🧹 Nitpick comments (2)
kubernetes/controller/internal/controller/component/component_controller_unit_test.go (1)

398-404: Minor: Duplicate nil check.

Line 403 duplicates the nil check already performed on line 399.

🔧 Remove duplicate assertion
 	cond := apimeta.FindStatusCondition(updatedComponent.GetConditions(), v1alpha1.ReadyCondition)
 	g.Expect(cond).ToNot(BeNil())
 	g.Expect(cond.Reason).To(Equal(v1alpha1.ResolutionInProgress),
 		"expected component ready-condition reason to be %s, got %s",
 		v1alpha1.ResolutionInProgress, cond.Reason)
-	g.Expect(cond).ToNot(BeNil())
 	g.Expect(cond.Message).To(ContainSubstring("resolution in progress"))
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@kubernetes/controller/internal/controller/component/component_controller_unit_test.go`
around lines 398 - 404, The test contains a duplicated nil-check on the resolved
condition: remove the redundant g.Expect(cond).ToNot(BeNil()) that appears after
the Reason assertion so the code uses a single nil-check (the one immediately
after cond := apimeta.FindStatusCondition(updatedComponent.GetConditions(),
v1alpha1.ReadyCondition)); keep the Reason and Message assertions (cond.Reason
and cond.Message) as-is.
kubernetes/controller/internal/controller/resource/resource_controller.go (1)

281-286: Consider clarifying the intent of the "effective ocm config changed" error.

Returning an error here is intentional to trigger the deferred patch for persisting the updated EffectiveOCMConfig. However, this pattern results in the reconciliation being retried via the error path rather than a clean requeue, and the error message may appear alarming in logs/events.

This is a stylistic choice and not a bug since the deferred patch will correctly persist the config change, but you might consider:

  1. Using a sentinel error type to distinguish this from actual failures
  2. Or documenting this pattern with a comment explaining it's an intentional early-exit mechanism
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@kubernetes/controller/internal/controller/resource/resource_controller.go`
around lines 281 - 286, Replace the generic error return used to trigger the
deferred patch with a clear sentinel error and/or an explanatory comment: where
the code currently checks
equality.Semantic.DeepEqual(resource.Status.EffectiveOCMConfig, configs) and
sets resource.Status.EffectiveOCMConfig = configs, add a short comment stating
this is an intentional early-exit to persist the updated EffectiveOCMConfig via
the deferred patch, and replace fmt.Errorf("effective ocm config changed") with
a package-level sentinel error (e.g., ErrEffectiveOCMConfigChanged) so callers
and logs can distinguish this intentional control flow from real failures;
ensure code that handles reconciliation treats this sentinel appropriately.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@kubernetes/controller/internal/controller/deployer/deployer_controller.go`:
- Around line 292-297: The defer currently only patches the status subresource
and so metadata mutations (finalizers, labels, annotations) made to the deployer
are lost; update the defer func (which currently calls
status.UpdateBeforePatch(deployer, r.EventRecorder, 0, err)) to first detect
metadata changes by comparing deployer.Finalizers, deployer.GetAnnotations(),
and deployer.GetLabels() to old.Finalizers/old annotations/old labels using
equality.Semantic.DeepEqual and, if any differ, call r.GetClient().Patch(ctx,
deployer, client.MergeFrom(old)) (joining any error with errors.Join), then
retain the existing check that patches the status subresource with
r.GetClient().Status().Patch(ctx, deployer, client.MergeFrom(old)) when
deployer.Status changed.

---

Outside diff comments:
In
`@kubernetes/controller/internal/controller/deployer/deployer_controller_test.go`:
- Around line 997-1018: The retry closure captures a stale "old" snapshot (old/
resourceObj) before Eventually, causing MergeFrom conflicts; update the closure
used by Eventually to fetch the latest object via k8sClient.Get (into a local
current variable), set current.Status.Component/Resource, call
status.MarkReady(recorder, current, ...), take old := current.DeepCopy() and
then call k8sClient.Status().Patch(ctx, current, client.MergeFrom(old)); apply
the same change to the other Eventually block that currently reuses the
precomputed old.

---

Nitpick comments:
In
`@kubernetes/controller/internal/controller/component/component_controller_unit_test.go`:
- Around line 398-404: The test contains a duplicated nil-check on the resolved
condition: remove the redundant g.Expect(cond).ToNot(BeNil()) that appears after
the Reason assertion so the code uses a single nil-check (the one immediately
after cond := apimeta.FindStatusCondition(updatedComponent.GetConditions(),
v1alpha1.ReadyCondition)); keep the Reason and Message assertions (cond.Reason
and cond.Message) as-is.

In `@kubernetes/controller/internal/controller/resource/resource_controller.go`:
- Around line 281-286: Replace the generic error return used to trigger the
deferred patch with a clear sentinel error and/or an explanatory comment: where
the code currently checks
equality.Semantic.DeepEqual(resource.Status.EffectiveOCMConfig, configs) and
sets resource.Status.EffectiveOCMConfig = configs, add a short comment stating
this is an intentional early-exit to persist the updated EffectiveOCMConfig via
the deferred patch, and replace fmt.Errorf("effective ocm config changed") with
a package-level sentinel error (e.g., ErrEffectiveOCMConfigChanged) so callers
and logs can distinguish this intentional control flow from real failures;
ensure code that handles reconciliation treats this sentinel appropriately.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 4d1338bd-da16-40ed-af68-5f014d50f18a

📥 Commits

Reviewing files that changed from the base of the PR and between 6b5d591 and ccfab50.

⛔ Files ignored due to path filters (1)
  • kubernetes/controller/go.sum is excluded by !**/*.sum
📒 Files selected for processing (37)
  • kubernetes/controller/api/v1alpha1/common_types.go
  • kubernetes/controller/api/v1alpha1/condition_types.go
  • kubernetes/controller/api/v1alpha1/interfaces.go
  • kubernetes/controller/api/v1alpha1/zz_generated.deepcopy.go
  • kubernetes/controller/chart/README.md
  • kubernetes/controller/chart/templates/manager/manager.yaml
  • kubernetes/controller/chart/values.schema.json
  • kubernetes/controller/chart/values.yaml
  • kubernetes/controller/cmd/main.go
  • kubernetes/controller/go.mod
  • kubernetes/controller/internal/configuration/config_test.go
  • kubernetes/controller/internal/controller/component/component_controller.go
  • kubernetes/controller/internal/controller/component/component_controller_test.go
  • kubernetes/controller/internal/controller/component/component_controller_unit_test.go
  • kubernetes/controller/internal/controller/deployer/deployer_controller.go
  • kubernetes/controller/internal/controller/deployer/deployer_controller_test.go
  • kubernetes/controller/internal/controller/repository/repository_controller.go
  • kubernetes/controller/internal/controller/repository/repository_controller_test.go
  • kubernetes/controller/internal/controller/resource/predicates.go
  • kubernetes/controller/internal/controller/resource/predicates_test.go
  • kubernetes/controller/internal/controller/resource/resource_controller.go
  • kubernetes/controller/internal/controller/resource/resource_controller_test.go
  • kubernetes/controller/internal/event/event.go
  • kubernetes/controller/internal/event/event_test.go
  • kubernetes/controller/internal/ocm/ocm_test.go
  • kubernetes/controller/internal/resolution/service_test.go
  • kubernetes/controller/internal/setup/integration_test.go
  • kubernetes/controller/internal/status/conditions.go
  • kubernetes/controller/internal/status/identifiable_contract.go
  • kubernetes/controller/internal/status/mutate_condition_status.go
  • kubernetes/controller/internal/status/register_status_defer.go
  • kubernetes/controller/internal/status/requeue.go
  • kubernetes/controller/internal/test/component.go
  • kubernetes/controller/internal/test/k8s.go
  • kubernetes/controller/internal/test/ocmrepository.go
  • kubernetes/controller/internal/test/resource.go
  • kubernetes/controller/internal/util/k8s.go
💤 Files with no reviewable changes (5)
  • kubernetes/controller/chart/values.yaml
  • kubernetes/controller/chart/values.schema.json
  • kubernetes/controller/chart/templates/manager/manager.yaml
  • kubernetes/controller/go.mod
  • kubernetes/controller/chart/README.md

The defer block in the deployer Reconcile function only called
Status().Apply(), which persists the status subresource but not
object metadata. Finalizers added (applySetPruneFinalizer,
resourceWatchFinalizer) and removed in reconcileDeletionTimestamp
were never written back to the API server, causing new deployers
to silently miss cleanup finalizers and deleting deployers to get
stuck.

Fix by capturing a DeepCopy of the deployer before reconciliation
and, in the defer, issuing a MergeFrom patch when Finalizers differ.

Adds integration tests that verify both finalizer addition (deployer
reaches Ready with both finalizers present) and removal (deployer is
fully deleted without getting stuck).

Signed-off-by: Jakob Möller <contact@jakob-moeller.com>

@fabianburth fabianburth left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One comment, not critical. LGTM

@jakobmoellerdev jakobmoellerdev merged commit b084c3f into open-component-model:main Mar 26, 2026
39 of 45 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

kind/chore chore, maintenance, etc. size/l Large

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants