@go-to-k/cdkd 0.162.3 → 0.163.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/dist/cli.js +140 -19
- package/dist/cli.js.map +1 -1
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -1004,9 +1004,12 @@ const MULTI_REGION_RECREATE_BLOCKED_TYPES = new Set(["AWS::DynamoDB::GlobalTable
|
|
|
1004
1004
|
* Cheap, synchronous read of the resource's recorded properties only.
|
|
1005
1005
|
* For `AWS::S3::Bucket` this returns `null` — the live `ListObjectsV2`
|
|
1006
1006
|
* probe to distinguish empty buckets (safe to recreate) from
|
|
1007
|
-
* non-empty (data loss)
|
|
1008
|
-
*
|
|
1009
|
-
*
|
|
1007
|
+
* non-empty (data loss) lives in
|
|
1008
|
+
* `src/deployment/recreate-targets.ts#probeStatefulRecreateTargetsAsync`
|
|
1009
|
+
* (issue [#648]) and runs after this sync first-cut. Sync callers can
|
|
1010
|
+
* still treat `null` as "not stateful" — the deploy command does both
|
|
1011
|
+
* passes back-to-back; only callers that explicitly opt out of the
|
|
1012
|
+
* async probe need to assume conservative "stateful" semantics.
|
|
1010
1013
|
*
|
|
1011
1014
|
* Returns the {@link StatefulReason} when the type is stateful (or
|
|
1012
1015
|
* `null` for non-stateful types).
|
|
@@ -1038,11 +1041,47 @@ function renderStatefulReason(reason) {
|
|
|
1038
1041
|
//#endregion
|
|
1039
1042
|
//#region src/deployment/recreate-targets.ts
|
|
1040
1043
|
/**
|
|
1044
|
+
* Pre-flight validation for `--recreate-via-cc-api <LogicalId>` deploy
|
|
1045
|
+
* flag (issue [#615]).
|
|
1046
|
+
*
|
|
1047
|
+
* Three things to validate before the deploy engine acts on the user's
|
|
1048
|
+
* recreate list:
|
|
1049
|
+
*
|
|
1050
|
+
* 1. Every named logical id MUST exist in the synth template. A typo
|
|
1051
|
+
* should fail fast, not silently skip.
|
|
1052
|
+
* 2. Every named logical id MUST exist in cdkd state (the recreate
|
|
1053
|
+
* operation requires an existing physical resource to destroy +
|
|
1054
|
+
* recreate). A logical id in the template but absent from state
|
|
1055
|
+
* is a CREATE on the next deploy regardless — recreate is a
|
|
1056
|
+
* no-op for fresh deploys and should error out with a clear
|
|
1057
|
+
* message rather than silently apply.
|
|
1058
|
+
* 3. Stateful-resource guard: every named target whose resource type
|
|
1059
|
+
* is in {@link STATEFUL_TYPES} (or conditionally stateful — S3
|
|
1060
|
+
* bucket with objects, LogGroup with retention) MUST be matched
|
|
1061
|
+
* by an explicit `--force-stateful-recreation` flag. The sync
|
|
1062
|
+
* first-cut runs from the recorded properties alone; the live
|
|
1063
|
+
* `s3:ListObjectsV2` probe (issue [#648]) promotes a `null`
|
|
1064
|
+
* reason to `'has-objects'` when a bucket actually contains data.
|
|
1065
|
+
* 4. Multi-region refusal: every named target whose resource type
|
|
1066
|
+
* is in {@link MULTI_REGION_RECREATE_BLOCKED_TYPES} (e.g.
|
|
1067
|
+
* `AWS::DynamoDB::GlobalTable`) is refused outright. Out of
|
|
1068
|
+
* scope for v1; no `--force-stateful-recreation` bypass since
|
|
1069
|
+
* this is a structural limitation, not a data-loss footgun.
|
|
1070
|
+
*
|
|
1071
|
+
* Plus one cross-flag invariant: `--recreate-via-cc-api MyLambda`
|
|
1072
|
+
* combined with `--allow-unsupported-properties AWS::Lambda::Function:LoggingConfig`
|
|
1073
|
+
* on a resource whose template carries `LoggingConfig` is **ambiguous
|
|
1074
|
+
* intent** — does the user want SDK + silent drop, or CC migration?
|
|
1075
|
+
* Fail fast and let the user pick one strategy per resource.
|
|
1076
|
+
*/
|
|
1077
|
+
/**
|
|
1041
1078
|
* Plan-time validation of the user's recreate-via-cc-api list.
|
|
1042
1079
|
*
|
|
1043
|
-
* Pure with respect to AWS — does NOT probe S3 bucket emptiness.
|
|
1044
|
-
*
|
|
1045
|
-
* `
|
|
1080
|
+
* Pure with respect to AWS — does NOT probe S3 bucket emptiness. Wrap
|
|
1081
|
+
* the result with {@link probeAndRevalidateStateful} to promote S3
|
|
1082
|
+
* targets' `statefulReason` via a live `s3:ListObjectsV2` round-trip
|
|
1083
|
+
* before rendering errors. The deploy command does this; the validator
|
|
1084
|
+
* itself stays sync so unit tests don't need an S3 mock.
|
|
1046
1085
|
*
|
|
1047
1086
|
* Input order is preserved; duplicate logical ids in the user's input
|
|
1048
1087
|
* are deduplicated.
|
|
@@ -1137,6 +1176,84 @@ function renderRecreateTargetsErrors(validation) {
|
|
|
1137
1176
|
}
|
|
1138
1177
|
return lines.length > 0 ? lines.join("\n") : null;
|
|
1139
1178
|
}
|
|
1179
|
+
/**
|
|
1180
|
+
* Async S3 object probe (issue [#648]).
|
|
1181
|
+
*
|
|
1182
|
+
* For every `AWS::S3::Bucket` target whose sync {@link StatefulReason}
|
|
1183
|
+
* is `null` (the sync map defers — see {@link isStatefulRecreateTargetSync}),
|
|
1184
|
+
* issues a single-page `ListObjectVersions(MaxKeys=1)` against the
|
|
1185
|
+
* bucket's recorded physical id. When the bucket has at least one
|
|
1186
|
+
* current object, prior version, OR delete-marker, promotes the
|
|
1187
|
+
* target's `statefulReason` to `'has-objects'`.
|
|
1188
|
+
*
|
|
1189
|
+
* Uses `ListObjectVersions` rather than `ListObjectsV2` so the probe
|
|
1190
|
+
* mirrors the s3-bucket-provider's `emptyBucket` view: a versioned
|
|
1191
|
+
* bucket whose current keys have all been soft-deleted (so
|
|
1192
|
+
* `ListObjectsV2.KeyCount === 0`) still holds prior versions +
|
|
1193
|
+
* delete-markers that the destroy + recreate cycle would lose. Using
|
|
1194
|
+
* the same listing API as the provider ensures the probe and the
|
|
1195
|
+
* destroy path agree on "empty".
|
|
1196
|
+
*
|
|
1197
|
+
* **Soft-fail on probe errors**: if `ListObjectVersions` throws
|
|
1198
|
+
* (permission denied, bucket-not-found mid-flight, transient network
|
|
1199
|
+
* error), logs a warn and leaves the target's `statefulReason` at the
|
|
1200
|
+
* sync result (`null`). The user can decide to proceed without the
|
|
1201
|
+
* probe by passing `--force-stateful-recreation`.
|
|
1202
|
+
*
|
|
1203
|
+
* Returns a NEW array of targets; the input is not mutated. Non-S3
|
|
1204
|
+
* targets and S3 targets whose sync reason is already non-null are
|
|
1205
|
+
* passed through unchanged.
|
|
1206
|
+
*/
|
|
1207
|
+
async function probeStatefulRecreateTargetsAsync(targets, s3Client, logger = getLogger().child("recreate-targets")) {
|
|
1208
|
+
const promoted = [];
|
|
1209
|
+
for (const target of targets) {
|
|
1210
|
+
if (target.resourceType !== "AWS::S3::Bucket" || target.statefulReason !== null) {
|
|
1211
|
+
promoted.push({ ...target });
|
|
1212
|
+
continue;
|
|
1213
|
+
}
|
|
1214
|
+
try {
|
|
1215
|
+
const result = await s3Client.send(new ListObjectVersionsCommand({
|
|
1216
|
+
Bucket: target.physicalId,
|
|
1217
|
+
MaxKeys: 1
|
|
1218
|
+
}));
|
|
1219
|
+
const hasVersions = (result.Versions?.length ?? 0) > 0;
|
|
1220
|
+
const hasDeleteMarkers = (result.DeleteMarkers?.length ?? 0) > 0;
|
|
1221
|
+
if (hasVersions || hasDeleteMarkers) promoted.push({
|
|
1222
|
+
...target,
|
|
1223
|
+
statefulReason: "has-objects"
|
|
1224
|
+
});
|
|
1225
|
+
else promoted.push({ ...target });
|
|
1226
|
+
} catch (e) {
|
|
1227
|
+
logger.warn(`--recreate-via-cc-api: live S3 probe failed for ${target.logicalId} (bucket ${target.physicalId}); leaving stateful guard at the sync result. If the bucket might be non-empty, re-run with --force-stateful-recreation. Underlying error: ${e instanceof Error ? e.message : String(e)}`);
|
|
1228
|
+
promoted.push({ ...target });
|
|
1229
|
+
}
|
|
1230
|
+
}
|
|
1231
|
+
return promoted;
|
|
1232
|
+
}
|
|
1233
|
+
/**
|
|
1234
|
+
* Async re-validation of the stateful-guard slice of a
|
|
1235
|
+
* {@link RecreateTargetsValidation}, after promoting S3 bucket reasons
|
|
1236
|
+
* via {@link probeStatefulRecreateTargetsAsync}.
|
|
1237
|
+
*
|
|
1238
|
+
* Skips the probe entirely when `forceStatefulRecreation: true` — the
|
|
1239
|
+
* sync validation already omits the blocked list in that case, and
|
|
1240
|
+
* skipping avoids an unnecessary AWS round-trip (plus permission-denied
|
|
1241
|
+
* warn-and-skip cycle on low-privilege CI roles).
|
|
1242
|
+
*
|
|
1243
|
+
* Returns a NEW validation; the input is not mutated. Non-stateful
|
|
1244
|
+
* categories (`unknownLogicalIds` / `missingFromState` /
|
|
1245
|
+
* `ambiguousIntent` / `blockedMultiRegionTargets`) are preserved verbatim.
|
|
1246
|
+
*/
|
|
1247
|
+
async function probeAndRevalidateStateful(input) {
|
|
1248
|
+
if (input.forceStatefulRecreation) return input.validation;
|
|
1249
|
+
const promoted = await probeStatefulRecreateTargetsAsync(input.validation.targets, input.s3Client);
|
|
1250
|
+
const blockedStatefulTargets = promoted.filter((t) => t.statefulReason !== null);
|
|
1251
|
+
return {
|
|
1252
|
+
...input.validation,
|
|
1253
|
+
targets: promoted,
|
|
1254
|
+
blockedStatefulTargets
|
|
1255
|
+
};
|
|
1256
|
+
}
|
|
1140
1257
|
|
|
1141
1258
|
//#endregion
|
|
1142
1259
|
//#region src/state/export-index-store.ts
|
|
@@ -33597,18 +33714,22 @@ async function deployCommand(stacks, options) {
|
|
|
33597
33714
|
let recreateViaCcApiTargets;
|
|
33598
33715
|
if (options.recreateViaCcApi?.length) {
|
|
33599
33716
|
const stateForRecreateCheck = await stackStateBackend.getState(stackInfo.stackName, stackRegion);
|
|
33600
|
-
const validation =
|
|
33601
|
-
|
|
33602
|
-
|
|
33603
|
-
|
|
33604
|
-
|
|
33605
|
-
|
|
33606
|
-
|
|
33607
|
-
|
|
33608
|
-
|
|
33609
|
-
|
|
33610
|
-
|
|
33611
|
-
|
|
33717
|
+
const validation = await probeAndRevalidateStateful({
|
|
33718
|
+
validation: validateRecreateTargets({
|
|
33719
|
+
template: stackInfo.template,
|
|
33720
|
+
state: stateForRecreateCheck?.state ?? {
|
|
33721
|
+
version: 7,
|
|
33722
|
+
stackName: stackInfo.stackName,
|
|
33723
|
+
region: stackRegion,
|
|
33724
|
+
resources: {},
|
|
33725
|
+
outputs: {},
|
|
33726
|
+
lastModified: Date.now()
|
|
33727
|
+
},
|
|
33728
|
+
recreateViaCcApi: options.recreateViaCcApi,
|
|
33729
|
+
allowUnsupportedProperties: new Set(options.allowUnsupportedProperties ?? []),
|
|
33730
|
+
forceStatefulRecreation: options.forceStatefulRecreation ?? false
|
|
33731
|
+
}),
|
|
33732
|
+
s3Client: stackAwsClients.s3,
|
|
33612
33733
|
forceStatefulRecreation: options.forceStatefulRecreation ?? false
|
|
33613
33734
|
});
|
|
33614
33735
|
const errorBlock = renderRecreateTargetsErrors(validation);
|
|
@@ -60328,7 +60449,7 @@ function reorderArgs(argv) {
|
|
|
60328
60449
|
*/
|
|
60329
60450
|
async function main() {
|
|
60330
60451
|
const program = new Command();
|
|
60331
|
-
program.name("cdkd").description("CDK Direct - Deploy AWS CDK apps directly via SDK/Cloud Control API").version("0.
|
|
60452
|
+
program.name("cdkd").description("CDK Direct - Deploy AWS CDK apps directly via SDK/Cloud Control API").version("0.163.0");
|
|
60332
60453
|
program.addCommand(createBootstrapCommand());
|
|
60333
60454
|
program.addCommand(createSynthCommand());
|
|
60334
60455
|
program.addCommand(createListCommand());
|