@go-to-k/cdkd 0.284.81 → 0.284.83
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-DW0VKnyp.js → asg-provider-B-KONBYk.js} +2 -2
- package/dist/{asg-provider-DW0VKnyp.js.map → asg-provider-B-KONBYk.js.map} +1 -1
- package/dist/cli.js +2 -2
- package/dist/{deploy-engine-DAY3Q6s4.js → deploy-engine-BES1Z20a.js} +320 -163
- package/dist/deploy-engine-BES1Z20a.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/dist/{program-DFsR9GOJ.js → program-BTxarL5l.js} +63 -23
- package/dist/{program-DFsR9GOJ.js.map → program-BTxarL5l.js.map} +1 -1
- package/dist/{version-D91AoEe5.js → version-DI03miJ9.js} +2 -2
- package/dist/{version-D91AoEe5.js.map → version-DI03miJ9.js.map} +1 -1
- package/package.json +1 -1
- package/dist/deploy-engine-DAY3Q6s4.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-DI03miJ9.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";
|
|
@@ -3868,6 +3868,168 @@ async function expectedOwnerParam(client) {
|
|
|
3868
3868
|
return owner ? { ExpectedBucketOwner: owner } : {};
|
|
3869
3869
|
}
|
|
3870
3870
|
|
|
3871
|
+
//#endregion
|
|
3872
|
+
//#region src/state/s3-noncurrent-version-purge.ts
|
|
3873
|
+
/**
|
|
3874
|
+
* Parenthetical used when a caller names nothing.
|
|
3875
|
+
*
|
|
3876
|
+
* True of ANY object this function is pointed at, which is the bar for a
|
|
3877
|
+
* default here: a caller that forgets to describe its object must still emit a
|
|
3878
|
+
* warning that is correct, just less specific. It deliberately does not guess
|
|
3879
|
+
* at content.
|
|
3880
|
+
*/
|
|
3881
|
+
const DEFAULT_OBJECT_DESCRIPTION = "the body of an object cdkd has just reported as removed";
|
|
3882
|
+
/**
|
|
3883
|
+
* `objectDescription` for the custom-resource response sidecar.
|
|
3884
|
+
*
|
|
3885
|
+
* A SHARED CONSTANT rather than the same literal at both sites, for the reason
|
|
3886
|
+
* this whole module is shared: the provider's own `cleanupResponseObject` and
|
|
3887
|
+
* `cdkd gc`'s sweep of the abandoned placeholders delete the SAME object, so a
|
|
3888
|
+
* reader must not be able to tell from the warning which of the two produced
|
|
3889
|
+
* it. Two literals are how one of them drifts — and it is not hypothetical:
|
|
3890
|
+
* review probed it by editing `gc.ts`'s string alone and the suite stayed
|
|
3891
|
+
* green, because nothing tied the two together. One binding cannot drift, and
|
|
3892
|
+
* needs no test to say so.
|
|
3893
|
+
*/
|
|
3894
|
+
const CUSTOM_RESOURCE_RESPONSE_OBJECT_DESCRIPTION = "a custom-resource response object, which is the handler's full cfn-response body including `Data`";
|
|
3895
|
+
/**
|
|
3896
|
+
* `DeleteObjects` is capped at 1000 entries per call.
|
|
3897
|
+
*
|
|
3898
|
+
* DEFENCE IN DEPTH, and unreachable today: `stale` is accumulated from a
|
|
3899
|
+
* SINGLE `ListObjectVersions` page, whose `Versions` + `DeleteMarkers` are
|
|
3900
|
+
* capped at 1000 COMBINED by `MaxKeys`, so the chunking below never takes its
|
|
3901
|
+
* second iteration. It is kept because the invariant it guards ("never hand
|
|
3902
|
+
* DeleteObjects more than 1000") is one a future change accumulating across
|
|
3903
|
+
* pages would silently break.
|
|
3904
|
+
*
|
|
3905
|
+
* Mutation coverage of this constant is ASYMMETRIC, which is worth stating
|
|
3906
|
+
* because the obvious summary is wrong in one direction: RAISING it is green
|
|
3907
|
+
* (nothing ever reaches the second chunk, so a bigger ceiling changes
|
|
3908
|
+
* nothing), while LOWERING it to 500 is RED — the multi-page fixture's
|
|
3909
|
+
* thousand-entry pages then split and the asserted batch shape changes. So the
|
|
3910
|
+
* value is fenced from below and not from above.
|
|
3911
|
+
*/
|
|
3912
|
+
const DELETE_BATCH_SIZE = 1e3;
|
|
3913
|
+
/** How many failing keys the warning names before it truncates. */
|
|
3914
|
+
const MAX_NAMED_FAILURES = 5;
|
|
3915
|
+
/**
|
|
3916
|
+
* Label for a `DeleteObjects` error entry that carries no `Key`.
|
|
3917
|
+
*
|
|
3918
|
+
* S3 always populates it in practice; the point is that an unnameable failure
|
|
3919
|
+
* must still COUNT, because the alternative measured here was `failed.size`
|
|
3920
|
+
* reaching 0 and the whole warning disappearing.
|
|
3921
|
+
*
|
|
3922
|
+
* Each keyless entry gets its OWN slot (`<unknown key #1>`, `#2`, ...) rather
|
|
3923
|
+
* than sharing one. Collapsing them was defended as "the honest reading", but
|
|
3924
|
+
* it is honest about NAMING and not about COUNTING: N keyless failures then
|
|
3925
|
+
* reported `1 key(s)`, which is the same prefixes-not-keys under-count this
|
|
3926
|
+
* change was raised to fix, arriving through the branch that fixed it. One
|
|
3927
|
+
* slot per failure can over-count if S3 ever returns two entries for one
|
|
3928
|
+
* object, which is the direction that errs toward reporting too much.
|
|
3929
|
+
*
|
|
3930
|
+
* The slot name is SYNTHETIC and its uniqueness is not enforced: a real key
|
|
3931
|
+
* literally called `<unknown key #1>` would merge with the first keyless
|
|
3932
|
+
* entry and under-count by one. Unreachable here — every caller passes
|
|
3933
|
+
* `custom-resource-responses/<requestId>.json` — and stated rather than left
|
|
3934
|
+
* implied, because "the name cannot collide" is the kind of unstated
|
|
3935
|
+
* invariant this module exists to stop asserting.
|
|
3936
|
+
*/
|
|
3937
|
+
const UNKNOWN_KEY_PREFIX = "<unknown key #";
|
|
3938
|
+
/** Reason recorded when a page says it is truncated but names no next key. */
|
|
3939
|
+
const TRUNCATED_NO_MARKER = "listing reported IsTruncated with no NextKeyMarker; the walk stopped early and versions may remain";
|
|
3940
|
+
/** Record a per-key failure reason without losing an earlier one. */
|
|
3941
|
+
function recordFailure(failed, key, reason) {
|
|
3942
|
+
const existing = failed.get(key);
|
|
3943
|
+
if (existing) existing.push(reason);
|
|
3944
|
+
else failed.set(key, [reason]);
|
|
3945
|
+
}
|
|
3946
|
+
const describe$1 = (error) => error instanceof Error ? error.message : String(error);
|
|
3947
|
+
async function purgeNoncurrentKeyVersions(s3Client, bucket, keys, options = {}) {
|
|
3948
|
+
if (keys.length === 0) return;
|
|
3949
|
+
const logger = options.logger ?? getLogger().child("s3-version-purge");
|
|
3950
|
+
const requestFields = options.requestFields ?? {};
|
|
3951
|
+
const wanted = new Set(keys);
|
|
3952
|
+
const prefixes = options.listPrefix !== void 0 ? [options.listPrefix] : keys;
|
|
3953
|
+
const failed = /* @__PURE__ */ new Map();
|
|
3954
|
+
const unknown = { n: 0 };
|
|
3955
|
+
for (const prefix of prefixes) try {
|
|
3956
|
+
await purgeUnderPrefix(s3Client, bucket, prefix, wanted, requestFields, failed, unknown);
|
|
3957
|
+
} catch (error) {
|
|
3958
|
+
const affected = options.listPrefix !== void 0 ? keys : [prefix];
|
|
3959
|
+
for (const key of affected) recordFailure(failed, key, describe$1(error));
|
|
3960
|
+
}
|
|
3961
|
+
if (failed.size > 0) {
|
|
3962
|
+
const named = [...failed.entries()].slice(0, MAX_NAMED_FAILURES).map(([key, reasons]) => `${key} (${reasons.join("; ")})`);
|
|
3963
|
+
const elided = failed.size - named.length;
|
|
3964
|
+
logger.warn(`Could not purge noncurrent versions of ${failed.size} key(s) in s3://${bucket}. Their previous versions survive and remain readable via GetObject with a VersionId (${options.objectDescription ?? DEFAULT_OBJECT_DESCRIPTION}). Grant s3:ListBucketVersions and s3:DeleteObjectVersion on the state bucket, or purge the key(s) by hand. Failures: ${named.join(", ")}` + (elided > 0 ? ` (and ${elided} more)` : ""));
|
|
3965
|
+
}
|
|
3966
|
+
}
|
|
3967
|
+
/**
|
|
3968
|
+
* Paginate `ListObjectVersions` under one prefix and delete every returned
|
|
3969
|
+
* entry that is in `wanted` and is not the current version.
|
|
3970
|
+
*
|
|
3971
|
+
* Throws only when the LISTING fails; per-key delete failures are recorded in
|
|
3972
|
+
* `failed` and do not stop the walk.
|
|
3973
|
+
*
|
|
3974
|
+
* Safe on an UNVERSIONED bucket: S3 answers there with the single live object
|
|
3975
|
+
* carrying `VersionId: 'null'` and `IsLatest: true`, which the `IsLatest`
|
|
3976
|
+
* filter drops — so nothing is deleted and nothing throws. A `'null'` version
|
|
3977
|
+
* id is NOT filtered out on its own, because a bucket whose versioning was
|
|
3978
|
+
* SUSPENDED can carry a genuine noncurrent `'null'` version holding the body.
|
|
3979
|
+
*/
|
|
3980
|
+
async function purgeUnderPrefix(s3Client, bucket, prefix, wanted, requestFields, failed, unknown) {
|
|
3981
|
+
let keyMarker;
|
|
3982
|
+
let versionIdMarker;
|
|
3983
|
+
do {
|
|
3984
|
+
const resp = await s3Client.send(new ListObjectVersionsCommand({
|
|
3985
|
+
Bucket: bucket,
|
|
3986
|
+
...requestFields,
|
|
3987
|
+
Prefix: prefix,
|
|
3988
|
+
...keyMarker !== void 0 && { KeyMarker: keyMarker },
|
|
3989
|
+
...versionIdMarker !== void 0 && { VersionIdMarker: versionIdMarker }
|
|
3990
|
+
}));
|
|
3991
|
+
const stale = [];
|
|
3992
|
+
for (const entry of [...resp.Versions ?? [], ...resp.DeleteMarkers ?? []]) {
|
|
3993
|
+
if (entry.Key === void 0 || !wanted.has(entry.Key)) continue;
|
|
3994
|
+
if (entry.IsLatest === void 0) recordFailure(failed, entry.Key, `version ${entry.VersionId ?? "<unknown>"}: listing omitted IsLatest, so the entry was left alone rather than risk deleting a current version`);
|
|
3995
|
+
if (entry.IsLatest !== false) continue;
|
|
3996
|
+
if (!entry.VersionId) continue;
|
|
3997
|
+
stale.push({
|
|
3998
|
+
Key: entry.Key,
|
|
3999
|
+
VersionId: entry.VersionId
|
|
4000
|
+
});
|
|
4001
|
+
}
|
|
4002
|
+
for (let i = 0; i < stale.length; i += DELETE_BATCH_SIZE) {
|
|
4003
|
+
const batch = stale.slice(i, i + DELETE_BATCH_SIZE);
|
|
4004
|
+
try {
|
|
4005
|
+
const deleted = await s3Client.send(new DeleteObjectsCommand({
|
|
4006
|
+
Bucket: bucket,
|
|
4007
|
+
...requestFields,
|
|
4008
|
+
Delete: {
|
|
4009
|
+
Objects: batch,
|
|
4010
|
+
Quiet: true
|
|
4011
|
+
}
|
|
4012
|
+
}));
|
|
4013
|
+
for (const err of deleted.Errors ?? []) {
|
|
4014
|
+
const reason = `version ${err.VersionId ?? "<unknown>"}: ${err.Code ?? "Error"}` + (err.Message ? ` - ${err.Message}` : "");
|
|
4015
|
+
if (err.Key !== void 0) recordFailure(failed, err.Key, reason);
|
|
4016
|
+
else {
|
|
4017
|
+
unknown.n += 1;
|
|
4018
|
+
recordFailure(failed, `${UNKNOWN_KEY_PREFIX}${unknown.n}>`, reason);
|
|
4019
|
+
}
|
|
4020
|
+
}
|
|
4021
|
+
} catch (error) {
|
|
4022
|
+
for (const object of batch) recordFailure(failed, object.Key, describe$1(error));
|
|
4023
|
+
}
|
|
4024
|
+
}
|
|
4025
|
+
if (resp.IsTruncated === true && resp.NextKeyMarker === void 0) {
|
|
4026
|
+
for (const key of wanted) if (key.startsWith(prefix)) recordFailure(failed, key, TRUNCATED_NO_MARKER);
|
|
4027
|
+
}
|
|
4028
|
+
keyMarker = resp.IsTruncated === true ? resp.NextKeyMarker : void 0;
|
|
4029
|
+
versionIdMarker = keyMarker !== void 0 ? resp.NextVersionIdMarker : void 0;
|
|
4030
|
+
} while (keyMarker !== void 0);
|
|
4031
|
+
}
|
|
4032
|
+
|
|
3871
4033
|
//#endregion
|
|
3872
4034
|
//#region src/cli/upload-cfn-template.ts
|
|
3873
4035
|
/**
|
|
@@ -3900,8 +4062,9 @@ const MIGRATE_TMP_PREFIX = "cdkd-migrate-tmp";
|
|
|
3900
4062
|
/**
|
|
3901
4063
|
* Upload a CFn template body to the cdkd state bucket and return both a
|
|
3902
4064
|
* virtual-hosted-style HTTPS URL CloudFormation can fetch via
|
|
3903
|
-
* `TemplateURL` and a `cleanup` callback that deletes the object
|
|
3904
|
-
*
|
|
4065
|
+
* `TemplateURL` and a `cleanup` callback that deletes the object, PURGES its
|
|
4066
|
+
* noncurrent versions (the state bucket is versioned — see the callback), and
|
|
4067
|
+
* destroys the S3 client.
|
|
3905
4068
|
*
|
|
3906
4069
|
* The state bucket's actual region is resolved via `GetBucketLocation`
|
|
3907
4070
|
* (cached per-process) so the upload client and the URL match the
|
|
@@ -3953,7 +4116,17 @@ async function uploadCfnTemplate(args) {
|
|
|
3953
4116
|
...await expectedOwnerParam(s3)
|
|
3954
4117
|
}));
|
|
3955
4118
|
} finally {
|
|
3956
|
-
|
|
4119
|
+
try {
|
|
4120
|
+
await purgeNoncurrentKeyVersions(s3, bucket, [key], {
|
|
4121
|
+
requestFields: await expectedOwnerParam(s3),
|
|
4122
|
+
logger: getLogger(),
|
|
4123
|
+
objectDescription: "the transient CloudFormation template body uploaded for this command"
|
|
4124
|
+
});
|
|
4125
|
+
} catch (purgeError) {
|
|
4126
|
+
getLogger().warn(`Could not purge noncurrent versions of the transient template s3://${bucket}/${key}; its previous versions survive and remain readable via GetObject with a VersionId. Grant s3:ListBucketVersions and s3:DeleteObjectVersion on the state bucket, or purge the key by hand. Underlying error: ${purgeError instanceof Error ? purgeError.message : String(purgeError)}`);
|
|
4127
|
+
} finally {
|
|
4128
|
+
s3.destroy();
|
|
4129
|
+
}
|
|
3957
4130
|
}
|
|
3958
4131
|
};
|
|
3959
4132
|
return {
|
|
@@ -4008,6 +4181,83 @@ function findLargeInlineResources(template, threshold = LARGE_INLINE_RESOURCE_TH
|
|
|
4008
4181
|
return result;
|
|
4009
4182
|
}
|
|
4010
4183
|
|
|
4184
|
+
//#endregion
|
|
4185
|
+
//#region src/utils/parameter-types.ts
|
|
4186
|
+
/**
|
|
4187
|
+
* ONE definition of "is this CloudFormation Parameter `Type` LIST-shaped?".
|
|
4188
|
+
*
|
|
4189
|
+
* cdkd used to hold TWO independent answers to this question (issue
|
|
4190
|
+
* [#2347](https://github.com/go-to-k/cdkd/issues/2347)):
|
|
4191
|
+
*
|
|
4192
|
+
* - `coerceParameterTypedValue` in `src/deployment/intrinsic-function-resolver.ts`
|
|
4193
|
+
* named exactly two list types (`List<Number>`, `CommaDelimitedList`) in a
|
|
4194
|
+
* `switch`, so every other `List<...>` spelling fell to `default` and a
|
|
4195
|
+
* `Ref` to it resolved to the raw comma-joined STRING;
|
|
4196
|
+
* - `stringifyParamDefault` in `src/synthesis/macro-expander.ts` tested
|
|
4197
|
+
* `inner.startsWith('List<') || inner === 'CommaDelimitedList'` when choosing
|
|
4198
|
+
* the placeholder shape for an `AWS::SSM::Parameter::Value<...>` parameter,
|
|
4199
|
+
* i.e. the WIDER, correct view.
|
|
4200
|
+
*
|
|
4201
|
+
* The two disagreeing is what let a `List<AWS::EC2::Subnet::Id>` child
|
|
4202
|
+
* parameter be handed to a nested stack as a string. They now share this
|
|
4203
|
+
* predicate, so a third spelling cannot appear without deleting this file.
|
|
4204
|
+
*
|
|
4205
|
+
* This lives in `src/utils/` rather than beside either consumer because it has
|
|
4206
|
+
* TWO, in different layers -- `src/deployment/intrinsic-function-resolver.ts`
|
|
4207
|
+
* and `src/synthesis/macro-expander.ts`. Hosting it in `src/deployment/` gave
|
|
4208
|
+
* the tree its FIRST `src/synthesis/**` -> `src/deployment/**` import, which
|
|
4209
|
+
* inverts the documented layer order (synthesis runs before deployment); every
|
|
4210
|
+
* other synthesis import goes to `../types`, `../utils` or `../cli`.
|
|
4211
|
+
* `src/utils/ip-protocol.ts` is the precedent, hosted here for the same reason
|
|
4212
|
+
* and stating it in the same place. `src/types/` was the other candidate and is
|
|
4213
|
+
* wrong for this: it carries type declarations plus the constants and helpers
|
|
4214
|
+
* that read them, not a standalone runtime predicate with no type of its own.
|
|
4215
|
+
*
|
|
4216
|
+
* ## What CloudFormation actually defines
|
|
4217
|
+
*
|
|
4218
|
+
* Measured 2026-08-28 against the AWS-published enumerations, NOT against a
|
|
4219
|
+
* library:
|
|
4220
|
+
*
|
|
4221
|
+
* - `parameters-section-structure.html` lists the base types as `String`,
|
|
4222
|
+
* `Number`, `List<Number>`, `CommaDelimitedList`, plus "AWS-specific
|
|
4223
|
+
* parameter types" and "Systems Manager parameter types". **A bare
|
|
4224
|
+
* `List<String>` is NOT in that enumeration.**
|
|
4225
|
+
* - `cloudformation-supplied-parameter-types.html` enumerates ten AWS-specific
|
|
4226
|
+
* SCALAR types and nine `List<AWS::...>` types (`List<AWS::EC2::Subnet::Id>`,
|
|
4227
|
+
* `List<AWS::EC2::SecurityGroup::Id>`, ...). `List<String>` appears only as
|
|
4228
|
+
* the INNER shape of the Systems Manager form
|
|
4229
|
+
* `AWS::SSM::Parameter::Value<List<String>>`.
|
|
4230
|
+
*
|
|
4231
|
+
* `List<String>` is nevertheless accepted here, because `aws-cdk-lib`'s own
|
|
4232
|
+
* `CfnParameter` accepts it (`isListType` in
|
|
4233
|
+
* `node_modules/aws-cdk-lib/core/lib/cfn-parameter.js` is a substring test) and
|
|
4234
|
+
* `valueAsList()` on such a parameter synthesizes a template cdkd will deploy
|
|
4235
|
+
* WITHOUT CloudFormation ever seeing it. Treating it as a list is the reading
|
|
4236
|
+
* that agrees with the app that produced it; the alternative silently hands a
|
|
4237
|
+
* string to something the CDK typed as a string list.
|
|
4238
|
+
*
|
|
4239
|
+
* ## Why this is `startsWith`, not `aws-cdk-lib`'s `indexOf`
|
|
4240
|
+
*
|
|
4241
|
+
* `indexOf('List<') >= 0` also matches `MyList<String>` and, load-bearing here,
|
|
4242
|
+
* the Systems Manager OUTER form `AWS::SSM::Parameter::Value<List<String>>`.
|
|
4243
|
+
* That outer form must NOT be list-shaped for the coercion: the VALUE supplied
|
|
4244
|
+
* for an SSM-typed parameter is a Parameter Store KEY, not the resolved list,
|
|
4245
|
+
* so splitting it on `,` would shred a key rather than build a list. The
|
|
4246
|
+
* macro-expander asks this question of the INNER shape it has already peeled
|
|
4247
|
+
* out of `Value<...>`, so the same predicate serves both sites unchanged.
|
|
4248
|
+
*
|
|
4249
|
+
* A closing `>` is required, so `List<`, `List<>` and `List<String` are NOT
|
|
4250
|
+
* list-shaped. That is the whole of the claim: this predicate is a test of the
|
|
4251
|
+
* SPELLING, not a validator. Measured, `List< >`, `List<a>`, `List<<>>` and
|
|
4252
|
+
* `List<X>>` all return `true` -- nothing here rejects a nonsense inner type,
|
|
4253
|
+
* and cdkd deploys without CloudFormation ever seeing the template, so no
|
|
4254
|
+
* service-side validation stands behind it either.
|
|
4255
|
+
*/
|
|
4256
|
+
function isListParameterType(type) {
|
|
4257
|
+
if (type === "CommaDelimitedList") return true;
|
|
4258
|
+
return type.length > 6 && type.startsWith("List<") && type.endsWith(">");
|
|
4259
|
+
}
|
|
4260
|
+
|
|
4011
4261
|
//#endregion
|
|
4012
4262
|
//#region src/synthesis/macro-expander.ts
|
|
4013
4263
|
/** 600 seconds = 10 minutes. SDK waiter's `maxWaitTime` is in seconds. */
|
|
@@ -4281,8 +4531,7 @@ function stringifyParamDefault(value, type, paramKey, logger) {
|
|
|
4281
4531
|
const known = PARAMETER_TYPE_PLACEHOLDERS[type];
|
|
4282
4532
|
if (known !== void 0) return known;
|
|
4283
4533
|
if (type.startsWith("AWS::SSM::Parameter::Value<")) {
|
|
4284
|
-
|
|
4285
|
-
if (inner.startsWith("List<") || inner === "CommaDelimitedList") return "placeholder,placeholder";
|
|
4534
|
+
if (isListParameterType(type.slice(27, -1))) return "placeholder,placeholder";
|
|
4286
4535
|
return "placeholder";
|
|
4287
4536
|
}
|
|
4288
4537
|
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.`);
|
|
@@ -7524,145 +7773,6 @@ async function rebuildClientForBucketRegion(client, bucket, opts = {}) {
|
|
|
7524
7773
|
return replacement;
|
|
7525
7774
|
}
|
|
7526
7775
|
|
|
7527
|
-
//#endregion
|
|
7528
|
-
//#region src/state/s3-noncurrent-version-purge.ts
|
|
7529
|
-
/**
|
|
7530
|
-
* `DeleteObjects` is capped at 1000 entries per call.
|
|
7531
|
-
*
|
|
7532
|
-
* DEFENCE IN DEPTH, and unreachable today: `stale` is accumulated from a
|
|
7533
|
-
* SINGLE `ListObjectVersions` page, whose `Versions` + `DeleteMarkers` are
|
|
7534
|
-
* capped at 1000 COMBINED by `MaxKeys`, so the chunking below never takes its
|
|
7535
|
-
* second iteration. It is kept because the invariant it guards ("never hand
|
|
7536
|
-
* DeleteObjects more than 1000") is one a future change accumulating across
|
|
7537
|
-
* pages would silently break.
|
|
7538
|
-
*
|
|
7539
|
-
* Mutation coverage of this constant is ASYMMETRIC, which is worth stating
|
|
7540
|
-
* because the obvious summary is wrong in one direction: RAISING it is green
|
|
7541
|
-
* (nothing ever reaches the second chunk, so a bigger ceiling changes
|
|
7542
|
-
* nothing), while LOWERING it to 500 is RED — the multi-page fixture's
|
|
7543
|
-
* thousand-entry pages then split and the asserted batch shape changes. So the
|
|
7544
|
-
* value is fenced from below and not from above.
|
|
7545
|
-
*/
|
|
7546
|
-
const DELETE_BATCH_SIZE = 1e3;
|
|
7547
|
-
/** How many failing keys the warning names before it truncates. */
|
|
7548
|
-
const MAX_NAMED_FAILURES = 5;
|
|
7549
|
-
/**
|
|
7550
|
-
* Label for a `DeleteObjects` error entry that carries no `Key`.
|
|
7551
|
-
*
|
|
7552
|
-
* S3 always populates it in practice; the point is that an unnameable failure
|
|
7553
|
-
* must still COUNT, because the alternative measured here was `failed.size`
|
|
7554
|
-
* reaching 0 and the whole warning disappearing.
|
|
7555
|
-
*
|
|
7556
|
-
* Each keyless entry gets its OWN slot (`<unknown key #1>`, `#2`, ...) rather
|
|
7557
|
-
* than sharing one. Collapsing them was defended as "the honest reading", but
|
|
7558
|
-
* it is honest about NAMING and not about COUNTING: N keyless failures then
|
|
7559
|
-
* reported `1 key(s)`, which is the same prefixes-not-keys under-count this
|
|
7560
|
-
* change was raised to fix, arriving through the branch that fixed it. One
|
|
7561
|
-
* slot per failure can over-count if S3 ever returns two entries for one
|
|
7562
|
-
* object, which is the direction that errs toward reporting too much.
|
|
7563
|
-
*
|
|
7564
|
-
* The slot name is SYNTHETIC and its uniqueness is not enforced: a real key
|
|
7565
|
-
* literally called `<unknown key #1>` would merge with the first keyless
|
|
7566
|
-
* entry and under-count by one. Unreachable here — every caller passes
|
|
7567
|
-
* `custom-resource-responses/<requestId>.json` — and stated rather than left
|
|
7568
|
-
* implied, because "the name cannot collide" is the kind of unstated
|
|
7569
|
-
* invariant this module exists to stop asserting.
|
|
7570
|
-
*/
|
|
7571
|
-
const UNKNOWN_KEY_PREFIX = "<unknown key #";
|
|
7572
|
-
/** Reason recorded when a page says it is truncated but names no next key. */
|
|
7573
|
-
const TRUNCATED_NO_MARKER = "listing reported IsTruncated with no NextKeyMarker; the walk stopped early and versions may remain";
|
|
7574
|
-
/** Record a per-key failure reason without losing an earlier one. */
|
|
7575
|
-
function recordFailure(failed, key, reason) {
|
|
7576
|
-
const existing = failed.get(key);
|
|
7577
|
-
if (existing) existing.push(reason);
|
|
7578
|
-
else failed.set(key, [reason]);
|
|
7579
|
-
}
|
|
7580
|
-
const describe$1 = (error) => error instanceof Error ? error.message : String(error);
|
|
7581
|
-
async function purgeNoncurrentKeyVersions(s3Client, bucket, keys, options = {}) {
|
|
7582
|
-
if (keys.length === 0) return;
|
|
7583
|
-
const logger = options.logger ?? getLogger().child("s3-version-purge");
|
|
7584
|
-
const requestFields = options.requestFields ?? {};
|
|
7585
|
-
const wanted = new Set(keys);
|
|
7586
|
-
const prefixes = options.listPrefix !== void 0 ? [options.listPrefix] : keys;
|
|
7587
|
-
const failed = /* @__PURE__ */ new Map();
|
|
7588
|
-
const unknown = { n: 0 };
|
|
7589
|
-
for (const prefix of prefixes) try {
|
|
7590
|
-
await purgeUnderPrefix(s3Client, bucket, prefix, wanted, requestFields, failed, unknown);
|
|
7591
|
-
} catch (error) {
|
|
7592
|
-
const affected = options.listPrefix !== void 0 ? keys : [prefix];
|
|
7593
|
-
for (const key of affected) recordFailure(failed, key, describe$1(error));
|
|
7594
|
-
}
|
|
7595
|
-
if (failed.size > 0) {
|
|
7596
|
-
const named = [...failed.entries()].slice(0, MAX_NAMED_FAILURES).map(([key, reasons]) => `${key} (${reasons.join("; ")})`);
|
|
7597
|
-
const elided = failed.size - named.length;
|
|
7598
|
-
logger.warn(`Could not purge noncurrent versions of ${failed.size} key(s) in s3://${bucket}. Their previous versions survive and remain readable via GetObject with a VersionId (for a custom-resource response object that is the handler's full response body, including \`Data\`). Grant s3:ListBucketVersions and s3:DeleteObjectVersion on the state bucket, or purge the key(s) by hand. Failures: ${named.join(", ")}` + (elided > 0 ? ` (and ${elided} more)` : ""));
|
|
7599
|
-
}
|
|
7600
|
-
}
|
|
7601
|
-
/**
|
|
7602
|
-
* Paginate `ListObjectVersions` under one prefix and delete every returned
|
|
7603
|
-
* entry that is in `wanted` and is not the current version.
|
|
7604
|
-
*
|
|
7605
|
-
* Throws only when the LISTING fails; per-key delete failures are recorded in
|
|
7606
|
-
* `failed` and do not stop the walk.
|
|
7607
|
-
*
|
|
7608
|
-
* Safe on an UNVERSIONED bucket: S3 answers there with the single live object
|
|
7609
|
-
* carrying `VersionId: 'null'` and `IsLatest: true`, which the `IsLatest`
|
|
7610
|
-
* filter drops — so nothing is deleted and nothing throws. A `'null'` version
|
|
7611
|
-
* id is NOT filtered out on its own, because a bucket whose versioning was
|
|
7612
|
-
* SUSPENDED can carry a genuine noncurrent `'null'` version holding the body.
|
|
7613
|
-
*/
|
|
7614
|
-
async function purgeUnderPrefix(s3Client, bucket, prefix, wanted, requestFields, failed, unknown) {
|
|
7615
|
-
let keyMarker;
|
|
7616
|
-
let versionIdMarker;
|
|
7617
|
-
do {
|
|
7618
|
-
const resp = await s3Client.send(new ListObjectVersionsCommand({
|
|
7619
|
-
Bucket: bucket,
|
|
7620
|
-
...requestFields,
|
|
7621
|
-
Prefix: prefix,
|
|
7622
|
-
...keyMarker !== void 0 && { KeyMarker: keyMarker },
|
|
7623
|
-
...versionIdMarker !== void 0 && { VersionIdMarker: versionIdMarker }
|
|
7624
|
-
}));
|
|
7625
|
-
const stale = [];
|
|
7626
|
-
for (const entry of [...resp.Versions ?? [], ...resp.DeleteMarkers ?? []]) {
|
|
7627
|
-
if (entry.Key === void 0 || !wanted.has(entry.Key)) continue;
|
|
7628
|
-
if (entry.IsLatest !== false) continue;
|
|
7629
|
-
if (!entry.VersionId) continue;
|
|
7630
|
-
stale.push({
|
|
7631
|
-
Key: entry.Key,
|
|
7632
|
-
VersionId: entry.VersionId
|
|
7633
|
-
});
|
|
7634
|
-
}
|
|
7635
|
-
for (let i = 0; i < stale.length; i += DELETE_BATCH_SIZE) {
|
|
7636
|
-
const batch = stale.slice(i, i + DELETE_BATCH_SIZE);
|
|
7637
|
-
try {
|
|
7638
|
-
const deleted = await s3Client.send(new DeleteObjectsCommand({
|
|
7639
|
-
Bucket: bucket,
|
|
7640
|
-
...requestFields,
|
|
7641
|
-
Delete: {
|
|
7642
|
-
Objects: batch,
|
|
7643
|
-
Quiet: true
|
|
7644
|
-
}
|
|
7645
|
-
}));
|
|
7646
|
-
for (const err of deleted.Errors ?? []) {
|
|
7647
|
-
const reason = `version ${err.VersionId ?? "<unknown>"}: ${err.Code ?? "Error"}` + (err.Message ? ` - ${err.Message}` : "");
|
|
7648
|
-
if (err.Key !== void 0) recordFailure(failed, err.Key, reason);
|
|
7649
|
-
else {
|
|
7650
|
-
unknown.n += 1;
|
|
7651
|
-
recordFailure(failed, `${UNKNOWN_KEY_PREFIX}${unknown.n}>`, reason);
|
|
7652
|
-
}
|
|
7653
|
-
}
|
|
7654
|
-
} catch (error) {
|
|
7655
|
-
for (const object of batch) recordFailure(failed, object.Key, describe$1(error));
|
|
7656
|
-
}
|
|
7657
|
-
}
|
|
7658
|
-
if (resp.IsTruncated === true && resp.NextKeyMarker === void 0) {
|
|
7659
|
-
for (const key of wanted) if (key.startsWith(prefix)) recordFailure(failed, key, TRUNCATED_NO_MARKER);
|
|
7660
|
-
}
|
|
7661
|
-
keyMarker = resp.IsTruncated === true ? resp.NextKeyMarker : void 0;
|
|
7662
|
-
versionIdMarker = keyMarker !== void 0 ? resp.NextVersionIdMarker : void 0;
|
|
7663
|
-
} while (keyMarker !== void 0);
|
|
7664
|
-
}
|
|
7665
|
-
|
|
7666
7776
|
//#endregion
|
|
7667
7777
|
//#region src/state/s3-state-backend.ts
|
|
7668
7778
|
/**
|
|
@@ -7984,7 +8094,6 @@ var S3StateBackend = class {
|
|
|
7984
8094
|
}));
|
|
7985
8095
|
this.logger.debug(`Deleted legacy state for stack: ${stackName}`);
|
|
7986
8096
|
}
|
|
7987
|
-
await this.deleteRollbackJournal(stackName, region);
|
|
7988
8097
|
this.logger.debug(`State deleted: ${stackName} (${region})`);
|
|
7989
8098
|
} catch (error) {
|
|
7990
8099
|
const normalized = normalizeAwsError(error, {
|
|
@@ -7992,6 +8101,8 @@ var S3StateBackend = class {
|
|
|
7992
8101
|
operation: "DeleteObject"
|
|
7993
8102
|
});
|
|
7994
8103
|
throw new StateError(`Failed to delete state for stack '${stackName}' (${region}): ${normalized.message}`, normalized);
|
|
8104
|
+
} finally {
|
|
8105
|
+
await this.deleteRollbackJournal(stackName, region);
|
|
7995
8106
|
}
|
|
7996
8107
|
}
|
|
7997
8108
|
/**
|
|
@@ -8238,7 +8349,8 @@ var S3StateBackend = class {
|
|
|
8238
8349
|
logger: this.logger
|
|
8239
8350
|
});
|
|
8240
8351
|
} catch (error) {
|
|
8241
|
-
|
|
8352
|
+
const describing = options.objectDescription ? ` (${options.objectDescription})` : "";
|
|
8353
|
+
this.logger.warn(`Could not purge noncurrent versions of ${keys.length} key(s) in bucket '${this.config.bucket}': the purge could not be started. Their previous versions survive and remain readable via GetObject with a VersionId${describing}. Grant s3:ListBucketVersions and s3:DeleteObjectVersion on the state bucket, or purge the key(s) by hand. Underlying error: ${error instanceof Error ? error.message : String(error)}`);
|
|
8242
8354
|
}
|
|
8243
8355
|
}
|
|
8244
8356
|
/**
|
|
@@ -8311,19 +8423,48 @@ var S3StateBackend = class {
|
|
|
8311
8423
|
* Delete the stack's rollback journal object (idempotent). Called on the
|
|
8312
8424
|
* deploy success path, after a clean rollback, and via {@link deleteState}
|
|
8313
8425
|
* so `cdkd destroy` / `cdkd state destroy` sweep it too.
|
|
8426
|
+
*
|
|
8427
|
+
* TWO steps, and the second is not housekeeping (issue
|
|
8428
|
+
* [#2346](https://github.com/go-to-k/cdkd/issues/2346) site 4). `cdkd
|
|
8429
|
+
* bootstrap` turns VERSIONING ON for the state bucket, so the
|
|
8430
|
+
* `DeleteObject` above only writes a DELETE MARKER and leaves every prior
|
|
8431
|
+
* version of the journal readable through `GetObject` with a `VersionId`.
|
|
8432
|
+
* The journal's `failedOperations[].attemptedProperties` is the PROPERTIES
|
|
8433
|
+
* OF THE FAILED WRITE, verbatim — measured 2026-08-20 on
|
|
8434
|
+
* `CdkdDeletionPolicySnapshotHeavyExample` as four surviving versions each
|
|
8435
|
+
* carrying a literal `"MasterUserPassword": "Cdkdcf2f..."` after cdkd
|
|
8436
|
+
* reported the state deleted (recorded in the `s3_stack_prefix` comment of
|
|
8437
|
+
* `tests/integration/s3-versions.sh`, ~line 270). A
|
|
8438
|
+
* delete-only cleanup therefore reports success while the credential stays
|
|
8439
|
+
* retrievable by anyone holding `s3:GetObjectVersion`.
|
|
8440
|
+
*
|
|
8441
|
+
* Unlike `state.json` — whose noncurrent versions ARE the state-recovery
|
|
8442
|
+
* capability versioning is enabled FOR, which is why {@link deleteState}
|
|
8443
|
+
* deliberately does NOT purge — the journal is TRANSIENT by design: it
|
|
8444
|
+
* exists only between a failed / interrupted deploy and its `cdkd
|
|
8445
|
+
* rollback`. There is no recovery capability to weigh against the purge.
|
|
8446
|
+
*
|
|
8447
|
+
* The purge runs UNCONDITIONALLY, including on the arm where the delete
|
|
8448
|
+
* failed. `purgeNoncurrentKeyVersions` filters on `IsLatest`, so a key
|
|
8449
|
+
* whose delete failed keeps its current version intact and only its history
|
|
8450
|
+
* goes — the worst case is that the object survives while its old bodies do
|
|
8451
|
+
* not, which is the safe direction. Skipping the purge when the delete
|
|
8452
|
+
* threw would leave every readable version behind with no warning at all,
|
|
8453
|
+
* the same partial-failure gap `cdkd gc` closed with its `finally`.
|
|
8314
8454
|
*/
|
|
8315
8455
|
async deleteRollbackJournal(stackName, region) {
|
|
8316
8456
|
await this.ensureClientForBucket();
|
|
8457
|
+
const key = this.getRollbackJournalKey(stackName, region);
|
|
8317
8458
|
try {
|
|
8318
8459
|
await this.s3Client.send(new DeleteObjectCommand({
|
|
8319
8460
|
Bucket: this.config.bucket,
|
|
8320
8461
|
...await this.ownerParam(),
|
|
8321
|
-
Key:
|
|
8462
|
+
Key: key
|
|
8322
8463
|
}));
|
|
8323
8464
|
} catch (error) {
|
|
8324
|
-
if (isNoSuchKey(error)
|
|
8325
|
-
this.logger.warn(`Failed to delete rollback journal for '${stackName}' (${region}): ${error instanceof Error ? error.message : String(error)}`);
|
|
8465
|
+
if (!isNoSuchKey(error) && error.name !== "NotFound") this.logger.warn(`Failed to delete rollback journal for '${stackName}' (${region}): ${error instanceof Error ? error.message : String(error)}`);
|
|
8326
8466
|
}
|
|
8467
|
+
await this.purgeNoncurrentVersions([key], { objectDescription: "the rollback journal, whose `failedOperations[].attemptedProperties` records the properties of the failed write verbatim" });
|
|
8327
8468
|
}
|
|
8328
8469
|
/**
|
|
8329
8470
|
* HeadObject probe — returns true on 200, false on NotFound. Other errors
|
|
@@ -16559,14 +16700,26 @@ function parameterTypeMayLoseSecretIdentity(type) {
|
|
|
16559
16700
|
* ONE definition of parameter-type coercion, at module scope so
|
|
16560
16701
|
* {@link parameterTypeMayLoseSecretIdentity} probes the same code the resolver
|
|
16561
16702
|
* runs rather than a copy of it.
|
|
16703
|
+
*
|
|
16704
|
+
* WHICH TYPES ARE LISTS is asked of the SHARED {@link isListParameterType}
|
|
16705
|
+
* rather than enumerated in the `switch` (issue #2347). The `switch` named only
|
|
16706
|
+
* `List<Number>` and `CommaDelimitedList`, so the nine `List<AWS::...>` types
|
|
16707
|
+
* CloudFormation defines -- `List<AWS::EC2::Subnet::Id>` and its siblings --
|
|
16708
|
+
* fell to `default` and a `Ref` to such a parameter resolved to the raw
|
|
16709
|
+
* comma-joined STRING, while `src/synthesis/macro-expander.ts` held the wider,
|
|
16710
|
+
* correct view of the very same question. Both sites now read one predicate.
|
|
16711
|
+
*
|
|
16712
|
+
* `List<Number>` keeps its own arm because it is the only list type whose
|
|
16713
|
+
* ELEMENTS are not strings; every other list type produces trimmed strings,
|
|
16714
|
+
* which is what CloudFormation says a `Ref` to one returns.
|
|
16562
16715
|
*/
|
|
16563
16716
|
function coerceParameterTypedValue(value, type) {
|
|
16564
16717
|
switch (type) {
|
|
16565
16718
|
case "Number": return Number(value);
|
|
16566
16719
|
case "List<Number>": return value.split(",").map((v) => Number(v.trim()));
|
|
16567
|
-
case "CommaDelimitedList": return value.split(",").map((v) => v.trim());
|
|
16568
|
-
default: return value;
|
|
16569
16720
|
}
|
|
16721
|
+
if (isListParameterType(type)) return value.split(",").map((v) => v.trim());
|
|
16722
|
+
return value;
|
|
16570
16723
|
}
|
|
16571
16724
|
/**
|
|
16572
16725
|
* The inherited `plaintext -> expression` pairs that `value` CARRIES.
|
|
@@ -16587,8 +16740,8 @@ function coerceParameterTypedValue(value, type) {
|
|
|
16587
16740
|
* for the same reason the redactor excludes them: a 3-character secret
|
|
16588
16741
|
* matches half the alphabet's worth of ordinary identifiers.
|
|
16589
16742
|
*
|
|
16590
|
-
* A `CommaDelimitedList`
|
|
16591
|
-
* string elements too.
|
|
16743
|
+
* A LIST-TYPED parameter — any `List<...>` type or `CommaDelimitedList` — arrives as an
|
|
16744
|
+
* array, so the scan walks string elements too.
|
|
16592
16745
|
*/
|
|
16593
16746
|
function inheritedSecretsCarriedBy(value, inherited) {
|
|
16594
16747
|
const candidates = [];
|
|
@@ -17919,7 +18072,7 @@ var IntrinsicFunctionResolver = class IntrinsicFunctionResolver {
|
|
|
17919
18072
|
const [delimiter, rawValues] = joinArgs;
|
|
17920
18073
|
let values = rawValues;
|
|
17921
18074
|
if (!Array.isArray(values)) values = await this.resolveValue(values, context);
|
|
17922
|
-
if (!Array.isArray(values)) throw new Error(`Fn::Join's second argument must be a list (an array literal or a list-returning intrinsic such as Fn::Cidr / Fn::GetAZs / Fn::Split / a Ref to a
|
|
18075
|
+
if (!Array.isArray(values)) throw new Error(`Fn::Join's second argument must be a list (an array literal or a list-returning intrinsic such as Fn::Cidr / Fn::GetAZs / Fn::Split / a Ref to a list-typed parameter — any List<...> type or CommaDelimitedList), but resolved to ${typeof values}`);
|
|
17923
18076
|
let result = (await Promise.all(values.map(async (v) => {
|
|
17924
18077
|
const resolved = await this.resolveValue(v, context);
|
|
17925
18078
|
return String(resolved);
|
|
@@ -18176,9 +18329,10 @@ var IntrinsicFunctionResolver = class IntrinsicFunctionResolver {
|
|
|
18176
18329
|
*
|
|
18177
18330
|
* - a list-valued `Fn::GetAtt` renders as `Fn::GetAtt [Zone, NameServers]`,
|
|
18178
18331
|
* naming both the resource and the attribute;
|
|
18179
|
-
* - a `Ref` to a
|
|
18180
|
-
* SECOND genuinely reachable array
|
|
18181
|
-
* renders as `Ref MyListParam`,
|
|
18332
|
+
* - a `Ref` to a LIST-TYPED parameter — any `List<...>` type or `CommaDelimitedList`, per the
|
|
18333
|
+
* shared `isListParameterType` — the SECOND genuinely reachable array
|
|
18334
|
+
* source, via `coerceParameterValue` — renders as `Ref MyListParam`,
|
|
18335
|
+
* naming the parameter.
|
|
18182
18336
|
*
|
|
18183
18337
|
* Anything else degrades to its bare intrinsic key, or to `undefined` for a
|
|
18184
18338
|
* literal (which the message then simply omits).
|
|
@@ -18283,7 +18437,7 @@ var IntrinsicFunctionResolver = class IntrinsicFunctionResolver {
|
|
|
18283
18437
|
const source = this.describeSplitValueSource(value);
|
|
18284
18438
|
const sourceClause = source ? ` (from ${source.label})` : "";
|
|
18285
18439
|
if (Array.isArray(resolvedValue)) {
|
|
18286
|
-
const remedy = source?.kind === "ref" ?
|
|
18440
|
+
const remedy = source?.kind === "ref" ? "A list-typed parameter — any List<...> type (List<AWS::EC2::Subnet::Id>, List<Number>, …) or CommaDelimitedList — is already a list." : source?.kind === "getatt" ? "A list-valued Fn::GetAtt (for example AWS::Route53::HostedZone.NameServers or AWS::EC2::VPC.Ipv6CidrBlocks) already returns a list. If you wrote the Fn::Split as a workaround for cdkd resolving that attribute to a comma-delimited string, that bug is fixed (PR #1868) and the workaround is no longer needed." : "Several intrinsics already return a list — among them a list-valued Fn::GetAtt, a Ref to a list-typed parameter (any List<...> type or CommaDelimitedList), Fn::GetAZs, Fn::Cidr, and Fn::Split itself.";
|
|
18287
18441
|
throw markNonRetryable(new IntrinsicResolutionRefusalError(`Fn::Split: the value to split${sourceClause} is ALREADY a list (an array of ${resolvedValue.length} item${resolvedValue.length === 1 ? "" : "s"}), not a string. CloudFormation rejects Fn::Split over a list too, so this template is not valid CloudFormation either. Remove the Fn::Split and use the value directly. ${remedy}`));
|
|
18288
18442
|
}
|
|
18289
18443
|
throw markNonRetryable(new IntrinsicResolutionRefusalError(`Fn::Split: the value to split${sourceClause} must be a string, got ${resolvedValue === null ? "null" : typeof resolvedValue}. Fn::Split accepts only a string; check the value or the intrinsic that produced it.`));
|
|
@@ -20387,7 +20541,7 @@ var CloudControlProvider = class {
|
|
|
20387
20541
|
await this.confirmDeleteTargetIdentity(logicalId, resourceType, physicalId, context);
|
|
20388
20542
|
if (context?.removeProtection === true && resourceType === "AWS::AutoScaling::AutoScalingGroup") {
|
|
20389
20543
|
this.logger.debug(`Delegating protected AutoScalingGroup ${logicalId} delete to the SDK ASGProvider (Cloud Control cannot force-delete a protected ASG)`);
|
|
20390
|
-
const { ASGProvider } = await import("./asg-provider-
|
|
20544
|
+
const { ASGProvider } = await import("./asg-provider-B-KONBYk.js").then((n) => n.n);
|
|
20391
20545
|
return await new ASGProvider().delete(logicalId, physicalId, resourceType, _properties, context);
|
|
20392
20546
|
}
|
|
20393
20547
|
const isProtectedEc2Instance = context?.removeProtection === true && resourceType === "AWS::EC2::Instance";
|
|
@@ -23299,7 +23453,10 @@ var CustomResourceProvider = class CustomResourceProvider {
|
|
|
23299
23453
|
} catch (error) {
|
|
23300
23454
|
this.logger.debug(`Failed to delete custom-resource response object s3://${bucket}/${responseKey}; it remains as a current object. Underlying error: ${error instanceof Error ? error.message : String(error)}`);
|
|
23301
23455
|
}
|
|
23302
|
-
await purgeNoncurrentKeyVersions(this.s3Client, bucket, [responseKey], {
|
|
23456
|
+
await purgeNoncurrentKeyVersions(this.s3Client, bucket, [responseKey], {
|
|
23457
|
+
logger: this.logger,
|
|
23458
|
+
objectDescription: CUSTOM_RESOURCE_RESPONSE_OBJECT_DESCRIPTION
|
|
23459
|
+
});
|
|
23303
23460
|
}
|
|
23304
23461
|
/**
|
|
23305
23462
|
* Convert property values to strings for CloudFormation compatibility
|
|
@@ -32301,5 +32458,5 @@ var DeployEngine = class {
|
|
|
32301
32458
|
};
|
|
32302
32459
|
|
|
32303
32460
|
//#endregion
|
|
32304
|
-
export { maskerOrIdentity as $, CFN_TEMPLATE_BODY_LIMIT as $n, maskSecretsInText as $t, renderStatefulReason as A, describeAwsFailure as An,
|
|
32305
|
-
//# sourceMappingURL=deploy-engine-
|
|
32461
|
+
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-BES1Z20a.js.map
|