@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 +55 -596
- package/dist/cli.js +181 -30
- package/dist/cli.js.map +3 -3
- package/dist/go-to-k-cdkd-0.30.1.tgz +0 -0
- package/package.json +1 -1
- package/dist/go-to-k-cdkd-0.29.0.tgz +0 -0
package/dist/cli.js
CHANGED
|
@@ -1304,14 +1304,14 @@ async function resolveStateBucketWithDefaultAndSource(cliBucket, region) {
|
|
|
1304
1304
|
const logger = getLogger();
|
|
1305
1305
|
logger.debug("No state bucket specified, resolving default from account...");
|
|
1306
1306
|
const { GetCallerIdentityCommand: GetCallerIdentityCommand11 } = await import("@aws-sdk/client-sts");
|
|
1307
|
-
const { S3Client:
|
|
1307
|
+
const { S3Client: S3Client12 } = await import("@aws-sdk/client-s3");
|
|
1308
1308
|
const { getAwsClients: getAwsClients2 } = await Promise.resolve().then(() => (init_aws_clients(), aws_clients_exports));
|
|
1309
1309
|
const awsClients = getAwsClients2();
|
|
1310
1310
|
const identity = await awsClients.sts.send(new GetCallerIdentityCommand11({}));
|
|
1311
1311
|
const accountId = identity.Account;
|
|
1312
1312
|
const newName = getDefaultStateBucketName(accountId);
|
|
1313
1313
|
const legacyName = getLegacyStateBucketName(accountId, region);
|
|
1314
|
-
const probe = new
|
|
1314
|
+
const probe = new S3Client12({ region: "us-east-1" });
|
|
1315
1315
|
try {
|
|
1316
1316
|
const newExists = await bucketExists(probe, newName);
|
|
1317
1317
|
const legacyExists = await bucketExists(probe, legacyName);
|
|
@@ -12966,7 +12966,9 @@ import {
|
|
|
12966
12966
|
GetFunctionCommand,
|
|
12967
12967
|
ListFunctionsCommand,
|
|
12968
12968
|
ListTagsCommand,
|
|
12969
|
-
ResourceNotFoundException
|
|
12969
|
+
ResourceNotFoundException,
|
|
12970
|
+
waitUntilFunctionActiveV2,
|
|
12971
|
+
waitUntilFunctionUpdatedV2
|
|
12970
12972
|
} from "@aws-sdk/client-lambda";
|
|
12971
12973
|
import {
|
|
12972
12974
|
DescribeNetworkInterfacesCommand,
|
|
@@ -13009,6 +13011,14 @@ var LambdaFunctionProvider = class {
|
|
|
13009
13011
|
eniWaitTimeoutMs = 10 * 60 * 1e3;
|
|
13010
13012
|
eniWaitInitialDelayMs = 1e4;
|
|
13011
13013
|
eniWaitMaxDelayMs = 3e4;
|
|
13014
|
+
// Budget for the post-CreateFunction / post-Update wait that blocks until
|
|
13015
|
+
// Configuration.State === 'Active' and LastUpdateStatus === 'Successful'.
|
|
13016
|
+
// Must NOT be skipped by --no-wait: downstream resources (Custom Resource
|
|
13017
|
+
// Invokes, EventSourceMappings, etc.) cannot operate against a function
|
|
13018
|
+
// still in Pending / InProgress, so this is required for correctness, not
|
|
13019
|
+
// a "convenience wait" like CloudFront / RDS readiness.
|
|
13020
|
+
// Seconds (the SDK waiter contract is seconds, not ms).
|
|
13021
|
+
functionReadyMaxWaitSeconds = 10 * 60;
|
|
13012
13022
|
// delstack-style ENI cleanup tunables.
|
|
13013
13023
|
// - initial sleep: gives AWS time to publish post-detach ENI state via
|
|
13014
13024
|
// DescribeNetworkInterfaces (right after the update, the API can return
|
|
@@ -13077,6 +13087,7 @@ var LambdaFunctionProvider = class {
|
|
|
13077
13087
|
Tags: tags
|
|
13078
13088
|
};
|
|
13079
13089
|
const response = await this.lambdaClient.send(new CreateFunctionCommand(createParams));
|
|
13090
|
+
await this.waitForFunctionActive(logicalId, resourceType, functionName);
|
|
13080
13091
|
this.logger.debug(`Successfully created Lambda function ${logicalId}: ${functionName}`);
|
|
13081
13092
|
return {
|
|
13082
13093
|
physicalId: response.FunctionName || functionName,
|
|
@@ -13086,6 +13097,9 @@ var LambdaFunctionProvider = class {
|
|
|
13086
13097
|
}
|
|
13087
13098
|
};
|
|
13088
13099
|
} catch (error) {
|
|
13100
|
+
if (error instanceof ProvisioningError) {
|
|
13101
|
+
throw error;
|
|
13102
|
+
}
|
|
13089
13103
|
const cause = error instanceof Error ? error : void 0;
|
|
13090
13104
|
throw new ProvisioningError(
|
|
13091
13105
|
`Failed to create Lambda function ${logicalId}: ${error instanceof Error ? error.message : String(error)}`,
|
|
@@ -13142,6 +13156,7 @@ var LambdaFunctionProvider = class {
|
|
|
13142
13156
|
};
|
|
13143
13157
|
await this.lambdaClient.send(new UpdateFunctionConfigurationCommand(configParams));
|
|
13144
13158
|
this.logger.debug(`Updated configuration for Lambda function ${physicalId}`);
|
|
13159
|
+
await this.waitForFunctionUpdated(logicalId, resourceType, physicalId);
|
|
13145
13160
|
}
|
|
13146
13161
|
const newCode = properties["Code"];
|
|
13147
13162
|
const oldCode = previousProperties["Code"];
|
|
@@ -13157,6 +13172,7 @@ var LambdaFunctionProvider = class {
|
|
|
13157
13172
|
};
|
|
13158
13173
|
await this.lambdaClient.send(new UpdateFunctionCodeCommand(codeParams));
|
|
13159
13174
|
this.logger.debug(`Updated code for Lambda function ${physicalId}`);
|
|
13175
|
+
await this.waitForFunctionUpdated(logicalId, resourceType, physicalId);
|
|
13160
13176
|
}
|
|
13161
13177
|
const getResponse = await this.lambdaClient.send(
|
|
13162
13178
|
new GetFunctionCommand({ FunctionName: physicalId })
|
|
@@ -13170,6 +13186,9 @@ var LambdaFunctionProvider = class {
|
|
|
13170
13186
|
}
|
|
13171
13187
|
};
|
|
13172
13188
|
} catch (error) {
|
|
13189
|
+
if (error instanceof ProvisioningError) {
|
|
13190
|
+
throw error;
|
|
13191
|
+
}
|
|
13173
13192
|
const cause = error instanceof Error ? error : void 0;
|
|
13174
13193
|
throw new ProvisioningError(
|
|
13175
13194
|
`Failed to update Lambda function ${logicalId}: ${error instanceof Error ? error.message : String(error)}`,
|
|
@@ -13336,6 +13355,58 @@ var LambdaFunctionProvider = class {
|
|
|
13336
13355
|
* Timeout is a soft warning — downstream Subnet/SG deletion has its own
|
|
13337
13356
|
* retries.
|
|
13338
13357
|
*/
|
|
13358
|
+
/**
|
|
13359
|
+
* Block until the function's State === 'Active'.
|
|
13360
|
+
*
|
|
13361
|
+
* Used after CreateFunction. Wraps the SDK's built-in
|
|
13362
|
+
* `waitUntilFunctionActiveV2` (acceptors: SUCCESS=Active, FAILURE=Failed,
|
|
13363
|
+
* RETRY=Pending). The waiter throws on FAILURE / TIMEOUT — both surface
|
|
13364
|
+
* as `ProvisioningError` with the function-name as physicalId so the
|
|
13365
|
+
* deploy engine's per-resource error handling treats them identically to
|
|
13366
|
+
* a CreateFunction failure.
|
|
13367
|
+
*/
|
|
13368
|
+
async waitForFunctionActive(logicalId, resourceType, functionName) {
|
|
13369
|
+
try {
|
|
13370
|
+
await waitUntilFunctionActiveV2(
|
|
13371
|
+
{ client: this.lambdaClient, maxWaitTime: this.functionReadyMaxWaitSeconds },
|
|
13372
|
+
{ FunctionName: functionName }
|
|
13373
|
+
);
|
|
13374
|
+
} catch (error) {
|
|
13375
|
+
const cause = error instanceof Error ? error : void 0;
|
|
13376
|
+
throw new ProvisioningError(
|
|
13377
|
+
`Lambda function ${logicalId} did not reach Active state: ${error instanceof Error ? error.message : String(error)}`,
|
|
13378
|
+
resourceType,
|
|
13379
|
+
logicalId,
|
|
13380
|
+
functionName,
|
|
13381
|
+
cause
|
|
13382
|
+
);
|
|
13383
|
+
}
|
|
13384
|
+
}
|
|
13385
|
+
/**
|
|
13386
|
+
* Block until the function's LastUpdateStatus === 'Successful'.
|
|
13387
|
+
*
|
|
13388
|
+
* Used after UpdateFunctionConfiguration / UpdateFunctionCode. Wraps the
|
|
13389
|
+
* SDK's `waitUntilFunctionUpdatedV2` (acceptors: SUCCESS=Successful,
|
|
13390
|
+
* FAILURE=Failed, RETRY=InProgress). Same error-wrapping contract as
|
|
13391
|
+
* `waitForFunctionActive`.
|
|
13392
|
+
*/
|
|
13393
|
+
async waitForFunctionUpdated(logicalId, resourceType, functionName) {
|
|
13394
|
+
try {
|
|
13395
|
+
await waitUntilFunctionUpdatedV2(
|
|
13396
|
+
{ client: this.lambdaClient, maxWaitTime: this.functionReadyMaxWaitSeconds },
|
|
13397
|
+
{ FunctionName: functionName }
|
|
13398
|
+
);
|
|
13399
|
+
} catch (error) {
|
|
13400
|
+
const cause = error instanceof Error ? error : void 0;
|
|
13401
|
+
throw new ProvisioningError(
|
|
13402
|
+
`Lambda function ${logicalId} update did not complete: ${error instanceof Error ? error.message : String(error)}`,
|
|
13403
|
+
resourceType,
|
|
13404
|
+
logicalId,
|
|
13405
|
+
functionName,
|
|
13406
|
+
cause
|
|
13407
|
+
);
|
|
13408
|
+
}
|
|
13409
|
+
}
|
|
13339
13410
|
/**
|
|
13340
13411
|
* Poll GetFunction until LastUpdateStatus is no longer `InProgress`.
|
|
13341
13412
|
*
|
|
@@ -13348,6 +13419,12 @@ var LambdaFunctionProvider = class {
|
|
|
13348
13419
|
* Bounded by eniWaitTimeoutMs (10min) and treated as a soft warning on
|
|
13349
13420
|
* timeout: the subsequent ENI cleanup loop and downstream retries cover
|
|
13350
13421
|
* the residual edge case.
|
|
13422
|
+
*
|
|
13423
|
+
* NOTE: deliberately separate from `waitForFunctionUpdated` (which uses
|
|
13424
|
+
* the SDK's `waitUntilFunctionUpdatedV2` and throws on FAILURE). The
|
|
13425
|
+
* pre-delete path needs a more lenient acceptor: if a prior update
|
|
13426
|
+
* failed, we still want to proceed with DeleteFunction rather than
|
|
13427
|
+
* abort, because the function is going away anyway.
|
|
13351
13428
|
*/
|
|
13352
13429
|
async waitForLambdaUpdateCompleted(functionName) {
|
|
13353
13430
|
const start = Date.now();
|
|
@@ -35330,6 +35407,7 @@ import {
|
|
|
35330
35407
|
waitUntilStackUpdateComplete,
|
|
35331
35408
|
waitUntilStackDeleteComplete
|
|
35332
35409
|
} from "@aws-sdk/client-cloudformation";
|
|
35410
|
+
import { S3Client as S3Client11, PutObjectCommand as PutObjectCommand5, DeleteObjectCommand as DeleteObjectCommand4 } from "@aws-sdk/client-s3";
|
|
35333
35411
|
var STABLE_TERMINAL_STATUSES = /* @__PURE__ */ new Set([
|
|
35334
35412
|
"CREATE_COMPLETE",
|
|
35335
35413
|
"UPDATE_COMPLETE",
|
|
@@ -35338,9 +35416,11 @@ var STABLE_TERMINAL_STATUSES = /* @__PURE__ */ new Set([
|
|
|
35338
35416
|
"IMPORT_ROLLBACK_COMPLETE"
|
|
35339
35417
|
]);
|
|
35340
35418
|
var TEMPLATE_BODY_LIMIT = 51200;
|
|
35419
|
+
var TEMPLATE_URL_LIMIT = 1048576;
|
|
35420
|
+
var MIGRATE_TMP_PREFIX = "cdkd-migrate-tmp";
|
|
35341
35421
|
async function retireCloudFormationStack(options) {
|
|
35342
35422
|
const logger = getLogger();
|
|
35343
|
-
const { cfnStackName, cfnClient, yes } = options;
|
|
35423
|
+
const { cfnStackName, cfnClient, yes, stateBucket, s3ClientOpts } = options;
|
|
35344
35424
|
logger.info(`[1/4] Inspecting CloudFormation stack '${cfnStackName}'...`);
|
|
35345
35425
|
const desc = await cfnClient.send(new DescribeStacksCommand({ StackName: cfnStackName }));
|
|
35346
35426
|
const stack = desc.Stacks?.[0];
|
|
@@ -35370,39 +35450,69 @@ async function retireCloudFormationStack(options) {
|
|
|
35370
35450
|
return { outcome: "cancelled" };
|
|
35371
35451
|
}
|
|
35372
35452
|
}
|
|
35373
|
-
let updateRan = false;
|
|
35374
35453
|
if (!modified) {
|
|
35375
35454
|
logger.info(`[2/4] Template already has Retain on every resource \u2014 skipping UpdateStack.`);
|
|
35376
35455
|
} else {
|
|
35377
35456
|
logger.info(`[2/4] Injected DeletionPolicy=Retain and UpdateReplacePolicy=Retain.`);
|
|
35378
|
-
if (newBody.length >
|
|
35457
|
+
if (newBody.length > TEMPLATE_URL_LIMIT) {
|
|
35379
35458
|
throw new Error(
|
|
35380
|
-
`Modified template is ${newBody.length} bytes, exceeds the
|
|
35459
|
+
`Modified template is ${newBody.length} bytes, exceeds the CloudFormation UpdateStack TemplateURL limit (${TEMPLATE_URL_LIMIT}). cdkd state has already been written; retire the stack manually with (1) shrink the template, then (2) UpdateStack with Retain policies, (3) DeleteStack \u2014 or split the stack and retry.`
|
|
35381
35460
|
);
|
|
35382
35461
|
}
|
|
35383
|
-
|
|
35384
|
-
|
|
35385
|
-
|
|
35386
|
-
|
|
35387
|
-
|
|
35388
|
-
|
|
35389
|
-
|
|
35390
|
-
})
|
|
35462
|
+
let updateInput;
|
|
35463
|
+
let s3Cleanup;
|
|
35464
|
+
if (newBody.length <= TEMPLATE_BODY_LIMIT) {
|
|
35465
|
+
updateInput = { TemplateBody: newBody };
|
|
35466
|
+
} else {
|
|
35467
|
+
logger.info(
|
|
35468
|
+
` Template is ${newBody.length} bytes (over ${TEMPLATE_BODY_LIMIT} inline limit) \u2014 uploading to state bucket '${stateBucket}'.`
|
|
35391
35469
|
);
|
|
35392
|
-
|
|
35393
|
-
|
|
35394
|
-
|
|
35395
|
-
|
|
35396
|
-
|
|
35397
|
-
}
|
|
35398
|
-
|
|
35399
|
-
|
|
35470
|
+
const uploaded = await uploadTemplateForUpdateStack({
|
|
35471
|
+
bucket: stateBucket,
|
|
35472
|
+
body: newBody,
|
|
35473
|
+
cfnStackName,
|
|
35474
|
+
...s3ClientOpts && { s3ClientOpts }
|
|
35475
|
+
});
|
|
35476
|
+
updateInput = { TemplateURL: uploaded.url };
|
|
35477
|
+
s3Cleanup = uploaded.cleanup;
|
|
35400
35478
|
}
|
|
35401
|
-
|
|
35402
|
-
|
|
35403
|
-
|
|
35404
|
-
|
|
35405
|
-
|
|
35479
|
+
try {
|
|
35480
|
+
logger.info(`[3/4] Updating CloudFormation stack with Retain policies...`);
|
|
35481
|
+
let updateRan = false;
|
|
35482
|
+
try {
|
|
35483
|
+
await cfnClient.send(
|
|
35484
|
+
new UpdateStackCommand({
|
|
35485
|
+
StackName: cfnStackName,
|
|
35486
|
+
...updateInput,
|
|
35487
|
+
Capabilities: capabilities
|
|
35488
|
+
})
|
|
35489
|
+
);
|
|
35490
|
+
updateRan = true;
|
|
35491
|
+
} catch (err) {
|
|
35492
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
35493
|
+
if (/No updates are to be performed/i.test(msg)) {
|
|
35494
|
+
logger.info(` CloudFormation reports no updates needed \u2014 proceeding to delete.`);
|
|
35495
|
+
} else {
|
|
35496
|
+
throw err;
|
|
35497
|
+
}
|
|
35498
|
+
}
|
|
35499
|
+
if (updateRan) {
|
|
35500
|
+
await waitUntilStackUpdateComplete(
|
|
35501
|
+
{ client: cfnClient, maxWaitTime: 1800 },
|
|
35502
|
+
{ StackName: cfnStackName }
|
|
35503
|
+
);
|
|
35504
|
+
}
|
|
35505
|
+
} finally {
|
|
35506
|
+
if (s3Cleanup) {
|
|
35507
|
+
try {
|
|
35508
|
+
await s3Cleanup();
|
|
35509
|
+
} catch (cleanupErr) {
|
|
35510
|
+
const msg = cleanupErr instanceof Error ? cleanupErr.message : String(cleanupErr);
|
|
35511
|
+
logger.warn(
|
|
35512
|
+
`Failed to delete temporary template upload from '${stateBucket}'. Clean up manually under prefix '${MIGRATE_TMP_PREFIX}/'. Cause: ${msg}`
|
|
35513
|
+
);
|
|
35514
|
+
}
|
|
35515
|
+
}
|
|
35406
35516
|
}
|
|
35407
35517
|
}
|
|
35408
35518
|
logger.info(`[4/4] Deleting CloudFormation stack '${cfnStackName}' (resources retained)...`);
|
|
@@ -35416,6 +35526,41 @@ async function retireCloudFormationStack(options) {
|
|
|
35416
35526
|
);
|
|
35417
35527
|
return { outcome: modified ? "retired" : "no-template-change" };
|
|
35418
35528
|
}
|
|
35529
|
+
async function uploadTemplateForUpdateStack(args) {
|
|
35530
|
+
const { bucket, body, cfnStackName, s3ClientOpts } = args;
|
|
35531
|
+
const region = await resolveBucketRegion(bucket, {
|
|
35532
|
+
...s3ClientOpts?.profile && { profile: s3ClientOpts.profile },
|
|
35533
|
+
...s3ClientOpts?.credentials && { credentials: s3ClientOpts.credentials }
|
|
35534
|
+
});
|
|
35535
|
+
const s3 = new S3Client11({
|
|
35536
|
+
region,
|
|
35537
|
+
...s3ClientOpts?.profile && { profile: s3ClientOpts.profile },
|
|
35538
|
+
...s3ClientOpts?.credentials && { credentials: s3ClientOpts.credentials }
|
|
35539
|
+
});
|
|
35540
|
+
const key = `${MIGRATE_TMP_PREFIX}/${cfnStackName}/${Date.now()}.json`;
|
|
35541
|
+
try {
|
|
35542
|
+
await s3.send(
|
|
35543
|
+
new PutObjectCommand5({
|
|
35544
|
+
Bucket: bucket,
|
|
35545
|
+
Key: key,
|
|
35546
|
+
Body: body,
|
|
35547
|
+
ContentType: "application/json"
|
|
35548
|
+
})
|
|
35549
|
+
);
|
|
35550
|
+
} catch (err) {
|
|
35551
|
+
s3.destroy();
|
|
35552
|
+
throw err;
|
|
35553
|
+
}
|
|
35554
|
+
const url = `https://${bucket}.s3.${region}.amazonaws.com/${key}`;
|
|
35555
|
+
const cleanup = async () => {
|
|
35556
|
+
try {
|
|
35557
|
+
await s3.send(new DeleteObjectCommand4({ Bucket: bucket, Key: key }));
|
|
35558
|
+
} finally {
|
|
35559
|
+
s3.destroy();
|
|
35560
|
+
}
|
|
35561
|
+
};
|
|
35562
|
+
return { url, cleanup };
|
|
35563
|
+
}
|
|
35419
35564
|
function injectRetainPolicies(templateBody, cfnStackName) {
|
|
35420
35565
|
let parsed;
|
|
35421
35566
|
try {
|
|
@@ -35695,7 +35840,13 @@ async function importCommand(stackArg, options) {
|
|
|
35695
35840
|
await retireCloudFormationStack({
|
|
35696
35841
|
cfnStackName: migrationCfnStackName,
|
|
35697
35842
|
cfnClient: awsClients.cloudFormation,
|
|
35698
|
-
yes: options.yes
|
|
35843
|
+
yes: options.yes,
|
|
35844
|
+
// Reuse cdkd's state bucket as transient storage for the
|
|
35845
|
+
// Retain-injected template when it exceeds the 51,200-byte
|
|
35846
|
+
// inline UpdateStack limit. Forward `--profile` so the
|
|
35847
|
+
// upload identity matches the one that just wrote cdkd state.
|
|
35848
|
+
stateBucket,
|
|
35849
|
+
...options.profile && { s3ClientOpts: { profile: options.profile } }
|
|
35699
35850
|
});
|
|
35700
35851
|
}
|
|
35701
35852
|
} finally {
|
|
@@ -35988,7 +36139,7 @@ function reorderArgs(argv) {
|
|
|
35988
36139
|
}
|
|
35989
36140
|
async function main() {
|
|
35990
36141
|
const program = new Command13();
|
|
35991
|
-
program.name("cdkd").description("CDK Direct - Deploy AWS CDK apps directly via SDK/Cloud Control API").version("0.
|
|
36142
|
+
program.name("cdkd").description("CDK Direct - Deploy AWS CDK apps directly via SDK/Cloud Control API").version("0.30.1");
|
|
35992
36143
|
program.addCommand(createBootstrapCommand());
|
|
35993
36144
|
program.addCommand(createSynthCommand());
|
|
35994
36145
|
program.addCommand(createListCommand());
|