Skip to content

Conversation

@bobh66
Copy link
Contributor

@bobh66 bobh66 commented Sep 12, 2025

Description of your changes

Added a MustCreate management policy that requires the external resource to NOT exist before the MR can create it.

Fixes #872

Tested using provider-kubernetes running a private branch of crossplane-runtime. Created a Secret and also an Object containing a Secret with the same name but different data, and managementPolicies: ['MustCreate', 'Observe', 'Update', 'LateInitialize', 'Delete'] and verified that when the Object was created the Secret remained unchanged and the Object reported an error that the Secret it was trying to create was already in existence.

Docs PR is crossplane/docs#993

I have:

Need help with this checklist? See the cheat sheet.

Copy link
Member

@jbw976 jbw976 left a comment

Choose a reason for hiding this comment

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

This looks reasonable as well @bobh66, thank you!

// All actions explicitly set, the same as default.
sets.New[xpv1.ManagementAction](xpv1.ManagementActionObserve, xpv1.ManagementActionCreate, xpv1.ManagementActionUpdate, xpv1.ManagementActionLateInitialize, xpv1.ManagementActionDelete),
// All actions explicitly set with MustCreate instead of Create.
sets.New[xpv1.ManagementAction](xpv1.ManagementActionObserve, xpv1.ManagementActionMustCreate, xpv1.ManagementActionUpdate, xpv1.ManagementActionLateInitialize, xpv1.ManagementActionDelete),
Copy link
Member

Choose a reason for hiding this comment

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

the combinatorics are starting to get cumbersome here - are we already tracking loosening up our requirement to define every possible allowed combination of policies? i couldn't find a tracking issue for that just looking now...

Copy link
Contributor

Choose a reason for hiding this comment

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

This was to ensure folks dont shoot themselves in the foot by adding unsupported/untested combinations, or combinations that dont make sense. Most of the sensible ones are here already.

Adding more policies will make this worse and if we do we should rethink this.

@lsviben
Copy link
Contributor

lsviben commented Oct 28, 2025

Similarly to the comment on adding the Orphan management policy, I am vary of adding new policies to the management policies except for the primitives we have.

Adding MustCreate would be adding a flavour of Create, so I worry that if we go in this direction we would add more such flavours in the future and it could become unmanagable. Or imagine if we would later want to combine the Orphan policy ["Observe", "Create", "Update", "LateInitialize"] from the other PR, with MustCreate. Would we need to add OrphanMustCreate that translates to ["Observe", "MustCreate", "Update", "LateInitialize"]. Furthermore * would lose its meaning as it would not anymore map to ALL.

In general I think the basic policies should follow the methods of the external managed client.

However, I do understand where the issue is coming from and what we are trying to achive here, I just don't think adding a new management policy is the right way. I wonder should we introduce some management options field? We could add mustCreate there, and maybe move lateInitialize there at one point, or anything else that pops out?

@bobh66
Copy link
Contributor Author

bobh66 commented Nov 4, 2025

@lsviben I don't disagree that managementPolicies is already too complicated - related comment here

@github-actions
Copy link

github-actions bot commented Feb 3, 2026

Crossplane does not currently have enough maintainers to address every issue and pull request. This pull request has been automatically marked as stale because it has had no activity in the last 90 days. It will be closed in 14 days if no further activity occurs. Adding a comment starting with /fresh will mark this PR as not stale.

@github-actions github-actions bot added the stale label Feb 3, 2026
@bobh66
Copy link
Contributor Author

bobh66 commented Feb 3, 2026

/fresh

@github-actions github-actions bot removed the stale label Feb 3, 2026
Signed-off-by: Bob Haddleton <bob.haddleton@nokia.com>
@bobh66 bobh66 force-pushed the add_mustcreate_policy branch from 597a4df to c972d61 Compare February 3, 2026 03:54
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Feb 3, 2026

📝 Walkthrough

Walkthrough

Adds a new ManagementAction value MustCreate, helper ExternalCreateNotStarted, and reconciler enforcement so that when MustCreate is set and an external resource already exists (and creation by this managed resource has not started), the reconciler records a reconcile error, emits a warning event, and avoids modifying the existing resource.

Changes

Cohort / File(s) Summary
Policy Constants
apis/common/policies.go, apis/common/v1/policies.go
Introduce ManagementActionMustCreate constant and update kubebuilder enum validation to include MustCreate.
Meta Helpers
pkg/meta/meta.go, pkg/meta/meta_test.go
Add ExternalCreateNotStarted(o metav1.Object) bool and tests validating pending/succeeded/failed/not-started states (note: test appears duplicated).
Reconciler Policy Logic
pkg/reconciler/managed/policies.go
Include MustCreate in defaultSupportedManagementPolicies, treat it in ShouldCreate(), and add MustCreate() on ManagementPoliciesResolver.
Reconciler Enforcement
pkg/reconciler/managed/reconciler.go
Add MustCreate() to ManagementPoliciesChecker, introduce errMustCreate, and add observe-path guard: if external exists, MustCreate() is true, and ExternalCreateNotStarted() is true, emit warning/event, set ReconcileError/Creating, update status, and return without modifying the existing resource.
Reconciler Tests
pkg/reconciler/managed/reconciler_legacy_test.go, pkg/reconciler/managed/reconciler_modern_test.go
Add test cases exercising the MustCreate policy path that assert external-create annotations and conditioned status updates (some test entries appear duplicated).

Sequence Diagram(s)

sequenceDiagram
    participant Reconciler
    participant MetaHelper as Meta Helper
    participant ExternalAPI as External API
    participant StatusUpdater as Status Updater

    Reconciler->>MetaHelper: ExternalCreateNotStarted(o)
    MetaHelper->>MetaHelper: read external-create annotations
    MetaHelper-->>Reconciler: true/false

    alt normal create allowed or create already started
        Reconciler->>ExternalAPI: Observe / Create / Update
        ExternalAPI-->>Reconciler: result
        Reconciler->>StatusUpdater: update status/conditions
    else MustCreate required and external exists and not started
        rect rgba(255, 100, 100, 0.5)
            Reconciler->>Reconciler: detect MustCreate + resource exists + not started
            Reconciler->>StatusUpdater: emit warning event and set ReconcileError/Creating condition
            StatusUpdater-->>Reconciler: status updated
            Reconciler-->>Reconciler: return without requeue
        end
    end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Would you like me to list the exact new and modified function signatures and constants for a focused review? Thanks for the clear change set.


Important

Pre-merge checks failed

Please resolve all errors before merging. Addressing warnings is optional.

❌ Failed checks (1 error)
Check name Status Explanation Resolution
Breaking Changes ❌ Error Pull request adds a new method to exported ManagementPoliciesChecker interface, which is a breaking change for external implementations. Add the 'breaking-change' label to acknowledge this source-level breaking change and provide clear migration documentation.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title 'Add MustCreate management policy' is descriptive and directly reflects the main change in the changeset, staying well under the 72-character limit.
Description check ✅ Passed The description clearly outlines the new MustCreate management policy, explains the testing approach, and references the linked issue #872, making it directly relevant to the changeset.
Linked Issues check ✅ Passed The code changes fully implement the MustCreate management policy feature as specified in issue #872 [#872], including the policy definition, validation logic, and error handling when resources already exist.
Out of Scope Changes check ✅ Passed All code changes are directly scoped to implementing the MustCreate management policy feature, including constants, validators, helper functions, and tests, with no extraneous modifications.

Important

Action Needed: IP Allowlist Update

If your organization protects your Git platform with IP whitelisting, please add the new CodeRabbit IP address to your allowlist:

  • 136.113.208.247/32 (new)
  • 34.170.211.100/32
  • 35.222.179.152/32

Failure to add the new IP will result in interrupted reviews.


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: 2

🤖 Fix all issues with AI agents
In `@pkg/reconciler/managed/policies.go`:
- Around line 218-229: The docstring for ManagementPoliciesResolver.MustCreate
is incorrect: it currently says the method returns true when the management
policy feature is disabled, but the implementation returns false; update the
comment to match the code (or change behavior if intended). Specifically, edit
the MustCreate comment to state that it returns false when the feature is
disabled and that it returns true only when
managementPolicies.Has(xpv1.ManagementActionMustCreate) is true, referencing the
MustCreate method and the managementPolicies/ enabled fields so reviewers can
locate the logic.

In `@pkg/reconciler/managed/reconciler.go`:
- Around line 74-75: The user-facing error constant errMustCreate should be made
actionable: update the string value of errMustCreate (and any other places where
it's used around the reconciler logic at the other occurrence) to include the
resource identifier (use the managed resource name or external name from the
managed object) and a clear next-step suggestion such as "delete the existing
external resource or remove the MustCreate policy". Locate the errMustCreate
constant in reconciler.go and the code paths that raise/return it (around the
other occurrence noted) and format the message to interpolate the resource name
and the suggested remediation so events/conditions show "resource <name> already
exists; either delete the external resource or remove MustCreate".
🧹 Nitpick comments (4)
pkg/meta/meta.go (1)

374-385: Clarify the doc comment + simplify return.
Would you be open to aligning the comment with the actual behavior (no external-create annotations) and returning the boolean directly?

♻️ Suggested tweak
-// ExternalCreateNotStarted returns true if the external resource was detected
-// during the initial Observe phase and was not created by this managed resource.
+// ExternalCreateNotStarted returns true when no external-create annotations
+// have been recorded on the managed resource yet.
 func ExternalCreateNotStarted(o metav1.Object) bool {
 	pending := GetExternalCreatePending(o)
 	succeeded := GetExternalCreateSucceeded(o)
 	failed := GetExternalCreateFailed(o)

-	if pending.IsZero() && succeeded.IsZero() && failed.IsZero() {
-		return true
-	}
-
-	return false
+	return pending.IsZero() && succeeded.IsZero() && failed.IsZero()
 }
pkg/meta/meta_test.go (1)

1228-1286: Tiny clarity nit: error label mismatch.
Is “ExternalCreateMissing” intentional? If not, aligning to the helper name makes failures easier to scan.

✏️ Suggested tweak
-			if diff := cmp.Diff(tc.want, got); diff != "" {
-				t.Errorf("ExternalCreateMissing(...): -want, +got:\n%s", diff)
-			}
+			if diff := cmp.Diff(tc.want, got); diff != "" {
+				t.Errorf("ExternalCreateNotStarted(...): -want, +got:\n%s", diff)
+			}
pkg/reconciler/managed/reconciler_modern_test.go (1)

1783-1869: Great coverage for MustCreate paths; small cleanup.
Could we drop the duplicate WithObservedGeneration(42) and (if you update the error string) align the expected error message here too?

♻️ Suggested cleanup
-							want.SetConditions(xpv1.Creating().WithObservedGeneration(42), xpv1.ReconcileError(errors.New(errMustCreate)).WithObservedGeneration(42).WithObservedGeneration(42))
+							want.SetConditions(
+								xpv1.Creating().WithObservedGeneration(42),
+								xpv1.ReconcileError(errors.New(errMustCreate)).WithObservedGeneration(42),
+							)
pkg/reconciler/managed/reconciler_legacy_test.go (1)

1816-1863: Great coverage for the MustCreate error scenario!

This test properly verifies that when an external resource already exists and MustCreate policy is set, the reconciler raises an error condition and doesn't requeue (which aligns with the PR objective of stopping processing in this scenario).

One small thing I noticed at line 1829 - there's a duplicate .WithObservedGeneration(42) call on the ReconcileError condition. While it likely doesn't affect functionality, could you clarify if this is intentional? If not, it would be cleaner to remove the redundant call for consistency with the other condition assertions.

🧹 Suggested cleanup for duplicate method call
-							want.SetConditions(xpv1.Creating().WithObservedGeneration(42), xpv1.ReconcileError(errors.New(errMustCreate)).WithObservedGeneration(42).WithObservedGeneration(42))
+							want.SetConditions(xpv1.Creating().WithObservedGeneration(42), xpv1.ReconcileError(errors.New(errMustCreate)).WithObservedGeneration(42))

Comment on lines +74 to 75
errMustCreate = "managed resource has mustcreate policy but external resource already exists"
)
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Make the MustCreate error actionable for users.
Right now the condition/event message is terse and uses jargon (“mustcreate”). Could we include the external name (or managed name) and a next step (delete existing resource or remove MustCreate)? This is user-facing in conditions/events.

🛠️ Suggested tweak
-	errMustCreate = "managed resource has mustcreate policy but external resource already exists"
+	errFmtMustCreate = "external resource %q already exists; delete it or remove the MustCreate policy to proceed"
-	if observation.ResourceExists && policy.MustCreate() && meta.ExternalCreateNotStarted(managed) {
-		log.Debug(errMustCreate)
-		record.Event(managed, event.Warning(reasonCannotCreate, errors.New(errMustCreate)))
-		status.MarkConditions(xpv1.Creating(), xpv1.ReconcileError(errors.New(errMustCreate)))
+	if observation.ResourceExists && policy.MustCreate() && meta.ExternalCreateNotStarted(managed) {
+		extName := meta.GetExternalName(managed)
+		if extName == "" {
+			extName = managed.GetName()
+		}
+		err := errors.Errorf(errFmtMustCreate, extName)
+		log.Debug(err.Error())
+		record.Event(managed, event.Warning(reasonCannotCreate, err))
+		status.MarkConditions(xpv1.Creating(), xpv1.ReconcileError(err))

Also applies to: 1151-1158

🤖 Prompt for AI Agents
In `@pkg/reconciler/managed/reconciler.go` around lines 74 - 75, The user-facing
error constant errMustCreate should be made actionable: update the string value
of errMustCreate (and any other places where it's used around the reconciler
logic at the other occurrence) to include the resource identifier (use the
managed resource name or external name from the managed object) and a clear
next-step suggestion such as "delete the existing external resource or remove
the MustCreate policy". Locate the errMustCreate constant in reconciler.go and
the code paths that raise/return it (around the other occurrence noted) and
format the message to interpolate the resource name and the suggested
remediation so events/conditions show "resource <name> already exists; either
delete the external resource or remove MustCreate".

@bobh66 bobh66 force-pushed the add_mustcreate_policy branch from c972d61 to a52580f Compare February 3, 2026 14:37
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

🤖 Fix all issues with AI agents
In `@pkg/meta/meta_test.go`:
- Around line 1281-1288: Update the test error string to match the function
under test: change the t.Errorf message that currently reads
"ExternalCreateMissing(...)" to "ExternalCreateNotStarted(...)" in the test loop
that invokes ExternalCreateNotStarted(tc.args.o) so the error output correctly
references ExternalCreateNotStarted when cmp.Diff detects a mismatch.

Signed-off-by: Bob Haddleton <bob.haddleton@nokia.com>
@bobh66 bobh66 force-pushed the add_mustcreate_policy branch from a52580f to f23c684 Compare February 3, 2026 14:44
@bobh66 bobh66 merged commit af8c3fb into crossplane:main Feb 3, 2026
16 of 17 checks passed
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.

Support MustCreate management policy to trigger failure when a resource already exists

3 participants