@go-to-k/cdkd 0.27.0 → 0.28.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 +37 -9
- package/dist/cli.js +126 -28
- package/dist/cli.js.map +2 -2
- package/dist/go-to-k-cdkd-0.28.1.tgz +0 -0
- package/dist/index.js +69 -9
- package/dist/index.js.map +2 -2
- package/package.json +1 -1
- package/dist/go-to-k-cdkd-0.27.0.tgz +0 -0
package/README.md
CHANGED
|
@@ -539,9 +539,6 @@ Both flags accept either form on each invocation:
|
|
|
539
539
|
`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.
|
|
540
540
|
|
|
541
541
|
```bash
|
|
542
|
-
# Bump the per-resource budget to one hour (matches the Custom Resource provider's polling cap)
|
|
543
|
-
cdkd deploy --resource-timeout 1h
|
|
544
|
-
|
|
545
542
|
# Surface "still running" warnings sooner on a fast-feedback dev loop
|
|
546
543
|
cdkd deploy --resource-warn-after 90s --resource-timeout 10m
|
|
547
544
|
|
|
@@ -550,19 +547,25 @@ cdkd deploy \
|
|
|
550
547
|
--resource-timeout 30m \
|
|
551
548
|
--resource-timeout AWS::CloudFront::Distribution=1h \
|
|
552
549
|
--resource-timeout AWS::RDS::DBCluster=1h30m
|
|
550
|
+
|
|
551
|
+
# Force Custom Resources to abort earlier than their 1h self-reported polling cap
|
|
552
|
+
cdkd deploy --resource-timeout AWS::CloudFormation::CustomResource=5m
|
|
553
553
|
```
|
|
554
554
|
|
|
555
555
|
### Why the default is 30m, not 1h
|
|
556
556
|
|
|
557
|
-
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
|
|
557
|
+
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.
|
|
558
|
+
|
|
559
|
+
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.
|
|
558
560
|
|
|
559
|
-
The error message on timeout names the resource, type, region, elapsed time, and operation, and reminds you
|
|
561
|
+
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:
|
|
560
562
|
|
|
561
563
|
```text
|
|
562
564
|
Resource MyBucket (AWS::S3::Bucket) in us-east-1 timed out after 30m during CREATE (elapsed 30m).
|
|
563
565
|
This may indicate a stuck Cloud Control polling loop, hung Custom Resource, or
|
|
564
|
-
slow ENI provisioning. Re-run with --resource-timeout
|
|
565
|
-
|
|
566
|
+
slow ENI provisioning. Re-run with --resource-timeout AWS::S3::Bucket=<DURATION>
|
|
567
|
+
to bump the budget for this resource type only, or --verbose to see the
|
|
568
|
+
underlying provider activity.
|
|
566
569
|
```
|
|
567
570
|
|
|
568
571
|
Note: `--resource-warn-after` must be less than `--resource-timeout`. Reversed values are rejected at parse time.
|
|
@@ -675,6 +678,20 @@ out of a larger stack — for example, you have one S3 bucket that was
|
|
|
675
678
|
created manually that you want cdkd to manage, while the rest of the
|
|
676
679
|
stack will be deployed fresh.
|
|
677
680
|
|
|
681
|
+
**Selective mode is non-destructive.** When state already exists for
|
|
682
|
+
the stack, listed resources are **merged** into it: unlisted entries
|
|
683
|
+
already in state are preserved (no `--force` needed). `--force` is
|
|
684
|
+
only required when a listed override would overwrite a resource
|
|
685
|
+
already in state — that's the one case where the merge is destructive.
|
|
686
|
+
This is the right command for "I have a deployed stack and want to
|
|
687
|
+
adopt one more resource into it":
|
|
688
|
+
|
|
689
|
+
```bash
|
|
690
|
+
# Existing state has Queue + Topic; add Bucket without affecting them.
|
|
691
|
+
cdkd import MyStack --resource MyBucket=my-bucket-name
|
|
692
|
+
# Resulting state: Queue + Topic (preserved) + Bucket (newly imported).
|
|
693
|
+
```
|
|
694
|
+
|
|
678
695
|
### Mode 3: hybrid (`--auto` with overrides)
|
|
679
696
|
|
|
680
697
|
```bash
|
|
@@ -695,7 +712,18 @@ the rest by tag automatically.
|
|
|
695
712
|
| ----------- | ----------------------------------------------------------------------------- |
|
|
696
713
|
| `--dry-run` | Preview what would be imported. State is NOT written. |
|
|
697
714
|
| `--yes` | Skip the confirmation prompt before writing state. |
|
|
698
|
-
| `--force` |
|
|
715
|
+
| `--force` | Confirm a destructive write to existing state — see below. |
|
|
716
|
+
|
|
717
|
+
`--force` is only needed when the import would lose data:
|
|
718
|
+
|
|
719
|
+
- **Auto / whole-stack mode + existing state**: required. The resource
|
|
720
|
+
map is rebuilt from the template, so any state entry not re-imported
|
|
721
|
+
is dropped.
|
|
722
|
+
- **Selective mode + listed override already in state**: required.
|
|
723
|
+
The listed entry is overwritten with the new physical id.
|
|
724
|
+
- **Selective mode without a conflict (pure merge)**: not required.
|
|
725
|
+
Unlisted state entries are preserved automatically.
|
|
726
|
+
- **No existing state (first-time import)**: not required.
|
|
699
727
|
|
|
700
728
|
### After import
|
|
701
729
|
|
|
@@ -743,7 +771,7 @@ table to predict behavior when migrating from `cdk import`.
|
|
|
743
771
|
| Bootstrap requirement | Bootstrap v12+ (deploy role needs to read the encrypted staging bucket). | cdkd's own state bucket; no CDK bootstrap version requirement. |
|
|
744
772
|
| 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. |
|
|
745
773
|
| Confirmation prompt before writing state | n/a (CloudFormation operates atomically). | Yes — cdkd asks before writing the state file. Skip with `--yes`. |
|
|
746
|
-
| `--force` | "Continue even if the diff includes updates or deletions" — about diff strictness. | "
|
|
774
|
+
| `--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.** |
|
|
747
775
|
| `--dry-run` | Implied by `--no-execute` (creates the changeset without executing). | Native: shows the import plan and exits without writing state. |
|
|
748
776
|
|
|
749
777
|
#### Practical implications when migrating from `cdk import`
|
package/dist/cli.js
CHANGED
|
@@ -1133,8 +1133,9 @@ var ResourceTimeoutError = class _ResourceTimeoutError extends CdkdError {
|
|
|
1133
1133
|
super(
|
|
1134
1134
|
`Resource ${logicalId} (${resourceType}) in ${region} timed out after ${timeoutLabel} during ${operation} (elapsed ${elapsedLabel}).
|
|
1135
1135
|
This may indicate a stuck Cloud Control polling loop, hung Custom Resource, or
|
|
1136
|
-
slow ENI provisioning. Re-run with --resource-timeout
|
|
1137
|
-
|
|
1136
|
+
slow ENI provisioning. Re-run with --resource-timeout ${resourceType}=<DURATION>
|
|
1137
|
+
to bump the budget for this resource type only, or --verbose to see the
|
|
1138
|
+
underlying provider activity.`,
|
|
1138
1139
|
"RESOURCE_TIMEOUT"
|
|
1139
1140
|
);
|
|
1140
1141
|
this.logicalId = logicalId;
|
|
@@ -7448,6 +7449,23 @@ var CustomResourceProvider = class _CustomResourceProvider {
|
|
|
7448
7449
|
logger = getLogger().child("CustomResourceProvider");
|
|
7449
7450
|
responseBucket;
|
|
7450
7451
|
responsePrefix;
|
|
7452
|
+
/**
|
|
7453
|
+
* Opt out of the deploy engine's outer transient-error retry loop.
|
|
7454
|
+
*
|
|
7455
|
+
* The loop re-invokes `provider.create()` from the top on a transient
|
|
7456
|
+
* SDK error (IAM propagation, HTTP 429/503, etc.). Each invocation
|
|
7457
|
+
* generates a brand-new RequestId and a brand-new pre-signed S3
|
|
7458
|
+
* response URL via `prepareInvocation()`. If the underlying Lambda has
|
|
7459
|
+
* already started — e.g. an outer retry fired between the placeholder
|
|
7460
|
+
* `PutObject` and the `Invoke`, or after the `Invoke` returned but a
|
|
7461
|
+
* spurious downstream error fired — the first attempt's Lambda
|
|
7462
|
+
* response lands at an S3 key that nobody polls, hanging the deploy
|
|
7463
|
+
* until the polling timeout. The provider already polls with its own
|
|
7464
|
+
* exponential backoff for async patterns (CDK Provider framework with
|
|
7465
|
+
* isCompleteHandler), so an outer retry adds nothing but the multi-
|
|
7466
|
+
* key bug.
|
|
7467
|
+
*/
|
|
7468
|
+
disableOuterRetry = true;
|
|
7451
7469
|
/** Max time to wait for synchronous S3 response after Lambda invocation (30 seconds) */
|
|
7452
7470
|
SYNC_RESPONSE_TIMEOUT_MS = 3e4;
|
|
7453
7471
|
/** Max time to wait for async S3 response (CDK Provider framework with isCompleteHandler) */
|
|
@@ -7467,6 +7485,22 @@ var CustomResourceProvider = class _CustomResourceProvider {
|
|
|
7467
7485
|
this.responsePrefix = config?.responsePrefix ?? "custom-resource-responses";
|
|
7468
7486
|
this.asyncResponseTimeoutMs = config?.asyncResponseTimeoutMs ?? _CustomResourceProvider.DEFAULT_ASYNC_RESPONSE_TIMEOUT_MS;
|
|
7469
7487
|
}
|
|
7488
|
+
/**
|
|
7489
|
+
* Self-reported minimum per-resource timeout.
|
|
7490
|
+
*
|
|
7491
|
+
* Custom Resource async invocations (CDK Provider framework with
|
|
7492
|
+
* `isCompleteHandler`) poll for up to `asyncResponseTimeoutMs`
|
|
7493
|
+
* (default 1 hour, matching CDK's `totalTimeout` default). The deploy
|
|
7494
|
+
* engine's global `--resource-timeout` default is 30 minutes, which
|
|
7495
|
+
* would abort a perfectly healthy CR mid-poll. By self-reporting the
|
|
7496
|
+
* polling cap, the engine lifts the deadline to `max(self-report,
|
|
7497
|
+
* global)` for CR resources only; a user-supplied per-type override
|
|
7498
|
+
* (`--resource-timeout AWS::CloudFormation::CustomResource=5m`) still
|
|
7499
|
+
* wins for explicit escape-hatching.
|
|
7500
|
+
*/
|
|
7501
|
+
getMinResourceTimeoutMs() {
|
|
7502
|
+
return this.asyncResponseTimeoutMs;
|
|
7503
|
+
}
|
|
7470
7504
|
/**
|
|
7471
7505
|
* Set the S3 bucket for custom resource responses
|
|
7472
7506
|
* Called by ProviderRegistry when state bucket is configured
|
|
@@ -31890,8 +31924,11 @@ var DeployEngine = class {
|
|
|
31890
31924
|
const baseLabel = `${verb} ${logicalId} (${resourceType})`;
|
|
31891
31925
|
renderer.addTask(logicalId, baseLabel);
|
|
31892
31926
|
const operationKind = change.changeType === "CREATE" ? "CREATE" : change.changeType === "DELETE" ? "DELETE" : "UPDATE";
|
|
31927
|
+
const provider = this.providerRegistry.getProvider(resourceType);
|
|
31928
|
+
const providerMinTimeoutMs = provider.getMinResourceTimeoutMs?.() ?? 0;
|
|
31893
31929
|
const warnAfterMs = this.options.resourceWarnAfterByType?.[resourceType] ?? this.options.resourceWarnAfterMs ?? DEFAULT_RESOURCE_WARN_AFTER_MS;
|
|
31894
|
-
const
|
|
31930
|
+
const globalTimeoutMs = this.options.resourceTimeoutMs ?? DEFAULT_RESOURCE_TIMEOUT_MS;
|
|
31931
|
+
const timeoutMs = this.options.resourceTimeoutByType?.[resourceType] ?? Math.max(providerMinTimeoutMs, globalTimeoutMs);
|
|
31895
31932
|
try {
|
|
31896
31933
|
await withResourceDeadline(
|
|
31897
31934
|
async () => {
|
|
@@ -31970,7 +32007,10 @@ var DeployEngine = class {
|
|
|
31970
32007
|
const { provider: createProvider, properties: createProps } = this.selectProviderWithSafetyNet(provider, resourceType, resolvedProps, logicalId);
|
|
31971
32008
|
const result = await this.withRetry(
|
|
31972
32009
|
() => createProvider.create(logicalId, resourceType, createProps),
|
|
31973
|
-
logicalId
|
|
32010
|
+
logicalId,
|
|
32011
|
+
void 0,
|
|
32012
|
+
void 0,
|
|
32013
|
+
provider
|
|
31974
32014
|
);
|
|
31975
32015
|
const dependencies = this.extractAllDependencies(template, logicalId);
|
|
31976
32016
|
stateResources[logicalId] = {
|
|
@@ -32024,7 +32064,10 @@ var DeployEngine = class {
|
|
|
32024
32064
|
const { provider: replaceProvider, properties: replaceProps } = this.selectProviderWithSafetyNet(provider, resourceType, resolvedProps, logicalId);
|
|
32025
32065
|
const createResult = await this.withRetry(
|
|
32026
32066
|
() => replaceProvider.create(logicalId, resourceType, replaceProps),
|
|
32027
|
-
logicalId
|
|
32067
|
+
logicalId,
|
|
32068
|
+
void 0,
|
|
32069
|
+
void 0,
|
|
32070
|
+
provider
|
|
32028
32071
|
);
|
|
32029
32072
|
const updateReplacePolicy = template?.Resources?.[logicalId]?.UpdateReplacePolicy;
|
|
32030
32073
|
if (updateReplacePolicy === "Retain") {
|
|
@@ -32075,7 +32118,10 @@ var DeployEngine = class {
|
|
|
32075
32118
|
updateProps,
|
|
32076
32119
|
currentProps
|
|
32077
32120
|
),
|
|
32078
|
-
logicalId
|
|
32121
|
+
logicalId,
|
|
32122
|
+
void 0,
|
|
32123
|
+
void 0,
|
|
32124
|
+
provider
|
|
32079
32125
|
);
|
|
32080
32126
|
} catch (updateError) {
|
|
32081
32127
|
const msg = updateError instanceof Error ? updateError.message : String(updateError);
|
|
@@ -32104,7 +32150,10 @@ var DeployEngine = class {
|
|
|
32104
32150
|
const { provider: replProvider, properties: replProps } = this.selectProviderWithSafetyNet(provider, resourceType, resolvedProps, logicalId);
|
|
32105
32151
|
const createResult = await this.withRetry(
|
|
32106
32152
|
() => replProvider.create(logicalId, resourceType, replProps),
|
|
32107
|
-
logicalId
|
|
32153
|
+
logicalId,
|
|
32154
|
+
void 0,
|
|
32155
|
+
void 0,
|
|
32156
|
+
provider
|
|
32108
32157
|
);
|
|
32109
32158
|
result = {
|
|
32110
32159
|
physicalId: createResult.physicalId,
|
|
@@ -32161,7 +32210,8 @@ var DeployEngine = class {
|
|
|
32161
32210
|
logicalId,
|
|
32162
32211
|
3,
|
|
32163
32212
|
// fewer retries for DELETE
|
|
32164
|
-
5e3
|
|
32213
|
+
5e3,
|
|
32214
|
+
provider
|
|
32165
32215
|
);
|
|
32166
32216
|
} catch (deleteError) {
|
|
32167
32217
|
const msg = deleteError instanceof Error ? deleteError.message : String(deleteError);
|
|
@@ -32338,8 +32388,18 @@ var DeployEngine = class {
|
|
|
32338
32388
|
* Thin wrapper over `withRetry` from ./retry.js that injects this engine's
|
|
32339
32389
|
* SIGINT-aware interrupt check and logger. The actual backoff schedule
|
|
32340
32390
|
* lives there.
|
|
32391
|
+
*
|
|
32392
|
+
* When the provider opts out via `disableOuterRetry`, the operation is
|
|
32393
|
+
* invoked exactly once and the retry loop is skipped entirely. The
|
|
32394
|
+
* Custom Resource provider uses this to avoid re-running its `create()`
|
|
32395
|
+
* — each invocation derives a fresh pre-signed S3 URL and RequestId,
|
|
32396
|
+
* so an outer retry leaves the previous attempt's Lambda response
|
|
32397
|
+
* stranded at an S3 key nobody polls.
|
|
32341
32398
|
*/
|
|
32342
|
-
async withRetry(operation, logicalId, maxRetries, initialDelayMs) {
|
|
32399
|
+
async withRetry(operation, logicalId, maxRetries, initialDelayMs, provider) {
|
|
32400
|
+
if (provider?.disableOuterRetry) {
|
|
32401
|
+
return operation();
|
|
32402
|
+
}
|
|
32343
32403
|
return withRetry(operation, logicalId, {
|
|
32344
32404
|
...maxRetries !== void 0 && { maxRetries },
|
|
32345
32405
|
...initialDelayMs !== void 0 && { initialDelayMs },
|
|
@@ -33019,16 +33079,19 @@ Acquiring lock for stack ${stackName}...`);
|
|
|
33019
33079
|
logger.warn(`Resource ${logicalId} not found in state, skipping`);
|
|
33020
33080
|
return;
|
|
33021
33081
|
}
|
|
33022
|
-
const warnAfterMs = ctx.resourceWarnAfterByType?.[resource.resourceType] ?? ctx.resourceWarnAfterMs ?? DEFAULT_RESOURCE_WARN_AFTER_MS;
|
|
33023
|
-
const timeoutMs = ctx.resourceTimeoutByType?.[resource.resourceType] ?? ctx.resourceTimeoutMs ?? DEFAULT_RESOURCE_TIMEOUT_MS;
|
|
33024
33082
|
const baseLabel = `Deleting ${logicalId} (${resource.resourceType})`;
|
|
33025
33083
|
renderer.addTask(logicalId, baseLabel);
|
|
33026
33084
|
try {
|
|
33027
33085
|
const provider = destroyProviderRegistry.getProvider(resource.resourceType);
|
|
33086
|
+
const providerMinTimeoutMs = provider.getMinResourceTimeoutMs?.() ?? 0;
|
|
33087
|
+
const warnAfterMs = ctx.resourceWarnAfterByType?.[resource.resourceType] ?? ctx.resourceWarnAfterMs ?? DEFAULT_RESOURCE_WARN_AFTER_MS;
|
|
33088
|
+
const globalTimeoutMs = ctx.resourceTimeoutMs ?? DEFAULT_RESOURCE_TIMEOUT_MS;
|
|
33089
|
+
const timeoutMs = ctx.resourceTimeoutByType?.[resource.resourceType] ?? Math.max(providerMinTimeoutMs, globalTimeoutMs);
|
|
33028
33090
|
await withResourceDeadline(
|
|
33029
33091
|
async () => {
|
|
33092
|
+
const maxAttempts = provider.disableOuterRetry ? 0 : 3;
|
|
33030
33093
|
let lastDeleteError;
|
|
33031
|
-
for (let attempt = 0; attempt <=
|
|
33094
|
+
for (let attempt = 0; attempt <= maxAttempts; attempt++) {
|
|
33032
33095
|
try {
|
|
33033
33096
|
await provider.delete(
|
|
33034
33097
|
logicalId,
|
|
@@ -33042,11 +33105,11 @@ Acquiring lock for stack ${stackName}...`);
|
|
|
33042
33105
|
lastDeleteError = retryError;
|
|
33043
33106
|
const msg = retryError instanceof Error ? retryError.message : String(retryError);
|
|
33044
33107
|
const isRetryable = msg.includes("Too Many Requests") || msg.includes("has dependencies") || msg.includes("can't be deleted since") || msg.includes("DependencyViolation");
|
|
33045
|
-
if (!isRetryable || attempt >=
|
|
33108
|
+
if (!isRetryable || attempt >= maxAttempts)
|
|
33046
33109
|
break;
|
|
33047
33110
|
const delay = 5e3 * Math.pow(2, attempt);
|
|
33048
33111
|
logger.debug(
|
|
33049
|
-
` \u23F3 Retrying delete ${logicalId} in ${delay / 1e3}s (attempt ${attempt + 1}
|
|
33112
|
+
` \u23F3 Retrying delete ${logicalId} in ${delay / 1e3}s (attempt ${attempt + 1}/${maxAttempts})`
|
|
33050
33113
|
);
|
|
33051
33114
|
await new Promise((resolve4) => setTimeout(resolve4, delay));
|
|
33052
33115
|
}
|
|
@@ -35196,12 +35259,6 @@ async function importCommand(stackArg, options) {
|
|
|
35196
35259
|
}
|
|
35197
35260
|
const targetRegion = stackInfo.region || region;
|
|
35198
35261
|
logger.info(`Target stack: ${stackInfo.stackName} (${targetRegion})`);
|
|
35199
|
-
const existing = await stateBackend.stateExists(stackInfo.stackName, targetRegion);
|
|
35200
|
-
if (existing && !options.force) {
|
|
35201
|
-
throw new Error(
|
|
35202
|
-
`State already exists for stack '${stackInfo.stackName}' (${targetRegion}). Pass --force to overwrite. (cdkd state import rebuilds the resource map from AWS, so the existing state \u2014 including any drift you've manually edited \u2014 will be lost.)`
|
|
35203
|
-
);
|
|
35204
|
-
}
|
|
35205
35262
|
const overrides = parseResourceOverrides(
|
|
35206
35263
|
options.resource,
|
|
35207
35264
|
options.resourceMapping,
|
|
@@ -35216,6 +35273,34 @@ async function importCommand(stackArg, options) {
|
|
|
35216
35273
|
`Selective mode: only importing the ${overrides.size} resource(s) you listed (${[...overrides.keys()].join(", ")}). Pass --auto to also tag-import the rest.`
|
|
35217
35274
|
);
|
|
35218
35275
|
}
|
|
35276
|
+
const existingResult = await stateBackend.getState(stackInfo.stackName, targetRegion);
|
|
35277
|
+
const existingState = existingResult?.state ?? null;
|
|
35278
|
+
const existingEtag = existingResult?.etag;
|
|
35279
|
+
const migrationPending = existingResult?.migrationPending ?? false;
|
|
35280
|
+
if (existingState) {
|
|
35281
|
+
if (!selectiveMode) {
|
|
35282
|
+
if (!options.force) {
|
|
35283
|
+
throw new Error(
|
|
35284
|
+
`State already exists for stack '${stackInfo.stackName}' (${targetRegion}). Auto / whole-stack import rebuilds the entire resource map from the template, which would drop any state entry not re-imported. Pass --force to confirm. To add specific resources without affecting unlisted ones, use --resource <id>=<physicalId> (selective merge \u2014 no --force needed).`
|
|
35285
|
+
);
|
|
35286
|
+
}
|
|
35287
|
+
} else {
|
|
35288
|
+
const conflicts = [...overrides.keys()].filter(
|
|
35289
|
+
(id) => Object.prototype.hasOwnProperty.call(existingState.resources, id)
|
|
35290
|
+
);
|
|
35291
|
+
if (conflicts.length > 0 && !options.force) {
|
|
35292
|
+
throw new Error(
|
|
35293
|
+
`Selective import would overwrite resource(s) already in state: ${conflicts.join(", ")}. Pass --force to confirm the overwrite, or remove these IDs from --resource / --resource-mapping.`
|
|
35294
|
+
);
|
|
35295
|
+
}
|
|
35296
|
+
const preservedCount = Object.keys(existingState.resources).filter(
|
|
35297
|
+
(id) => !overrides.has(id)
|
|
35298
|
+
).length;
|
|
35299
|
+
logger.info(
|
|
35300
|
+
`Merging into existing state for ${stackInfo.stackName} (${targetRegion}): preserving ${preservedCount} unlisted resource(s)` + (conflicts.length > 0 ? `, overwriting ${conflicts.length} listed entry(ies)` : "")
|
|
35301
|
+
);
|
|
35302
|
+
}
|
|
35303
|
+
}
|
|
35219
35304
|
const template = stackInfo.template;
|
|
35220
35305
|
const templateParser = new TemplateParser();
|
|
35221
35306
|
const resources = collectImportableResources(template);
|
|
@@ -35266,8 +35351,12 @@ async function importCommand(stackArg, options) {
|
|
|
35266
35351
|
return;
|
|
35267
35352
|
}
|
|
35268
35353
|
if (!options.yes) {
|
|
35354
|
+
const importedCount = importedRows.length;
|
|
35355
|
+
const preservedCount = selectiveMode && existingState ? Object.keys(existingState.resources).filter((id) => !overrides.has(id)).length : 0;
|
|
35356
|
+
const totalAfter = importedCount + preservedCount;
|
|
35357
|
+
const breakdown = preservedCount > 0 ? ` (${importedCount} new/overwritten + ${preservedCount} preserved)` : "";
|
|
35269
35358
|
const ok = await confirmPrompt3(
|
|
35270
|
-
`Write state for ${stackInfo.stackName} (${targetRegion}) with ${
|
|
35359
|
+
`Write state for ${stackInfo.stackName} (${targetRegion}) with ${totalAfter} resource(s)${breakdown}?`
|
|
35271
35360
|
);
|
|
35272
35361
|
if (!ok) {
|
|
35273
35362
|
logger.info("Import cancelled.");
|
|
@@ -35279,9 +35368,18 @@ async function importCommand(stackArg, options) {
|
|
|
35279
35368
|
targetRegion,
|
|
35280
35369
|
rows,
|
|
35281
35370
|
templateParser,
|
|
35282
|
-
template
|
|
35371
|
+
template,
|
|
35372
|
+
existingState,
|
|
35373
|
+
selectiveMode
|
|
35283
35374
|
);
|
|
35284
|
-
|
|
35375
|
+
const saveOptions = {};
|
|
35376
|
+
if (existingEtag) {
|
|
35377
|
+
saveOptions.expectedEtag = existingEtag;
|
|
35378
|
+
}
|
|
35379
|
+
if (migrationPending) {
|
|
35380
|
+
saveOptions.migrateLegacy = true;
|
|
35381
|
+
}
|
|
35382
|
+
await stateBackend.saveState(stackInfo.stackName, targetRegion, stackState, saveOptions);
|
|
35285
35383
|
logger.info(`\u2713 State written: ${stackInfo.stackName} (${targetRegion})`);
|
|
35286
35384
|
logger.info(
|
|
35287
35385
|
` ${importedRows.length} resource(s) imported. Run 'cdkd diff' to see how the imported state lines up with the template.`
|
|
@@ -35437,8 +35535,8 @@ function collectImportableResources(template) {
|
|
|
35437
35535
|
}
|
|
35438
35536
|
return out;
|
|
35439
35537
|
}
|
|
35440
|
-
function buildStackState(stackName, region, rows, templateParser, template) {
|
|
35441
|
-
const resources = {};
|
|
35538
|
+
function buildStackState(stackName, region, rows, templateParser, template, existingState, selectiveMode) {
|
|
35539
|
+
const resources = selectiveMode && existingState ? { ...existingState.resources } : {};
|
|
35442
35540
|
for (const row of rows) {
|
|
35443
35541
|
if (row.outcome !== "imported" || !row.physicalId)
|
|
35444
35542
|
continue;
|
|
@@ -35459,7 +35557,7 @@ function buildStackState(stackName, region, rows, templateParser, template) {
|
|
|
35459
35557
|
stackName,
|
|
35460
35558
|
region,
|
|
35461
35559
|
resources,
|
|
35462
|
-
outputs: {},
|
|
35560
|
+
outputs: existingState?.outputs ?? {},
|
|
35463
35561
|
lastModified: Date.now()
|
|
35464
35562
|
};
|
|
35465
35563
|
}
|
|
@@ -35534,7 +35632,7 @@ function createImportCommand() {
|
|
|
35534
35632
|
false
|
|
35535
35633
|
).option("--dry-run", "Show planned imports without writing state", false).option(
|
|
35536
35634
|
"--force",
|
|
35537
|
-
"
|
|
35635
|
+
"Confirm a destructive write to existing state. Required for auto / whole-stack import when state already exists (rebuilds the entire resource map). Also required in selective mode if a listed override would overwrite a resource already in state. Not needed for a pure selective merge (adding new resources without touching unlisted entries).",
|
|
35538
35636
|
false
|
|
35539
35637
|
).action(withErrorHandling(importCommand));
|
|
35540
35638
|
[...commonOptions, ...appOptions, ...stateOptions, ...contextOptions].forEach(
|
|
@@ -35573,7 +35671,7 @@ function reorderArgs(argv) {
|
|
|
35573
35671
|
}
|
|
35574
35672
|
async function main() {
|
|
35575
35673
|
const program = new Command13();
|
|
35576
|
-
program.name("cdkd").description("CDK Direct - Deploy AWS CDK apps directly via SDK/Cloud Control API").version("0.
|
|
35674
|
+
program.name("cdkd").description("CDK Direct - Deploy AWS CDK apps directly via SDK/Cloud Control API").version("0.28.1");
|
|
35577
35675
|
program.addCommand(createBootstrapCommand());
|
|
35578
35676
|
program.addCommand(createSynthCommand());
|
|
35579
35677
|
program.addCommand(createListCommand());
|