@go-to-k/cdkd 0.284.83 → 0.284.85
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-B-KONBYk.js → asg-provider-CnpxfVci.js} +2 -2
- package/dist/{asg-provider-B-KONBYk.js.map → asg-provider-CnpxfVci.js.map} +1 -1
- package/dist/cli.js +2 -2
- package/dist/{deploy-engine-BES1Z20a.js → deploy-engine-BoDdOfCO.js} +262 -25
- package/dist/deploy-engine-BoDdOfCO.js.map +1 -0
- package/dist/index.d.ts +3 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/{program-BTxarL5l.js → program--txqxUQg.js} +7 -6
- package/dist/{program-BTxarL5l.js.map → program--txqxUQg.js.map} +1 -1
- package/dist/{version-DI03miJ9.js → version-cNxTfzBH.js} +2 -2
- package/dist/{version-DI03miJ9.js.map → version-cNxTfzBH.js.map} +1 -1
- package/package.json +1 -1
- package/dist/deploy-engine-BES1Z20a.js.map +0 -1
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { d as generateResourceName, f as generateResourceNameWithFallback, g as withStackName, m as looksLikeCdkdGeneratedName, n as getLogger, o as getLiveRenderer, p as getCurrentStackName, u as applyDefaultNameForFallback } from "./logger-C9-E73FI.js";
|
|
2
|
-
import { t as getCdkdVersion } from "./version-
|
|
2
|
+
import { t as getCdkdVersion } from "./version-cNxTfzBH.js";
|
|
3
3
|
import { AsyncLocalStorage } from "node:async_hooks";
|
|
4
4
|
import { randomUUID } from "node:crypto";
|
|
5
5
|
import { CreateBucketCommand, DeleteObjectCommand, DeleteObjectsCommand, GetBucketLocationCommand, GetObjectCommand, HeadBucketCommand, HeadObjectCommand, ListObjectVersionsCommand, ListObjectsV2Command, NoSuchKey, PutBucketEncryptionCommand, PutBucketPolicyCommand, PutObjectCommand, PutPublicAccessBlockCommand, S3Client, S3ServiceException } from "@aws-sdk/client-s3";
|
|
@@ -4257,6 +4257,85 @@ function isListParameterType(type) {
|
|
|
4257
4257
|
if (type === "CommaDelimitedList") return true;
|
|
4258
4258
|
return type.length > 6 && type.startsWith("List<") && type.endsWith(">");
|
|
4259
4259
|
}
|
|
4260
|
+
/**
|
|
4261
|
+
* The literal prefix of the Systems Manager parameter form. Declared ABOVE the
|
|
4262
|
+
* docblock below so that docblock attaches to {@link ssmResolvedValueType}
|
|
4263
|
+
* rather than to this constant.
|
|
4264
|
+
*/
|
|
4265
|
+
const SSM_PARAMETER_VALUE_PREFIX = "AWS::SSM::Parameter::Value<";
|
|
4266
|
+
/**
|
|
4267
|
+
* ONE definition of "peel the inner shape out of
|
|
4268
|
+
* `AWS::SSM::Parameter::Value<...>`", returning `undefined` when `type` is not
|
|
4269
|
+
* that form.
|
|
4270
|
+
*
|
|
4271
|
+
* It lives beside {@link isListParameterType} for the reason that predicate
|
|
4272
|
+
* exists at all: this file was created by issue
|
|
4273
|
+
* [#2347](https://github.com/go-to-k/cdkd/issues/2347) after cdkd was found
|
|
4274
|
+
* holding TWO answers to one type question in these same two modules, and
|
|
4275
|
+
* issue [#2367](https://github.com/go-to-k/cdkd/issues/2367) then wrote a
|
|
4276
|
+
* SECOND peel in `src/deployment/intrinsic-function-resolver.ts` next to the
|
|
4277
|
+
* one already in `src/synthesis/macro-expander.ts` -- reproducing the exact
|
|
4278
|
+
* shape #2347 had just deleted, one question with two spellings in one file
|
|
4279
|
+
* pair. Both now call this.
|
|
4280
|
+
*
|
|
4281
|
+
* ## The two callers, and the one deliberate difference between them
|
|
4282
|
+
*
|
|
4283
|
+
* The peel is shared; the handling of a MALFORMED spelling is not, and that is
|
|
4284
|
+
* a call-site policy rather than a second answer:
|
|
4285
|
+
*
|
|
4286
|
+
* - `resolveParameters` (deployment) treats `undefined` as "do not coerce" and
|
|
4287
|
+
* keeps the resolved string verbatim -- the safe direction on a path that
|
|
4288
|
+
* writes state.
|
|
4289
|
+
* - `stringifyParamDefault` (synthesis) keeps its own pre-existing behaviour
|
|
4290
|
+
* of emitting the SCALAR placeholder for anything carrying the prefix,
|
|
4291
|
+
* including a malformed one, rather than falling through to its generic
|
|
4292
|
+
* warn + `PARAMETER_PLACEHOLDER`. Routing a malformed spelling to that
|
|
4293
|
+
* fallback would change the emitted placeholder text (`placeholder` ->
|
|
4294
|
+
* `cdkd-macro-expand-placeholder`) and add a warn line, which is a
|
|
4295
|
+
* behaviour change unrelated to #2367.
|
|
4296
|
+
*
|
|
4297
|
+
* The STRICTNESS here is the stricter of the two originals: a closing `>` is
|
|
4298
|
+
* required and the inner shape must be non-empty, so `Value<` and `Value<>`
|
|
4299
|
+
* peel to `undefined`. The synthesis site never required either, but it also
|
|
4300
|
+
* never distinguished the cases -- `''` is not list-shaped, so it took the
|
|
4301
|
+
* scalar arm, which is what its `undefined` branch now does explicitly.
|
|
4302
|
+
*
|
|
4303
|
+
* ## WHAT THE SUPPLIED VALUE IS -- AN OPEN DISAGREEMENT INSIDE THIS REPO
|
|
4304
|
+
*
|
|
4305
|
+
* This function answers only "what shape does `Value<...>` WRAP". It
|
|
4306
|
+
* deliberately does NOT settle what the value SUPPLIED for such a parameter
|
|
4307
|
+
* means, because cdkd currently holds two incompatible readings and this
|
|
4308
|
+
* function's callers do not need the answer:
|
|
4309
|
+
*
|
|
4310
|
+
* - **Read from the AWS documentation** (`cloudformation-supplied-parameter-types.html`,
|
|
4311
|
+
* 2026-08-29): the supplied value is ONE Parameter Store key, phrased in the
|
|
4312
|
+
* singular throughout ("you must specify a Parameter Store key", "you must
|
|
4313
|
+
* provide the parameter name"), with `Value<List<String>>` /
|
|
4314
|
+
* `Value<CommaDelimitedList>` described as "a Systems Manager parameter
|
|
4315
|
+
* whose value is a list of strings". `aws-cdk-lib`'s own
|
|
4316
|
+
* `StringListParameter.fromListParameterAttributes` agrees: it emits
|
|
4317
|
+
* `{type: 'AWS::SSM::Parameter::Value<List<String>>', default:
|
|
4318
|
+
* attrs.parameterName}` -- a single name.
|
|
4319
|
+
* - **A LIVE CloudFormation OBSERVATION** recorded at
|
|
4320
|
+
* `src/synthesis/macro-expander.ts` (the CR-MJ3 fix): a single-string
|
|
4321
|
+
* placeholder against a `Value<List<*>>` type "would reject the changeset
|
|
4322
|
+
* with `Parameter ... must be a list`", which is why that site emits a
|
|
4323
|
+
* 2-element comma-joined placeholder. Someone watched CloudFormation do
|
|
4324
|
+
* that, and a live observation outranks a documentation read.
|
|
4325
|
+
*
|
|
4326
|
+
* THESE MAY BOTH BE TRUE OF DIFFERENT THINGS -- CloudFormation's pre-macro
|
|
4327
|
+
* changeset VALIDATOR may demand a list-shaped literal while the runtime
|
|
4328
|
+
* resolves one key -- and that reconciliation is plausible but UNMEASURED. It
|
|
4329
|
+
* is recorded as unresolved rather than decided, and neither caller depends on
|
|
4330
|
+
* it: the synthesis site is choosing a placeholder for a validator, and the
|
|
4331
|
+
* deployment site is coercing a value `GetParameter` ALREADY returned, which is
|
|
4332
|
+
* downstream of whatever the supplied key meant.
|
|
4333
|
+
*/
|
|
4334
|
+
function ssmResolvedValueType(type) {
|
|
4335
|
+
if (!type.startsWith("AWS::SSM::Parameter::Value<") || !type.endsWith(">")) return void 0;
|
|
4336
|
+
const inner = type.slice(27, -1);
|
|
4337
|
+
return inner.length > 0 ? inner : void 0;
|
|
4338
|
+
}
|
|
4260
4339
|
|
|
4261
4340
|
//#endregion
|
|
4262
4341
|
//#region src/synthesis/macro-expander.ts
|
|
@@ -4531,7 +4610,8 @@ function stringifyParamDefault(value, type, paramKey, logger) {
|
|
|
4531
4610
|
const known = PARAMETER_TYPE_PLACEHOLDERS[type];
|
|
4532
4611
|
if (known !== void 0) return known;
|
|
4533
4612
|
if (type.startsWith("AWS::SSM::Parameter::Value<")) {
|
|
4534
|
-
|
|
4613
|
+
const inner = ssmResolvedValueType(type);
|
|
4614
|
+
if (inner !== void 0 && isListParameterType(inner)) return "placeholder,placeholder";
|
|
4535
4615
|
return "placeholder";
|
|
4536
4616
|
}
|
|
4537
4617
|
logger.warn(`Parameter '${paramKey}' has unrecognized CFn Type '${type}'; using a generic string placeholder for the transient macro-expansion changeset. If CFn rejects the changeset with a type error, file an issue with the offending Type.`);
|
|
@@ -14972,8 +15052,10 @@ function describe(value) {
|
|
|
14972
15052
|
//#region src/provisioning/region-check.ts
|
|
14973
15053
|
/**
|
|
14974
15054
|
* Verify that the AWS client's region matches the region the resource is
|
|
14975
|
-
* expected to live in before treating a `NotFound` error as idempotent
|
|
14976
|
-
* delete success
|
|
15055
|
+
* expected to live in — before treating a `NotFound` error as idempotent
|
|
15056
|
+
* delete success (`phase: 'not-found'`), or before issuing a mutating call
|
|
15057
|
+
* against a state-recorded physical id at all (`'pre-delete'` /
|
|
15058
|
+
* `'pre-update'`, issue #2301).
|
|
14977
15059
|
*
|
|
14978
15060
|
* Why: a destroy run with the wrong region would otherwise receive
|
|
14979
15061
|
* `*NotFound` for every resource and silently strip them all from state,
|
|
@@ -14982,13 +15064,24 @@ function describe(value) {
|
|
|
14982
15064
|
* `us-west-2` removed from state by a destroy that ran with a `us-east-1`
|
|
14983
15065
|
* client.
|
|
14984
15066
|
*
|
|
14985
|
-
*
|
|
15067
|
+
* And a `NotFound` is not the only way that ends badly, which is what the
|
|
15068
|
+
* pre-flight phases add: many physical ids are names rather than ARNs, so the
|
|
15069
|
+
* same name usually EXISTS in the client's region too (the same stack deployed
|
|
15070
|
+
* twice, or cdkd's own `resource-name.ts` deriving an identical name from an
|
|
15071
|
+
* identical stack + logical id). Then the wrong-region call never errors — it
|
|
15072
|
+
* succeeds against the wrong resource. That path is unrecoverable on delete
|
|
15073
|
+
* and a misapplied configuration on update, and neither ever reaches the
|
|
15074
|
+
* `NotFound` branch this helper originally lived on.
|
|
15075
|
+
*
|
|
15076
|
+
* Behavior (identical in every phase):
|
|
14986
15077
|
* - If `expectedRegion` is unset, this is a no-op (back-compat: existing
|
|
14987
15078
|
* idempotent semantics preserved for callers that have not been
|
|
14988
|
-
* threaded with state region).
|
|
15079
|
+
* threaded with state region). An EMPTY string counts as unset — a caller
|
|
15080
|
+
* typed `region: string` can hand one over, and refusing on it would make
|
|
15081
|
+
* this guard reject its own default.
|
|
14989
15082
|
* - If `clientRegion` matches `expectedRegion`, returns silently.
|
|
14990
15083
|
* - Otherwise throws `ProvisioningError` so the caller surfaces the
|
|
14991
|
-
* mismatch instead of swallowing the NotFound.
|
|
15084
|
+
* mismatch instead of swallowing the NotFound / issuing the call.
|
|
14992
15085
|
*
|
|
14993
15086
|
* @param clientRegion Region resolved from the AWS SDK client config
|
|
14994
15087
|
* (typically `await client.config.region()`).
|
|
@@ -14998,13 +15091,27 @@ function describe(value) {
|
|
|
14998
15091
|
* message and on the thrown ProvisioningError.
|
|
14999
15092
|
* @param logicalId Logical ID of the resource, used in the error message
|
|
15000
15093
|
* and on the thrown ProvisioningError.
|
|
15001
|
-
* @param physicalId Optional physical ID,
|
|
15002
|
-
*
|
|
15094
|
+
* @param physicalId Optional physical ID, carried on the thrown
|
|
15095
|
+
* ProvisioningError.
|
|
15096
|
+
* @param phase Which call is being guarded — see {@link RegionCheckPhase}.
|
|
15097
|
+
* Defaults to the historical `'not-found'` so every pre-#2301 call site
|
|
15098
|
+
* keeps its exact wording.
|
|
15003
15099
|
*/
|
|
15004
|
-
function assertRegionMatch(clientRegion, expectedRegion, resourceType, logicalId, physicalId) {
|
|
15100
|
+
function assertRegionMatch(clientRegion, expectedRegion, resourceType, logicalId, physicalId, phase = "not-found") {
|
|
15005
15101
|
if (!expectedRegion) return;
|
|
15006
|
-
if (!clientRegion) throw new ProvisioningError(`Refusing to treat NotFound as idempotent delete success for ${logicalId} (${resourceType}): AWS client region is unknown but stack state expects ${expectedRegion}. The resource may exist in ${expectedRegion} and would be silently removed from state if this NotFound were trusted.`, resourceType, logicalId, physicalId);
|
|
15007
|
-
if (clientRegion !== expectedRegion) throw new ProvisioningError(`Refusing to treat NotFound as idempotent delete success for ${logicalId} (${resourceType}): AWS client region ${clientRegion} does not match stack state region ${expectedRegion}. The resource likely still exists in ${expectedRegion}; rerun the destroy with the correct region (e.g. --region ${expectedRegion}).`, resourceType, logicalId, physicalId);
|
|
15102
|
+
if (!clientRegion) throw new ProvisioningError(phase === "not-found" ? `Refusing to treat NotFound as idempotent delete success for ${logicalId} (${resourceType}): AWS client region is unknown but stack state expects ${expectedRegion}. The resource may exist in ${expectedRegion} and would be silently removed from state if this NotFound were trusted.` : `Refusing to ${phaseVerb(phase)} ${logicalId} (${resourceType}): AWS client region is unknown but stack state records the resource in ${expectedRegion}. cdkd cannot confirm that the physical id recorded in state names the resource this client would act on, so the ${phaseVerb(phase)} is not issued. Point the AWS client at ${expectedRegion} (AWS_REGION or your AWS profile) and re-run.`, resourceType, logicalId, physicalId);
|
|
15103
|
+
if (clientRegion !== expectedRegion) throw new ProvisioningError(phase === "not-found" ? `Refusing to treat NotFound as idempotent delete success for ${logicalId} (${resourceType}): AWS client region ${clientRegion} does not match stack state region ${expectedRegion}. The resource likely still exists in ${expectedRegion}; rerun the destroy with the correct region (e.g. --region ${expectedRegion}).` : `Refusing to ${phaseVerb(phase)} ${logicalId} (${resourceType}): AWS client region ${clientRegion} does not match stack state region ${expectedRegion}. The physical id recorded in cdkd state names a resource in ${expectedRegion}, so this ${phaseVerb(phase)} would act on whatever carries that id in ${clientRegion} instead — which for a name-shaped physical id is a different resource that usually exists. Point the AWS client at ${expectedRegion} (AWS_REGION or your AWS profile) and re-run; when the run spans several regions at once (cdkd drift --all), select the stacks in one region per run, because no single client region is correct for all of them. If the recorded region is the wrong one, correct the state record (cdkd state show).`, resourceType, logicalId, physicalId);
|
|
15104
|
+
}
|
|
15105
|
+
/**
|
|
15106
|
+
* The operation a pre-flight phase is about to issue, for the message.
|
|
15107
|
+
*
|
|
15108
|
+
* `'not-found'` is EXCLUDED from the parameter type rather than mapped to a
|
|
15109
|
+
* verb: both call sites already sit in the `else` of a
|
|
15110
|
+
* `phase === 'not-found' ? ... : ...`, so an arm answering for it would be
|
|
15111
|
+
* unreachable, and an unreachable arm is a claim no test can hold to account.
|
|
15112
|
+
*/
|
|
15113
|
+
function phaseVerb(phase) {
|
|
15114
|
+
return phase === "pre-update" ? "update" : "delete";
|
|
15008
15115
|
}
|
|
15009
15116
|
|
|
15010
15117
|
//#endregion
|
|
@@ -16722,6 +16829,62 @@ function coerceParameterTypedValue(value, type) {
|
|
|
16722
16829
|
return value;
|
|
16723
16830
|
}
|
|
16724
16831
|
/**
|
|
16832
|
+
* Bind a template-declared `Default` the way the USER-SUPPLIED path binds a
|
|
16833
|
+
* value (issue
|
|
16834
|
+
* [#2367](https://github.com/go-to-k/cdkd/issues/2367)).
|
|
16835
|
+
*
|
|
16836
|
+
* `resolveParameters` writes `parameters[name]` at three sites and only the
|
|
16837
|
+
* user-supplied one asked the coercion anything, so a parameter declared
|
|
16838
|
+
* `Type: CommaDelimitedList` with `Default: "a,b,c"` and no CLI override
|
|
16839
|
+
* reached every consumer as the raw string -- `Fn::Select` over it threw
|
|
16840
|
+
* `Fn::Select: list must be an array, got string`, and a bare `Ref` handed the
|
|
16841
|
+
* provider a comma-joined scalar where the resource schema declares a list.
|
|
16842
|
+
* The defect predates the #2347 widening: it hits `CommaDelimitedList` and
|
|
16843
|
+
* `List<Number>`, the two list types the `switch` has recognised all along.
|
|
16844
|
+
*
|
|
16845
|
+
* CloudFormation's own documentation is written in exactly these terms --
|
|
16846
|
+
* `parameters-section-structure.html`'s worked example declares
|
|
16847
|
+
* `VpcAzs: {Type: CommaDelimitedList, Default: "us-west-2a, us-west-2b,
|
|
16848
|
+
* us-west-2c"}` and then reads it with `Fn::Select`, which is the case that
|
|
16849
|
+
* threw.
|
|
16850
|
+
*
|
|
16851
|
+
* ONLY A STRING IS COERCED, and that is the whole of the rule. `Default` is
|
|
16852
|
+
* typed `unknown` because it is whatever the template parser produced, and the
|
|
16853
|
+
* shapes are not hypothetical -- measured 2026-08-29 on both parsers cdkd
|
|
16854
|
+
* feeds this from:
|
|
16855
|
+
*
|
|
16856
|
+
* - `aws-cdk-lib`'s `CfnParameter._toCloudFormation` emits `Default:
|
|
16857
|
+
* this.default` with no conversion, so `{type: 'Number', default: 42}`
|
|
16858
|
+
* synthesizes the JSON NUMBER `42`, and `{type: 'CommaDelimitedList',
|
|
16859
|
+
* default: ['a','b','c']}` synthesizes a JSON ARRAY;
|
|
16860
|
+
* - `parseCfnTemplate` (`src/cli/yaml-cfn.ts`), on the `cdkd import
|
|
16861
|
+
* --migrate-from-cloudformation` / `cdkd export` path, resolves `Default:
|
|
16862
|
+
* 42` to a number, `Default: "42"` to a string, a YAML sequence to an array
|
|
16863
|
+
* and `Default: true` to a boolean.
|
|
16864
|
+
*
|
|
16865
|
+
* FOR THE SHAPES MEASURED ABOVE, a non-string default is already what the
|
|
16866
|
+
* declared type calls for -- `42` for a `Number`, `['a','b']` for a
|
|
16867
|
+
* `CommaDelimitedList` -- so coercing it could only damage it.
|
|
16868
|
+
* `String(['a,b','c'])` is `'a,b,c'`, which the split would then shred into
|
|
16869
|
+
* THREE elements, and `String(true)` would turn a boolean a consumer sees today
|
|
16870
|
+
* into text. Stringifying first is therefore not a harmless normalization, and
|
|
16871
|
+
* `coerceParameterTypedValue` takes a `string` precisely because parsing the
|
|
16872
|
+
* wire text is its whole job.
|
|
16873
|
+
*
|
|
16874
|
+
* THE CLAIM IS SCOPED TO THOSE SHAPES ON PURPOSE, because a mismatched pairing
|
|
16875
|
+
* is reachable and is NOT in it: YAML admits `Type: CommaDelimitedList` with
|
|
16876
|
+
* `Default: 42` or `Default: true`, and such a default is passed through as the
|
|
16877
|
+
* scalar it parsed to rather than becoming a one-element list. That is the
|
|
16878
|
+
* PRE-EXISTING behaviour, unchanged here and deliberately so -- a template
|
|
16879
|
+
* pairing a list type with a scalar default is malformed CloudFormation, and
|
|
16880
|
+
* inventing a coercion for it on a path that writes state is a bigger decision
|
|
16881
|
+
* than this fix.
|
|
16882
|
+
*/
|
|
16883
|
+
function coerceParameterDefault(defaultValue, type) {
|
|
16884
|
+
if (typeof defaultValue !== "string") return defaultValue;
|
|
16885
|
+
return coerceParameterTypedValue(defaultValue, type);
|
|
16886
|
+
}
|
|
16887
|
+
/**
|
|
16725
16888
|
* The inherited `plaintext -> expression` pairs that `value` CARRIES.
|
|
16726
16889
|
*
|
|
16727
16890
|
* ONE definition, shared by the RECORDING side
|
|
@@ -17229,11 +17392,12 @@ var IntrinsicFunctionResolver = class IntrinsicFunctionResolver {
|
|
|
17229
17392
|
const ssmPath = String(paramDef.Default);
|
|
17230
17393
|
this.logger.debug(`Parameter ${name}: resolving SSM parameter path ${ssmPath}`);
|
|
17231
17394
|
const resolved = await this.resolveSSMParameter(ssmPath);
|
|
17232
|
-
|
|
17395
|
+
const resolvedType = ssmResolvedValueType(paramDef.Type);
|
|
17396
|
+
parameters[name] = resolvedType === void 0 ? resolved : this.coerceParameterValue(resolved, resolvedType);
|
|
17233
17397
|
this.logger.debug(`Parameter ${name}: resolved SSM value ${maskInherited(stringifyParameterForLog(paramDef, resolved))}`);
|
|
17234
17398
|
continue;
|
|
17235
17399
|
}
|
|
17236
|
-
parameters[name] = paramDef.Default;
|
|
17400
|
+
parameters[name] = coerceParameterDefault(paramDef.Default, paramDef.Type);
|
|
17237
17401
|
this.logger.debug(`Parameter ${name}: using default value ${maskInherited(stringifyParameterForLog(paramDef, paramDef.Default))}`);
|
|
17238
17402
|
continue;
|
|
17239
17403
|
}
|
|
@@ -20478,8 +20642,9 @@ var CloudControlProvider = class {
|
|
|
20478
20642
|
/**
|
|
20479
20643
|
* Update a resource using Cloud Control API
|
|
20480
20644
|
*/
|
|
20481
|
-
async update(logicalId, physicalId, resourceType, properties, previousProperties) {
|
|
20645
|
+
async update(logicalId, physicalId, resourceType, properties, previousProperties, context) {
|
|
20482
20646
|
this.logger.debug(`Updating resource ${logicalId} (${resourceType}), physical ID: ${physicalId}`);
|
|
20647
|
+
await this.assertRecordedRegionAgainstClient("pre-update", context?.expectedRegion, resourceType, logicalId, physicalId);
|
|
20483
20648
|
try {
|
|
20484
20649
|
const cleanPreviousProperties = stringifyJsonProperties(resourceType, stripNullValues(previousProperties));
|
|
20485
20650
|
const cleanProperties = stringifyJsonProperties(resourceType, stripNullValues(properties));
|
|
@@ -20538,10 +20703,11 @@ var CloudControlProvider = class {
|
|
|
20538
20703
|
async delete(logicalId, physicalId, resourceType, _properties, context) {
|
|
20539
20704
|
this.logger.debug(`Deleting resource ${logicalId} (${resourceType}), physical ID: ${physicalId}`);
|
|
20540
20705
|
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);
|
|
20706
|
+
await this.assertRecordedRegionAgainstClient("pre-delete", context?.expectedRegion, resourceType, logicalId, physicalId);
|
|
20541
20707
|
await this.confirmDeleteTargetIdentity(logicalId, resourceType, physicalId, context);
|
|
20542
20708
|
if (context?.removeProtection === true && resourceType === "AWS::AutoScaling::AutoScalingGroup") {
|
|
20543
20709
|
this.logger.debug(`Delegating protected AutoScalingGroup ${logicalId} delete to the SDK ASGProvider (Cloud Control cannot force-delete a protected ASG)`);
|
|
20544
|
-
const { ASGProvider } = await import("./asg-provider-
|
|
20710
|
+
const { ASGProvider } = await import("./asg-provider-CnpxfVci.js").then((n) => n.n);
|
|
20545
20711
|
return await new ASGProvider().delete(logicalId, physicalId, resourceType, _properties, context);
|
|
20546
20712
|
}
|
|
20547
20713
|
const isProtectedEc2Instance = context?.removeProtection === true && resourceType === "AWS::EC2::Instance";
|
|
@@ -20564,7 +20730,7 @@ var CloudControlProvider = class {
|
|
|
20564
20730
|
} catch (error) {
|
|
20565
20731
|
const err = error;
|
|
20566
20732
|
if (error instanceof CloudControlOperationFailedError && error.ccOperation === "DELETE" && error.ccErrorCode === "NotFound" || err.name === "ResourceNotFoundException" || err.message?.includes("does not exist") || err.message?.includes("not found") || err.message?.includes("NotFound")) {
|
|
20567
|
-
|
|
20733
|
+
await this.assertRecordedRegionAgainstClient("not-found", context?.expectedRegion, resourceType, logicalId, physicalId);
|
|
20568
20734
|
this.logger.debug(`Resource ${logicalId} already deleted (not found), treating as success`);
|
|
20569
20735
|
return;
|
|
20570
20736
|
}
|
|
@@ -20578,15 +20744,77 @@ var CloudControlProvider = class {
|
|
|
20578
20744
|
}
|
|
20579
20745
|
}
|
|
20580
20746
|
/**
|
|
20747
|
+
* Refuse a Cloud Control call whose target region cannot be shown to be the
|
|
20748
|
+
* one the state record was written in (issue #2301).
|
|
20749
|
+
*
|
|
20750
|
+
* The ONE place this comparison happens, for all three phases: the
|
|
20751
|
+
* pre-flights at the top of `delete()` and `update()`, and the reactive
|
|
20752
|
+
* `not-found` arm inside `delete()`'s catch block. They can therefore never
|
|
20753
|
+
* disagree about what "unknown region" means, nor about how a region is
|
|
20754
|
+
* SPELLED -- the second one was live before this became shared: the reactive
|
|
20755
|
+
* arm compared raw while the pre-flight folded case, so one correct call
|
|
20756
|
+
* could pass the first and be refused by the second. THREE inputs, THREE outcomes, and they are
|
|
20757
|
+
* deliberately not two:
|
|
20758
|
+
*
|
|
20759
|
+
* - NO recorded region (`undefined`, or an empty / whitespace-only string)
|
|
20760
|
+
* -> PROCEED, and do not even resolve the client region. This is the
|
|
20761
|
+
* guard's OWN default: a `version: 1` state record predates the
|
|
20762
|
+
* region-scoped key layout and carries no region at all, and callers
|
|
20763
|
+
* typed `region: string` (`deploy-engine.ts`'s `stackRegion`) can hand
|
|
20764
|
+
* over `''`. Refusing on the absence would break every ordinary
|
|
20765
|
+
* destroy / update of a pre-v2 record, which is the over-tightening
|
|
20766
|
+
* failure a one-directional fence never sees.
|
|
20767
|
+
* - A recorded region that MATCHES the client -> proceed silently. This is
|
|
20768
|
+
* the ordinary path and it must stay free of new refusals: the whole
|
|
20769
|
+
* fleet of same-region deletes and updates runs through here.
|
|
20770
|
+
* - A recorded region that DIFFERS, or a client region that cannot be
|
|
20771
|
+
* resolved at all -> REFUSE before issuing anything.
|
|
20772
|
+
*
|
|
20773
|
+
* The unresolvable-client-region arm is the one asymmetry worth naming:
|
|
20774
|
+
* {@link CloudControlProvider.confirmDeleteTargetIdentity} PROCEEDS when it
|
|
20775
|
+
* cannot establish a region, and this helper refuses. The two are answering
|
|
20776
|
+
* different questions. That probe asks a remote service where a globally
|
|
20777
|
+
* unique NAME lives, and a least-privilege role that was never granted
|
|
20778
|
+
* `s3:GetBucketLocation` would be stranded by a refusal. Here the caller has
|
|
20779
|
+
* positively recorded a region, the comparison is local and free, and a
|
|
20780
|
+
* client that cannot say where it points cannot be shown to point at that
|
|
20781
|
+
* region -- the same answer `assertRegionMatch` has always given on its
|
|
20782
|
+
* `not-found` phase.
|
|
20783
|
+
*
|
|
20784
|
+
* The refusal is marked non-retryable because it is deterministic: both
|
|
20785
|
+
* loops that wrap these calls -- the destroy runner's own attempt loop and
|
|
20786
|
+
* the deploy engine's / rollback executor's `withRetry` -- would otherwise
|
|
20787
|
+
* spend their full budget re-deriving the same verdict, which reads to a
|
|
20788
|
+
* user as flaky AWS rather than as a refusal.
|
|
20789
|
+
*/
|
|
20790
|
+
async assertRecordedRegionAgainstClient(phase, expectedRegion, resourceType, logicalId, physicalId) {
|
|
20791
|
+
const recordedRegion = canonicalizeRegion(expectedRegion?.trim());
|
|
20792
|
+
if (recordedRegion === void 0 || recordedRegion === "") return;
|
|
20793
|
+
let clientRegion;
|
|
20794
|
+
try {
|
|
20795
|
+
clientRegion = canonicalizeRegion((await this.cloudControlClient.config.region())?.trim());
|
|
20796
|
+
} catch (error) {
|
|
20797
|
+
this.logger.debug(`Could not resolve the Cloud Control client region before the ${phase} region check for ${logicalId} (${resourceType}): ${error instanceof Error ? error.message : String(error)}`);
|
|
20798
|
+
clientRegion = void 0;
|
|
20799
|
+
}
|
|
20800
|
+
try {
|
|
20801
|
+
assertRegionMatch(clientRegion, recordedRegion, resourceType, logicalId, physicalId, phase);
|
|
20802
|
+
} catch (error) {
|
|
20803
|
+
throw markNonRetryable(error);
|
|
20804
|
+
}
|
|
20805
|
+
}
|
|
20806
|
+
/**
|
|
20581
20807
|
* Confirm that the resource `physicalId` names actually lives in the region
|
|
20582
20808
|
* this destroy is targeting, for the types in
|
|
20583
20809
|
* {@link CC_DELETE_IDENTITY_CHECKED_TYPES}. No-op for every other type.
|
|
20584
20810
|
*
|
|
20585
20811
|
* WHAT THIS GUARDS THAT `assertRegionMatch` DOES NOT
|
|
20586
20812
|
* ---------------------------------------------------
|
|
20587
|
-
* The
|
|
20588
|
-
*
|
|
20589
|
-
*
|
|
20813
|
+
* The `assertRegionMatch` comparison — which since issue #2301 runs both as
|
|
20814
|
+
* an unconditional pre-flight and on the `NotFound` arm below — compares the
|
|
20815
|
+
* CLIENT's region against the STATE's. That misses this hazard however often
|
|
20816
|
+
* it runs: both of its inputs can agree while the bucket the physical id
|
|
20817
|
+
* names sits somewhere else entirely. An `AWS::S3::Bucket` physical id is
|
|
20590
20818
|
* a GLOBALLY unique name, so a state record written before the issue #2227 /
|
|
20591
20819
|
* #2245 guards existed can name a bucket that is ours but lives elsewhere --
|
|
20592
20820
|
* a cdkd-GENERATED bucket name carries no region or account: for a name cdkd
|
|
@@ -29498,7 +29726,10 @@ async function replaySingle(op, stateResources, stackName, ctx, resolver, orphan
|
|
|
29498
29726
|
op.resourceType,
|
|
29499
29727
|
desiredProps ?? {},
|
|
29500
29728
|
currentProps ?? {},
|
|
29501
|
-
{
|
|
29729
|
+
{
|
|
29730
|
+
maskSecrets: createSecretMasker(secrets),
|
|
29731
|
+
expectedRegion: ctx.region
|
|
29732
|
+
}
|
|
29502
29733
|
], op.logicalId, logger, isInterrupted, secrets);
|
|
29503
29734
|
stateResources[op.logicalId] = redactRollbackRecord(recordAfterRollbackUpdate(previousState, revertResult), secrets, previousState.properties);
|
|
29504
29735
|
const rollbackPartial = updatePartialReason(revertResult);
|
|
@@ -29651,7 +29882,10 @@ async function replayFailedOperations(failedOps, stateResources, stackName, ctx,
|
|
|
29651
29882
|
op.resourceType,
|
|
29652
29883
|
desiredProps ?? {},
|
|
29653
29884
|
attemptedProps ?? {},
|
|
29654
|
-
{
|
|
29885
|
+
{
|
|
29886
|
+
maskSecrets: createSecretMasker(secrets),
|
|
29887
|
+
expectedRegion: ctx.region
|
|
29888
|
+
}
|
|
29655
29889
|
], op.logicalId, logger, options.isInterrupted, secrets);
|
|
29656
29890
|
stateResources[op.logicalId] = redactRollbackRecord(recordAfterRollbackUpdate(prev, revertFailedResult), secrets, prev.properties);
|
|
29657
29891
|
const revertFailedPartial = updatePartialReason(revertFailedResult);
|
|
@@ -31957,7 +32191,10 @@ var DeployEngine = class {
|
|
|
31957
32191
|
let result;
|
|
31958
32192
|
let resultProvisionedBy = updateDecision.provisionedBy;
|
|
31959
32193
|
try {
|
|
31960
|
-
result = await this.withRetry(() => withCurrentResourceSecrets(updateSecrets, () => updateProvider.update(logicalId, currentResource.physicalId, resourceType, updateProps, currentProps, {
|
|
32194
|
+
result = await this.withRetry(() => withCurrentResourceSecrets(updateSecrets, () => updateProvider.update(logicalId, currentResource.physicalId, resourceType, updateProps, currentProps, {
|
|
32195
|
+
maskSecrets: createSecretMasker(updateSecrets),
|
|
32196
|
+
expectedRegion: this.stackRegion
|
|
32197
|
+
})), logicalId, void 0, void 0, updateProvider);
|
|
31961
32198
|
} catch (updateError) {
|
|
31962
32199
|
const msg = updateError instanceof Error ? updateError.message : String(updateError);
|
|
31963
32200
|
const ccUnsupported = msg.includes("UnsupportedActionException") || msg.includes("does not support UPDATE");
|
|
@@ -32050,7 +32287,7 @@ var DeployEngine = class {
|
|
|
32050
32287
|
}), logicalId, 3, 5e3, deleteProvider);
|
|
32051
32288
|
} catch (deleteError) {
|
|
32052
32289
|
const msg = deleteError instanceof Error ? deleteError.message : String(deleteError);
|
|
32053
|
-
if (!isInterruptedWaitError(deleteError) && (msg.includes("does not exist") || msg.includes("was not found") || msg.includes("not found") || msg.includes("No policy found") || msg.includes("NoSuchEntity") || msg.includes("NotFoundException") || msg.includes("ResourceNotFoundException"))) this.logger.debug(`Resource ${logicalId} already deleted (${msg}), removing from state`);
|
|
32290
|
+
if (!isInterruptedWaitError(deleteError) && !isMarkedNonRetryable(deleteError) && (msg.includes("does not exist") || msg.includes("was not found") || msg.includes("not found") || msg.includes("No policy found") || msg.includes("NoSuchEntity") || msg.includes("NotFoundException") || msg.includes("ResourceNotFoundException"))) this.logger.debug(`Resource ${logicalId} already deleted (${msg}), removing from state`);
|
|
32054
32291
|
else throw deleteError;
|
|
32055
32292
|
}
|
|
32056
32293
|
const deleteSkipped = deleteSkipReason(deleteResult);
|
|
@@ -32459,4 +32696,4 @@ var DeployEngine = class {
|
|
|
32459
32696
|
|
|
32460
32697
|
//#endregion
|
|
32461
32698
|
export { maskerOrIdentity as $, CFN_TEMPLATE_BODY_LIMIT as $n, maskSecretsInText as $t, renderStatefulReason as A, describeAwsFailure as An, PartialFailureError as Ar, requireConfigString as At, exportAliasCollisionScrubWarning as B, Synthesizer as Bn, normalizeAwsError as Br, INTRINSIC_KEYS as Bt, isFinalSnapshotError as C, getBootstrapMarkerKey as Cn, DynamicReferenceRegionAmbiguousError as Cr, coerceCfnBoolean as Ct, extractDeploymentEventError as D, validateAssetBucketName as Dn, LockError as Dr, replayWarn as Dt, makeCanonicalizePropertiesFn as E, readBootstrapMarkerBody as En, LocalStartServiceError as Er, readConfigString as Et, green as F, partitionSensitiveEnv as Fn, StackTerminationProtectionError as Fr, s3BucketDualStackDomainName as Ft, IAMRoleProvider as G, resolveAutoAssetStorage as Gn, markNonRetryable as Gr, STATE_SOURCED_CROSS_GENERATION_RULES as Gt, secretBearingStateKeyWarning as H, getDefaultStateBucketName as Hn, isMarkedNonRetryable as Hr, withRetry as Ht, red as I, runDockerForeground as In, StateError as Ir, s3BucketRegionalDomainName as It, ProviderRegistry as J, resolveStateBucketWithDefault as Jn, __exportAll as Jr, createSecretMasker as Jt, collectInlinePolicyNamesManagedBySiblings as K, resolveCaptureObservedState as Kn, markRedactedCause as Kr, STATE_SOURCED_READBACK_RULES as Kt, yellow as L, runDockerStreaming as Ln, SynthesisError as Lr, s3BucketWebsiteUrl as Lt, bold as M, dockerSpawnEnvWithSensitive as Mn, ResourceTimeoutError as Mr, producerRegionsFromState as Mt, cyan as N, formatDockerLoginError as Nn, ResourceUpdateNotSupportedError as Nr, s3BucketArn as Nt, MULTI_REGION_RECREATE_BLOCKED_TYPES as O, validateContainerRepoName as On, MissingCdkCliError as Or, requireConfigArray as Ot, gray as P, getDockerCmd as Pn, StackHasActiveImportsError as Pr, s3BucketDomainName as Pt, maskDeep as Q, warnDeprecatedNoPrefixCliFlag as Qn, maskSecretsInError as Qt, collectDeclaredOutputNames as R, AssetManifestLoader as Rn, formatError as Rr, applyRoleArnIfSet as Rt, createPreDeleteFinalSnapshot as S, ensureAssetStorage as Sn, DeployCancelledError as Sr, assertRegionMatch as St, unsupportedFinalSnapshotError as T, parseBootstrapMarker as Tn, LocalMigrateError as Tr, configStringRefusal as Tt, stateKeySecretExposure as U, getLegacyStateBucketName as Un, isRetryableTransientError as Ur, DagBuilder as Ut, isExportAliasCollision as V, synthesisStatusMessage as Vn, withErrorHandling as Vr, describeTypeWithThrottleRetry as Vt, getCurrentResourceSecrets as W, resolveApp as Wn, isThrottlingError as Wr, TemplateParser as Wt, findSilentDropProperties as X, resolveUseCdkBootstrapAssets as Xn, errorCauseChain as Xt, findActionableSilentDrops as Y, resolveStateBucketWithDefaultAndSource as Yn, dynamicReferenceTokens as Yt, createMaskedRetryLogger as Z, stateBucketExistenceConfirmed as Zn, isSingleDynamicReferenceToken as Zt, computeImplicitDeleteEdges as _, escapeRegExp$1 as _n, AssetError as _r, parameterTypeMayLoseSecretIdentity as _t, DeploymentEventsStore as a, rebuildClientForBucketRegion as an, expectedOwnerParam as ar, isInterruptedWaitError as at, buildFinalSnapshotIdentifier as b, BOOTSTRAP_MARKER_PREFIX as bn, CrossAccountSecretRefusalError as br, normalizeAwsTagsToCfn as bt, replayFailedOperations as c, importableOutputs as cn, derivePartitionAndUrlSuffix as cr, slowCcOperationTimeoutMs as ct, updatePartialReason as d, stringifyValue as dn, clearBucketRegionCache as dr, IntrinsicFunctionResolver as dt, redactSecretsForState as en, CFN_TEMPLATE_URL_LIMIT as er, CUSTOM_RESOURCE_RESPONSE_PREFIX as et, UNSPECIFIED_SKIP_REASON as f, WorkGraph as fn, resolveBucketRegion as fr, carriesDynamicReference as ft, IMPLICIT_DELETE_DEPENDENCIES as g, rewriteTemplateAssetReferences as gn, setAwsClients as gr, isUnboundTemplateParameter as gt, maskingRetryLogger as h, loadPublishableAssetManifest as hn, resetAwsClients as hr, getAccountInfo as ht, DeploymentEventsReader as i, S3StateBackend as in, CUSTOM_RESOURCE_RESPONSE_OBJECT_DESCRIPTION as ir, interruptWatchListenerCount as it, formatResourceLine as j, buildDockerImage as jn, ProvisioningError as jr, classifyReplaySecretRegion as jt, isStatefulRecreateTargetSync as k, buildDenyExternalAccessPolicy as kn, NestedStackChildDirectDestroyError as kr, requireConfigObject as kt, replayRollback as l, shouldRetainResource as ln, AssemblyReader as lr, disableInstanceApiTermination as lt, withResourceDeadline as m, createAssetRedirectResolver as mn, getAwsClients as mr, coerceParameterTypedValue as mt, DEFAULT_RESOURCE_WARN_AFTER_MS as n, LockManager as nn, findLargeInlineResources as nr, beginCommandInterruptScope as nt, planFailedOps as o, exportNamesCarriedFrom as on, PARTITION_TABLE as or, startInterruptWatch as ot, deleteSkipReason as p, buildAssetRedirectMap as pn, AwsClients as pr, cfnRefValueFromPhysicalId as pt, clearOnUpdateRemoval as q, resolveSkipPrefix as qn, retryClassificationText as qr, TEMPLATE_SOURCED_RULES as qt, DeployEngine as r, displaySafe as rn, uploadCfnTemplate as rr, endCommandInterruptScope as rt, planRollback as s, importableOutputKeys as sn, canonicalizeRegion as sr, CloudControlProvider as st, DEFAULT_RESOURCE_TIMEOUT_MS as t, scrubResourceRecord as tn, MIGRATE_TMP_PREFIX as tr, DEFAULT_STATE_PREFIX as tt, updatePartialMessage as u, AssetPublisher as un, processStackMessages as ur, isTerminationProtectionPropagationError as ut, ATOMIC_FINAL_SNAPSHOT_TYPES as v, stripControlChars as vn, CdkdError as vr, refStateLookupFromResource as vt, refusesFinalSnapshot as w, isCrossRegionRedirect as wn, LocalInvokeBuildError as wr, configBooleanRefusal as wt, ccRoutedFinalSnapshotError as x, assertAssetBucketRegion as xn, DependencyError as xr, resolveExplicitPhysicalId as xt, PRE_DELETE_SNAPSHOT_TYPES as y, AssetModeResolver as yn, ConfigError as yr, WAFv2WebACLProvider as yt, collectPublishedOutputNames as z, getDockerImageBySourceHash as zn, isCdkdError as zr, DiffCalculator as zt };
|
|
32462
|
-
//# sourceMappingURL=deploy-engine-
|
|
32699
|
+
//# sourceMappingURL=deploy-engine-BoDdOfCO.js.map
|