@go-to-k/cdkd 0.29.0 → 0.30.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -24,6 +24,7 @@
24
24
  - **Diff calculation**: Self-implemented resource/property-level diff between desired template and current state
25
25
  - **S3-based state management**: No DynamoDB required, uses S3 conditional writes for locking
26
26
  - **DAG-based parallelization**: Analyze `Ref`/`Fn::GetAtt` dependencies and execute in parallel
27
+ - **`--no-wait` for async resources**: Skip the multi-minute wait on CloudFront / RDS / ElastiCache and return as soon as the create call returns (CloudFormation always blocks)
27
28
 
28
29
  > **Note**: Resource types not covered by either SDK Providers or Cloud Control API cannot be deployed with cdkd. If you encounter an unsupported resource type, deployment will fail with a clear error message.
29
30
 
@@ -90,58 +91,9 @@ Reproduce with `./tests/benchmark/run-benchmark.sh all`. See [tests/benchmark/RE
90
91
  └────────┘ └────────┘
91
92
  ```
92
93
 
93
- ### Detailed Processing Flow (`cdkd deploy`)
94
-
95
- ```
96
- 1. CLI Layer
97
- ├── Resolve --app (CLI > CDKD_APP env > cdk.json "app")
98
- ├── Resolve --state-bucket (CLI > env > cdk.json > auto: cdkd-state-{accountId}, with legacy fallback to cdkd-state-{accountId}-{region})
99
- └── Initialize AWS clients
100
-
101
- 2. Synthesis (self-implemented, no CDK CLI dependency)
102
- ├── Short-circuit: if --app is an existing directory, treat it as a
103
- │ pre-synthesized cloud assembly and skip the steps below
104
- ├── Load context (merge order, later wins):
105
- │ ├── CDK defaults (path-metadata, asset-metadata, version-reporting, bundling-stacks)
106
- │ ├── ~/.cdk.json "context" field (user defaults)
107
- │ ├── cdk.json "context" field (project settings)
108
- │ ├── cdk.context.json (cached lookups, reloaded each iteration)
109
- │ └── CLI -c key=value (highest priority)
110
- ├── Execute CDK app as subprocess
111
- │ ├── child_process.spawn(app command)
112
- │ ├── Pass env: CDK_OUTDIR, CDK_CONTEXT_JSON, CDK_DEFAULT_REGION/ACCOUNT
113
- │ └── App writes Cloud Assembly to cdk.out/
114
- ├── Parse cdk.out/manifest.json
115
- │ ├── Extract stacks (type: aws:cloudformation:stack)
116
- │ ├── Extract asset manifests (type: cdk:asset-manifest)
117
- │ └── Extract stack dependencies
118
- └── Context provider loop (if missing context detected):
119
- ├── Resolve via AWS SDK (all CDK context provider types supported)
120
- ├── Save to cdk.context.json
121
- └── Re-execute CDK app with updated context
122
-
123
- 3. Asset Publishing + Deployment (WorkGraph DAG)
124
- ├── Each asset is a node, each stack deploy is a node
125
- │ ├── asset-publish nodes: 8 concurrent (file S3 uploads + Docker build+push)
126
- │ ├── stack nodes: 4 concurrent deployments
127
- │ ├── Dependencies: asset-publish → stack (all assets complete before deploy)
128
- │ └── Inter-stack: stack A → stack B (CDK dependency order)
129
- ├── Region resolved from asset manifest destination (stack's target region)
130
- ├── Skip if already exists (HeadObject for S3, DescribeImages for ECR)
131
- ├── Per-stack deploy flow:
132
- │ ├── Acquire S3 lock (optimistic locking)
133
- │ ├── Load current state from S3
134
- │ ├── Build DAG from template (Ref/Fn::GetAtt/DependsOn)
135
- │ ├── Calculate diff (CREATE/UPDATE/DELETE)
136
- │ ├── Resolve intrinsic functions (Ref, Fn::Sub, Fn::Join, etc.)
137
- │ ├── Execute via event-driven DAG dispatch (a resource starts as
138
- │ │ soon as ALL of its own deps complete; no level barrier):
139
- │ │ ├── SDK Providers (direct API calls, preferred)
140
- │ │ └── Cloud Control API (fallback, async polling)
141
- │ ├── Save state after each successful resource (partial state save)
142
- │ └── Release lock
143
- └── synth does NOT publish assets or deploy (deploy only)
144
- ```
94
+ For a step-by-step walkthrough of the full `cdkd deploy` pipeline (CLI
95
+ parsing → synthesis → asset publishing → per-stack deploy), see
96
+ [docs/architecture.md](docs/architecture.md#5-end-to-end-pipeline-walkthrough-cdkd-deploy).
145
97
 
146
98
  ## Supported Features
147
99
 
@@ -181,101 +133,16 @@ Reproduce with `./tests/benchmark/run-benchmark.sh all`. See [tests/benchmark/RE
181
133
 
182
134
  ### Resource Provisioning
183
135
 
184
- | Category | Resource Type | Provider | Status |
185
- |----------|--------------|----------|--------|
186
- | **IAM** | AWS::IAM::Role | SDK Provider | |
187
- | **IAM** | AWS::IAM::Policy | SDK Provider | ✅ |
188
- | **IAM** | AWS::IAM::InstanceProfile | SDK Provider | |
189
- | **IAM** | AWS::IAM::User | SDK Provider | |
190
- | **IAM** | AWS::IAM::Group | SDK Provider | ✅ |
191
- | **IAM** | AWS::IAM::UserToGroupAddition | SDK Provider | ✅ |
192
- | **Storage** | AWS::S3::Bucket | SDK Provider | ✅ |
193
- | **Storage** | AWS::S3::BucketPolicy | SDK Provider | ✅ |
194
- | **Messaging** | AWS::SQS::Queue | SDK Provider | ✅ |
195
- | **Messaging** | AWS::SQS::QueuePolicy | SDK Provider | ✅ |
196
- | **Messaging** | AWS::SNS::Topic | SDK Provider | ✅ |
197
- | **Messaging** | AWS::SNS::Subscription | SDK Provider | ✅ |
198
- | **Messaging** | AWS::SNS::TopicPolicy | SDK Provider | ✅ |
199
- | **Compute** | AWS::Lambda::Function | SDK Provider | ✅ |
200
- | **Compute** | AWS::Lambda::Permission | SDK Provider | ✅ |
201
- | **Compute** | AWS::Lambda::Url | SDK Provider | ✅ |
202
- | **Compute** | AWS::Lambda::EventSourceMapping | SDK Provider | ✅ |
203
- | **Compute** | AWS::Lambda::LayerVersion | SDK Provider | ✅ |
204
- | **Database** | AWS::DynamoDB::Table | SDK Provider | ✅ |
205
- | **Monitoring** | AWS::Logs::LogGroup | SDK Provider | ✅ |
206
- | **Monitoring** | AWS::CloudWatch::Alarm | SDK Provider | ✅ |
207
- | **Secrets** | AWS::SecretsManager::Secret | SDK Provider | ✅ |
208
- | **Config** | AWS::SSM::Parameter | SDK Provider | ✅ |
209
- | **Events** | AWS::Events::Rule | SDK Provider | ✅ |
210
- | **Events** | AWS::Events::EventBus | SDK Provider | ✅ |
211
- | **Networking** | AWS::EC2::VPC | SDK Provider | ✅ |
212
- | **Networking** | AWS::EC2::Subnet | SDK Provider | ✅ |
213
- | **Networking** | AWS::EC2::InternetGateway | SDK Provider | ✅ |
214
- | **Networking** | AWS::EC2::VPCGatewayAttachment | SDK Provider | ✅ |
215
- | **Networking** | AWS::EC2::RouteTable | SDK Provider | ✅ |
216
- | **Networking** | AWS::EC2::Route | SDK Provider | ✅ |
217
- | **Networking** | AWS::EC2::SubnetRouteTableAssociation | SDK Provider | ✅ |
218
- | **Networking** | AWS::EC2::SecurityGroup | SDK Provider | ✅ |
219
- | **Networking** | AWS::EC2::SecurityGroupIngress | SDK Provider | ✅ |
220
- | **Networking** | AWS::EC2::NetworkAcl | SDK Provider | ✅ |
221
- | **Networking** | AWS::EC2::NetworkAclEntry | SDK Provider | ✅ |
222
- | **Networking** | AWS::EC2::SubnetNetworkAclAssociation | SDK Provider | ✅ |
223
- | **Compute** | AWS::EC2::Instance | SDK Provider | ✅ |
224
- | **API Gateway** | AWS::ApiGateway::Account | SDK Provider | ✅ |
225
- | **API Gateway** | AWS::ApiGateway::Resource | SDK Provider | ✅ |
226
- | **API Gateway** | AWS::ApiGateway::Deployment | SDK Provider | ✅ |
227
- | **API Gateway** | AWS::ApiGateway::Stage | SDK Provider | ✅ |
228
- | **API Gateway** | AWS::ApiGateway::Method | SDK Provider | ✅ |
229
- | **API Gateway** | AWS::ApiGateway::Authorizer | SDK Provider | ✅ |
230
- | **API Gateway** | AWS::ApiGatewayV2::Api | SDK Provider | ✅ |
231
- | **API Gateway** | AWS::ApiGatewayV2::Stage | SDK Provider | ✅ |
232
- | **API Gateway** | AWS::ApiGatewayV2::Integration | SDK Provider | ✅ |
233
- | **API Gateway** | AWS::ApiGatewayV2::Route | SDK Provider | ✅ |
234
- | **API Gateway** | AWS::ApiGatewayV2::Authorizer | SDK Provider | ✅ |
235
- | **CDN** | AWS::CloudFront::CloudFrontOriginAccessIdentity | SDK Provider | ✅ |
236
- | **CDN** | AWS::CloudFront::Distribution | SDK Provider | ✅ |
237
- | **Orchestration** | AWS::StepFunctions::StateMachine | SDK Provider | ✅ |
238
- | **Container** | AWS::ECS::Cluster | SDK Provider | ✅ |
239
- | **Container** | AWS::ECS::TaskDefinition | SDK Provider | ✅ |
240
- | **Container** | AWS::ECS::Service | SDK Provider | ✅ |
241
- | **Load Balancing** | AWS::ElasticLoadBalancingV2::LoadBalancer | SDK Provider | ✅ |
242
- | **Load Balancing** | AWS::ElasticLoadBalancingV2::TargetGroup | SDK Provider | ✅ |
243
- | **Load Balancing** | AWS::ElasticLoadBalancingV2::Listener | SDK Provider | ✅ |
244
- | **Database** | AWS::RDS::DBSubnetGroup | SDK Provider | ✅ |
245
- | **Database** | AWS::RDS::DBCluster | SDK Provider | ✅ |
246
- | **Database** | AWS::RDS::DBInstance | SDK Provider | ✅ |
247
- | **DNS** | AWS::Route53::HostedZone | SDK Provider | ✅ |
248
- | **DNS** | AWS::Route53::RecordSet | SDK Provider | ✅ |
249
- | **Security** | AWS::WAFv2::WebACL | SDK Provider | ✅ |
250
- | **Auth** | AWS::Cognito::UserPool | SDK Provider | ✅ |
251
- | **Cache** | AWS::ElastiCache::CacheCluster | SDK Provider | ✅ |
252
- | **Cache** | AWS::ElastiCache::SubnetGroup | SDK Provider | ✅ |
253
- | **Discovery** | AWS::ServiceDiscovery::PrivateDnsNamespace | SDK Provider | ✅ |
254
- | **Discovery** | AWS::ServiceDiscovery::Service | SDK Provider | ✅ |
255
- | **GraphQL** | AWS::AppSync::GraphQLApi | SDK Provider | ✅ |
256
- | **GraphQL** | AWS::AppSync::GraphQLSchema | SDK Provider | ✅ |
257
- | **GraphQL** | AWS::AppSync::DataSource | SDK Provider | ✅ |
258
- | **GraphQL** | AWS::AppSync::Resolver | SDK Provider | ✅ |
259
- | **GraphQL** | AWS::AppSync::ApiKey | SDK Provider | ✅ |
260
- | **Analytics** | AWS::Glue::Database | SDK Provider | ✅ |
261
- | **Analytics** | AWS::Glue::Table | SDK Provider | ✅ |
262
- | **Encryption** | AWS::KMS::Key | SDK Provider | ✅ |
263
- | **Encryption** | AWS::KMS::Alias | SDK Provider | ✅ |
264
- | **Streaming** | AWS::Kinesis::Stream | SDK Provider | ✅ |
265
- | **Streaming** | AWS::KinesisFirehose::DeliveryStream | SDK Provider | ✅ |
266
- | **Storage** | AWS::EFS::FileSystem | SDK Provider | ✅ |
267
- | **Storage** | AWS::EFS::MountTarget | SDK Provider | ✅ |
268
- | **Storage** | AWS::EFS::AccessPoint | SDK Provider | ✅ |
269
- | **Storage** | AWS::S3Express::DirectoryBucket | SDK Provider | ✅ |
270
- | **Storage** | AWS::S3Tables::TableBucket | SDK Provider | ✅ |
271
- | **Storage** | AWS::S3Tables::Namespace | SDK Provider | ✅ |
272
- | **Storage** | AWS::S3Tables::Table | SDK Provider | ✅ |
273
- | **Storage** | AWS::S3Vectors::VectorBucket | SDK Provider | ✅ |
274
- | **Audit** | AWS::CloudTrail::Trail | SDK Provider | ✅ |
275
- | **CI/CD** | AWS::CodeBuild::Project | SDK Provider | ✅ |
276
- | **AI/ML** | AWS::BedrockAgentCore::Runtime | SDK Provider | ✅ |
277
- | **Custom** | Custom::* (Lambda/SNS-backed) | SDK Provider | ✅ |
278
- | **Other** | All other resource types | Cloud Control | ✅ |
136
+ cdkd ships **90+ dedicated SDK Providers** (direct AWS SDK calls, no
137
+ polling overhead) covering the most-used services — IAM, Lambda, S3,
138
+ DynamoDB, EC2, RDS, ECS, API Gateway, CloudFront, Step Functions, EFS,
139
+ KMS, Cognito, AppSync, and more. **Any other CloudFormation resource
140
+ type** is handled via the Cloud Control API fallback (async polling).
141
+ Resource types not supported by either path fail at deploy time with a
142
+ clear error.
143
+
144
+ See **[docs/supported-resources.md](docs/supported-resources.md)** for
145
+ the full per-type table.
279
146
 
280
147
  ### Other Features
281
148
 
@@ -431,8 +298,8 @@ cdkd destroy --all --force
431
298
  cdkd force-unlock MyStack
432
299
 
433
300
  # Adopt already-deployed AWS resources into cdkd state.
434
- # See "Importing existing resources" below for the full guide (auto / selective /
435
- # hybrid modes, --resource overrides, --resource-mapping CDK CLI compatibility).
301
+ # See docs/import.md for the full guide (auto / selective / hybrid modes,
302
+ # --resource overrides, --resource-mapping CDK CLI compatibility).
436
303
  cdkd import MyStack --dry-run
437
304
  cdkd import MyStack --yes
438
305
 
@@ -503,76 +370,30 @@ cdkd state destroy MyStack --region us-east-1
503
370
  > `cdkd destroy` (synth-driven, deletes AWS resources + state) and
504
371
  > `cdkd state destroy` (state-driven, same effect) round out the matrix.
505
372
 
506
- ### Concurrency Options
507
-
508
- | Option | Default | Description |
509
- | --- | --- | --- |
510
- | `--concurrency` | 10 | Maximum concurrent resource operations per stack |
511
- | `--stack-concurrency` | 4 | Maximum concurrent stack deployments |
512
- | `--asset-publish-concurrency` | 8 | Maximum concurrent asset publish operations (S3 + ECR push) |
513
- | `--image-build-concurrency` | 4 | Maximum concurrent Docker image builds |
514
-
515
- ## `--no-wait`
373
+ ## `--no-wait`: skip async-resource waits
516
374
 
517
- By default, cdkd waits for async resources (CloudFront Distribution, RDS Cluster/Instance, ElastiCache) to reach a ready state before completing — the same behavior as CloudFormation.
518
-
519
- Use `--no-wait` to skip this and return immediately after resource creation:
375
+ CloudFront Distributions, RDS Clusters/Instances, and ElastiCache
376
+ typically take 3–15 minutes for AWS to fully propagate. By default
377
+ cdkd waits for them to reach a ready state the same behavior as
378
+ CloudFormation. Pass `--no-wait` to return as soon as the create call
379
+ returns:
520
380
 
521
381
  ```bash
522
382
  cdkd deploy --no-wait
523
383
  ```
524
384
 
525
- This can significantly speed up deployments with CloudFront (which takes 3-15 minutes to deploy to edge locations). The resource is fully functional once AWS finishes the async deployment.
526
-
527
- ## Per-resource timeout
528
-
529
- Both `cdkd deploy` and `cdkd destroy` (including `cdkd state destroy`) enforce a wall-clock deadline on every individual CREATE / UPDATE / DELETE so a stuck Cloud Control polling loop, hung Custom Resource handler, or slow ENI release cannot block the run forever.
530
-
531
- | Option | Default | Description |
532
- | --- | --- | --- |
533
- | `--resource-warn-after <duration_or_type=duration>` | `5m` | Warn when a single resource operation has been running longer than this. The live progress line is suffixed with `[taking longer than expected, Nm+]` and a `WARN` log line is emitted (printed above the live area in TTY mode, plain stderr otherwise). Repeatable. |
534
- | `--resource-timeout <duration_or_type=duration>` | `30m` | Abort a single resource operation that exceeds this. The deploy / destroy fails with `ResourceTimeoutError` (wrapped in `ProvisioningError`) and the existing rollback / state-preservation path runs. Repeatable. |
535
-
536
- Durations are written as `<number>s`, `<number>m`, or `<number>h` (e.g. `30s`, `90s`, `5m`, `1.5h`). Zero, negative, missing-unit, and unknown-unit values are rejected at parse time.
537
-
538
- Both flags accept either form on each invocation:
539
-
540
- - **Bare duration** (`30m`) sets the global default. The last bare value wins.
541
- - **`TYPE=DURATION`** (`AWS::CloudFront::Distribution=1h`) adds a per-resource-type override that supersedes the global default for that type only.
542
-
543
- `TYPE` must look like `AWS::Service::Resource`; malformed types are rejected at parse time. `warn < timeout` is enforced both globally and per-type — so `--resource-warn-after AWS::X=10m --resource-timeout AWS::X=5m` is a parse-time error.
544
-
545
- ```bash
546
- # Surface "still running" warnings sooner on a fast-feedback dev loop
547
- cdkd deploy --resource-warn-after 90s --resource-timeout 10m
548
-
549
- # Keep the global default tight, raise it only for resources known to take longer
550
- cdkd deploy \
551
- --resource-timeout 30m \
552
- --resource-timeout AWS::CloudFront::Distribution=1h \
553
- --resource-timeout AWS::RDS::DBCluster=1h30m
554
-
555
- # Force Custom Resources to abort earlier than their 1h self-reported polling cap
556
- cdkd deploy --resource-timeout AWS::CloudFormation::CustomResource=5m
557
- ```
558
-
559
- ### Why the default is 30m, not 1h
560
-
561
- cdkd's Custom Resource provider polls async handlers (`isCompleteHandler` pattern) for up to one hour before giving up. Setting the per-resource timeout to 1h by default would make a single hung non-CR resource hold the whole stack for an hour even though no other resource type ever needs more than a few minutes. The 30m global default catches stuck operations faster.
385
+ The resource is fully functional once AWS finishes the async
386
+ deployment in the background. CloudFormation has no equivalent — once
387
+ you submit a stack, you wait for everything.
562
388
 
563
- For Custom Resources specifically, the provider self-reports its 1h polling cap to the engine via the `getMinResourceTimeoutMs()` interface — the deploy engine resolves the per-resource budget as `max(provider self-report, --resource-timeout global)`, so CR resources get their full hour automatically without the user having to remember `--resource-timeout 1h`. To force CR to abort earlier than its self-reported cap, pass an explicit per-type override (`--resource-timeout AWS::CloudFormation::CustomResource=5m`). Per-type overrides always win over the provider's self-report — they're the documented escape hatch.
564
-
565
- The error message on timeout names the resource, type, region, elapsed time, and operation, and reminds you that long-running resources self-report their needed budget — when you see CR time out, the cause is genuinely the handler, not too-tight a default:
566
-
567
- ```text
568
- Resource MyBucket (AWS::S3::Bucket) in us-east-1 timed out after 30m during CREATE (elapsed 30m).
569
- This may indicate a stuck Cloud Control polling loop, hung Custom Resource, or
570
- slow ENI provisioning. Re-run with --resource-timeout AWS::S3::Bucket=<DURATION>
571
- to bump the budget for this resource type only, or --verbose to see the
572
- underlying provider activity.
573
- ```
389
+ ## Other CLI flags
574
390
 
575
- Note: `--resource-warn-after` must be less than `--resource-timeout`. Reversed values are rejected at parse time.
391
+ For concurrency knobs (`--concurrency`, `--stack-concurrency`,
392
+ `--asset-publish-concurrency`, `--image-build-concurrency`) and
393
+ per-resource timeout flags (`--resource-warn-after`,
394
+ `--resource-timeout` — including the per-resource-type override syntax
395
+ and the rationale for the 30m default), see
396
+ **[docs/cli-reference.md](docs/cli-reference.md)**.
576
397
 
577
398
  ## Example
578
399
 
@@ -602,397 +423,48 @@ LambdaStack
602
423
 
603
424
  Resources are dispatched as soon as their own dependencies complete (event-driven DAG). ServiceRole and Table run in parallel; DefaultPolicy starts the moment ServiceRole is done — without waiting for Table — and Handler starts the moment DefaultPolicy is done.
604
425
 
605
- ## Architecture
606
-
607
- Built on modern AWS tooling:
608
-
609
- - **Synthesis orchestration** - Executes CDK app as subprocess (synthesis itself is done by aws-cdk-lib), parses Cloud Assembly (manifest.json) directly, context provider loop (missing context → SDK lookup → re-synthesize)
610
- - **Self-implemented asset publisher** - S3 file upload with ZIP packaging (via `archiver`) and ECR Docker image publishing
611
- - **AWS SDK v3** - Direct resource provisioning
612
- - **Cloud Control API** - Fallback resource management for types without SDK Providers
613
- - **S3 Conditional Writes** - State locking via `If-None-Match`/`If-Match`
614
-
615
426
  ## Importing existing resources
616
427
 
617
- `cdkd import` adopts AWS resources that are already deployed (e.g. via
618
- `cdk deploy`, manual creation, or another tool) into cdkd state, so the
619
- next `cdkd deploy` updates them in-place instead of trying to CREATE
620
- duplicates.
621
-
622
- It reads the CDK app to find logical IDs, resource types, and
623
- dependencies, then matches each logical ID to a real AWS resource in
624
- one of three modes:
625
-
626
- All examples below assume cdkd reads the CDK app command from `cdk.json`
627
- (the typical case). Pass `--app "<command>"` only if you're running cdkd
628
- outside the CDK project directory or want to override `cdk.json`.
629
-
630
- ### Mode 1: auto (default — no flags)
428
+ `cdkd import` adopts AWS resources that are already deployed (via
429
+ `cdk deploy`, manual creation, or another tool) into cdkd state so the
430
+ next `cdkd deploy` updates them in-place instead of CREATEing duplicates.
631
431
 
632
432
  ```bash
633
- cdkd import MyStack
634
- ```
635
-
636
- Imports **every** resource in the synthesized template by tag. cdkd
637
- looks up each resource using its `aws:cdk:path` tag (which CDK
638
- automatically writes), so resources deployed by `cdk deploy` are found
639
- without any manual work. Useful for **adopting a whole stack** that was
640
- previously deployed by `cdk deploy`. This is cdkd's value-add over
641
- `cdk import` — CDK CLI does not have a tag-based bulk-import mode.
642
-
643
- ### Mode 2: selective (CDK CLI parity — when explicit overrides are given)
644
-
645
- ```bash
646
- # Import ONLY MyBucket; the other resources in the template are left alone.
647
- cdkd import MyStack --resource MyBucket=my-bucket-name
648
-
649
- # Several resources at once (--resource is repeatable).
650
- cdkd import MyStack \
651
- --resource MyBucket=my-bucket-name \
652
- --resource MyFn=my-function-name
653
-
654
- # CDK CLI compat: read overrides from a JSON file.
655
- cdkd import MyStack --resource-mapping mapping.json
656
- # mapping.json: { "MyBucket": "my-bucket-name", "MyFn": "my-function-name" }
657
-
658
- # CDK CLI compat: inline JSON (handy for non-TTY CI scripts).
659
- cdkd import MyStack --resource-mapping-inline '{"MyBucket":"my-bucket-name"}'
660
-
661
- # Capture cdkd's resolved logicalId→physicalId mapping for re-use.
662
- # Combine with --auto (or no flags) to record the tag-based lookups.
663
- cdkd import MyStack --record-resource-mapping ./mapping.json
664
- # mapping.json after the run: { "MyBucket": "my-bucket-name", ... }
665
- # Replay non-interactively in CI:
666
- cdkd import MyStack --resource-mapping ./mapping.json --yes
667
- ```
668
-
669
- When at least one `--resource` flag (or a `--resource-mapping` /
670
- `--resource-mapping-inline` payload) is supplied, **only the listed
671
- resources are imported**. Every other resource in the template is
672
- reported as `out of scope` and left out of state — the next `cdkd
673
- deploy` will treat them as new and CREATE them. This matches the
674
- semantics of `cdk import --resource-mapping` /
675
- `--resource-mapping-inline`. cdkd validates that every override key is
676
- a real logical ID in the template; a typo aborts the run rather than
677
- silently importing nothing. `--resource-mapping` and
678
- `--resource-mapping-inline` are mutually exclusive — pick one source.
679
-
680
- Use selective mode when you want to **adopt a few specific resources**
681
- out of a larger stack — for example, you have one S3 bucket that was
682
- created manually that you want cdkd to manage, while the rest of the
683
- stack will be deployed fresh.
684
-
685
- **Selective mode is non-destructive.** When state already exists for
686
- the stack, listed resources are **merged** into it: unlisted entries
687
- already in state are preserved (no `--force` needed). `--force` is
688
- only required when a listed override would overwrite a resource
689
- already in state — that's the one case where the merge is destructive.
690
- This is the right command for "I have a deployed stack and want to
691
- adopt one more resource into it":
433
+ # Adopt a whole stack previously deployed by cdk deploy (tag-based auto-lookup).
434
+ cdkd import MyStack --yes
692
435
 
693
- ```bash
694
- # Existing state has Queue + Topic; add Bucket without affecting them.
436
+ # Adopt only specific resources (CDK CLI parity).
695
437
  cdkd import MyStack --resource MyBucket=my-bucket-name
696
- # Resulting state: Queue + Topic (preserved) + Bucket (newly imported).
697
- ```
698
438
 
699
- ### Mode 3: hybrid (`--auto` with overrides)
700
-
701
- ```bash
702
- cdkd import MyStack \
703
- --resource MyBucket=my-bucket-name \
704
- --auto
705
- ```
706
-
707
- Listed resources use the explicit physical ID you supplied; **every
708
- other resource still goes through tag-based auto-import**. Useful when
709
- you have one resource whose tag-based lookup is unreliable (e.g. you
710
- deleted and re-created it without the tag) but you want cdkd to find
711
- the rest by tag automatically.
712
-
713
- ### Common flags
714
-
715
- | Flag | Purpose |
716
- | --- | --- |
717
- | `--dry-run` | Preview what would be imported. State is NOT written. |
718
- | `--yes` | Skip the confirmation prompt before writing state (and the CloudFormation retirement prompt under `--migrate-from-cloudformation`). |
719
- | `--force` | Confirm a destructive write to existing state — see below. |
720
- | `--migrate-from-cloudformation [name]` | After cdkd state is written, retire the source CloudFormation stack: inject `DeletionPolicy: Retain` + `UpdateReplacePolicy: Retain` on every resource via `UpdateStack`, then `DeleteStack`. AWS resources are NOT deleted. See [Migrating from `cdk deploy` (CloudFormation) to cdkd](#migrating-from-cdk-deploy-cloudformation-to-cdkd) below. |
721
-
722
- `--force` is only needed when the import would lose data:
723
-
724
- - **Auto / whole-stack mode + existing state**: required. The resource
725
- map is rebuilt from the template, so any state entry not re-imported
726
- is dropped.
727
- - **Selective mode + listed override already in state**: required.
728
- The listed entry is overwritten with the new physical id.
729
- - **Selective mode without a conflict (pure merge)**: not required.
730
- Unlisted state entries are preserved automatically.
731
- - **No existing state (first-time import)**: not required.
732
-
733
- ### Migrating from `cdk deploy` (CloudFormation) to cdkd
734
-
735
- If a stack was previously deployed via `cdk deploy` (and is therefore
736
- managed by CloudFormation), `cdkd import --migrate-from-cloudformation` adopts
737
- the resources into cdkd state AND retires the source CloudFormation
738
- stack in one go:
739
-
740
- ```bash
439
+ # Migrate off CloudFormation in one shot — adopt + retire the source CFn stack.
741
440
  cdkd import MyStack --migrate-from-cloudformation --yes
742
441
  ```
743
442
 
744
- No `--resource <id>=<physical>` flags are needed cdkd recovers each
745
- resource's physical id directly from CloudFormation via
746
- `DescribeStackResources`, so it works for both `cdk deploy`-managed and
747
- `cdkd deploy`-managed stacks. (cdkd's tag-based auto-lookup can't help
748
- here: upstream `cdk deploy` doesn't propagate the `aws:cdk:path` template
749
- metadata as a real AWS tag, and AWS reserves the `aws:` tag prefix so
750
- neither cdkd nor a CFn `UpdateStack` can add it on the way through.)
751
-
752
- The flow:
753
-
754
- 1. `DescribeStackResources` — ask CloudFormation for every
755
- `(LogicalResourceId, PhysicalResourceId)` pair in the source stack.
756
- These are merged into the import overrides; user-supplied
757
- `--resource <id>=<physical>` flags take precedence over CFn's view.
758
- 2. `cdkd import` runs and adopts every resource into cdkd state via
759
- each provider's `import()` method, using the CFn-resolved physical
760
- ids as direct lookups.
761
- 3. `cdkd` writes state.
762
- 4. `DescribeStacks` + `GetTemplate` + `UpdateStack` to inject
763
- `DeletionPolicy: Retain` and `UpdateReplacePolicy: Retain` on every
764
- resource — a metadata-only update.
765
- 5. `DeleteStack` — every resource is now `Retain`, so CloudFormation
766
- walks the stack and skips every resource. The stack record disappears;
767
- the underlying AWS resources are left intact and are now solely
768
- managed by cdkd.
769
-
770
- Steps 1–5 all run inside the same lock so a concurrent `cdkd deploy`
771
- cannot race the in-flight migration.
772
-
773
- By default the CloudFormation stack name is taken from the cdkd stack
774
- name (the typical case — CDK uses the synthesized stack name as the CFn
775
- stack name). Pass an explicit value when the names differ:
776
-
777
- ```bash
778
- cdkd import MyStack --migrate-from-cloudformation LegacyCfnStackName --yes
779
- ```
780
-
781
- Limitations:
782
-
783
- - **JSON-only.** The Retain-policy injection in step 4 targets the CDK-
784
- generated JSON template. Hand-written YAML CFn stacks fail with a
785
- clear error; retire them manually.
786
- - **51,200-byte template limit.** The modified template is submitted
787
- inline via `TemplateBody`. Stacks whose modified template exceeds
788
- this limit fail in step 4 with a clear error pointing to the manual
789
- 3-step procedure (S3-backed `TemplateURL` fallback is a planned
790
- follow-up). cdkd state has already been written at that point, so
791
- re-runs and manual cleanup are both supported.
792
- - **Not compatible with `--dry-run`.** The post-state-write
793
- `UpdateStack` + `DeleteStack` are real side-effects and cannot be
794
- faithfully simulated. Use plain `cdkd import --dry-run` to preview
795
- per-resource import outcomes.
796
- - **Partial imports leave unmanaged resources.** If a resource cannot
797
- be imported (no provider, AWS not-found, etc.), `DeleteStack` skips
798
- it (Retain) and cdkd never wrote it into state — so the resource
799
- exists in AWS but unmanaged by both CloudFormation and cdkd. cdkd
800
- warns loudly when this happens; either re-import the missing
801
- resources first or accept the orphaning intentionally.
802
-
803
- ### After import
804
-
805
- Run `cdkd diff` to see how the imported state lines up with the
806
- template. If the resource's actual properties differ from the template,
807
- the next `cdkd deploy` will UPDATE them to match. If you imported only
808
- some resources (selective mode), the remaining template resources
809
- appear as `to create` in the diff.
810
-
811
- ### Provider support
812
-
813
- Tag-based auto-lookup is implemented for the most-used resource types
814
- (S3 Bucket, Lambda Function, IAM Role, SNS Topic, SQS Queue, DynamoDB
815
- Table, Logs LogGroup, EventBridge EventBus, KMS Key/Alias, Secrets
816
- Manager Secret, SSM Parameter, EC2 VPC/Subnet/SecurityGroup, RDS,
817
- ECS Cluster/Service/TaskDefinition, CloudFront Distribution, Cognito
818
- User Pool — the full list is in [CLAUDE.md](CLAUDE.md)). For resource
819
- types without auto-lookup support (ApiGateway sub-resources, niche
820
- services, anything in Cloud Control API), use the explicit
821
- `--resource <id>=<physicalId>` override mode — selective mode handles
822
- exactly this case. Resource types whose provider does not implement
823
- import are reported as `unsupported` and skipped.
824
-
825
- ### `cdkd import` vs upstream `cdk import`
826
-
827
- cdkd's `import` command mirrors the surface of upstream
828
- [`cdk import`](https://docs.aws.amazon.com/cdk/v2/guide/ref-cli-cmd-import.html)
829
- where it can, but the underlying mechanism is fundamentally different
830
- and a handful of upstream-only flags are not implemented. Use this
831
- table to predict behavior when migrating from `cdk import`.
832
-
833
- | Topic | `cdk import` (upstream) | `cdkd import` |
834
- | --- | --- | --- |
835
- | Mechanism | CloudFormation `CreateChangeSet` with `ResourcesToImport` — atomic, all-or-nothing. | Per-resource SDK calls (e.g. `s3:HeadBucket`, `lambda:GetFunction`, IAM `ListRoleTags`). **Not atomic.** |
836
- | Failure mode | Failed import rolls the changeset back; the stack is left unchanged. | Per-resource: `imported` / `skipped-not-found` / `skipped-no-impl` / `skipped-out-of-scope` / `failed` rows are summarized. State is written for whatever succeeded — but only after a confirmation prompt (or `--yes`), so a partial run is opt-in. To roll a partial import back, use `cdkd state orphan <stack>` (drops the state record only). |
837
- | Selective mode (`--resource-mapping <file>`) | Supported. Listed resources are imported; unlisted resources cause the changeset to fail. | Supported. Listed resources are imported; unlisted resources are reported as `out of scope` and left out of state (next `cdkd deploy` will CREATE them). |
838
- | Selective mode (`--resource <id>=<physical>` repeatable) | Not supported (upstream uses interactive prompts or a mapping file). | Supported as cdkd's CLI-friendly equivalent. |
839
- | `--resource-mapping-inline '<json>'` | Supported (use in non-TTY environments). | Supported. Same shape as `--resource-mapping <file>` but supplied as a string — useful for non-TTY CI scripts that do not want a separate file. Mutually exclusive with `--resource-mapping`. |
840
- | `--record-resource-mapping <file>` | Supported (writes the mapping the user typed at the prompt to a file for re-use). | Supported. Writes the resolved `{logicalId: physicalId}` map (covers explicit overrides AND cdkd's tag-based auto-lookup) to the file before the confirmation prompt. The file is produced even if the user says "no" or under `--dry-run`, so the resolved data is never thrown away. |
841
- | Interactive prompt for missing IDs | Default in TTY — prompts for every resource not covered by a mapping file. | **Not supported.** cdkd is non-interactive: missing logical IDs are looked up by `aws:cdk:path` tag in `auto` / `hybrid` modes, or skipped as `out of scope` in selective mode. The only prompt is the final "write state?" confirmation, which `--yes` skips. |
842
- | Typo'd logical ID | Aborts with a clear error before any AWS calls. | Aborts with a clear error before any AWS calls — checked against the synthesized template. |
843
- | Whole-stack tag-based import | **Not supported.** | **cdkd-specific.** With no flags, cdkd looks every resource up by its `aws:cdk:path` tag — the typical case for adopting a stack previously deployed by `cdk deploy`. |
844
- | Hybrid mode (overrides + tag fallback) | **Not supported.** | **cdkd-specific.** `--auto` together with `--resource` lets listed resources use the explicit physical id while everything else still goes through tag lookup. |
845
- | Nested stacks (`AWS::CloudFormation::Stack`) | Explicitly unsupported. | Also unsupported in practice — cdkd does not deploy nested CloudFormation stacks at all (no `AWS::CloudFormation::Stack` provider). The `Stack` resource itself would be reported as `unsupported`. CDK Stages (separate top-level stacks) are fine: pass the stack's display path or physical name as the positional argument. |
846
- | Bootstrap requirement | Bootstrap v12+ (deploy role needs to read the encrypted staging bucket). | cdkd's own state bucket; no CDK bootstrap version requirement. |
847
- | Resource-type coverage | Whatever [CloudFormation supports for import](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/resource-import-supported-resources.html). | The set of cdkd providers that implement `import()` (see [CLAUDE.md](CLAUDE.md) for the current list). For any other CC-API-supported type, use `--resource <id>=<physical>` to drive the Cloud Control API fallback. The two lists overlap heavily but are not identical. |
848
- | Confirmation prompt before writing state | n/a (CloudFormation operates atomically). | Yes — cdkd asks before writing the state file. Skip with `--yes`. |
849
- | `--force` | "Continue even if the diff includes updates or deletions" — about diff strictness. | "Confirm a destructive write to existing state" — required for auto/whole-stack rebuild and for overwriting a listed entry already in state; not required for a pure selective merge. **Same flag name, different meaning.** |
850
- | `--dry-run` | Implied by `--no-execute` (creates the changeset without executing). | Native: shows the import plan and exits without writing state. |
851
-
852
- #### Practical implications when migrating from `cdk import`
853
-
854
- - If you script around `--resource-mapping <file>`: behavior matches.
855
- The file format (`{"LogicalId": "physical-id"}`) is the same.
856
- - If you script around `--resource-mapping-inline`: behavior matches.
857
- The JSON shape is the same as `--resource-mapping <file>`.
858
- - If you script around `--record-resource-mapping <file>`: behavior
859
- matches. cdkd writes the resolved `{logicalId: physicalId}` map to
860
- the file before the confirmation prompt — and even if the user says
861
- "no" or under `--dry-run` — so you can capture cdkd's tag-based
862
- auto-lookup result and replay it via `--resource-mapping` in CI.
863
- - If your workflow relies on the interactive prompt: rewrite as
864
- `--resource-mapping <file>`. cdkd will not prompt.
865
- - If you rely on atomic rollback: cdkd cannot offer that — its
866
- per-resource model writes state only after the full pass completes
867
- (and after confirmation), so a partial run is bounded, but if a
868
- later resource fails after several earlier ones already returned
869
- successfully and you confirm the write, those earlier ones are
870
- in cdkd state. Use `cdkd state orphan <stack>` to back out.
871
- - If you import nested stacks: neither tool supports this. Convert
872
- to top-level CDK stacks first.
443
+ See **[docs/import.md](docs/import.md)** for the full guide: three import
444
+ modes (auto / selective / hybrid), `--resource-mapping` CDK CLI
445
+ compatibility, CloudFormation migration flow, provider coverage, and the
446
+ parity matrix vs upstream `cdk import`.
873
447
 
874
448
  ## State Management
875
449
 
876
- State is stored in S3. Keys are scoped by `(stackName, region)` so the same
877
- stack name deployed to two regions has two independent state files:
878
-
879
- ```
880
- s3://{state-bucket}/
881
- └── {prefix}/ # Default: "cdkd" (configurable via --state-prefix)
882
- ├── MyStack/
883
- │ └── us-east-1/
884
- │ ├── state.json # Resource state (version: 2)
885
- │ └── lock.json # Exclusive deploy lock
886
- └── AnotherStack/
887
- ├── us-east-1/
888
- │ ├── state.json
889
- │ └── lock.json
890
- └── us-west-2/ # same stackName, different region
891
- ├── state.json
892
- └── lock.json
893
- ```
894
-
895
- > **Caveat: same `stackName` in multiple regions becomes visible after
896
- > `env.region` changes.** Before this layout shipped, changing a stack's
897
- > `env.region` between deploys silently overwrote the prior region's state
898
- > and `cdkd destroy` ran against the wrong region. cdkd now treats the two
899
- > regions as independent. Use `cdkd state list` to see both, and
900
- > `cdkd state orphan <stack> --stack-region <region>` to prune one without
901
- > touching the other.
902
- >
903
- > **Legacy key-layout migration (within the same bucket):** state files
904
- > written by cdkd before this layout (`version: 1`, flat
905
- > `cdkd/{stackName}/state.json`) are still readable. The next cdkd write
906
- > auto-migrates to the new region-prefixed key
907
- > (`cdkd/{stackName}/{region}/state.json`) and removes the legacy file —
908
- > no manual action required. An older cdkd binary reading a `version: 2`
909
- > file fails with a clear "upgrade cdkd" error rather than silently
910
- > mishandling it.
911
- >
912
- > Note: this only covers the **key layout inside an existing state
913
- > bucket**. The separate **bucket-name migration** (legacy
914
- > `cdkd-state-{accountId}-{region}` → new `cdkd-state-{accountId}`)
915
- > is described below and does NOT auto-migrate.
916
-
917
- ### Bucket migration
918
-
919
- The default state-bucket name changed in v0.11.0 from the region-suffixed
920
- `cdkd-state-{accountId}-{region}` to the region-free
921
- `cdkd-state-{accountId}`. The bucket name is region-free because S3 names
922
- are globally unique, so teammates with different profile regions all
923
- converge on the same bucket; the bucket's actual region is auto-detected
924
- via `GetBucketLocation`.
925
-
926
- Existing users keep working without doing anything: when only the legacy
927
- bucket exists, cdkd transparently falls back to it and emits a
928
- deprecation warning. To stop the warning (and consolidate state into the
929
- new bucket) run:
930
-
931
- ```bash
932
- # Per-region: copies all objects from cdkd-state-{accountId}-{region}
933
- # into cdkd-state-{accountId}. Source bucket is kept by default.
934
- cdkd state migrate --region us-east-1
935
-
936
- # Optional: delete the legacy bucket once the copy is verified.
937
- cdkd state migrate --region us-east-1 --remove-legacy
938
- ```
939
-
940
- This migration is **account-wide / per-region**, not per-stack — running
941
- it once per region clears the legacy bucket for that region in one shot.
942
- For multi-region accounts, run it once per region (each invocation copies
943
- into the same destination bucket).
944
-
945
- `cdkd state migrate` refuses to run while any stack has an active
946
- `lock.json` (an in-flight `cdkd deploy` / `destroy` would race the copy),
947
- verifies object-count parity between source and destination before any
948
- source cleanup, and only deletes the legacy bucket when
949
- `--remove-legacy` is passed.
950
-
951
- See the [Configuration](#configuration) table below for the full
952
- precedence rules of the `--state-bucket` flag and its env-var / cdk.json
953
- fallbacks.
954
-
955
- ### Configuration
450
+ State is stored in S3 with optimistic locking via S3 Conditional Writes
451
+ (no DynamoDB required). Keys are scoped by `(stackName, region)` so the
452
+ same stack deployed to two regions has two independent state files.
956
453
 
957
454
  | Setting | CLI | cdk.json | Env var | Default |
958
455
  |---------|-----|----------|---------|---------|
959
- | Bucket | `--state-bucket` | `context.cdkd.stateBucket` | `CDKD_STATE_BUCKET` | `cdkd-state-{accountId}` (legacy `cdkd-state-{accountId}-{region}` is still read with a deprecation warning) |
456
+ | Bucket | `--state-bucket` | `context.cdkd.stateBucket` | `CDKD_STATE_BUCKET` | `cdkd-state-{accountId}` (legacy `cdkd-state-{accountId}-{region}` is still read with a deprecation warning — run `cdkd state migrate` to consolidate) |
960
457
  | Prefix | `--state-prefix` | - | - | `cdkd` |
961
458
 
962
- ### Multi-app isolation
963
-
964
- The state bucket is shared across all CDK apps in the same account/region by default. To isolate apps, use different prefixes:
965
-
966
- ```bash
967
- # App A
968
- cdkd deploy --state-prefix app-a
969
-
970
- # App B
971
- cdkd deploy --state-prefix app-b
972
- ```
973
-
974
- > **Note**: `cdkd destroy --all` only targets stacks from the current CDK app (determined by synthesis), not all stacks in the bucket.
459
+ The state bucket is shared across all CDK apps in the same account by
460
+ default. To isolate apps, pass different `--state-prefix` values.
461
+ `cdkd destroy --all` only targets stacks from the current CDK app
462
+ (determined by synthesis), not all stacks in the bucket.
975
463
 
976
- State schema:
977
-
978
- ```typescript
979
- {
980
- version: 2,
981
- stackName: "MyStack",
982
- region: "us-east-1",
983
- resources: {
984
- "MyFunction": {
985
- physicalId: "arn:aws:lambda:...",
986
- resourceType: "AWS::Lambda::Function",
987
- properties: { ... },
988
- attributes: { Arn: "...", ... }, // For Fn::GetAtt
989
- dependencies: ["MyBucket"] // For proper deletion order
990
- }
991
- },
992
- outputs: { ... },
993
- lastModified: 1234567890
994
- }
995
- ```
464
+ See **[docs/state-management.md](docs/state-management.md)** for the full
465
+ spec: S3 key layout, optimistic-locking mechanism (ETag-based), state
466
+ schema, legacy `version: 1` migration, bucket-name migration via
467
+ `cdkd state migrate`, and troubleshooting.
996
468
 
997
469
  ## Stack Outputs
998
470
 
@@ -1022,19 +494,6 @@ After deployment, outputs are resolved and saved to the S3 state file:
1022
494
  - cdkd: Outputs saved in S3 state file (e.g., `s3://bucket/cdkd/MyStack/us-east-1/state.json`)
1023
495
  - Both resolve intrinsic functions (Ref, Fn::GetAtt, etc.) to actual values
1024
496
 
1025
- ## Testing
1026
-
1027
- - Unit tests covering all layers
1028
- - Integration examples verified with real AWS deployments (see `tests/integration/`)
1029
- - E2E test script for automated deploy/diff/update/destroy cycles
1030
-
1031
- ```bash
1032
- pnpm test # Run unit tests
1033
- pnpm run test:coverage # With coverage report
1034
- ```
1035
-
1036
- See [docs/testing.md](docs/testing.md) for integration and E2E testing instructions.
1037
-
1038
497
  ## License
1039
498
 
1040
499
  Apache 2.0