@go-to-k/cdkd 0.164.0 → 0.165.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 +117 -3
- package/dist/cli.js.map +1 -1
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -1256,6 +1256,67 @@ async function probeAndRevalidateStateful(input) {
|
|
|
1256
1256
|
};
|
|
1257
1257
|
}
|
|
1258
1258
|
|
|
1259
|
+
//#endregion
|
|
1260
|
+
//#region src/cli/commands/recreate-downstream-consumers.ts
|
|
1261
|
+
/**
|
|
1262
|
+
* Walk every stack in the cdkd state bucket and find consumers whose
|
|
1263
|
+
* `imports[]` reference the producer `(producerStack, producerRegion)`.
|
|
1264
|
+
*
|
|
1265
|
+
* Skips the producer itself (self-imports are invalid in CFn / cdkd).
|
|
1266
|
+
*
|
|
1267
|
+
* Soft-fails per consumer: an unreadable state file is logged at debug
|
|
1268
|
+
* and skipped — the warn block falls back to "we couldn't enumerate
|
|
1269
|
+
* downstream consumers" rather than blocking the deploy. The caller
|
|
1270
|
+
* always proceeds; this is informational only.
|
|
1271
|
+
*/
|
|
1272
|
+
async function findDownstreamConsumers(input) {
|
|
1273
|
+
const logger = getLogger().child("recreate-downstream");
|
|
1274
|
+
let refs;
|
|
1275
|
+
try {
|
|
1276
|
+
refs = await input.stateBackend.listStacks();
|
|
1277
|
+
} catch (err) {
|
|
1278
|
+
logger.debug(`findDownstreamConsumers: listStacks failed; falling back to empty enumeration. ${err instanceof Error ? err.message : String(err)}`);
|
|
1279
|
+
return [];
|
|
1280
|
+
}
|
|
1281
|
+
return (await Promise.all(refs.map(async (ref) => {
|
|
1282
|
+
const region = ref.region ?? input.baseRegion;
|
|
1283
|
+
if (ref.stackName === input.producerStack && region === input.producerRegion) return null;
|
|
1284
|
+
try {
|
|
1285
|
+
const imports = (await input.stateBackend.getState(ref.stackName, region))?.state.imports;
|
|
1286
|
+
if (!imports || imports.length === 0) return null;
|
|
1287
|
+
const matches = imports.filter((entry) => entry.sourceStack === input.producerStack && entry.sourceRegion === input.producerRegion);
|
|
1288
|
+
if (matches.length === 0) return null;
|
|
1289
|
+
return matches.map((entry) => ({
|
|
1290
|
+
consumerStack: ref.stackName,
|
|
1291
|
+
consumerRegion: region,
|
|
1292
|
+
exportName: entry.exportName,
|
|
1293
|
+
intrinsic: "ImportValue"
|
|
1294
|
+
}));
|
|
1295
|
+
} catch (err) {
|
|
1296
|
+
logger.debug(`findDownstreamConsumers: skip ${ref.stackName} (${region}); ${err instanceof Error ? err.message : String(err)}`);
|
|
1297
|
+
return null;
|
|
1298
|
+
}
|
|
1299
|
+
}))).filter((r) => r !== null).flat();
|
|
1300
|
+
}
|
|
1301
|
+
/**
|
|
1302
|
+
* Render the per-consumer subset of the warn block as a multi-line
|
|
1303
|
+
* string suitable for piping into the logger. Returns `null` when the
|
|
1304
|
+
* enumeration is empty (caller skips the subsection in that case).
|
|
1305
|
+
*
|
|
1306
|
+
* Shape:
|
|
1307
|
+
* ```
|
|
1308
|
+
* Downstream consumers of <ProducerStack>'s outputs (will need re-deploy):
|
|
1309
|
+
* - StackB (region) reads ExportName via Fn::ImportValue
|
|
1310
|
+
* - StackC (region) reads OtherExport via Fn::ImportValue
|
|
1311
|
+
* ```
|
|
1312
|
+
*/
|
|
1313
|
+
function renderDownstreamConsumers(producerStack, consumers) {
|
|
1314
|
+
if (consumers.length === 0) return null;
|
|
1315
|
+
const lines = [` Downstream consumers of ${producerStack}'s outputs (will need re-deploy after this run):`];
|
|
1316
|
+
for (const c of consumers) lines.push(` - ${c.consumerStack} (${c.consumerRegion}) reads ${c.exportName} via Fn::${c.intrinsic}`);
|
|
1317
|
+
return lines.join("\n");
|
|
1318
|
+
}
|
|
1319
|
+
|
|
1259
1320
|
//#endregion
|
|
1260
1321
|
//#region src/cli/commands/recreate-confirm-prompt.ts
|
|
1261
1322
|
/**
|
|
@@ -1295,6 +1356,10 @@ async function promptRecreateConfirm(input) {
|
|
|
1295
1356
|
logger.warn(` - ${dataLossPrefix}${t.logicalId} (${t.resourceType})${stateNote}`);
|
|
1296
1357
|
if (stateful) logger.warn(` DATA: all data in ${t.logicalId} will be lost (no automatic data migration)`);
|
|
1297
1358
|
}
|
|
1359
|
+
if (input.downstreamConsumers && input.downstreamConsumers.length > 0) {
|
|
1360
|
+
const rendered = renderDownstreamConsumers(input.stackName, input.downstreamConsumers);
|
|
1361
|
+
if (rendered) logger.warn(rendered);
|
|
1362
|
+
}
|
|
1298
1363
|
logger.warn(" The destroy + recreate cycle is per-resource; sibling resources are unaffected. Downstream consumers of any recreated resource's outputs (Fn::GetStackOutput / Fn::ImportValue) will need a re-deploy to see the new physical id.");
|
|
1299
1364
|
if (input.yes) return true;
|
|
1300
1365
|
if (process.stdin.isTTY !== true) throw new Error("--recreate-via-cc-api confirm prompt cannot run in a non-interactive environment. Pass --yes / -y to confirm the destroy + recreate cycle, or run the deploy from a real terminal.");
|
|
@@ -33793,10 +33858,17 @@ async function deployCommand(stacks, options) {
|
|
|
33793
33858
|
if (errorBlock) throw new CdkdError(errorBlock, "RECREATE_VIA_CC_API_INVALID");
|
|
33794
33859
|
recreateViaCcApiTargets = new Set(validation.targets.map((t) => t.logicalId));
|
|
33795
33860
|
if (recreateViaCcApiTargets.size > 0) {
|
|
33861
|
+
const downstreamConsumers = await findDownstreamConsumers({
|
|
33862
|
+
producerStack: stackInfo.stackName,
|
|
33863
|
+
producerRegion: stackRegion,
|
|
33864
|
+
stateBackend: stackStateBackend,
|
|
33865
|
+
baseRegion
|
|
33866
|
+
});
|
|
33796
33867
|
if (!await promptRecreateConfirm({
|
|
33797
33868
|
stackName: stackInfo.stackName,
|
|
33798
33869
|
targets: validation.targets,
|
|
33799
|
-
yes: options.yes ?? false
|
|
33870
|
+
yes: options.yes ?? false,
|
|
33871
|
+
downstreamConsumers
|
|
33800
33872
|
})) return;
|
|
33801
33873
|
}
|
|
33802
33874
|
}
|
|
@@ -58752,6 +58824,7 @@ async function localInvokeCommand(target, options) {
|
|
|
58752
58824
|
region: options.region
|
|
58753
58825
|
});
|
|
58754
58826
|
await ensureDockerAvailable();
|
|
58827
|
+
const profileCredentials = options.profile ? await resolveProfileCredentials(options.profile) : void 0;
|
|
58755
58828
|
const appCmd = resolveApp(options.app);
|
|
58756
58829
|
if (!appCmd) throw new Error("No CDK app specified. Pass --app, set CDKD_APP, or add \"app\" to cdk.json.");
|
|
58757
58830
|
logger.info("Synthesizing CDK app...");
|
|
@@ -58849,7 +58922,10 @@ async function localInvokeCommand(target, options) {
|
|
|
58849
58922
|
logger.warn(`--assume-role: STS AssumeRole(${resolvedAssumeRoleArn}) failed: ${reason}. Falling back to the developer's shell credentials.`);
|
|
58850
58923
|
}
|
|
58851
58924
|
}
|
|
58852
|
-
if (!assumeSucceeded)
|
|
58925
|
+
if (!assumeSucceeded) {
|
|
58926
|
+
forwardAwsEnv(dockerEnv);
|
|
58927
|
+
applyProfileCredentialsOverlay(dockerEnv, profileCredentials, false);
|
|
58928
|
+
}
|
|
58853
58929
|
let debugPort;
|
|
58854
58930
|
if (options.debugPort) {
|
|
58855
58931
|
debugPort = Number(options.debugPort);
|
|
@@ -59286,6 +59362,44 @@ function forwardAwsEnv(env) {
|
|
|
59286
59362
|
}
|
|
59287
59363
|
}
|
|
59288
59364
|
/**
|
|
59365
|
+
* Issue #657: overlay `--profile <p>`-resolved credentials onto the
|
|
59366
|
+
* Lambda container's env block AFTER `forwardAwsEnv` has copied
|
|
59367
|
+
* `process.env.AWS_*`. The overlay covers SSO / IAM Identity Center /
|
|
59368
|
+
* fromIni / role-assumption profiles uniformly (resolved via the SDK's
|
|
59369
|
+
* default credential chain in `resolveProfileCredentials`). Without
|
|
59370
|
+
* this overlay, a dev who runs `cdkd local invoke --profile dev`
|
|
59371
|
+
* AND has no `AWS_ACCESS_KEY_ID` env var (the common SSO / Identity
|
|
59372
|
+
* Center case) sees the Lambda boot with no creds → handler's AWS SDK
|
|
59373
|
+
* call fails with `Could not load credentials from any providers`.
|
|
59374
|
+
*
|
|
59375
|
+
* Precedence (codifies existing semantics + this new layer):
|
|
59376
|
+
* 1. `--assume-role <arn>` (per-Lambda STS creds) — unchanged
|
|
59377
|
+
* 2. NEW: `--profile <p>` resolved + cached (this helper)
|
|
59378
|
+
* 3. `process.env.AWS_*` forwarded — when `--profile` not set
|
|
59379
|
+
*
|
|
59380
|
+
* Region from `forwardAwsEnv` is preserved — only the credential
|
|
59381
|
+
* triple is overlaid.
|
|
59382
|
+
*
|
|
59383
|
+
* When the resolved profile is long-lived (no `sessionToken`), any
|
|
59384
|
+
* inherited `AWS_SESSION_TOKEN` is stripped — a mismatched (long-
|
|
59385
|
+
* lived AKID + foreign session) would otherwise cause an SDK error
|
|
59386
|
+
* inside the container.
|
|
59387
|
+
*
|
|
59388
|
+
* No-op when `profileCreds` is `undefined` (profile not set) or when
|
|
59389
|
+
* `assumeRoleActive` is true (assume-role already won; its STS-issued
|
|
59390
|
+
* creds must not be clobbered by the profile overlay).
|
|
59391
|
+
*
|
|
59392
|
+
* Exported for unit-test isolation (see `local-invoke-profile-creds.test.ts`).
|
|
59393
|
+
*/
|
|
59394
|
+
function applyProfileCredentialsOverlay(env, profileCreds, assumeRoleActive) {
|
|
59395
|
+
if (!profileCreds) return;
|
|
59396
|
+
if (assumeRoleActive) return;
|
|
59397
|
+
env["AWS_ACCESS_KEY_ID"] = profileCreds.accessKeyId;
|
|
59398
|
+
env["AWS_SECRET_ACCESS_KEY"] = profileCreds.secretAccessKey;
|
|
59399
|
+
if (profileCreds.sessionToken) env["AWS_SESSION_TOKEN"] = profileCreds.sessionToken;
|
|
59400
|
+
else delete env["AWS_SESSION_TOKEN"];
|
|
59401
|
+
}
|
|
59402
|
+
/**
|
|
59289
59403
|
* Materialize an inline Lambda body (`Code.ZipFile`) to a tmpdir and
|
|
59290
59404
|
* return the directory the container should mount at /var/task. The
|
|
59291
59405
|
* filename is derived from the function's Handler property and the
|
|
@@ -60505,7 +60619,7 @@ function reorderArgs(argv) {
|
|
|
60505
60619
|
*/
|
|
60506
60620
|
async function main() {
|
|
60507
60621
|
const program = new Command();
|
|
60508
|
-
program.name("cdkd").description("CDK Direct - Deploy AWS CDK apps directly via SDK/Cloud Control API").version("0.
|
|
60622
|
+
program.name("cdkd").description("CDK Direct - Deploy AWS CDK apps directly via SDK/Cloud Control API").version("0.165.0");
|
|
60509
60623
|
program.addCommand(createBootstrapCommand());
|
|
60510
60624
|
program.addCommand(createSynthCommand());
|
|
60511
60625
|
program.addCommand(createListCommand());
|