@go-to-k/cdkd 0.86.0 → 0.87.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 +154 -27
- package/dist/cli.js.map +3 -3
- package/dist/go-to-k-cdkd-0.87.0.tgz +0 -0
- package/package.json +1 -1
- package/dist/go-to-k-cdkd-0.86.0.tgz +0 -0
package/dist/cli.js
CHANGED
|
@@ -79270,7 +79270,8 @@ import {
|
|
|
79270
79270
|
DescribeTypeCommand,
|
|
79271
79271
|
DeleteChangeSetCommand,
|
|
79272
79272
|
waitUntilChangeSetCreateComplete,
|
|
79273
|
-
waitUntilStackImportComplete
|
|
79273
|
+
waitUntilStackImportComplete,
|
|
79274
|
+
waitUntilStackUpdateComplete as waitUntilStackUpdateComplete2
|
|
79274
79275
|
} from "@aws-sdk/client-cloudformation";
|
|
79275
79276
|
init_aws_clients();
|
|
79276
79277
|
var NEVER_IMPORTABLE_TYPES = /* @__PURE__ */ new Set([
|
|
@@ -79457,39 +79458,90 @@ async function exportCommand(stackArg, options) {
|
|
|
79457
79458
|
}
|
|
79458
79459
|
}
|
|
79459
79460
|
try {
|
|
79460
|
-
const {
|
|
79461
|
-
|
|
79461
|
+
const { phase1Imports, phase2Creates, blocked } = await buildImportPlan(
|
|
79462
|
+
state,
|
|
79463
|
+
template,
|
|
79464
|
+
awsClients.cloudFormation
|
|
79465
|
+
);
|
|
79466
|
+
if (blocked.length > 0) {
|
|
79467
|
+
logger.error("The following resources block migration:");
|
|
79468
|
+
for (const b of blocked) {
|
|
79469
|
+
logger.error(` - ${b.logicalId} (${b.resourceType}): ${b.reason}`);
|
|
79470
|
+
}
|
|
79471
|
+
throw new Error(
|
|
79472
|
+
`${blocked.length} resource(s) block migration. Either destroy them first (cdkd destroy / cdkd state destroy cherry-picked), or remove them from the CDK app and re-synthesize.`
|
|
79473
|
+
);
|
|
79474
|
+
}
|
|
79475
|
+
if (phase2Creates.length > 0 && !options.includeNonImportable) {
|
|
79462
79476
|
logger.error("The following resources cannot be imported into CloudFormation:");
|
|
79463
|
-
for (const
|
|
79464
|
-
logger.error(` - ${
|
|
79477
|
+
for (const p of phase2Creates) {
|
|
79478
|
+
logger.error(` - ${p.logicalId} (${p.resourceType}): CFn cannot import this type`);
|
|
79465
79479
|
}
|
|
79466
79480
|
throw new Error(
|
|
79467
|
-
`${
|
|
79481
|
+
`${phase2Creates.length} non-importable resource(s) detected (Custom::*). Pass --include-non-importable to run a 2-phase migration: phase 1 imports the importable resources; phase 2 CFn-CREATEs the non-importable ones (re-invoking each Custom Resource's backing Lambda onCreate handler \u2014 make sure those are idempotent). Or destroy these resources first.`
|
|
79468
79482
|
);
|
|
79469
79483
|
}
|
|
79470
|
-
if (
|
|
79471
|
-
logger.warn("No resources to
|
|
79484
|
+
if (phase1Imports.length === 0 && phase2Creates.length === 0) {
|
|
79485
|
+
logger.warn("No resources to migrate \u2014 cdkd state is empty.");
|
|
79472
79486
|
return;
|
|
79473
79487
|
}
|
|
79474
|
-
|
|
79488
|
+
if (phase1Imports.length === 0) {
|
|
79489
|
+
throw new Error(
|
|
79490
|
+
"No importable resources in the template. CloudFormation IMPORT changeset requires at least one importable resource for phase 1."
|
|
79491
|
+
);
|
|
79492
|
+
}
|
|
79493
|
+
printPlan(phase1Imports, cfnStackName);
|
|
79494
|
+
if (phase2Creates.length > 0) {
|
|
79495
|
+
logger.info(`Phase 2 will CREATE ${phase2Creates.length} non-importable resource(s):`);
|
|
79496
|
+
for (const p of phase2Creates) {
|
|
79497
|
+
logger.info(` ${p.logicalId} (${p.resourceType})`);
|
|
79498
|
+
}
|
|
79499
|
+
logger.info("");
|
|
79500
|
+
}
|
|
79475
79501
|
if (options.dryRun) {
|
|
79476
79502
|
logger.info("--dry-run: no CloudFormation changeset will be created.");
|
|
79477
79503
|
return;
|
|
79478
79504
|
}
|
|
79479
79505
|
if (!options.yes) {
|
|
79506
|
+
const phase2Note = phase2Creates.length > 0 ? ` Phase 2 will then CREATE ${phase2Creates.length} non-importable resource(s) (invoking each Custom Resource's onCreate handler).` : "";
|
|
79480
79507
|
const ok = await confirmPrompt6(
|
|
79481
|
-
`Create CloudFormation stack '${cfnStackName}' by importing ${
|
|
79508
|
+
`Create CloudFormation stack '${cfnStackName}' by importing ${phase1Imports.length} resource(s) from cdkd state '${resolvedStackName}' (${targetRegion})?` + phase2Note + ` AWS resources are unchanged on import. cdkd state for '${resolvedStackName}' will be deleted on success.`
|
|
79482
79509
|
);
|
|
79483
79510
|
if (!ok) {
|
|
79484
79511
|
logger.info("Migration cancelled. cdkd state and CloudFormation are unchanged.");
|
|
79485
79512
|
return;
|
|
79486
79513
|
}
|
|
79487
79514
|
}
|
|
79488
|
-
const
|
|
79489
|
-
await executeImportChangeSet(
|
|
79515
|
+
const phase1Template = filterTemplateForImport(template, phase1Imports);
|
|
79516
|
+
await executeImportChangeSet(
|
|
79517
|
+
awsClients.cloudFormation,
|
|
79518
|
+
cfnStackName,
|
|
79519
|
+
phase1Template,
|
|
79520
|
+
phase1Imports
|
|
79521
|
+
);
|
|
79490
79522
|
logger.info(
|
|
79491
|
-
`\u2713 CloudFormation stack '${cfnStackName}' created via IMPORT. ${
|
|
79523
|
+
`\u2713 Phase 1: CloudFormation stack '${cfnStackName}' created via IMPORT. ${phase1Imports.length} resource(s) imported.`
|
|
79492
79524
|
);
|
|
79525
|
+
if (phase2Creates.length > 0) {
|
|
79526
|
+
try {
|
|
79527
|
+
await executeUpdateChangeSet(awsClients.cloudFormation, cfnStackName, template);
|
|
79528
|
+
logger.info(`\u2713 Phase 2: ${phase2Creates.length} non-importable resource(s) CREATEd.`);
|
|
79529
|
+
} catch (err) {
|
|
79530
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
79531
|
+
throw new Error(
|
|
79532
|
+
`Phase 1 (IMPORT) succeeded; phase 2 (UPDATE) failed: ${msg}
|
|
79533
|
+
|
|
79534
|
+
The CloudFormation stack '${cfnStackName}' now contains the imported resources but is missing the ${phase2Creates.length} non-importable resource(s). cdkd state is UNCHANGED so you can inspect what's in it, but DO NOT run \`cdkd deploy\` against this stack (the imported resources are now CFn-managed). To recover:
|
|
79535
|
+
1. Fix the failure cause (typically an onCreate Lambda error).
|
|
79536
|
+
2. Re-run the phase 2 UPDATE manually with the full synth template:
|
|
79537
|
+
aws cloudformation create-change-set --stack-name ${cfnStackName} \\
|
|
79538
|
+
--change-set-name cdkd-phase2-retry --change-set-type UPDATE \\
|
|
79539
|
+
--template-body file://<full-template.json>
|
|
79540
|
+
3. Once phase 2 succeeds, run: cdkd state orphan ${resolvedStackName}
|
|
79541
|
+
to clean up cdkd's stale state record.`
|
|
79542
|
+
);
|
|
79543
|
+
}
|
|
79544
|
+
}
|
|
79493
79545
|
await stateBackend.deleteState(resolvedStackName, targetRegion);
|
|
79494
79546
|
logger.info(
|
|
79495
79547
|
`cdkd state for '${resolvedStackName}' (${targetRegion}) removed. Manage the stack with 'cdk deploy' or 'aws cloudformation' from here on.`
|
|
@@ -79585,13 +79637,17 @@ async function assertCfnStackAbsent(cfnClient, stackName) {
|
|
|
79585
79637
|
throw err;
|
|
79586
79638
|
}
|
|
79587
79639
|
}
|
|
79640
|
+
function isPhase2CreatableType(resourceType) {
|
|
79641
|
+
return resourceType.startsWith("Custom::");
|
|
79642
|
+
}
|
|
79588
79643
|
async function buildImportPlan(state, template, cfnClient) {
|
|
79589
79644
|
const templateResources = template["Resources"];
|
|
79590
79645
|
if (!templateResources || typeof templateResources !== "object" || Array.isArray(templateResources)) {
|
|
79591
79646
|
throw new Error("Template has no Resources section.");
|
|
79592
79647
|
}
|
|
79593
|
-
const
|
|
79594
|
-
const
|
|
79648
|
+
const phase1Imports = [];
|
|
79649
|
+
const phase2Creates = [];
|
|
79650
|
+
const blocked = [];
|
|
79595
79651
|
const identifierCache = /* @__PURE__ */ new Map();
|
|
79596
79652
|
for (const [logicalId, raw] of Object.entries(templateResources)) {
|
|
79597
79653
|
if (!raw || typeof raw !== "object" || Array.isArray(raw))
|
|
@@ -79600,19 +79656,24 @@ async function buildImportPlan(state, template, cfnClient) {
|
|
|
79600
79656
|
const resourceType = resource.Type ?? "";
|
|
79601
79657
|
if (!resourceType)
|
|
79602
79658
|
continue;
|
|
79659
|
+
if (resourceType === "AWS::CDK::Metadata") {
|
|
79660
|
+
continue;
|
|
79661
|
+
}
|
|
79603
79662
|
if (isNeverImportableType(resourceType)) {
|
|
79604
|
-
if (resourceType
|
|
79605
|
-
|
|
79606
|
-
|
|
79607
|
-
|
|
79608
|
-
|
|
79609
|
-
|
|
79610
|
-
|
|
79663
|
+
if (isPhase2CreatableType(resourceType)) {
|
|
79664
|
+
phase2Creates.push({ logicalId, resourceType });
|
|
79665
|
+
} else {
|
|
79666
|
+
blocked.push({
|
|
79667
|
+
logicalId,
|
|
79668
|
+
resourceType,
|
|
79669
|
+
reason: "CloudFormation cannot import or recreate this resource type"
|
|
79670
|
+
});
|
|
79671
|
+
}
|
|
79611
79672
|
continue;
|
|
79612
79673
|
}
|
|
79613
79674
|
const stateEntry = state.resources[logicalId];
|
|
79614
79675
|
if (!stateEntry || !stateEntry.physicalId) {
|
|
79615
|
-
|
|
79676
|
+
blocked.push({
|
|
79616
79677
|
logicalId,
|
|
79617
79678
|
resourceType,
|
|
79618
79679
|
reason: "no entry in cdkd state (resource is in template but was not deployed by cdkd)"
|
|
@@ -79628,21 +79689,21 @@ async function buildImportPlan(state, template, cfnClient) {
|
|
|
79628
79689
|
identifierCache
|
|
79629
79690
|
);
|
|
79630
79691
|
} catch (err) {
|
|
79631
|
-
|
|
79692
|
+
blocked.push({
|
|
79632
79693
|
logicalId,
|
|
79633
79694
|
resourceType,
|
|
79634
79695
|
reason: "could not resolve resource identifier: " + (err instanceof Error ? err.message : String(err))
|
|
79635
79696
|
});
|
|
79636
79697
|
continue;
|
|
79637
79698
|
}
|
|
79638
|
-
|
|
79699
|
+
phase1Imports.push({
|
|
79639
79700
|
logicalId,
|
|
79640
79701
|
resourceType,
|
|
79641
79702
|
physicalId: stateEntry.physicalId,
|
|
79642
79703
|
resourceIdentifier
|
|
79643
79704
|
});
|
|
79644
79705
|
}
|
|
79645
|
-
return {
|
|
79706
|
+
return { phase1Imports, phase2Creates, blocked };
|
|
79646
79707
|
}
|
|
79647
79708
|
async function resolveResourceIdentifier(resourceType, physicalId, cfnClient, cache2) {
|
|
79648
79709
|
let entry = cache2.get(resourceType);
|
|
@@ -79831,6 +79892,68 @@ async function executeImportChangeSet(cfnClient, stackName, template, plan) {
|
|
|
79831
79892
|
throw err;
|
|
79832
79893
|
}
|
|
79833
79894
|
}
|
|
79895
|
+
async function executeUpdateChangeSet(cfnClient, stackName, template) {
|
|
79896
|
+
const logger = getLogger();
|
|
79897
|
+
const changeSetName = `cdkd-phase2-${Date.now()}`;
|
|
79898
|
+
const templateBody = JSON.stringify(template, null, 2);
|
|
79899
|
+
if (templateBody.length > 51200) {
|
|
79900
|
+
throw new Error(
|
|
79901
|
+
`Full template is ${templateBody.length} bytes, over the 51,200-byte inline TemplateBody limit for phase-2 UPDATE. TemplateURL upload is not yet implemented.`
|
|
79902
|
+
);
|
|
79903
|
+
}
|
|
79904
|
+
logger.info(
|
|
79905
|
+
`Creating UPDATE changeset '${changeSetName}' for phase 2 (${templateBody.length} bytes)...`
|
|
79906
|
+
);
|
|
79907
|
+
try {
|
|
79908
|
+
await cfnClient.send(
|
|
79909
|
+
new CreateChangeSetCommand({
|
|
79910
|
+
StackName: stackName,
|
|
79911
|
+
ChangeSetName: changeSetName,
|
|
79912
|
+
ChangeSetType: "UPDATE",
|
|
79913
|
+
TemplateBody: templateBody,
|
|
79914
|
+
Capabilities: ["CAPABILITY_IAM", "CAPABILITY_NAMED_IAM", "CAPABILITY_AUTO_EXPAND"]
|
|
79915
|
+
})
|
|
79916
|
+
);
|
|
79917
|
+
} catch (err) {
|
|
79918
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
79919
|
+
throw new Error(`Failed to create UPDATE changeset: ${msg}`);
|
|
79920
|
+
}
|
|
79921
|
+
try {
|
|
79922
|
+
await waitUntilChangeSetCreateComplete(
|
|
79923
|
+
{ client: cfnClient, maxWaitTime: 600 },
|
|
79924
|
+
{ StackName: stackName, ChangeSetName: changeSetName }
|
|
79925
|
+
);
|
|
79926
|
+
} catch (err) {
|
|
79927
|
+
try {
|
|
79928
|
+
const desc = await cfnClient.send(
|
|
79929
|
+
new DescribeChangeSetCommand({ StackName: stackName, ChangeSetName: changeSetName })
|
|
79930
|
+
);
|
|
79931
|
+
const reason = desc.StatusReason ?? "unknown";
|
|
79932
|
+
await cfnClient.send(new DeleteChangeSetCommand({ StackName: stackName, ChangeSetName: changeSetName })).catch(() => {
|
|
79933
|
+
});
|
|
79934
|
+
throw new Error(`UPDATE changeset FAILED: ${reason}`);
|
|
79935
|
+
} catch (innerErr) {
|
|
79936
|
+
if (innerErr instanceof Error && innerErr.message.startsWith("UPDATE changeset FAILED")) {
|
|
79937
|
+
throw innerErr;
|
|
79938
|
+
}
|
|
79939
|
+
throw err;
|
|
79940
|
+
}
|
|
79941
|
+
}
|
|
79942
|
+
logger.info(`Executing UPDATE changeset...`);
|
|
79943
|
+
try {
|
|
79944
|
+
await cfnClient.send(
|
|
79945
|
+
new ExecuteChangeSetCommand({ StackName: stackName, ChangeSetName: changeSetName })
|
|
79946
|
+
);
|
|
79947
|
+
await waitUntilStackUpdateComplete2(
|
|
79948
|
+
{ client: cfnClient, maxWaitTime: 3600 },
|
|
79949
|
+
{ StackName: stackName }
|
|
79950
|
+
);
|
|
79951
|
+
} catch (err) {
|
|
79952
|
+
await cfnClient.send(new DeleteChangeSetCommand({ StackName: stackName, ChangeSetName: changeSetName })).catch(() => {
|
|
79953
|
+
});
|
|
79954
|
+
throw err;
|
|
79955
|
+
}
|
|
79956
|
+
}
|
|
79834
79957
|
function refuseTransientContextIfUnsafe(options) {
|
|
79835
79958
|
const overrides = options.context ?? [];
|
|
79836
79959
|
if (overrides.length === 0)
|
|
@@ -79900,6 +80023,10 @@ function createExportCommand() {
|
|
|
79900
80023
|
"--accept-transient-context",
|
|
79901
80024
|
"Allow CLI -c key=value overrides at export time even though they are not persisted to cdk.json / cdk.context.json (default: refuse). When set, the user is responsible for passing the same -c flags to every future cdk deploy.",
|
|
79902
80025
|
false
|
|
80026
|
+
).option(
|
|
80027
|
+
"--include-non-importable",
|
|
80028
|
+
"Run a 2-phase migration when the stack contains non-importable resources (Custom::*). Phase 1 imports the importable resources; phase 2 CFn-CREATEs the non-importable ones, which re-invokes each Custom Resource's onCreate handler. Make sure onCreate is idempotent before enabling.",
|
|
80029
|
+
false
|
|
79903
80030
|
).action(withErrorHandling(exportCommand));
|
|
79904
80031
|
[...commonOptions, ...appOptions, ...stateOptions, ...contextOptions].forEach(
|
|
79905
80032
|
(opt) => cmd.addOption(opt)
|
|
@@ -79938,7 +80065,7 @@ function reorderArgs(argv) {
|
|
|
79938
80065
|
}
|
|
79939
80066
|
async function main() {
|
|
79940
80067
|
const program = new Command18();
|
|
79941
|
-
program.name("cdkd").description("CDK Direct - Deploy AWS CDK apps directly via SDK/Cloud Control API").version("0.
|
|
80068
|
+
program.name("cdkd").description("CDK Direct - Deploy AWS CDK apps directly via SDK/Cloud Control API").version("0.87.0");
|
|
79942
80069
|
program.addCommand(createBootstrapCommand());
|
|
79943
80070
|
program.addCommand(createSynthCommand());
|
|
79944
80071
|
program.addCommand(createListCommand());
|