@go-to-k/cdkd 0.283.4 → 0.283.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{asg-provider-CZQewQcH.js → asg-provider-B9eSuRPc.js} +2 -2
- package/dist/{asg-provider-CZQewQcH.js.map → asg-provider-B9eSuRPc.js.map} +1 -1
- package/dist/cli.js +429 -41
- package/dist/cli.js.map +1 -1
- package/dist/{deploy-engine-C605sBVJ.js → deploy-engine-BcwNPlSk.js} +39 -7
- package/dist/{deploy-engine-C605sBVJ.js.map → deploy-engine-BcwNPlSk.js.map} +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { $ as configStringRefusal, $t as resolveSkipPrefix, A as green, At as rewriteTemplateAssetReferences, B as slowCcOperationTimeoutMs, Bt as formatDockerLoginError, C as MULTI_REGION_RECREATE_BLOCKED_TYPES, Cn as CdkdError, Ct as shouldRetainResource, D as bold, Dn as LocalInvokeBuildError$1, Dt as buildAssetRedirectMap, E as formatResourceLine, En as DeployCancelledError, Et as WorkGraph, F as clearOnUpdateRemoval, Fn as ResourceTimeoutError, Ft as parseBootstrapMarker, G as getAccountInfo, Gt as getDockerImageBySourceHash, H as isTerminationProtectionPropagationError, Ht as runDockerForeground, I as ProviderRegistry, In as ResourceUpdateNotSupportedError, It as validateAssetBucketName, J as normalizeAwsTagsToCfn, Jt as getDefaultStateBucketName, K as refStateLookupFromResource, Kt as Synthesizer, L as findActionableSilentDrops, Ln as StackHasActiveImportsError, Lt as validateContainerRepoName, M as yellow, Mn as NestedStackChildDirectDestroyError, Mt as BOOTSTRAP_MARKER_PREFIX, N as IAMRoleProvider, Nn as PartialFailureError, Nt as ensureAssetStorage, O as cyan, On as LocalMigrateError, Ot as createAssetRedirectResolver, P as collectInlinePolicyNamesManagedBySiblings, Pn as ProvisioningError, Pt as getBootstrapMarkerKey, Q as configBooleanRefusal, Qt as resolveCaptureObservedState, R as findSilentDropProperties, Rn as StackTerminationProtectionError, Rt as buildDenyExternalAccessPolicy, S as extractDeploymentEventError, St as rebuildClientForBucketRegion, T as renderStatefulReason, Tt as stringifyValue, U as IntrinsicFunctionResolver, Un as normalizeAwsError, Ut as runDockerStreaming, V as disableInstanceApiTermination, Vt as getDockerCmd, W as cfnRefValueFromPhysicalId, Wn as withErrorHandling, Wt as AssetManifestLoader, X as assertRegionMatch, Xt as resolveApp, Y as resolveExplicitPhysicalId, Yt as getLegacyStateBucketName, Z as coerceCfnBoolean, Zt as resolveAutoAssetStorage, _ as createPreDeleteFinalSnapshot, _n as resolveBucketRegion, _t as markNonRetryable, a as DeploymentEventsStore, an as CFN_TEMPLATE_BODY_LIMIT, at as s3BucketArn, b as unsupportedFinalSnapshotError, bn as resetAwsClients, bt as LockManager, c as replayFailedOperations, cn as findLargeInlineResources, ct as s3BucketRegionalDomainName, d as IMPLICIT_DELETE_DEPENDENCIES, dn as PARTITION_TABLE, dt as DiffCalculator, en as resolveStateBucketWithDefault, et as readConfigString, f as computeImplicitDeleteEdges, fn as canonicalizeRegion, ft as describeTypeWithThrottleRetry, g as ccRoutedFinalSnapshotError, gt as isThrottlingError, h as buildFinalSnapshotIdentifier, hn as processStackMessages, ht as isRetryableTransientError, i as DeploymentEventsReader, in as warnDeprecatedNoPrefixCliFlag, it as requireConfigString, j as red, jn as MissingCdkCliError, jt as AssetModeResolver, k as gray, kn as LocalStartServiceError, kt as loadPublishableAssetManifest, l as replayRollback, ln as uploadCfnTemplate, lt as s3BucketWebsiteUrl, m as PRE_DELETE_SNAPSHOT_TYPES, mn as AssemblyReader, mt as isMarkedNonRetryable, n as DEFAULT_RESOURCE_WARN_AFTER_MS, nn as resolveUseCdkBootstrapAssets, nt as requireConfigArray, o as planFailedOps, on as CFN_TEMPLATE_URL_LIMIT, ot as s3BucketDomainName, p as ATOMIC_FINAL_SNAPSHOT_TYPES, pn as derivePartitionAndUrlSuffix, pt as withRetry, q as WAFv2WebACLProvider, qt as synthesisStatusMessage, r as DeployEngine, rn as stateBucketExistenceConfirmed, rt as requireConfigObject, s as planRollback, sn as MIGRATE_TMP_PREFIX, st as s3BucketDualStackDomainName, t as DEFAULT_RESOURCE_TIMEOUT_MS, tn as resolveStateBucketWithDefaultAndSource, tt as replayWarn, u as withResourceDeadline, un as expectedOwnerParam, ut as applyRoleArnIfSet, v as isFinalSnapshotError, vn as AwsClients, vt as DagBuilder, w as isStatefulRecreateTargetSync, wt as AssetPublisher, x as makeCanonicalizePropertiesFn, xn as setAwsClients, xt as S3StateBackend, y as refusesFinalSnapshot, yn as getAwsClients, yt as TemplateParser, z as CloudControlProvider, zt as buildDockerImage } from "./deploy-engine-
|
|
2
|
+
import { $ as configStringRefusal, $t as resolveSkipPrefix, A as green, At as rewriteTemplateAssetReferences, B as slowCcOperationTimeoutMs, Bt as formatDockerLoginError, C as MULTI_REGION_RECREATE_BLOCKED_TYPES, Cn as CdkdError, Ct as shouldRetainResource, D as bold, Dn as LocalInvokeBuildError$1, Dt as buildAssetRedirectMap, E as formatResourceLine, En as DeployCancelledError, Et as WorkGraph, F as clearOnUpdateRemoval, Fn as ResourceTimeoutError, Ft as parseBootstrapMarker, G as getAccountInfo, Gt as getDockerImageBySourceHash, H as isTerminationProtectionPropagationError, Ht as runDockerForeground, I as ProviderRegistry, In as ResourceUpdateNotSupportedError, It as validateAssetBucketName, J as normalizeAwsTagsToCfn, Jt as getDefaultStateBucketName, K as refStateLookupFromResource, Kt as Synthesizer, L as findActionableSilentDrops, Ln as StackHasActiveImportsError, Lt as validateContainerRepoName, M as yellow, Mn as NestedStackChildDirectDestroyError, Mt as BOOTSTRAP_MARKER_PREFIX, N as IAMRoleProvider, Nn as PartialFailureError, Nt as ensureAssetStorage, O as cyan, On as LocalMigrateError, Ot as createAssetRedirectResolver, P as collectInlinePolicyNamesManagedBySiblings, Pn as ProvisioningError, Pt as getBootstrapMarkerKey, Q as configBooleanRefusal, Qt as resolveCaptureObservedState, R as findSilentDropProperties, Rn as StackTerminationProtectionError, Rt as buildDenyExternalAccessPolicy, S as extractDeploymentEventError, St as rebuildClientForBucketRegion, T as renderStatefulReason, Tt as stringifyValue, U as IntrinsicFunctionResolver, Un as normalizeAwsError, Ut as runDockerStreaming, V as disableInstanceApiTermination, Vt as getDockerCmd, W as cfnRefValueFromPhysicalId, Wn as withErrorHandling, Wt as AssetManifestLoader, X as assertRegionMatch, Xt as resolveApp, Y as resolveExplicitPhysicalId, Yt as getLegacyStateBucketName, Z as coerceCfnBoolean, Zt as resolveAutoAssetStorage, _ as createPreDeleteFinalSnapshot, _n as resolveBucketRegion, _t as markNonRetryable, a as DeploymentEventsStore, an as CFN_TEMPLATE_BODY_LIMIT, at as s3BucketArn, b as unsupportedFinalSnapshotError, bn as resetAwsClients, bt as LockManager, c as replayFailedOperations, cn as findLargeInlineResources, ct as s3BucketRegionalDomainName, d as IMPLICIT_DELETE_DEPENDENCIES, dn as PARTITION_TABLE, dt as DiffCalculator, en as resolveStateBucketWithDefault, et as readConfigString, f as computeImplicitDeleteEdges, fn as canonicalizeRegion, ft as describeTypeWithThrottleRetry, g as ccRoutedFinalSnapshotError, gt as isThrottlingError, h as buildFinalSnapshotIdentifier, hn as processStackMessages, ht as isRetryableTransientError, i as DeploymentEventsReader, in as warnDeprecatedNoPrefixCliFlag, it as requireConfigString, j as red, jn as MissingCdkCliError, jt as AssetModeResolver, k as gray, kn as LocalStartServiceError, kt as loadPublishableAssetManifest, l as replayRollback, ln as uploadCfnTemplate, lt as s3BucketWebsiteUrl, m as PRE_DELETE_SNAPSHOT_TYPES, mn as AssemblyReader, mt as isMarkedNonRetryable, n as DEFAULT_RESOURCE_WARN_AFTER_MS, nn as resolveUseCdkBootstrapAssets, nt as requireConfigArray, o as planFailedOps, on as CFN_TEMPLATE_URL_LIMIT, ot as s3BucketDomainName, p as ATOMIC_FINAL_SNAPSHOT_TYPES, pn as derivePartitionAndUrlSuffix, pt as withRetry, q as WAFv2WebACLProvider, qt as synthesisStatusMessage, r as DeployEngine, rn as stateBucketExistenceConfirmed, rt as requireConfigObject, s as planRollback, sn as MIGRATE_TMP_PREFIX, st as s3BucketDualStackDomainName, t as DEFAULT_RESOURCE_TIMEOUT_MS, tn as resolveStateBucketWithDefaultAndSource, tt as replayWarn, u as withResourceDeadline, un as expectedOwnerParam, ut as applyRoleArnIfSet, v as isFinalSnapshotError, vn as AwsClients, vt as DagBuilder, w as isStatefulRecreateTargetSync, wt as AssetPublisher, x as makeCanonicalizePropertiesFn, xn as setAwsClients, xt as S3StateBackend, y as refusesFinalSnapshot, yn as getAwsClients, yt as TemplateParser, z as CloudControlProvider, zt as buildDockerImage } from "./deploy-engine-BcwNPlSk.js";
|
|
3
3
|
import { a as getLiveRenderer, c as PATTERN_B_RESOURCE_TYPES, d as generateResourceNameWithFallback, h as withStackName, i as runStackBuffered, m as withSkipPrefix, n as getLogger, o as PATTERN_B_NAME_OPTIONS, s as PATTERN_B_NAME_PROPERTIES, u as generateResourceName } from "./logger-zRrlbaQt.js";
|
|
4
|
-
import { t as ASGProvider } from "./asg-provider-
|
|
4
|
+
import { t as ASGProvider } from "./asg-provider-B9eSuRPc.js";
|
|
5
5
|
import { AsyncLocalStorage } from "node:async_hooks";
|
|
6
6
|
import { createHash, randomBytes, randomUUID } from "node:crypto";
|
|
7
7
|
import { CopyObjectCommand, CreateBucketCommand, DeleteBucketAnalyticsConfigurationCommand, DeleteBucketCommand, DeleteBucketCorsCommand, DeleteBucketEncryptionCommand, DeleteBucketIntelligentTieringConfigurationCommand, DeleteBucketInventoryConfigurationCommand, DeleteBucketLifecycleCommand, DeleteBucketMetricsConfigurationCommand, DeleteBucketOwnershipControlsCommand, DeleteBucketPolicyCommand, DeleteBucketReplicationCommand, DeleteBucketTaggingCommand, DeleteBucketWebsiteCommand, DeleteObjectsCommand, GetBucketAccelerateConfigurationCommand, GetBucketCorsCommand, GetBucketEncryptionCommand, GetBucketLifecycleConfigurationCommand, GetBucketLocationCommand, GetBucketLoggingCommand, GetBucketNotificationConfigurationCommand, GetBucketOwnershipControlsCommand, GetBucketPolicyCommand, GetBucketReplicationCommand, GetBucketTaggingCommand, GetBucketVersioningCommand, GetBucketWebsiteCommand, GetObjectCommand, GetObjectLockConfigurationCommand, GetPublicAccessBlockCommand, HeadBucketCommand, ListBucketAnalyticsConfigurationsCommand, ListBucketIntelligentTieringConfigurationsCommand, ListBucketInventoryConfigurationsCommand, ListBucketMetricsConfigurationsCommand, ListObjectVersionsCommand, ListObjectsV2Command, NoSuchBucket, PutBucketAccelerateConfigurationCommand, PutBucketAnalyticsConfigurationCommand, PutBucketCorsCommand, PutBucketEncryptionCommand, PutBucketIntelligentTieringConfigurationCommand, PutBucketInventoryConfigurationCommand, PutBucketLifecycleConfigurationCommand, PutBucketLoggingCommand, PutBucketMetricsConfigurationCommand, PutBucketNotificationConfigurationCommand, PutBucketOwnershipControlsCommand, PutBucketPolicyCommand, PutBucketReplicationCommand, PutBucketTaggingCommand, PutBucketVersioningCommand, PutBucketWebsiteCommand, PutObjectCommand, PutObjectLockConfigurationCommand, PutPublicAccessBlockCommand, S3Client, S3ServiceException } from "@aws-sdk/client-s3";
|
|
@@ -2619,6 +2619,38 @@ function collectCrossStackRefs(node, onRef) {
|
|
|
2619
2619
|
//#endregion
|
|
2620
2620
|
//#region src/provisioning/providers/iam-policy-provider.ts
|
|
2621
2621
|
/**
|
|
2622
|
+
* The short `ResourceDeleteResult.reason` the no-policy-name DELETE arm
|
|
2623
|
+
* reports (issue [#1770](https://github.com/go-to-k/cdkd/issues/1770)).
|
|
2624
|
+
*
|
|
2625
|
+
* Rendered inline on the destroy status line, so it is the SHORT form; the full
|
|
2626
|
+
* remediation sentence goes out as the `logger.warn` beside it. It says "in
|
|
2627
|
+
* state" rather than "in physicalId" because the arm is only reached once BOTH
|
|
2628
|
+
* sources have been exhausted — the physicalId and `properties['PolicyName']`.
|
|
2629
|
+
*/
|
|
2630
|
+
const POLICY_NAME_SKIP_REASON = "no policy name in state — no delete issued";
|
|
2631
|
+
/**
|
|
2632
|
+
* The short `ResourceDeleteResult.reason` for a record that names no principal
|
|
2633
|
+
* to detach the inline policy from (issue
|
|
2634
|
+
* [#1770](https://github.com/go-to-k/cdkd/issues/1770) review).
|
|
2635
|
+
*
|
|
2636
|
+
* Distinct from {@link POLICY_NAME_SKIP_REASON}: the policy may be perfectly
|
|
2637
|
+
* well named and it is the ATTACHMENT that is unknown, so pointing the user at
|
|
2638
|
+
* the physicalId would send them to the wrong half of the record.
|
|
2639
|
+
*/
|
|
2640
|
+
const POLICY_NO_TARGET_SKIP_REASON = "no Roles/Groups/Users in state — no delete issued";
|
|
2641
|
+
/**
|
|
2642
|
+
* The deploy-side caveat the skip warning in this file carries (issue
|
|
2643
|
+
* [#1762](https://github.com/go-to-k/cdkd/issues/1762)).
|
|
2644
|
+
*
|
|
2645
|
+
* "Repair state.json and re-run" is only true on DESTROY, where the skip KEEPS
|
|
2646
|
+
* the record. The same arm is ALSO reached from `deploy-engine.ts` and
|
|
2647
|
+
* `rollback-executor.ts`, which discard the delete result and DROP the record —
|
|
2648
|
+
* there the id is gone, so re-running cannot help and the resource has to be
|
|
2649
|
+
* removed by hand. Mirrors the caveat `compositeIdFormatMessage` already
|
|
2650
|
+
* carries for the composite-id family.
|
|
2651
|
+
*/
|
|
2652
|
+
const DEPLOY_SKIP_CAVEAT$3 = "NOTE this arm is ALSO reached from cdkd deploy (the DELETE of a resource removed from the template, plus the replacement / rollback deletes), which DROP the state record and report success (https://github.com/go-to-k/cdkd/issues/1762) — there, remove the resource by hand.";
|
|
2653
|
+
/**
|
|
2622
2654
|
* AWS IAM Policy Provider
|
|
2623
2655
|
*
|
|
2624
2656
|
* Implements resource provisioning for AWS::IAM::Policy using the IAM SDK.
|
|
@@ -2780,21 +2812,34 @@ var IAMPolicyProvider = class {
|
|
|
2780
2812
|
*/
|
|
2781
2813
|
async delete(logicalId, physicalId, resourceType, properties, context) {
|
|
2782
2814
|
this.logger.debug(`Deleting IAM policy ${logicalId}: ${physicalId}`);
|
|
2783
|
-
const
|
|
2815
|
+
const policyNameFromPhysicalId = physicalId.includes(":") ? physicalId.split(":")[0] : physicalId;
|
|
2816
|
+
const policyNameFromProperties = typeof properties?.["PolicyName"] === "string" ? properties["PolicyName"] : void 0;
|
|
2817
|
+
const policyName = policyNameFromPhysicalId || policyNameFromProperties;
|
|
2818
|
+
const roles = properties?.["Roles"];
|
|
2819
|
+
const groups = properties?.["Groups"];
|
|
2820
|
+
const users = properties?.["Users"];
|
|
2821
|
+
const legacyRoleFromPhysicalId = !roles && !groups && !users && physicalId.includes(":") ? physicalId.split(":")[1] : void 0;
|
|
2784
2822
|
if (!policyName) {
|
|
2785
|
-
this.logger.warn(`Invalid physical ID format: ${physicalId}, skipping deletion`);
|
|
2786
|
-
return
|
|
2823
|
+
this.logger.warn(`Invalid physical ID format: ${physicalId}, and no PolicyName in the state record's properties — skipping deletion. No AWS call is issued, so the inline policy is LEFT ATTACHED to its roles / groups / users, UNLESS the role / group / user it is attached to is itself part of this stack (deleting that principal removes its inline policies, and then only the cdkd record is stale — clear it with 'cdkd state orphan <stack>'). Otherwise repair the physicalId in state.json and re-run, or delete the inline policy by hand. ${DEPLOY_SKIP_CAVEAT$3}`);
|
|
2824
|
+
return {
|
|
2825
|
+
outcome: "skipped",
|
|
2826
|
+
reason: POLICY_NAME_SKIP_REASON
|
|
2827
|
+
};
|
|
2828
|
+
}
|
|
2829
|
+
if (!roles && !groups && !users && !legacyRoleFromPhysicalId) {
|
|
2830
|
+
this.logger.warn(`No Roles, Groups or Users in the state record for IAM policy ${logicalId} (physicalId "${physicalId}"), skipping deletion — an inline policy exists only as an attachment, so with no principal named there is no delete to issue and the policy is LEFT ATTACHED wherever it is, UNLESS the role / group / user it is attached to is itself part of this stack (deleting that principal removes its inline policies, and then only the cdkd record is stale — clear it with 'cdkd state orphan <stack>'). Otherwise restore Roles / Groups / Users in state.json and re-run, or delete the inline policy by hand. ${DEPLOY_SKIP_CAVEAT$3}`);
|
|
2831
|
+
return {
|
|
2832
|
+
outcome: "skipped",
|
|
2833
|
+
reason: POLICY_NO_TARGET_SKIP_REASON
|
|
2834
|
+
};
|
|
2787
2835
|
}
|
|
2788
2836
|
const onNotFound = async (target) => {
|
|
2789
2837
|
assertRegionMatch(await this.iamClient.config.region(), context?.expectedRegion, resourceType, logicalId, `${physicalId} (${target})`);
|
|
2790
2838
|
};
|
|
2791
2839
|
try {
|
|
2792
|
-
|
|
2793
|
-
|
|
2794
|
-
|
|
2795
|
-
if (!roles && !groups && !users && physicalId.includes(":")) {
|
|
2796
|
-
const firstRole = physicalId.split(":")[1];
|
|
2797
|
-
if (firstRole) try {
|
|
2840
|
+
if (legacyRoleFromPhysicalId) {
|
|
2841
|
+
const firstRole = legacyRoleFromPhysicalId;
|
|
2842
|
+
try {
|
|
2798
2843
|
await this.iamClient.send(new DeleteRolePolicyCommand({
|
|
2799
2844
|
RoleName: firstRole,
|
|
2800
2845
|
PolicyName: policyName
|
|
@@ -3893,6 +3938,36 @@ var IAMAccessKeyProvider = class {
|
|
|
3893
3938
|
//#endregion
|
|
3894
3939
|
//#region src/provisioning/providers/iam-user-group-provider.ts
|
|
3895
3940
|
/**
|
|
3941
|
+
* The short `ResourceDeleteResult.reason` the no-properties
|
|
3942
|
+
* `AWS::IAM::UserToGroupAddition` DELETE arm reports (issue
|
|
3943
|
+
* [#1770](https://github.com/go-to-k/cdkd/issues/1770)).
|
|
3944
|
+
*
|
|
3945
|
+
* Rendered inline on the destroy status line, so it is the SHORT form; the full
|
|
3946
|
+
* remediation sentence goes out as the `logger.warn` beside it. It names the
|
|
3947
|
+
* MEMBERSHIP rather than the resource because that is what survives — the
|
|
3948
|
+
* resource itself is metadata, the users staying in the group is the residue.
|
|
3949
|
+
*/
|
|
3950
|
+
const MEMBERSHIP_NO_PROPERTIES_SKIP_REASON = "no properties in state — group membership not removed";
|
|
3951
|
+
/**
|
|
3952
|
+
* Sibling of {@link MEMBERSHIP_NO_PROPERTIES_SKIP_REASON} for the arm where the
|
|
3953
|
+
* record has properties but is missing `GroupName` or `Users` — both REQUIRED
|
|
3954
|
+
* by the CloudFormation schema, so their absence is corruption, not an
|
|
3955
|
+
* empty-but-valid membership list.
|
|
3956
|
+
*/
|
|
3957
|
+
const MEMBERSHIP_MISSING_FIELDS_SKIP_REASON = "GroupName/Users missing from state — membership not removed";
|
|
3958
|
+
/**
|
|
3959
|
+
* The deploy-side caveat both `UserToGroupAddition` skip warnings carry (issue
|
|
3960
|
+
* [#1762](https://github.com/go-to-k/cdkd/issues/1762)).
|
|
3961
|
+
*
|
|
3962
|
+
* "Repair state.json and re-run" is only true on DESTROY, where the skip KEEPS
|
|
3963
|
+
* the record. The same arms are ALSO reached from `deploy-engine.ts` and
|
|
3964
|
+
* `rollback-executor.ts`, which discard the delete result and DROP the record —
|
|
3965
|
+
* there the id is gone, so re-running cannot help and the memberships have to
|
|
3966
|
+
* be removed by hand. Mirrors the caveat `compositeIdFormatMessage` already
|
|
3967
|
+
* carries for the composite-id family.
|
|
3968
|
+
*/
|
|
3969
|
+
const DEPLOY_SKIP_CAVEAT$2 = "NOTE this arm is ALSO reached from cdkd deploy (the DELETE of a resource removed from the template, plus the replacement / rollback deletes), which DROP the state record and report success (https://github.com/go-to-k/cdkd/issues/1762) — there, remove the memberships by hand.";
|
|
3970
|
+
/**
|
|
3896
3971
|
* AWS IAM User / Group / UserToGroupAddition Provider
|
|
3897
3972
|
*
|
|
3898
3973
|
* Implements resource provisioning for:
|
|
@@ -4531,14 +4606,20 @@ var IAMUserGroupProvider = class {
|
|
|
4531
4606
|
async deleteUserToGroupAddition(logicalId, physicalId, resourceType, properties, _context) {
|
|
4532
4607
|
this.logger.debug(`Deleting IAM UserToGroupAddition ${logicalId}`);
|
|
4533
4608
|
if (!properties) {
|
|
4534
|
-
this.logger.
|
|
4535
|
-
return
|
|
4609
|
+
this.logger.warn(`No properties for UserToGroupAddition ${logicalId}, skipping deletion — GroupName and Users are both required to call RemoveUserFromGroup, so no AWS call is issued and the group memberships are LEFT IN PLACE, UNLESS the group or the users are themselves part of this stack (their own deletes remove exactly these memberships, and then only the cdkd record is stale — clear it with 'cdkd state orphan <stack>'). Otherwise restore the record's properties in state.json and re-run, or remove the users from the group by hand. ${DEPLOY_SKIP_CAVEAT$2}`);
|
|
4610
|
+
return {
|
|
4611
|
+
outcome: "skipped",
|
|
4612
|
+
reason: MEMBERSHIP_NO_PROPERTIES_SKIP_REASON
|
|
4613
|
+
};
|
|
4536
4614
|
}
|
|
4537
4615
|
const groupName = properties["GroupName"];
|
|
4538
4616
|
const users = properties["Users"];
|
|
4539
4617
|
if (!groupName || !users) {
|
|
4540
|
-
this.logger.
|
|
4541
|
-
return
|
|
4618
|
+
this.logger.warn(`Missing GroupName or Users for ${logicalId}, skipping deletion — both are required to call RemoveUserFromGroup, so no AWS call is issued and the group memberships are LEFT IN PLACE, UNLESS the group or the users are themselves part of this stack (their own deletes remove exactly these memberships, and then only the cdkd record is stale — clear it with 'cdkd state orphan <stack>'). Otherwise restore them in state.json and re-run, or remove the users from the group by hand. ${DEPLOY_SKIP_CAVEAT$2}`);
|
|
4619
|
+
return {
|
|
4620
|
+
outcome: "skipped",
|
|
4621
|
+
reason: MEMBERSHIP_MISSING_FIELDS_SKIP_REASON
|
|
4622
|
+
};
|
|
4542
4623
|
}
|
|
4543
4624
|
try {
|
|
4544
4625
|
for (const userName of users) try {
|
|
@@ -11748,6 +11829,36 @@ var LambdaFunctionProvider = class {
|
|
|
11748
11829
|
//#endregion
|
|
11749
11830
|
//#region src/provisioning/providers/lambda-permission-provider.ts
|
|
11750
11831
|
/**
|
|
11832
|
+
* The short `ResourceDeleteResult.reason` the missing-`FunctionName` DELETE arm
|
|
11833
|
+
* reports (issue [#1770](https://github.com/go-to-k/cdkd/issues/1770)).
|
|
11834
|
+
*
|
|
11835
|
+
* Rendered inline on the destroy status line, so it is the SHORT form; the full
|
|
11836
|
+
* remediation sentence goes out as the `logger.warn` beside it.
|
|
11837
|
+
*/
|
|
11838
|
+
const PERMISSION_FUNCTION_NAME_SKIP_REASON = "FunctionName missing from state — no delete issued";
|
|
11839
|
+
/**
|
|
11840
|
+
* The short `ResourceDeleteResult.reason` for a composite physicalId whose
|
|
11841
|
+
* trailing StatementId segment is empty (`"<functionArn>|"`).
|
|
11842
|
+
*
|
|
11843
|
+
* Its own reason rather than {@link PERMISSION_FUNCTION_NAME_SKIP_REASON}: the
|
|
11844
|
+
* function may be perfectly well named, and pointing the user at FunctionName
|
|
11845
|
+
* would send them to the wrong half of the record.
|
|
11846
|
+
*/
|
|
11847
|
+
const PERMISSION_STATEMENT_ID_SKIP_REASON = "empty StatementId in state physicalId — no delete issued";
|
|
11848
|
+
/**
|
|
11849
|
+
* The deploy-side caveat the skip warning in this file carries (issue
|
|
11850
|
+
* [#1762](https://github.com/go-to-k/cdkd/issues/1762)).
|
|
11851
|
+
*
|
|
11852
|
+
* "Repair state.json and re-run" is only true on DESTROY, where the skip KEEPS
|
|
11853
|
+
* the record. The same arm is ALSO reached from `deploy-engine.ts` and
|
|
11854
|
+
* `rollback-executor.ts`, which discard the delete result and DROP the record —
|
|
11855
|
+
* there the id is gone, so re-running cannot help and the resource has to be
|
|
11856
|
+
* removed by hand. Mirrors the caveat `compositeIdFormatMessage` already
|
|
11857
|
+
* carries for the composite-id family; a remedy that is impossible on the path
|
|
11858
|
+
* the user is actually on is worse than no remedy.
|
|
11859
|
+
*/
|
|
11860
|
+
const DEPLOY_SKIP_CAVEAT$1 = "NOTE this arm is ALSO reached from cdkd deploy (the DELETE of a resource removed from the template, plus the replacement / rollback deletes), which DROP the state record and report success (https://github.com/go-to-k/cdkd/issues/1762) — there, remove the resource by hand.";
|
|
11861
|
+
/**
|
|
11751
11862
|
* AWS Lambda Permission Provider
|
|
11752
11863
|
*
|
|
11753
11864
|
* Implements resource provisioning for AWS::Lambda::Permission using the Lambda SDK.
|
|
@@ -11850,13 +11961,36 @@ var LambdaPermissionProvider = class {
|
|
|
11850
11961
|
*/
|
|
11851
11962
|
async delete(logicalId, physicalId, resourceType, properties, context) {
|
|
11852
11963
|
this.logger.debug(`Deleting Lambda permission ${logicalId}: ${physicalId}`);
|
|
11853
|
-
const
|
|
11964
|
+
const physicalIdSegments = physicalId.split("|");
|
|
11965
|
+
const isComposite = physicalIdSegments.length > 1;
|
|
11966
|
+
const statementId = isComposite ? physicalIdSegments[physicalIdSegments.length - 1] : physicalId;
|
|
11967
|
+
const functionNameFromProperties = typeof properties?.["FunctionName"] === "string" ? properties["FunctionName"] : void 0;
|
|
11968
|
+
let functionNameFromPhysicalId;
|
|
11969
|
+
const leadingSegment = isComposite ? physicalIdSegments[0] : "";
|
|
11970
|
+
if (leadingSegment !== "") {
|
|
11971
|
+
if (!leadingSegment.startsWith("arn:")) functionNameFromPhysicalId = leadingSegment;
|
|
11972
|
+
else if (leadingSegment.includes(":function:")) {
|
|
11973
|
+
const arnRegion = leadingSegment.split(":")[3];
|
|
11974
|
+
const clientRegion = await this.lambdaClient.config.region();
|
|
11975
|
+
if (arnRegion === clientRegion) functionNameFromPhysicalId = leadingSegment;
|
|
11976
|
+
else this.logger.debug(`Lambda permission ${logicalId}: physicalId ARN region ${arnRegion} does not match the client region ${clientRegion}, so it cannot be used as a FunctionName`);
|
|
11977
|
+
}
|
|
11978
|
+
}
|
|
11979
|
+
if (statementId === "") {
|
|
11980
|
+
this.logger.warn(`Lambda permission ${logicalId} has no StatementId in its physicalId ("${physicalId}"), skipping deletion — no AWS call is issued, so the permission statement is LEFT IN PLACE on the function's resource policy, UNLESS the function itself is part of this stack (deleting it removes its whole resource policy, and then only the cdkd record is stale — clear it with 'cdkd state orphan <stack>'). Otherwise repair the physicalId in state.json and re-run, or remove the statement by hand ('aws lambda remove-permission'). ${DEPLOY_SKIP_CAVEAT$1}`);
|
|
11981
|
+
return {
|
|
11982
|
+
outcome: "skipped",
|
|
11983
|
+
reason: PERMISSION_STATEMENT_ID_SKIP_REASON
|
|
11984
|
+
};
|
|
11985
|
+
}
|
|
11986
|
+
const functionName = functionNameFromProperties || functionNameFromPhysicalId;
|
|
11854
11987
|
if (!functionName) {
|
|
11855
|
-
this.logger.warn(`FunctionName not available for Lambda permission ${logicalId}, skipping deletion`);
|
|
11856
|
-
return
|
|
11988
|
+
this.logger.warn(`FunctionName not available for Lambda permission ${logicalId} (neither the state record's FunctionName nor a function ARN in the physicalId), skipping deletion — no AWS call is issued, so the permission statement is LEFT IN PLACE on the function's resource policy, UNLESS the function itself is part of this stack (deleting it removes its whole resource policy, and then only the cdkd record is stale — clear it with 'cdkd state orphan <stack>'). Otherwise repair the record's FunctionName in state.json and re-run, or remove the statement by hand ('aws lambda remove-permission'). ${DEPLOY_SKIP_CAVEAT$1}`);
|
|
11989
|
+
return {
|
|
11990
|
+
outcome: "skipped",
|
|
11991
|
+
reason: PERMISSION_FUNCTION_NAME_SKIP_REASON
|
|
11992
|
+
};
|
|
11857
11993
|
}
|
|
11858
|
-
let statementId = physicalId;
|
|
11859
|
-
if (physicalId.includes("|")) statementId = physicalId.split("|").pop();
|
|
11860
11994
|
try {
|
|
11861
11995
|
await this.lambdaClient.send(new RemovePermissionCommand({
|
|
11862
11996
|
FunctionName: functionName,
|
|
@@ -12679,6 +12813,36 @@ var LambdaEventSourceMappingProvider = class {
|
|
|
12679
12813
|
//#endregion
|
|
12680
12814
|
//#region src/provisioning/providers/lambda-layer-provider.ts
|
|
12681
12815
|
/**
|
|
12816
|
+
* The short `ResourceDeleteResult.reason` the two malformed-`LayerVersionArn`
|
|
12817
|
+
* DELETE arms report (issue
|
|
12818
|
+
* [#1770](https://github.com/go-to-k/cdkd/issues/1770)).
|
|
12819
|
+
*
|
|
12820
|
+
* Rendered inline on the destroy status line
|
|
12821
|
+
* (`⚠ MyLayer (AWS::Lambda::LayerVersion) skipped (<reason>)`), so it is the
|
|
12822
|
+
* SHORT form — the full remediation sentence goes out as the `logger.warn`
|
|
12823
|
+
* beside it. Exported so the wording is pinned by a test rather than retyped.
|
|
12824
|
+
*/
|
|
12825
|
+
const LAYER_ARN_SKIP_REASON = "malformed LayerVersionArn in state — no delete issued";
|
|
12826
|
+
/**
|
|
12827
|
+
* Sibling of {@link LAYER_ARN_SKIP_REASON} for the arm where the ARN has the
|
|
12828
|
+
* right shape but its trailing version segment is not a number. Kept distinct
|
|
12829
|
+
* because the two point at different halves of the id, and the destroy line is
|
|
12830
|
+
* all the user sees.
|
|
12831
|
+
*/
|
|
12832
|
+
const LAYER_VERSION_SKIP_REASON = "unparsable version in state LayerVersionArn — no delete issued";
|
|
12833
|
+
/**
|
|
12834
|
+
* The deploy-side caveat both skip warnings in this file carry (issue
|
|
12835
|
+
* [#1762](https://github.com/go-to-k/cdkd/issues/1762)).
|
|
12836
|
+
*
|
|
12837
|
+
* "Repair state.json and re-run" is only true on DESTROY, where the skip KEEPS
|
|
12838
|
+
* the record. The same arms are ALSO reached from `deploy-engine.ts` and
|
|
12839
|
+
* `rollback-executor.ts`, which discard the delete result and DROP the record —
|
|
12840
|
+
* there the id is gone, so re-running cannot help and the resource has to be
|
|
12841
|
+
* removed by hand. Mirrors the caveat `compositeIdFormatMessage` already
|
|
12842
|
+
* carries for the composite-id family.
|
|
12843
|
+
*/
|
|
12844
|
+
const DEPLOY_SKIP_CAVEAT = "NOTE this arm is ALSO reached from cdkd deploy (the DELETE of a resource removed from the template, plus the replacement / rollback deletes), which DROP the state record and report success (https://github.com/go-to-k/cdkd/issues/1762) — there, remove the resource by hand.";
|
|
12845
|
+
/**
|
|
12682
12846
|
* AWS Lambda LayerVersion Provider
|
|
12683
12847
|
*
|
|
12684
12848
|
* Implements resource provisioning for AWS::Lambda::LayerVersion using the Lambda SDK.
|
|
@@ -12768,14 +12932,20 @@ var LambdaLayerVersionProvider = class {
|
|
|
12768
12932
|
this.logger.debug(`Deleting Lambda layer version ${logicalId}: ${physicalId}`);
|
|
12769
12933
|
const arnParts = physicalId.split(":");
|
|
12770
12934
|
if (arnParts.length < 8) {
|
|
12771
|
-
this.logger.warn(`Invalid LayerVersionArn format: ${physicalId}, skipping deletion`);
|
|
12772
|
-
return
|
|
12935
|
+
this.logger.warn(`Invalid LayerVersionArn format: ${physicalId}, skipping deletion — no AWS call is issued, so the layer version is LEFT IN PLACE and still counts against the account's storage quota. Repair the physicalId in state.json and re-run, or delete the layer version by hand. ${DEPLOY_SKIP_CAVEAT}`);
|
|
12936
|
+
return {
|
|
12937
|
+
outcome: "skipped",
|
|
12938
|
+
reason: LAYER_ARN_SKIP_REASON
|
|
12939
|
+
};
|
|
12773
12940
|
}
|
|
12774
12941
|
const layerName = arnParts[6];
|
|
12775
12942
|
const versionNumber = parseInt(arnParts[7], 10);
|
|
12776
12943
|
if (isNaN(versionNumber)) {
|
|
12777
|
-
this.logger.warn(`Could not parse version number from ARN: ${physicalId}, skipping deletion`);
|
|
12778
|
-
return
|
|
12944
|
+
this.logger.warn(`Could not parse version number from ARN: ${physicalId}, skipping deletion — no AWS call is issued, so the layer version is LEFT IN PLACE and still counts against the account's storage quota. Repair the physicalId in state.json and re-run, or delete the layer version by hand. ${DEPLOY_SKIP_CAVEAT}`);
|
|
12945
|
+
return {
|
|
12946
|
+
outcome: "skipped",
|
|
12947
|
+
reason: LAYER_VERSION_SKIP_REASON
|
|
12948
|
+
};
|
|
12779
12949
|
}
|
|
12780
12950
|
try {
|
|
12781
12951
|
await this.lambdaClient.send(new DeleteLayerVersionCommand({
|
|
@@ -59623,6 +59793,27 @@ const COMPOSITE_ID_SPLITTERS = {
|
|
|
59623
59793
|
propertiesOverlay: { VpcId: vpcId }
|
|
59624
59794
|
};
|
|
59625
59795
|
},
|
|
59796
|
+
"AWS::EC2::VPCCidrBlock": (physicalId, properties) => {
|
|
59797
|
+
if (!physicalId.trim()) throw new Error("empty physical id for AWS::EC2::VPCCidrBlock (expected an association id)");
|
|
59798
|
+
const parts = physicalId.split("|").map((p) => p.trim());
|
|
59799
|
+
if (parts.length > 2) throw new Error(`expected a bare '<Id>' or '<Id>|<VpcId>', got ${parts.length} parts: '${physicalId}'`);
|
|
59800
|
+
const id = parts[0];
|
|
59801
|
+
if (!id) throw new Error(`empty part in '<Id>|<VpcId>': '${physicalId}'`);
|
|
59802
|
+
if (!id.startsWith(VPC_CIDR_ASSOCIATION_ID_PREFIX)) throw new Error(`'${physicalId}' does not start with an association id: CloudFormation identifies an AWS::EC2::VPCCidrBlock by [Id, VpcId], so the first segment must be a '${VPC_CIDR_ASSOCIATION_ID_PREFIX}…' id. State entry may be corrupt, or the segments may be reversed; re-deploy the resource to refresh state`);
|
|
59803
|
+
let vpcId;
|
|
59804
|
+
if (parts.length === 2) {
|
|
59805
|
+
vpcId = parts[1];
|
|
59806
|
+
if (!vpcId) throw new Error(`empty part in '<Id>|<VpcId>': '${physicalId}'`);
|
|
59807
|
+
} else vpcId = readStringProperty(properties, "VpcId", "AWS::EC2::VPCCidrBlock");
|
|
59808
|
+
if (!VPC_ID_PATTERN.test(vpcId)) throw new Error(`'${vpcId}' is not a VPC id (expected 'vpc-…', not an association id): '${physicalId}' may have its segments reversed`);
|
|
59809
|
+
return {
|
|
59810
|
+
resourceIdentifier: {
|
|
59811
|
+
Id: id,
|
|
59812
|
+
VpcId: vpcId
|
|
59813
|
+
},
|
|
59814
|
+
propertiesOverlay: { VpcId: vpcId }
|
|
59815
|
+
};
|
|
59816
|
+
},
|
|
59626
59817
|
"AWS::EC2::Route": (physicalId, properties) => {
|
|
59627
59818
|
const parts = physicalId.split("|");
|
|
59628
59819
|
if (parts.length !== 2) throw new Error(`expected 2 parts ('<routeTableId>|<destination>'), got ${parts.length}: '${physicalId}'`);
|
|
@@ -59752,6 +59943,26 @@ const EIP_ALLOCATION_ID_PREFIX = "eipalloc-";
|
|
|
59752
59943
|
* validate an address AWS itself minted.
|
|
59753
59944
|
*/
|
|
59754
59945
|
const EIP_PUBLIC_IP_PATTERN = /^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$/;
|
|
59946
|
+
/**
|
|
59947
|
+
* The prefix every `AWS::EC2::VPCCidrBlock` association id carries. The same
|
|
59948
|
+
* kind of shape discriminator as {@link EIP_ALLOCATION_ID_PREFIX}: a VPC id and
|
|
59949
|
+
* an association id BOTH start with `vpc-`, so the longer prefix is what tells
|
|
59950
|
+
* the two segments apart.
|
|
59951
|
+
*/
|
|
59952
|
+
const VPC_CIDR_ASSOCIATION_ID_PREFIX = "vpc-cidr-assoc-";
|
|
59953
|
+
/**
|
|
59954
|
+
* The other half of that discriminator. ANCHORED, so an association id
|
|
59955
|
+
* (`vpc-cidr-assoc-…`, which also begins `vpc-`) does NOT match — a reversed
|
|
59956
|
+
* pair is exactly what this exists to catch.
|
|
59957
|
+
*
|
|
59958
|
+
* The character class is `[0-9a-z]` rather than hex, and the LENGTH is not
|
|
59959
|
+
* pinned. EC2 ids are hex today, but `cdkd export` is all-or-nothing, so a
|
|
59960
|
+
* false REFUSAL here blocks an entire export — and the discrimination does not
|
|
59961
|
+
* depend on the alphabet at all: it is the `-` in `cidr-assoc`, which no
|
|
59962
|
+
* character class here admits. Widening the class therefore costs nothing and
|
|
59963
|
+
* removes a false-refusal vector.
|
|
59964
|
+
*/
|
|
59965
|
+
const VPC_ID_PATTERN = /^vpc-[0-9a-z]+$/;
|
|
59755
59966
|
const GATEWAY_ATTACHMENT_TYPE_IGW = "IGW";
|
|
59756
59967
|
const GATEWAY_ATTACHMENT_TYPE_VGW = "VGW";
|
|
59757
59968
|
/**
|
|
@@ -60347,8 +60558,8 @@ async function exportCommand(stackArg, options) {
|
|
|
60347
60558
|
});
|
|
60348
60559
|
if (blocked.length > 0) {
|
|
60349
60560
|
logger.error("The following resources block migration:");
|
|
60350
|
-
for (const
|
|
60351
|
-
throw new Error(`${blocked.
|
|
60561
|
+
for (const line of groupBlockedReasons(blocked)) logger.error(line);
|
|
60562
|
+
throw new Error(`${new Set(blocked.map((b) => b.logicalId)).size} resource(s) block migration. Either destroy them first (cdkd destroy / cdkd state destroy cherry-picked), or remove them from the CDK app and re-synthesize.`);
|
|
60352
60563
|
}
|
|
60353
60564
|
const owner = `${process.env["USER"] || "unknown"}@${process.env["HOSTNAME"] || "host"}:${process.pid}`;
|
|
60354
60565
|
if (!options.dryRun) {
|
|
@@ -60955,12 +61166,25 @@ async function buildImportPlan(state, template, cfnClient, parentStackName, opti
|
|
|
60955
61166
|
});
|
|
60956
61167
|
continue;
|
|
60957
61168
|
}
|
|
61169
|
+
const propertiesOverlay = resolved.propertiesOverlay ?? resolved.resourceIdentifier;
|
|
61170
|
+
const unrepresentable = unrepresentableOverlayFields(raw, propertiesOverlay);
|
|
61171
|
+
if (unrepresentable.length > 0) {
|
|
61172
|
+
for (const u of unrepresentable) blocked.push({
|
|
61173
|
+
logicalId,
|
|
61174
|
+
resourceType,
|
|
61175
|
+
reason: unrepresentableOverlayMessage({
|
|
61176
|
+
logicalId,
|
|
61177
|
+
resourceType
|
|
61178
|
+
}, u.field, u.current, u.value)
|
|
61179
|
+
});
|
|
61180
|
+
continue;
|
|
61181
|
+
}
|
|
60958
61182
|
phase1Imports.push({
|
|
60959
61183
|
logicalId,
|
|
60960
61184
|
resourceType,
|
|
60961
61185
|
physicalId: stateEntry.physicalId,
|
|
60962
61186
|
resourceIdentifier: resolved.resourceIdentifier,
|
|
60963
|
-
propertiesOverlay
|
|
61187
|
+
propertiesOverlay
|
|
60964
61188
|
});
|
|
60965
61189
|
}
|
|
60966
61190
|
return {
|
|
@@ -60972,6 +61196,28 @@ async function buildImportPlan(state, template, cfnClient, parentStackName, opti
|
|
|
60972
61196
|
};
|
|
60973
61197
|
}
|
|
60974
61198
|
/**
|
|
61199
|
+
* Render the `blocked` list one bullet per RESOURCE, folding a resource's
|
|
61200
|
+
* several reasons into it.
|
|
61201
|
+
*
|
|
61202
|
+
* The pre-flight pushes one entry per offending FIELD (issue #1787), so a
|
|
61203
|
+
* multi-field overlay such as `AWS::S3Tables::Namespace` contributes two rows
|
|
61204
|
+
* for one resource — which made the count ("1 resource(s)") disagree with the
|
|
61205
|
+
* bullets printed under it. Grouping keeps the two consistent without losing a
|
|
61206
|
+
* reason.
|
|
61207
|
+
*/
|
|
61208
|
+
function groupBlockedReasons(blocked) {
|
|
61209
|
+
const byResource = /* @__PURE__ */ new Map();
|
|
61210
|
+
for (const b of blocked) {
|
|
61211
|
+
const entry = byResource.get(b.logicalId) ?? {
|
|
61212
|
+
resourceType: b.resourceType,
|
|
61213
|
+
reasons: []
|
|
61214
|
+
};
|
|
61215
|
+
entry.reasons.push(b.reason);
|
|
61216
|
+
byResource.set(b.logicalId, entry);
|
|
61217
|
+
}
|
|
61218
|
+
return [...byResource].map(([logicalId, { resourceType, reasons }]) => reasons.length === 1 ? ` - ${logicalId} (${resourceType}): ${reasons[0]}` : ` - ${logicalId} (${resourceType}):\n${reasons.map((r) => ` - ${r}`).join("\n")}`);
|
|
61219
|
+
}
|
|
61220
|
+
/**
|
|
60975
61221
|
* Build the `ResourceIdentifier` map CloudFormation IMPORT expects in
|
|
60976
61222
|
* `ResourcesToImport[].ResourceIdentifier` for the given resource type
|
|
60977
61223
|
* and cdkd state's physical ID.
|
|
@@ -61230,14 +61476,14 @@ function injectDeletionPolicyForImport(template) {
|
|
|
61230
61476
|
}
|
|
61231
61477
|
return injected;
|
|
61232
61478
|
}
|
|
61233
|
-
function filterTemplateForImport(template, plan) {
|
|
61479
|
+
function filterTemplateForImport(template, plan, warnOnRewrite = true) {
|
|
61234
61480
|
const allow = new Map(plan.map((p) => [p.logicalId, p]));
|
|
61235
61481
|
const original = template["Resources"];
|
|
61236
61482
|
const filteredResources = {};
|
|
61237
61483
|
for (const [logicalId, resource] of Object.entries(original)) {
|
|
61238
61484
|
const entry = allow.get(logicalId);
|
|
61239
61485
|
if (!entry) continue;
|
|
61240
|
-
filteredResources[logicalId] = overlayResourceIdentifierOnProperties(resource, entry);
|
|
61486
|
+
filteredResources[logicalId] = overlayResourceIdentifierOnProperties(resource, entry, warnOnRewrite);
|
|
61241
61487
|
}
|
|
61242
61488
|
const result = {
|
|
61243
61489
|
...template,
|
|
@@ -61264,8 +61510,10 @@ function filterTemplateForImport(template, plan) {
|
|
|
61264
61510
|
* migration" value proposition. Closes [#319].
|
|
61265
61511
|
*
|
|
61266
61512
|
* **Post-v0.95 behavior** (this function): override only when synth
|
|
61267
|
-
* already carries a *literal
|
|
61268
|
-
* from `ResourceIdentifier`.
|
|
61513
|
+
* already carries a *literal* value for the field AND it differs
|
|
61514
|
+
* from `ResourceIdentifier`. Four cases (the fourth added by issue #1787,
|
|
61515
|
+
* which also widened "literal" past `typeof === 'string'` — see
|
|
61516
|
+
* {@link classifyOverlayCurrentValue} for the full classification):
|
|
61269
61517
|
*
|
|
61270
61518
|
* - **Absent** (auto-generated names — user did NOT declare a physical
|
|
61271
61519
|
* name in CDK code): `Properties[field]` stays absent. Matches
|
|
@@ -61302,12 +61550,19 @@ function filterTemplateForImport(template, plan) {
|
|
|
61302
61550
|
* names` flip are no longer in this case — Properties.RoleName matches
|
|
61303
61551
|
* the AWS name without override.
|
|
61304
61552
|
*
|
|
61553
|
+
* - **Unrepresentable** (a list carrying an intrinsic / a nested list / an
|
|
61554
|
+
* empty list): REFUSED with a message naming the resource, the property and
|
|
61555
|
+
* the scalar to declare instead. Preserving ANY of the three reproduces the
|
|
61556
|
+
* opaque `CreateChangeSet` rejection; what makes OVERRIDING wrong differs per
|
|
61557
|
+
* shape, so {@link refusalReason} says which rather than asserting one cause
|
|
61558
|
+
* for all — only a list actually carrying an object element has an intrinsic
|
|
61559
|
+
* to discard. Either way the command stops before submitting anything
|
|
61560
|
+
* (issue #1787).
|
|
61561
|
+
*
|
|
61305
61562
|
* For literal-match (Properties already has the right value), the
|
|
61306
|
-
* override is a no-op
|
|
61307
|
-
* `Properties[field] !== overlayValue && typeof Properties[field] ===
|
|
61308
|
-
* 'string'`.
|
|
61563
|
+
* override is a no-op.
|
|
61309
61564
|
*/
|
|
61310
|
-
function overlayResourceIdentifierOnProperties(resource, entry) {
|
|
61565
|
+
function overlayResourceIdentifierOnProperties(resource, entry, warnOnRewrite = true) {
|
|
61311
61566
|
if (!resource || typeof resource !== "object" || Array.isArray(resource)) return resource;
|
|
61312
61567
|
const r = resource;
|
|
61313
61568
|
const existingProperties = r["Properties"];
|
|
@@ -61315,7 +61570,13 @@ function overlayResourceIdentifierOnProperties(resource, entry) {
|
|
|
61315
61570
|
const overlay = entry.propertiesOverlay ?? entry.resourceIdentifier;
|
|
61316
61571
|
for (const [field, value] of Object.entries(overlay)) {
|
|
61317
61572
|
const current = properties[field];
|
|
61318
|
-
|
|
61573
|
+
const kind = classifyOverlayCurrentValue(current);
|
|
61574
|
+
if (kind === "intrinsic" || kind === "absent") continue;
|
|
61575
|
+
if (kind === "unrepresentable") throw new Error(unrepresentableOverlayMessage(entry, field, current, value));
|
|
61576
|
+
if (current !== value) {
|
|
61577
|
+
if (warnOnRewrite && typeof current !== "string") getLogger().warn(`${entry.logicalId} (${entry.resourceType}): rewriting the identifier property '${field}' from ${describeOverlayValueShape(current)} to the scalar '${value}' recorded in cdkd state. CloudFormation types this property as a string; the exported template will carry the scalar.`);
|
|
61578
|
+
properties[field] = value;
|
|
61579
|
+
}
|
|
61319
61580
|
}
|
|
61320
61581
|
return {
|
|
61321
61582
|
...r,
|
|
@@ -61323,6 +61584,133 @@ function overlayResourceIdentifierOnProperties(resource, entry) {
|
|
|
61323
61584
|
};
|
|
61324
61585
|
}
|
|
61325
61586
|
/**
|
|
61587
|
+
* Why a given `unrepresentable` value is refused rather than rewritten.
|
|
61588
|
+
*
|
|
61589
|
+
* THREE shapes reach `unrepresentable`, and they are refused for two different
|
|
61590
|
+
* reasons — an earlier revision of this message asserted the intrinsic reason
|
|
61591
|
+
* for all of them, which is false for two of the three. The distinction is not
|
|
61592
|
+
* cosmetic: a message that names a cause the value does not have sends the user
|
|
61593
|
+
* looking for an intrinsic that is not there.
|
|
61594
|
+
*/
|
|
61595
|
+
function refusalReason(current) {
|
|
61596
|
+
if (Array.isArray(current) && current.length === 0) return "cdkd will not invent a scalar where the template declared no value";
|
|
61597
|
+
return Array.isArray(current) && current.some((e) => e !== null && typeof e === "object" && !Array.isArray(e)) ? "cdkd will not rewrite it, because doing so would discard the intrinsic it carries" : "cdkd will not replace a value it cannot represent as the scalar the schema declares";
|
|
61598
|
+
}
|
|
61599
|
+
/**
|
|
61600
|
+
* The shared message for a refused overlay value — used by the pre-flight in
|
|
61601
|
+
* {@link buildImportPlan} (which BLOCKS the resource) and by
|
|
61602
|
+
* {@link overlayResourceIdentifierOnProperties}'s defense-in-depth throw, so
|
|
61603
|
+
* the two cannot drift into describing the same defect differently.
|
|
61604
|
+
*/
|
|
61605
|
+
function unrepresentableOverlayMessage(entry, field, current, value) {
|
|
61606
|
+
return `${entry.logicalId} (${entry.resourceType}): the identifier property '${field}' is ${describeOverlayValueShape(current)}, but CloudFormation types it as a string. ${refusalReason(current)}. Declare '${field}' as the scalar '${value}' (or drop the addPropertyOverride that made it a list) and re-run 'cdkd export'.`;
|
|
61607
|
+
}
|
|
61608
|
+
/**
|
|
61609
|
+
* Every overlay field of `entry` whose value in `resource`'s `Properties` is
|
|
61610
|
+
* {@link classifyOverlayCurrentValue}-`unrepresentable`.
|
|
61611
|
+
*
|
|
61612
|
+
* Exists so the refusal happens during PLANNING rather than during template
|
|
61613
|
+
* preprocessing. `overlayResourceIdentifierOnProperties` is called from three
|
|
61614
|
+
* places, and two of them are past the point of no return: `--dry-run` returns
|
|
61615
|
+
* BEFORE any overlay call (so a dry run would report a clean plan for a
|
|
61616
|
+
* template the real run aborts on), and the nested-stack path calls it inside
|
|
61617
|
+
* the per-stack IMPORT loop — where a throw on stack N leaves stacks 1..N-1
|
|
61618
|
+
* already imported, outside the loop's own "stacks imported so far" recovery
|
|
61619
|
+
* handler. Running the same classification in `buildImportPlan` puts the
|
|
61620
|
+
* verdict where every other unfixable-resource verdict already lives: reported
|
|
61621
|
+
* for EVERY offender at once, before the lock, before dry-run's return, and
|
|
61622
|
+
* before any AWS write.
|
|
61623
|
+
*/
|
|
61624
|
+
function unrepresentableOverlayFields(resource, overlay) {
|
|
61625
|
+
if (!resource || typeof resource !== "object" || Array.isArray(resource)) return [];
|
|
61626
|
+
const props = resource["Properties"];
|
|
61627
|
+
if (!props || typeof props !== "object" || Array.isArray(props)) return [];
|
|
61628
|
+
const bag = props;
|
|
61629
|
+
const out = [];
|
|
61630
|
+
for (const [field, value] of Object.entries(overlay)) if (classifyOverlayCurrentValue(bag[field]) === "unrepresentable") out.push({
|
|
61631
|
+
field,
|
|
61632
|
+
current: bag[field],
|
|
61633
|
+
value
|
|
61634
|
+
});
|
|
61635
|
+
return out;
|
|
61636
|
+
}
|
|
61637
|
+
/**
|
|
61638
|
+
* Classify what the synth template currently carries at an identifier field,
|
|
61639
|
+
* for {@link overlayResourceIdentifierOnProperties} (issue #1787).
|
|
61640
|
+
*
|
|
61641
|
+
* The pre-#1787 rule was `typeof current === 'string'`, which is correct for
|
|
61642
|
+
* the two cases it was designed for (an absent key, and a `Ref` / `Fn::GetAtt`
|
|
61643
|
+
* intrinsic that must be preserved — issue #319) and WRONG for a third,
|
|
61644
|
+
* reachable case: a field the template carries as a non-string LITERAL. The
|
|
61645
|
+
* measured instance is `AWS::S3Tables::Namespace`, whose CFn registry type is
|
|
61646
|
+
* `{"type": "string"}` and whose CDK L1 emits a string, but
|
|
61647
|
+
* `addPropertyOverride('Namespace', ['analytics'])` synthesizes
|
|
61648
|
+
* `"Namespace": ["analytics"]` — which cdkd deploys, because
|
|
61649
|
+
* `s3-tables-provider.ts` accepts both wire shapes. The array is not a literal
|
|
61650
|
+
* string, so the overlay left it alone and it reached `CreateChangeSet`, where
|
|
61651
|
+
* CFn answered with an opaque rejection.
|
|
61652
|
+
*
|
|
61653
|
+
* The classification, and why each arm is what it is:
|
|
61654
|
+
*
|
|
61655
|
+
* - `absent` (`undefined` only) — unchanged. An auto-generated name the user
|
|
61656
|
+
* never declared; CFn accepts the IMPORT on `ResourceIdentifier` alone. An
|
|
61657
|
+
* explicit `null` is deliberately NOT here: the key IS present, the shallow
|
|
61658
|
+
* Properties copy keeps it, and `"Namespace": null` then reaches
|
|
61659
|
+
* `CreateChangeSet` — the same opaque-rejection class as the array, through
|
|
61660
|
+
* the same `addPropertyOverride` hatch. It is a literal, and overwritten.
|
|
61661
|
+
* - `intrinsic` (a non-array object) — unchanged. `{Ref: Parent}` /
|
|
61662
|
+
* `{Fn::GetAtt: [...]}` resolve during changeset processing to exactly this
|
|
61663
|
+
* resource's `ResourceIdentifier` value, so clobbering them would only make
|
|
61664
|
+
* the post-export `cdk diff` dirty (issue #319).
|
|
61665
|
+
* - `literal` (string / number / boolean, or an array of those) — OVERRIDDEN
|
|
61666
|
+
* with the overlay value. This is not a guess: the overlay value comes from
|
|
61667
|
+
* the cdkd-recorded physicalId, which is the authority on what the resource
|
|
61668
|
+
* IS, while the template side is only what CDK happened to synthesize. So a
|
|
61669
|
+
* single-element `['analytics']` and a bare `analytics` both become the
|
|
61670
|
+
* scalar CFn's schema declares, exactly as the pre-existing prefixed-name
|
|
61671
|
+
* literal-mismatch case already does.
|
|
61672
|
+
* - `unrepresentable` (an array containing an object / array / null, or an
|
|
61673
|
+
* EMPTY array) — REFUSED. Preserving any of them ships the same opaque CFn
|
|
61674
|
+
* rejection this change exists to remove; the reason OVERRIDING is wrong
|
|
61675
|
+
* differs by shape, and {@link refusalReason} splits it three ways rather
|
|
61676
|
+
* than asserting one cause:
|
|
61677
|
+
* - a list carrying an OBJECT element — overriding discards an intrinsic
|
|
61678
|
+
* the user wrote;
|
|
61679
|
+
* - a NESTED list, or one holding `null` — no intrinsic to protect, it is
|
|
61680
|
+
* simply not representable as the declared scalar;
|
|
61681
|
+
* - an EMPTY list — nothing to reconcile against the identifier, and
|
|
61682
|
+
* materializing a scalar where the template declared "no value" is the
|
|
61683
|
+
* invention this classification exists to avoid.
|
|
61684
|
+
* Saying "would discard an intrinsic" for all three was the defect the
|
|
61685
|
+
* #1787 review round removed; do not re-collapse it.
|
|
61686
|
+
*/
|
|
61687
|
+
function classifyOverlayCurrentValue(current) {
|
|
61688
|
+
if (current === void 0) return "absent";
|
|
61689
|
+
if (current === null) return "literal";
|
|
61690
|
+
if (Array.isArray(current)) {
|
|
61691
|
+
if (current.length === 0) return "unrepresentable";
|
|
61692
|
+
return current.every((e) => typeof e === "string" || typeof e === "number" || typeof e === "boolean") ? "literal" : "unrepresentable";
|
|
61693
|
+
}
|
|
61694
|
+
if (typeof current === "object") return "intrinsic";
|
|
61695
|
+
return "literal";
|
|
61696
|
+
}
|
|
61697
|
+
/**
|
|
61698
|
+
* Human-readable shape of an overlay value, for the refusal error and the
|
|
61699
|
+
* rewrite warning. Bounded: the value is user-supplied and lands in an error
|
|
61700
|
+
* message that a failed export persists, so a large list is summarized rather
|
|
61701
|
+
* than serialized whole.
|
|
61702
|
+
*/
|
|
61703
|
+
function describeOverlayValueShape(current) {
|
|
61704
|
+
if (Array.isArray(current)) {
|
|
61705
|
+
if (current.length === 0) return "an empty list";
|
|
61706
|
+
const preview = JSON.stringify(current);
|
|
61707
|
+
const shown = preview.length > 120 ? `${preview.slice(0, 120)}…` : preview;
|
|
61708
|
+
return `a ${current.length}-element list (${shown})`;
|
|
61709
|
+
}
|
|
61710
|
+
if (current === null) return "an explicit null";
|
|
61711
|
+
return `a ${typeof current} (${JSON.stringify(current)})`;
|
|
61712
|
+
}
|
|
61713
|
+
/**
|
|
61326
61714
|
* Build the phase-2 UPDATE template: full synth template with the same
|
|
61327
61715
|
* conditional overlay applied to every phase-1 import. Phase-1 and
|
|
61328
61716
|
* phase-2 must stay symmetric — when `overlayResourceIdentifierOnProperties`
|
|
@@ -61357,7 +61745,7 @@ function applyImportOverlayForPhase2(template, phase1Imports) {
|
|
|
61357
61745
|
const resourcesMap = resources;
|
|
61358
61746
|
for (const entry of phase1Imports) {
|
|
61359
61747
|
const r = resourcesMap[entry.logicalId];
|
|
61360
|
-
if (r !== void 0) resourcesMap[entry.logicalId] = overlayResourceIdentifierOnProperties(r, entry);
|
|
61748
|
+
if (r !== void 0) resourcesMap[entry.logicalId] = overlayResourceIdentifierOnProperties(r, entry, false);
|
|
61361
61749
|
}
|
|
61362
61750
|
return result;
|
|
61363
61751
|
}
|
|
@@ -62029,8 +62417,8 @@ async function runPerStackImportLoop(args) {
|
|
|
62029
62417
|
skipImportSupportPreflight: options.skipImportSupportPreflight === true
|
|
62030
62418
|
});
|
|
62031
62419
|
if (plan.blocked.length > 0) {
|
|
62032
|
-
const lines = plan.blocked
|
|
62033
|
-
throw new Error(`Stack '${meta.cdkdStackName}' has ${plan.blocked.
|
|
62420
|
+
const lines = groupBlockedReasons(plan.blocked);
|
|
62421
|
+
throw new Error(`Stack '${meta.cdkdStackName}' has ${new Set(plan.blocked.map((b) => b.logicalId)).size} resource(s) that block migration:\n${lines.join("\n")}`);
|
|
62034
62422
|
}
|
|
62035
62423
|
perStackPlans.push({
|
|
62036
62424
|
cdkdName: meta.cdkdStackName,
|
|
@@ -62147,7 +62535,7 @@ async function runPerStackImportLoop(args) {
|
|
|
62147
62535
|
}
|
|
62148
62536
|
const rewrittenNestedRows = /* @__PURE__ */ new Map();
|
|
62149
62537
|
if (plan.nestedStackRows.length > 0) {
|
|
62150
|
-
const phase1BTemplate = filterTemplateForImport(plan.template, plan.phase1Imports);
|
|
62538
|
+
const phase1BTemplate = filterTemplateForImport(plan.template, plan.phase1Imports, false);
|
|
62151
62539
|
injectDeletionPolicyForImport(phase1BTemplate);
|
|
62152
62540
|
const phase1BResources = [];
|
|
62153
62541
|
for (const row of plan.nestedStackRows) {
|
|
@@ -76618,7 +77006,7 @@ function createMigrateCommand() {
|
|
|
76618
77006
|
*/
|
|
76619
77007
|
function buildProgram() {
|
|
76620
77008
|
const program = new Command();
|
|
76621
|
-
program.name("cdkd").description("CDK Direct - Deploy AWS CDK apps directly via SDK/Cloud Control API").version("0.283.
|
|
77009
|
+
program.name("cdkd").description("CDK Direct - Deploy AWS CDK apps directly via SDK/Cloud Control API").version("0.283.5");
|
|
76622
77010
|
program.hook("preAction", (_thisCommand, actionCommand) => {
|
|
76623
77011
|
const { profile } = actionCommand.optsWithGlobals();
|
|
76624
77012
|
if (profile !== void 0) process.env["AWS_PROFILE"] = profile;
|