Skip to content

Comments

Upgrade controller-runtime, address breaking changes, and fix linter issues#905

Merged
adamwg merged 4 commits intocrossplane:mainfrom
adamkasztenny:upgrade-controller-runtime
Feb 5, 2026
Merged

Upgrade controller-runtime, address breaking changes, and fix linter issues#905
adamwg merged 4 commits intocrossplane:mainfrom
adamkasztenny:upgrade-controller-runtime

Conversation

@adamkasztenny
Copy link
Contributor

@adamkasztenny adamkasztenny commented Jan 19, 2026

Description of your changes

The breaking change is described in kubernetes-sigs/controller-runtime#3321.

Turns out this is a bit of a rabbit hole since Go also had to be upgraded, followed by golangci-lint, which caused a lot of linter failures which had to be fixed. Some lint failures were auto-fixed, others needed changes.

AI disclosure: Some commits were created with Cursor using Claude 4.5 Opus.

Fixes #904.

I have:

  • Read and followed Crossplane's [contribution process].
  • Run earthly +reviewable to ensure this PR is ready for review.
  • [ ] Added or updated unit tests.
    • Not relevant here I think. Existing tests should cover linter updates
  • [ ] Linked a PR or a [docs tracking issue] to [document this change].
  • [ ] Added backport release-x.y labels to auto-backport this PR.
    • Not necessary in this case I think

@adamkasztenny adamkasztenny requested a review from a team as a code owner January 19, 2026 19:31
@adamkasztenny adamkasztenny requested a review from adamwg January 19, 2026 19:31
@adamkasztenny adamkasztenny marked this pull request as draft January 19, 2026 19:31
@adamkasztenny adamkasztenny force-pushed the upgrade-controller-runtime branch 2 times, most recently from 2c0fb48 to 837d3a7 Compare January 19, 2026 19:40
@adamkasztenny adamkasztenny force-pushed the upgrade-controller-runtime branch 2 times, most recently from fe53e56 to 6e789c1 Compare February 5, 2026 00:28
@adamkasztenny adamkasztenny marked this pull request as ready for review February 5, 2026 00:29
This upgrade required several changes to address breaking changes and
new linter requirements:

- Update go.mod dependencies for controller-runtime v0.21.0
- Fix gocritic deprecatedComment lint issues by adding blank lines
  before Deprecated: comments
- Fix godoclint duplicate package doc comments
- Fix staticcheck warnings for deprecated webhook interfaces by adding
  assertions for new admission.Defaulter and admission.Validator interfaces
- Add revive linter exclusion for the errors package (intentionally
  shadows stdlib)
- Apply golangci-lint auto-fixes (slices.Contains, maps.Copy, etc.)
- Fix flaky test by sorting GVKs by full key (Group, Version, Kind)

Created with Cursor using Claude 4.5 Opus

Signed-off-by: Adam Kasztenny <adam.kasztenny@elastic.co>
@adamkasztenny adamkasztenny force-pushed the upgrade-controller-runtime branch from 6e789c1 to 552d14c Compare February 5, 2026 00:34
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Feb 5, 2026

📝 Walkthrough

Walkthrough

Adds SubResource Apply support (wrapperStatusClient.Apply and mock/test support), updates webhook tests to assert controller-runtime admission interfaces, adds a golangci-lint exclusion for pkg/errors/, makes tests deterministic (GVK sorting) and applies widespread test formatting and minor scaffolding tweaks.

Changes

Cohort / File(s) Summary
Linting Configuration
\.golangci\.yml
Add revive exclusion for pkg/errors/ to avoid conflicts with Go stdlib package names.
SubResource Apply Implementation
pkg/resource/unstructured/client.go, pkg/resource/unstructured/client_test.go
Add Apply on wrapperStatusClient that errors for unstructured Wrapper objects and otherwise delegates to underlying kube Apply; tests updated (formatting).
Mock SubResource Infrastructure
pkg/test/fake.go
Add MockSubResourceApplyFn type, MockApply field on MockSubResourceClient, and Apply method to enable mocking SubResource apply in tests.
Webhook Interface Assertions
pkg/webhook/mutator_test.go, pkg/webhook/validator_test.go
Replace single-interface assertions with grouped var blocks asserting both legacy webhook.CustomDefaulter/CustomValidator and admission.Defaulter/Validator[runtime.Object], adding nolint comments.
GVK Sorting in Reconciler Tests
pkg/reconciler/customresourcesgate/reconciler_test.go
Introduce shared sortGVK comparator and apply it to gate call slices to ensure deterministic ordering before diffs.
Test Formatting & Minor Test Scaffolding
pkg/fieldpath/paved.go, pkg/fieldpath/paved_test.go, pkg/gate/gate_test.go, pkg/meta/meta_test.go, pkg/ratelimiter/reconciler_test.go, pkg/reconciler/managed/..., pkg/reconciler/providerconfig/reconciler_test.go, pkg/reference/namespaced_reference_test.go, pkg/resource/*_test.go, pkg/resource/predicates_test.go, pkg/resource/providerconfig_test.go
Widespread whitespace/formatting tweaks (blank lines added); some tests add explicit return nil in mocked closures; TestGateConcurrency switches to wg.Go. No production API signature changes except the Apply addition.
Unstructured Client Tests Formatting
pkg/resource/unstructured/client_test.go
Inserted blank lines after return nil in multiple test callbacks; no semantic changes.

Sequence Diagram(s)

mermaid
sequenceDiagram
participant Controller
participant WrapperStatusClient as WrapperStatusClient
participant KubeClient as KubeClient
Controller->>WrapperStatusClient: Apply(ctx, ApplyConfiguration, opts...)
WrapperStatusClient-->>WrapperStatusClient: check if obj implements Wrapper
alt unstructured (implements Wrapper)
WrapperStatusClient->>Controller: return error ("cannot Apply unstructured object")
else structured
WrapperStatusClient->>KubeClient: Apply(ctx, ApplyConfiguration, opts...)
KubeClient-->>WrapperStatusClient: result / error
WrapperStatusClient-->>Controller: return result / error
end

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Suggested reviewers

  • adamwg
  • phisco

Would you like a follow-up PR to remove the nolint comments once callers are fully migrated to the new admission interfaces?

🚥 Pre-merge checks | ✅ 4 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Title check ⚠️ Warning The title exceeds the 72-character requirement at 75 characters and accurately describes the main changes: upgrading controller-runtime and addressing breaking changes and linter issues. Reduce the title to 72 characters or fewer while maintaining clarity; consider shortening to 'Upgrade controller-runtime and fix breaking changes' (49 characters).
✅ Passed checks (4 passed)
Check name Status Explanation
Description check ✅ Passed The description clearly explains the changes: upgrades to controller-runtime with resulting Go and linter upgrades, lists the fixes applied, and references the related issue #904.
Linked Issues check ✅ Passed The PR directly addresses issue #904 by upgrading controller-runtime to resolve breaking changes, updating Go and linter tooling, and fixing resulting linter failures throughout the codebase.
Out of Scope Changes check ✅ Passed All changes are scoped to addressing controller-runtime upgrade requirements: dependency updates, linter configuration adjustments, breaking API changes (Apply method additions), and whitespace/formatting fixes for linter compliance.
Breaking Changes ✅ Passed This pull request contains no breaking changes to the public Go APIs in crossplane-runtime. All modifications to non-test Go files are backwards-compatible additions of new methods and types, with no removals, renames, signature changes, or behavioral changes to existing exported APIs.

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


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.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

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 (3)
pkg/gate/gate_test.go (1)

251-262: ⚠️ Potential issue | 🔴 Critical

sync.WaitGroup doesn't have a Go method—this won't compile.

The loop on line 257 calls wg.Go(), but sync.WaitGroup only provides Add(), Done(), and Wait(). Did you mean to use errgroup.Group (which would require importing golang.org/x/sync/errgroup), or would you prefer to stick with sync.WaitGroup and adjust to the Add/Done pattern?

🛠️ Suggested fix (sync.WaitGroup)
	for range numGoroutines {
-		wg.Go(func() {
+		wg.Add(1)
+		go func() {
+			defer wg.Done()
			g.Register(func() {
				callCount <- struct{}{}
			}, "shared-condition")
-		})
+		}()
	}
pkg/reconciler/managed/reconciler_modern_test.go (1)

1946-1985: ⚠️ Potential issue | 🟡 Minor

Test name says Create-only, but setup uses ManagementActionAll.

ManagementPolicyCreateCreateSuccessful sets ManagementActionAll, which overlaps the All case and doesn’t exercise Create-only behavior. Did you intend this to validate Create-only? If so, switching to ManagementActionCreate (or renaming the case) would make the intent clear. Thanks!

Potential alignment with the case name
-						MockGet: test.NewMockGetFn(nil, func(obj client.Object) error {
-							mg := asModernManaged(obj, 42)
-							mg.SetManagementPolicies(xpv1.ManagementPolicies{xpv1.ManagementActionAll})
+						MockGet: test.NewMockGetFn(nil, func(obj client.Object) error {
+							mg := asModernManaged(obj, 42)
+							mg.SetManagementPolicies(xpv1.ManagementPolicies{xpv1.ManagementActionCreate})
 
 							return nil
 						}),
@@
-							want := newModernManaged(42)
-							want.SetManagementPolicies(xpv1.ManagementPolicies{xpv1.ManagementActionAll})
+							want := newModernManaged(42)
+							want.SetManagementPolicies(xpv1.ManagementPolicies{xpv1.ManagementActionCreate})

As per coding guidelines: **/*_test.go: "Check for proper test case naming and reason fields."

pkg/test/fake.go (1)

399-431: ⚠️ Potential issue | 🟠 Major

Wire Apply mocks through MockClient to complete the implementation and avoid nil panics.

The Apply method was added to MockSubResourceClient, but the wiring is incomplete. Status() and SubResource() don't wire the Apply field, and MockClient lacks MockStatusApply and MockSubResourceApply fields or defaults. If any code calls Status().Apply() or SubResource().Apply(), it will panic on a nil function.

The suggested wiring (fields, defaults, and method updates) follows the existing pattern for other mock methods—thank you for including that detail! This would keep Apply configurable and safe, just like the other sub-resource operations.

Suggested wiring (fields, defaults, and helpers)
 // A MockSubResourceApplyFn is used to mock client.SubResourceClient's apply implementation.
 type MockSubResourceApplyFn func(ctx context.Context, obj runtime.ApplyConfiguration, opts ...client.SubResourceApplyOption) error
+
+// NewMockSubResourceApplyFn returns a MockSubResourceApplyFn that returns the supplied error.
+func NewMockSubResourceApplyFn(err error, afn ...ApplyFn) MockSubResourceApplyFn {
+	return func(_ context.Context, obj runtime.ApplyConfiguration, _ ...client.SubResourceApplyOption) error {
+		for _, fn := range afn {
+			if fnErr := fn(obj); fnErr != nil {
+				return fnErr
+			}
+		}
+		return err
+	}
+}
@@
 type MockClient struct {
 	MockGet         MockGetFn
@@
 	MockStatusCreate MockSubResourceCreateFn
 	MockStatusUpdate MockSubResourceUpdateFn
 	MockStatusPatch  MockSubResourcePatchFn
+	MockStatusApply  MockSubResourceApplyFn
@@
 	MockSubResourceGet    MockSubResourceGetFn
 	MockSubResourceCreate MockSubResourceCreateFn
 	MockSubResourceUpdate MockSubResourceUpdateFn
 	MockSubResourcePatch  MockSubResourcePatchFn
+	MockSubResourceApply  MockSubResourceApplyFn
@@
 func NewMockClient() *MockClient {
 	return &MockClient{
@@
 		MockStatusUpdate: NewMockSubResourceUpdateFn(nil),
 		MockStatusPatch:  NewMockSubResourcePatchFn(nil),
+		MockStatusApply:  NewMockSubResourceApplyFn(nil),
@@
+		MockSubResourceApply: NewMockSubResourceApplyFn(nil),
@@
 func (c *MockClient) Status() client.SubResourceWriter {
 	return &MockSubResourceClient{
 		MockCreate: c.MockStatusCreate,
 		MockUpdate: c.MockStatusUpdate,
 		MockPatch:  c.MockStatusPatch,
+		MockApply:  c.MockStatusApply,
 	}
 }
@@
 func (c *MockClient) SubResource(_ string) client.SubResourceClient {
 	return &MockSubResourceClient{
 		MockGet:    c.MockSubResourceGet,
 		MockCreate: c.MockSubResourceCreate,
 		MockUpdate: c.MockSubResourceUpdate,
 		MockPatch:  c.MockSubResourcePatch,
+		MockApply:  c.MockSubResourceApply,
 	}
 }
🤖 Fix all issues with AI agents
In `@pkg/resource/unstructured/client.go`:
- Around line 209-217: Change the error returned by wrapperStatusClient.Apply
when obj is a Wrapper to be user‑actionable: update the message in the Apply
method (where it currently checks "if u, ok := obj.(Wrapper); ok") to explain
the operation the user attempted (applying a configuration to an unstructured
object), suggest a concrete next step (e.g., use a typed
runtime.ApplyConfiguration or use client.Patch with ApplyPatchType), and avoid
developer jargon; keep references to Wrapper and runtime.ApplyConfiguration so
users know the alternatives.
🧹 Nitpick comments (2)
pkg/resource/unstructured/client_test.go (1)

83-107: Consider adding reason fields to the table-driven cases.

These cases don’t include a reason string, so failures lose intent. Would you be open to adding reason and printing it in the error output (like other tests in this repo) to improve clarity? Thanks!

Example pattern (apply across other cases if you agree)
-	cases := map[string]struct {
-		c    client.Client
-		args args
-		want error
-	}{
+	cases := map[string]struct {
+		reason string
+		c      client.Client
+		args   args
+		want   error
+	}{
 		"Unwrapped": {
+			reason: "Unstructured objects should pass through unchanged",
 			c: &test.MockClient{MockGet: test.NewMockGetFn(nil, func(obj client.Object) error {
 				if obj.(metav1.Object).GetName() != nameUnwrapped {
 					return errWrapped
 				}
 
 				return nil
 			})},
 			args: args{obj: NewUnwrapped()},
 		},
@@
-			if diff := cmp.Diff(tc.want, got, test.EquateErrors()); diff != "" {
-				t.Errorf("\nc.Get(...): -want error, +got error:\n %s", diff)
+			if diff := cmp.Diff(tc.want, got, test.EquateErrors()); diff != "" {
+				t.Errorf("\nReason: %s\nc.Get(...): -want error, +got error:\n %s", tc.reason, diff)
 			}

As per coding guidelines: **/*_test.go: "Check for proper test case naming and reason fields."

pkg/webhook/mutator_test.go (1)

81-83: Consider using cmpopts.EquateErrors() for consistency with test guidelines.

The coding guidelines specify using cmp.Diff with cmpopts.EquateErrors() for error testing in test files. The current code uses test.EquateErrors(), which is a Crossplane-specific function with different semantics—it checks error type and string equality, whereas cmpopts.EquateErrors() uses errors.Is semantics for wrapped errors. If the wrapped error behavior is what you need, we'd recommend aligning with the standard pattern.

Suggested change
 import (
 	"context"
 	"testing"
 
 	"github.com/google/go-cmp/cmp"
+	"github.com/google/go-cmp/cmp/cmpopts"
 	"k8s.io/apimachinery/pkg/runtime"
 	"sigs.k8s.io/controller-runtime/pkg/webhook"
 	"sigs.k8s.io/controller-runtime/pkg/webhook/admission"
-
-	"github.com/crossplane/crossplane-runtime/v2/pkg/test"
 )
@@
-	if diff := cmp.Diff(tc.err, err, test.EquateErrors()); diff != "" {
+	if diff := cmp.Diff(tc.err, err, cmpopts.EquateErrors()); diff != "" {
 		t.Errorf("\n%s\nDefault(...): -want, +got\n%s\n", tc.reason, diff)
 	}

@adamkasztenny adamkasztenny changed the title Upgrade controller-runtime Upgrade controller-runtime, address breaking changes, and fix linter issues Feb 5, 2026
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
Signed-off-by: Adam Kasztenny <adam.kasztenny@elastic.co>
@adamkasztenny adamkasztenny force-pushed the upgrade-controller-runtime branch from b682fd0 to 1182b3b Compare February 5, 2026 01:29
@adamkasztenny
Copy link
Contributor Author

@coderabbitai review

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Feb 5, 2026

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

Created with Cursor using Claude 4.5 Opus

Signed-off-by: Adam Kasztenny <adam.kasztenny@elastic.co>
@adamkasztenny
Copy link
Contributor Author

@coderabbitai review

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Feb 5, 2026

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@adamwg
Copy link
Member

adamwg commented Feb 5, 2026

I think the lint/test failures are because the gomod2nix.toml needs an update. ./nix.sh run .#tidy should do that.

Regenerate gomod2nix.toml to reflect the upgraded controller-runtime
dependency and fix Nix build failures.

Created with Cursor using Claude 4.5 Opus

Signed-off-by: Adam Kasztenny <adam.kasztenny@elastic.co>
@adamkasztenny
Copy link
Contributor Author

@adamwg Fixed, can you run CI again?

Copy link
Member

@adamwg adamwg left a comment

Choose a reason for hiding this comment

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

lgtm, thanks for working through all the linter fixes!

@adamwg adamwg merged commit 5afc0fe into crossplane:main Feb 5, 2026
11 checks passed
@adamkasztenny adamkasztenny deleted the upgrade-controller-runtime branch February 5, 2026 21:21
@adamkasztenny
Copy link
Contributor Author

Thanks @adamwg! Should we wait for a release to resolve the issues on our end or can we just use the latest commit in our go.mod?

@jbw976
Copy link
Member

jbw976 commented Feb 5, 2026

It's not uncommon for folks to update their go.mod to pick up latest from main on crossplane-runtime, if that works for your needs. Otherwise, we should have the v2.2 RC published early next week and the full v2.2 release week of Feb 17

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.

Upgrade controller-runtime dependency

3 participants