Skip to content

feat: force-delete phantom Cognito UserPoolUICustomizationAttachment (#647)#648

Merged
go-to-k merged 1 commit into
mainfrom
feat/support-cognito-ui-customization-attachment
Jun 29, 2026
Merged

feat: force-delete phantom Cognito UserPoolUICustomizationAttachment (#647)#648
go-to-k merged 1 commit into
mainfrom
feat/support-cognito-ui-customization-attachment

Conversation

@go-to-k

@go-to-k go-to-k commented Jun 29, 2026

Copy link
Copy Markdown
Owner

Closes #647.

Problem

A stack containing AWS::Cognito::UserPoolUICustomizationAttachment whose CREATE fails (it requires an existing UserPoolDomain) leaves a phantom in DELETE_FAILED on rollback: the resource does not actually exist in AWS, but CloudFormation cannot delete "nothing" cleanly, so the stack is stuck in ROLLBACK_FAILED.

Because the type was not in the force-delete support list, delstack aborted the entire deletion with UnsupportedResourceError instead of removing the stack.

Approach: per-type Operator, not a generic retain

The issue suggested a type-agnostic "retain anything that can't be force-deleted" fallback. That was deliberately not adopted: blanket-retaining unknown DELETE_FAILED resources can silently leave real, billable resources behind, which makes delstack equivalent to CloudFormation's built-in FORCE_DELETE_STACK, erasing the "no orphaned resources" guarantee that is delstack's reason to exist.

Instead this adds a no-op Operator for this specific type, mirroring the existing CustomOperator:

  • This attachment is only UI customization (CSS/logo) on a user pool, applied via SetUICustomization, which requires a domain. It reaches DELETE_FAILED only as a phantom, where there is genuinely nothing to delete.
  • The operator is a no-op; the CFN delete loop then retains the logical resource to drop the phantom from the stack.
  • This is the same justified "phantom -> retain" pattern delstack already uses for Custom Resources, scoped to a type that was explicitly vetted as having no real/billable resource when DELETE_FAILED.

The phantom-on-failed-create phenomenon is a bounded class (resource providers with non-idempotent delete handlers: Custom Resources, Attachment/Association/Settings style types), not universal, and its biggest source (Custom Resources) is already covered. So per-type support remains sustainable; unknown types still abort loudly rather than orphaning anything.

Changes

  • resourcetype: add CognitoUserPoolUICustomizationAttachment.
  • internal/operation/cognito_user_pool_ui_customization_attachment.go: no-op Operator + rationale comment.
  • operator_factory.go / operator_collection.go: wire it in (4 sites + supported-resources table row).
  • operator_collection_test.go: cover the new type (asserts it is now supported, not unsupported).
  • README.md: add a row to the supported-resources table.

E2E

New generic e2e/create_failed scenario (not Cognito-specific by intent):

  • Deploys a stack that intentionally fails CREATE (this Cognito attachment without a domain), leaving a ROLLBACK_FAILED stack with a DELETE_FAILED phantom. deploy.go treats the expected cdk deploy failure as non-fatal and verifies the phantom precondition (errors out if no phantom was produced, so it never validates silently).
  • make e2e_create_failed then runs delstack and confirms the stuck stack is force-deleted.
  • Documented as standing in for the whole class of create-failed phantom types, so future similar types are covered here.

Verified locally: make lint_diff clean, make test green, both e2e modules go vet/go build, and cdk synth produces the intended template (logical id UICustomization, no domain). Real AWS E2E runs are left to the maintainer per project convention.

…647)

A stack containing AWS::Cognito::UserPoolUICustomizationAttachment whose
CREATE fails (e.g. no UserPoolDomain exists) leaves a phantom resource in
DELETE_FAILED on rollback: it does not actually exist in AWS, but
CloudFormation cannot delete "nothing", so the stack is stuck. delstack
previously aborted the whole deletion with UnsupportedResourceError instead
of removing the stack.

Add a no-op Operator for this type (mirroring the existing CustomOperator):
there is no real, billable resource to delete, so the CFN delete loop retains
the logical resource to drop the phantom from the stack. This keeps delstack
true to "no orphaned resources" because a phantom is, by definition, nothing
left behind, unlike CloudFormation's blanket FORCE_DELETE_STACK.

Also add a generic `e2e/create_failed` scenario that manufactures a
create-failed phantom (via this Cognito attachment without a domain) and
verifies delstack force-deletes the stuck stack. It stands in for the whole
class of create-failed phantom types (Custom Resources and
Attachment/Association/Settings style resources with non-idempotent delete
handlers).
@go-to-k
go-to-k merged commit 90fa7a6 into main Jun 29, 2026
5 checks passed
@go-to-k
go-to-k deleted the feat/support-cognito-ui-customization-attachment branch June 29, 2026 05:33
@github-actions github-actions Bot mentioned this pull request Jun 29, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Force-delete/retain arbitrary DELETE_FAILED members so any stack is always deletable (hit on Cognito UICustomizationAttachment phantom)

1 participant