Repository navigation
feat: enable ManagementPolicies by default - #41
Merged
Merged
Conversation
ManagementPolicies has been stable in Crossplane for a long time, and provider-aws and provider-kubernetes already run with it on. This provider still gated it behind a flag defaulting to false, and still described it as alpha. The practical consequence is that any resource with a non-default managementPolicies silently fails to reconcile: `spec.managementPolicies` is set to a non-default value but the feature is not enabled: [Create Observe Update LateInitialize] That is not an edge case under Crossplane v2. Namespaced managed resources have no deletionPolicy field at all, so managementPolicies is the only way to express "do not delete the external resource on delete". Orphaning a Repository or a Team - which platform-ref-upbound does by default - is impossible without it. It was also awkward to turn on. Upbound Spaces' admission webhook rejects arbitrary args on a package runtime, so the only route was the ENABLE_MANAGEMENT_POLICIES env var via a DeploymentRuntimeConfig, which is not obvious from the flag name alone. Flips the default to true, keeping --enable-management-policies=false as an opt-out, and renames the internal flag constant to drop "Alpha" now that it is on by default. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
haarchri
approved these changes
Sep 24, 2026
ytsarev
added a commit
to upbound/platform-ref-upbound
that referenced
this pull request
Sep 24, 2026
upbound/provider-upbound#41 flips --enable-management-policies to true by default and is merged, but no release contains it yet - v1.1.1 is still the latest and is what upbound.yaml pins, so the DeploymentRuntimeConfig is still required today. Records the removal condition in both the README and the e2e suite, so the workaround does not outlive the fix: bump provider-upbound once a release ships with it, then delete step 5a and the enable-management-policies runtime config. Also notes explicitly that step 5b is NOT in the same category. Server-side apply is the correct default for provider-kubernetes; the Spaces API gateway is the exception, so disabling it stays a permanent part of this setup rather than something awaiting an upstream fix. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
ytsarev
added a commit
to upbound/platform-ref-upbound
that referenced
this pull request
Sep 25, 2026
…ine (#37) * feat(deps)!: move project to v2alpha1 and bump providers to the v2 line Adopts the meta.dev.upbound.io/v2alpha1 project format and raises the Crossplane constraint to >=v2.0.0-0. Also fixes a dependency deadlock. The old constraints pinned provider-kubernetes to v0 and provider-upbound to v0.9.1. On a control plane where newer versions are already installed, Crossplane's Lock resolver (with --enable-dependency-version-upgrades) prunes them as outdated, cannot find an upgrade that satisfies the constraint, and aborts. Because the resolver only processes implied[0] per reconcile, it never reaches the embedded function packages, which are then reported as "missing dependencies" - a symptom that hides the real cause. provider-family-aws -> v2.8.1 (now pinned explicitly) provider-aws-iam -> v2.8.1 provider-aws-secretsmanager -> v2.8.1 provider-kubernetes v0 -> v1.3.3 provider-upbound v0.9.1 -> v1.1.1 function-auto-ready >=v0.0.0 -> v0.7.0 These versions ship the modern .m. API groups alongside the legacy ones, so this commit stands alone: the existing v1 compositions and tests are unaffected and stay green. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * feat!: migrate to Crossplane v2 namespaced APIs and drop the X prefix XRDs move to apiextensions.crossplane.io/v2 with scope: Namespaced, and the X prefix is dropped now that v2 has no claims: XEnvironment -> Environment XSharedAWSSecret -> SharedAWSSecret XUpboundRepoSet -> UpboundRepoSet Compositions stay on apiextensions.crossplane.io/v1 - there is no v2 Composition. Function directories are renamed to match, which changes the published OCI paths to platform-ref-upbound_{environments,sharedawssecret, upboundreposet}. Composed resources move to the modern .m. provider groups. Note the kubernetes Object version moves *backwards*, v1alpha2 -> v1alpha1, because the modern group only publishes v1alpha1: kubernetes.crossplane.io/v1alpha2 -> kubernetes.m.crossplane.io/v1alpha1 iam.aws.upbound.io/v1beta1 -> iam.aws.m.upbound.io/v1beta1 secretsmanager.aws.upbound.io/.. -> secretsmanager.aws.m.upbound.io/.. aws.upbound.io/v1beta1 -> aws.m.upbound.io/v1beta1 iam.upbound.io/v1alpha1 -> iam.m.upbound.io/v1alpha1 repository.upbound.io/v1alpha1 -> repository.m.upbound.io/v1alpha1 upbound.io/v1alpha1 -> m.upbound.io/v1alpha1 apiextensions.crossplane.io Usage -> protection.crossplane.io/v1beta1 Namespaced managed resources have no deletionPolicy field, so the XR-level deletionPolicy parameter is retained and translated per resource via a new utils._managementPolicies helper: Delete -> ["*"], Orphan -> ["Create", "Observe","Update","LateInitialize"]. providerConfigRef now requires kind; every reference points at a namespaced ProviderConfig this project itself composes. writeConnectionSecretToRef loses its namespace field, so the IAM AccessKey connection secret now lands in the XR's namespace - the references.patchesFrom block that copies it follows it rather than reading a hardcoded "default". Two v2 behaviours worth recording, both of which fail silently: - A namespaced XR carries spec.crossplane, whose resourceRefs Crossplane fills with plain dicts. Spreading the whole observed spec into the typed XR schema then fails to type-check, so the functions take only spec.parameters. - An observed composed resource must carry metadata.namespace matching the XR, or crossplane render does not associate it and ocds lookups return nothing, silently disabling observed-state branches. Also fixes a pre-existing failure in the no-cloudprovider suite, which injected an XR with an empty spec into extraResources/observedResources; up test run now sources the observed composite from there, wiping the spec the xrPath example provides. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * docs: document the v2 namespaced API and the ManagementPolicies prerequisite Records the breaking changes for operators: the kinds lose their X prefix, XRs are namespaced (kubectl get environments -n <ns>), and the bootstrap ProviderConfig created by hand is now the namespaced kubernetes.m.crossplane.io/v1alpha1 kind in the XR's namespace. Adds a required step to enable ManagementPolicies on provider-upbound. Orphaning is expressed with managementPolicies in v2, but provider-upbound gates that behind an alpha feature that defaults to off, so without this the composed Repository and Team never reconcile: `spec.managementPolicies` is set to a non-default value but the feature is not enabled It must be set as an environment variable rather than a container arg: Upbound Spaces' admission webhook rejects arbitrary args on a package runtime but permits env vars, and the provider reads ENABLE_MANAGEMENT_POLICIES as an alias for its --enable-management-policies flag. provider-aws and provider-kubernetes already have it on. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * fix: emit base64 `data` instead of `stringData` in composed Secrets provider-kubernetes v1.3.3 applies server-side and records field ownership for exactly what it wrote. Kubernetes converts `stringData` to `data` on write, so the owned field is never present on the stored object and every subsequent observe fails: failed calling ExtractInto for unstructured: unable to convert managed fields for provider-kubernetes/<name> to unstructured, expected map, got <nil> The Objects then never reconcile, the ProviderConfigs built from those kubeconfig Secrets never become usable, and the Objects cannot even be deleted, because finalization also needs an observe. Found on a live control plane; composition tests passed throughout, since a render cannot show what the API server does to stringData on write. Verified by before/after event counts on the same cluster: 27 CannotObserveExternalResource events before, zero across 11 minutes after, with the stored Secrets now carrying data.kubeconfig and no stringData. Only surfaced now because this is the first release on provider-kubernetes v1; the v0 line did not apply server-side. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * fix: harden optional-field guards against None Two pipeline-aborting crashes seen on a live control plane, both from the same trap: an absent optional field yields None, not Undefined, so a guard written against Undefined passes and the following attribute access fails. main.k:76 invalid value 'NoneType' to load attribute 'org' sharedawssecret/main.k invalid value 'UndefinedType' to load attribute 'create' _initReady now uses `?.` on every hop rather than testing the parent once. The first reconcile of any Environment hit this, before status.upbound was populated; it self-recovered, so it only showed up as a one-shot error event. secretsManagerSecret is now treated as opt-out: only an explicit `false` disables creation. The first attempt at this fix tested `== Undefined`, which was worse than the crash - it silently skipped creating the secret altogether. The new test caught that, which is the whole reason it exists. Adds test-sharedawssecret-no-secretsmanager-block. All four existing sharedawssecret cases set secretsManagerSecret, so nothing covered the omission - which is exactly the shape Environment produces when sharedSecret is given without one. Confirmed the test bites: reverting the fix reproduces "invalid value 'UndefinedType' to load attribute 'create'" while the other four stay green. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * docs: require disabling server-side apply on provider-kubernetes provider-kubernetes v1 defaults --enable-server-side-apply to true. The Upbound Spaces API rejects apply patches: Unsupported patch format. Only merge and json patch are supported. so every object this configuration creates through a Spaces-backed ProviderConfig fails - the environment group Namespace, the ControlPlane, the SharedSecretStore and the SharedExternalSecret. Objects targeting a normal control-plane API are unaffected, which is what makes the split so easy to misread as a composition bug. Setting ENABLE_SERVER_SIDE_APPLY=false selects the provider's PatchingResourceSyncer, which uses a merge patch that Spaces accepts. As with ManagementPolicies it has to go in as an environment variable, since the Spaces admission webhook rejects arbitrary args on a package runtime. Verified on a live control plane: with this set, the group Namespace and a real ControlPlane (Ready=True, Healthy=True, ControlPlaneProvisioned=True) were created in the Space, along with the SharedSecretStore and SharedExternalSecret. 10 of 11 composed Objects reconcile; the remaining one needs an argocd namespace on the target control plane and is environmental. Step 6 now covers both provider runtime prerequisites. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * docs: clarify that it is the Spaces API gateway, not control planes, that rejects SSA Spaces control planes are ordinary Kubernetes API servers and accept server-side apply; verified with `kubectl apply --server-side --dry-run=server` against a control plane endpoint (serverside-applied) and against the gateway (BadRequest). The gateway at https://<spaceHost>, which serves groups and spaces.upbound.io resources, is what rejects apply patches - "invalid patch type" for core kinds, "Unsupported patch format. Only merge and json patch are supported." for spaces.upbound.io kinds. That distinction is what makes the failure split by ProviderConfig: the <env>-space and <group>-group configs target the gateway, the control-plane-scoped one does not. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * fix: guard _initReady by truthiness, not `!= Undefined` My previous attempt at this was wrong. Switching to `?.` on every hop stopped the crash at the guard itself but let an empty status through, so the pipeline died four lines later instead: main.k:137 envGroupName = "{}-{}".format(_oxrStatusUpbound.bootstrapGroup, ...) invalid value 'NoneType' to load attribute 'bootstrapGroup' In KCL both `None != Undefined` and `None?.field != Undefined` evaluate to true, so no Undefined-based test can distinguish "status absent" from "status present". These fields are all non-empty strings once set, so a plain truthy test is the only form correct for None, Undefined and "" alike. This is the same trap as the secretsManagerSecret guard, which is why that one is written as an explicit `== False` comparison. Adds test-environment-uninitialized, which reproduces the live shape by setting `status = {upbound = None}` rather than omitting status. That distinction is the whole point: with status merely absent the test passes against the broken guard too, because a render supplies Undefined where a real API server gives None. With None it fails on the original code with the exact live error, and passes on this one. The crash predates the v2 migration; it was only ever a one-shot error on a fresh XR that self-recovered on the next reconcile, so it never surfaced. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * docs: restructure the README as a followable guide The setup steps had grown to nine, several of them interleaved with explanation, and the reference material repeated itself across an overview list, a "what does X do" section and the per-API sections. Reorganised into How it works / Prerequisites / Getting started / API reference / Development, with parameter tables per API instead of prose bullet lists. Net 552 -> 457 lines despite adding material. The important addition is an architecture section explaining the two-control- plane model, which was not documented anywhere. Everything - the XRs and all composed resources - lives on the bootstrap control plane; the environment control plane it creates starts empty by design, because nothing here deploys into it. Argo CD does, via the cluster-registration secret written back to the bootstrap plane. Without that, an empty `up alpha query managed` against a new environment reads as a failure when it is the expected result. Also now documented, all learned from a live run: - Environment derives spaceHost/org/bootstrapGroup/bootstrapCtp by parsing the bootstrap kubeconfig, which is why status.upbound is briefly empty - web identity as the preferred AWS path, with the exact OIDC subject - the OIDC provider being an account-wide singleton, and why Orphan matters - SharedAWSSecret issuing a long-lived IAM access key, and why - createArgoSecret needing Argo CD on the bootstrap plane, with a pointer to addon-argocd-core - which Spaces endpoint rejects server-side apply, and which does not Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * fix: never delete an adopted OIDC provider, and add the e2e suite An OIDC provider supplied via aws.providerRole.oidcProviderArn is adopted, not created - and AWS allows exactly one provider per URL per account, so proidc.upbound.io is shared by every Upbound integration in that account. It was inheriting the XR-level deletionPolicy, meaning a single `deletionPolicy: Delete` teardown would have deleted it and broken all of them. Adoption now implies orphaning; a provider the composition actually creates still follows deletionPolicy. No new XRD field: "do not delete what you did not create" is the correct behaviour rather than something a user should have to opt into. Adds test-environment-adopted-oidc-is-orphaned, which pins both halves - the adopted provider orphaned while the Role beside it still honours Delete. Verified it bites: reverting the fix fails it with `spec.managementPolicies: Invalid value: ["*"]`. Adds tests/e2etest-environment, which provisions a real group, control plane, IAM role and Secrets Manager secret, then asserts Ready. Notes: - Secrets reach the test through file.read_env on UP_-prefixed variables; manifest generation runs in a container and nothing else crosses. Each read asserts rather than silently emitting an empty credential. - AWS uses web identity, so no AWS credential exists anywhere. - The bootstrap ProviderConfig uses InjectedIdentity, because the test's own control plane name is not knowable when the manifest is generated. The kubeconfig Secret beside it only has to parse - the composition reads it solely to derive spaceHost/org/bootstrapGroup/bootstrapCtp. - Both providers are declared in initResources with their DeploymentRuntimeConfigs already bound, since neither default works against Spaces. - deletionPolicy: Delete so CI cleans up after itself, which is only safe because of the adopted-OIDC fix above. Requires a new UP_E2E_SPACE_HOST repo secret; the other UP_E2E_* already exist. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * docs: mark the ManagementPolicies workaround as temporary upbound/provider-upbound#41 flips --enable-management-policies to true by default and is merged, but no release contains it yet - v1.1.1 is still the latest and is what upbound.yaml pins, so the DeploymentRuntimeConfig is still required today. Records the removal condition in both the README and the e2e suite, so the workaround does not outlive the fix: bump provider-upbound once a release ships with it, then delete step 5a and the enable-management-policies runtime config. Also notes explicitly that step 5b is NOT in the same category. Server-side apply is the correct default for provider-kubernetes; the Spaces API gateway is the exception, so disabling it stays a permanent part of this setup rather than something awaiting an upstream fix. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * refactor: model Crossplane's own CRDs instead of hand-writing dicts Adds Crossplane's CRDs as a git apiDependency, pinned to release-2.4 to match the Crossplane the target control planes run, so pkg.crossplane.io and protection.crossplane.io objects get generated models. Two places were writing untyped dicts for want of them: - pKubernetesHelper.k built its protection.crossplane.io/v1beta1 Usage by hand. This predates the e2e work; the v2 migration only changed the apiVersion string inside it. - the e2e suite's initResources built DeploymentRuntimeConfig and Provider by hand, which I had written off as not worth a dependency. It is worth it - a typo in any of those literals would have surfaced as a confusing server-side rejection during a 40-minute e2e run rather than at build. What stays a literal is deliberate: apiVersion/kind pairs inside `runtimeConfigRef` and inside Usage's `of`/`by` are references to a type, not objects being constructed, and every other remaining string sits inside an Object's forProvider.manifest - a resource on a *remote* cluster, which this project has no models for by definition. Costs a network fetch of the crossplane repo at build time, alongside the existing k8s api-dependency. 13/13 composition tests. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * fix(ci): scope composition tests to tests/test-* `up test run tests/*` generates manifests for every directory it matches, including e2e ones it will not execute. tests/e2etest-environment asserts that its UP_E2E_* variables are set, so it failed generation in the composition-tests job, taking the whole run down. The assert is deliberate and stays - the alternative is a run that provisions a real control plane and real AWS resources before discovering the credential was empty. It is the glob that was wrong. composition-tests now runs tests/test-*; the e2e workflow keeps tests/* since it supplies the variables. README says the same and explains why, because `tests/*` is the obvious thing to type. Verified with the e2e variables explicitly unset: 13/13. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * style: fix yamllint sequence indentation in the Environment XRD Five sequences were indented +2 under their key while the rest of the file puts sequence items at the parent's indent. With yamllint's default `spaces: consistent` the minority style is an error, so the file has been failing the linter - on main as well, at the same five places one line earlier. They surfaced on this PR rather than earlier because the yamllint workflow runs reviewdog with `filter_mode: nofilter`, which reports the whole file rather than just changed lines, and renaming apis/xenvironments -> apis/environments makes git treat the file as touched. The job itself still reported success - reviewdog's github-pr-review reporter posts comments but does not fail the run, despite fail_on_error: true - so this was only visible as five review comments. Whitespace only: `git diff -w` is empty and the parsed YAML is byte-identical after a round trip. `yamllint apis/` is now clean, 13/13 tests still pass. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * fix(e2e): read the variable names the workflow actually exports The e2e suite asked for UP_E2E_API_TOKEN / UP_E2E_ORG / UP_E2E_GROUP / UP_E2E_SPACE_HOST. The workflow resolves the UP_E2E_* secrets into UP_API_TOKEN / UP_ORG / UP_GROUP and exports those, so every one of the suite's asserts would have fired and the run would have died at manifest generation the moment someone applied the run-e2e-tests label. The suite now reads the names the workflow exports. UP_SPACE_HOST is gone - the host is derived from UP_SPACE instead, which the workflow also uses for its `up ctx` step, so the two cannot drift. That step had the space hardcoded; it now reads UP_SPACE as well, defaulting to the value it previously hardcoded. No new repository secret is needed after all. Verified three ways: composition tests pass with nothing exported (13/13), the e2e generates with the workflow's variables set, and it still refuses loudly when they are missing. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * fix(e2e): default UP_SPACE instead of asserting on it The first labelled run failed at manifest generation with "UP_SPACE must be set". e2e.yaml triggers on pull_request_target, which takes the workflow definition from the base branch and the code from the PR head - so the UP_SPACE export added to the workflow in this PR is not set when this PR is tested, while the test demanding it is. Any variable introduced alongside a test in the same PR hits this. UP_SPACE now falls back to the space the workflow switches to. That is a different kind of value from the credentials: a missing token has no safe default and must fail loudly, whereas the space name only has to agree with the `up ctx` step, and it does. The credential asserts are unchanged - UP_API_TOKEN, UP_ORG and UP_GROUP still fail generation when unset. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * fix: do not report Ready before the Environment has initialised Until the bootstrap kubeconfig is observed and status.upbound populated, the composition emits exactly one composed resource: the observer watching that kubeconfig Secret. It goes ready as soon as the Secret exists, so function-auto-ready saw "every composed resource is ready" and reported the whole Environment as Ready - before a single group, control plane, ProviderConfig or IAM resource had been created. Anything polling Ready to decide the environment is up got a false positive for that window. It is easy to miss by hand, because by the time you look the XR has genuinely converged; it only bites something checking within seconds of creation. The observer is now marked krm.kcl.dev/ready False until _initReady, using the same annotation the ProviderConfigs here already use to force readiness. Renders confirm both directions: uninitialised, the XR is not Ready and names the observer as the unready resource; initialised, the observer drops out of the unready list and readiness follows the real resources. Found because the e2e suite passed in two seconds flat. The suite asserts Ready, so it was asserting this false positive and tearing down before any provisioning began - a green run that tested nothing. With this fix that assertion becomes meaningful. No composition test guards it: the signal is the composite's Ready condition, and its conditions carry lastTransitionTime, which assertResources cannot match (list assertions are exact). The e2e is the guard. * fix(e2e): set uptest.upbound.io/timeout on the Environment XR spec.timeoutSeconds does not reach uptest's per-resource assertion, which defaults to 30 seconds; only this annotation overrides it. I had wrongly concluded timeoutSeconds was propagating because an assert ran for 292s - that was uptest's own retry wrapper, not the 3600 configured below. The timeout has to outlast provider *installation*, not just provisioning. `up test run` begins asserting once the configuration package reports ready, which is not the same as its dependency providers being ready. Provider images are large, so on a fresh control plane there is a multi-minute window where the managed resource CRDs do not exist and the composition cannot succeed. That window is what made the two runs disagree. Both were fresh control planes; the first got providers ready within seconds and initialised, the second was still installing and the XR never advanced past the bootstrap observer. Same code, different install timing - a flake, not a defect in the composition. * fix: restore the observer's composition resource name My readiness change added an annotations block onto metadata built by utils._metadata(), assuming `|` merged into the annotations map. It replaces it. That silently dropped krm.kcl.dev/composition-resource-name, so the observer was filed under its object name instead of "observedCtpKubeconfig". main.k looks it up by that key: ocds.observedCtpKubeconfig?.Resource?.status?.atProvider?.manifest?.data?.kubeconfig which then returned nothing. The kubeconfig was observed correctly and the regexes match it fine - the function simply could not find the resource. So status.upbound stayed {} and the Environment never initialised, which is what the last two e2e runs were reporting. Diagnosed by keeping a failed e2e control plane alive (skipDelete plus --skip-control-plane-cleanup) and reading the live objects: the observer had data.kubeconfig present, the XR had "upbound":{}, and the annotation read crossplane.io/composition-resource-name: e2e-bootstrap-ctp-kubeconfig-observed. Both annotations are now set explicitly rather than merged, with a comment about the replace-not-merge behaviour. Note the same pattern exists on several ProviderConfigs here, which lose their resource name the same way. It is harmless - nothing looks those up by key - and changing them now would rename live composed resources, orphaning and recreating them in existing environments. Left alone deliberately. * fix: compose the group ProviderConfig regardless of createGroup The ControlPlane and the nested SharedAWSSecret both reference the environment group's ProviderConfig, but it was only composed when createGroup was true. Setting createGroup: false therefore left both pointing at a ProviderConfig that never existed. Only the group object itself and the space-level ProviderConfig that creates it belong behind createGroup; managing resources inside the group needs that ProviderConfig either way. Covers it with tests/test-environment-existing-group. * test(e2e): deploy into a pre-existing group CI authenticates as a team-scoped robot. Upbound grants RBAC per group - a team is bound to one group with an ObjectRoleBinding, and nothing grants "create any group" short of an organization owner - so the robot could not create the <UP_GROUP>-e2e group the environment provisions, and every Object landing inside it came back forbidden. The group and its ObjectRoleBinding are now provisioned once by hand and outlive a run; the suite sets createGroup: false. Teardown still removes the control plane, secret stores and AWS resources. Documents the identity an Environment needs, and the pre-created group alternative, in the README. * feat: expose recoveryWindowInDays on the Secrets Manager secret AWS does not delete a Secrets Manager secret outright - it schedules it and reserves the name for the recovery window, 30 days by default. An environment torn down and recreated under the same name fails with "You can't create this secret because a secret with this name is already scheduled for deletion" until the window closes, which is what the e2e suite hit on its first successful teardown. Exposes the window on SharedAWSSecret, passes it through from Environment, and sets 0 in the e2e so the suite is re-runnable. Left unset the AWS default still applies, so nothing changes for anyone who wants the secret recoverable. The pass-through guards on `!= Undefined` rather than truthiness: 0 is both the meaningful value and a falsy one. * fix: depend on version ranges rather than exact versions A Configuration's dependency version is a constraint, and an exact version is the narrowest one there is. A control plane that already runs a newer patch or minor of provider-kubernetes (or any other dependency) cannot satisfy it, and the Lock fails with DependencyResolutionFailed - the same way v0.7.0 failed on merck, just in the other direction. Use floor-plus-major ranges: the tested version is the minimum, and anything up to the next major is accepted. The k8s API dependency stays exact; it is a git ref, not a package constraint. * fix: keep Environment names unique across namespaces With a Namespaced XRD, team-a/prod and team-b/prod are both valid, but every name outside the XR's namespace was built from metadata.name alone. Both XRs mapped to the same Upbound group - and so the same ControlPlane, Team, Robot and Argo secret - the same AWS IAM and Secrets Manager names, and the same kubeconfig Secrets in the bootstrap control plane's `default` namespace. With deletionPolicy: Delete, deleting one tore down the other. - The group is now <bootstrapGroup>-<namespace>-<name>. Everything org-wide or AWS-side derives from it. - Kubeconfig Secrets are written to the XR's namespace, and the ProviderConfigs read them from there. - The IAM Role name, which the namespace lengthens, gets the same 64-character truncation SharedAWSSecret applies to its IAM user. Covered by tests/test-environment-namespaced-names. The e2e's pre-created group is renamed to match. --------- Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Enables ManagementPolicies by default.
Why
ManagementPolicies has been stable in Crossplane for a long time, and both
provider-awsandprovider-kubernetesalready ship with it on — verified on a live control plane, with no args set on either deployment. This provider still defaulted the flag tofalseand still described the feature as alpha.The consequence is that any resource with a non-default
managementPoliciesfails to reconcile:Under Crossplane v2 this stops being an edge case. Namespaced managed resources have no
deletionPolicyfield at all, somanagementPoliciesis the only way to express "do not delete the external resource". Orphaning aRepositoryor aTeamis simply not possible without this — andplatform-ref-upboundorphans repositories by default, precisely so that deleting an XR does not destroy published packages.It was also awkward to turn on. Upbound Spaces' admission webhook rejects arbitrary
argson a package runtime:so the only route on a managed control plane is the
ENABLE_MANAGEMENT_POLICIESenv var via aDeploymentRuntimeConfig— not obvious from the flag name, and easy to get wrong by reaching forargsfirst.What changed
--enable-management-policiesnow defaults totrue;--enable-management-policies=falseremains as an opt-out.EnableAlphaManagementPolicies→EnableManagementPolicies(12 controllers), and the startup log no longer claims "Alpha feature enabled".internal/only, no API change.Verification
go build ./...,go vet ./...andgo test ./...all pass.Behaviour was confirmed against a live Upbound control plane (Crossplane 2.4.1-up.1) before this change, by injecting the env var through a
DeploymentRuntimeConfig: a composedRepositorycarrying["Create","Observe","Update","LateInitialize"]moved from the feature-not-enabled error to a normal auth failure against a deliberately invalid token, with the policy accepted as stored. This PR makes that the default rather than something each operator has to discover.🤖 Generated with Claude Code