@go-to-k/cdkd 0.281.12 → 0.281.14
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-JZvPyCEp.js → asg-provider-C1WbHit9.js} +2 -2
- package/dist/{asg-provider-JZvPyCEp.js.map → asg-provider-C1WbHit9.js.map} +1 -1
- package/dist/cli.js +89 -57
- package/dist/cli.js.map +1 -1
- package/dist/{deploy-engine-CPDPlPQN.js → deploy-engine-3BZtmJqZ.js} +322 -51
- package/dist/deploy-engine-3BZtmJqZ.js.map +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/package.json +1 -1
- package/dist/deploy-engine-CPDPlPQN.js.map +0 -1
|
@@ -216,6 +216,33 @@ function formatDuration(ms) {
|
|
|
216
216
|
return minutes === 0 ? `${hours}h` : `${hours}h${minutes}m`;
|
|
217
217
|
}
|
|
218
218
|
/**
|
|
219
|
+
* A DELIBERATE refusal to resolve an intrinsic function, as opposed to a
|
|
220
|
+
* "the referenced thing does not exist" miss (issue
|
|
221
|
+
* [#1740](https://github.com/go-to-k/cdkd/issues/1740)).
|
|
222
|
+
*
|
|
223
|
+
* The distinction exists for exactly one consumer: `Fn::Sub`'s variable
|
|
224
|
+
* resolution, which speculatively tries `Ref` and then `Fn::GetAtt` and keeps
|
|
225
|
+
* the raw `${...}` placeholder when neither resolves. That warn-and-keep is the
|
|
226
|
+
* long-standing, deliberate behavior for a genuinely unknown variable — but a
|
|
227
|
+
* bare `catch` around it also swallowed every REFUSAL the resolver raises on
|
|
228
|
+
* purpose (`guardedPhysicalIdFallback`'s ARN / URL shape hard-fail, the
|
|
229
|
+
* `--strict-getatt` rejection, `rejectPlaceholderArnAttribute`), so a template
|
|
230
|
+
* that hard-fails when the reference sits in a resource property silently
|
|
231
|
+
* degraded to shipping a literal `${Resource.Attribute}` to AWS when the
|
|
232
|
+
* IDENTICAL reference was written inside an `Fn::Sub`.
|
|
233
|
+
*
|
|
234
|
+
* Throwing this class rather than a bare `Error` is what lets that catch
|
|
235
|
+
* re-raise a refusal (carrying its own message and remedy) while leaving the
|
|
236
|
+
* not-found path on warn-and-keep. Nothing else branches on it.
|
|
237
|
+
*/
|
|
238
|
+
var IntrinsicResolutionRefusalError = class IntrinsicResolutionRefusalError extends CdkdError {
|
|
239
|
+
constructor(message, cause) {
|
|
240
|
+
super(message, "INTRINSIC_RESOLUTION_REFUSAL", cause);
|
|
241
|
+
this.name = "IntrinsicResolutionRefusalError";
|
|
242
|
+
Object.setPrototypeOf(this, IntrinsicResolutionRefusalError.prototype);
|
|
243
|
+
}
|
|
244
|
+
};
|
|
245
|
+
/**
|
|
219
246
|
* Dependency resolution errors
|
|
220
247
|
*/
|
|
221
248
|
var DependencyError = class DependencyError extends CdkdError {
|
|
@@ -9205,6 +9232,53 @@ async function applyRoleArnIfSet(opts) {
|
|
|
9205
9232
|
}
|
|
9206
9233
|
}
|
|
9207
9234
|
|
|
9235
|
+
//#endregion
|
|
9236
|
+
//#region src/utils/aws-partition.ts
|
|
9237
|
+
/**
|
|
9238
|
+
* AWS partition / URL-suffix derivation, shared across layers.
|
|
9239
|
+
*
|
|
9240
|
+
* Lives in `src/utils/` because it has consumers in two different layers: the
|
|
9241
|
+
* `cdkd local *` command family (which passes a region in once to keep the STS
|
|
9242
|
+
* hop minimal) and the provisioning layer's `AppSyncProvider`, which rebuilds a
|
|
9243
|
+
* child resource's ARN when AWS did not report one.
|
|
9244
|
+
*
|
|
9245
|
+
* It was originally defined in `src/local/ecs-task-resolver.ts`, which
|
|
9246
|
+
* re-exports it so every existing call site is unchanged; a provisioning
|
|
9247
|
+
* provider importing from `src/local/**` would invert the layering.
|
|
9248
|
+
*
|
|
9249
|
+
* NOTE `getAccountInfo().partition`
|
|
9250
|
+
* (`src/deployment/intrinsic-function-resolver.ts`) was hardcoded to `'aws'`
|
|
9251
|
+
* until issue #1730, which made it derive through THIS helper — so the two now
|
|
9252
|
+
* agree and either spelling is correct. Prefer this one where a region is
|
|
9253
|
+
* already in hand, since it needs no STS round trip.
|
|
9254
|
+
*/
|
|
9255
|
+
/**
|
|
9256
|
+
* Derive the AWS partition / URL suffix for an AWS region. Same mapping
|
|
9257
|
+
* CloudFormation applies to `${AWS::Partition}` / `${AWS::URLSuffix}`.
|
|
9258
|
+
*/
|
|
9259
|
+
function derivePartitionAndUrlSuffix(region) {
|
|
9260
|
+
if (region.startsWith("cn-")) return {
|
|
9261
|
+
partition: "aws-cn",
|
|
9262
|
+
urlSuffix: "amazonaws.com.cn"
|
|
9263
|
+
};
|
|
9264
|
+
if (region.startsWith("us-gov-")) return {
|
|
9265
|
+
partition: "aws-us-gov",
|
|
9266
|
+
urlSuffix: "amazonaws.com"
|
|
9267
|
+
};
|
|
9268
|
+
if (region.startsWith("us-iso-")) return {
|
|
9269
|
+
partition: "aws-iso",
|
|
9270
|
+
urlSuffix: "c2s.ic.gov"
|
|
9271
|
+
};
|
|
9272
|
+
if (region.startsWith("us-isob-")) return {
|
|
9273
|
+
partition: "aws-iso-b",
|
|
9274
|
+
urlSuffix: "sc2s.sgov.gov"
|
|
9275
|
+
};
|
|
9276
|
+
return {
|
|
9277
|
+
partition: "aws",
|
|
9278
|
+
urlSuffix: "amazonaws.com"
|
|
9279
|
+
};
|
|
9280
|
+
}
|
|
9281
|
+
|
|
9208
9282
|
//#endregion
|
|
9209
9283
|
//#region src/provisioning/config-shape.ts
|
|
9210
9284
|
/**
|
|
@@ -10522,10 +10596,13 @@ const REF_RETURNS_SEGMENT_AT_INDEX = /* @__PURE__ */ new Map([["AWS::Route53::Re
|
|
|
10522
10596
|
*
|
|
10523
10597
|
* Value: the attribute keys to try, in order.
|
|
10524
10598
|
*
|
|
10525
|
-
* Degradation is deliberate and matches the sibling recoveries
|
|
10526
|
-
*
|
|
10527
|
-
*
|
|
10528
|
-
*
|
|
10599
|
+
* Degradation is deliberate and matches the sibling recoveries: when the
|
|
10600
|
+
* attribute is absent the raw compound id is returned rather than a fabricated
|
|
10601
|
+
* ARN. Since issue #1728 an IMPORTED child records the same attribute set
|
|
10602
|
+
* `create()` does (`AppSyncProvider.childImportAttributes`), so the miss is no
|
|
10603
|
+
* longer the normal case for an adopted resource — it is now reached by a
|
|
10604
|
+
* record written before #1681/#1728, or by an import whose ARN build failed and
|
|
10605
|
+
* warned.
|
|
10529
10606
|
*/
|
|
10530
10607
|
const REF_RETURNS_ARN_FROM_STATE = /* @__PURE__ */ new Map([
|
|
10531
10608
|
["AWS::AppSync::ApiKey", ["Arn"]],
|
|
@@ -10709,41 +10786,103 @@ const cachedDynamicReferences = {};
|
|
|
10709
10786
|
*/
|
|
10710
10787
|
const cachedEc2InstanceAttributes = {};
|
|
10711
10788
|
/**
|
|
10789
|
+
* Re-derive the partition for a region the caller overrode (issue #1730).
|
|
10790
|
+
*
|
|
10791
|
+
* `partition` is a FUNCTION of `region`, so handing back a cached entry with a
|
|
10792
|
+
* different region than the one its partition was derived from would produce
|
|
10793
|
+
* `arn:aws:...:cn-north-1:...` for a `cn-` override. Every return path that
|
|
10794
|
+
* swaps the region goes through here.
|
|
10795
|
+
*/
|
|
10796
|
+
function withOverrideRegion(info, region) {
|
|
10797
|
+
return {
|
|
10798
|
+
...info,
|
|
10799
|
+
region,
|
|
10800
|
+
partition: derivePartitionAndUrlSuffix(region).partition
|
|
10801
|
+
};
|
|
10802
|
+
}
|
|
10803
|
+
/**
|
|
10804
|
+
* How long a FABRICATED answer is reused before STS is retried (issue #1730,
|
|
10805
|
+
* PR review). Deliberately not the success path's forever-cache — the whole
|
|
10806
|
+
* point is that a transient blip must not poison the run — but not zero either:
|
|
10807
|
+
* `getAccountInfo` is on the path of EVERY `Fn::GetAtt` and every
|
|
10808
|
+
* `AWS::AccountId` / `AWS::Partition` / `AWS::StackId` pseudo-parameter, so an
|
|
10809
|
+
* uncached failure re-issues `GetCallerIdentity` (with the SDK's own 3-attempt
|
|
10810
|
+
* retry + backoff) dozens of times per stack and prints one warning each. This
|
|
10811
|
+
* window collapses a burst into one call while still letting a later phase of
|
|
10812
|
+
* the same deploy heal.
|
|
10813
|
+
*/
|
|
10814
|
+
const FABRICATED_ACCOUNT_INFO_TTL_MS = 1e4;
|
|
10815
|
+
/** Test seam for {@link FABRICATED_ACCOUNT_INFO_TTL_MS} expiry. */
|
|
10816
|
+
const accountInfoClock = { now: () => Date.now() };
|
|
10817
|
+
let fabricatedAccountInfo = null;
|
|
10818
|
+
/**
|
|
10819
|
+
* The single in-flight lookup, so N concurrent callers share ONE round trip.
|
|
10820
|
+
*
|
|
10821
|
+
* The TTL above collapses SEQUENTIAL callers; this collapses PARALLEL ones
|
|
10822
|
+
* (PR review). `cdkd deploy --concurrency 10` resolves ten resources' intrinsics
|
|
10823
|
+
* at once, so without it an STS outage costs ten `GetCallerIdentity` calls —
|
|
10824
|
+
* each with the SDK's own 3-attempt retry — and ten identical warnings per
|
|
10825
|
+
* window. Cleared in a `finally` so a failure cannot wedge it.
|
|
10826
|
+
*/
|
|
10827
|
+
let accountInfoInFlight = null;
|
|
10828
|
+
/**
|
|
10712
10829
|
* Get AWS account information from STS
|
|
10713
10830
|
*/
|
|
10714
10831
|
async function getAccountInfo(overrideRegion) {
|
|
10715
|
-
|
|
10716
|
-
|
|
10717
|
-
|
|
10718
|
-
|
|
10719
|
-
|
|
10720
|
-
|
|
10832
|
+
const forRegion = (info) => overrideRegion && overrideRegion !== info.region ? withOverrideRegion(info, overrideRegion) : info;
|
|
10833
|
+
if (cachedAccountInfo) return forRegion(cachedAccountInfo);
|
|
10834
|
+
if (fabricatedAccountInfo && accountInfoClock.now() < fabricatedAccountInfo.expiresAt) return forRegion(fabricatedAccountInfo.info);
|
|
10835
|
+
if (accountInfoInFlight) return forRegion(await accountInfoInFlight);
|
|
10836
|
+
accountInfoInFlight = resolveAccountInfo(overrideRegion);
|
|
10837
|
+
try {
|
|
10838
|
+
return forRegion(await accountInfoInFlight);
|
|
10839
|
+
} finally {
|
|
10840
|
+
accountInfoInFlight = null;
|
|
10721
10841
|
}
|
|
10842
|
+
}
|
|
10843
|
+
async function resolveAccountInfo(overrideRegion) {
|
|
10722
10844
|
const logger = getLogger().child("IntrinsicFunctionResolver");
|
|
10723
10845
|
const stsClient = getAwsClients().sts;
|
|
10724
10846
|
try {
|
|
10725
|
-
const
|
|
10847
|
+
const response = await stsClient.send(new GetCallerIdentityCommand({}));
|
|
10848
|
+
const accountId = response.Account || "123456789012";
|
|
10726
10849
|
const region = overrideRegion || process.env["AWS_REGION"] || "us-east-1";
|
|
10727
|
-
const partition =
|
|
10728
|
-
|
|
10850
|
+
const partition = derivePartitionAndUrlSuffix(region).partition;
|
|
10851
|
+
const resolved = {
|
|
10729
10852
|
accountId,
|
|
10730
10853
|
region,
|
|
10731
|
-
partition
|
|
10854
|
+
partition,
|
|
10855
|
+
...response.Account ? {} : { fabricated: true }
|
|
10732
10856
|
};
|
|
10733
|
-
|
|
10734
|
-
|
|
10735
|
-
|
|
10736
|
-
region: overrideRegion
|
|
10857
|
+
if (resolved.fabricated) fabricatedAccountInfo = {
|
|
10858
|
+
info: resolved,
|
|
10859
|
+
expiresAt: accountInfoClock.now() + FABRICATED_ACCOUNT_INFO_TTL_MS
|
|
10737
10860
|
};
|
|
10738
|
-
|
|
10861
|
+
else {
|
|
10862
|
+
cachedAccountInfo = resolved;
|
|
10863
|
+
fabricatedAccountInfo = null;
|
|
10864
|
+
}
|
|
10865
|
+
logger.debug(`Retrieved AWS account info: ${accountId}, ${region}, ${partition}`);
|
|
10866
|
+
return resolved;
|
|
10739
10867
|
} catch (error) {
|
|
10740
10868
|
logger.warn(`Failed to get AWS account info from STS: ${error instanceof Error ? error.message : String(error)}, using defaults`);
|
|
10741
|
-
|
|
10869
|
+
const region = overrideRegion || process.env["AWS_REGION"] || "us-east-1";
|
|
10870
|
+
const fallback = {
|
|
10742
10871
|
accountId: process.env["AWS_ACCOUNT_ID"] || "123456789012",
|
|
10743
|
-
region
|
|
10744
|
-
partition:
|
|
10872
|
+
region,
|
|
10873
|
+
partition: derivePartitionAndUrlSuffix(region).partition,
|
|
10874
|
+
...process.env["AWS_ACCOUNT_ID"] ? {} : { fabricated: true }
|
|
10745
10875
|
};
|
|
10746
|
-
|
|
10876
|
+
if (fallback.fabricated) {
|
|
10877
|
+
if (!cachedAccountInfo) fabricatedAccountInfo = {
|
|
10878
|
+
info: fallback,
|
|
10879
|
+
expiresAt: accountInfoClock.now() + FABRICATED_ACCOUNT_INFO_TTL_MS
|
|
10880
|
+
};
|
|
10881
|
+
} else {
|
|
10882
|
+
cachedAccountInfo = fallback;
|
|
10883
|
+
fabricatedAccountInfo = null;
|
|
10884
|
+
}
|
|
10885
|
+
return fallback;
|
|
10747
10886
|
}
|
|
10748
10887
|
}
|
|
10749
10888
|
/**
|
|
@@ -11111,6 +11250,7 @@ var IntrinsicFunctionResolver = class {
|
|
|
11111
11250
|
if (!(resource.resourceType === "AWS::EC2::VPC" && attributeName === "Ipv6CidrBlocks") && resource.attributes !== void 0) {
|
|
11112
11251
|
const flatValue = resource.attributes[attributeName];
|
|
11113
11252
|
if (flatValue !== void 0) {
|
|
11253
|
+
this.rejectPlaceholderArnAttribute(resource, attributeName, flatValue, logicalId);
|
|
11114
11254
|
this.logger.debug(`Resolved Fn::GetAtt from attributes: ${logicalId}.${attributeName} -> ${stringifyAttributeForLog(attributeName, flatValue)}`);
|
|
11115
11255
|
return flatValue;
|
|
11116
11256
|
}
|
|
@@ -11128,19 +11268,103 @@ var IntrinsicFunctionResolver = class {
|
|
|
11128
11268
|
}
|
|
11129
11269
|
}
|
|
11130
11270
|
}
|
|
11131
|
-
const value = await this.
|
|
11271
|
+
const value = await this.constructGuardedAttribute(resource, attributeName, context, logicalId);
|
|
11132
11272
|
this.logger.debug(`Resolved Fn::GetAtt: ${logicalId}.${attributeName} -> ${stringifyAttributeForLog(attributeName, value)}`);
|
|
11133
11273
|
return value;
|
|
11134
11274
|
}
|
|
11135
11275
|
/**
|
|
11136
|
-
*
|
|
11276
|
+
* Refuse a pre-#1681 PLACEHOLDER ARN served from the cached attribute map
|
|
11277
|
+
* (issue #1729) — the `Fn::GetAtt` half of the guard
|
|
11278
|
+
* {@link cfnRefValueFromPhysicalId} applies to `Ref`.
|
|
11279
|
+
*
|
|
11280
|
+
* `Ref` and `Fn::GetAtt` read the SAME recorded attribute, and #1681 treated
|
|
11281
|
+
* only the `Ref` side: for an `AWS::AppSync::*` child created by a pre-#1681
|
|
11282
|
+
* binary, `{"Fn::GetAtt": ["MyDataSource", "DataSourceArn"]}` still resolved
|
|
11283
|
+
* to `arn:aws:appsync:*:*:apis/.../datasources/...` — structurally valid,
|
|
11284
|
+
* unusable, and indistinguishable downstream from a real ARN.
|
|
11285
|
+
*
|
|
11286
|
+
* Scoped to the {@link REF_RETURNS_ARN_FROM_STATE} types AND their declared
|
|
11287
|
+
* ARN attribute names, the narrowest form of the fix: a wildcard-bearing ARN
|
|
11288
|
+
* is only KNOWN to be a placeholder for these three attributes, and some
|
|
11289
|
+
* other type could legitimately cache an ARN-shaped string carrying a `*` in
|
|
11290
|
+
* a position {@link isPlaceholderArn} inspects. Every other attribute of
|
|
11291
|
+
* these same types (`AWS::AppSync::ApiKey`'s `ApiKey`,
|
|
11292
|
+
* `AWS::AppSync::DataSource`'s `Name`) is untouched.
|
|
11293
|
+
*
|
|
11294
|
+
* THROWS rather than degrading, which is where it diverges from the `Ref`
|
|
11295
|
+
* half, and deliberately: `Ref`'s fallback is the raw compound id, whereas
|
|
11296
|
+
* the value here is requested under an ARN-suffixed attribute name, so
|
|
11297
|
+
* handing back a non-ARN would be exactly the shape mismatch
|
|
11298
|
+
* {@link guardedPhysicalIdFallback} already hard-fails on (the #1103 class —
|
|
11299
|
+
* a green deploy that ships a wrong value into stack Outputs / an IAM
|
|
11300
|
+
* policy). A resource in this state has no correct value to serve, so the
|
|
11301
|
+
* honest answer is to say so and name the remedy: the record heals on the
|
|
11302
|
+
* resource's next in-place update (#1727).
|
|
11303
|
+
*/
|
|
11304
|
+
rejectPlaceholderArnAttribute(resource, attributeName, value, logicalId) {
|
|
11305
|
+
if (!REF_RETURNS_ARN_FROM_STATE.get(resource.resourceType)?.includes(attributeName)) return;
|
|
11306
|
+
if (typeof value !== "string" || !isPlaceholderArn(value)) return;
|
|
11307
|
+
throw new IntrinsicResolutionRefusalError(`Cannot resolve Fn::GetAtt [${logicalId}, ${attributeName}] for ${resource.resourceType}: the recorded value "${value}" is a placeholder written by a cdkd version older than issue #1681 — its region and account fields are literal wildcards, so it is not a usable ARN. Deploy the stack again so the resource's next update heals the record (cdkd now records the real ARN), or re-import the resource.`);
|
|
11308
|
+
}
|
|
11309
|
+
/**
|
|
11310
|
+
* Construct resource attribute value based on resource type, refusing to
|
|
11311
|
+
* SERVE one built from a fabricated account id (issue #1730).
|
|
11312
|
+
*
|
|
11313
|
+
* Thin wrapper over {@link constructAttribute}. ~30 branches there
|
|
11314
|
+
* build `arn:<partition>:<svc>:<region>:<accountId>:...`, and when the account
|
|
11315
|
+
* id is the hardcoded `123456789012` fallback the result is an ARN naming
|
|
11316
|
+
* SOMEONE ELSE'S account with no wildcard in it — so `isPlaceholderArn` cannot
|
|
11317
|
+
* catch it and every consumer downstream, the state record included, receives
|
|
11318
|
+
* a confidently wrong value. Refusing matches how
|
|
11319
|
+
* {@link guardedPhysicalIdFallback} already treats a knowably-wrong `*Arn`
|
|
11320
|
+
* (the #1103 class): a resource in this state has no correct value to serve.
|
|
11321
|
+
*
|
|
11322
|
+
* The test is on the CONSTRUCTED VALUE, not on the attribute NAME, and that
|
|
11323
|
+
* precision is the whole point: `AWS::S3::Bucket`'s `Arn` is
|
|
11324
|
+
* `arn:aws:s3:::<bucket>` with no account field, so a name-based `*Arn` guard
|
|
11325
|
+
* would refuse a value the fabricated id cannot corrupt. Everything the
|
|
11326
|
+
* account id does not appear in — `DomainName`, `Endpoint`, `WebsiteURL` —
|
|
11327
|
+
* keeps resolving unchanged.
|
|
11328
|
+
*
|
|
11329
|
+
* The match is a BARE substring rather than the colon-delimited `:<id>:` an
|
|
11330
|
+
* ARN uses, because not every account embedding is an ARN field: PR review
|
|
11331
|
+
* caught `AWS::ECR::Repository`'s `RepositoryUri`
|
|
11332
|
+
* (`<accountId>.dkr.ecr.<region>.amazonaws.com/<repo>`), the one such site in
|
|
11333
|
+
* this method, where a colon-delimited test served the fabricated URI and
|
|
11334
|
+
* silently nullified the `CloudControlProvider` omission of the SAME
|
|
11335
|
+
* attribute. The direction is deliberately fail-SAFE: refusing is the honest
|
|
11336
|
+
* answer whenever cdkd cannot confirm the account, so a value that merely
|
|
11337
|
+
* CONTAINS the placeholder digits (a physicalId recorded against the AWS
|
|
11338
|
+
* documentation account) is refused rather than served — and only while STS
|
|
11339
|
+
* is failing, when the deploy has bigger problems.
|
|
11340
|
+
*
|
|
11341
|
+
* NOTE the naming: the per-type construction below KEEPS the name
|
|
11342
|
+
* `constructAttribute` and this guard takes a new one, rather than the other
|
|
11343
|
+
* way round. `scripts/gen-sdk-attr-coverage.ts` collects the set of resource
|
|
11344
|
+
* types `constructAttribute` references to decide which `*Arn` attributes the
|
|
11345
|
+
* resolver can already answer, so renaming that method emptied its walk and
|
|
11346
|
+
* the critic reported fresh `gap`s for CloudTrail Trail / RDS DBCluster /
|
|
11347
|
+
* DBInstance (measured — the first cut of this change did exactly that).
|
|
11348
|
+
*/
|
|
11349
|
+
async constructGuardedAttribute(resource, attributeName, context, logicalId) {
|
|
11350
|
+
const accountInfo = await getAccountInfo(this.resolverRegion);
|
|
11351
|
+
const value = await this.constructAttribute(resource, attributeName, context, logicalId, accountInfo);
|
|
11352
|
+
if (accountInfo.fabricated && typeof value === "string" && value.includes(accountInfo.accountId)) throw new IntrinsicResolutionRefusalError(`Cannot resolve Fn::GetAtt [${logicalId}, ${attributeName}] for ${resource.resourceType}: STS did not report this deploy's account id, so cdkd would build the value from the placeholder account ${accountInfo.accountId} — structurally valid, naming a different account, and indistinguishable downstream from a real one. Fix the AWS credentials (or set AWS_ACCOUNT_ID to this deploy's account) and deploy again.`);
|
|
11353
|
+
return value;
|
|
11354
|
+
}
|
|
11355
|
+
/**
|
|
11356
|
+
* The per-resource-type attribute construction itself.
|
|
11137
11357
|
*
|
|
11138
11358
|
* Many CloudFormation attributes are not returned by Cloud Control API,
|
|
11139
11359
|
* so we need to construct them manually.
|
|
11360
|
+
*
|
|
11361
|
+
* Reached only through {@link constructGuardedAttribute}, which vets the
|
|
11362
|
+
* result. Keep this method's NAME — `scripts/gen-sdk-attr-coverage.ts` reads
|
|
11363
|
+
* the resource types it references.
|
|
11140
11364
|
*/
|
|
11141
|
-
async constructAttribute(resource, attributeName, _context, logicalId) {
|
|
11365
|
+
async constructAttribute(resource, attributeName, _context, logicalId, accountInfo) {
|
|
11142
11366
|
const { resourceType, physicalId } = resource;
|
|
11143
|
-
const { region, accountId, partition } =
|
|
11367
|
+
const { region, accountId, partition } = accountInfo;
|
|
11144
11368
|
if (resourceType === "AWS::DynamoDB::Table" || resourceType === "AWS::DynamoDB::GlobalTable") switch (attributeName) {
|
|
11145
11369
|
case "Arn": return `arn:${partition}:dynamodb:${region}:${accountId}:table/${physicalId}`;
|
|
11146
11370
|
case "StreamArn": return;
|
|
@@ -11325,7 +11549,7 @@ var IntrinsicFunctionResolver = class {
|
|
|
11325
11549
|
}
|
|
11326
11550
|
if (resourceType === "AWS::ECR::Repository") switch (attributeName) {
|
|
11327
11551
|
case "Arn": return `arn:${partition}:ecr:${region}:${accountId}:repository/${physicalId}`;
|
|
11328
|
-
case "RepositoryUri": return `${accountId}.dkr.ecr.${region}.
|
|
11552
|
+
case "RepositoryUri": return `${accountId}.dkr.ecr.${region}.${derivePartitionAndUrlSuffix(region).urlSuffix}/${physicalId}`;
|
|
11329
11553
|
default: return this.guardedPhysicalIdFallback(logicalId, attributeName, resourceType, physicalId);
|
|
11330
11554
|
}
|
|
11331
11555
|
if (resourceType === "AWS::ECS::Cluster") switch (attributeName) {
|
|
@@ -11453,8 +11677,8 @@ var IntrinsicFunctionResolver = class {
|
|
|
11453
11677
|
guardedPhysicalIdFallback(logicalId, attributeName, resourceType, physicalId) {
|
|
11454
11678
|
const expectsArnShape = attributeName.endsWith("Arn") && !physicalId.startsWith("arn:");
|
|
11455
11679
|
const expectsUrlShape = attributeName.endsWith("Url") && !/^https?:\/\//.test(physicalId);
|
|
11456
|
-
if (expectsArnShape || expectsUrlShape) throw new
|
|
11457
|
-
if (this.strictGetAtt) throw new
|
|
11680
|
+
if (expectsArnShape || expectsUrlShape) throw new IntrinsicResolutionRefusalError(`Cannot resolve Fn::GetAtt [${logicalId}, ${attributeName}] for ${resourceType}: attributes are not enriched for this resource type, and the physical ID fallback "${physicalId}" is not ${expectsArnShape ? "an ARN (arn:...)" : "a URL (http(s)://...)"}. CloudFormation would return a different value here, so falling back to the physical ID would silently produce a wrong value (e.g. in stack Outputs). Avoid this Fn::GetAtt, or file an issue at https://github.com/go-to-k/cdkd/issues so cdkd can enrich ${resourceType}.${attributeName}.`);
|
|
11681
|
+
if (this.strictGetAtt) throw new IntrinsicResolutionRefusalError(`Cannot resolve Fn::GetAtt [${logicalId}, ${attributeName}] for ${resourceType}: attributes are not enriched for this resource type, and --strict-getatt rejects the physical ID fallback "${physicalId}" (which may not be the value CloudFormation would return). Drop --strict-getatt to fall back with a warning, avoid this Fn::GetAtt, or file an issue at https://github.com/go-to-k/cdkd/issues so cdkd can enrich ${resourceType}.${attributeName}.`);
|
|
11458
11682
|
this.physicalIdFallbackCount++;
|
|
11459
11683
|
this.logger.warn(`Unknown attribute ${attributeName} for resource type ${resourceType}, returning physical ID`);
|
|
11460
11684
|
return physicalId;
|
|
@@ -11478,6 +11702,17 @@ var IntrinsicFunctionResolver = class {
|
|
|
11478
11702
|
return result;
|
|
11479
11703
|
}
|
|
11480
11704
|
/**
|
|
11705
|
+
* The warning emitted when `Fn::Sub` keeps a `${...}` placeholder verbatim.
|
|
11706
|
+
*
|
|
11707
|
+
* It carries the underlying reason (issue #1740 item 2): the old text
|
|
11708
|
+
* asserted `not found` for EVERY failure, which was the wrong cause whenever
|
|
11709
|
+
* the variable WAS found and its resolution failed for some other reason.
|
|
11710
|
+
* Deliberate refusals no longer reach this path at all — they re-throw.
|
|
11711
|
+
*/
|
|
11712
|
+
subPlaceholderWarning(varName, error) {
|
|
11713
|
+
return `Fn::Sub variable ${varName} could not be resolved (${error instanceof Error ? error.message : String(error)}), keeping placeholder`;
|
|
11714
|
+
}
|
|
11715
|
+
/**
|
|
11481
11716
|
* Resolve Fn::Sub intrinsic function
|
|
11482
11717
|
*
|
|
11483
11718
|
* Fn::Sub supports two forms:
|
|
@@ -11522,16 +11757,18 @@ var IntrinsicFunctionResolver = class {
|
|
|
11522
11757
|
else try {
|
|
11523
11758
|
const value = await this.resolveRef(varNameStr, context);
|
|
11524
11759
|
replacement = String(value);
|
|
11525
|
-
} catch {
|
|
11760
|
+
} catch (refError) {
|
|
11526
11761
|
if (varNameStr.includes(".")) try {
|
|
11527
11762
|
const value = await this.resolveGetAtt(varNameStr, context);
|
|
11528
11763
|
replacement = String(value);
|
|
11529
|
-
} catch {
|
|
11530
|
-
|
|
11764
|
+
} catch (getAttError) {
|
|
11765
|
+
if (getAttError instanceof IntrinsicResolutionRefusalError) throw getAttError;
|
|
11766
|
+
this.logger.warn(this.subPlaceholderWarning(varNameStr, getAttError));
|
|
11531
11767
|
replacement = match[0];
|
|
11532
11768
|
}
|
|
11533
11769
|
else {
|
|
11534
|
-
|
|
11770
|
+
if (refError instanceof IntrinsicResolutionRefusalError) throw refError;
|
|
11771
|
+
this.logger.warn(this.subPlaceholderWarning(varNameStr, refError));
|
|
11535
11772
|
replacement = match[0];
|
|
11536
11773
|
}
|
|
11537
11774
|
}
|
|
@@ -12137,9 +12374,9 @@ var IntrinsicFunctionResolver = class {
|
|
|
12137
12374
|
case "AWS::StackName": return context?.stackName ?? "UnknownStack";
|
|
12138
12375
|
case "AWS::StackId": {
|
|
12139
12376
|
const info = await getAccountInfo(this.resolverRegion);
|
|
12140
|
-
return `arn:
|
|
12377
|
+
return `arn:${info.partition}:cloudformation:${info.region}:${info.accountId}:stack/${context?.stackName ?? "UnknownStack"}/cdkd`;
|
|
12141
12378
|
}
|
|
12142
|
-
case "AWS::URLSuffix": return
|
|
12379
|
+
case "AWS::URLSuffix": return derivePartitionAndUrlSuffix(this.resolverRegion).urlSuffix;
|
|
12143
12380
|
case "AWS::NotificationARNs": return "";
|
|
12144
12381
|
case "AWS::NoValue": return AWS_NO_VALUE;
|
|
12145
12382
|
default: return;
|
|
@@ -13167,7 +13404,7 @@ var CloudControlProvider = class {
|
|
|
13167
13404
|
if (context?.finalSnapshotIdentifier !== void 0) throw new ProvisioningError(`${logicalId} (${resourceType}) requires a final snapshot (DeletionPolicy: Snapshot), but the Cloud Control API delete route has no final-snapshot parameter. Re-run with --skip-final-snapshot after snapshotting manually, or retain the resource.`, resourceType, logicalId, physicalId);
|
|
13168
13405
|
if (context?.removeProtection === true && resourceType === "AWS::AutoScaling::AutoScalingGroup") {
|
|
13169
13406
|
this.logger.debug(`Delegating protected AutoScalingGroup ${logicalId} delete to the SDK ASGProvider (Cloud Control cannot force-delete a protected ASG)`);
|
|
13170
|
-
const { ASGProvider } = await import("./asg-provider-
|
|
13407
|
+
const { ASGProvider } = await import("./asg-provider-C1WbHit9.js").then((n) => n.n);
|
|
13171
13408
|
await new ASGProvider().delete(logicalId, physicalId, resourceType, _properties, context);
|
|
13172
13409
|
return;
|
|
13173
13410
|
}
|
|
@@ -13300,6 +13537,31 @@ var CloudControlProvider = class {
|
|
|
13300
13537
|
}
|
|
13301
13538
|
}
|
|
13302
13539
|
/**
|
|
13540
|
+
* Account info for an ARN / URI this provider SYNTHESIZES and records, or
|
|
13541
|
+
* `undefined` when it must not be built (issue
|
|
13542
|
+
* [#1730](https://github.com/go-to-k/cdkd/issues/1730)).
|
|
13543
|
+
*
|
|
13544
|
+
* `getAccountInfo` falls back to a hardcoded `123456789012` when STS cannot
|
|
13545
|
+
* answer, and an ARN built from it is structurally valid with no wildcard in
|
|
13546
|
+
* any field — so `isPlaceholderArn` (issue #1681) cannot catch it and every
|
|
13547
|
+
* downstream consumer receives a confidently wrong value that is then
|
|
13548
|
+
* RECORDED into state as the resource's `Fn::GetAtt` answer.
|
|
13549
|
+
*
|
|
13550
|
+
* Omitting the attribute is the honest answer and mirrors
|
|
13551
|
+
* `AppSyncProvider.childImportAttributes`: the resolver's own
|
|
13552
|
+
* `guardedPhysicalIdFallback` then hard-fails an `*Arn` read with a message
|
|
13553
|
+
* naming the cause, instead of a green deploy shipping an ARN for someone
|
|
13554
|
+
* else's account, and the record heals on the resource's next update.
|
|
13555
|
+
*/
|
|
13556
|
+
async accountInfoForSynthesizedArn(resourceType, attributeName, physicalId) {
|
|
13557
|
+
const accountInfo = await getAccountInfo();
|
|
13558
|
+
if (accountInfo.fabricated) {
|
|
13559
|
+
this.logger.warn(`Not enriching ${resourceType} ${attributeName} for ${physicalId}: STS did not report this deploy's account id, so the value would be built from a placeholder account and would be indistinguishable from a real one. Fix the credentials (or set AWS_ACCOUNT_ID) and deploy again — the record heals on the next update.`);
|
|
13560
|
+
return;
|
|
13561
|
+
}
|
|
13562
|
+
return accountInfo;
|
|
13563
|
+
}
|
|
13564
|
+
/**
|
|
13303
13565
|
* Enrich resource attributes with computed values
|
|
13304
13566
|
*
|
|
13305
13567
|
* CC API GetResource returns property names that match CloudFormation
|
|
@@ -13378,9 +13640,11 @@ var CloudControlProvider = class {
|
|
|
13378
13640
|
break;
|
|
13379
13641
|
case "AWS::KMS::Key":
|
|
13380
13642
|
if (!enriched["Arn"]) try {
|
|
13381
|
-
const kmsAccountInfo = await
|
|
13382
|
-
|
|
13383
|
-
|
|
13643
|
+
const kmsAccountInfo = await this.accountInfoForSynthesizedArn(resourceType, "Arn", physicalId);
|
|
13644
|
+
if (kmsAccountInfo) {
|
|
13645
|
+
enriched["Arn"] = `arn:${kmsAccountInfo.partition}:kms:${kmsAccountInfo.region}:${kmsAccountInfo.accountId}:key/${physicalId}`;
|
|
13646
|
+
this.logger.debug(`Enriched KMS Key Arn for ${physicalId}: ${String(enriched["Arn"])}`);
|
|
13647
|
+
}
|
|
13384
13648
|
} catch (error) {
|
|
13385
13649
|
this.logger.debug(`Failed to construct KMS Key Arn for ${physicalId}: ${error instanceof Error ? error.message : String(error)}`);
|
|
13386
13650
|
}
|
|
@@ -13394,15 +13658,20 @@ var CloudControlProvider = class {
|
|
|
13394
13658
|
break;
|
|
13395
13659
|
case "AWS::ECR::Repository":
|
|
13396
13660
|
if (!enriched["Arn"]) try {
|
|
13397
|
-
const ecrAccountInfo = await
|
|
13398
|
-
|
|
13399
|
-
|
|
13661
|
+
const ecrAccountInfo = await this.accountInfoForSynthesizedArn(resourceType, "Arn", physicalId);
|
|
13662
|
+
if (ecrAccountInfo) {
|
|
13663
|
+
enriched["Arn"] = `arn:${ecrAccountInfo.partition}:ecr:${ecrAccountInfo.region}:${ecrAccountInfo.accountId}:repository/${physicalId}`;
|
|
13664
|
+
this.logger.debug(`Enriched ECR Repository Arn for ${physicalId}: ${String(enriched["Arn"])}`);
|
|
13665
|
+
}
|
|
13400
13666
|
} catch (error) {
|
|
13401
13667
|
this.logger.debug(`Failed to construct ECR Repository Arn: ${error instanceof Error ? error.message : String(error)}`);
|
|
13402
13668
|
}
|
|
13403
13669
|
if (!enriched["RepositoryUri"]) try {
|
|
13404
|
-
const ecrAccountInfo = await
|
|
13405
|
-
|
|
13670
|
+
const ecrAccountInfo = await this.accountInfoForSynthesizedArn(resourceType, "RepositoryUri", physicalId);
|
|
13671
|
+
if (ecrAccountInfo) {
|
|
13672
|
+
const { urlSuffix } = derivePartitionAndUrlSuffix(ecrAccountInfo.region);
|
|
13673
|
+
enriched["RepositoryUri"] = `${ecrAccountInfo.accountId}.dkr.ecr.${ecrAccountInfo.region}.${urlSuffix}/${physicalId}`;
|
|
13674
|
+
}
|
|
13406
13675
|
} catch {}
|
|
13407
13676
|
break;
|
|
13408
13677
|
case "AWS::EC2::EIP":
|
|
@@ -13423,9 +13692,11 @@ var CloudControlProvider = class {
|
|
|
13423
13692
|
break;
|
|
13424
13693
|
case "AWS::Kinesis::Stream":
|
|
13425
13694
|
if (!enriched["Arn"]) try {
|
|
13426
|
-
const kinesisAccountInfo = await
|
|
13427
|
-
|
|
13428
|
-
|
|
13695
|
+
const kinesisAccountInfo = await this.accountInfoForSynthesizedArn(resourceType, "Arn", physicalId);
|
|
13696
|
+
if (kinesisAccountInfo) {
|
|
13697
|
+
enriched["Arn"] = `arn:${kinesisAccountInfo.partition}:kinesis:${kinesisAccountInfo.region}:${kinesisAccountInfo.accountId}:stream/${physicalId}`;
|
|
13698
|
+
this.logger.debug(`Enriched Kinesis Stream Arn for ${physicalId}: ${String(enriched["Arn"])}`);
|
|
13699
|
+
}
|
|
13429
13700
|
} catch (error) {
|
|
13430
13701
|
this.logger.debug(`Failed to construct Kinesis Stream Arn for ${physicalId}: ${error instanceof Error ? error.message : String(error)}`);
|
|
13431
13702
|
}
|
|
@@ -20041,7 +20312,7 @@ const FLUSH_INTERVAL_MS = 2e3;
|
|
|
20041
20312
|
const FLUSH_EVENT_THRESHOLD = 50;
|
|
20042
20313
|
/** Build-time cdkd version, with a dev fallback for non-built contexts. */
|
|
20043
20314
|
function getCdkdVersion() {
|
|
20044
|
-
return "0.281.
|
|
20315
|
+
return "0.281.14";
|
|
20045
20316
|
}
|
|
20046
20317
|
/**
|
|
20047
20318
|
* Generate a time-sortable unique run id, e.g.
|
|
@@ -22209,5 +22480,5 @@ var DeployEngine = class {
|
|
|
22209
22480
|
};
|
|
22210
22481
|
|
|
22211
22482
|
//#endregion
|
|
22212
|
-
export { replayWarn as $,
|
|
22213
|
-
//# sourceMappingURL=deploy-engine-
|
|
22483
|
+
export { replayWarn as $, findLargeInlineResources as $t, green as A, isCdkdError as An, buildDockerImage as At, slowCcOperationTimeoutMs as B, getLegacyStateBucketName as Bt, MULTI_REGION_RECREATE_BLOCKED_TYPES as C, ResourceTimeoutError as Cn, AssetModeResolver as Ct, bold as D, StateError as Dn, parseBootstrapMarker as Dt, formatResourceLine as E, StackTerminationProtectionError as En, getBootstrapMarkerKey as Et, clearOnUpdateRemoval as F, AssetManifestLoader as Ft, getAccountInfo as G, resolveStateBucketWithDefault as Gt, isTerminationProtectionPropagationError as H, resolveAutoAssetStorage as Ht, ProviderRegistry as I, getDockerImageBySourceHash as It, normalizeAwsTagsToCfn as J, stateBucketExistenceConfirmed as Jt, refStateLookupFromResource as K, resolveStateBucketWithDefaultAndSource as Kt, findActionableSilentDrops as L, Synthesizer as Lt, yellow as M, withErrorHandling as Mn, getDockerCmd as Mt, IAMRoleProvider as N, __exportAll as Nn, runDockerForeground as Nt, cyan as O, SynthesisError as On, validateAssetBucketName as Ot, collectInlinePolicyNamesManagedBySiblings as P, runDockerStreaming as Pt, readConfigString as Q, MIGRATE_TMP_PREFIX as Qt, findSilentDropProperties as R, synthesisStatusMessage as Rt, extractDeploymentEventError as S, ProvisioningError as Sn, rewriteTemplateAssetReferences as St, renderStatefulReason as T, StackHasActiveImportsError as Tn, ensureAssetStorage as Tt, IntrinsicFunctionResolver as U, resolveCaptureObservedState as Ut, disableInstanceApiTermination as V, resolveApp as Vt, cfnRefValueFromPhysicalId as W, resolveSkipPrefix as Wt, assertRegionMatch as X, CFN_TEMPLATE_BODY_LIMIT as Xt, resolveExplicitPhysicalId as Y, warnDeprecatedNoPrefixCliFlag as Yt, configStringRefusal as Z, CFN_TEMPLATE_URL_LIMIT as Zt, createPreDeleteFinalSnapshot as _, LocalStartServiceError as _n, stringifyValue as _t, DeploymentEventsStore as a, resolveBucketRegion as an, DiffCalculator as at, unsupportedFinalSnapshotError as b, NestedStackChildDirectDestroyError as bn, createAssetRedirectResolver as bt, replayFailedOperations as c, resetAwsClients as cn, isRetryableTransientError as ct, IMPLICIT_DELETE_DEPENDENCIES as d, CdkdError as dn, TemplateParser as dt, uploadCfnTemplate as en, requireConfigArray as et, computeImplicitDeleteEdges as f, ConfigError as fn, LockManager as ft, ccRoutedFinalSnapshotError as g, LocalMigrateError as gn, AssetPublisher as gt, buildFinalSnapshotIdentifier as h, LocalInvokeBuildError as hn, shouldRetainResource as ht, DeploymentEventsReader as i, clearBucketRegionCache as in, applyRoleArnIfSet as it, red as j, normalizeAwsError as jn, formatDockerLoginError as jt, gray as k, formatError as kn, validateContainerRepoName as kt, replayRollback as l, setAwsClients as ln, isThrottlingError as lt, PRE_DELETE_SNAPSHOT_TYPES as m, DeployCancelledError as mn, rebuildClientForBucketRegion as mt, DEFAULT_RESOURCE_WARN_AFTER_MS as n, AssemblyReader as nn, requireConfigString as nt, planFailedOps as o, AwsClients as on, describeTypeWithThrottleRetry as ot, ATOMIC_FINAL_SNAPSHOT_TYPES as p, DependencyError as pn, S3StateBackend as pt, WAFv2WebACLProvider as q, resolveUseCdkBootstrapAssets as qt, DeployEngine as r, processStackMessages as rn, derivePartitionAndUrlSuffix as rt, planRollback as s, getAwsClients as sn, withRetry as st, DEFAULT_RESOURCE_TIMEOUT_MS as t, expectedOwnerParam as tn, requireConfigObject as tt, withResourceDeadline as u, AssetError as un, DagBuilder as ut, isFinalSnapshotError as v, LockError as vn, WorkGraph as vt, isStatefulRecreateTargetSync as w, ResourceUpdateNotSupportedError as wn, BOOTSTRAP_MARKER_PREFIX as wt, makeCanonicalizePropertiesFn as x, PartialFailureError as xn, loadPublishableAssetManifest as xt, refusesFinalSnapshot as y, MissingCdkCliError as yn, buildAssetRedirectMap as yt, CloudControlProvider as z, getDefaultStateBucketName as zt };
|
|
22484
|
+
//# sourceMappingURL=deploy-engine-3BZtmJqZ.js.map
|