Skip to content

feat(cli): add cdk orphan command to detach resources from a stack#1324

Closed
rix0rrr wants to merge 11 commits intoaws:mainfrom
LeeroyHannigan:lhnng-orphan-resource
Closed

feat(cli): add cdk orphan command to detach resources from a stack#1324
rix0rrr wants to merge 11 commits intoaws:mainfrom
LeeroyHannigan:lhnng-orphan-resource

Conversation

@rix0rrr
Copy link
Copy Markdown
Contributor

@rix0rrr rix0rrr commented Apr 9, 2026

Adds a new CLI command that safely removes resources from a CloudFormation stack without deleting them, enabling resource type migrations (e.g. DynamoDB Table to GlobalTable).

cdk orphan --path <ConstructPath> will:

  • Find all resources under the construct path via aws:cdk:path metadata
  • Resolve {Ref} values via DescribeStackResources
  • Resolve {Fn::GetAtt} values by injecting temporary stack Outputs
  • Set DeletionPolicy: Retain on all matched resources
  • Remove the resources from the stack
  • Output an inline cdk import command with the resource mapping

Also adds inline JSON support for --resource-mapping in cdk import, and exposes stackSdk() on Deployments for read-only SDK access.


By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license

@rix0rrr
Copy link
Copy Markdown
Contributor Author

rix0rrr commented Apr 9, 2026

This is actually @LeeroyHannigan's change, I'm just turning it into a PR so I can leave comments more conveniently.

@github-actions github-actions Bot added the p2 label Apr 9, 2026
@aws-cdk-automation aws-cdk-automation requested a review from a team April 9, 2026 08:14
Copy link
Copy Markdown
Contributor Author

@rix0rrr rix0rrr left a comment

Choose a reason for hiding this comment

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

Great start!

Initial round of comments on this.

This also needs an integ test (and to be frank probably more than 1 😉 ).

Comment thread packages/@aws-cdk/toolkit-lib/lib/api/deployments/deployments.ts Outdated
Comment thread packages/@aws-cdk/toolkit-lib/lib/api/resource-import/importer.ts Outdated
Comment thread packages/aws-cdk/lib/cli/cdk-toolkit.ts Outdated
Comment thread packages/aws-cdk/lib/cli/cdk-toolkit.ts Outdated
Comment thread packages/aws-cdk/lib/cli/cdk-toolkit.ts Outdated
Comment thread packages/@aws-cdk/toolkit-lib/lib/api/orphan/orphaner.ts Outdated
Comment thread packages/@aws-cdk/toolkit-lib/lib/api/orphan/orphaner.ts Outdated
Comment thread packages/@aws-cdk/toolkit-lib/lib/api/orphan/orphaner.ts Outdated
Comment thread packages/@aws-cdk/toolkit-lib/lib/api/orphan/orphaner.ts Outdated
Comment thread packages/aws-cdk/lib/cli/cdk-toolkit.ts
Comment thread packages/aws-cdk/lib/cli/cdk-toolkit.ts
auto-merge was automatically disabled April 9, 2026 17:48

Head branch was pushed to by a user without write access

@LeeroyHannigan LeeroyHannigan force-pushed the lhnng-orphan-resource branch from 82e0371 to 8ad5411 Compare April 9, 2026 18:12
@codecov-commenter
Copy link
Copy Markdown

codecov-commenter commented Apr 9, 2026

Codecov Report

❌ Patch coverage is 71.42857% with 20 lines in your changes missing coverage. Please review.
✅ Project coverage is 88.14%. Comparing base (b96bcce) to head (f576cc1).
⚠️ Report is 3 commits behind head on main.

Files with missing lines Patch % Lines
packages/aws-cdk/lib/cli/cli.ts 7.14% 13 Missing ⚠️
packages/aws-cdk/lib/cli/cdk-toolkit.ts 79.41% 7 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main    #1324      +/-   ##
==========================================
- Coverage   88.29%   88.14%   -0.15%     
==========================================
  Files          73       74       +1     
  Lines       10386    10494     +108     
  Branches     1413     1428      +15     
==========================================
+ Hits         9170     9250      +80     
- Misses       1189     1216      +27     
- Partials       27       28       +1     
Flag Coverage Δ
suite.unit 88.14% <71.42%> (-0.15%) ⬇️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

Comment thread packages/@aws-cdk/toolkit-lib/lib/actions/orphan/index.ts
Comment thread packages/@aws-cdk/toolkit-lib/lib/actions/orphan/orphaner.ts Outdated
Comment on lines +316 to +320
// Only include the primary resource for each construct path
// e.g. for path "MyTable", match "StackName/MyTable/Resource" exactly
const cdkPath = resource.Metadata?.[PATH_METADATA_KEY] ?? '';
const primaryPaths = constructPaths.map(p => `${stack.stackName}/${p}/Resource`);
if (!primaryPaths.includes(cdkPath)) continue;
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

This smells to me. I can't be bothered to try to understand what this is doing exactly, but it doesn't feel right.

Why the interpolation with stackName? Why only exactly this L1 reosurce? Why includes(cdkPath) instead of startsWith(cdkPath)?

What are we trying to do here?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Removed the path filter entirely. Will need to discuss.

Comment thread packages/@aws-cdk/toolkit-lib/lib/actions/orphan/orphaner.ts Outdated
Comment thread packages/@aws-cdk/toolkit-lib/lib/toolkit/toolkit.ts
Comment thread packages/aws-cdk/lib/cli/cdk-toolkit.ts Outdated
Comment thread packages/aws-cdk/lib/cli/cdk-toolkit.ts Outdated
# `cdk import`
- cloudformation:GetTemplateSummary
# `cdk orphan`
- cloudformation:ListStackResources
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

This needs an update to the bootstrap stack version as well (in 2 places)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Done, thanks

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I changed my mind. Can we change this to:

                  - cloudformation:List*
                  - cloudformation:Describe*

I'm tired of having to add every new read-only permissions one by one :(

# `cdk import`
- cloudformation:GetTemplateSummary
# `cdk orphan`
- cloudformation:ListStackResources
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I changed my mind. Can we change this to:

                  - cloudformation:List*
                  - cloudformation:Describe*

I'm tired of having to add every new read-only permissions one by one :(


// Step 1/3: Resolve GetAtt attribute values via temporary stack outputs
await this.ioHelper.defaults.info('Step 1/3: Resolving attribute values...');
const resolvedValues = await this.resolveGetAttValues(stack, cfn, logicalIds, currentTemplate, physicalIds);
Copy link
Copy Markdown
Contributor Author

@rix0rrr rix0rrr Apr 20, 2026

Choose a reason for hiding this comment

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

I just realized there's something missing.

A {Fn::Sub} has implicit interpolation inside its format string. It can look like this:

{ 
  "Fn::Sub": "before ${ImplicitRef} ${ImplicitAtt.AttrName} after"
}

{ 
  "Fn::Sub": [
    "before ${ImplicitRef} ${ImplicitAtt.AttrName} or ${Explicit} after",
    {
      "Explicit": { "Fn::GetAtt": ["Resource", "Attr"] }
    }
}

For the final case we don't have to do anything (Resource.Attr) because (I assume) it would already be found. But the 2 strings can contain implicit references:

  • ${ImplicitRef} inside format string - resolves the same as { "Ref": "ImplicitRef" } would.
  • ${ImplicitAtt.AttrName} inside format string - resolves the same as { "Fn::GetAtt": ["ImplicitAtt", "AttrName"] } would.

We need to discover these references in the discovery stage, and replace them in the replacement stage (be sure to add tests).

Note: the format string can occur in 2 forms, either inside an array or as a direct argument.

Be sure to add tests on these.

Comment thread packages/aws-cdk/README.md Outdated
Comment on lines +798 to +800
⚠️**CAUTION**⚠️: CDK Orphan is currently experimental and may have
breaking changes in the future. Make sure to use the `--unstable=orphan` flag
when using this command.
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Let's make this sound less scary.

Suggested change
⚠️**CAUTION**⚠️: CDK Orphan is currently experimental and may have
breaking changes in the future. Make sure to use the `--unstable=orphan` flag
when using this command.
> `cdk orphan` is currently experimental, meaning we reserve the right to change
> options and flag names in the future. Pass the `--unstable=orphan` flag when using
> this command and be aware of this when using it in scripts.

Lee Hannigan added 10 commits April 20, 2026 15:14
Adds a new CLI command that safely removes resources from a CloudFormation
stack without deleting them, enabling resource type migrations (e.g.
DynamoDB Table to GlobalTable).

`cdk orphan --path <ConstructPath>` will:
- Find all resources under the construct path via aws:cdk:path metadata
- Resolve {Ref} values via DescribeStackResources
- Resolve {Fn::GetAtt} values by injecting temporary stack Outputs
- Set DeletionPolicy: Retain on all matched resources
- Remove the resources from the stack
- Output an inline `cdk import` command with the resource mapping

Also adds inline JSON support for `--resource-mapping` in `cdk import`,
and exposes `stackSdk()` on Deployments for read-only SDK access.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants