-
Notifications
You must be signed in to change notification settings - Fork 108
feat: cdk debootstrap command #952
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
sanjanaravikumar-az
wants to merge
10
commits into
main
Choose a base branch
from
sanjrkmr/cdk-debootstrap
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
10 commits
Select commit
Hold shift + click to select a range
1d1f222
feat: rfc for cdk debootstrap command
sanjanaravikumar-az 4657d4b
Merge branch 'main' into sanjrkmr/cdk-debootstrap
sanjanaravikumar-az 70928ff
fix: resolve markdownlint violations
sanjanaravikumar-az ecb6f12
chore: trigger CI
sanjanaravikumar-az e23028c
Update 0949-cdk-debootstrap.md
sanjanaravikumar-az 917eb7f
Update 0949-cdk-debootstrap.md
sanjanaravikumar-az edda76f
address reviewer feedback
sanjanaravikumar-az 3f5ebbe
align environment selection with cdk bootstrap
sanjanaravikumar-az 8b53e6b
Merge branch 'main' into sanjrkmr/cdk-debootstrap
sanjanaravikumar-az 440114b
Merge branch 'main' into sanjrkmr/cdk-debootstrap
sanjanaravikumar-az File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
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
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,323 @@ | ||
| # `cdk debootstrap` — Reverse CDK Bootstrap | ||
|
|
||
| * **Original Author(s):**: @sanjanaravikumar-az | ||
| * **Tracking Issue**: [#949](https://github.com/aws/aws-cdk-rfcs/issues/949) | ||
| * **API Bar Raiser**: @rix0rrr | ||
|
|
||
| There is no supported way to reverse `cdk bootstrap`. Users who stop using CDK | ||
| in an account/region, or need to re-bootstrap with a different configuration | ||
| must manually empty S3 buckets, delete ECR images, and remove the CloudFormation | ||
| stack. This is an error-prone process and leads to broken states. | ||
|
|
||
| ## Working Backwards | ||
|
|
||
| ### CHANGELOG | ||
|
|
||
| ``` | ||
| feat(cli): add `cdk debootstrap` command to reverse CDK bootstrap | ||
| ``` | ||
|
|
||
| ### README | ||
|
|
||
| #### `cdk debootstrap` | ||
|
|
||
| Removes the CDK bootstrap stack and all associated resources from an AWS | ||
| environment that get created when `cdk bootstrap` is run. | ||
|
|
||
| ##### Usage | ||
|
|
||
| When you want to remove bootstrap resources from your account, run: | ||
|
|
||
| ```console | ||
| $ cdk debootstrap aws://123456789012/us-east-1 | ||
| ``` | ||
|
|
||
| Run from the directory of a CDK app (with a `cdk.json`) without arguments to | ||
| remove the bootstrap resources for all environments referenced by the app, | ||
| mirroring how `cdk bootstrap` targets environments: | ||
|
|
||
| ```console | ||
| $ cdk debootstrap | ||
| ``` | ||
|
|
||
| Before anything is deleted, you are shown exactly which environments and | ||
| resources will be removed and asked to confirm (see Confirmation prompts). | ||
|
|
||
| ##### What it does | ||
|
|
||
| `cdk debootstrap` performs a complete teardown of the CDKToolkit CloudFormation | ||
| stack in a specified environment. All bootstrapped resources will be deleted, | ||
| including the S3 asset bucket, ECR repository, and all files and images within | ||
| them. IAM roles, KMS keys, and the SSM parameter are also removed. Because the | ||
| bucket must be emptied before it can be deleted, the duration of this operation | ||
| depends on how many objects are in the bucket. | ||
|
|
||
| ##### Confirmation prompts | ||
|
|
||
| The command displays two sequential prompts before any destructive action. | ||
|
|
||
| **Prompt 1 - Dependency warning** : | ||
|
|
||
| This is shown when there are dependent stacks found. | ||
|
|
||
| ``` | ||
| ⚠️ The following stacks use this bootstrap and will no longer be deployable or destroyable via CDK: | ||
| - MyAppStack | ||
| - MyOtherStack | ||
|
|
||
| Do you want to continue? (y/N): | ||
| ``` | ||
|
|
||
| **Prompt 2 - Final confirmation**: | ||
|
|
||
| ``` | ||
| This will permanently delete all assets in the staging bucket and ECR repository. | ||
| The bootstrap stack can be re-created with `cdk bootstrap`, but stored assets cannot be recovered. | ||
|
|
||
| You are about to destroy the CDK bootstrap stack in: | ||
| Account : 123456789012 | ||
| Region : us-east-1 | ||
| Stack : CDKToolkit (qualifier: hnb659fds) | ||
|
|
||
|
iankhou marked this conversation as resolved.
|
||
| Are you sure you want to proceed? (y/N): | ||
| ``` | ||
|
|
||
| `--force` skips the dependency check entirely. `--yes` skips all interactive | ||
|
sanjanaravikumar-az marked this conversation as resolved.
|
||
| prompts. | ||
|
|
||
| ##### Flags | ||
|
|
||
| | Flag | Description | | ||
| | --- | --- | | ||
| | `--force` | Skip the dependency check entirely | | ||
| | `--yes` | Skip all interactive prompts (global flag) | | ||
| | `--qualifier` | Target a non-default bootstrap stack (default: `hnb659fds`) | | ||
| | `--toolkit-stack-name` | Override the default stack name `CDKToolkit` | | ||
| | `--profile` | AWS named profile to use | | ||
|
sanjanaravikumar-az marked this conversation as resolved.
|
||
|
|
||
| ##### Idempotency | ||
|
|
||
| The command is safe to re-run after partial failure. You can run it again to | ||
| pick up where you left off - for example, if the stack is already gone but the | ||
| bucket still exists, it skips to bucket cleanup. | ||
|
|
||
| ##### Examples | ||
|
|
||
| Remove bootstrap from a specific environment: | ||
|
|
||
| ```console | ||
| $ cdk debootstrap aws://123456789012/us-east-1 | ||
| ``` | ||
|
|
||
| Remove bootstrap for all environments referenced by the current CDK app: | ||
|
|
||
| ```console | ||
| $ cdk debootstrap | ||
| ``` | ||
|
|
||
| Skip dependency check and all prompts (for CI): | ||
|
|
||
| ```console | ||
| $ cdk debootstrap aws://123456789012/us-east-1 --force --yes | ||
| ``` | ||
|
|
||
| Remove a non-default bootstrap stack: | ||
|
|
||
| ```console | ||
| $ cdk debootstrap aws://123456789012/us-east-1 --qualifier myqualifier | ||
| ``` | ||
|
|
||
| --- | ||
|
|
||
| ``` | ||
| [ ] Signed-off by API Bar Raiser @xxxxx | ||
| ``` | ||
|
|
||
| ## Public FAQ | ||
|
|
||
| ### What are we launching today? | ||
|
|
||
| A new CDK CLI command, `cdk debootstrap`, that provides a supported way to | ||
| completely remove the CDK bootstrap stack and all its resources from an AWS | ||
| environment. | ||
|
|
||
| ### Why should I use this feature? | ||
|
|
||
| `cdk debootstrap` should be used when: | ||
|
|
||
| - You are done using CDK in an account/region and want to remove all CDK | ||
| infrastructure | ||
| - You need to remove an old bootstrap before re-bootstrapping with a different | ||
| qualifier, trust relationships, or permissions boundary | ||
| - You are recycling sandbox/workshop accounts among users and need to return | ||
| them to a clean state | ||
| - You need a clean-room environment for integration testing (0-to-100% | ||
| validation) | ||
| - You got into a broken bootstrap state (e.g., manually deleted the bucket) | ||
| and need a full reset | ||
|
|
||
| ## Internal FAQ | ||
|
|
||
| ### Why are we doing this? | ||
|
|
||
| [GitHub Issue #643](https://github.com/aws/aws-cdk-cli/issues/643) has been | ||
| open since 2018 and was escalated to P1. Users who attempted manual teardown | ||
| ended up with broken states - deleting the bucket without deleting the stack, | ||
| or the stack without the bucket and would get locked out of their environment. | ||
|
|
||
| ### Why should we _not_ do this? | ||
|
|
||
| - **Risk of accidental data loss**: Users could destroy bootstrap infrastructure | ||
| that active CDK applications depend on, making those applications | ||
| unmanageable. Unlike `cdk destroy` which is application scoped, | ||
| `cdk debootstrap` affects every CDK deployment in that account/region/qualifier | ||
| combination. | ||
|
|
||
| This risk is mitigated by the dependency detection warning and the two | ||
| confirmation prompts. | ||
|
|
||
|
sanjanaravikumar-az marked this conversation as resolved.
|
||
| ### What is the technical solution (design) of this feature? | ||
|
|
||
| **Command:** `cdk debootstrap` is a new top-level CLI command mirroring the | ||
| `deploy`/`destroy` pairing. | ||
|
|
||
| **Environment selection:** `cdk debootstrap` uses the same environment | ||
| selection behavior as `cdk bootstrap`: | ||
|
|
||
| - `cdk debootstrap aws://123456789012/us-east-1` - targets that environment | ||
| directly | ||
| - `cdk debootstrap` (in a CDK app directory) - targets all environments | ||
| referenced by the app | ||
| - `cdk debootstrap` (no `cdk.json`, no arguments) - errors and asks for an | ||
| explicit environment | ||
|
|
||
| We do not use an interactive picker. This keeps the command consistent with | ||
| the rest of the bootstrap command family and usable in CI. The risk of | ||
| targeting the wrong environment is handled by the confirmation prompt, which | ||
| lists every environment and resource that will be deleted before proceeding. | ||
|
|
||
| **Dependency detection:** We read the actual resource names (bucket name, ECR | ||
| repo name, role ARNs) from the bootstrap stack's outputs and resources, then | ||
| search for those actual values in other stacks: | ||
|
|
||
| 1. Get the actual bootstrap resource names from the CDKToolkit stack (via | ||
| stack outputs and `GetTemplate`) | ||
| 2. Call `DescribeStacks` to list all stacks in the account/region | ||
| 3. For each stack, check its `RoleARN` field, template body, and parameters | ||
| for any reference to the bootstrap resource names | ||
| 4. Any match means that stack depends on this bootstrap | ||
|
|
||
| This approach catches all stacks that reference the bootstrap — including | ||
| stacks deployed against a customized bootstrap template where resources were | ||
| renamed. | ||
|
|
||
| **Termination protection:** If the bootstrap stack has termination protection | ||
| enabled, the command aborts with a clear message asking you to disable it | ||
| first. | ||
|
|
||
| **Deletion sequence:** | ||
|
|
||
| 1. Discover bootstrap stack via `ToolkitInfo.lookup()` | ||
| 2. Check for dependent stacks (`DescribeStacks` + role ARN pattern matching + | ||
| bucket/ECR name matching) | ||
| 3. Check termination protection | ||
| 4. Prompt for confirmation - includes dependency warning and final confirmation | ||
| 5. Empty all S3 buckets in the stack by resource type via `GetTemplate`, not | ||
| hardcoded logical IDs | ||
| 6. Delete all ECR repository images by resource type | ||
| 7. Delete the CloudFormation stack: CDKToolkit | ||
| 8. Delete the orphaned S3 bucket(s) as retained due to | ||
| `DeletionPolicy: Retain` | ||
|
|
||
| **S3 versioned bucket emptying (step 5):** The staging bucket has versioning | ||
| enabled. Running `deleteObjects` on current versions is insufficient because | ||
| every version of every object and all delete markers must be deleted. This | ||
| implementation calls `listObjectVersions` to enumerate all version IDs, and | ||
| then `deleteObjects` with explicit version IDs in batches of 1000 with 50 | ||
| parallel workers. The existing `GarbageCollector.readBucketInBatches()` already | ||
| handles this pattern. | ||
|
|
||
| **Idempotency:** Each step checks current state. If the stack is gone but an | ||
| orphaned bucket still exists, re-running skips to bucket cleanup. We check to | ||
| see if the orphaned bucket exists by running `HeadBucket` using the | ||
| deterministic name. | ||
|
|
||
| **Prompts:** The command issues up to two sequential prompts before any | ||
| destructive action: | ||
|
|
||
| - **Prompt 1 - Dependency warning** : If dependent stacks are detected, it | ||
| lists the affected stacks and asks "Do you want to continue?" | ||
| - **Prompt 2 - Final confirmation**: Shows what will be deleted, asks "Are you | ||
| sure?" | ||
|
|
||
| **Flags:** | ||
|
|
||
| - `--force` skips the dependency check entirely | ||
| - `--yes` auto-answers all interactive prompts | ||
| - `--qualifier` targets non-default bootstrap stacks | ||
|
|
||
| **Reusable code:** The implementation reuses existing toolkit-lib primitives: | ||
|
|
||
| - `ToolkitInfo.lookup()` for stack discovery | ||
| - `GarbageCollector.parallelDeleteS3()` and `readBucketInBatches()` for S3 | ||
| emptying | ||
| - `destroyStack()` for CloudFormation stack deletion | ||
| - The `cdk destroy` confirmation pattern for user prompting | ||
|
|
||
| ### Is this a breaking change? | ||
|
|
||
| No. This is a new command that does not affect existing behavior. The existing | ||
| `cdk bootstrap` behavior is unchanged. | ||
|
|
||
| ### What alternative solutions did you consider? | ||
|
|
||
| **`cdk bootstrap --destroy`:** Adds a flag to the existing command. This flag | ||
| wouldn't just modify a command's behavior, but invert it. Also, | ||
| `cdk bootstrap --destroy --force` is three flags deep and is awkward from a UX | ||
| perspective. | ||
|
sanjanaravikumar-az marked this conversation as resolved.
|
||
|
|
||
| ### What are the drawbacks of this solution? | ||
|
|
||
| - **Cross-account visibility:** If Account B deploys into Account A using A's | ||
| bootstrap roles through the `--trust` flag, then deleting A's bootstrap means | ||
| B would not be able to deploy into A anymore. We can detect that | ||
| `TrustedAccounts` is configured and warn, but cannot proactively notify | ||
| Account B. | ||
| - **Custom bootstrap templates:** Whoever bootstrapped with a custom template | ||
| may have additional resources with `DeletionPolicy: Retain` that we can't | ||
| clean up. We identify these via `GetTemplate` and warn them. | ||
| - **Race conditions:** If someone runs `cdk deploy` concurrently with | ||
| `cdk debootstrap`, the deployment will fail. This requires operational | ||
| coordination. | ||
|
|
||
| ### What is the high-level project plan? | ||
|
|
||
| 1. **Implementation in toolkit-lib:** Add `bootstrapDestroy()` method to the | ||
| Toolkit class, implementing the deletion sequence with dependency detection | ||
| and idempotency. | ||
| 2. **CLI wiring:** Add `cdk debootstrap` command definition, argument parsing, | ||
| and routing to toolkit-lib. | ||
| 3. **Testing:** Unit tests for each step, integration tests for the full | ||
| sequence (happy path, partial failure re-run, dependent stacks warning). | ||
| 4. **Documentation:** Add `cdk debootstrap` to CLI reference docs, update | ||
| bootstrap documentation to reference the teardown path. | ||
|
|
||
| ### Are there any open issues that need to be addressed later? | ||
|
|
||
| - **Environment discovery outside an app:** Without a `cdk.json`, the command | ||
| requires explicit environment arguments. Whether to support discovering | ||
| bootstrapped environments outside of an app context - by scanning configured | ||
| regions, for example - is a future consideration. | ||
|
|
||
| ## Appendix | ||
|
|
||
| ### Known Limitations | ||
|
|
||
| | Limitation | Detail | | ||
| | --- | --- | | ||
| | Cross-account bootstrap | If Account B deploys into Account A using A's bootstrap roles, destroying A's bootstrap breaks B's ability to deploy into A. A warning is printed if `TrustedAccounts` is configured. | | ||
| | Custom bootstrap templates | Resources are enumerated by type (`AWS::S3::Bucket`, `AWS::ECR::Repository`) via `GetTemplate`. Resources with `DeletionPolicy: Retain` are identified and a warning is printed that they will be orphaned. | | ||
| | S3 Object Lock | If the bucket has Object Lock enabled, deletion will fail. You must remove Object Lock manually before retrying. | | ||
| | KMS key deletion delay | AWS enforces a 7–30 day waiting period. The key is scheduled for deletion but remains pending. | | ||
| | Large buckets | Emptying millions of objects may take 10–30 minutes. Recovery from mid-step failure is via idempotent re-run. | | ||
| | Race conditions | Concurrent `cdk deploy` will fail as resources are removed. Operational coordination required. | | ||
Oops, something went wrong.
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.
Uh oh!
There was an error while loading. Please reload this page.