@go-to-k/cdkd 0.57.0 → 0.58.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 +27 -0
- package/dist/cli.js +123 -15
- package/dist/cli.js.map +2 -2
- package/dist/go-to-k-cdkd-0.58.0.tgz +0 -0
- package/dist/index.js +4 -1
- package/dist/index.js.map +2 -2
- package/package.json +1 -1
- package/dist/go-to-k-cdkd-0.57.0.tgz +0 -0
package/README.md
CHANGED
|
@@ -487,6 +487,33 @@ Two `orphan` variants at different granularities:
|
|
|
487
487
|
Both `cdkd destroy` (synth-driven) and `cdkd state destroy`
|
|
488
488
|
(state-driven, no synth) delete AWS resources + state.
|
|
489
489
|
|
|
490
|
+
## Stack termination protection
|
|
491
|
+
|
|
492
|
+
CDK's `new Stack(app, 'X', { terminationProtection: true })` is
|
|
493
|
+
honored by `cdkd destroy` and `cdkd destroy --all`. A protected
|
|
494
|
+
stack is refused before the lock is acquired and before any
|
|
495
|
+
per-resource delete runs; in `--all` runs sibling unprotected
|
|
496
|
+
stacks still destroy and the protected ones contribute to the
|
|
497
|
+
partial-failure exit code 2.
|
|
498
|
+
|
|
499
|
+
Bypass workflow:
|
|
500
|
+
|
|
501
|
+
1. Edit the CDK code to set `terminationProtection: false`.
|
|
502
|
+
2. Redeploy: `cdkd deploy MyStack`.
|
|
503
|
+
3. Retry: `cdkd destroy MyStack`.
|
|
504
|
+
|
|
505
|
+
`cdkd state destroy` (state-only, no synth) does **not** honor
|
|
506
|
+
`terminationProtection` — the flag is a CDK property surfaced via
|
|
507
|
+
synth and is not stored in cdkd's state.json. Use `cdkd destroy`
|
|
508
|
+
when synth is available, or accept that `state destroy` is the
|
|
509
|
+
explicit "I know what I'm doing, ignore CDK guards" escape hatch.
|
|
510
|
+
|
|
511
|
+
A future `--remove-protection` flag (separate scope) will provide
|
|
512
|
+
an explicit one-shot bypass without editing CDK code.
|
|
513
|
+
|
|
514
|
+
`cdkd diff` (read-only) and `cdkd deploy` (forward-only) are
|
|
515
|
+
unaffected — only destroy is gated.
|
|
516
|
+
|
|
490
517
|
## `publish-assets`: synth + build + publish, no deploy
|
|
491
518
|
|
|
492
519
|
`cdkd publish-assets` runs the asset half of the deploy pipeline
|
package/dist/cli.js
CHANGED
|
@@ -1207,6 +1207,18 @@ var ResourceUpdateNotSupportedError = class _ResourceUpdateNotSupportedError ext
|
|
|
1207
1207
|
}
|
|
1208
1208
|
exitCode = 2;
|
|
1209
1209
|
};
|
|
1210
|
+
var StackTerminationProtectionError = class _StackTerminationProtectionError extends CdkdError {
|
|
1211
|
+
constructor(stackName, cause) {
|
|
1212
|
+
super(
|
|
1213
|
+
`Stack '${stackName}' has terminationProtection: true and cannot be destroyed. Set terminationProtection: false in the CDK code, redeploy, then retry 'cdkd destroy ${stackName}'.`,
|
|
1214
|
+
"STACK_TERMINATION_PROTECTION",
|
|
1215
|
+
cause
|
|
1216
|
+
);
|
|
1217
|
+
this.stackName = stackName;
|
|
1218
|
+
this.name = "StackTerminationProtectionError";
|
|
1219
|
+
Object.setPrototypeOf(this, _StackTerminationProtectionError.prototype);
|
|
1220
|
+
}
|
|
1221
|
+
};
|
|
1210
1222
|
function isCdkdError(error) {
|
|
1211
1223
|
return error instanceof CdkdError;
|
|
1212
1224
|
}
|
|
@@ -1915,7 +1927,10 @@ var AssemblyReader = class {
|
|
|
1915
1927
|
assetManifestPath,
|
|
1916
1928
|
dependencyNames,
|
|
1917
1929
|
region: env?.region !== "unknown-region" ? env?.region : void 0,
|
|
1918
|
-
account: env?.account !== "unknown-account" ? env?.account : void 0
|
|
1930
|
+
account: env?.account !== "unknown-account" ? env?.account : void 0,
|
|
1931
|
+
...props?.terminationProtection !== void 0 && {
|
|
1932
|
+
terminationProtection: props.terminationProtection
|
|
1933
|
+
}
|
|
1919
1934
|
};
|
|
1920
1935
|
}
|
|
1921
1936
|
/**
|
|
@@ -13565,7 +13580,7 @@ var SNSTopicProvider = class {
|
|
|
13565
13580
|
if (properties["DeliveryStatusLogging"]) {
|
|
13566
13581
|
const loggingConfigs = properties["DeliveryStatusLogging"];
|
|
13567
13582
|
for (const config of loggingConfigs) {
|
|
13568
|
-
const protocol = config["Protocol"];
|
|
13583
|
+
const protocol = normalizeDeliveryStatusProtocolOrThrow(config["Protocol"], logicalId);
|
|
13569
13584
|
if (config["SuccessFeedbackRoleArn"]) {
|
|
13570
13585
|
await this.snsClient.send(
|
|
13571
13586
|
new SetTopicAttributesCommand({
|
|
@@ -13658,7 +13673,7 @@ var SNSTopicProvider = class {
|
|
|
13658
13673
|
if (JSON.stringify(properties["DeliveryStatusLogging"]) !== JSON.stringify(previousProperties["DeliveryStatusLogging"])) {
|
|
13659
13674
|
const loggingConfigs = properties["DeliveryStatusLogging"] || [];
|
|
13660
13675
|
for (const config of loggingConfigs) {
|
|
13661
|
-
const protocol = config["Protocol"];
|
|
13676
|
+
const protocol = normalizeDeliveryStatusProtocolOrThrow(config["Protocol"], logicalId);
|
|
13662
13677
|
if (config["SuccessFeedbackRoleArn"]) {
|
|
13663
13678
|
await this.snsClient.send(
|
|
13664
13679
|
new SetTopicAttributesCommand({
|
|
@@ -13798,16 +13813,22 @@ var SNSTopicProvider = class {
|
|
|
13798
13813
|
* FailureFeedbackRoleArn?}]`. Walks the known protocol prefix list
|
|
13799
13814
|
* (`HTTP` / `HTTPS` / `SQS` / `Lambda` / `Firehose` / `Application`); a
|
|
13800
13815
|
* protocol is included in the result iff at least one of its three
|
|
13801
|
-
* sub-attributes is set on the topic. Entries are sorted by
|
|
13802
|
-
* for stable positional compare (AWS does not
|
|
13803
|
-
* across `GetTopicAttributes` calls).
|
|
13816
|
+
* sub-attributes is set on the topic. Entries are sorted by canonical
|
|
13817
|
+
* PascalCase `Protocol` for stable positional compare (AWS does not
|
|
13818
|
+
* preserve template order across `GetTopicAttributes` calls).
|
|
13819
|
+
*
|
|
13820
|
+
* The emitted `Protocol` value preserves state's case when known
|
|
13821
|
+
* (CDK templates emit lowercase `'lambda'` / `'sqs'` / ...; AWS's
|
|
13822
|
+
* attribute prefix is PascalCase). Without case preservation the
|
|
13823
|
+
* comparator would fire false drift on every clean run for any
|
|
13824
|
+
* lowercase-`Protocol` template.
|
|
13804
13825
|
*
|
|
13805
13826
|
* `Subscription` is omitted because CDK manages it via separate
|
|
13806
13827
|
* `AWS::SNS::Subscription` resources, not as a Topic property.
|
|
13807
13828
|
*
|
|
13808
13829
|
* Returns `undefined` when the topic is gone (`NotFoundException`).
|
|
13809
13830
|
*/
|
|
13810
|
-
async readCurrentState(physicalId, _logicalId, _resourceType) {
|
|
13831
|
+
async readCurrentState(physicalId, _logicalId, _resourceType, properties) {
|
|
13811
13832
|
let attrs;
|
|
13812
13833
|
try {
|
|
13813
13834
|
const resp = await this.snsClient.send(
|
|
@@ -13849,7 +13870,10 @@ var SNSTopicProvider = class {
|
|
|
13849
13870
|
}
|
|
13850
13871
|
}
|
|
13851
13872
|
}
|
|
13852
|
-
result["DeliveryStatusLogging"] = mapDeliveryStatusLogging(
|
|
13873
|
+
result["DeliveryStatusLogging"] = mapDeliveryStatusLogging(
|
|
13874
|
+
attrs,
|
|
13875
|
+
stateProtocolCaseMap(properties?.["DeliveryStatusLogging"])
|
|
13876
|
+
);
|
|
13853
13877
|
try {
|
|
13854
13878
|
const tagsResp = await this.snsClient.send(
|
|
13855
13879
|
new ListTagsForResourceCommand({ ResourceArn: physicalId })
|
|
@@ -13938,7 +13962,55 @@ var SNS_DELIVERY_STATUS_PROTOCOLS = [
|
|
|
13938
13962
|
"Lambda",
|
|
13939
13963
|
"SQS"
|
|
13940
13964
|
];
|
|
13941
|
-
function
|
|
13965
|
+
function normalizeDeliveryStatusProtocol(input) {
|
|
13966
|
+
if (typeof input !== "string")
|
|
13967
|
+
return void 0;
|
|
13968
|
+
const lower = input.toLowerCase();
|
|
13969
|
+
switch (lower) {
|
|
13970
|
+
case "application":
|
|
13971
|
+
return "Application";
|
|
13972
|
+
case "firehose":
|
|
13973
|
+
return "Firehose";
|
|
13974
|
+
case "http":
|
|
13975
|
+
return "HTTP";
|
|
13976
|
+
case "https":
|
|
13977
|
+
return "HTTPS";
|
|
13978
|
+
case "lambda":
|
|
13979
|
+
return "Lambda";
|
|
13980
|
+
case "sqs":
|
|
13981
|
+
return "SQS";
|
|
13982
|
+
default:
|
|
13983
|
+
return void 0;
|
|
13984
|
+
}
|
|
13985
|
+
}
|
|
13986
|
+
function normalizeDeliveryStatusProtocolOrThrow(input, logicalId) {
|
|
13987
|
+
const normalized = normalizeDeliveryStatusProtocol(input);
|
|
13988
|
+
if (normalized === void 0) {
|
|
13989
|
+
throw new Error(
|
|
13990
|
+
`SNS topic ${logicalId}: unsupported DeliveryStatusLogging protocol ${JSON.stringify(input)}. Expected one of ${SNS_DELIVERY_STATUS_PROTOCOLS.join(", ")} (case-insensitive).`
|
|
13991
|
+
);
|
|
13992
|
+
}
|
|
13993
|
+
return normalized;
|
|
13994
|
+
}
|
|
13995
|
+
function stateProtocolCaseMap(stateLogging) {
|
|
13996
|
+
const map = /* @__PURE__ */ new Map();
|
|
13997
|
+
if (!Array.isArray(stateLogging))
|
|
13998
|
+
return map;
|
|
13999
|
+
for (const entry of stateLogging) {
|
|
14000
|
+
if (!entry || typeof entry !== "object")
|
|
14001
|
+
continue;
|
|
14002
|
+
const raw = entry["Protocol"];
|
|
14003
|
+
if (typeof raw !== "string")
|
|
14004
|
+
continue;
|
|
14005
|
+
const normalized = normalizeDeliveryStatusProtocol(raw);
|
|
14006
|
+
if (!normalized)
|
|
14007
|
+
continue;
|
|
14008
|
+
if (!map.has(normalized))
|
|
14009
|
+
map.set(normalized, raw);
|
|
14010
|
+
}
|
|
14011
|
+
return map;
|
|
14012
|
+
}
|
|
14013
|
+
function mapDeliveryStatusLogging(attrs, stateCaseMap = /* @__PURE__ */ new Map()) {
|
|
13942
14014
|
const result = [];
|
|
13943
14015
|
for (const protocol of SNS_DELIVERY_STATUS_PROTOCOLS) {
|
|
13944
14016
|
const success = attrs[`${protocol}SuccessFeedbackRoleArn`];
|
|
@@ -13946,7 +14018,9 @@ function mapDeliveryStatusLogging(attrs) {
|
|
|
13946
14018
|
const failure = attrs[`${protocol}FailureFeedbackRoleArn`];
|
|
13947
14019
|
if (success === void 0 && sample === void 0 && failure === void 0)
|
|
13948
14020
|
continue;
|
|
13949
|
-
const entry = {
|
|
14021
|
+
const entry = {
|
|
14022
|
+
Protocol: stateCaseMap.get(protocol) ?? protocol
|
|
14023
|
+
};
|
|
13950
14024
|
if (success !== void 0)
|
|
13951
14025
|
entry["SuccessFeedbackRoleArn"] = success;
|
|
13952
14026
|
if (sample !== void 0)
|
|
@@ -27595,7 +27669,18 @@ var ECSProvider = class {
|
|
|
27595
27669
|
let resp;
|
|
27596
27670
|
try {
|
|
27597
27671
|
resp = await this.getClient().send(
|
|
27598
|
-
|
|
27672
|
+
// AWS DescribeClusters omits `settings` / `configuration` from the
|
|
27673
|
+
// response unless they are explicitly requested via `include`. Without
|
|
27674
|
+
// SETTINGS / CONFIGURATIONS the readCurrentState round-trip silently
|
|
27675
|
+
// surfaces empty `ClusterSettings: []` even when the cluster has
|
|
27676
|
+
// containerInsights enabled — a console-side toggle then can't be
|
|
27677
|
+
// detected as drift because both the deploy-time observedProperties
|
|
27678
|
+
// baseline AND the drift-time AWS read would identically miss the
|
|
27679
|
+
// field. Discovered by the drift-revert integ test (PR #201).
|
|
27680
|
+
new DescribeClustersCommand({
|
|
27681
|
+
clusters: [physicalId],
|
|
27682
|
+
include: ["TAGS", "SETTINGS", "CONFIGURATIONS"]
|
|
27683
|
+
})
|
|
27599
27684
|
);
|
|
27600
27685
|
} catch {
|
|
27601
27686
|
return void 0;
|
|
@@ -32342,7 +32427,10 @@ var ServiceDiscoveryProvider = class {
|
|
|
32342
32427
|
providerRegion = process.env["AWS_REGION"];
|
|
32343
32428
|
logger = getLogger().child("ServiceDiscoveryProvider");
|
|
32344
32429
|
handledProperties = /* @__PURE__ */ new Map([
|
|
32345
|
-
[
|
|
32430
|
+
[
|
|
32431
|
+
"AWS::ServiceDiscovery::PrivateDnsNamespace",
|
|
32432
|
+
/* @__PURE__ */ new Set(["Name", "Vpc", "Description", "Tags", "Properties"])
|
|
32433
|
+
],
|
|
32346
32434
|
[
|
|
32347
32435
|
"AWS::ServiceDiscovery::Service",
|
|
32348
32436
|
/* @__PURE__ */ new Set([
|
|
@@ -32444,13 +32532,18 @@ var ServiceDiscoveryProvider = class {
|
|
|
32444
32532
|
logicalId
|
|
32445
32533
|
);
|
|
32446
32534
|
}
|
|
32535
|
+
const propsBag = properties["Properties"];
|
|
32536
|
+
const dnsProps = propsBag?.["DnsProperties"];
|
|
32537
|
+
const soa = dnsProps?.["SOA"];
|
|
32538
|
+
const inputProperties = soa?.TTL !== void 0 ? { DnsProperties: { SOA: { TTL: Number(soa.TTL) } } } : void 0;
|
|
32447
32539
|
try {
|
|
32448
32540
|
const response = await client.send(
|
|
32449
32541
|
new CreatePrivateDnsNamespaceCommand({
|
|
32450
32542
|
Name: name,
|
|
32451
32543
|
Vpc: vpc,
|
|
32452
32544
|
...description && { Description: description },
|
|
32453
|
-
...tags && tags.length > 0 && { Tags: tags }
|
|
32545
|
+
...tags && tags.length > 0 && { Tags: tags },
|
|
32546
|
+
...inputProperties && { Properties: inputProperties }
|
|
32454
32547
|
})
|
|
32455
32548
|
);
|
|
32456
32549
|
const operationId = response.OperationId;
|
|
@@ -32840,6 +32933,12 @@ var ServiceDiscoveryProvider = class {
|
|
|
32840
32933
|
if (ns.Name !== void 0)
|
|
32841
32934
|
result["Name"] = ns.Name;
|
|
32842
32935
|
result["Description"] = ns.Description ?? "";
|
|
32936
|
+
const soa = ns.Properties?.DnsProperties?.SOA;
|
|
32937
|
+
if (soa?.TTL !== void 0) {
|
|
32938
|
+
result["Properties"] = { DnsProperties: { SOA: { TTL: soa.TTL } } };
|
|
32939
|
+
} else {
|
|
32940
|
+
result["Properties"] = {};
|
|
32941
|
+
}
|
|
32843
32942
|
if (ns.Arn)
|
|
32844
32943
|
await this.attachTags(result, ns.Arn);
|
|
32845
32944
|
return result;
|
|
@@ -43153,7 +43252,10 @@ async function destroyCommand(stackArgs, options) {
|
|
|
43153
43252
|
appStacks = result.stacks.map((s) => ({
|
|
43154
43253
|
stackName: s.stackName,
|
|
43155
43254
|
displayName: s.displayName,
|
|
43156
|
-
...s.region && { region: s.region }
|
|
43255
|
+
...s.region && { region: s.region },
|
|
43256
|
+
...s.terminationProtection !== void 0 && {
|
|
43257
|
+
terminationProtection: s.terminationProtection
|
|
43258
|
+
}
|
|
43157
43259
|
}));
|
|
43158
43260
|
} catch {
|
|
43159
43261
|
logger.debug("Could not synthesize app, falling back to state-based stack list");
|
|
@@ -43212,6 +43314,12 @@ Preparing to destroy stack: ${stackName}`);
|
|
|
43212
43314
|
const refs = stateRefsByName.get(stackName) ?? [];
|
|
43213
43315
|
const synthStack = appStacks.find((s) => s.stackName === stackName);
|
|
43214
43316
|
const synthRegion = synthStack?.region;
|
|
43317
|
+
if (synthStack?.terminationProtection === true) {
|
|
43318
|
+
const err = new StackTerminationProtectionError(stackName);
|
|
43319
|
+
logger.error(` \u2717 ${err.message}`);
|
|
43320
|
+
totalErrors++;
|
|
43321
|
+
continue;
|
|
43322
|
+
}
|
|
43215
43323
|
let stackTargetRegion;
|
|
43216
43324
|
if (refs.length === 0) {
|
|
43217
43325
|
logger.warn(`No state found for stack ${stackName}, skipping`);
|
|
@@ -46250,7 +46358,7 @@ function reorderArgs(argv) {
|
|
|
46250
46358
|
}
|
|
46251
46359
|
async function main() {
|
|
46252
46360
|
const program = new Command14();
|
|
46253
|
-
program.name("cdkd").description("CDK Direct - Deploy AWS CDK apps directly via SDK/Cloud Control API").version("0.
|
|
46361
|
+
program.name("cdkd").description("CDK Direct - Deploy AWS CDK apps directly via SDK/Cloud Control API").version("0.58.0");
|
|
46254
46362
|
program.addCommand(createBootstrapCommand());
|
|
46255
46363
|
program.addCommand(createSynthCommand());
|
|
46256
46364
|
program.addCommand(createListCommand());
|