@go-to-k/cdkd 0.25.0 → 0.27.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 +59 -16
- package/dist/cli.js +1053 -186
- package/dist/cli.js.map +4 -4
- package/dist/go-to-k-cdkd-0.27.0.tgz +0 -0
- package/dist/index.js +29 -2
- package/dist/index.js.map +2 -2
- package/package.json +1 -1
- package/dist/go-to-k-cdkd-0.25.0.tgz +0 -0
package/dist/cli.js
CHANGED
|
@@ -528,29 +528,82 @@ function parseDuration(value) {
|
|
|
528
528
|
const multiplier = unit === "s" ? 1e3 : unit === "m" ? 6e4 : 36e5;
|
|
529
529
|
return Math.round(num * multiplier);
|
|
530
530
|
}
|
|
531
|
+
var RESOURCE_TYPE_REGEX = /^[A-Z][A-Za-z0-9]+::[A-Z][A-Za-z0-9]+::[A-Z][A-Za-z0-9]+$/;
|
|
532
|
+
function parseResourceTimeoutToken(flagName) {
|
|
533
|
+
return (raw, previous) => {
|
|
534
|
+
const acc = previous ?? { perTypeMs: {} };
|
|
535
|
+
if (!acc.perTypeMs)
|
|
536
|
+
acc.perTypeMs = {};
|
|
537
|
+
const eqIndex = raw.indexOf("=");
|
|
538
|
+
if (eqIndex === -1) {
|
|
539
|
+
acc.globalMs = parseDuration(raw);
|
|
540
|
+
return acc;
|
|
541
|
+
}
|
|
542
|
+
const typePart = raw.substring(0, eqIndex).trim();
|
|
543
|
+
const durationPart = raw.substring(eqIndex + 1).trim();
|
|
544
|
+
if (!RESOURCE_TYPE_REGEX.test(typePart)) {
|
|
545
|
+
throw new Error(
|
|
546
|
+
`Invalid ${flagName} value "${raw}": left-hand side must be a CloudFormation resource type like AWS::Service::Resource (got "${typePart}")`
|
|
547
|
+
);
|
|
548
|
+
}
|
|
549
|
+
if (durationPart.length === 0) {
|
|
550
|
+
throw new Error(
|
|
551
|
+
`Invalid ${flagName} value "${raw}": missing duration after '=' (e.g. ${typePart}=1h)`
|
|
552
|
+
);
|
|
553
|
+
}
|
|
554
|
+
let ms;
|
|
555
|
+
try {
|
|
556
|
+
ms = parseDuration(durationPart);
|
|
557
|
+
} catch (err) {
|
|
558
|
+
const inner = err instanceof Error ? err.message : String(err);
|
|
559
|
+
throw new Error(`Invalid ${flagName} value "${raw}": ${inner}`);
|
|
560
|
+
}
|
|
561
|
+
acc.perTypeMs[typePart] = ms;
|
|
562
|
+
return acc;
|
|
563
|
+
};
|
|
564
|
+
}
|
|
531
565
|
var resourceTimeoutOptions = [
|
|
532
|
-
// Default
|
|
533
|
-
//
|
|
534
|
-
//
|
|
535
|
-
//
|
|
536
|
-
// `
|
|
537
|
-
//
|
|
566
|
+
// Default is `undefined` (NOT a pre-seeded ResourceTimeoutOption) — the
|
|
567
|
+
// command handler resolves missing globalMs to DEFAULT_RESOURCE_*_MS
|
|
568
|
+
// at the call site. Pre-seeding here would force every accumulator
|
|
569
|
+
// call to carry a snapshot, and would also surprise unit tests that
|
|
570
|
+
// expect `opts.resourceTimeout` to be `undefined` when the flag is not
|
|
571
|
+
// passed.
|
|
538
572
|
new Option(
|
|
539
|
-
"--resource-warn-after <duration>",
|
|
540
|
-
"Warn when a single resource operation exceeds this wall-clock duration (e.g. 5m
|
|
541
|
-
).default(
|
|
573
|
+
"--resource-warn-after <duration_or_type=duration>",
|
|
574
|
+
"Warn when a single resource operation exceeds this wall-clock duration. Repeatable: pass a bare duration (e.g. 5m) to set the global default, or TYPE=DURATION (e.g. AWS::CloudFront::Distribution=10m) for a per-type override."
|
|
575
|
+
).default(void 0, "5m").argParser(parseResourceTimeoutToken("--resource-warn-after")),
|
|
542
576
|
new Option(
|
|
543
|
-
"--resource-timeout <duration>",
|
|
544
|
-
"Abort a single resource operation that exceeds this wall-clock duration. Custom-Resource-heavy stacks may need to raise this above the default 30m (the Custom Resource provider's polling cap is 1h)."
|
|
545
|
-
).default(
|
|
577
|
+
"--resource-timeout <duration_or_type=duration>",
|
|
578
|
+
"Abort a single resource operation that exceeds this wall-clock duration. Repeatable: pass a bare duration (e.g. 30m) to set the global default, or TYPE=DURATION (e.g. AWS::CloudFront::Distribution=1h) for a per-type override. Custom-Resource-heavy stacks may need to raise this above the default 30m (the Custom Resource provider's polling cap is 1h)."
|
|
579
|
+
).default(void 0, "30m").argParser(parseResourceTimeoutToken("--resource-timeout"))
|
|
546
580
|
];
|
|
547
581
|
function validateResourceTimeouts(opts) {
|
|
548
582
|
const warn = opts.resourceWarnAfter;
|
|
549
583
|
const timeout = opts.resourceTimeout;
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
)
|
|
584
|
+
const globalWarn = warn?.globalMs;
|
|
585
|
+
const globalTimeout = timeout?.globalMs;
|
|
586
|
+
if (typeof globalWarn === "number" && typeof globalTimeout === "number") {
|
|
587
|
+
if (globalWarn >= globalTimeout) {
|
|
588
|
+
throw new Error(
|
|
589
|
+
`--resource-warn-after (${globalWarn}ms) must be less than --resource-timeout (${globalTimeout}ms)`
|
|
590
|
+
);
|
|
591
|
+
}
|
|
592
|
+
}
|
|
593
|
+
const warnPerType = warn?.perTypeMs ?? {};
|
|
594
|
+
const timeoutPerType = timeout?.perTypeMs ?? {};
|
|
595
|
+
const types = /* @__PURE__ */ new Set([...Object.keys(warnPerType), ...Object.keys(timeoutPerType)]);
|
|
596
|
+
for (const t of types) {
|
|
597
|
+
const effectiveWarn = warnPerType[t] ?? globalWarn;
|
|
598
|
+
const effectiveTimeout = timeoutPerType[t] ?? globalTimeout;
|
|
599
|
+
if (typeof effectiveWarn !== "number" || typeof effectiveTimeout !== "number") {
|
|
600
|
+
continue;
|
|
601
|
+
}
|
|
602
|
+
if (effectiveWarn >= effectiveTimeout) {
|
|
603
|
+
throw new Error(
|
|
604
|
+
`--resource-warn-after for ${t} (${effectiveWarn}ms) must be less than --resource-timeout for ${t} (${effectiveTimeout}ms)`
|
|
605
|
+
);
|
|
606
|
+
}
|
|
554
607
|
}
|
|
555
608
|
}
|
|
556
609
|
var deployOptions = [
|
|
@@ -8585,6 +8638,33 @@ var IAMRoleProvider = class {
|
|
|
8585
8638
|
this.logger.debug(`Added/updated ${tagsToAdd.length} tags on role ${roleName}`);
|
|
8586
8639
|
}
|
|
8587
8640
|
}
|
|
8641
|
+
/**
|
|
8642
|
+
* Resolve a single `Fn::GetAtt` attribute for an existing IAM role.
|
|
8643
|
+
*
|
|
8644
|
+
* CloudFormation's `AWS::IAM::Role` exposes `Arn` and `RoleId`; both are
|
|
8645
|
+
* available from the `GetRole` response. See:
|
|
8646
|
+
* https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-iam-role.html#aws-resource-iam-role-return-values
|
|
8647
|
+
*
|
|
8648
|
+
* Used by `cdkd orphan` to live-fetch attribute values that need to be
|
|
8649
|
+
* substituted into sibling references.
|
|
8650
|
+
*/
|
|
8651
|
+
async getAttribute(physicalId, _resourceType, attributeName) {
|
|
8652
|
+
try {
|
|
8653
|
+
const resp = await this.iamClient.send(new GetRoleCommand({ RoleName: physicalId }));
|
|
8654
|
+
switch (attributeName) {
|
|
8655
|
+
case "Arn":
|
|
8656
|
+
return resp.Role?.Arn;
|
|
8657
|
+
case "RoleId":
|
|
8658
|
+
return resp.Role?.RoleId;
|
|
8659
|
+
default:
|
|
8660
|
+
return void 0;
|
|
8661
|
+
}
|
|
8662
|
+
} catch (err) {
|
|
8663
|
+
if (err instanceof NoSuchEntityException)
|
|
8664
|
+
return void 0;
|
|
8665
|
+
throw err;
|
|
8666
|
+
}
|
|
8667
|
+
}
|
|
8588
8668
|
/**
|
|
8589
8669
|
* Adopt an existing IAM role into cdkd state.
|
|
8590
8670
|
*
|
|
@@ -10435,17 +10515,35 @@ var S3BucketProvider = class {
|
|
|
10435
10515
|
return region || "us-east-1";
|
|
10436
10516
|
}
|
|
10437
10517
|
/**
|
|
10438
|
-
* Build attributes for an S3 bucket
|
|
10518
|
+
* Build attributes for an S3 bucket.
|
|
10519
|
+
*
|
|
10520
|
+
* Covers every CloudFormation `Fn::GetAtt` return value for
|
|
10521
|
+
* `AWS::S3::Bucket`. All fields are derivable from `bucketName` + region —
|
|
10522
|
+
* no extra AWS API call is needed. See:
|
|
10523
|
+
* https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-s3-bucket.html#aws-properties-s3-bucket-return-values
|
|
10439
10524
|
*/
|
|
10440
10525
|
async buildAttributes(bucketName) {
|
|
10441
10526
|
const region = await this.getRegion();
|
|
10442
10527
|
return {
|
|
10443
10528
|
Arn: `arn:aws:s3:::${bucketName}`,
|
|
10444
10529
|
DomainName: `${bucketName}.s3.amazonaws.com`,
|
|
10530
|
+
DualStackDomainName: `${bucketName}.s3.dualstack.${region}.amazonaws.com`,
|
|
10445
10531
|
RegionalDomainName: `${bucketName}.s3.${region}.amazonaws.com`,
|
|
10446
10532
|
WebsiteURL: `http://${bucketName}.s3-website-${region}.amazonaws.com`
|
|
10447
10533
|
};
|
|
10448
10534
|
}
|
|
10535
|
+
/**
|
|
10536
|
+
* Resolve a single `Fn::GetAtt` attribute for an existing bucket.
|
|
10537
|
+
*
|
|
10538
|
+
* Used by `cdkd orphan` to live-fetch attribute values that need to be
|
|
10539
|
+
* substituted into sibling references. All S3 Bucket attributes are
|
|
10540
|
+
* derivable from bucket name + region, so this avoids the round trip and
|
|
10541
|
+
* reuses the same templating as `buildAttributes`.
|
|
10542
|
+
*/
|
|
10543
|
+
async getAttribute(physicalId, _resourceType, attributeName) {
|
|
10544
|
+
const attrs = await this.buildAttributes(physicalId);
|
|
10545
|
+
return attrs[attributeName];
|
|
10546
|
+
}
|
|
10449
10547
|
/**
|
|
10450
10548
|
* Apply versioning configuration if specified
|
|
10451
10549
|
*/
|
|
@@ -11740,6 +11838,43 @@ var SQSQueueProvider = class {
|
|
|
11740
11838
|
return `arn:aws:sqs:unknown:unknown:${queueName}`;
|
|
11741
11839
|
}
|
|
11742
11840
|
}
|
|
11841
|
+
/**
|
|
11842
|
+
* Resolve a single `Fn::GetAtt` attribute for an existing SQS queue.
|
|
11843
|
+
*
|
|
11844
|
+
* CloudFormation's `AWS::SQS::Queue` exposes `Arn`, `QueueName` and
|
|
11845
|
+
* `QueueUrl`. The cdkd physicalId is the queue URL; `QueueUrl` and
|
|
11846
|
+
* `QueueName` are derivable from it without an AWS call, while `Arn`
|
|
11847
|
+
* requires `GetQueueAttributes`. See:
|
|
11848
|
+
* https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-sqs-queues.html#aws-properties-sqs-queues-return-values
|
|
11849
|
+
*
|
|
11850
|
+
* Used by `cdkd orphan` to live-fetch attribute values that need to be
|
|
11851
|
+
* substituted into sibling references.
|
|
11852
|
+
*/
|
|
11853
|
+
async getAttribute(physicalId, _resourceType, attributeName) {
|
|
11854
|
+
switch (attributeName) {
|
|
11855
|
+
case "QueueUrl":
|
|
11856
|
+
return physicalId;
|
|
11857
|
+
case "QueueName":
|
|
11858
|
+
return physicalId.substring(physicalId.lastIndexOf("/") + 1);
|
|
11859
|
+
case "Arn": {
|
|
11860
|
+
try {
|
|
11861
|
+
const resp = await this.sqsClient.send(
|
|
11862
|
+
new GetQueueAttributesCommand({
|
|
11863
|
+
QueueUrl: physicalId,
|
|
11864
|
+
AttributeNames: ["QueueArn"]
|
|
11865
|
+
})
|
|
11866
|
+
);
|
|
11867
|
+
return resp.Attributes?.["QueueArn"];
|
|
11868
|
+
} catch (err) {
|
|
11869
|
+
if (err instanceof QueueDoesNotExist)
|
|
11870
|
+
return void 0;
|
|
11871
|
+
throw err;
|
|
11872
|
+
}
|
|
11873
|
+
}
|
|
11874
|
+
default:
|
|
11875
|
+
return void 0;
|
|
11876
|
+
}
|
|
11877
|
+
}
|
|
11743
11878
|
/**
|
|
11744
11879
|
* Adopt an existing SQS queue into cdkd state.
|
|
11745
11880
|
*
|
|
@@ -12275,6 +12410,28 @@ var SNSTopicProvider = class {
|
|
|
12275
12410
|
);
|
|
12276
12411
|
}
|
|
12277
12412
|
}
|
|
12413
|
+
/**
|
|
12414
|
+
* Resolve a single `Fn::GetAtt` attribute for an existing SNS topic.
|
|
12415
|
+
*
|
|
12416
|
+
* CloudFormation's `AWS::SNS::Topic` exposes `TopicName` and `TopicArn`.
|
|
12417
|
+
* The cdkd physicalId is the topic ARN, so both are derivable without
|
|
12418
|
+
* an AWS call. See:
|
|
12419
|
+
* https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-sns-topic.html#aws-properties-sns-topic-return-values
|
|
12420
|
+
*
|
|
12421
|
+
* Used by `cdkd orphan` to live-fetch attribute values that need to be
|
|
12422
|
+
* substituted into sibling references.
|
|
12423
|
+
*/
|
|
12424
|
+
// eslint-disable-next-line @typescript-eslint/require-await -- consistent async signature with other providers
|
|
12425
|
+
async getAttribute(physicalId, _resourceType, attributeName) {
|
|
12426
|
+
switch (attributeName) {
|
|
12427
|
+
case "TopicArn":
|
|
12428
|
+
return physicalId;
|
|
12429
|
+
case "TopicName":
|
|
12430
|
+
return physicalId.split(":").pop();
|
|
12431
|
+
default:
|
|
12432
|
+
return void 0;
|
|
12433
|
+
}
|
|
12434
|
+
}
|
|
12278
12435
|
/**
|
|
12279
12436
|
* Adopt an existing SNS topic into cdkd state.
|
|
12280
12437
|
*
|
|
@@ -13256,6 +13413,43 @@ var LambdaFunctionProvider = class {
|
|
|
13256
13413
|
}
|
|
13257
13414
|
return (crc ^ 4294967295) >>> 0;
|
|
13258
13415
|
}
|
|
13416
|
+
/**
|
|
13417
|
+
* Resolve a single `Fn::GetAtt` attribute for an existing Lambda function.
|
|
13418
|
+
*
|
|
13419
|
+
* CloudFormation's `AWS::Lambda::Function` exposes `Arn`,
|
|
13420
|
+
* `SnapStartResponse.ApplyOn`, and `SnapStartResponse.OptimizationStatus`
|
|
13421
|
+
* as documented at
|
|
13422
|
+
* https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-lambda-function.html#aws-resource-lambda-function-return-values.
|
|
13423
|
+
*
|
|
13424
|
+
* All three live in the same `GetFunction` response (`Configuration.FunctionArn`
|
|
13425
|
+
* and `Configuration.SnapStart.{ApplyOn,OptimizationStatus}`), so a single API
|
|
13426
|
+
* call covers every supported attr. Used by `cdkd orphan` to live-fetch
|
|
13427
|
+
* attribute values that need to be substituted into sibling references.
|
|
13428
|
+
*/
|
|
13429
|
+
async getAttribute(physicalId, _resourceType, attributeName) {
|
|
13430
|
+
if (attributeName !== "Arn" && attributeName !== "SnapStartResponse.ApplyOn" && attributeName !== "SnapStartResponse.OptimizationStatus") {
|
|
13431
|
+
return void 0;
|
|
13432
|
+
}
|
|
13433
|
+
try {
|
|
13434
|
+
const resp = await this.lambdaClient.send(
|
|
13435
|
+
new GetFunctionCommand({ FunctionName: physicalId })
|
|
13436
|
+
);
|
|
13437
|
+
switch (attributeName) {
|
|
13438
|
+
case "Arn":
|
|
13439
|
+
return resp.Configuration?.FunctionArn;
|
|
13440
|
+
case "SnapStartResponse.ApplyOn":
|
|
13441
|
+
return resp.Configuration?.SnapStart?.ApplyOn;
|
|
13442
|
+
case "SnapStartResponse.OptimizationStatus":
|
|
13443
|
+
return resp.Configuration?.SnapStart?.OptimizationStatus;
|
|
13444
|
+
default:
|
|
13445
|
+
return void 0;
|
|
13446
|
+
}
|
|
13447
|
+
} catch (err) {
|
|
13448
|
+
if (err instanceof ResourceNotFoundException)
|
|
13449
|
+
return void 0;
|
|
13450
|
+
throw err;
|
|
13451
|
+
}
|
|
13452
|
+
}
|
|
13259
13453
|
/**
|
|
13260
13454
|
* Adopt an existing Lambda function into cdkd state.
|
|
13261
13455
|
*
|
|
@@ -13528,6 +13722,7 @@ var LambdaPermissionProvider = class {
|
|
|
13528
13722
|
import {
|
|
13529
13723
|
CreateFunctionUrlConfigCommand,
|
|
13530
13724
|
DeleteFunctionUrlConfigCommand,
|
|
13725
|
+
GetFunctionUrlConfigCommand as GetFunctionUrlConfigCommand2,
|
|
13531
13726
|
UpdateFunctionUrlConfigCommand,
|
|
13532
13727
|
ResourceNotFoundException as ResourceNotFoundException3
|
|
13533
13728
|
} from "@aws-sdk/client-lambda";
|
|
@@ -13655,6 +13850,36 @@ var LambdaUrlProvider = class {
|
|
|
13655
13850
|
);
|
|
13656
13851
|
}
|
|
13657
13852
|
}
|
|
13853
|
+
/**
|
|
13854
|
+
* Resolve a single `Fn::GetAtt` attribute for an existing Lambda Function
|
|
13855
|
+
* URL.
|
|
13856
|
+
*
|
|
13857
|
+
* CloudFormation's `AWS::Lambda::Url` exposes `FunctionArn` and
|
|
13858
|
+
* `FunctionUrl`. Both come from `GetFunctionUrlConfig`. See:
|
|
13859
|
+
* https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-lambda-url.html#aws-resource-lambda-url-return-values
|
|
13860
|
+
*
|
|
13861
|
+
* Used by `cdkd orphan` to live-fetch attribute values that need to be
|
|
13862
|
+
* substituted into sibling references.
|
|
13863
|
+
*/
|
|
13864
|
+
async getAttribute(physicalId, _resourceType, attributeName) {
|
|
13865
|
+
try {
|
|
13866
|
+
const resp = await this.lambdaClient.send(
|
|
13867
|
+
new GetFunctionUrlConfigCommand2({ FunctionName: physicalId })
|
|
13868
|
+
);
|
|
13869
|
+
switch (attributeName) {
|
|
13870
|
+
case "FunctionArn":
|
|
13871
|
+
return resp.FunctionArn;
|
|
13872
|
+
case "FunctionUrl":
|
|
13873
|
+
return resp.FunctionUrl;
|
|
13874
|
+
default:
|
|
13875
|
+
return void 0;
|
|
13876
|
+
}
|
|
13877
|
+
} catch (err) {
|
|
13878
|
+
if (err instanceof ResourceNotFoundException3)
|
|
13879
|
+
return void 0;
|
|
13880
|
+
throw err;
|
|
13881
|
+
}
|
|
13882
|
+
}
|
|
13658
13883
|
/**
|
|
13659
13884
|
* Adopt an existing Lambda Function URL into cdkd state.
|
|
13660
13885
|
*
|
|
@@ -14366,6 +14591,40 @@ var DynamoDBTableProvider = class {
|
|
|
14366
14591
|
}
|
|
14367
14592
|
throw new Error(`Table ${tableName} did not reach ACTIVE status within ${maxAttempts} seconds`);
|
|
14368
14593
|
}
|
|
14594
|
+
/**
|
|
14595
|
+
* Resolve a single `Fn::GetAtt` attribute for an existing DynamoDB table.
|
|
14596
|
+
*
|
|
14597
|
+
* CloudFormation's `AWS::DynamoDB::Table` exposes `Arn`, `StreamArn`
|
|
14598
|
+
* (a.k.a. `LatestStreamArn` in the SDK; CFn returns the latest enabled
|
|
14599
|
+
* stream's ARN), and `LatestStreamLabel`. All three are sibling fields on
|
|
14600
|
+
* the same `DescribeTable` response, so a single API call covers every
|
|
14601
|
+
* supported attr. See:
|
|
14602
|
+
* https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-dynamodb-table.html#aws-resource-dynamodb-table-return-values
|
|
14603
|
+
*
|
|
14604
|
+
* Used by `cdkd orphan` to live-fetch attribute values that need to be
|
|
14605
|
+
* substituted into sibling references.
|
|
14606
|
+
*/
|
|
14607
|
+
async getAttribute(physicalId, _resourceType, attributeName) {
|
|
14608
|
+
try {
|
|
14609
|
+
const resp = await this.dynamoDBClient.send(
|
|
14610
|
+
new DescribeTableCommand2({ TableName: physicalId })
|
|
14611
|
+
);
|
|
14612
|
+
switch (attributeName) {
|
|
14613
|
+
case "Arn":
|
|
14614
|
+
return resp.Table?.TableArn;
|
|
14615
|
+
case "StreamArn":
|
|
14616
|
+
return resp.Table?.LatestStreamArn;
|
|
14617
|
+
case "LatestStreamLabel":
|
|
14618
|
+
return resp.Table?.LatestStreamLabel;
|
|
14619
|
+
default:
|
|
14620
|
+
return void 0;
|
|
14621
|
+
}
|
|
14622
|
+
} catch (err) {
|
|
14623
|
+
if (err instanceof ResourceNotFoundException6)
|
|
14624
|
+
return void 0;
|
|
14625
|
+
throw err;
|
|
14626
|
+
}
|
|
14627
|
+
}
|
|
14369
14628
|
/**
|
|
14370
14629
|
* Adopt an existing DynamoDB table into cdkd state.
|
|
14371
14630
|
*
|
|
@@ -14653,6 +14912,23 @@ var LogsLogGroupProvider = class {
|
|
|
14653
14912
|
return `arn:aws:logs:unknown:unknown:log-group:${logGroupName}:*`;
|
|
14654
14913
|
}
|
|
14655
14914
|
}
|
|
14915
|
+
/**
|
|
14916
|
+
* Resolve a single `Fn::GetAtt` attribute for an existing log group.
|
|
14917
|
+
*
|
|
14918
|
+
* CloudFormation's `AWS::Logs::LogGroup` exposes only `Arn`. The ARN is
|
|
14919
|
+
* derivable from the log group name + account + region via the existing
|
|
14920
|
+
* `buildArn` helper. See:
|
|
14921
|
+
* https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-logs-loggroup.html#aws-resource-logs-loggroup-return-values
|
|
14922
|
+
*
|
|
14923
|
+
* Used by `cdkd orphan` to live-fetch attribute values that need to be
|
|
14924
|
+
* substituted into sibling references.
|
|
14925
|
+
*/
|
|
14926
|
+
async getAttribute(physicalId, _resourceType, attributeName) {
|
|
14927
|
+
if (attributeName !== "Arn") {
|
|
14928
|
+
return void 0;
|
|
14929
|
+
}
|
|
14930
|
+
return this.buildArn(physicalId);
|
|
14931
|
+
}
|
|
14656
14932
|
/**
|
|
14657
14933
|
* Adopt an existing CloudWatch Logs log group into cdkd state.
|
|
14658
14934
|
*
|
|
@@ -16690,29 +16966,63 @@ var EC2Provider = class {
|
|
|
16690
16966
|
}
|
|
16691
16967
|
}
|
|
16692
16968
|
}
|
|
16969
|
+
/**
|
|
16970
|
+
* Resolve a single `Fn::GetAtt` attribute for an `AWS::EC2::VPC`.
|
|
16971
|
+
*
|
|
16972
|
+
* CloudFormation returns `CidrBlock`, `CidrBlockAssociations`,
|
|
16973
|
+
* `DefaultNetworkAcl`, `DefaultSecurityGroup`, and `Ipv6CidrBlocks`. See:
|
|
16974
|
+
* https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-ec2-vpc.html#aws-resource-ec2-vpc-return-values
|
|
16975
|
+
*
|
|
16976
|
+
* `DefaultNetworkAcl` and `DefaultSecurityGroup` previously returned wrong
|
|
16977
|
+
* values (DHCP options id and `undefined` respectively); the AWS console
|
|
16978
|
+
* surfaces these the same way as CFn — by filtering the relevant
|
|
16979
|
+
* `Describe*` API on `vpc-id` + the `default` flag.
|
|
16980
|
+
*/
|
|
16693
16981
|
async getVpcAttribute(physicalId, attributeName) {
|
|
16694
|
-
if (attributeName === "VpcId")
|
|
16695
|
-
return physicalId;
|
|
16696
16982
|
try {
|
|
16697
|
-
const response = await this.ec2Client.send(new DescribeVpcsCommand2({ VpcIds: [physicalId] }));
|
|
16698
|
-
const vpc = response.Vpcs?.[0];
|
|
16699
|
-
if (!vpc)
|
|
16700
|
-
return void 0;
|
|
16701
16983
|
switch (attributeName) {
|
|
16702
|
-
case "
|
|
16703
|
-
|
|
16704
|
-
|
|
16705
|
-
|
|
16706
|
-
|
|
16707
|
-
|
|
16708
|
-
|
|
16709
|
-
|
|
16710
|
-
|
|
16711
|
-
return
|
|
16712
|
-
|
|
16713
|
-
|
|
16714
|
-
|
|
16715
|
-
|
|
16984
|
+
case "DefaultNetworkAcl": {
|
|
16985
|
+
const resp = await this.ec2Client.send(
|
|
16986
|
+
new DescribeNetworkAclsCommand({
|
|
16987
|
+
Filters: [
|
|
16988
|
+
{ Name: "vpc-id", Values: [physicalId] },
|
|
16989
|
+
{ Name: "default", Values: ["true"] }
|
|
16990
|
+
]
|
|
16991
|
+
})
|
|
16992
|
+
);
|
|
16993
|
+
return resp.NetworkAcls?.[0]?.NetworkAclId;
|
|
16994
|
+
}
|
|
16995
|
+
case "DefaultSecurityGroup": {
|
|
16996
|
+
const resp = await this.ec2Client.send(
|
|
16997
|
+
new DescribeSecurityGroupsCommand2({
|
|
16998
|
+
Filters: [
|
|
16999
|
+
{ Name: "vpc-id", Values: [physicalId] },
|
|
17000
|
+
{ Name: "group-name", Values: ["default"] }
|
|
17001
|
+
]
|
|
17002
|
+
})
|
|
17003
|
+
);
|
|
17004
|
+
return resp.SecurityGroups?.[0]?.GroupId;
|
|
17005
|
+
}
|
|
17006
|
+
default: {
|
|
17007
|
+
const response = await this.ec2Client.send(
|
|
17008
|
+
new DescribeVpcsCommand2({ VpcIds: [physicalId] })
|
|
17009
|
+
);
|
|
17010
|
+
const vpc = response.Vpcs?.[0];
|
|
17011
|
+
if (!vpc)
|
|
17012
|
+
return void 0;
|
|
17013
|
+
switch (attributeName) {
|
|
17014
|
+
case "CidrBlock":
|
|
17015
|
+
return vpc.CidrBlock;
|
|
17016
|
+
case "Ipv6CidrBlocks":
|
|
17017
|
+
return vpc.Ipv6CidrBlockAssociationSet?.filter(
|
|
17018
|
+
(a) => a.Ipv6CidrBlockState?.State === "associated"
|
|
17019
|
+
).map((a) => a.Ipv6CidrBlock) || [];
|
|
17020
|
+
case "CidrBlockAssociations":
|
|
17021
|
+
return vpc.CidrBlockAssociationSet?.map((a) => a.AssociationId) || [];
|
|
17022
|
+
default:
|
|
17023
|
+
return void 0;
|
|
17024
|
+
}
|
|
17025
|
+
}
|
|
16716
17026
|
}
|
|
16717
17027
|
} catch {
|
|
16718
17028
|
return void 0;
|
|
@@ -31580,8 +31890,8 @@ var DeployEngine = class {
|
|
|
31580
31890
|
const baseLabel = `${verb} ${logicalId} (${resourceType})`;
|
|
31581
31891
|
renderer.addTask(logicalId, baseLabel);
|
|
31582
31892
|
const operationKind = change.changeType === "CREATE" ? "CREATE" : change.changeType === "DELETE" ? "DELETE" : "UPDATE";
|
|
31583
|
-
const warnAfterMs = this.options.resourceWarnAfterMs ?? DEFAULT_RESOURCE_WARN_AFTER_MS;
|
|
31584
|
-
const timeoutMs = this.options.resourceTimeoutMs ?? DEFAULT_RESOURCE_TIMEOUT_MS;
|
|
31893
|
+
const warnAfterMs = this.options.resourceWarnAfterByType?.[resourceType] ?? this.options.resourceWarnAfterMs ?? DEFAULT_RESOURCE_WARN_AFTER_MS;
|
|
31894
|
+
const timeoutMs = this.options.resourceTimeoutByType?.[resourceType] ?? this.options.resourceTimeoutMs ?? DEFAULT_RESOURCE_TIMEOUT_MS;
|
|
31585
31895
|
try {
|
|
31586
31896
|
await withResourceDeadline(
|
|
31587
31897
|
async () => {
|
|
@@ -32085,8 +32395,8 @@ async function deployCommand(stacks, options) {
|
|
|
32085
32395
|
}
|
|
32086
32396
|
warnIfDeprecatedRegion(options);
|
|
32087
32397
|
validateResourceTimeouts({
|
|
32088
|
-
resourceWarnAfter: options.resourceWarnAfter,
|
|
32089
|
-
resourceTimeout: options.resourceTimeout
|
|
32398
|
+
...options.resourceWarnAfter && { resourceWarnAfter: options.resourceWarnAfter },
|
|
32399
|
+
...options.resourceTimeout && { resourceTimeout: options.resourceTimeout }
|
|
32090
32400
|
});
|
|
32091
32401
|
if (!options.wait) {
|
|
32092
32402
|
process.env["CDKD_NO_WAIT"] = "true";
|
|
@@ -32281,8 +32591,18 @@ Deploying stack: ${stackInfo.stackName}${stackRegion !== baseRegion ? ` (region:
|
|
|
32281
32591
|
concurrency: options.concurrency,
|
|
32282
32592
|
dryRun: options.dryRun,
|
|
32283
32593
|
noRollback: !options.rollback,
|
|
32284
|
-
|
|
32285
|
-
|
|
32594
|
+
...options.resourceWarnAfter?.globalMs !== void 0 && {
|
|
32595
|
+
resourceWarnAfterMs: options.resourceWarnAfter.globalMs
|
|
32596
|
+
},
|
|
32597
|
+
...options.resourceTimeout?.globalMs !== void 0 && {
|
|
32598
|
+
resourceTimeoutMs: options.resourceTimeout.globalMs
|
|
32599
|
+
},
|
|
32600
|
+
...options.resourceWarnAfter?.perTypeMs && {
|
|
32601
|
+
resourceWarnAfterByType: options.resourceWarnAfter.perTypeMs
|
|
32602
|
+
},
|
|
32603
|
+
...options.resourceTimeout?.perTypeMs && {
|
|
32604
|
+
resourceTimeoutByType: options.resourceTimeout.perTypeMs
|
|
32605
|
+
}
|
|
32286
32606
|
},
|
|
32287
32607
|
stackRegion
|
|
32288
32608
|
);
|
|
@@ -32692,8 +33012,6 @@ Acquiring lock for stack ${stackName}...`);
|
|
|
32692
33012
|
logger.debug(
|
|
32693
33013
|
`Deletion level ${executionLevels.length - levelIndex}/${executionLevels.length} (${level.length} resources)`
|
|
32694
33014
|
);
|
|
32695
|
-
const warnAfterMs = ctx.resourceWarnAfterMs ?? DEFAULT_RESOURCE_WARN_AFTER_MS;
|
|
32696
|
-
const timeoutMs = ctx.resourceTimeoutMs ?? DEFAULT_RESOURCE_TIMEOUT_MS;
|
|
32697
33015
|
const stackRegion2 = state.region ?? ctx.baseRegion;
|
|
32698
33016
|
const deletePromises = level.map(async (logicalId) => {
|
|
32699
33017
|
const resource = state.resources[logicalId];
|
|
@@ -32701,6 +33019,8 @@ Acquiring lock for stack ${stackName}...`);
|
|
|
32701
33019
|
logger.warn(`Resource ${logicalId} not found in state, skipping`);
|
|
32702
33020
|
return;
|
|
32703
33021
|
}
|
|
33022
|
+
const warnAfterMs = ctx.resourceWarnAfterByType?.[resource.resourceType] ?? ctx.resourceWarnAfterMs ?? DEFAULT_RESOURCE_WARN_AFTER_MS;
|
|
33023
|
+
const timeoutMs = ctx.resourceTimeoutByType?.[resource.resourceType] ?? ctx.resourceTimeoutMs ?? DEFAULT_RESOURCE_TIMEOUT_MS;
|
|
32704
33024
|
const baseLabel = `Deleting ${logicalId} (${resource.resourceType})`;
|
|
32705
33025
|
renderer.addTask(logicalId, baseLabel);
|
|
32706
33026
|
try {
|
|
@@ -32821,8 +33141,8 @@ async function destroyCommand(stackArgs, options) {
|
|
|
32821
33141
|
}
|
|
32822
33142
|
warnIfDeprecatedRegion(options);
|
|
32823
33143
|
validateResourceTimeouts({
|
|
32824
|
-
resourceWarnAfter: options.resourceWarnAfter,
|
|
32825
|
-
resourceTimeout: options.resourceTimeout
|
|
33144
|
+
...options.resourceWarnAfter && { resourceWarnAfter: options.resourceWarnAfter },
|
|
33145
|
+
...options.resourceTimeout && { resourceTimeout: options.resourceTimeout }
|
|
32826
33146
|
});
|
|
32827
33147
|
const region = options.region || process.env["AWS_REGION"] || "us-east-1";
|
|
32828
33148
|
const stateBucket = await resolveStateBucketWithDefault(options.stateBucket, region);
|
|
@@ -32956,8 +33276,18 @@ Preparing to destroy stack: ${stackName}`);
|
|
|
32956
33276
|
...options.profile && { profile: options.profile },
|
|
32957
33277
|
stateBucket,
|
|
32958
33278
|
skipConfirmation: options.yes || options.force,
|
|
32959
|
-
|
|
32960
|
-
|
|
33279
|
+
...options.resourceWarnAfter?.globalMs !== void 0 && {
|
|
33280
|
+
resourceWarnAfterMs: options.resourceWarnAfter.globalMs
|
|
33281
|
+
},
|
|
33282
|
+
...options.resourceTimeout?.globalMs !== void 0 && {
|
|
33283
|
+
resourceTimeoutMs: options.resourceTimeout.globalMs
|
|
33284
|
+
},
|
|
33285
|
+
...options.resourceWarnAfter?.perTypeMs && {
|
|
33286
|
+
resourceWarnAfterByType: options.resourceWarnAfter.perTypeMs
|
|
33287
|
+
},
|
|
33288
|
+
...options.resourceTimeout?.perTypeMs && {
|
|
33289
|
+
resourceTimeoutByType: options.resourceTimeout.perTypeMs
|
|
33290
|
+
}
|
|
32961
33291
|
});
|
|
32962
33292
|
}
|
|
32963
33293
|
} finally {
|
|
@@ -32986,15 +33316,432 @@ function createDestroyCommand() {
|
|
|
32986
33316
|
import * as readline2 from "node:readline/promises";
|
|
32987
33317
|
import { Command as Command7 } from "commander";
|
|
32988
33318
|
init_aws_clients();
|
|
32989
|
-
|
|
33319
|
+
|
|
33320
|
+
// src/cli/cdk-path.ts
|
|
33321
|
+
function readCdkPath(resource) {
|
|
33322
|
+
const meta = resource.Metadata;
|
|
33323
|
+
if (!meta)
|
|
33324
|
+
return "";
|
|
33325
|
+
const v = meta["aws:cdk:path"];
|
|
33326
|
+
return typeof v === "string" ? v : "";
|
|
33327
|
+
}
|
|
33328
|
+
function buildCdkPathIndex(template) {
|
|
33329
|
+
const index = /* @__PURE__ */ new Map();
|
|
33330
|
+
for (const [logicalId, resource] of Object.entries(template.Resources)) {
|
|
33331
|
+
const path = readCdkPath(resource);
|
|
33332
|
+
if (path)
|
|
33333
|
+
index.set(path, logicalId);
|
|
33334
|
+
}
|
|
33335
|
+
return index;
|
|
33336
|
+
}
|
|
33337
|
+
|
|
33338
|
+
// src/analyzer/orphan-rewriter.ts
|
|
33339
|
+
var AttributeFetcher = class {
|
|
33340
|
+
constructor(orphans, providerRegistry, options) {
|
|
33341
|
+
this.orphans = orphans;
|
|
33342
|
+
this.providerRegistry = providerRegistry;
|
|
33343
|
+
this.options = options;
|
|
33344
|
+
}
|
|
33345
|
+
cache = /* @__PURE__ */ new Map();
|
|
33346
|
+
logger = getLogger().child("OrphanRewriter");
|
|
33347
|
+
/**
|
|
33348
|
+
* Return the orphan's resolved value for `Ref` (its physicalId) — never
|
|
33349
|
+
* needs an AWS call.
|
|
33350
|
+
*/
|
|
33351
|
+
ref(orphanLogicalId) {
|
|
33352
|
+
const o = this.orphans[orphanLogicalId];
|
|
33353
|
+
if (!o) {
|
|
33354
|
+
throw new Error(
|
|
33355
|
+
`Internal: Ref to '${orphanLogicalId}' has no orphan entry \u2014 should have been filtered out`
|
|
33356
|
+
);
|
|
33357
|
+
}
|
|
33358
|
+
return o.physicalId;
|
|
33359
|
+
}
|
|
33360
|
+
/**
|
|
33361
|
+
* Return the orphan's resolved value for `Fn::GetAtt`. Hits the live
|
|
33362
|
+
* provider on first call; subsequent calls reuse the cached result.
|
|
33363
|
+
*
|
|
33364
|
+
* Returns `{ ok: true, value }` on success; `{ ok: false, reason }`
|
|
33365
|
+
* when the live fetch failed AND the `--force` cache fallback either
|
|
33366
|
+
* was disabled or also lacked the attribute. In the cache-fallback
|
|
33367
|
+
* success path returns `{ ok: true, value, fromCache: true }`.
|
|
33368
|
+
*/
|
|
33369
|
+
async getAtt(orphanLogicalId, attribute) {
|
|
33370
|
+
const cacheKey = `${orphanLogicalId}\0${attribute}`;
|
|
33371
|
+
if (this.cache.has(cacheKey)) {
|
|
33372
|
+
return { ok: true, value: this.cache.get(cacheKey) };
|
|
33373
|
+
}
|
|
33374
|
+
const orphan = this.orphans[orphanLogicalId];
|
|
33375
|
+
if (!orphan) {
|
|
33376
|
+
return {
|
|
33377
|
+
ok: false,
|
|
33378
|
+
reason: `Internal: GetAtt to '${orphanLogicalId}' has no orphan entry`
|
|
33379
|
+
};
|
|
33380
|
+
}
|
|
33381
|
+
let provider;
|
|
33382
|
+
try {
|
|
33383
|
+
provider = this.providerRegistry.getProvider(orphan.resourceType);
|
|
33384
|
+
} catch (err) {
|
|
33385
|
+
return {
|
|
33386
|
+
ok: false,
|
|
33387
|
+
reason: `no provider available for ${orphan.resourceType}: ${err instanceof Error ? err.message : String(err)}`
|
|
33388
|
+
};
|
|
33389
|
+
}
|
|
33390
|
+
if (!provider.getAttribute) {
|
|
33391
|
+
return this.cacheFallback(
|
|
33392
|
+
orphanLogicalId,
|
|
33393
|
+
attribute,
|
|
33394
|
+
`provider for ${orphan.resourceType} does not implement getAttribute`
|
|
33395
|
+
);
|
|
33396
|
+
}
|
|
33397
|
+
try {
|
|
33398
|
+
const value = await provider.getAttribute(orphan.physicalId, orphan.resourceType, attribute);
|
|
33399
|
+
if (value === void 0) {
|
|
33400
|
+
return this.cacheFallback(
|
|
33401
|
+
orphanLogicalId,
|
|
33402
|
+
attribute,
|
|
33403
|
+
`provider returned undefined for ${orphan.resourceType}.${attribute}`
|
|
33404
|
+
);
|
|
33405
|
+
}
|
|
33406
|
+
this.cache.set(cacheKey, value);
|
|
33407
|
+
return { ok: true, value };
|
|
33408
|
+
} catch (err) {
|
|
33409
|
+
return this.cacheFallback(
|
|
33410
|
+
orphanLogicalId,
|
|
33411
|
+
attribute,
|
|
33412
|
+
err instanceof Error ? err.message : String(err)
|
|
33413
|
+
);
|
|
33414
|
+
}
|
|
33415
|
+
}
|
|
33416
|
+
/**
|
|
33417
|
+
* Try the orphan's `state.attributes[attribute]` as a last-resort value
|
|
33418
|
+
* source under `--force`. Without `--force`, returns the original
|
|
33419
|
+
* failure reason unchanged (caller pushes to `unresolvable`).
|
|
33420
|
+
*/
|
|
33421
|
+
cacheFallback(orphanLogicalId, attribute, reason) {
|
|
33422
|
+
if (!this.options.force) {
|
|
33423
|
+
return { ok: false, reason };
|
|
33424
|
+
}
|
|
33425
|
+
const orphan = this.orphans[orphanLogicalId];
|
|
33426
|
+
const cached = orphan.attributes?.[attribute];
|
|
33427
|
+
if (cached === void 0) {
|
|
33428
|
+
this.logger.warn(
|
|
33429
|
+
`--force: state.attributes also lacks '${orphanLogicalId}.${attribute}'; leaving the original intrinsic in place.`
|
|
33430
|
+
);
|
|
33431
|
+
return {
|
|
33432
|
+
ok: false,
|
|
33433
|
+
reason: `${reason}; state.attributes cache also has no value for '${attribute}'`
|
|
33434
|
+
};
|
|
33435
|
+
}
|
|
33436
|
+
this.logger.warn(
|
|
33437
|
+
`--force: live fetch failed for '${orphanLogicalId}.${attribute}' (${reason}); falling back to cached value from state.attributes.`
|
|
33438
|
+
);
|
|
33439
|
+
const cacheKey = `${orphanLogicalId}\0${attribute}`;
|
|
33440
|
+
this.cache.set(cacheKey, cached);
|
|
33441
|
+
return { ok: true, value: cached, fromCache: true };
|
|
33442
|
+
}
|
|
33443
|
+
};
|
|
33444
|
+
async function rewriteResourceReferences(state, orphanLogicalIds, providerRegistry, options = {}) {
|
|
33445
|
+
const orphanSet = new Set(orphanLogicalIds);
|
|
33446
|
+
const orphans = {};
|
|
33447
|
+
for (const id of orphanLogicalIds) {
|
|
33448
|
+
const r = state.resources[id];
|
|
33449
|
+
if (!r) {
|
|
33450
|
+
throw new Error(`rewriteResourceReferences: orphan '${id}' not found in state.resources`);
|
|
33451
|
+
}
|
|
33452
|
+
orphans[id] = r;
|
|
33453
|
+
}
|
|
33454
|
+
const fetcher = new AttributeFetcher(orphans, providerRegistry, options);
|
|
33455
|
+
const rewrites = [];
|
|
33456
|
+
const unresolvable = [];
|
|
33457
|
+
const newResources = {};
|
|
33458
|
+
for (const [logicalId, resource] of Object.entries(state.resources)) {
|
|
33459
|
+
if (orphanSet.has(logicalId))
|
|
33460
|
+
continue;
|
|
33461
|
+
const rewrittenProperties = await rewriteValue(
|
|
33462
|
+
resource.properties,
|
|
33463
|
+
`properties`,
|
|
33464
|
+
logicalId,
|
|
33465
|
+
orphanSet,
|
|
33466
|
+
fetcher,
|
|
33467
|
+
rewrites,
|
|
33468
|
+
unresolvable
|
|
33469
|
+
);
|
|
33470
|
+
const rewrittenAttributes = resource.attributes ? await rewriteValue(
|
|
33471
|
+
resource.attributes,
|
|
33472
|
+
`attributes`,
|
|
33473
|
+
logicalId,
|
|
33474
|
+
orphanSet,
|
|
33475
|
+
fetcher,
|
|
33476
|
+
rewrites,
|
|
33477
|
+
unresolvable
|
|
33478
|
+
) : void 0;
|
|
33479
|
+
const newDeps = (resource.dependencies ?? []).filter((dep) => {
|
|
33480
|
+
if (orphanSet.has(dep)) {
|
|
33481
|
+
rewrites.push({
|
|
33482
|
+
logicalId,
|
|
33483
|
+
path: "dependencies",
|
|
33484
|
+
kind: "dependency",
|
|
33485
|
+
before: dep,
|
|
33486
|
+
after: null,
|
|
33487
|
+
orphanLogicalId: dep
|
|
33488
|
+
});
|
|
33489
|
+
return false;
|
|
33490
|
+
}
|
|
33491
|
+
return true;
|
|
33492
|
+
});
|
|
33493
|
+
newResources[logicalId] = {
|
|
33494
|
+
...resource,
|
|
33495
|
+
properties: rewrittenProperties,
|
|
33496
|
+
...rewrittenAttributes !== void 0 && {
|
|
33497
|
+
attributes: rewrittenAttributes
|
|
33498
|
+
},
|
|
33499
|
+
dependencies: newDeps
|
|
33500
|
+
};
|
|
33501
|
+
}
|
|
33502
|
+
const newOutputs = {};
|
|
33503
|
+
for (const [name, value] of Object.entries(state.outputs ?? {})) {
|
|
33504
|
+
newOutputs[name] = await rewriteValue(
|
|
33505
|
+
value,
|
|
33506
|
+
`outputs.${name}`,
|
|
33507
|
+
`<output:${name}>`,
|
|
33508
|
+
orphanSet,
|
|
33509
|
+
fetcher,
|
|
33510
|
+
rewrites,
|
|
33511
|
+
unresolvable
|
|
33512
|
+
);
|
|
33513
|
+
}
|
|
33514
|
+
return {
|
|
33515
|
+
state: {
|
|
33516
|
+
...state,
|
|
33517
|
+
resources: newResources,
|
|
33518
|
+
outputs: newOutputs,
|
|
33519
|
+
lastModified: Date.now()
|
|
33520
|
+
},
|
|
33521
|
+
rewrites,
|
|
33522
|
+
unresolvable
|
|
33523
|
+
};
|
|
33524
|
+
}
|
|
33525
|
+
async function rewriteValue(value, pathPrefix, ownerLogicalId, orphanSet, fetcher, rewrites, unresolvable) {
|
|
33526
|
+
if (typeof value !== "object" || value === null)
|
|
33527
|
+
return value;
|
|
33528
|
+
if (Array.isArray(value)) {
|
|
33529
|
+
const out2 = [];
|
|
33530
|
+
for (let i = 0; i < value.length; i++) {
|
|
33531
|
+
out2.push(
|
|
33532
|
+
await rewriteValue(
|
|
33533
|
+
value[i],
|
|
33534
|
+
`${pathPrefix}[${i}]`,
|
|
33535
|
+
ownerLogicalId,
|
|
33536
|
+
orphanSet,
|
|
33537
|
+
fetcher,
|
|
33538
|
+
rewrites,
|
|
33539
|
+
unresolvable
|
|
33540
|
+
)
|
|
33541
|
+
);
|
|
33542
|
+
}
|
|
33543
|
+
return out2;
|
|
33544
|
+
}
|
|
33545
|
+
const obj = value;
|
|
33546
|
+
if ("Ref" in obj && Object.keys(obj).length === 1 && typeof obj["Ref"] === "string") {
|
|
33547
|
+
const target = obj["Ref"];
|
|
33548
|
+
if (orphanSet.has(target)) {
|
|
33549
|
+
const replaced = fetcher.ref(target);
|
|
33550
|
+
rewrites.push({
|
|
33551
|
+
logicalId: ownerLogicalId,
|
|
33552
|
+
path: pathPrefix,
|
|
33553
|
+
kind: "ref",
|
|
33554
|
+
before: { Ref: target },
|
|
33555
|
+
after: replaced,
|
|
33556
|
+
orphanLogicalId: target
|
|
33557
|
+
});
|
|
33558
|
+
return replaced;
|
|
33559
|
+
}
|
|
33560
|
+
return value;
|
|
33561
|
+
}
|
|
33562
|
+
if ("Fn::GetAtt" in obj && Object.keys(obj).length === 1) {
|
|
33563
|
+
const arg = obj["Fn::GetAtt"];
|
|
33564
|
+
let target;
|
|
33565
|
+
let attribute;
|
|
33566
|
+
if (Array.isArray(arg) && arg.length === 2 && typeof arg[0] === "string" && typeof arg[1] === "string") {
|
|
33567
|
+
target = arg[0];
|
|
33568
|
+
attribute = arg[1];
|
|
33569
|
+
} else if (typeof arg === "string") {
|
|
33570
|
+
const dot = arg.indexOf(".");
|
|
33571
|
+
if (dot > 0) {
|
|
33572
|
+
target = arg.slice(0, dot);
|
|
33573
|
+
attribute = arg.slice(dot + 1);
|
|
33574
|
+
}
|
|
33575
|
+
}
|
|
33576
|
+
if (target && attribute && orphanSet.has(target)) {
|
|
33577
|
+
const result = await fetcher.getAtt(target, attribute);
|
|
33578
|
+
if (result.ok) {
|
|
33579
|
+
rewrites.push({
|
|
33580
|
+
logicalId: ownerLogicalId,
|
|
33581
|
+
path: pathPrefix,
|
|
33582
|
+
kind: "getAtt",
|
|
33583
|
+
before: { "Fn::GetAtt": [target, attribute] },
|
|
33584
|
+
after: result.value,
|
|
33585
|
+
orphanLogicalId: target
|
|
33586
|
+
});
|
|
33587
|
+
return result.value;
|
|
33588
|
+
}
|
|
33589
|
+
unresolvable.push({
|
|
33590
|
+
logicalId: ownerLogicalId,
|
|
33591
|
+
path: pathPrefix,
|
|
33592
|
+
orphanLogicalId: target,
|
|
33593
|
+
attribute,
|
|
33594
|
+
reason: result.reason
|
|
33595
|
+
});
|
|
33596
|
+
return value;
|
|
33597
|
+
}
|
|
33598
|
+
return value;
|
|
33599
|
+
}
|
|
33600
|
+
if ("Fn::Sub" in obj && Object.keys(obj).length === 1) {
|
|
33601
|
+
const arg = obj["Fn::Sub"];
|
|
33602
|
+
let template;
|
|
33603
|
+
let varMap;
|
|
33604
|
+
if (typeof arg === "string") {
|
|
33605
|
+
template = arg;
|
|
33606
|
+
} else if (Array.isArray(arg) && arg.length === 2 && typeof arg[0] === "string" && typeof arg[1] === "object" && arg[1] !== null) {
|
|
33607
|
+
template = arg[0];
|
|
33608
|
+
varMap = arg[1];
|
|
33609
|
+
}
|
|
33610
|
+
if (template !== void 0) {
|
|
33611
|
+
const { rewritten, didChange, hasUnresolvable } = await rewriteSubTemplate(
|
|
33612
|
+
template,
|
|
33613
|
+
ownerLogicalId,
|
|
33614
|
+
pathPrefix,
|
|
33615
|
+
orphanSet,
|
|
33616
|
+
fetcher,
|
|
33617
|
+
rewrites,
|
|
33618
|
+
unresolvable,
|
|
33619
|
+
varMap
|
|
33620
|
+
);
|
|
33621
|
+
if (didChange) {
|
|
33622
|
+
const stillHasIntrinsics = /\$\{[^}]+\}/.test(rewritten);
|
|
33623
|
+
if (varMap && stillHasIntrinsics) {
|
|
33624
|
+
return { "Fn::Sub": [rewritten, varMap] };
|
|
33625
|
+
}
|
|
33626
|
+
if (stillHasIntrinsics) {
|
|
33627
|
+
return { "Fn::Sub": rewritten };
|
|
33628
|
+
}
|
|
33629
|
+
return rewritten;
|
|
33630
|
+
}
|
|
33631
|
+
return value;
|
|
33632
|
+
}
|
|
33633
|
+
return value;
|
|
33634
|
+
}
|
|
33635
|
+
const out = {};
|
|
33636
|
+
for (const [k, v] of Object.entries(obj)) {
|
|
33637
|
+
out[k] = await rewriteValue(
|
|
33638
|
+
v,
|
|
33639
|
+
pathPrefix === "" ? k : `${pathPrefix}.${k}`,
|
|
33640
|
+
ownerLogicalId,
|
|
33641
|
+
orphanSet,
|
|
33642
|
+
fetcher,
|
|
33643
|
+
rewrites,
|
|
33644
|
+
unresolvable
|
|
33645
|
+
);
|
|
33646
|
+
}
|
|
33647
|
+
return out;
|
|
33648
|
+
}
|
|
33649
|
+
async function rewriteSubTemplate(template, ownerLogicalId, pathPrefix, orphanSet, fetcher, rewrites, unresolvable, varMap) {
|
|
33650
|
+
const placeholderRe = /\$\{([^}]+)\}/g;
|
|
33651
|
+
const matches = [...template.matchAll(placeholderRe)];
|
|
33652
|
+
if (matches.length === 0) {
|
|
33653
|
+
return { rewritten: template, didChange: false, hasUnresolvable: false };
|
|
33654
|
+
}
|
|
33655
|
+
let didChange = false;
|
|
33656
|
+
let hasUnresolvable = false;
|
|
33657
|
+
let cursor = 0;
|
|
33658
|
+
let out = "";
|
|
33659
|
+
for (const m of matches) {
|
|
33660
|
+
const inner = m[1] ?? "";
|
|
33661
|
+
const start = m.index ?? 0;
|
|
33662
|
+
out += template.slice(cursor, start);
|
|
33663
|
+
cursor = start + m[0].length;
|
|
33664
|
+
if (varMap && inner in varMap) {
|
|
33665
|
+
out += m[0];
|
|
33666
|
+
continue;
|
|
33667
|
+
}
|
|
33668
|
+
const dot = inner.indexOf(".");
|
|
33669
|
+
if (dot < 0) {
|
|
33670
|
+
if (orphanSet.has(inner)) {
|
|
33671
|
+
const replaced = fetcher.ref(inner);
|
|
33672
|
+
rewrites.push({
|
|
33673
|
+
logicalId: ownerLogicalId,
|
|
33674
|
+
path: pathPrefix,
|
|
33675
|
+
kind: "sub",
|
|
33676
|
+
before: m[0],
|
|
33677
|
+
after: replaced,
|
|
33678
|
+
orphanLogicalId: inner
|
|
33679
|
+
});
|
|
33680
|
+
out += replaced;
|
|
33681
|
+
didChange = true;
|
|
33682
|
+
} else {
|
|
33683
|
+
out += m[0];
|
|
33684
|
+
}
|
|
33685
|
+
} else {
|
|
33686
|
+
const target = inner.slice(0, dot);
|
|
33687
|
+
const attribute = inner.slice(dot + 1);
|
|
33688
|
+
if (orphanSet.has(target)) {
|
|
33689
|
+
const result = await fetcher.getAtt(target, attribute);
|
|
33690
|
+
if (result.ok) {
|
|
33691
|
+
const stringified = String(result.value);
|
|
33692
|
+
rewrites.push({
|
|
33693
|
+
logicalId: ownerLogicalId,
|
|
33694
|
+
path: pathPrefix,
|
|
33695
|
+
kind: "sub",
|
|
33696
|
+
before: m[0],
|
|
33697
|
+
after: stringified,
|
|
33698
|
+
orphanLogicalId: target
|
|
33699
|
+
});
|
|
33700
|
+
out += stringified;
|
|
33701
|
+
didChange = true;
|
|
33702
|
+
} else {
|
|
33703
|
+
unresolvable.push({
|
|
33704
|
+
logicalId: ownerLogicalId,
|
|
33705
|
+
path: pathPrefix,
|
|
33706
|
+
orphanLogicalId: target,
|
|
33707
|
+
attribute,
|
|
33708
|
+
reason: result.reason
|
|
33709
|
+
});
|
|
33710
|
+
out += m[0];
|
|
33711
|
+
hasUnresolvable = true;
|
|
33712
|
+
}
|
|
33713
|
+
} else {
|
|
33714
|
+
out += m[0];
|
|
33715
|
+
}
|
|
33716
|
+
}
|
|
33717
|
+
}
|
|
33718
|
+
out += template.slice(cursor);
|
|
33719
|
+
return { rewritten: out, didChange, hasUnresolvable };
|
|
33720
|
+
}
|
|
33721
|
+
|
|
33722
|
+
// src/cli/commands/orphan.ts
|
|
33723
|
+
async function orphanCommand(pathArgs, options) {
|
|
32990
33724
|
const logger = getLogger();
|
|
32991
33725
|
if (options.verbose)
|
|
32992
33726
|
logger.setLevel("debug");
|
|
32993
33727
|
warnIfDeprecatedRegion(options);
|
|
33728
|
+
if (pathArgs.length === 0) {
|
|
33729
|
+
throw new Error(
|
|
33730
|
+
"'cdkd orphan' requires at least one construct path, e.g. 'cdkd orphan MyStack/MyTable'.\n To remove a stack's state record (the previous behavior), use:\n cdkd state orphan MyStack"
|
|
33731
|
+
);
|
|
33732
|
+
}
|
|
33733
|
+
for (const p of pathArgs) {
|
|
33734
|
+
if (!p.includes("/")) {
|
|
33735
|
+
throw new Error(
|
|
33736
|
+
`'cdkd orphan' now expects a construct path like 'MyStack/MyTable'.
|
|
33737
|
+
Got: '${p}'
|
|
33738
|
+
To remove a stack's state record (the previous behavior), use:
|
|
33739
|
+
cdkd state orphan ${p}`
|
|
33740
|
+
);
|
|
33741
|
+
}
|
|
33742
|
+
}
|
|
32994
33743
|
const region = options.region || process.env["AWS_REGION"] || "us-east-1";
|
|
32995
33744
|
const stateBucket = await resolveStateBucketWithDefault(options.stateBucket, region);
|
|
32996
|
-
logger.info("Starting stack orphan...");
|
|
32997
|
-
logger.debug("Options:", options);
|
|
32998
33745
|
if (options.region) {
|
|
32999
33746
|
process.env["AWS_REGION"] = options.region;
|
|
33000
33747
|
process.env["AWS_DEFAULT_REGION"] = options.region;
|
|
@@ -33005,10 +33752,7 @@ async function orphanCommand(stackArgs, options) {
|
|
|
33005
33752
|
});
|
|
33006
33753
|
setAwsClients(awsClients);
|
|
33007
33754
|
try {
|
|
33008
|
-
const stateConfig = {
|
|
33009
|
-
bucket: stateBucket,
|
|
33010
|
-
prefix: options.statePrefix
|
|
33011
|
-
};
|
|
33755
|
+
const stateConfig = { bucket: stateBucket, prefix: options.statePrefix };
|
|
33012
33756
|
const stateBackend = new S3StateBackend(awsClients.s3, stateConfig, {
|
|
33013
33757
|
...options.region && { region: options.region },
|
|
33014
33758
|
...options.profile && { profile: options.profile }
|
|
@@ -33016,150 +33760,245 @@ async function orphanCommand(stackArgs, options) {
|
|
|
33016
33760
|
await stateBackend.verifyBucketExists();
|
|
33017
33761
|
const lockManager = new LockManager(awsClients.s3, stateConfig);
|
|
33018
33762
|
const appCmd = options.app || resolveApp();
|
|
33019
|
-
|
|
33020
|
-
if (appCmd) {
|
|
33021
|
-
try {
|
|
33022
|
-
const synthesizer = new Synthesizer();
|
|
33023
|
-
const context = parseContextOptions(options.context);
|
|
33024
|
-
const result = await synthesizer.synthesize({
|
|
33025
|
-
app: appCmd,
|
|
33026
|
-
output: options.output || "cdk.out",
|
|
33027
|
-
...Object.keys(context).length > 0 && { context }
|
|
33028
|
-
});
|
|
33029
|
-
appStacks = result.stacks.map((s) => ({
|
|
33030
|
-
stackName: s.stackName,
|
|
33031
|
-
displayName: s.displayName,
|
|
33032
|
-
...s.region && { region: s.region }
|
|
33033
|
-
}));
|
|
33034
|
-
} catch {
|
|
33035
|
-
logger.debug("Could not synthesize app, falling back to state-based stack list");
|
|
33036
|
-
}
|
|
33037
|
-
}
|
|
33038
|
-
const allStateRefs = await stateBackend.listStacks();
|
|
33039
|
-
let candidateStacks;
|
|
33040
|
-
if (appStacks.length > 0) {
|
|
33041
|
-
const stateNames = new Set(allStateRefs.map((r) => r.stackName));
|
|
33042
|
-
candidateStacks = appStacks.filter((s) => stateNames.has(s.stackName));
|
|
33043
|
-
} else if (stackArgs.length > 0 || options.stack || options.all) {
|
|
33044
|
-
const seen = /* @__PURE__ */ new Set();
|
|
33045
|
-
candidateStacks = [];
|
|
33046
|
-
for (const ref of allStateRefs) {
|
|
33047
|
-
if (seen.has(ref.stackName))
|
|
33048
|
-
continue;
|
|
33049
|
-
seen.add(ref.stackName);
|
|
33050
|
-
candidateStacks.push({ stackName: ref.stackName });
|
|
33051
|
-
}
|
|
33052
|
-
} else {
|
|
33053
|
-
throw new Error(
|
|
33054
|
-
"Could not determine which stacks belong to this app. Specify stack names explicitly, use --all, or ensure --app / cdk.json is configured."
|
|
33055
|
-
);
|
|
33056
|
-
}
|
|
33057
|
-
const stackPatterns = stackArgs.length > 0 ? stackArgs : options.stack ? [options.stack] : [];
|
|
33058
|
-
let stackNames;
|
|
33059
|
-
if (options.all) {
|
|
33060
|
-
stackNames = candidateStacks.map((s) => s.stackName);
|
|
33061
|
-
} else if (stackPatterns.length > 0) {
|
|
33062
|
-
stackNames = matchStacks(candidateStacks, stackPatterns).map((s) => s.stackName);
|
|
33063
|
-
} else if (candidateStacks.length === 1) {
|
|
33064
|
-
stackNames = candidateStacks.map((s) => s.stackName);
|
|
33065
|
-
} else if (candidateStacks.length === 0) {
|
|
33066
|
-
logger.info("No stacks found in state");
|
|
33067
|
-
return;
|
|
33068
|
-
} else {
|
|
33763
|
+
if (!appCmd) {
|
|
33069
33764
|
throw new Error(
|
|
33070
|
-
|
|
33765
|
+
"'cdkd orphan' requires a CDK app: pass --app or set it in cdk.json. The template is read to resolve construct paths to logical IDs."
|
|
33071
33766
|
);
|
|
33072
33767
|
}
|
|
33073
|
-
|
|
33074
|
-
|
|
33075
|
-
|
|
33076
|
-
|
|
33077
|
-
|
|
33078
|
-
|
|
33079
|
-
|
|
33080
|
-
|
|
33081
|
-
|
|
33082
|
-
|
|
33768
|
+
logger.info("Synthesizing CDK app to read template...");
|
|
33769
|
+
const synthesizer = new Synthesizer();
|
|
33770
|
+
const context = parseContextOptions(options.context);
|
|
33771
|
+
const result = await synthesizer.synthesize({
|
|
33772
|
+
app: appCmd,
|
|
33773
|
+
output: options.output || "cdk.out",
|
|
33774
|
+
...Object.keys(context).length > 0 && { context }
|
|
33775
|
+
});
|
|
33776
|
+
const resolved = resolveConstructPaths(pathArgs, result.stacks);
|
|
33777
|
+
const stackInfo = resolved.stack;
|
|
33778
|
+
const orphanLogicalIds = resolved.logicalIds;
|
|
33779
|
+
const targetRegion = await pickStackRegion(
|
|
33780
|
+
stateBackend,
|
|
33781
|
+
stackInfo.stackName,
|
|
33782
|
+
stackInfo.region,
|
|
33783
|
+
options.stackRegion
|
|
33784
|
+
);
|
|
33785
|
+
logger.info(
|
|
33786
|
+
`Target: ${stackInfo.stackName} (${targetRegion}); orphaning ${orphanLogicalIds.length} resource(s): ${orphanLogicalIds.join(", ")}`
|
|
33787
|
+
);
|
|
33788
|
+
const owner = `${process.env["USER"] || "unknown"}@${process.env["HOSTNAME"] || "host"}:${process.pid}`;
|
|
33789
|
+
if (!options.dryRun) {
|
|
33790
|
+
await lockManager.acquireLock(stackInfo.stackName, targetRegion, owner, "orphan");
|
|
33083
33791
|
}
|
|
33084
|
-
|
|
33085
|
-
|
|
33086
|
-
|
|
33087
|
-
|
|
33088
|
-
|
|
33089
|
-
|
|
33792
|
+
try {
|
|
33793
|
+
const stateData = await stateBackend.getState(stackInfo.stackName, targetRegion);
|
|
33794
|
+
if (!stateData) {
|
|
33795
|
+
throw new Error(
|
|
33796
|
+
`No state found for stack '${stackInfo.stackName}' (${targetRegion}). Nothing to orphan. (Did the stack get deployed?)`
|
|
33797
|
+
);
|
|
33090
33798
|
}
|
|
33091
|
-
const
|
|
33092
|
-
|
|
33093
|
-
|
|
33799
|
+
const { state, etag, migrationPending } = stateData;
|
|
33800
|
+
const missing = orphanLogicalIds.filter((id) => !(id in state.resources));
|
|
33801
|
+
if (missing.length > 0) {
|
|
33802
|
+
const have = Object.keys(state.resources).join(", ");
|
|
33094
33803
|
throw new Error(
|
|
33095
|
-
`
|
|
33804
|
+
`Resource(s) not in state for stack '${stackInfo.stackName}' (${targetRegion}): ${missing.join(", ")}.
|
|
33805
|
+
Available logical IDs: ${have}`
|
|
33096
33806
|
);
|
|
33097
33807
|
}
|
|
33098
|
-
|
|
33099
|
-
|
|
33100
|
-
|
|
33101
|
-
|
|
33102
|
-
|
|
33103
|
-
|
|
33104
|
-
|
|
33105
|
-
|
|
33106
|
-
|
|
33107
|
-
|
|
33808
|
+
const providerRegistry = new ProviderRegistry();
|
|
33809
|
+
registerAllProviders(providerRegistry);
|
|
33810
|
+
const rewriteResult = await rewriteResourceReferences(
|
|
33811
|
+
state,
|
|
33812
|
+
orphanLogicalIds,
|
|
33813
|
+
providerRegistry,
|
|
33814
|
+
{ force: options.force }
|
|
33815
|
+
);
|
|
33816
|
+
printRewriteSummary(rewriteResult.rewrites, orphanLogicalIds);
|
|
33817
|
+
if (rewriteResult.unresolvable.length > 0 && !options.force) {
|
|
33818
|
+
printUnresolvable(rewriteResult.unresolvable);
|
|
33819
|
+
throw new Error(
|
|
33820
|
+
`Orphan aborted: ${rewriteResult.unresolvable.length} reference(s) could not be resolved.
|
|
33821
|
+
Re-run with --force to fall back to cached attribute values from state, or fix the underlying provider/AWS issue and retry.`
|
|
33822
|
+
);
|
|
33108
33823
|
}
|
|
33109
|
-
if (
|
|
33110
|
-
|
|
33111
|
-
|
|
33112
|
-
|
|
33113
|
-
WARNING: This removes cdkd's state record for [${targetList}] only. AWS resources will NOT be deleted.
|
|
33114
|
-
Use 'cdkd destroy ${stackName}' if you want to delete the actual resources.
|
|
33115
|
-
|
|
33116
|
-
`
|
|
33824
|
+
if (rewriteResult.unresolvable.length > 0) {
|
|
33825
|
+
printUnresolvable(rewriteResult.unresolvable);
|
|
33826
|
+
logger.warn(
|
|
33827
|
+
`--force: continuing despite ${rewriteResult.unresolvable.length} unresolved reference(s); the original intrinsic was left in place where the cache also lacked the value.`
|
|
33117
33828
|
);
|
|
33118
|
-
|
|
33119
|
-
|
|
33120
|
-
|
|
33121
|
-
|
|
33122
|
-
|
|
33123
|
-
|
|
33829
|
+
}
|
|
33830
|
+
if (options.dryRun) {
|
|
33831
|
+
logger.info("--dry-run: state will NOT be written. Re-run without --dry-run to apply.");
|
|
33832
|
+
return;
|
|
33833
|
+
}
|
|
33834
|
+
if (!options.yes && !options.force) {
|
|
33835
|
+
const ok = await confirmPrompt(
|
|
33836
|
+
`Orphan ${orphanLogicalIds.length} resource(s) from cdkd state for ${stackInfo.stackName} (${targetRegion})? AWS resources will NOT be deleted.`
|
|
33124
33837
|
);
|
|
33125
|
-
|
|
33126
|
-
|
|
33127
|
-
|
|
33128
|
-
logger.info(`Cancelled orphan of stack: ${stackName}`);
|
|
33129
|
-
continue;
|
|
33838
|
+
if (!ok) {
|
|
33839
|
+
logger.info("Orphan cancelled.");
|
|
33840
|
+
return;
|
|
33130
33841
|
}
|
|
33131
33842
|
}
|
|
33132
|
-
|
|
33133
|
-
|
|
33134
|
-
|
|
33135
|
-
|
|
33136
|
-
|
|
33137
|
-
|
|
33138
|
-
|
|
33139
|
-
|
|
33140
|
-
|
|
33843
|
+
await stateBackend.saveState(stackInfo.stackName, targetRegion, rewriteResult.state, {
|
|
33844
|
+
expectedEtag: etag,
|
|
33845
|
+
...migrationPending && { migrateLegacy: true }
|
|
33846
|
+
});
|
|
33847
|
+
logger.info(
|
|
33848
|
+
`Orphaned ${orphanLogicalIds.length} resource(s) from state: ${stackInfo.stackName} (${targetRegion}). AWS resources are still in AWS; cdkd will no longer manage them.`
|
|
33849
|
+
);
|
|
33850
|
+
} finally {
|
|
33851
|
+
if (!options.dryRun) {
|
|
33852
|
+
await lockManager.releaseLock(stackInfo.stackName, targetRegion).catch((err) => {
|
|
33853
|
+
logger.warn(
|
|
33854
|
+
`Failed to release lock: ${err instanceof Error ? err.message : String(err)}`
|
|
33855
|
+
);
|
|
33856
|
+
});
|
|
33141
33857
|
}
|
|
33142
33858
|
}
|
|
33143
33859
|
} finally {
|
|
33144
33860
|
awsClients.destroy();
|
|
33145
33861
|
}
|
|
33146
33862
|
}
|
|
33863
|
+
function resolveConstructPaths(paths, stacks) {
|
|
33864
|
+
const byStackName = /* @__PURE__ */ new Map();
|
|
33865
|
+
const byDisplayName = /* @__PURE__ */ new Map();
|
|
33866
|
+
for (const s of stacks) {
|
|
33867
|
+
byStackName.set(s.stackName, s);
|
|
33868
|
+
byDisplayName.set(s.displayName, s);
|
|
33869
|
+
}
|
|
33870
|
+
let stack;
|
|
33871
|
+
const logicalIds = [];
|
|
33872
|
+
for (const p of paths) {
|
|
33873
|
+
const slash = p.indexOf("/");
|
|
33874
|
+
if (slash <= 0 || slash === p.length - 1) {
|
|
33875
|
+
throw new Error(`Invalid construct path '${p}'. Expected '<StackName>/<Path/To/Resource>'.`);
|
|
33876
|
+
}
|
|
33877
|
+
const head = p.slice(0, slash);
|
|
33878
|
+
const candidate = byDisplayName.get(head) ?? byStackName.get(head);
|
|
33879
|
+
if (!candidate) {
|
|
33880
|
+
const available = stacks.map((s) => s.displayName ?? s.stackName).join(", ");
|
|
33881
|
+
throw new Error(
|
|
33882
|
+
`Construct path '${p}': stack '${head}' not found in synthesized app. Available: ${available}`
|
|
33883
|
+
);
|
|
33884
|
+
}
|
|
33885
|
+
if (stack === void 0) {
|
|
33886
|
+
stack = candidate;
|
|
33887
|
+
} else if (stack.stackName !== candidate.stackName) {
|
|
33888
|
+
throw new Error(
|
|
33889
|
+
`All construct paths must reference the same stack. Got '${stack.stackName}' and '${candidate.stackName}'. Run 'cdkd orphan' once per stack.`
|
|
33890
|
+
);
|
|
33891
|
+
}
|
|
33892
|
+
const cdkPath = p;
|
|
33893
|
+
const index = buildCdkPathIndex(candidate.template);
|
|
33894
|
+
const logicalId = index.get(cdkPath);
|
|
33895
|
+
if (!logicalId) {
|
|
33896
|
+
const available = [...index.keys()].sort().join("\n ");
|
|
33897
|
+
throw new Error(
|
|
33898
|
+
`Construct path '${cdkPath}' not found in template for stack '${candidate.stackName}'.
|
|
33899
|
+
Available paths:
|
|
33900
|
+
${available}`
|
|
33901
|
+
);
|
|
33902
|
+
}
|
|
33903
|
+
if (!logicalIds.includes(logicalId)) {
|
|
33904
|
+
logicalIds.push(logicalId);
|
|
33905
|
+
}
|
|
33906
|
+
}
|
|
33907
|
+
if (!stack) {
|
|
33908
|
+
throw new Error("No construct paths supplied.");
|
|
33909
|
+
}
|
|
33910
|
+
return { stack, logicalIds };
|
|
33911
|
+
}
|
|
33912
|
+
async function pickStackRegion(stateBackend, stackName, synthRegion, flag) {
|
|
33913
|
+
const refs = (await stateBackend.listStacks()).filter((r) => r.stackName === stackName);
|
|
33914
|
+
if (refs.length === 0) {
|
|
33915
|
+
if (flag)
|
|
33916
|
+
return flag;
|
|
33917
|
+
if (synthRegion)
|
|
33918
|
+
return synthRegion;
|
|
33919
|
+
throw new Error(
|
|
33920
|
+
`No state found for stack '${stackName}'. Run 'cdkd state list' to see available stacks.`
|
|
33921
|
+
);
|
|
33922
|
+
}
|
|
33923
|
+
if (flag) {
|
|
33924
|
+
const found = refs.find((r) => r.region === flag);
|
|
33925
|
+
if (!found) {
|
|
33926
|
+
const seen = refs.map((r) => r.region ?? "(legacy)").join(", ");
|
|
33927
|
+
throw new Error(
|
|
33928
|
+
`No state found for stack '${stackName}' in region '${flag}'. Available regions: ${seen}.`
|
|
33929
|
+
);
|
|
33930
|
+
}
|
|
33931
|
+
return flag;
|
|
33932
|
+
}
|
|
33933
|
+
if (synthRegion) {
|
|
33934
|
+
const found = refs.find((r) => r.region === synthRegion);
|
|
33935
|
+
if (found)
|
|
33936
|
+
return synthRegion;
|
|
33937
|
+
}
|
|
33938
|
+
if (refs.length === 1) {
|
|
33939
|
+
return refs[0].region ?? synthRegion ?? "";
|
|
33940
|
+
}
|
|
33941
|
+
const regions = refs.map((r) => r.region ?? "(legacy)").join(", ");
|
|
33942
|
+
throw new Error(
|
|
33943
|
+
`Stack '${stackName}' has state in multiple regions: ${regions}. Re-run with --stack-region <region> to disambiguate.`
|
|
33944
|
+
);
|
|
33945
|
+
}
|
|
33946
|
+
function printRewriteSummary(rewrites, orphanLogicalIds) {
|
|
33947
|
+
const logger = getLogger();
|
|
33948
|
+
logger.info("");
|
|
33949
|
+
logger.info(`Orphaning ${orphanLogicalIds.length} resource(s): ${orphanLogicalIds.join(", ")}`);
|
|
33950
|
+
if (rewrites.length === 0) {
|
|
33951
|
+
logger.info(" No sibling references \u2014 every reference was already to a non-orphan resource.");
|
|
33952
|
+
return;
|
|
33953
|
+
}
|
|
33954
|
+
logger.info(`Applied ${rewrites.length} rewrite(s):`);
|
|
33955
|
+
for (const r of rewrites) {
|
|
33956
|
+
const before = stringifyForAudit(r.before);
|
|
33957
|
+
const after = r.kind === "dependency" ? "(dropped)" : stringifyForAudit(r.after);
|
|
33958
|
+
logger.info(` [${r.kind}] ${r.logicalId}.${r.path}: ${before} \u2192 ${after}`);
|
|
33959
|
+
}
|
|
33960
|
+
}
|
|
33961
|
+
function printUnresolvable(unresolvable) {
|
|
33962
|
+
const logger = getLogger();
|
|
33963
|
+
logger.error(`${unresolvable.length} reference(s) could not be resolved:`);
|
|
33964
|
+
for (const u of unresolvable) {
|
|
33965
|
+
logger.error(` ${u.logicalId}.${u.path}: ${u.orphanLogicalId}.${u.attribute} \u2014 ${u.reason}`);
|
|
33966
|
+
}
|
|
33967
|
+
}
|
|
33968
|
+
function stringifyForAudit(value) {
|
|
33969
|
+
if (typeof value === "string")
|
|
33970
|
+
return JSON.stringify(value);
|
|
33971
|
+
return JSON.stringify(value);
|
|
33972
|
+
}
|
|
33973
|
+
async function confirmPrompt(prompt) {
|
|
33974
|
+
const rl = readline2.createInterface({ input: process.stdin, output: process.stdout });
|
|
33975
|
+
try {
|
|
33976
|
+
const ans = await rl.question(`${prompt} [y/N] `);
|
|
33977
|
+
return /^y(es)?$/i.test(ans.trim());
|
|
33978
|
+
} finally {
|
|
33979
|
+
rl.close();
|
|
33980
|
+
}
|
|
33981
|
+
}
|
|
33147
33982
|
function createOrphanCommand() {
|
|
33148
33983
|
const cmd = new Command7("orphan").description(
|
|
33149
|
-
"Remove
|
|
33984
|
+
"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>'."
|
|
33150
33985
|
).argument(
|
|
33151
|
-
"
|
|
33152
|
-
"
|
|
33153
|
-
).option(
|
|
33986
|
+
"<paths...>",
|
|
33987
|
+
"Construct paths to orphan, e.g. 'MyStack/MyTable'. Multiple paths must reference the same stack."
|
|
33988
|
+
).option(
|
|
33154
33989
|
"--stack-region <region>",
|
|
33155
33990
|
"Region of the stack record to operate on. Required when the same stack name has state in multiple regions."
|
|
33991
|
+
).option(
|
|
33992
|
+
"--dry-run",
|
|
33993
|
+
"Compute and print the rewrite audit table without acquiring a lock or saving state.",
|
|
33994
|
+
false
|
|
33156
33995
|
).action(withErrorHandling(orphanCommand));
|
|
33157
33996
|
[
|
|
33158
33997
|
...commonOptions,
|
|
33159
33998
|
...appOptions,
|
|
33160
33999
|
...stateOptions,
|
|
33161
|
-
...stackOptions,
|
|
33162
34000
|
...destroyOptions,
|
|
34001
|
+
// adds -f / --force (escape hatch for unresolvable references + skip confirm)
|
|
33163
34002
|
...contextOptions
|
|
33164
34003
|
].forEach((opt) => cmd.addOption(opt));
|
|
33165
34004
|
cmd.addOption(deprecatedRegionOption);
|
|
@@ -33351,7 +34190,7 @@ async function stateMigrateCommand(options) {
|
|
|
33351
34190
|
}
|
|
33352
34191
|
if (!options.yes) {
|
|
33353
34192
|
const action = options.removeLegacy ? "and DELETE the source bucket" : "(source bucket will be kept)";
|
|
33354
|
-
const ok = await
|
|
34193
|
+
const ok = await confirmPrompt2(
|
|
33355
34194
|
`Copy ${sourceObjects.length} object(s) from ${legacyBucket} -> ${newBucket} ${action}?`
|
|
33356
34195
|
);
|
|
33357
34196
|
if (!ok) {
|
|
@@ -33556,7 +34395,7 @@ async function emptyBucketAllVersions(s3, bucket) {
|
|
|
33556
34395
|
versionIdMarker = resp.NextVersionIdMarker;
|
|
33557
34396
|
} while (keyMarker || versionIdMarker);
|
|
33558
34397
|
}
|
|
33559
|
-
async function
|
|
34398
|
+
async function confirmPrompt2(prompt) {
|
|
33560
34399
|
const rl = readline3.createInterface({ input: process.stdin, output: process.stdout });
|
|
33561
34400
|
try {
|
|
33562
34401
|
const ans = await rl.question(`${prompt} [y/N] `);
|
|
@@ -34013,8 +34852,8 @@ async function stateDestroyCommand(stackArgs, options) {
|
|
|
34013
34852
|
process.env["CDKD_NO_LIVE"] = "1";
|
|
34014
34853
|
}
|
|
34015
34854
|
validateResourceTimeouts({
|
|
34016
|
-
resourceWarnAfter: options.resourceWarnAfter,
|
|
34017
|
-
resourceTimeout: options.resourceTimeout
|
|
34855
|
+
...options.resourceWarnAfter && { resourceWarnAfter: options.resourceWarnAfter },
|
|
34856
|
+
...options.resourceTimeout && { resourceTimeout: options.resourceTimeout }
|
|
34018
34857
|
});
|
|
34019
34858
|
if (!options.all && stackArgs.length === 0) {
|
|
34020
34859
|
throw new Error(
|
|
@@ -34116,8 +34955,18 @@ Preparing to destroy stack: ${stackName}${ref.region ? ` (${ref.region})` : ""}`
|
|
|
34116
34955
|
// skipped when `options.yes` is set OR `--all` was set (the user
|
|
34117
34956
|
// already accepted the batch prompt).
|
|
34118
34957
|
skipConfirmation: options.yes || options.all === true,
|
|
34119
|
-
|
|
34120
|
-
|
|
34958
|
+
...options.resourceWarnAfter?.globalMs !== void 0 && {
|
|
34959
|
+
resourceWarnAfterMs: options.resourceWarnAfter.globalMs
|
|
34960
|
+
},
|
|
34961
|
+
...options.resourceTimeout?.globalMs !== void 0 && {
|
|
34962
|
+
resourceTimeoutMs: options.resourceTimeout.globalMs
|
|
34963
|
+
},
|
|
34964
|
+
...options.resourceWarnAfter?.perTypeMs && {
|
|
34965
|
+
resourceWarnAfterByType: options.resourceWarnAfter.perTypeMs
|
|
34966
|
+
},
|
|
34967
|
+
...options.resourceTimeout?.perTypeMs && {
|
|
34968
|
+
resourceTimeoutByType: options.resourceTimeout.perTypeMs
|
|
34969
|
+
}
|
|
34121
34970
|
});
|
|
34122
34971
|
totalErrors += result.errorCount;
|
|
34123
34972
|
}
|
|
@@ -34285,7 +35134,7 @@ function createStateCommand() {
|
|
|
34285
35134
|
}
|
|
34286
35135
|
|
|
34287
35136
|
// src/cli/commands/import.ts
|
|
34288
|
-
import { readFileSync as readFileSync5 } from "node:fs";
|
|
35137
|
+
import { readFileSync as readFileSync5, writeFileSync as writeFileSync4 } from "node:fs";
|
|
34289
35138
|
import * as readline5 from "node:readline/promises";
|
|
34290
35139
|
import { Command as Command12 } from "commander";
|
|
34291
35140
|
init_aws_clients();
|
|
@@ -34404,6 +35253,9 @@ async function importCommand(stackArg, options) {
|
|
|
34404
35253
|
rows.push(outcome);
|
|
34405
35254
|
}
|
|
34406
35255
|
printSummary(rows);
|
|
35256
|
+
if (options.recordResourceMapping) {
|
|
35257
|
+
writeRecordedMapping(options.recordResourceMapping, rows);
|
|
35258
|
+
}
|
|
34407
35259
|
if (options.dryRun) {
|
|
34408
35260
|
logger.info("--dry-run: state will NOT be written. Re-run without --dry-run to apply.");
|
|
34409
35261
|
return;
|
|
@@ -34414,7 +35266,7 @@ async function importCommand(stackArg, options) {
|
|
|
34414
35266
|
return;
|
|
34415
35267
|
}
|
|
34416
35268
|
if (!options.yes) {
|
|
34417
|
-
const ok = await
|
|
35269
|
+
const ok = await confirmPrompt3(
|
|
34418
35270
|
`Write state for ${stackInfo.stackName} (${targetRegion}) with ${importedRows.length} resource(s)?`
|
|
34419
35271
|
);
|
|
34420
35272
|
if (!ok) {
|
|
@@ -34557,12 +35409,24 @@ function parseMappingJson(raw, source) {
|
|
|
34557
35409
|
}
|
|
34558
35410
|
return out;
|
|
34559
35411
|
}
|
|
34560
|
-
function
|
|
34561
|
-
const
|
|
34562
|
-
|
|
34563
|
-
|
|
34564
|
-
|
|
34565
|
-
|
|
35412
|
+
function writeRecordedMapping(filePath, rows) {
|
|
35413
|
+
const logger = getLogger();
|
|
35414
|
+
const map = {};
|
|
35415
|
+
for (const row of rows) {
|
|
35416
|
+
if (row.outcome === "imported" && row.physicalId) {
|
|
35417
|
+
map[row.logicalId] = row.physicalId;
|
|
35418
|
+
}
|
|
35419
|
+
}
|
|
35420
|
+
const body = JSON.stringify(map, null, 2) + "\n";
|
|
35421
|
+
try {
|
|
35422
|
+
writeFileSync4(filePath, body, "utf-8");
|
|
35423
|
+
logger.info(`Wrote resolved mapping to ${filePath} (${Object.keys(map).length} entry(ies))`);
|
|
35424
|
+
} catch (err) {
|
|
35425
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
35426
|
+
logger.error(
|
|
35427
|
+
`Failed to write --record-resource-mapping file '${filePath}': ${msg}. Continuing \u2014 the import already resolved every physical id in memory.`
|
|
35428
|
+
);
|
|
35429
|
+
}
|
|
34566
35430
|
}
|
|
34567
35431
|
function collectImportableResources(template) {
|
|
34568
35432
|
const out = [];
|
|
@@ -34635,7 +35499,7 @@ function formatOutcome(outcome) {
|
|
|
34635
35499
|
return "\u2717";
|
|
34636
35500
|
}
|
|
34637
35501
|
}
|
|
34638
|
-
async function
|
|
35502
|
+
async function confirmPrompt3(prompt) {
|
|
34639
35503
|
const rl = readline5.createInterface({ input: process.stdin, output: process.stdout });
|
|
34640
35504
|
try {
|
|
34641
35505
|
const ans = await rl.question(`${prompt} [y/N] `);
|
|
@@ -34661,6 +35525,9 @@ function createImportCommand() {
|
|
|
34661
35525
|
).option(
|
|
34662
35526
|
"--resource-mapping-inline <json>",
|
|
34663
35527
|
"Inline JSON object of {logicalId: physicalId} overrides (CDK CLI `cdk import --resource-mapping-inline` compatible). Same shape as --resource-mapping but supplied as a string \u2014 useful for non-TTY CI scripts that do not want a separate file. Implies selective mode unless --auto is set. Mutually exclusive with --resource-mapping."
|
|
35528
|
+
).option(
|
|
35529
|
+
"--record-resource-mapping <file>",
|
|
35530
|
+
'After cdkd resolves every logical ID (via --resource / --resource-mapping / tag-based auto-lookup), write the resulting {logicalId: physicalId} map to <file> as JSON. Useful in auto / hybrid mode for capturing the tag-resolved mapping and feeding it back as --resource-mapping in non-interactive CI re-runs. Written before the confirmation prompt (so the user can review the file before saying "yes") and even when the user says "no". Mirrors `cdk import --record-resource-mapping`.'
|
|
34664
35531
|
).option(
|
|
34665
35532
|
"--auto",
|
|
34666
35533
|
"Hybrid mode: when explicit overrides are supplied, ALSO tag-import every other resource in the template. Without this flag, --resource / --resource-mapping behave as a whitelist (CDK CLI parity).",
|
|
@@ -34706,7 +35573,7 @@ function reorderArgs(argv) {
|
|
|
34706
35573
|
}
|
|
34707
35574
|
async function main() {
|
|
34708
35575
|
const program = new Command13();
|
|
34709
|
-
program.name("cdkd").description("CDK Direct - Deploy AWS CDK apps directly via SDK/Cloud Control API").version("0.
|
|
35576
|
+
program.name("cdkd").description("CDK Direct - Deploy AWS CDK apps directly via SDK/Cloud Control API").version("0.27.0");
|
|
34710
35577
|
program.addCommand(createBootstrapCommand());
|
|
34711
35578
|
program.addCommand(createSynthCommand());
|
|
34712
35579
|
program.addCommand(createListCommand());
|