@go-to-k/cdkd 0.34.0 → 0.36.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/README.md +29 -0
- package/dist/cli.js +537 -42
- package/dist/cli.js.map +4 -4
- package/dist/go-to-k-cdkd-0.36.0.tgz +0 -0
- package/dist/index.js +44 -0
- package/dist/index.js.map +2 -2
- package/package.json +1 -1
- package/dist/go-to-k-cdkd-0.34.0.tgz +0 -0
package/dist/cli.js
CHANGED
|
@@ -447,7 +447,7 @@ var init_aws_clients = __esm({
|
|
|
447
447
|
});
|
|
448
448
|
|
|
449
449
|
// src/cli/index.ts
|
|
450
|
-
import { Command as
|
|
450
|
+
import { Command as Command14 } from "commander";
|
|
451
451
|
|
|
452
452
|
// src/cli/commands/bootstrap.ts
|
|
453
453
|
import { Command, Option as Option2 } from "commander";
|
|
@@ -1206,7 +1206,10 @@ Caused by: ${error.cause.message}`;
|
|
|
1206
1206
|
}
|
|
1207
1207
|
function handleError(error) {
|
|
1208
1208
|
const logger = getLogger();
|
|
1209
|
-
|
|
1209
|
+
const silent = error instanceof CdkdError && error.silent;
|
|
1210
|
+
if (!silent) {
|
|
1211
|
+
logger.error(formatError(error));
|
|
1212
|
+
}
|
|
1210
1213
|
if (error instanceof Error && error.stack) {
|
|
1211
1214
|
logger.debug("Stack trace:", error.stack);
|
|
1212
1215
|
}
|
|
@@ -7579,6 +7582,50 @@ Error: ${err.message || "Unknown error"}`,
|
|
|
7579
7582
|
}
|
|
7580
7583
|
return resourceType.startsWith("AWS::");
|
|
7581
7584
|
}
|
|
7585
|
+
/**
|
|
7586
|
+
* Read the AWS-current properties of a resource managed via Cloud Control
|
|
7587
|
+
* API, for `cdkd drift` comparison.
|
|
7588
|
+
*
|
|
7589
|
+
* Strategy: `GetResource(TypeName, Identifier)` returns `ResourceModel` as
|
|
7590
|
+
* a JSON string of every property AWS reports for the resource. Parse and
|
|
7591
|
+
* surface it as the AWS-current snapshot — the drift command intersects
|
|
7592
|
+
* this against the keys present in cdkd state, so AWS-only keys (timestamps,
|
|
7593
|
+
* generated ids, etc.) are filtered out at compare time.
|
|
7594
|
+
*
|
|
7595
|
+
* Returns `undefined` for the unique cases that mean "drift unknown" (the
|
|
7596
|
+
* resource was deleted out from under cdkd, or the response had no
|
|
7597
|
+
* Properties field). Re-throws on any other error so the drift command can
|
|
7598
|
+
* surface throttling / access-denied issues to the user.
|
|
7599
|
+
*
|
|
7600
|
+
* This single CC API implementation gives drift detection coverage to every
|
|
7601
|
+
* resource type that goes through CC API — the majority of cdkd's surface.
|
|
7602
|
+
* SDK Providers add their own `readCurrentState` incrementally (PR D).
|
|
7603
|
+
*/
|
|
7604
|
+
async readCurrentState(physicalId, _logicalId, resourceType) {
|
|
7605
|
+
try {
|
|
7606
|
+
const response = await this.cloudControlClient.send(
|
|
7607
|
+
new GetResourceCommand2({
|
|
7608
|
+
TypeName: resourceType,
|
|
7609
|
+
Identifier: physicalId
|
|
7610
|
+
})
|
|
7611
|
+
);
|
|
7612
|
+
const raw = response.ResourceDescription?.Properties;
|
|
7613
|
+
if (typeof raw !== "string" || raw.length === 0) {
|
|
7614
|
+
return void 0;
|
|
7615
|
+
}
|
|
7616
|
+
const parsed = JSON.parse(raw);
|
|
7617
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
7618
|
+
return void 0;
|
|
7619
|
+
}
|
|
7620
|
+
return parsed;
|
|
7621
|
+
} catch (error) {
|
|
7622
|
+
const err = error;
|
|
7623
|
+
if (err.name === "ResourceNotFoundException") {
|
|
7624
|
+
return void 0;
|
|
7625
|
+
}
|
|
7626
|
+
throw error;
|
|
7627
|
+
}
|
|
7628
|
+
}
|
|
7582
7629
|
/**
|
|
7583
7630
|
* Adopt an already-deployed resource into cdkd state via Cloud Control API.
|
|
7584
7631
|
*
|
|
@@ -33456,8 +33503,321 @@ function createDiffCommand() {
|
|
|
33456
33503
|
return cmd;
|
|
33457
33504
|
}
|
|
33458
33505
|
|
|
33506
|
+
// src/cli/commands/drift.ts
|
|
33507
|
+
import { Command as Command6, Option as Option3 } from "commander";
|
|
33508
|
+
init_aws_clients();
|
|
33509
|
+
|
|
33510
|
+
// src/analyzer/drift-calculator.ts
|
|
33511
|
+
function calculateResourceDrift(stateProperties, awsProperties) {
|
|
33512
|
+
const drifts = [];
|
|
33513
|
+
for (const key of Object.keys(stateProperties)) {
|
|
33514
|
+
diffAt(key, stateProperties[key], awsProperties[key], drifts);
|
|
33515
|
+
}
|
|
33516
|
+
return drifts;
|
|
33517
|
+
}
|
|
33518
|
+
function diffAt(path, stateValue, awsValue, out) {
|
|
33519
|
+
if (deepEqual(stateValue, awsValue))
|
|
33520
|
+
return;
|
|
33521
|
+
if (isPlainObject(stateValue) && isPlainObject(awsValue) && !Array.isArray(stateValue) && !Array.isArray(awsValue)) {
|
|
33522
|
+
for (const key of Object.keys(stateValue)) {
|
|
33523
|
+
diffAt(`${path}.${key}`, stateValue[key], awsValue[key], out);
|
|
33524
|
+
}
|
|
33525
|
+
return;
|
|
33526
|
+
}
|
|
33527
|
+
out.push({ path, stateValue, awsValue });
|
|
33528
|
+
}
|
|
33529
|
+
function deepEqual(a, b) {
|
|
33530
|
+
if (a === b)
|
|
33531
|
+
return true;
|
|
33532
|
+
if (a === null || b === null || a === void 0 || b === void 0) {
|
|
33533
|
+
return a === b;
|
|
33534
|
+
}
|
|
33535
|
+
if (typeof a !== typeof b)
|
|
33536
|
+
return false;
|
|
33537
|
+
if (typeof a !== "object")
|
|
33538
|
+
return false;
|
|
33539
|
+
if (Array.isArray(a) || Array.isArray(b)) {
|
|
33540
|
+
if (!Array.isArray(a) || !Array.isArray(b))
|
|
33541
|
+
return false;
|
|
33542
|
+
if (a.length !== b.length)
|
|
33543
|
+
return false;
|
|
33544
|
+
return a.every((v, i) => deepEqual(v, b[i]));
|
|
33545
|
+
}
|
|
33546
|
+
const aObj = a;
|
|
33547
|
+
const bObj = b;
|
|
33548
|
+
const aKeys = Object.keys(aObj);
|
|
33549
|
+
const bKeys = Object.keys(bObj);
|
|
33550
|
+
if (aKeys.length !== bKeys.length)
|
|
33551
|
+
return false;
|
|
33552
|
+
for (const key of aKeys) {
|
|
33553
|
+
if (!Object.prototype.hasOwnProperty.call(bObj, key))
|
|
33554
|
+
return false;
|
|
33555
|
+
if (!deepEqual(aObj[key], bObj[key]))
|
|
33556
|
+
return false;
|
|
33557
|
+
}
|
|
33558
|
+
return true;
|
|
33559
|
+
}
|
|
33560
|
+
function isPlainObject(value) {
|
|
33561
|
+
return typeof value === "object" && value !== null;
|
|
33562
|
+
}
|
|
33563
|
+
|
|
33564
|
+
// src/cli/commands/drift.ts
|
|
33565
|
+
var DriftDetectedError = class _DriftDetectedError extends CdkdError {
|
|
33566
|
+
silent = true;
|
|
33567
|
+
constructor() {
|
|
33568
|
+
super("drift detected", "DRIFT_DETECTED");
|
|
33569
|
+
this.name = "DriftDetectedError";
|
|
33570
|
+
Object.setPrototypeOf(this, _DriftDetectedError.prototype);
|
|
33571
|
+
}
|
|
33572
|
+
};
|
|
33573
|
+
async function driftCommand(stacks, options) {
|
|
33574
|
+
const logger = getLogger();
|
|
33575
|
+
if (options.verbose) {
|
|
33576
|
+
logger.setLevel("debug");
|
|
33577
|
+
}
|
|
33578
|
+
warnIfDeprecatedRegion(options);
|
|
33579
|
+
if (!options.all && stacks.length === 0) {
|
|
33580
|
+
throw new Error("Stack name is required. Usage: cdkd drift <stack> [<stack>...] | --all");
|
|
33581
|
+
}
|
|
33582
|
+
await applyRoleArnIfSet({ roleArn: options.roleArn, region: options.region });
|
|
33583
|
+
const awsClients = new AwsClients({
|
|
33584
|
+
...options.region && { region: options.region },
|
|
33585
|
+
...options.profile && { profile: options.profile }
|
|
33586
|
+
});
|
|
33587
|
+
setAwsClients(awsClients);
|
|
33588
|
+
try {
|
|
33589
|
+
const region = options.region || process.env["AWS_REGION"] || "us-east-1";
|
|
33590
|
+
const bucket = await resolveStateBucketWithDefault(options.stateBucket, region);
|
|
33591
|
+
const prefix = options.statePrefix;
|
|
33592
|
+
const stateBackend = new S3StateBackend(
|
|
33593
|
+
awsClients.s3,
|
|
33594
|
+
{ bucket, prefix },
|
|
33595
|
+
{
|
|
33596
|
+
region,
|
|
33597
|
+
...options.profile && { profile: options.profile }
|
|
33598
|
+
}
|
|
33599
|
+
);
|
|
33600
|
+
await stateBackend.verifyBucketExists();
|
|
33601
|
+
const providerRegistry = new ProviderRegistry();
|
|
33602
|
+
registerAllProviders(providerRegistry);
|
|
33603
|
+
providerRegistry.setCustomResourceResponseBucket(bucket);
|
|
33604
|
+
const stateRefs = await stateBackend.listStacks();
|
|
33605
|
+
const targetRefs = resolveTargetRefs(stacks, stateRefs, options);
|
|
33606
|
+
const reports = [];
|
|
33607
|
+
for (const ref of targetRefs) {
|
|
33608
|
+
if (!ref.region) {
|
|
33609
|
+
throw new Error(
|
|
33610
|
+
`Stack '${ref.stackName}' has only a legacy state record without a region. Run 'cdkd deploy ${ref.stackName}' (or any cdkd write) to migrate it to the region-scoped layout, then re-run drift detection.`
|
|
33611
|
+
);
|
|
33612
|
+
}
|
|
33613
|
+
const report = await runDriftForStack(
|
|
33614
|
+
ref.stackName,
|
|
33615
|
+
ref.region,
|
|
33616
|
+
stateBackend,
|
|
33617
|
+
providerRegistry
|
|
33618
|
+
);
|
|
33619
|
+
reports.push(report);
|
|
33620
|
+
}
|
|
33621
|
+
if (options.json) {
|
|
33622
|
+
writeJsonReport(reports);
|
|
33623
|
+
} else {
|
|
33624
|
+
writeHumanReport(reports);
|
|
33625
|
+
}
|
|
33626
|
+
const drifted = reports.some((r) => r.outcomes.some((o) => o.kind === "drifted"));
|
|
33627
|
+
if (drifted) {
|
|
33628
|
+
throw new DriftDetectedError();
|
|
33629
|
+
}
|
|
33630
|
+
} finally {
|
|
33631
|
+
awsClients.destroy();
|
|
33632
|
+
}
|
|
33633
|
+
}
|
|
33634
|
+
function resolveTargetRefs(stacks, stateRefs, options) {
|
|
33635
|
+
if (options.all) {
|
|
33636
|
+
if (stateRefs.length === 0) {
|
|
33637
|
+
throw new Error("No stacks found in state bucket.");
|
|
33638
|
+
}
|
|
33639
|
+
if (options.stackRegion) {
|
|
33640
|
+
return stateRefs.filter((r) => r.region === options.stackRegion);
|
|
33641
|
+
}
|
|
33642
|
+
return stateRefs;
|
|
33643
|
+
}
|
|
33644
|
+
const out = [];
|
|
33645
|
+
for (const stackName of stacks) {
|
|
33646
|
+
const matches = stateRefs.filter((r) => r.stackName === stackName);
|
|
33647
|
+
if (matches.length === 0) {
|
|
33648
|
+
throw new Error(
|
|
33649
|
+
`No state found for stack '${stackName}'. Run 'cdkd state list' to see available stacks.`
|
|
33650
|
+
);
|
|
33651
|
+
}
|
|
33652
|
+
if (options.stackRegion) {
|
|
33653
|
+
const ref = matches.find((r) => r.region === options.stackRegion);
|
|
33654
|
+
if (!ref) {
|
|
33655
|
+
const seen = matches.map((r) => r.region ?? "(legacy)").join(", ");
|
|
33656
|
+
throw new Error(
|
|
33657
|
+
`No state found for stack '${stackName}' in region '${options.stackRegion}'. Available regions: ${seen}.`
|
|
33658
|
+
);
|
|
33659
|
+
}
|
|
33660
|
+
out.push(ref);
|
|
33661
|
+
continue;
|
|
33662
|
+
}
|
|
33663
|
+
if (matches.length === 1) {
|
|
33664
|
+
out.push(matches[0]);
|
|
33665
|
+
continue;
|
|
33666
|
+
}
|
|
33667
|
+
const regions = matches.map((r) => r.region ?? "(legacy)").join(", ");
|
|
33668
|
+
throw new Error(
|
|
33669
|
+
`Stack '${stackName}' has state in multiple regions: ${regions}. Re-run with --stack-region <region> to disambiguate.`
|
|
33670
|
+
);
|
|
33671
|
+
}
|
|
33672
|
+
return out;
|
|
33673
|
+
}
|
|
33674
|
+
async function runDriftForStack(stackName, region, stateBackend, providerRegistry) {
|
|
33675
|
+
const result = await stateBackend.getState(stackName, region);
|
|
33676
|
+
if (!result) {
|
|
33677
|
+
throw new Error(
|
|
33678
|
+
`No state found for stack '${stackName}' (${region}). Run 'cdkd state list' to see available stacks.`
|
|
33679
|
+
);
|
|
33680
|
+
}
|
|
33681
|
+
return await withStackName(stackName, async () => {
|
|
33682
|
+
const outcomes = [];
|
|
33683
|
+
const state = result.state;
|
|
33684
|
+
const entries = Object.entries(state.resources ?? {}).sort(([a], [b]) => a.localeCompare(b));
|
|
33685
|
+
for (const [logicalId, resource] of entries) {
|
|
33686
|
+
if (providerRegistry.shouldSkipResource(resource.resourceType)) {
|
|
33687
|
+
continue;
|
|
33688
|
+
}
|
|
33689
|
+
let provider;
|
|
33690
|
+
try {
|
|
33691
|
+
provider = providerRegistry.getProvider(resource.resourceType);
|
|
33692
|
+
} catch {
|
|
33693
|
+
outcomes.push({
|
|
33694
|
+
kind: "unsupported",
|
|
33695
|
+
logicalId,
|
|
33696
|
+
resourceType: resource.resourceType
|
|
33697
|
+
});
|
|
33698
|
+
continue;
|
|
33699
|
+
}
|
|
33700
|
+
if (!provider.readCurrentState) {
|
|
33701
|
+
outcomes.push({
|
|
33702
|
+
kind: "unsupported",
|
|
33703
|
+
logicalId,
|
|
33704
|
+
resourceType: resource.resourceType
|
|
33705
|
+
});
|
|
33706
|
+
continue;
|
|
33707
|
+
}
|
|
33708
|
+
const aws = await provider.readCurrentState(
|
|
33709
|
+
resource.physicalId,
|
|
33710
|
+
logicalId,
|
|
33711
|
+
resource.resourceType
|
|
33712
|
+
);
|
|
33713
|
+
if (aws === void 0) {
|
|
33714
|
+
outcomes.push({
|
|
33715
|
+
kind: "unsupported",
|
|
33716
|
+
logicalId,
|
|
33717
|
+
resourceType: resource.resourceType
|
|
33718
|
+
});
|
|
33719
|
+
continue;
|
|
33720
|
+
}
|
|
33721
|
+
const changes = calculateResourceDrift(resource.properties ?? {}, aws);
|
|
33722
|
+
if (changes.length === 0) {
|
|
33723
|
+
outcomes.push({ kind: "clean", logicalId, resourceType: resource.resourceType });
|
|
33724
|
+
} else {
|
|
33725
|
+
outcomes.push({
|
|
33726
|
+
kind: "drifted",
|
|
33727
|
+
logicalId,
|
|
33728
|
+
resourceType: resource.resourceType,
|
|
33729
|
+
changes
|
|
33730
|
+
});
|
|
33731
|
+
}
|
|
33732
|
+
}
|
|
33733
|
+
return { stackName, region, outcomes };
|
|
33734
|
+
});
|
|
33735
|
+
}
|
|
33736
|
+
function writeJsonReport(reports) {
|
|
33737
|
+
const payload = reports.map((r) => {
|
|
33738
|
+
const drifted = r.outcomes.filter((o) => o.kind === "drifted").map((o) => ({ logicalId: o.logicalId, type: o.resourceType, changes: o.changes }));
|
|
33739
|
+
const clean = r.outcomes.filter((o) => o.kind === "clean").map((o) => ({ logicalId: o.logicalId, type: o.resourceType }));
|
|
33740
|
+
const notSupported = r.outcomes.filter((o) => o.kind === "unsupported").map((o) => ({ logicalId: o.logicalId, type: o.resourceType }));
|
|
33741
|
+
return { stack: r.stackName, region: r.region, drifted, clean, notSupported };
|
|
33742
|
+
});
|
|
33743
|
+
process.stdout.write(`${JSON.stringify(payload, null, 2)}
|
|
33744
|
+
`);
|
|
33745
|
+
}
|
|
33746
|
+
function writeHumanReport(reports) {
|
|
33747
|
+
for (const report of reports) {
|
|
33748
|
+
const drifted = report.outcomes.filter(
|
|
33749
|
+
(o) => o.kind === "drifted"
|
|
33750
|
+
);
|
|
33751
|
+
const unsupported = report.outcomes.filter(
|
|
33752
|
+
(o) => o.kind === "unsupported"
|
|
33753
|
+
);
|
|
33754
|
+
const total = report.outcomes.length;
|
|
33755
|
+
if (drifted.length === 0) {
|
|
33756
|
+
process.stdout.write(
|
|
33757
|
+
`\u2713 ${report.stackName} (${report.region}): no drift detected (${total} resource${total === 1 ? "" : "s"} checked, ${unsupported.length} unsupported)
|
|
33758
|
+
`
|
|
33759
|
+
);
|
|
33760
|
+
} else {
|
|
33761
|
+
const word = drifted.length === 1 ? "resource" : "resources";
|
|
33762
|
+
process.stdout.write(
|
|
33763
|
+
`
|
|
33764
|
+
\u26A0 ${report.stackName} (${report.region}): drift detected on ${drifted.length} ${word}
|
|
33765
|
+
|
|
33766
|
+
`
|
|
33767
|
+
);
|
|
33768
|
+
for (const o of drifted) {
|
|
33769
|
+
process.stdout.write(` ~ ${o.logicalId} (${o.resourceType})
|
|
33770
|
+
`);
|
|
33771
|
+
for (const change of o.changes) {
|
|
33772
|
+
process.stdout.write(` - ${change.path}: ${formatScalar(change.stateValue)}
|
|
33773
|
+
`);
|
|
33774
|
+
process.stdout.write(` + ${change.path}: ${formatScalar(change.awsValue)}
|
|
33775
|
+
`);
|
|
33776
|
+
}
|
|
33777
|
+
process.stdout.write("\n");
|
|
33778
|
+
}
|
|
33779
|
+
}
|
|
33780
|
+
if (unsupported.length > 0) {
|
|
33781
|
+
process.stdout.write(
|
|
33782
|
+
`
|
|
33783
|
+
${unsupported.length} resource(s) reported as drift unknown \u2014 provider does not yet support drift detection:
|
|
33784
|
+
`
|
|
33785
|
+
);
|
|
33786
|
+
for (const o of unsupported) {
|
|
33787
|
+
process.stdout.write(` ? ${o.logicalId} (${o.resourceType})
|
|
33788
|
+
`);
|
|
33789
|
+
}
|
|
33790
|
+
}
|
|
33791
|
+
}
|
|
33792
|
+
}
|
|
33793
|
+
function formatScalar(value) {
|
|
33794
|
+
if (value === null)
|
|
33795
|
+
return "null";
|
|
33796
|
+
if (value === void 0)
|
|
33797
|
+
return "undefined";
|
|
33798
|
+
if (typeof value === "string")
|
|
33799
|
+
return value;
|
|
33800
|
+
if (typeof value === "number" || typeof value === "boolean")
|
|
33801
|
+
return String(value);
|
|
33802
|
+
return JSON.stringify(value);
|
|
33803
|
+
}
|
|
33804
|
+
function stackRegionOption() {
|
|
33805
|
+
return new Option3(
|
|
33806
|
+
"--stack-region <region>",
|
|
33807
|
+
"Region of the stack record to inspect. Required when the same stack name has state in multiple regions."
|
|
33808
|
+
);
|
|
33809
|
+
}
|
|
33810
|
+
function createDriftCommand() {
|
|
33811
|
+
const cmd = new Command6("drift").description(
|
|
33812
|
+
"Detect drift between cdkd state and AWS reality. Exits 0 when no drift, 1 when drift is detected."
|
|
33813
|
+
).argument("[stacks...]", "Stack name(s) to check (physical CloudFormation names)").option("--all", "Check every stack in the state bucket", false).option("--json", "Output as JSON", false).addOption(stackRegionOption()).action(withErrorHandling(driftCommand));
|
|
33814
|
+
[...commonOptions, ...stateOptions].forEach((opt) => cmd.addOption(opt));
|
|
33815
|
+
cmd.addOption(deprecatedRegionOption);
|
|
33816
|
+
return cmd;
|
|
33817
|
+
}
|
|
33818
|
+
|
|
33459
33819
|
// src/cli/commands/destroy.ts
|
|
33460
|
-
import { Command as
|
|
33820
|
+
import { Command as Command7 } from "commander";
|
|
33461
33821
|
init_aws_clients();
|
|
33462
33822
|
|
|
33463
33823
|
// src/cli/commands/destroy-runner.ts
|
|
@@ -33880,7 +34240,7 @@ Preparing to destroy stack: ${stackName}`);
|
|
|
33880
34240
|
}
|
|
33881
34241
|
}
|
|
33882
34242
|
function createDestroyCommand() {
|
|
33883
|
-
const cmd = new
|
|
34243
|
+
const cmd = new Command7("destroy").description("Destroy all resources in the stack").argument(
|
|
33884
34244
|
"[stacks...]",
|
|
33885
34245
|
"Stack name(s) to destroy. Accepts physical CloudFormation names (e.g. 'MyStage-Api') or CDK display paths (e.g. 'MyStage/Api'). Supports wildcards (e.g. 'MyStage/*')."
|
|
33886
34246
|
).option("--all", "Destroy all stacks", false).action(withErrorHandling(destroyCommand));
|
|
@@ -33899,7 +34259,7 @@ function createDestroyCommand() {
|
|
|
33899
34259
|
|
|
33900
34260
|
// src/cli/commands/orphan.ts
|
|
33901
34261
|
import * as readline2 from "node:readline/promises";
|
|
33902
|
-
import { Command as
|
|
34262
|
+
import { Command as Command8 } from "commander";
|
|
33903
34263
|
init_aws_clients();
|
|
33904
34264
|
|
|
33905
34265
|
// src/cli/cdk-path.ts
|
|
@@ -34580,7 +34940,7 @@ async function confirmPrompt(prompt) {
|
|
|
34580
34940
|
}
|
|
34581
34941
|
}
|
|
34582
34942
|
function createOrphanCommand() {
|
|
34583
|
-
const cmd = new
|
|
34943
|
+
const cmd = new Command8("orphan").description(
|
|
34584
34944
|
"Remove one or more resources from cdkd state by construct path (does NOT delete AWS resources). Mirrors aws-cdk-cli's 'cdk orphan --unstable=orphan'. Synth-driven; for the previous whole-stack-orphan behavior, use 'cdkd state orphan <stack>'."
|
|
34585
34945
|
).argument(
|
|
34586
34946
|
"<paths...>",
|
|
@@ -34606,41 +34966,174 @@ function createOrphanCommand() {
|
|
|
34606
34966
|
}
|
|
34607
34967
|
|
|
34608
34968
|
// src/cli/commands/publish-assets.ts
|
|
34609
|
-
import { Option as
|
|
34610
|
-
async function publishAssetsCommand(options) {
|
|
34969
|
+
import { Option as Option4, Command as Command9 } from "commander";
|
|
34970
|
+
async function publishAssetsCommand(stacks, options) {
|
|
34611
34971
|
const logger = getLogger();
|
|
34612
34972
|
if (options.verbose) {
|
|
34613
34973
|
logger.setLevel("debug");
|
|
34614
34974
|
}
|
|
34615
34975
|
warnIfDeprecatedRegion(options);
|
|
34616
34976
|
await applyRoleArnIfSet({ roleArn: options.roleArn, region: options.region });
|
|
34617
|
-
|
|
34618
|
-
|
|
34619
|
-
|
|
34620
|
-
|
|
34621
|
-
|
|
34977
|
+
const app = resolveApp(options.app);
|
|
34978
|
+
if (!app) {
|
|
34979
|
+
throw new Error(
|
|
34980
|
+
'No app command specified. Use --app, set CDKD_APP env var, or add "app" to cdk.json'
|
|
34981
|
+
);
|
|
34982
|
+
}
|
|
34983
|
+
logger.info("Synthesizing CDK app...");
|
|
34984
|
+
const synthesizer = new Synthesizer();
|
|
34985
|
+
const context = parseContextOptions(options.context);
|
|
34986
|
+
const synthOptions = {
|
|
34987
|
+
app,
|
|
34988
|
+
output: options.output,
|
|
34622
34989
|
...options.region && { region: options.region },
|
|
34623
|
-
|
|
34624
|
-
|
|
34625
|
-
}
|
|
34626
|
-
|
|
34990
|
+
...options.profile && { profile: options.profile },
|
|
34991
|
+
...Object.keys(context).length > 0 && { context }
|
|
34992
|
+
};
|
|
34993
|
+
const result = await synthesizer.synthesize(synthOptions);
|
|
34994
|
+
const { stacks: allStacks } = result;
|
|
34995
|
+
logger.debug(`Found ${allStacks.length} stack(s) in assembly`);
|
|
34996
|
+
const stackPatterns = stacks.length > 0 ? stacks : options.stack ? [options.stack] : [];
|
|
34997
|
+
let targetStacks;
|
|
34998
|
+
if (options.all) {
|
|
34999
|
+
targetStacks = allStacks;
|
|
35000
|
+
} else if (stackPatterns.length > 0) {
|
|
35001
|
+
targetStacks = matchStacks(allStacks, stackPatterns);
|
|
35002
|
+
} else if (allStacks.length === 1) {
|
|
35003
|
+
targetStacks = allStacks;
|
|
35004
|
+
} else {
|
|
35005
|
+
throw new Error(
|
|
35006
|
+
`Multiple stacks found: ${allStacks.map(describeStack).join(", ")}. Specify stack name(s) or use --all`
|
|
35007
|
+
);
|
|
35008
|
+
}
|
|
35009
|
+
if (targetStacks.length === 0) {
|
|
35010
|
+
throw new Error(
|
|
35011
|
+
stackPatterns.length > 0 ? `No stacks matching ${stackPatterns.join(", ")} found in assembly. Available: ${allStacks.map(describeStack).join(", ")}` : "No stacks found in assembly"
|
|
35012
|
+
);
|
|
35013
|
+
}
|
|
35014
|
+
const baseRegion = options.region || process.env["AWS_REGION"] || "us-east-1";
|
|
35015
|
+
const { STSClient: STSClient10, GetCallerIdentityCommand: GetCallerIdentityCommand11 } = await import("@aws-sdk/client-sts");
|
|
35016
|
+
const stsClient = new STSClient10({ region: baseRegion });
|
|
35017
|
+
const callerIdentity = await stsClient.send(new GetCallerIdentityCommand11({}));
|
|
35018
|
+
const accountId = callerIdentity.Account;
|
|
35019
|
+
stsClient.destroy();
|
|
35020
|
+
const assetPublisher = new AssetPublisher();
|
|
35021
|
+
const results = [];
|
|
35022
|
+
for (const stack of targetStacks) {
|
|
35023
|
+
const startedAt = Date.now();
|
|
35024
|
+
let assetCount = 0;
|
|
35025
|
+
let error;
|
|
35026
|
+
try {
|
|
35027
|
+
if (!stack.assetManifestPath) {
|
|
35028
|
+
logger.debug(`Stack ${stack.stackName} has no asset manifest; nothing to publish`);
|
|
35029
|
+
results.push({
|
|
35030
|
+
stackName: stack.stackName,
|
|
35031
|
+
displayName: stack.displayName,
|
|
35032
|
+
assetCount: 0,
|
|
35033
|
+
durationMs: Date.now() - startedAt
|
|
35034
|
+
});
|
|
35035
|
+
continue;
|
|
35036
|
+
}
|
|
35037
|
+
logger.info(`
|
|
35038
|
+
Publishing assets for stack: ${describeStack(stack)}`);
|
|
35039
|
+
const workGraph = new WorkGraph();
|
|
35040
|
+
let nodeIds = [];
|
|
35041
|
+
try {
|
|
35042
|
+
nodeIds = assetPublisher.addAssetsToGraph(workGraph, stack.assetManifestPath, {
|
|
35043
|
+
accountId,
|
|
35044
|
+
region: stack.region || baseRegion,
|
|
35045
|
+
...options.profile && { profile: options.profile },
|
|
35046
|
+
nodePrefix: `${stack.stackName}:`
|
|
35047
|
+
});
|
|
35048
|
+
} catch (err) {
|
|
35049
|
+
const e = err;
|
|
35050
|
+
if (e.code === "ENOENT") {
|
|
35051
|
+
logger.debug(
|
|
35052
|
+
`Asset manifest not found for ${stack.stackName} (${stack.assetManifestPath}); skipping`
|
|
35053
|
+
);
|
|
35054
|
+
results.push({
|
|
35055
|
+
stackName: stack.stackName,
|
|
35056
|
+
displayName: stack.displayName,
|
|
35057
|
+
assetCount: 0,
|
|
35058
|
+
durationMs: Date.now() - startedAt
|
|
35059
|
+
});
|
|
35060
|
+
continue;
|
|
35061
|
+
}
|
|
35062
|
+
throw err;
|
|
35063
|
+
}
|
|
35064
|
+
assetCount = nodeIds.filter((id) => id.startsWith("asset-publish:")).length;
|
|
35065
|
+
if (assetCount === 0) {
|
|
35066
|
+
logger.info(" (no assets to publish)");
|
|
35067
|
+
results.push({
|
|
35068
|
+
stackName: stack.stackName,
|
|
35069
|
+
displayName: stack.displayName,
|
|
35070
|
+
assetCount: 0,
|
|
35071
|
+
durationMs: Date.now() - startedAt
|
|
35072
|
+
});
|
|
35073
|
+
continue;
|
|
35074
|
+
}
|
|
35075
|
+
await workGraph.execute(
|
|
35076
|
+
{
|
|
35077
|
+
"asset-build": options.imageBuildConcurrency,
|
|
35078
|
+
"asset-publish": options.assetPublishConcurrency,
|
|
35079
|
+
stack: 0
|
|
35080
|
+
},
|
|
35081
|
+
(node) => assetPublisher.executeNode(node)
|
|
35082
|
+
);
|
|
35083
|
+
logger.info(
|
|
35084
|
+
` \u2713 Published ${assetCount} asset(s) in ${((Date.now() - startedAt) / 1e3).toFixed(2)}s`
|
|
35085
|
+
);
|
|
35086
|
+
} catch (err) {
|
|
35087
|
+
error = err instanceof Error ? err : new Error(String(err));
|
|
35088
|
+
logger.error(` \u2717 ${stack.stackName}: ${error.message}`);
|
|
35089
|
+
}
|
|
35090
|
+
results.push({
|
|
35091
|
+
stackName: stack.stackName,
|
|
35092
|
+
displayName: stack.displayName,
|
|
35093
|
+
assetCount,
|
|
35094
|
+
durationMs: Date.now() - startedAt,
|
|
35095
|
+
...error && { error }
|
|
35096
|
+
});
|
|
35097
|
+
}
|
|
35098
|
+
const failed = results.filter((r) => r.error);
|
|
35099
|
+
const totalAssets = results.reduce((sum, r) => sum + r.assetCount, 0);
|
|
35100
|
+
logger.info("\nPublish Summary:");
|
|
35101
|
+
for (const r of results) {
|
|
35102
|
+
const tag = r.error ? "\u2717" : "\u2713";
|
|
35103
|
+
const id = r.displayName === r.stackName ? r.stackName : `${r.displayName} (${r.stackName})`;
|
|
35104
|
+
const detail = r.error ? `failed: ${r.error.message}` : `${r.assetCount} asset(s), ${(r.durationMs / 1e3).toFixed(2)}s`;
|
|
35105
|
+
logger.info(` ${tag} ${id} \u2014 ${detail}`);
|
|
35106
|
+
}
|
|
35107
|
+
if (failed.length > 0) {
|
|
35108
|
+
throw new PartialFailureError(
|
|
35109
|
+
`Asset publishing completed with ${failed.length} stack failure(s) (${totalAssets} asset(s) published successfully across the rest).`
|
|
35110
|
+
);
|
|
35111
|
+
}
|
|
35112
|
+
logger.info(`
|
|
35113
|
+
\u2705 Asset publishing complete (${totalAssets} asset(s))`);
|
|
34627
35114
|
}
|
|
34628
35115
|
function createPublishAssetsCommand() {
|
|
34629
|
-
const cmd = new
|
|
34630
|
-
|
|
35116
|
+
const cmd = new Command9("publish-assets").description(
|
|
35117
|
+
"Synthesize the CDK app and publish assets to S3/ECR for the selected stack(s) without deploying"
|
|
35118
|
+
).argument(
|
|
35119
|
+
"[stacks...]",
|
|
35120
|
+
"Stack name(s) to publish assets for. Accepts physical CloudFormation names (e.g. 'MyStage-Api') or CDK display paths (e.g. 'MyStage/Api'). Supports wildcards."
|
|
35121
|
+
).option("--all", "Publish assets for all stacks", false).addOption(
|
|
35122
|
+
new Option4(
|
|
34631
35123
|
"--asset-publish-concurrency <number>",
|
|
34632
35124
|
"Maximum concurrent asset publish operations"
|
|
34633
35125
|
).default(8).argParser((value) => parseInt(value, 10))
|
|
34634
35126
|
).addOption(
|
|
34635
|
-
new
|
|
35127
|
+
new Option4("--image-build-concurrency <number>", "Maximum concurrent Docker image builds").default(4).argParser((value) => parseInt(value, 10))
|
|
34636
35128
|
).action(withErrorHandling(publishAssetsCommand));
|
|
34637
|
-
commonOptions.forEach((opt) => cmd.addOption(opt));
|
|
35129
|
+
[...commonOptions, ...appOptions, ...contextOptions].forEach((opt) => cmd.addOption(opt));
|
|
35130
|
+
cmd.addOption(new Option4("--stack <name>", "Stack name to publish (alternative to positional)"));
|
|
34638
35131
|
cmd.addOption(deprecatedRegionOption);
|
|
34639
35132
|
return cmd;
|
|
34640
35133
|
}
|
|
34641
35134
|
|
|
34642
35135
|
// src/cli/commands/force-unlock.ts
|
|
34643
|
-
import { Command as
|
|
35136
|
+
import { Command as Command10, Option as Option5 } from "commander";
|
|
34644
35137
|
init_aws_clients();
|
|
34645
35138
|
async function forceUnlockCommand(stackArgs, options) {
|
|
34646
35139
|
const logger = getLogger();
|
|
@@ -34701,8 +35194,8 @@ async function forceUnlockCommand(stackArgs, options) {
|
|
|
34701
35194
|
}
|
|
34702
35195
|
}
|
|
34703
35196
|
function createForceUnlockCommand() {
|
|
34704
|
-
const cmd = new
|
|
34705
|
-
new
|
|
35197
|
+
const cmd = new Command10("force-unlock").description("Force-release a stale lock on a stack").argument("[stacks...]", "Stack name(s) to unlock").addOption(
|
|
35198
|
+
new Option5(
|
|
34706
35199
|
"--stack-region <region>",
|
|
34707
35200
|
"Stack region whose lock to release (use when the same stack name has locks in multiple regions). Defaults to all regions where the stack has state."
|
|
34708
35201
|
)
|
|
@@ -34714,7 +35207,7 @@ function createForceUnlockCommand() {
|
|
|
34714
35207
|
|
|
34715
35208
|
// src/cli/commands/state.ts
|
|
34716
35209
|
import * as readline4 from "node:readline/promises";
|
|
34717
|
-
import { Command as
|
|
35210
|
+
import { Command as Command12, Option as Option6 } from "commander";
|
|
34718
35211
|
import {
|
|
34719
35212
|
GetBucketLocationCommand as GetBucketLocationCommand2,
|
|
34720
35213
|
GetObjectCommand as GetObjectCommand4,
|
|
@@ -34724,7 +35217,7 @@ init_aws_clients();
|
|
|
34724
35217
|
|
|
34725
35218
|
// src/cli/commands/state-migrate.ts
|
|
34726
35219
|
import * as readline3 from "node:readline/promises";
|
|
34727
|
-
import { Command as
|
|
35220
|
+
import { Command as Command11 } from "commander";
|
|
34728
35221
|
import {
|
|
34729
35222
|
CopyObjectCommand,
|
|
34730
35223
|
CreateBucketCommand as CreateBucketCommand4,
|
|
@@ -35008,7 +35501,7 @@ async function confirmPrompt2(prompt) {
|
|
|
35008
35501
|
}
|
|
35009
35502
|
}
|
|
35010
35503
|
function createStateMigrateCommand() {
|
|
35011
|
-
const cmd = new
|
|
35504
|
+
const cmd = new Command11("migrate").description(
|
|
35012
35505
|
"Migrate state from the legacy region-suffixed bucket (cdkd-state-{account}-{region}) to the new region-free default (cdkd-state-{account}). Source bucket is kept by default; pass --remove-legacy to delete it after a successful migration."
|
|
35013
35506
|
).option(
|
|
35014
35507
|
"--region <region>",
|
|
@@ -35167,7 +35660,7 @@ async function stateListCommand(options) {
|
|
|
35167
35660
|
}
|
|
35168
35661
|
}
|
|
35169
35662
|
function createStateListCommand() {
|
|
35170
|
-
const cmd = new
|
|
35663
|
+
const cmd = new Command12("list").alias("ls").description("List stacks registered in the cdkd state bucket").option("-l, --long", "Show resource count, last-modified time, and lock status", false).option("--json", "Output as JSON", false).action(withErrorHandling(stateListCommand));
|
|
35171
35664
|
[...commonOptions, ...stateOptions].forEach((opt) => cmd.addOption(opt));
|
|
35172
35665
|
cmd.addOption(deprecatedRegionOption);
|
|
35173
35666
|
return cmd;
|
|
@@ -35271,7 +35764,7 @@ function formatLockSummary(lockInfo) {
|
|
|
35271
35764
|
return `locked by ${lockInfo.owner}${opStr}, ${expiresStr}`;
|
|
35272
35765
|
}
|
|
35273
35766
|
function createStateResourcesCommand() {
|
|
35274
|
-
const cmd = new
|
|
35767
|
+
const cmd = new Command12("resources").description("List resources recorded in a stack's state").argument("<stack>", "Stack name (physical CloudFormation name)").option("-l, --long", "Include dependencies and attributes per resource", false).option("--json", "Output as JSON", false).addOption(stackRegionOption2()).action(withErrorHandling(stateResourcesCommand));
|
|
35275
35768
|
[...commonOptions, ...stateOptions].forEach((opt) => cmd.addOption(opt));
|
|
35276
35769
|
cmd.addOption(deprecatedRegionOption);
|
|
35277
35770
|
return cmd;
|
|
@@ -35359,7 +35852,7 @@ async function stateShowCommand(stackName, options) {
|
|
|
35359
35852
|
}
|
|
35360
35853
|
}
|
|
35361
35854
|
function createStateShowCommand() {
|
|
35362
|
-
const cmd = new
|
|
35855
|
+
const cmd = new Command12("show").description("Show the full cdkd state record for a stack (metadata, outputs, resources)").argument("<stack>", "Stack name (physical CloudFormation name)").option("--json", "Output the raw state and lock as JSON", false).addOption(stackRegionOption2()).action(withErrorHandling(stateShowCommand));
|
|
35363
35856
|
[...commonOptions, ...stateOptions].forEach((opt) => cmd.addOption(opt));
|
|
35364
35857
|
cmd.addOption(deprecatedRegionOption);
|
|
35365
35858
|
return cmd;
|
|
@@ -35435,16 +35928,16 @@ Use 'cdkd destroy ${stackName}' if you want to delete the actual resources.
|
|
|
35435
35928
|
setup.dispose();
|
|
35436
35929
|
}
|
|
35437
35930
|
}
|
|
35438
|
-
function
|
|
35439
|
-
return new
|
|
35931
|
+
function stackRegionOption2() {
|
|
35932
|
+
return new Option6(
|
|
35440
35933
|
"--stack-region <region>",
|
|
35441
35934
|
"Region of the stack record to operate on. Required when the same stack name has state in multiple regions."
|
|
35442
35935
|
);
|
|
35443
35936
|
}
|
|
35444
35937
|
function createStateOrphanCommand() {
|
|
35445
|
-
const cmd = new
|
|
35938
|
+
const cmd = new Command12("orphan").description(
|
|
35446
35939
|
"Orphan one or more stacks from cdkd state (removes the state record; does NOT delete AWS resources)"
|
|
35447
|
-
).argument("<stacks...>", "Stack name(s) to orphan from state").option("-f, --force", "Skip confirmation and remove even if the stack is locked", false).addOption(
|
|
35940
|
+
).argument("<stacks...>", "Stack name(s) to orphan from state").option("-f, --force", "Skip confirmation and remove even if the stack is locked", false).addOption(stackRegionOption2()).action(withErrorHandling(stateOrphanCommand));
|
|
35448
35941
|
[...commonOptions, ...stateOptions].forEach((opt) => cmd.addOption(opt));
|
|
35449
35942
|
cmd.addOption(deprecatedRegionOption);
|
|
35450
35943
|
return cmd;
|
|
@@ -35585,9 +36078,9 @@ Preparing to destroy stack: ${stackName}${ref.region ? ` (${ref.region})` : ""}`
|
|
|
35585
36078
|
}
|
|
35586
36079
|
}
|
|
35587
36080
|
function createStateDestroyCommand() {
|
|
35588
|
-
const cmd = new
|
|
36081
|
+
const cmd = new Command12("destroy").description(
|
|
35589
36082
|
"Destroy a stack's AWS resources and remove its state record without requiring the CDK app. For removing only the state record (keeping AWS resources intact), use 'cdkd state orphan'."
|
|
35590
|
-
).argument("[stacks...]", "Stack name(s) to destroy (physical CloudFormation names)").option("--all", "Destroy every stack in the state bucket", false).addOption(
|
|
36083
|
+
).argument("[stacks...]", "Stack name(s) to destroy (physical CloudFormation names)").option("--all", "Destroy every stack in the state bucket", false).addOption(stackRegionOption2()).addHelpText(
|
|
35591
36084
|
"after",
|
|
35592
36085
|
[
|
|
35593
36086
|
"",
|
|
@@ -35720,14 +36213,14 @@ async function stateInfoCommand(options) {
|
|
|
35720
36213
|
}
|
|
35721
36214
|
}
|
|
35722
36215
|
function createStateInfoCommand() {
|
|
35723
|
-
const cmd = new
|
|
36216
|
+
const cmd = new Command12("info").description(
|
|
35724
36217
|
"Show cdkd state bucket info (bucket name, region, source, schema version, stack count)"
|
|
35725
36218
|
).option("--json", "Output as JSON", false).action(withErrorHandling(stateInfoCommand));
|
|
35726
36219
|
[...commonOptions, ...stateOptions].forEach((opt) => cmd.addOption(opt));
|
|
35727
36220
|
return cmd;
|
|
35728
36221
|
}
|
|
35729
36222
|
function createStateCommand() {
|
|
35730
|
-
const cmd = new
|
|
36223
|
+
const cmd = new Command12("state").description("Manage cdkd state stored in S3");
|
|
35731
36224
|
cmd.addCommand(createStateInfoCommand());
|
|
35732
36225
|
cmd.addCommand(createStateListCommand());
|
|
35733
36226
|
cmd.addCommand(createStateResourcesCommand());
|
|
@@ -35741,7 +36234,7 @@ function createStateCommand() {
|
|
|
35741
36234
|
// src/cli/commands/import.ts
|
|
35742
36235
|
import { readFileSync as readFileSync5, writeFileSync as writeFileSync4 } from "node:fs";
|
|
35743
36236
|
import * as readline6 from "node:readline/promises";
|
|
35744
|
-
import { Command as
|
|
36237
|
+
import { Command as Command13 } from "commander";
|
|
35745
36238
|
init_aws_clients();
|
|
35746
36239
|
|
|
35747
36240
|
// src/cli/commands/retire-cfn-stack.ts
|
|
@@ -36421,7 +36914,7 @@ async function confirmPrompt4(prompt) {
|
|
|
36421
36914
|
}
|
|
36422
36915
|
}
|
|
36423
36916
|
function createImportCommand() {
|
|
36424
|
-
const cmd = new
|
|
36917
|
+
const cmd = new Command13("import").description(
|
|
36425
36918
|
"Adopt already-deployed AWS resources into cdkd state. Reads the CDK app to find logical IDs, resource types, and dependencies. With no flags, imports every resource via the aws:cdk:path tag. With --resource / --resource-mapping, only the listed resources are imported (CDK CLI parity); pass --auto to also tag-import the rest."
|
|
36426
36919
|
).argument(
|
|
36427
36920
|
"[stack]",
|
|
@@ -36469,6 +36962,7 @@ var SUBCOMMANDS = /* @__PURE__ */ new Set([
|
|
|
36469
36962
|
"ls",
|
|
36470
36963
|
"deploy",
|
|
36471
36964
|
"diff",
|
|
36965
|
+
"drift",
|
|
36472
36966
|
"destroy",
|
|
36473
36967
|
"orphan",
|
|
36474
36968
|
"import",
|
|
@@ -36487,13 +36981,14 @@ function reorderArgs(argv) {
|
|
|
36487
36981
|
return [...prefix, ...cmdAndAfter, ...beforeCmd];
|
|
36488
36982
|
}
|
|
36489
36983
|
async function main() {
|
|
36490
|
-
const program = new
|
|
36491
|
-
program.name("cdkd").description("CDK Direct - Deploy AWS CDK apps directly via SDK/Cloud Control API").version("0.
|
|
36984
|
+
const program = new Command14();
|
|
36985
|
+
program.name("cdkd").description("CDK Direct - Deploy AWS CDK apps directly via SDK/Cloud Control API").version("0.36.0");
|
|
36492
36986
|
program.addCommand(createBootstrapCommand());
|
|
36493
36987
|
program.addCommand(createSynthCommand());
|
|
36494
36988
|
program.addCommand(createListCommand());
|
|
36495
36989
|
program.addCommand(createDeployCommand());
|
|
36496
36990
|
program.addCommand(createDiffCommand());
|
|
36991
|
+
program.addCommand(createDriftCommand());
|
|
36497
36992
|
program.addCommand(createDestroyCommand());
|
|
36498
36993
|
program.addCommand(createOrphanCommand());
|
|
36499
36994
|
program.addCommand(createImportCommand());
|