@go-to-k/cdkd 0.28.2 → 0.30.0

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
 
@@ -161,6 +113,7 @@ Reproduce with `./tests/benchmark/run-benchmark.sh all`. See [tests/benchmark/RE
161
113
  | `Fn::Or` | ✅ Supported | Logical OR (2-10 conditions) |
162
114
  | `Fn::Not` | ✅ Supported | Logical NOT |
163
115
  | `Fn::ImportValue` | ✅ Supported | Cross-stack references via S3 state |
116
+ | `Fn::GetStackOutput` | ✅ Supported (same-account) | Cross-stack / cross-region output reference via S3 state. Cross-account `RoleArn` is rejected with a clear error (not yet implemented). |
164
117
  | `Fn::FindInMap` | ✅ Supported | Mapping lookup |
165
118
  | `Fn::GetAZs` | ✅ Supported | Availability Zone list |
166
119
  | `Fn::Base64` | ✅ Supported | Base64 encoding |
@@ -180,101 +133,16 @@ Reproduce with `./tests/benchmark/run-benchmark.sh all`. See [tests/benchmark/RE
180
133
 
181
134
  ### Resource Provisioning
182
135
 
183
- | Category | Resource Type | Provider | Status |
184
- |----------|--------------|----------|--------|
185
- | **IAM** | AWS::IAM::Role | SDK Provider | |
186
- | **IAM** | AWS::IAM::Policy | SDK Provider | ✅ |
187
- | **IAM** | AWS::IAM::InstanceProfile | SDK Provider | |
188
- | **IAM** | AWS::IAM::User | SDK Provider | |
189
- | **IAM** | AWS::IAM::Group | SDK Provider | ✅ |
190
- | **IAM** | AWS::IAM::UserToGroupAddition | SDK Provider | ✅ |
191
- | **Storage** | AWS::S3::Bucket | SDK Provider | ✅ |
192
- | **Storage** | AWS::S3::BucketPolicy | SDK Provider | ✅ |
193
- | **Messaging** | AWS::SQS::Queue | SDK Provider | ✅ |
194
- | **Messaging** | AWS::SQS::QueuePolicy | SDK Provider | ✅ |
195
- | **Messaging** | AWS::SNS::Topic | SDK Provider | ✅ |
196
- | **Messaging** | AWS::SNS::Subscription | SDK Provider | ✅ |
197
- | **Messaging** | AWS::SNS::TopicPolicy | SDK Provider | ✅ |
198
- | **Compute** | AWS::Lambda::Function | SDK Provider | ✅ |
199
- | **Compute** | AWS::Lambda::Permission | SDK Provider | ✅ |
200
- | **Compute** | AWS::Lambda::Url | SDK Provider | ✅ |
201
- | **Compute** | AWS::Lambda::EventSourceMapping | SDK Provider | ✅ |
202
- | **Compute** | AWS::Lambda::LayerVersion | SDK Provider | ✅ |
203
- | **Database** | AWS::DynamoDB::Table | SDK Provider | ✅ |
204
- | **Monitoring** | AWS::Logs::LogGroup | SDK Provider | ✅ |
205
- | **Monitoring** | AWS::CloudWatch::Alarm | SDK Provider | ✅ |
206
- | **Secrets** | AWS::SecretsManager::Secret | SDK Provider | ✅ |
207
- | **Config** | AWS::SSM::Parameter | SDK Provider | ✅ |
208
- | **Events** | AWS::Events::Rule | SDK Provider | ✅ |
209
- | **Events** | AWS::Events::EventBus | SDK Provider | ✅ |
210
- | **Networking** | AWS::EC2::VPC | SDK Provider | ✅ |
211
- | **Networking** | AWS::EC2::Subnet | SDK Provider | ✅ |
212
- | **Networking** | AWS::EC2::InternetGateway | SDK Provider | ✅ |
213
- | **Networking** | AWS::EC2::VPCGatewayAttachment | SDK Provider | ✅ |
214
- | **Networking** | AWS::EC2::RouteTable | SDK Provider | ✅ |
215
- | **Networking** | AWS::EC2::Route | SDK Provider | ✅ |
216
- | **Networking** | AWS::EC2::SubnetRouteTableAssociation | SDK Provider | ✅ |
217
- | **Networking** | AWS::EC2::SecurityGroup | SDK Provider | ✅ |
218
- | **Networking** | AWS::EC2::SecurityGroupIngress | SDK Provider | ✅ |
219
- | **Networking** | AWS::EC2::NetworkAcl | SDK Provider | ✅ |
220
- | **Networking** | AWS::EC2::NetworkAclEntry | SDK Provider | ✅ |
221
- | **Networking** | AWS::EC2::SubnetNetworkAclAssociation | SDK Provider | ✅ |
222
- | **Compute** | AWS::EC2::Instance | SDK Provider | ✅ |
223
- | **API Gateway** | AWS::ApiGateway::Account | SDK Provider | ✅ |
224
- | **API Gateway** | AWS::ApiGateway::Resource | SDK Provider | ✅ |
225
- | **API Gateway** | AWS::ApiGateway::Deployment | SDK Provider | ✅ |
226
- | **API Gateway** | AWS::ApiGateway::Stage | SDK Provider | ✅ |
227
- | **API Gateway** | AWS::ApiGateway::Method | SDK Provider | ✅ |
228
- | **API Gateway** | AWS::ApiGateway::Authorizer | SDK Provider | ✅ |
229
- | **API Gateway** | AWS::ApiGatewayV2::Api | SDK Provider | ✅ |
230
- | **API Gateway** | AWS::ApiGatewayV2::Stage | SDK Provider | ✅ |
231
- | **API Gateway** | AWS::ApiGatewayV2::Integration | SDK Provider | ✅ |
232
- | **API Gateway** | AWS::ApiGatewayV2::Route | SDK Provider | ✅ |
233
- | **API Gateway** | AWS::ApiGatewayV2::Authorizer | SDK Provider | ✅ |
234
- | **CDN** | AWS::CloudFront::CloudFrontOriginAccessIdentity | SDK Provider | ✅ |
235
- | **CDN** | AWS::CloudFront::Distribution | SDK Provider | ✅ |
236
- | **Orchestration** | AWS::StepFunctions::StateMachine | SDK Provider | ✅ |
237
- | **Container** | AWS::ECS::Cluster | SDK Provider | ✅ |
238
- | **Container** | AWS::ECS::TaskDefinition | SDK Provider | ✅ |
239
- | **Container** | AWS::ECS::Service | SDK Provider | ✅ |
240
- | **Load Balancing** | AWS::ElasticLoadBalancingV2::LoadBalancer | SDK Provider | ✅ |
241
- | **Load Balancing** | AWS::ElasticLoadBalancingV2::TargetGroup | SDK Provider | ✅ |
242
- | **Load Balancing** | AWS::ElasticLoadBalancingV2::Listener | SDK Provider | ✅ |
243
- | **Database** | AWS::RDS::DBSubnetGroup | SDK Provider | ✅ |
244
- | **Database** | AWS::RDS::DBCluster | SDK Provider | ✅ |
245
- | **Database** | AWS::RDS::DBInstance | SDK Provider | ✅ |
246
- | **DNS** | AWS::Route53::HostedZone | SDK Provider | ✅ |
247
- | **DNS** | AWS::Route53::RecordSet | SDK Provider | ✅ |
248
- | **Security** | AWS::WAFv2::WebACL | SDK Provider | ✅ |
249
- | **Auth** | AWS::Cognito::UserPool | SDK Provider | ✅ |
250
- | **Cache** | AWS::ElastiCache::CacheCluster | SDK Provider | ✅ |
251
- | **Cache** | AWS::ElastiCache::SubnetGroup | SDK Provider | ✅ |
252
- | **Discovery** | AWS::ServiceDiscovery::PrivateDnsNamespace | SDK Provider | ✅ |
253
- | **Discovery** | AWS::ServiceDiscovery::Service | SDK Provider | ✅ |
254
- | **GraphQL** | AWS::AppSync::GraphQLApi | SDK Provider | ✅ |
255
- | **GraphQL** | AWS::AppSync::GraphQLSchema | SDK Provider | ✅ |
256
- | **GraphQL** | AWS::AppSync::DataSource | SDK Provider | ✅ |
257
- | **GraphQL** | AWS::AppSync::Resolver | SDK Provider | ✅ |
258
- | **GraphQL** | AWS::AppSync::ApiKey | SDK Provider | ✅ |
259
- | **Analytics** | AWS::Glue::Database | SDK Provider | ✅ |
260
- | **Analytics** | AWS::Glue::Table | SDK Provider | ✅ |
261
- | **Encryption** | AWS::KMS::Key | SDK Provider | ✅ |
262
- | **Encryption** | AWS::KMS::Alias | SDK Provider | ✅ |
263
- | **Streaming** | AWS::Kinesis::Stream | SDK Provider | ✅ |
264
- | **Streaming** | AWS::KinesisFirehose::DeliveryStream | SDK Provider | ✅ |
265
- | **Storage** | AWS::EFS::FileSystem | SDK Provider | ✅ |
266
- | **Storage** | AWS::EFS::MountTarget | SDK Provider | ✅ |
267
- | **Storage** | AWS::EFS::AccessPoint | SDK Provider | ✅ |
268
- | **Storage** | AWS::S3Express::DirectoryBucket | SDK Provider | ✅ |
269
- | **Storage** | AWS::S3Tables::TableBucket | SDK Provider | ✅ |
270
- | **Storage** | AWS::S3Tables::Namespace | SDK Provider | ✅ |
271
- | **Storage** | AWS::S3Tables::Table | SDK Provider | ✅ |
272
- | **Storage** | AWS::S3Vectors::VectorBucket | SDK Provider | ✅ |
273
- | **Audit** | AWS::CloudTrail::Trail | SDK Provider | ✅ |
274
- | **CI/CD** | AWS::CodeBuild::Project | SDK Provider | ✅ |
275
- | **AI/ML** | AWS::BedrockAgentCore::Runtime | SDK Provider | ✅ |
276
- | **Custom** | Custom::* (Lambda/SNS-backed) | SDK Provider | ✅ |
277
- | **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.
278
146
 
279
147
  ### Other Features
280
148
 
@@ -283,6 +151,7 @@ Reproduce with `./tests/benchmark/run-benchmark.sh all`. See [tests/benchmark/RE
283
151
  | CloudFormation Parameters | ✅ | Default values, type coercion |
284
152
  | Conditions | ✅ | With logical operators |
285
153
  | Cross-stack references | ✅ | Via `Fn::ImportValue` + S3 state |
154
+ | Cross-region references | ✅ (same-account) | Via `Fn::GetStackOutput` + S3 state. Cross-account `RoleArn` not yet implemented. |
286
155
  | JSON Patch updates | ✅ | RFC 6902, minimal patches |
287
156
  | Resource replacement detection | ✅ | 10+ resource types |
288
157
  | Dynamic References | ✅ | `{{resolve:secretsmanager:...}}`, `{{resolve:ssm:...}}` |
@@ -429,8 +298,8 @@ cdkd destroy --all --force
429
298
  cdkd force-unlock MyStack
430
299
 
431
300
  # Adopt already-deployed AWS resources into cdkd state.
432
- # See "Importing existing resources" below for the full guide (auto / selective /
433
- # 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).
434
303
  cdkd import MyStack --dry-run
435
304
  cdkd import MyStack --yes
436
305
 
@@ -501,76 +370,30 @@ cdkd state destroy MyStack --region us-east-1
501
370
  > `cdkd destroy` (synth-driven, deletes AWS resources + state) and
502
371
  > `cdkd state destroy` (state-driven, same effect) round out the matrix.
503
372
 
504
- ### Concurrency Options
505
-
506
- | Option | Default | Description |
507
- | --- | --- | --- |
508
- | `--concurrency` | 10 | Maximum concurrent resource operations per stack |
509
- | `--stack-concurrency` | 4 | Maximum concurrent stack deployments |
510
- | `--asset-publish-concurrency` | 8 | Maximum concurrent asset publish operations (S3 + ECR push) |
511
- | `--image-build-concurrency` | 4 | Maximum concurrent Docker image builds |
512
-
513
- ## `--no-wait`
373
+ ## `--no-wait`: skip async-resource waits
514
374
 
515
- 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.
516
-
517
- 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:
518
380
 
519
381
  ```bash
520
382
  cdkd deploy --no-wait
521
383
  ```
522
384
 
523
- 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.
524
-
525
- ## Per-resource timeout
526
-
527
- 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.
528
-
529
- | Option | Default | Description |
530
- | --- | --- | --- |
531
- | `--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. |
532
- | `--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. |
533
-
534
- 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.
535
-
536
- Both flags accept either form on each invocation:
537
-
538
- - **Bare duration** (`30m`) sets the global default. The last bare value wins.
539
- - **`TYPE=DURATION`** (`AWS::CloudFront::Distribution=1h`) adds a per-resource-type override that supersedes the global default for that type only.
540
-
541
- `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.
542
-
543
- ```bash
544
- # Surface "still running" warnings sooner on a fast-feedback dev loop
545
- cdkd deploy --resource-warn-after 90s --resource-timeout 10m
546
-
547
- # Keep the global default tight, raise it only for resources known to take longer
548
- cdkd deploy \
549
- --resource-timeout 30m \
550
- --resource-timeout AWS::CloudFront::Distribution=1h \
551
- --resource-timeout AWS::RDS::DBCluster=1h30m
552
-
553
- # Force Custom Resources to abort earlier than their 1h self-reported polling cap
554
- cdkd deploy --resource-timeout AWS::CloudFormation::CustomResource=5m
555
- ```
556
-
557
- ### Why the default is 30m, not 1h
558
-
559
- 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.
560
388
 
561
- 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.
389
+ ## Other CLI flags
562
390
 
563
- 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:
564
-
565
- ```text
566
- Resource MyBucket (AWS::S3::Bucket) in us-east-1 timed out after 30m during CREATE (elapsed 30m).
567
- This may indicate a stuck Cloud Control polling loop, hung Custom Resource, or
568
- slow ENI provisioning. Re-run with --resource-timeout AWS::S3::Bucket=<DURATION>
569
- to bump the budget for this resource type only, or --verbose to see the
570
- underlying provider activity.
571
- ```
572
-
573
- 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)**.
574
397
 
575
398
  ## Example
576
399
 
@@ -600,326 +423,48 @@ LambdaStack
600
423
 
601
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.
602
425
 
603
- ## Architecture
604
-
605
- Built on modern AWS tooling:
606
-
607
- - **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)
608
- - **Self-implemented asset publisher** - S3 file upload with ZIP packaging (via `archiver`) and ECR Docker image publishing
609
- - **AWS SDK v3** - Direct resource provisioning
610
- - **Cloud Control API** - Fallback resource management for types without SDK Providers
611
- - **S3 Conditional Writes** - State locking via `If-None-Match`/`If-Match`
612
-
613
426
  ## Importing existing resources
614
427
 
615
- `cdkd import` adopts AWS resources that are already deployed (e.g. via
616
- `cdk deploy`, manual creation, or another tool) into cdkd state, so the
617
- next `cdkd deploy` updates them in-place instead of trying to CREATE
618
- duplicates.
619
-
620
- It reads the CDK app to find logical IDs, resource types, and
621
- dependencies, then matches each logical ID to a real AWS resource in
622
- one of three modes:
623
-
624
- All examples below assume cdkd reads the CDK app command from `cdk.json`
625
- (the typical case). Pass `--app "<command>"` only if you're running cdkd
626
- outside the CDK project directory or want to override `cdk.json`.
627
-
628
- ### Mode 1: auto (default — no flags)
629
-
630
- ```bash
631
- cdkd import MyStack
632
- ```
633
-
634
- Imports **every** resource in the synthesized template by tag. cdkd
635
- looks up each resource using its `aws:cdk:path` tag (which CDK
636
- automatically writes), so resources deployed by `cdk deploy` are found
637
- without any manual work. Useful for **adopting a whole stack** that was
638
- previously deployed by `cdk deploy`. This is cdkd's value-add over
639
- `cdk import` — CDK CLI does not have a tag-based bulk-import mode.
640
-
641
- ### Mode 2: selective (CDK CLI parity — when explicit overrides are given)
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.
642
431
 
643
432
  ```bash
644
- # Import ONLY MyBucket; the other resources in the template are left alone.
645
- cdkd import MyStack --resource MyBucket=my-bucket-name
646
-
647
- # Several resources at once (--resource is repeatable).
648
- cdkd import MyStack \
649
- --resource MyBucket=my-bucket-name \
650
- --resource MyFn=my-function-name
651
-
652
- # CDK CLI compat: read overrides from a JSON file.
653
- cdkd import MyStack --resource-mapping mapping.json
654
- # mapping.json: { "MyBucket": "my-bucket-name", "MyFn": "my-function-name" }
655
-
656
- # CDK CLI compat: inline JSON (handy for non-TTY CI scripts).
657
- cdkd import MyStack --resource-mapping-inline '{"MyBucket":"my-bucket-name"}'
658
-
659
- # Capture cdkd's resolved logicalId→physicalId mapping for re-use.
660
- # Combine with --auto (or no flags) to record the tag-based lookups.
661
- cdkd import MyStack --record-resource-mapping ./mapping.json
662
- # mapping.json after the run: { "MyBucket": "my-bucket-name", ... }
663
- # Replay non-interactively in CI:
664
- cdkd import MyStack --resource-mapping ./mapping.json --yes
665
- ```
666
-
667
- When at least one `--resource` flag (or a `--resource-mapping` /
668
- `--resource-mapping-inline` payload) is supplied, **only the listed
669
- resources are imported**. Every other resource in the template is
670
- reported as `out of scope` and left out of state — the next `cdkd
671
- deploy` will treat them as new and CREATE them. This matches the
672
- semantics of `cdk import --resource-mapping` /
673
- `--resource-mapping-inline`. cdkd validates that every override key is
674
- a real logical ID in the template; a typo aborts the run rather than
675
- silently importing nothing. `--resource-mapping` and
676
- `--resource-mapping-inline` are mutually exclusive — pick one source.
677
-
678
- Use selective mode when you want to **adopt a few specific resources**
679
- out of a larger stack — for example, you have one S3 bucket that was
680
- created manually that you want cdkd to manage, while the rest of the
681
- stack will be deployed fresh.
682
-
683
- **Selective mode is non-destructive.** When state already exists for
684
- the stack, listed resources are **merged** into it: unlisted entries
685
- already in state are preserved (no `--force` needed). `--force` is
686
- only required when a listed override would overwrite a resource
687
- already in state — that's the one case where the merge is destructive.
688
- This is the right command for "I have a deployed stack and want to
689
- 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
690
435
 
691
- ```bash
692
- # Existing state has Queue + Topic; add Bucket without affecting them.
436
+ # Adopt only specific resources (CDK CLI parity).
693
437
  cdkd import MyStack --resource MyBucket=my-bucket-name
694
- # Resulting state: Queue + Topic (preserved) + Bucket (newly imported).
695
- ```
696
-
697
- ### Mode 3: hybrid (`--auto` with overrides)
698
438
 
699
- ```bash
700
- cdkd import MyStack \
701
- --resource MyBucket=my-bucket-name \
702
- --auto
439
+ # Migrate off CloudFormation in one shot — adopt + retire the source CFn stack.
440
+ cdkd import MyStack --migrate-from-cloudformation --yes
703
441
  ```
704
442
 
705
- Listed resources use the explicit physical ID you supplied; **every
706
- other resource still goes through tag-based auto-import**. Useful when
707
- you have one resource whose tag-based lookup is unreliable (e.g. you
708
- deleted and re-created it without the tag) but you want cdkd to find
709
- the rest by tag automatically.
710
-
711
- ### Common flags
712
-
713
- | Flag | Purpose |
714
- | ----------- | ----------------------------------------------------------------------------- |
715
- | `--dry-run` | Preview what would be imported. State is NOT written. |
716
- | `--yes` | Skip the confirmation prompt before writing state. |
717
- | `--force` | Confirm a destructive write to existing state — see below. |
718
-
719
- `--force` is only needed when the import would lose data:
720
-
721
- - **Auto / whole-stack mode + existing state**: required. The resource
722
- map is rebuilt from the template, so any state entry not re-imported
723
- is dropped.
724
- - **Selective mode + listed override already in state**: required.
725
- The listed entry is overwritten with the new physical id.
726
- - **Selective mode without a conflict (pure merge)**: not required.
727
- Unlisted state entries are preserved automatically.
728
- - **No existing state (first-time import)**: not required.
729
-
730
- ### After import
731
-
732
- Run `cdkd diff` to see how the imported state lines up with the
733
- template. If the resource's actual properties differ from the template,
734
- the next `cdkd deploy` will UPDATE them to match. If you imported only
735
- some resources (selective mode), the remaining template resources
736
- appear as `to create` in the diff.
737
-
738
- ### Provider support
739
-
740
- Tag-based auto-lookup is implemented for the most-used resource types
741
- (S3 Bucket, Lambda Function, IAM Role, SNS Topic, SQS Queue, DynamoDB
742
- Table, Logs LogGroup, EventBridge EventBus, KMS Key/Alias, Secrets
743
- Manager Secret, SSM Parameter, EC2 VPC/Subnet/SecurityGroup, RDS,
744
- ECS Cluster/Service/TaskDefinition, CloudFront Distribution, Cognito
745
- User Pool — the full list is in [CLAUDE.md](CLAUDE.md)). For resource
746
- types without auto-lookup support (ApiGateway sub-resources, niche
747
- services, anything in Cloud Control API), use the explicit
748
- `--resource <id>=<physicalId>` override mode — selective mode handles
749
- exactly this case. Resource types whose provider does not implement
750
- import are reported as `unsupported` and skipped.
751
-
752
- ### `cdkd import` vs upstream `cdk import`
753
-
754
- cdkd's `import` command mirrors the surface of upstream
755
- [`cdk import`](https://docs.aws.amazon.com/cdk/v2/guide/ref-cli-cmd-import.html)
756
- where it can, but the underlying mechanism is fundamentally different
757
- and a handful of upstream-only flags are not implemented. Use this
758
- table to predict behavior when migrating from `cdk import`.
759
-
760
- | Topic | `cdk import` (upstream) | `cdkd import` |
761
- | --- | --- | --- |
762
- | Mechanism | CloudFormation `CreateChangeSet` with `ResourcesToImport` — atomic, all-or-nothing. | Per-resource SDK calls (e.g. `s3:HeadBucket`, `lambda:GetFunction`, IAM `ListRoleTags`). **Not atomic.** |
763
- | 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). |
764
- | 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). |
765
- | Selective mode (`--resource <id>=<physical>` repeatable) | Not supported (upstream uses interactive prompts or a mapping file). | Supported as cdkd's CLI-friendly equivalent. |
766
- | `--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`. |
767
- | `--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. |
768
- | 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. |
769
- | 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. |
770
- | 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`. |
771
- | 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. |
772
- | 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. |
773
- | Bootstrap requirement | Bootstrap v12+ (deploy role needs to read the encrypted staging bucket). | cdkd's own state bucket; no CDK bootstrap version requirement. |
774
- | 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. |
775
- | Confirmation prompt before writing state | n/a (CloudFormation operates atomically). | Yes — cdkd asks before writing the state file. Skip with `--yes`. |
776
- | `--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.** |
777
- | `--dry-run` | Implied by `--no-execute` (creates the changeset without executing). | Native: shows the import plan and exits without writing state. |
778
-
779
- #### Practical implications when migrating from `cdk import`
780
-
781
- - If you script around `--resource-mapping <file>`: behavior matches.
782
- The file format (`{"LogicalId": "physical-id"}`) is the same.
783
- - If you script around `--resource-mapping-inline`: behavior matches.
784
- The JSON shape is the same as `--resource-mapping <file>`.
785
- - If you script around `--record-resource-mapping <file>`: behavior
786
- matches. cdkd writes the resolved `{logicalId: physicalId}` map to
787
- the file before the confirmation prompt — and even if the user says
788
- "no" or under `--dry-run` — so you can capture cdkd's tag-based
789
- auto-lookup result and replay it via `--resource-mapping` in CI.
790
- - If your workflow relies on the interactive prompt: rewrite as
791
- `--resource-mapping <file>`. cdkd will not prompt.
792
- - If you rely on atomic rollback: cdkd cannot offer that — its
793
- per-resource model writes state only after the full pass completes
794
- (and after confirmation), so a partial run is bounded, but if a
795
- later resource fails after several earlier ones already returned
796
- successfully and you confirm the write, those earlier ones are
797
- in cdkd state. Use `cdkd state orphan <stack>` to back out.
798
- - If you import nested stacks: neither tool supports this. Convert
799
- 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`.
800
447
 
801
448
  ## State Management
802
449
 
803
- State is stored in S3. Keys are scoped by `(stackName, region)` so the same
804
- stack name deployed to two regions has two independent state files:
805
-
806
- ```
807
- s3://{state-bucket}/
808
- └── {prefix}/ # Default: "cdkd" (configurable via --state-prefix)
809
- ├── MyStack/
810
- │ └── us-east-1/
811
- │ ├── state.json # Resource state (version: 2)
812
- │ └── lock.json # Exclusive deploy lock
813
- └── AnotherStack/
814
- ├── us-east-1/
815
- │ ├── state.json
816
- │ └── lock.json
817
- └── us-west-2/ # same stackName, different region
818
- ├── state.json
819
- └── lock.json
820
- ```
821
-
822
- > **Caveat: same `stackName` in multiple regions becomes visible after
823
- > `env.region` changes.** Before this layout shipped, changing a stack's
824
- > `env.region` between deploys silently overwrote the prior region's state
825
- > and `cdkd destroy` ran against the wrong region. cdkd now treats the two
826
- > regions as independent. Use `cdkd state list` to see both, and
827
- > `cdkd state orphan <stack> --stack-region <region>` to prune one without
828
- > touching the other.
829
- >
830
- > **Legacy key-layout migration (within the same bucket):** state files
831
- > written by cdkd before this layout (`version: 1`, flat
832
- > `cdkd/{stackName}/state.json`) are still readable. The next cdkd write
833
- > auto-migrates to the new region-prefixed key
834
- > (`cdkd/{stackName}/{region}/state.json`) and removes the legacy file —
835
- > no manual action required. An older cdkd binary reading a `version: 2`
836
- > file fails with a clear "upgrade cdkd" error rather than silently
837
- > mishandling it.
838
- >
839
- > Note: this only covers the **key layout inside an existing state
840
- > bucket**. The separate **bucket-name migration** (legacy
841
- > `cdkd-state-{accountId}-{region}` → new `cdkd-state-{accountId}`)
842
- > is described below and does NOT auto-migrate.
843
-
844
- ### Bucket migration
845
-
846
- The default state-bucket name changed in v0.11.0 from the region-suffixed
847
- `cdkd-state-{accountId}-{region}` to the region-free
848
- `cdkd-state-{accountId}`. The bucket name is region-free because S3 names
849
- are globally unique, so teammates with different profile regions all
850
- converge on the same bucket; the bucket's actual region is auto-detected
851
- via `GetBucketLocation`.
852
-
853
- Existing users keep working without doing anything: when only the legacy
854
- bucket exists, cdkd transparently falls back to it and emits a
855
- deprecation warning. To stop the warning (and consolidate state into the
856
- new bucket) run:
857
-
858
- ```bash
859
- # Per-region: copies all objects from cdkd-state-{accountId}-{region}
860
- # into cdkd-state-{accountId}. Source bucket is kept by default.
861
- cdkd state migrate --region us-east-1
862
-
863
- # Optional: delete the legacy bucket once the copy is verified.
864
- cdkd state migrate --region us-east-1 --remove-legacy
865
- ```
866
-
867
- This migration is **account-wide / per-region**, not per-stack — running
868
- it once per region clears the legacy bucket for that region in one shot.
869
- For multi-region accounts, run it once per region (each invocation copies
870
- into the same destination bucket).
871
-
872
- `cdkd state migrate` refuses to run while any stack has an active
873
- `lock.json` (an in-flight `cdkd deploy` / `destroy` would race the copy),
874
- verifies object-count parity between source and destination before any
875
- source cleanup, and only deletes the legacy bucket when
876
- `--remove-legacy` is passed.
877
-
878
- See the [Configuration](#configuration) table below for the full
879
- precedence rules of the `--state-bucket` flag and its env-var / cdk.json
880
- fallbacks.
881
-
882
- ### 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.
883
453
 
884
454
  | Setting | CLI | cdk.json | Env var | Default |
885
455
  |---------|-----|----------|---------|---------|
886
- | 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) |
887
457
  | Prefix | `--state-prefix` | - | - | `cdkd` |
888
458
 
889
- ### Multi-app isolation
890
-
891
- The state bucket is shared across all CDK apps in the same account/region by default. To isolate apps, use different prefixes:
892
-
893
- ```bash
894
- # App A
895
- cdkd deploy --state-prefix app-a
896
-
897
- # App B
898
- cdkd deploy --state-prefix app-b
899
- ```
900
-
901
- > **Note**: `cdkd destroy --all` only targets stacks from the current CDK app (determined by synthesis), not all stacks in the bucket.
902
-
903
- State schema:
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.
904
463
 
905
- ```typescript
906
- {
907
- version: 2,
908
- stackName: "MyStack",
909
- region: "us-east-1",
910
- resources: {
911
- "MyFunction": {
912
- physicalId: "arn:aws:lambda:...",
913
- resourceType: "AWS::Lambda::Function",
914
- properties: { ... },
915
- attributes: { Arn: "...", ... }, // For Fn::GetAtt
916
- dependencies: ["MyBucket"] // For proper deletion order
917
- }
918
- },
919
- outputs: { ... },
920
- lastModified: 1234567890
921
- }
922
- ```
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.
923
468
 
924
469
  ## Stack Outputs
925
470
 
@@ -949,19 +494,6 @@ After deployment, outputs are resolved and saved to the S3 state file:
949
494
  - cdkd: Outputs saved in S3 state file (e.g., `s3://bucket/cdkd/MyStack/us-east-1/state.json`)
950
495
  - Both resolve intrinsic functions (Ref, Fn::GetAtt, etc.) to actual values
951
496
 
952
- ## Testing
953
-
954
- - Unit tests covering all layers
955
- - Integration examples verified with real AWS deployments (see `tests/integration/`)
956
- - E2E test script for automated deploy/diff/update/destroy cycles
957
-
958
- ```bash
959
- pnpm test # Run unit tests
960
- pnpm run test:coverage # With coverage report
961
- ```
962
-
963
- See [docs/testing.md](docs/testing.md) for integration and E2E testing instructions.
964
-
965
497
  ## License
966
498
 
967
499
  Apache 2.0