Repository navigation
[Gateway Operator 1.2.x] externalRefId and in-place plan/token updates for Subscription CRs - #3920
Open
thivindu wants to merge 3 commits into
Conversation
…operator Add an optional spec.externalRefId to the Subscription CR, used as the gateway subscription ID. Update subscriptionPlanId, subscriptionToken (including a rotated valueFrom Secret) and status in place through the management API instead of rejecting them as immutable. Removing the plan from the CR clears it on the gateway. Only externalRefId, apiId, applicationId and the billing IDs stay immutable. The immutable-fields fingerprint replaces the old create-fields fingerprint, and the legacy annotation is removed on the next reconcile. After a create conflict, an existing subscription is adopted only when it belongs to the CR's API and application: - With externalRefId, the subscription with that ID must be listed under the CR's API and application; otherwise a generic, non-retryable 409 is reported that doesn't say whether the ID or the token collided. - Without it, an ID is returned only when exactly one subscription on the API matches the CR's application (unset, empty and blank application IDs mean no application); otherwise the conflict is retried. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
thivindu
requested review from
AnuGayan,
Arshardh,
CrowleyRajapakse,
HeshanSudarshana,
HiranyaKavishani,
Induwara04,
Krishanx92,
PasanT9,
Piumal1999,
RakhithaRR,
Tharsanan1,
Thushani-Jayasekera,
ashera96,
chamilaadhi,
dushaniw,
hisanhunais,
lasanthaS,
pubudu538,
renuka-fernando,
senthuran16,
tgtshanika,
tharikaGitHub and
tharindu1st
as code owners
October 25, 2026 17:33
Contributor
|
Important Review skippedAuto reviews are disabled on base/target branches other than the default branch. Please check the settings in the CodeRabbit UI or the
|
RakhithaRR
approved these changes
Oct 27, 2026
- Retry the reconcile when the status-only fallback fails transiently, keeping the original error in the message; a successful or permanent failure still reports the original error. - Apply the status-only fallback to a subscription adopted after a create conflict too, through one applySubscriptionUpdate helper for both paths. - Fail the update when the gateway reports a different plan than the one sent, so a gateway that ignores in-place plan updates doesn't leave the CR Programmed. - Compare billingCustomerId and billingSubscriptionId before recording the baseline for a CR without a stored fingerprint. - Add tests for each, and for an immutable-field edit after the baseline. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This branch has not been deployed
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.
Purpose
The gateway management API now supports caller-set subscription IDs and in-place plan and token updates (#3804), and the
apCLI supports them too (#3820). This PR brings the same support to the gateway operator'sSubscriptionCR on the 1.2.x operator Helm chart branch.It backports #3918. The subscription changes are identical; only the generated
zz_generated.deepcopy.gofiles differ, becausemainhas API types this branch doesn't.Until now, a deployed Subscription CR could only change
spec.status:spec.subscriptionPlanIdorspec.subscriptionTokenwas rejected as immutable.valueFromSecret failed with "the gateway management API cannot update an existing subscription token".In every case the only option was to delete and recreate the CR. That left the subscription missing in between, and gave it a new gateway ID.
Related To:
Related PRs:
Changes
Subscription CR (
v1andv1alpha1)spec.externalRefId: used as the gateway subscription ID instead of a generated UUID.^[A-Za-z0-9][A-Za-z0-9._-]*$.subscriptionPlanId,subscriptionTokenandstatus.externalRefId,apiId,applicationId,billingCustomerIdandbillingSubscriptionId.config/crd/bases, and the Helm chart's CRD copy.Controller
subscriptionPlanIdsends"subscriptionPlanId": null, which clears the plan on the gateway.valueFromSecret. The existing Secret drift detection triggers the redeploy.400or409, the operator sends the status on its own, and the CR still reports the original error.404is not followed by a status-only update.status.idis still listed underspec.apiId, belongs tospec.applicationId, and, on gateways that report it, matchesspec.externalRefId.subscription-last-applied-create-fingerprint) removed. That annotation also covered the plan and token.apiId, therefore stays rejected after the upgrade.409, the existing subscription is adopted only if:externalRefId, it has that ID, is listed under the CR's own API, and belongs to the CR's application;externalRefId, it's the only subscription on the API for the CR's application. Application IDs are compared after trimming; unset, empty and blank all mean no application;status.id. Otherwise both CRs would manage, and on deletion remove, the same subscription.externalRefId, the CR gets a generic, non-retryable conflict that doesn't say whether the ID or the token collided. This stops a CR from taking over another subscription and overwriting its plan and token.externalRefId, the create is retried as before.externalRefId, assign their own IDs, and leaveexternalRefIdout of their responses. On those, recovery uses the API and application lookup, as it did before.Gateway client
SubscriptionCreatePayloadandSubscriptionResponsecarryexternalRefId.SubscriptionUpdatePayloadaddssubscriptionPlanId(always sent;nilis an explicitnull) andsubscriptionToken.UpdateSubscriptionStatus, which sends only the status and leaves the plan and token unchanged.ListSubscriptionsandSameApplication.FindSubscriptionIDByAPIAndApplicationnow uses them: it matches normalized application IDs, and returns an ID only when exactly one subscription matches.Example
gateway/examples/subscription.yamldocumentsexternalRefIdand which fields can change in place.Compatibility
externalRefIdneed a gateway with the subscription API changes from Allow caller-set subscription IDs, in-place plan/token updates, and fix subscription plan conflict errors #3804. Older gateways accept the PUT but ignore the plan and token, so such changes don't take effect there. Status updates and create conflict recovery work as before.Known limitations
spec.status.status.idyet.Testing
ListSubscriptions,SameApplication, and the update payload, including the explicitnullplan;FindSubscriptionIDByAPIAndApplication, including matching only the normalized application and returning nothing when several subscriptions match;externalRefIdsubscription on the same API and application;409;externalRefId, and whenexternalRefIdis unset or empty;400or409, or when the token Secret is missing;404, or while waiting for a SubscriptionPlan to deploy;apiId,applicationIdorexternalRefIdno longer match;go build,go vetand all operator unit tests pass.make manifests generatewith the pinnedcontroller-genv0.16.5 produces no further changes, and the Helm chart's CRD copy matches the generated base.🤖 Generated with Claude Code