@go-to-k/cdkd 0.257.2 → 0.257.4
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-4KjJhMTY.js → asg-provider-BhmMKcnJ.js} +2 -2
- package/dist/{asg-provider-4KjJhMTY.js.map → asg-provider-BhmMKcnJ.js.map} +1 -1
- package/dist/cli.js +209 -1715
- package/dist/cli.js.map +1 -1
- package/dist/{deploy-engine-wmvXbyoB.js → deploy-engine-DMpr3qtv.js} +263 -437
- package/dist/deploy-engine-DMpr3qtv.js.map +1 -0
- package/dist/index.d.ts +5 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/package.json +1 -1
- package/dist/deploy-engine-wmvXbyoB.js.map +0 -1
|
@@ -2,7 +2,7 @@ import { a as getLiveRenderer, d as generateResourceNameWithFallback, l as apply
|
|
|
2
2
|
import { randomUUID } from "node:crypto";
|
|
3
3
|
import { CreateBucketCommand, DeleteObjectCommand, DeleteObjectsCommand, GetBucketLocationCommand, GetObjectCommand, HeadBucketCommand, HeadObjectCommand, ListObjectsV2Command, NoSuchKey, PutBucketEncryptionCommand, PutBucketPolicyCommand, PutObjectCommand, PutPublicAccessBlockCommand, S3Client, S3ServiceException } from "@aws-sdk/client-s3";
|
|
4
4
|
import { CloudControlClient, CreateResourceCommand, DeleteResourceCommand, GetResourceCommand, GetResourceRequestStatusCommand, ListResourcesCommand, UpdateResourceCommand } from "@aws-sdk/client-cloudcontrol";
|
|
5
|
-
import { AttachRolePolicyCommand, CreateRoleCommand, DeleteRoleCommand, DeleteRolePermissionsBoundaryCommand, DeleteRolePolicyCommand, DetachRolePolicyCommand, GetRoleCommand, GetRolePolicyCommand, IAMClient, ListAttachedRolePoliciesCommand, ListInstanceProfilesForRoleCommand, ListRolePoliciesCommand, ListRoleTagsCommand,
|
|
5
|
+
import { AttachRolePolicyCommand, CreateRoleCommand, DeleteRoleCommand, DeleteRolePermissionsBoundaryCommand, DeleteRolePolicyCommand, DetachRolePolicyCommand, GetRoleCommand, GetRolePolicyCommand, IAMClient, ListAttachedRolePoliciesCommand, ListInstanceProfilesForRoleCommand, ListRolePoliciesCommand, ListRoleTagsCommand, NoSuchEntityException, PutRolePermissionsBoundaryCommand, PutRolePolicyCommand, RemoveRoleFromInstanceProfileCommand, TagRoleCommand, UntagRoleCommand, UpdateAssumeRolePolicyCommand, UpdateRoleCommand } from "@aws-sdk/client-iam";
|
|
6
6
|
import { SQSClient } from "@aws-sdk/client-sqs";
|
|
7
7
|
import { PublishCommand, SNSClient } from "@aws-sdk/client-sns";
|
|
8
8
|
import { GetFunctionCommand, GetFunctionUrlConfigCommand, InvokeCommand, LambdaClient, UpdateFunctionConfigurationCommand, waitUntilFunctionActiveV2, waitUntilFunctionUpdatedV2 } from "@aws-sdk/client-lambda";
|
|
@@ -35,7 +35,7 @@ import { DescribeDBClustersCommand, DescribeDBInstancesCommand, RDSClient } from
|
|
|
35
35
|
import { DescribeReplicationGroupsCommand, ElastiCacheClient } from "@aws-sdk/client-elasticache";
|
|
36
36
|
import { DescribeClustersCommand, RedshiftClient } from "@aws-sdk/client-redshift";
|
|
37
37
|
import { DescribeDomainCommand, OpenSearchClient } from "@aws-sdk/client-opensearch";
|
|
38
|
-
import { CreateWebACLCommand, DeleteWebACLCommand, GetWebACLCommand, ListTagsForResourceCommand as ListTagsForResourceCommand$11,
|
|
38
|
+
import { CreateWebACLCommand, DeleteWebACLCommand, GetWebACLCommand, ListTagsForResourceCommand as ListTagsForResourceCommand$11, TagResourceCommand as TagResourceCommand$12, UntagResourceCommand as UntagResourceCommand$12, UpdateWebACLCommand, WAFNonexistentItemException, WAFV2Client } from "@aws-sdk/client-wafv2";
|
|
39
39
|
import { getSignedUrl } from "@aws-sdk/s3-request-presigner";
|
|
40
40
|
|
|
41
41
|
//#region \0rolldown/runtime.js
|
|
@@ -2488,6 +2488,25 @@ function findLargeInlineResources(template, threshold = LARGE_INLINE_RESOURCE_TH
|
|
|
2488
2488
|
const WAITER_MAX_WAIT_SECONDS = 600;
|
|
2489
2489
|
const PARAMETER_PLACEHOLDER = "cdkd-macro-expand-placeholder";
|
|
2490
2490
|
/**
|
|
2491
|
+
* AWS's managed pre-deployment validation hooks
|
|
2492
|
+
* (`AWS::EarlyValidation::*`, e.g. `ResourceExistenceCheck`) can
|
|
2493
|
+
* reject the transient expansion changeset INTERMITTENTLY (issue
|
|
2494
|
+
* #1151: two consecutive rejections followed by a clean pass on the
|
|
2495
|
+
* same template minutes later, with the same named resources
|
|
2496
|
+
* existing throughout). The changeset is never executed — cdkd only
|
|
2497
|
+
* reads the Processed-stage template — so a validation-hook rejection
|
|
2498
|
+
* carries no real risk and is worth retrying with a fresh transient
|
|
2499
|
+
* stack before failing the whole run.
|
|
2500
|
+
*/
|
|
2501
|
+
const EARLY_VALIDATION_MAX_ATTEMPTS = 3;
|
|
2502
|
+
const EARLY_VALIDATION_RETRY_BASE_DELAY_MS = 2e3;
|
|
2503
|
+
/** Matches the changeset FAILED StatusReason emitted by the hook family. */
|
|
2504
|
+
function isEarlyValidationRejection(err) {
|
|
2505
|
+
return err instanceof MacroExpansionError && /AWS::EarlyValidation::/.test(err.message);
|
|
2506
|
+
}
|
|
2507
|
+
/** Test seam: overridable sleep so retry tests don't wait wall-clock. */
|
|
2508
|
+
const retryDelays = { sleep: (ms) => new Promise((resolve) => setTimeout(resolve, ms)) };
|
|
2509
|
+
/**
|
|
2491
2510
|
* Capabilities sent on every `CreateChangeSet` call:
|
|
2492
2511
|
*
|
|
2493
2512
|
* - `CAPABILITY_AUTO_EXPAND` is the load-bearing one — required for CFn
|
|
@@ -2583,6 +2602,19 @@ const PARAMETER_TYPE_PLACEHOLDERS = {
|
|
|
2583
2602
|
async function expandMacros(template, opts) {
|
|
2584
2603
|
const logger = getLogger().child("MacroExpander");
|
|
2585
2604
|
if (!containsMacro(template)) return template;
|
|
2605
|
+
for (let attempt = 1;; attempt++) try {
|
|
2606
|
+
return await expandMacrosAttempt(template, opts, logger);
|
|
2607
|
+
} catch (err) {
|
|
2608
|
+
if (attempt < EARLY_VALIDATION_MAX_ATTEMPTS && isEarlyValidationRejection(err)) {
|
|
2609
|
+
const delayMs = EARLY_VALIDATION_RETRY_BASE_DELAY_MS * 2 ** (attempt - 1);
|
|
2610
|
+
logger.warn(`Macro expansion changeset was rejected by an AWS EarlyValidation hook (attempt ${attempt}/${EARLY_VALIDATION_MAX_ATTEMPTS}); this rejection is known to be intermittent — retrying with a fresh transient stack in ${delayMs / 1e3}s...`);
|
|
2611
|
+
await retryDelays.sleep(delayMs);
|
|
2612
|
+
continue;
|
|
2613
|
+
}
|
|
2614
|
+
throw err;
|
|
2615
|
+
}
|
|
2616
|
+
}
|
|
2617
|
+
async function expandMacrosAttempt(template, opts, logger) {
|
|
2586
2618
|
const macros = enumerateMacros(template);
|
|
2587
2619
|
logger.debug(`Macro expansion: detected transforms [${macros.join(", ")}], starting CFn round-trip...`);
|
|
2588
2620
|
const transientStackName = `cdkd-macro-expand-${randomUUID().slice(0, 16)}`;
|
|
@@ -3131,19 +3163,7 @@ var Synthesizer = class {
|
|
|
3131
3163
|
this.logger.debug(`Using pre-synthesized cloud assembly at ${appPath}`);
|
|
3132
3164
|
const manifest = this.assemblyReader.readManifest(appPath);
|
|
3133
3165
|
const stacks = this.assemblyReader.getAllStacks(appPath, manifest);
|
|
3134
|
-
|
|
3135
|
-
let presynthAccountId;
|
|
3136
|
-
try {
|
|
3137
|
-
const stsClient = new STSClient({ ...presynthRegion && { region: presynthRegion } });
|
|
3138
|
-
presynthAccountId = (await stsClient.send(new GetCallerIdentityCommand({}))).Account;
|
|
3139
|
-
stsClient.destroy();
|
|
3140
|
-
} catch {
|
|
3141
|
-
this.logger.debug("Could not resolve AWS account ID via STS (pre-synth branch)");
|
|
3142
|
-
}
|
|
3143
|
-
await this.expandMacrosForStacks(stacks, options, {
|
|
3144
|
-
region: presynthRegion,
|
|
3145
|
-
...presynthAccountId && { accountId: presynthAccountId }
|
|
3146
|
-
});
|
|
3166
|
+
if (!options.deferMacroExpansion) await this.expandMacrosForStacks(stacks, options);
|
|
3147
3167
|
this.logger.debug(`Loaded ${stacks.length} stack(s) from pre-synthesized assembly`);
|
|
3148
3168
|
return {
|
|
3149
3169
|
manifest,
|
|
@@ -3162,7 +3182,8 @@ var Synthesizer = class {
|
|
|
3162
3182
|
"aws:cdk:version-reporting": true,
|
|
3163
3183
|
"aws:cdk:bundling-stacks": ["**"]
|
|
3164
3184
|
};
|
|
3165
|
-
const
|
|
3185
|
+
const explicitRegion = options.region || process.env["AWS_REGION"] || process.env["AWS_DEFAULT_REGION"];
|
|
3186
|
+
const region = explicitRegion || await resolveSdkDefaultRegion(options.profile);
|
|
3166
3187
|
let accountId;
|
|
3167
3188
|
try {
|
|
3168
3189
|
const stsClient = new STSClient({ ...region && { region } });
|
|
@@ -3196,8 +3217,8 @@ var Synthesizer = class {
|
|
|
3196
3217
|
const manifest = this.assemblyReader.readManifest(outputDir);
|
|
3197
3218
|
if (!manifest.missing || manifest.missing.length === 0) {
|
|
3198
3219
|
const stacks = this.assemblyReader.getAllStacks(outputDir, manifest);
|
|
3199
|
-
await this.expandMacrosForStacks(stacks, options, {
|
|
3200
|
-
region,
|
|
3220
|
+
if (!options.deferMacroExpansion) await this.expandMacrosForStacks(stacks, options, {
|
|
3221
|
+
region: explicitRegion,
|
|
3201
3222
|
...accountId && { accountId }
|
|
3202
3223
|
});
|
|
3203
3224
|
this.logger.debug(`Synthesis complete: ${stacks.length} stack(s)`);
|
|
@@ -3218,10 +3239,16 @@ var Synthesizer = class {
|
|
|
3218
3239
|
}
|
|
3219
3240
|
}
|
|
3220
3241
|
/**
|
|
3221
|
-
* List stack names in CDK app
|
|
3242
|
+
* List stack names in CDK app. Names come straight from the
|
|
3243
|
+
* manifest, so the macro-expansion pre-pass is skipped entirely —
|
|
3244
|
+
* listing a macro app needs no AWS region or CloudFormation access
|
|
3245
|
+
* (issue #1150).
|
|
3222
3246
|
*/
|
|
3223
3247
|
async listStacks(options) {
|
|
3224
|
-
return (await this.synthesize(
|
|
3248
|
+
return (await this.synthesize({
|
|
3249
|
+
...options,
|
|
3250
|
+
deferMacroExpansion: true
|
|
3251
|
+
})).stacks.map((s) => s.stackName);
|
|
3225
3252
|
}
|
|
3226
3253
|
/**
|
|
3227
3254
|
* Per-stack macro-expansion pass (Issue #463). Mutates each stack's
|
|
@@ -3230,23 +3257,36 @@ var Synthesizer = class {
|
|
|
3230
3257
|
* analyzer / provisioner pipeline consumes the templates, so every
|
|
3231
3258
|
* downstream stage sees the post-expansion shape.
|
|
3232
3259
|
*
|
|
3233
|
-
*
|
|
3234
|
-
*
|
|
3235
|
-
*
|
|
3236
|
-
*
|
|
3237
|
-
*
|
|
3238
|
-
*
|
|
3239
|
-
*
|
|
3240
|
-
*
|
|
3260
|
+
* PUBLIC since issue #1150: selection-aware commands (deploy / diff)
|
|
3261
|
+
* synthesize with `deferMacroExpansion: true` and invoke this
|
|
3262
|
+
* directly with ONLY the stacks they will actually consume, so an
|
|
3263
|
+
* unselected macro-carrying sibling can never fail or slow the run.
|
|
3264
|
+
*
|
|
3265
|
+
* Skipped silently when no stack carries a macro — pure no-op cost
|
|
3266
|
+
* (in particular, no STS hop). When a macro IS detected and the
|
|
3267
|
+
* caller did NOT thread a resolved region/account, both are
|
|
3268
|
+
* resolved here (STS for the default state bucket; the region chain
|
|
3269
|
+
* below for the CFn client). Throws `SynthesisError` when no region
|
|
3270
|
+
* can be resolved (the upstream caller treats it as a synth
|
|
3271
|
+
* failure) and propagates `MacroExpansionError` (from {@link
|
|
3272
|
+
* expandMacros}) on any CFn-side failure during the round-trip.
|
|
3241
3273
|
*/
|
|
3242
3274
|
async expandMacrosForStacks(stacks, options, resolved) {
|
|
3243
3275
|
const stacksWithMacros = stacks.filter((s) => containsMacro(s.template));
|
|
3244
3276
|
if (stacksWithMacros.length === 0) return;
|
|
3245
|
-
const region = resolved?.region || options.region || process.env["AWS_REGION"] || process.env["AWS_DEFAULT_REGION"] || stacksWithMacros[0]?.region;
|
|
3277
|
+
const region = resolved?.region || options.region || process.env["AWS_REGION"] || process.env["AWS_DEFAULT_REGION"] || stacksWithMacros[0]?.region || await resolveSdkDefaultRegion(options.profile);
|
|
3246
3278
|
if (!region) throw new SynthesisError(`Stack(s) [${stacksWithMacros.map((s) => s.stackName).join(", ")}] use CloudFormation macros (Transform / Fn::Transform) but cdkd could not resolve an AWS region for the expansion round-trip. Set AWS_REGION, pass --region <r>, or set env: { region: '<r>' } in your CDK Stack constructor.`);
|
|
3279
|
+
let accountId = resolved?.accountId;
|
|
3280
|
+
if (resolved === void 0 && !options.stateBucket) try {
|
|
3281
|
+
const stsClient = new STSClient({ region });
|
|
3282
|
+
accountId = (await stsClient.send(new GetCallerIdentityCommand({}))).Account;
|
|
3283
|
+
stsClient.destroy();
|
|
3284
|
+
} catch {
|
|
3285
|
+
this.logger.debug("Could not resolve AWS account ID via STS (macro expansion)");
|
|
3286
|
+
}
|
|
3247
3287
|
let stateBucket;
|
|
3248
3288
|
if (options.stateBucket) stateBucket = options.stateBucket;
|
|
3249
|
-
else if (
|
|
3289
|
+
else if (accountId) stateBucket = `cdkd-state-${accountId}`;
|
|
3250
3290
|
else {
|
|
3251
3291
|
const oversize = stacksWithMacros.find((s) => JSON.stringify(s.template).length > 51200);
|
|
3252
3292
|
if (oversize) throw new SynthesisError(`Stack '${oversize.stackName}' uses CloudFormation macros AND its serialized template exceeds the 51,200-byte inline TemplateBody limit, so cdkd must upload the template to S3 for the transient expansion changeset. cdkd could not resolve a state bucket: STS GetCallerIdentity failed AND --state-bucket was not provided. Pass --state-bucket <name> (cdkd uses the same bucket as cdkd deploy state storage; typically 'cdkd-state-<accountId>').`);
|
|
@@ -3268,6 +3308,31 @@ var Synthesizer = class {
|
|
|
3268
3308
|
}
|
|
3269
3309
|
};
|
|
3270
3310
|
/**
|
|
3311
|
+
* Resolve the AWS SDK's own default region (env vars, shared config
|
|
3312
|
+
* file profile region, etc.) — the same chain every cdkd AWS client
|
|
3313
|
+
* consults implicitly when constructed without an explicit region.
|
|
3314
|
+
* Returns `undefined` when the chain yields nothing so callers can
|
|
3315
|
+
* fall through to their own hard-error (issue #1149: the
|
|
3316
|
+
* macro-expansion pre-pass previously checked only explicit
|
|
3317
|
+
* option/env sources and hard-errored for users whose region lives in
|
|
3318
|
+
* `~/.aws/config`).
|
|
3319
|
+
*
|
|
3320
|
+
* Implemented by asking a throwaway STS client for its resolved
|
|
3321
|
+
* region provider — that is exactly the chain the real provisioning
|
|
3322
|
+
* clients use, so the two can never diverge.
|
|
3323
|
+
*/
|
|
3324
|
+
async function resolveSdkDefaultRegion(profile) {
|
|
3325
|
+
let client;
|
|
3326
|
+
try {
|
|
3327
|
+
client = new STSClient({ ...profile && { profile } });
|
|
3328
|
+
return await client.config.region() || void 0;
|
|
3329
|
+
} catch {
|
|
3330
|
+
return;
|
|
3331
|
+
} finally {
|
|
3332
|
+
client?.destroy?.();
|
|
3333
|
+
}
|
|
3334
|
+
}
|
|
3335
|
+
/**
|
|
3271
3336
|
* Check if two sets contain the same elements
|
|
3272
3337
|
*/
|
|
3273
3338
|
function setsEqual(a, b) {
|
|
@@ -8158,21 +8223,6 @@ function resolveExplicitPhysicalId(input, nameProperty) {
|
|
|
8158
8223
|
}
|
|
8159
8224
|
}
|
|
8160
8225
|
/**
|
|
8161
|
-
* The standard tag CDK puts on every deployed resource — its construct path
|
|
8162
|
-
* within the app, e.g. `MyStack/MyConstruct/MyBucket`. Used as the lookup key
|
|
8163
|
-
* when no explicit name is in the template.
|
|
8164
|
-
*/
|
|
8165
|
-
const CDK_PATH_TAG = "aws:cdk:path";
|
|
8166
|
-
/**
|
|
8167
|
-
* Match an AWS resource's tag set against the CDK path the template carries.
|
|
8168
|
-
* Returns true if the resource was deployed by the same CDK construct.
|
|
8169
|
-
*/
|
|
8170
|
-
function matchesCdkPath(tags, cdkPath) {
|
|
8171
|
-
if (!tags || !cdkPath) return false;
|
|
8172
|
-
for (const t of tags) if (t.Key === "aws:cdk:path" && t.Value === cdkPath) return true;
|
|
8173
|
-
return false;
|
|
8174
|
-
}
|
|
8175
|
-
/**
|
|
8176
8226
|
* Re-shape an AWS tag list (any of the common shapes — array of `{Key, Value}`,
|
|
8177
8227
|
* map keyed by tag name, or v2-style array of `{TagKey, TagValue}`) into the
|
|
8178
8228
|
* canonical CFn shape (`Array<{Key, Value}>`) that cdkd state holds, with
|
|
@@ -8215,330 +8265,6 @@ function normalizeAwsTagsToCfn(tags) {
|
|
|
8215
8265
|
return out;
|
|
8216
8266
|
}
|
|
8217
8267
|
|
|
8218
|
-
//#endregion
|
|
8219
|
-
//#region src/deployment/retryable-errors.ts
|
|
8220
|
-
/**
|
|
8221
|
-
* Patterns that mark an AWS error as a transient/retryable failure.
|
|
8222
|
-
* Each entry is a substring match against the error message; all of these
|
|
8223
|
-
* are situations where the same call typically succeeds after a short delay
|
|
8224
|
-
* because of eventual consistency or just-created-dependency propagation.
|
|
8225
|
-
*/
|
|
8226
|
-
const RETRYABLE_ERROR_MESSAGE_PATTERNS = [
|
|
8227
|
-
"cannot be assumed",
|
|
8228
|
-
"Firehose is unable to assume role",
|
|
8229
|
-
"is unable to assume provided role",
|
|
8230
|
-
"role defined for the function",
|
|
8231
|
-
"not authorized to perform",
|
|
8232
|
-
"execution role",
|
|
8233
|
-
"trust policy",
|
|
8234
|
-
"Role validation failed",
|
|
8235
|
-
"does not have required permissions",
|
|
8236
|
-
"Trusted Entity",
|
|
8237
|
-
"currently in the following state: Pending",
|
|
8238
|
-
"has dependencies and cannot be deleted",
|
|
8239
|
-
"can't be deleted since it has",
|
|
8240
|
-
"DependencyViolation",
|
|
8241
|
-
"does not exist",
|
|
8242
|
-
"Schema is currently being altered",
|
|
8243
|
-
"Invalid principal in policy",
|
|
8244
|
-
"Policy Error: PrincipalNotFound",
|
|
8245
|
-
"Invalid value for the parameter Policy",
|
|
8246
|
-
"required permissions for: ENHANCED_MONITORING",
|
|
8247
|
-
"Caught ServiceAccessDeniedException",
|
|
8248
|
-
"permissions required to assume the role",
|
|
8249
|
-
"authorized to assume the provided role",
|
|
8250
|
-
"conflicting conditional operation",
|
|
8251
|
-
"scheduled for deletion",
|
|
8252
|
-
"Cannot access stream",
|
|
8253
|
-
"Please ensure the role can perform",
|
|
8254
|
-
"KMS key is invalid for CreateGrant",
|
|
8255
|
-
"Policy contains a statement with one or more invalid principals",
|
|
8256
|
-
"Invalid IAM Instance Profile",
|
|
8257
|
-
"Invalid InstanceProfile",
|
|
8258
|
-
"Failed to authorize instance profile",
|
|
8259
|
-
"Could not deliver test message",
|
|
8260
|
-
"wait 60 seconds",
|
|
8261
|
-
"concurrent update operation",
|
|
8262
|
-
"because it is in use",
|
|
8263
|
-
"Rate exceeded"
|
|
8264
|
-
];
|
|
8265
|
-
/**
|
|
8266
|
-
* HTTP status codes that always indicate a transient failure worth retrying.
|
|
8267
|
-
* 429 = Too Many Requests (throttle), 503 = Service Unavailable.
|
|
8268
|
-
*/
|
|
8269
|
-
const RETRYABLE_HTTP_STATUS_CODES = /* @__PURE__ */ new Set([429, 503]);
|
|
8270
|
-
/**
|
|
8271
|
-
* AWS SDK v3 canonical throttling error names. Mirrors
|
|
8272
|
-
* `@aws-sdk/service-error-classification`'s `THROTTLING_ERROR_CODES` — any
|
|
8273
|
-
* error (or wrapped cause) whose `name` is one of these is a transient rate-
|
|
8274
|
-
* limit rejection worth retrying with backoff. Detecting by NAME is more
|
|
8275
|
-
* robust than by HTTP status because most AWS throttles surface as HTTP 400
|
|
8276
|
-
* (not 429) with the throttling signal carried only in the error code / name
|
|
8277
|
-
* (e.g. SSM `ThrottlingException` for the `Rate exceeded` message).
|
|
8278
|
-
*/
|
|
8279
|
-
const THROTTLING_ERROR_NAMES = /* @__PURE__ */ new Set([
|
|
8280
|
-
"BandwidthLimitExceeded",
|
|
8281
|
-
"EC2ThrottledException",
|
|
8282
|
-
"LimitExceededException",
|
|
8283
|
-
"PriorRequestNotComplete",
|
|
8284
|
-
"ProvisionedThroughputExceededException",
|
|
8285
|
-
"RequestLimitExceeded",
|
|
8286
|
-
"RequestThrottled",
|
|
8287
|
-
"RequestThrottledException",
|
|
8288
|
-
"SlowDown",
|
|
8289
|
-
"ThrottledException",
|
|
8290
|
-
"Throttling",
|
|
8291
|
-
"ThrottlingException",
|
|
8292
|
-
"TooManyRequestsException",
|
|
8293
|
-
"TransactionInProgressException"
|
|
8294
|
-
]);
|
|
8295
|
-
/**
|
|
8296
|
-
* Walk the error + its `.cause` chain (bounded) looking for a rate-limit
|
|
8297
|
-
* signal — either an AWS SDK v3 throttling error `name`
|
|
8298
|
-
* ({@link THROTTLING_ERROR_NAMES}) or a retryable HTTP status
|
|
8299
|
-
* ({@link RETRYABLE_HTTP_STATUS_CODES}) on `$metadata`.
|
|
8300
|
-
*
|
|
8301
|
-
* cdkd wraps the original AWS error in a `ProvisioningError`, so the signal is
|
|
8302
|
-
* typically one cause-link deep; the bounded walk also tolerates SDK errors
|
|
8303
|
-
* that nest a `$response`/cause without exploding on a cyclic chain.
|
|
8304
|
-
*
|
|
8305
|
-
* BOTH signals are checked at EVERY depth. An earlier version checked the name
|
|
8306
|
-
* to depth 5 but the HTTP status only at depths 0 and 1, so a 429 nested two
|
|
8307
|
-
* links deep was missed. This is the single shared cause-walk: the read-only
|
|
8308
|
-
* import tag walk (`src/provisioning/import-tag-walk.ts`) composes its own
|
|
8309
|
-
* classifier on top of this function rather than re-implementing the traversal,
|
|
8310
|
-
* so the two cannot drift apart again.
|
|
8311
|
-
*/
|
|
8312
|
-
function isThrottlingError(error) {
|
|
8313
|
-
let current = error;
|
|
8314
|
-
for (let depth = 0; depth < 5 && current != null; depth++) {
|
|
8315
|
-
const name = current.name;
|
|
8316
|
-
if (typeof name === "string" && THROTTLING_ERROR_NAMES.has(name)) return true;
|
|
8317
|
-
const status = current.$metadata?.httpStatusCode;
|
|
8318
|
-
if (status !== void 0 && RETRYABLE_HTTP_STATUS_CODES.has(status)) return true;
|
|
8319
|
-
current = current.cause;
|
|
8320
|
-
}
|
|
8321
|
-
return false;
|
|
8322
|
-
}
|
|
8323
|
-
/**
|
|
8324
|
-
* Determine whether an AWS error should be retried.
|
|
8325
|
-
*
|
|
8326
|
-
* Checks (in order):
|
|
8327
|
-
* 1. Rate-limit signal on the error or any wrapped cause — throttling error
|
|
8328
|
-
* `name` or retryable HTTP status (most AWS throttles are HTTP 400, not
|
|
8329
|
-
* 429, so the name check carries most of the weight). See
|
|
8330
|
-
* {@link isThrottlingError}.
|
|
8331
|
-
* 2. Substring match against {@link RETRYABLE_ERROR_MESSAGE_PATTERNS}
|
|
8332
|
-
*/
|
|
8333
|
-
function isRetryableTransientError(error, message) {
|
|
8334
|
-
if (isThrottlingError(error)) return true;
|
|
8335
|
-
return RETRYABLE_ERROR_MESSAGE_PATTERNS.some((p) => message.includes(p));
|
|
8336
|
-
}
|
|
8337
|
-
|
|
8338
|
-
//#endregion
|
|
8339
|
-
//#region src/deployment/retry.ts
|
|
8340
|
-
/**
|
|
8341
|
-
* Retry helper for resource provisioning operations that hit transient
|
|
8342
|
-
* AWS eventual-consistency errors (IAM propagation, Lambda Pending state,
|
|
8343
|
-
* dependency violations, etc.).
|
|
8344
|
-
*
|
|
8345
|
-
* Extracted from DeployEngine so the backoff schedule can be unit-tested
|
|
8346
|
-
* in isolation. The retryable-error classifier itself lives in
|
|
8347
|
-
* `./retryable-errors.ts`.
|
|
8348
|
-
*/
|
|
8349
|
-
const defaultSleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
8350
|
-
/**
|
|
8351
|
-
* Run `operation`, retrying transient failures with exponential backoff
|
|
8352
|
-
* capped at `maxDelayMs`.
|
|
8353
|
-
*
|
|
8354
|
-
* Backoff at the defaults (initialDelayMs=1_000, maxDelayMs=8_000, maxRetries=8):
|
|
8355
|
-
* 1s -> 2s -> 4s -> 8s -> 8s -> 8s -> 8s -> 8s (cumulative 47s)
|
|
8356
|
-
*
|
|
8357
|
-
* Non-retryable errors are rethrown immediately. The transient-error
|
|
8358
|
-
* classifier is `isRetryableTransientError` from ./retryable-errors.ts.
|
|
8359
|
-
*/
|
|
8360
|
-
async function withRetry(operation, logicalId, opts = {}) {
|
|
8361
|
-
const maxRetries = opts.maxRetries ?? 8;
|
|
8362
|
-
const initialDelayMs = opts.initialDelayMs ?? 1e3;
|
|
8363
|
-
const maxDelayMs = opts.maxDelayMs ?? 8e3;
|
|
8364
|
-
const sleep = opts.sleep ?? defaultSleep;
|
|
8365
|
-
let lastError;
|
|
8366
|
-
for (let attempt = 0; attempt <= maxRetries; attempt++) try {
|
|
8367
|
-
return await operation();
|
|
8368
|
-
} catch (error) {
|
|
8369
|
-
lastError = error;
|
|
8370
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
8371
|
-
if (!(opts.isRetryable ? opts.isRetryable(message, error) : isRetryableTransientError(error, message)) || attempt >= maxRetries) throw error;
|
|
8372
|
-
const delay = Math.min(initialDelayMs * Math.pow(2, attempt), maxDelayMs);
|
|
8373
|
-
opts.logger?.debug(` ⏳ Retrying ${logicalId} in ${delay / 1e3}s (attempt ${attempt + 1}/${maxRetries}) - ${message}`);
|
|
8374
|
-
for (let waited = 0; waited < delay; waited += 1e3) {
|
|
8375
|
-
if (opts.isInterrupted?.()) throw opts.onInterrupted ? opts.onInterrupted() : /* @__PURE__ */ new Error("Interrupted");
|
|
8376
|
-
await sleep(Math.min(1e3, delay - waited));
|
|
8377
|
-
}
|
|
8378
|
-
}
|
|
8379
|
-
throw lastError;
|
|
8380
|
-
}
|
|
8381
|
-
|
|
8382
|
-
//#endregion
|
|
8383
|
-
//#region src/provisioning/import-tag-walk.ts
|
|
8384
|
-
/**
|
|
8385
|
-
* Shared, throttle-tolerant `aws:cdk:path` tag walk for `ResourceProvider.import`.
|
|
8386
|
-
*
|
|
8387
|
-
* Providers that adopt a resource without an explicit name property fall back
|
|
8388
|
-
* to step 3 of the lookup order documented in `./import-helpers.ts`: enumerate
|
|
8389
|
-
* the service's `List*`/`Describe*` pages, then issue ONE per-candidate read
|
|
8390
|
-
* (`DescribeX` / `ListTagsForResource`) to obtain the tag set — the list
|
|
8391
|
-
* summaries usually do not carry tags. That is an inherent **N+1** read
|
|
8392
|
-
* pattern: an account with many resources of the type produces one API call
|
|
8393
|
-
* per candidate in a tight loop, which is exactly the shape AWS rate-limits.
|
|
8394
|
-
*
|
|
8395
|
-
* Every provider previously hand-rolled this loop with NO backoff, so a single
|
|
8396
|
-
* throttled `Describe*` aborted the whole `cdkd import` run. This helper
|
|
8397
|
-
* centralises the loop and wraps BOTH the list page fetch and the per-candidate
|
|
8398
|
-
* describe in the deploy engine's `withRetry` with exponential backoff.
|
|
8399
|
-
*
|
|
8400
|
-
* ## Why a narrower classifier than `isRetryableTransientError`
|
|
8401
|
-
*
|
|
8402
|
-
* The deploy engine's classifier is tuned for the WRITE path: it also treats
|
|
8403
|
-
* eventual-consistency phrasings like `does not exist` / `not authorized to
|
|
8404
|
-
* perform` as transient, because a just-created dependency legitimately needs a
|
|
8405
|
-
* moment to propagate. On a read-only import walk those messages mean the
|
|
8406
|
-
* opposite — the candidate really is gone, or the caller's credentials really
|
|
8407
|
-
* lack the permission — and retrying them burns the full backoff budget per
|
|
8408
|
-
* candidate before surfacing the true error.
|
|
8409
|
-
*
|
|
8410
|
-
* So the walk retries throttling ONLY, reusing the deploy engine's throttle
|
|
8411
|
-
* tables verbatim ({@link THROTTLING_ERROR_NAMES} /
|
|
8412
|
-
* {@link RETRYABLE_HTTP_STATUS_CODES}) rather than maintaining a second copy.
|
|
8413
|
-
*
|
|
8414
|
-
* ## Batching
|
|
8415
|
-
*
|
|
8416
|
-
* Batched tag reads are deliberately NOT modelled here: the services this
|
|
8417
|
-
* helper currently serves (EMR `DescribeCluster`, DocDB
|
|
8418
|
-
* `ListTagsForResource`) expose only single-resource reads. A service that
|
|
8419
|
-
* genuinely offers a batch API (e.g. CodeCommit `BatchGetRepositories`) can
|
|
8420
|
-
* satisfy several candidates from one call inside its own `describe` callback,
|
|
8421
|
-
* or bypass the helper entirely.
|
|
8422
|
-
*/
|
|
8423
|
-
/** Max number of retries after the first attempt, per API call in the walk. */
|
|
8424
|
-
const DEFAULT_MAX_RETRIES = 5;
|
|
8425
|
-
/** Initial backoff; each retry doubles it up to {@link DEFAULT_MAX_DELAY_MS}. */
|
|
8426
|
-
const DEFAULT_INITIAL_DELAY_MS = 500;
|
|
8427
|
-
/** Cap for the per-retry delay (0.5s -> 1s -> 2s -> 4s -> 5s, ~12.5s total). */
|
|
8428
|
-
const DEFAULT_MAX_DELAY_MS = 5e3;
|
|
8429
|
-
/**
|
|
8430
|
-
* Wall-clock ceiling for the WHOLE walk (all pages + all candidates), not just
|
|
8431
|
-
* one call. Backoff is per-call, so without this a sustained throttle against a
|
|
8432
|
-
* large account degrades into `(pages + candidates) x ~12.5s` of near-silent
|
|
8433
|
-
* retrying — ~42 minutes for 200 candidates, with no way to tell a slow walk
|
|
8434
|
-
* from a hung one. 10 minutes is generous for a healthy walk (a 200-candidate
|
|
8435
|
-
* DocDB account completes in seconds when AWS is not throttling) and bounds the
|
|
8436
|
-
* pathological case to something a user will wait through.
|
|
8437
|
-
*/
|
|
8438
|
-
const DEFAULT_MAX_WALK_MS = 10 * 6e4;
|
|
8439
|
-
/**
|
|
8440
|
-
* Ceiling on pages fetched. A service that returns a non-advancing pagination
|
|
8441
|
-
* token (a bug, or a marker cdkd echoes back wrongly) would otherwise spin
|
|
8442
|
-
* forever. This is now the shared path every migrated provider runs on, so the
|
|
8443
|
-
* guard belongs here rather than in each caller.
|
|
8444
|
-
*/
|
|
8445
|
-
const DEFAULT_MAX_PAGES = 1e3;
|
|
8446
|
-
/**
|
|
8447
|
-
* Whether an error hit during the read-only tag walk is a rate-limit rejection
|
|
8448
|
-
* worth backing off on.
|
|
8449
|
-
*
|
|
8450
|
-
* Delegates the error + `.cause` chain traversal to the deploy engine's
|
|
8451
|
-
* {@link isThrottlingError} (throttling error names + retryable HTTP statuses,
|
|
8452
|
-
* at every cause depth) and adds the canonical `Rate exceeded` message, which
|
|
8453
|
-
* several services return with an HTTP 400 and a service-specific error name.
|
|
8454
|
-
*
|
|
8455
|
-
* This is deliberately NARROWER than `isRetryableTransientError`: that
|
|
8456
|
-
* classifier also treats write-path eventual-consistency phrasings (`does not
|
|
8457
|
-
* exist`, `not authorized to perform`) as transient, because a just-created
|
|
8458
|
-
* dependency legitimately needs a moment to propagate. On a read-only walk
|
|
8459
|
-
* those mean the candidate really is gone or the credentials really lack the
|
|
8460
|
-
* permission, and retrying them burns the full backoff budget per candidate
|
|
8461
|
-
* before surfacing the true error.
|
|
8462
|
-
*
|
|
8463
|
-
* Exported for direct unit testing and for providers whose tag walk cannot use
|
|
8464
|
-
* {@link importTagWalk} verbatim.
|
|
8465
|
-
*/
|
|
8466
|
-
function isThrottlingLikeError(error, message) {
|
|
8467
|
-
return isThrottlingError(error) || message.includes("Rate exceeded");
|
|
8468
|
-
}
|
|
8469
|
-
/**
|
|
8470
|
-
* Module-level test hooks for the walk's backoff.
|
|
8471
|
-
*
|
|
8472
|
-
* Providers do not expose a retry option on `import()`, so their wiring tests
|
|
8473
|
-
* cannot inject `retry.sleep` per call — without this hook every throttle-path
|
|
8474
|
-
* wiring test pays a real 0.5s+ backoff wait. Tests set `sleep` to a resolved
|
|
8475
|
-
* no-op in `beforeEach` (and clear it in `afterEach`); a per-call
|
|
8476
|
-
* `retry.sleep` still wins when supplied. Never set this in production code.
|
|
8477
|
-
*/
|
|
8478
|
-
const importTagWalkTestHooks = {};
|
|
8479
|
-
/** Thrown when the walk exceeds its wall-clock budget or page ceiling. */
|
|
8480
|
-
var ImportTagWalkLimitError = class extends Error {
|
|
8481
|
-
constructor(message) {
|
|
8482
|
-
super(message);
|
|
8483
|
-
this.name = "ImportTagWalkLimitError";
|
|
8484
|
-
}
|
|
8485
|
-
};
|
|
8486
|
-
/**
|
|
8487
|
-
* Paginate `listPage`, `describe` each candidate, and return the first one
|
|
8488
|
-
* whose tags carry the requested `aws:cdk:path`. Returns `null` when no
|
|
8489
|
-
* candidate matches (or when `cdkPath` is empty).
|
|
8490
|
-
*
|
|
8491
|
-
* Both callbacks are individually retried with exponential backoff on
|
|
8492
|
-
* throttling errors, so one rate-limited call mid-walk no longer aborts the
|
|
8493
|
-
* whole import.
|
|
8494
|
-
*/
|
|
8495
|
-
async function importTagWalk(options) {
|
|
8496
|
-
const { cdkPath, listPage, describe, tagsOf } = options;
|
|
8497
|
-
if (!cdkPath) return null;
|
|
8498
|
-
const logicalId = options.logicalId ?? "import";
|
|
8499
|
-
const logger = options.retry?.logger ?? getLogger();
|
|
8500
|
-
const maxWalkMs = options.retry?.maxWalkMs ?? DEFAULT_MAX_WALK_MS;
|
|
8501
|
-
const maxPages = options.retry?.maxPages ?? DEFAULT_MAX_PAGES;
|
|
8502
|
-
const sleep = options.retry?.sleep ?? importTagWalkTestHooks.sleep;
|
|
8503
|
-
const retryOpts = {
|
|
8504
|
-
maxRetries: options.retry?.maxRetries ?? DEFAULT_MAX_RETRIES,
|
|
8505
|
-
initialDelayMs: options.retry?.initialDelayMs ?? DEFAULT_INITIAL_DELAY_MS,
|
|
8506
|
-
maxDelayMs: options.retry?.maxDelayMs ?? DEFAULT_MAX_DELAY_MS,
|
|
8507
|
-
isRetryable: (message, error) => isThrottlingLikeError(error, message),
|
|
8508
|
-
logger,
|
|
8509
|
-
...options.retry?.isInterrupted && { isInterrupted: options.retry.isInterrupted },
|
|
8510
|
-
...options.retry?.onInterrupted && { onInterrupted: options.retry.onInterrupted },
|
|
8511
|
-
...sleep && { sleep }
|
|
8512
|
-
};
|
|
8513
|
-
const startedAt = Date.now();
|
|
8514
|
-
const assertWithinBudget = (stage) => {
|
|
8515
|
-
const elapsed = Date.now() - startedAt;
|
|
8516
|
-
if (elapsed >= maxWalkMs) throw new ImportTagWalkLimitError(`Timed out looking up ${logicalId} by its aws:cdk:path tag after ${Math.round(elapsed / 1e3)}s (limit ${Math.round(maxWalkMs / 1e3)}s, while ${stage}). AWS is likely throttling the lookup; retry, or pass an explicit physical id with --resource ${logicalId}=<physicalId>.`);
|
|
8517
|
-
};
|
|
8518
|
-
let marker;
|
|
8519
|
-
let pages = 0;
|
|
8520
|
-
do {
|
|
8521
|
-
assertWithinBudget("listing candidates");
|
|
8522
|
-
if (++pages > maxPages) throw new ImportTagWalkLimitError(`Gave up looking up ${logicalId} by its aws:cdk:path tag after ${maxPages} pages (the service may be returning a non-advancing pagination token). Pass an explicit physical id with --resource ${logicalId}=<physicalId>.`);
|
|
8523
|
-
const currentMarker = marker;
|
|
8524
|
-
const page = await withRetry(() => listPage(currentMarker), logicalId, retryOpts);
|
|
8525
|
-
for (const summary of page.items ?? []) {
|
|
8526
|
-
assertWithinBudget("reading candidate tags");
|
|
8527
|
-
const detail = await withRetry(() => describe(summary), logicalId, retryOpts);
|
|
8528
|
-
if (detail === void 0) {
|
|
8529
|
-
logger.debug(` ↷ Skipped an ${logicalId} import candidate: its detail lookup returned no result`);
|
|
8530
|
-
continue;
|
|
8531
|
-
}
|
|
8532
|
-
if (matchesCdkPath(tagsOf(detail, summary), cdkPath)) return {
|
|
8533
|
-
summary,
|
|
8534
|
-
detail
|
|
8535
|
-
};
|
|
8536
|
-
}
|
|
8537
|
-
marker = page.nextMarker;
|
|
8538
|
-
} while (marker);
|
|
8539
|
-
return null;
|
|
8540
|
-
}
|
|
8541
|
-
|
|
8542
8268
|
//#endregion
|
|
8543
8269
|
//#region src/provisioning/providers/wafv2-provider.ts
|
|
8544
8270
|
/**
|
|
@@ -8830,12 +8556,6 @@ var WAFv2WebACLProvider = class {
|
|
|
8830
8556
|
* Lookup order:
|
|
8831
8557
|
* 1. `--resource <id>=<arn>` override → verify with `GetWebACL` (parses
|
|
8832
8558
|
* Name/Id/Scope back out of the ARN).
|
|
8833
|
-
* 2. Walk `ListWebACLs(Scope)` → match `aws:cdk:path` via
|
|
8834
|
-
* `ListTagsForResource(ResourceARN)` (returns
|
|
8835
|
-
* `TagInfoForResource.TagList`, standard `Key`/`Value` shape).
|
|
8836
|
-
*
|
|
8837
|
-
* `Scope` is required by `ListWebACLs` — read from
|
|
8838
|
-
* `Properties.Scope` (`REGIONAL` is the default).
|
|
8839
8559
|
*/
|
|
8840
8560
|
async import(input) {
|
|
8841
8561
|
if (input.knownPhysicalId) try {
|
|
@@ -8853,31 +8573,7 @@ var WAFv2WebACLProvider = class {
|
|
|
8853
8573
|
if (err instanceof WAFNonexistentItemException) return null;
|
|
8854
8574
|
throw err;
|
|
8855
8575
|
}
|
|
8856
|
-
|
|
8857
|
-
const match = await importTagWalk({
|
|
8858
|
-
cdkPath: input.cdkPath,
|
|
8859
|
-
logicalId: input.logicalId,
|
|
8860
|
-
listPage: async (marker) => {
|
|
8861
|
-
const list = await this.getClient().send(new ListWebACLsCommand({
|
|
8862
|
-
Scope: scope,
|
|
8863
|
-
...marker && { NextMarker: marker }
|
|
8864
|
-
}));
|
|
8865
|
-
return {
|
|
8866
|
-
items: list.WebACLs,
|
|
8867
|
-
nextMarker: list.NextMarker
|
|
8868
|
-
};
|
|
8869
|
-
},
|
|
8870
|
-
describe: async (item) => {
|
|
8871
|
-
if (!item.ARN) return void 0;
|
|
8872
|
-
return await this.getClient().send(new ListTagsForResourceCommand$11({ ResourceARN: item.ARN }));
|
|
8873
|
-
},
|
|
8874
|
-
tagsOf: (tagsResp) => tagsResp.TagInfoForResource?.TagList
|
|
8875
|
-
});
|
|
8876
|
-
if (!match) return null;
|
|
8877
|
-
return {
|
|
8878
|
-
physicalId: match.summary.ARN,
|
|
8879
|
-
attributes: {}
|
|
8880
|
-
};
|
|
8576
|
+
return null;
|
|
8881
8577
|
}
|
|
8882
8578
|
};
|
|
8883
8579
|
|
|
@@ -11343,7 +11039,7 @@ var CloudControlProvider = class {
|
|
|
11343
11039
|
this.logger.debug(`Deleting resource ${logicalId} (${resourceType}), physical ID: ${physicalId}`);
|
|
11344
11040
|
if (context?.removeProtection === true && resourceType === "AWS::AutoScaling::AutoScalingGroup") {
|
|
11345
11041
|
this.logger.debug(`Delegating protected AutoScalingGroup ${logicalId} delete to the SDK ASGProvider (Cloud Control cannot force-delete a protected ASG)`);
|
|
11346
|
-
const { ASGProvider } = await import("./asg-provider-
|
|
11042
|
+
const { ASGProvider } = await import("./asg-provider-BhmMKcnJ.js").then((n) => n.n);
|
|
11347
11043
|
await new ASGProvider().delete(logicalId, physicalId, resourceType, _properties, context);
|
|
11348
11044
|
return;
|
|
11349
11045
|
}
|
|
@@ -15673,12 +15369,6 @@ var IAMRoleProvider = class {
|
|
|
15673
15369
|
* Lookup order:
|
|
15674
15370
|
* 1. `--resource` override or `Properties.RoleName` → use directly,
|
|
15675
15371
|
* verify via `GetRole`.
|
|
15676
|
-
* 2. `ListRoles` + `ListRoleTags`, match `aws:cdk:path` tag.
|
|
15677
|
-
*
|
|
15678
|
-
* `ListRoles` is paginated and IAM is global (no region scoping), so this
|
|
15679
|
-
* walks every role in the account once. Acceptable for the cardinalities
|
|
15680
|
-
* we expect (typically <100 roles per account); larger accounts may want
|
|
15681
|
-
* to provide `--resource` overrides instead.
|
|
15682
15372
|
*/
|
|
15683
15373
|
async import(input) {
|
|
15684
15374
|
const explicit = resolveExplicitPhysicalId(input, "RoleName");
|
|
@@ -15692,32 +15382,7 @@ var IAMRoleProvider = class {
|
|
|
15692
15382
|
if (err instanceof NoSuchEntityException) return null;
|
|
15693
15383
|
throw err;
|
|
15694
15384
|
}
|
|
15695
|
-
|
|
15696
|
-
cdkPath: input.cdkPath,
|
|
15697
|
-
logicalId: input.logicalId,
|
|
15698
|
-
listPage: async (marker) => {
|
|
15699
|
-
const list = await this.iamClient.send(new ListRolesCommand({ ...marker && { Marker: marker } }));
|
|
15700
|
-
return {
|
|
15701
|
-
items: list.Roles,
|
|
15702
|
-
nextMarker: list.IsTruncated ? list.Marker : void 0
|
|
15703
|
-
};
|
|
15704
|
-
},
|
|
15705
|
-
describe: async (role) => {
|
|
15706
|
-
if (!role.RoleName) return void 0;
|
|
15707
|
-
try {
|
|
15708
|
-
return await this.iamClient.send(new ListRoleTagsCommand({ RoleName: role.RoleName }));
|
|
15709
|
-
} catch (err) {
|
|
15710
|
-
if (err instanceof NoSuchEntityException) return void 0;
|
|
15711
|
-
throw err;
|
|
15712
|
-
}
|
|
15713
|
-
},
|
|
15714
|
-
tagsOf: (tags) => tags.Tags
|
|
15715
|
-
});
|
|
15716
|
-
if (!match) return null;
|
|
15717
|
-
return {
|
|
15718
|
-
physicalId: match.summary.RoleName,
|
|
15719
|
-
attributes: {}
|
|
15720
|
-
};
|
|
15385
|
+
return null;
|
|
15721
15386
|
}
|
|
15722
15387
|
};
|
|
15723
15388
|
/**
|
|
@@ -16213,6 +15878,167 @@ function computeImplicitDeleteEdges(resources) {
|
|
|
16213
15878
|
return edges;
|
|
16214
15879
|
}
|
|
16215
15880
|
|
|
15881
|
+
//#endregion
|
|
15882
|
+
//#region src/deployment/retryable-errors.ts
|
|
15883
|
+
/**
|
|
15884
|
+
* Patterns that mark an AWS error as a transient/retryable failure.
|
|
15885
|
+
* Each entry is a substring match against the error message; all of these
|
|
15886
|
+
* are situations where the same call typically succeeds after a short delay
|
|
15887
|
+
* because of eventual consistency or just-created-dependency propagation.
|
|
15888
|
+
*/
|
|
15889
|
+
const RETRYABLE_ERROR_MESSAGE_PATTERNS = [
|
|
15890
|
+
"cannot be assumed",
|
|
15891
|
+
"Firehose is unable to assume role",
|
|
15892
|
+
"is unable to assume provided role",
|
|
15893
|
+
"role defined for the function",
|
|
15894
|
+
"not authorized to perform",
|
|
15895
|
+
"execution role",
|
|
15896
|
+
"trust policy",
|
|
15897
|
+
"Role validation failed",
|
|
15898
|
+
"does not have required permissions",
|
|
15899
|
+
"Trusted Entity",
|
|
15900
|
+
"currently in the following state: Pending",
|
|
15901
|
+
"has dependencies and cannot be deleted",
|
|
15902
|
+
"can't be deleted since it has",
|
|
15903
|
+
"DependencyViolation",
|
|
15904
|
+
"does not exist",
|
|
15905
|
+
"Schema is currently being altered",
|
|
15906
|
+
"Invalid principal in policy",
|
|
15907
|
+
"Policy Error: PrincipalNotFound",
|
|
15908
|
+
"Invalid value for the parameter Policy",
|
|
15909
|
+
"required permissions for: ENHANCED_MONITORING",
|
|
15910
|
+
"Caught ServiceAccessDeniedException",
|
|
15911
|
+
"permissions required to assume the role",
|
|
15912
|
+
"authorized to assume the provided role",
|
|
15913
|
+
"conflicting conditional operation",
|
|
15914
|
+
"scheduled for deletion",
|
|
15915
|
+
"Cannot access stream",
|
|
15916
|
+
"Please ensure the role can perform",
|
|
15917
|
+
"KMS key is invalid for CreateGrant",
|
|
15918
|
+
"Policy contains a statement with one or more invalid principals",
|
|
15919
|
+
"Invalid IAM Instance Profile",
|
|
15920
|
+
"Invalid InstanceProfile",
|
|
15921
|
+
"Failed to authorize instance profile",
|
|
15922
|
+
"Could not deliver test message",
|
|
15923
|
+
"wait 60 seconds",
|
|
15924
|
+
"concurrent update operation",
|
|
15925
|
+
"because it is in use",
|
|
15926
|
+
"Rate exceeded"
|
|
15927
|
+
];
|
|
15928
|
+
/**
|
|
15929
|
+
* HTTP status codes that always indicate a transient failure worth retrying.
|
|
15930
|
+
* 429 = Too Many Requests (throttle), 503 = Service Unavailable.
|
|
15931
|
+
*/
|
|
15932
|
+
const RETRYABLE_HTTP_STATUS_CODES = /* @__PURE__ */ new Set([429, 503]);
|
|
15933
|
+
/**
|
|
15934
|
+
* AWS SDK v3 canonical throttling error names. Mirrors
|
|
15935
|
+
* `@aws-sdk/service-error-classification`'s `THROTTLING_ERROR_CODES` — any
|
|
15936
|
+
* error (or wrapped cause) whose `name` is one of these is a transient rate-
|
|
15937
|
+
* limit rejection worth retrying with backoff. Detecting by NAME is more
|
|
15938
|
+
* robust than by HTTP status because most AWS throttles surface as HTTP 400
|
|
15939
|
+
* (not 429) with the throttling signal carried only in the error code / name
|
|
15940
|
+
* (e.g. SSM `ThrottlingException` for the `Rate exceeded` message).
|
|
15941
|
+
*/
|
|
15942
|
+
const THROTTLING_ERROR_NAMES = /* @__PURE__ */ new Set([
|
|
15943
|
+
"BandwidthLimitExceeded",
|
|
15944
|
+
"EC2ThrottledException",
|
|
15945
|
+
"LimitExceededException",
|
|
15946
|
+
"PriorRequestNotComplete",
|
|
15947
|
+
"ProvisionedThroughputExceededException",
|
|
15948
|
+
"RequestLimitExceeded",
|
|
15949
|
+
"RequestThrottled",
|
|
15950
|
+
"RequestThrottledException",
|
|
15951
|
+
"SlowDown",
|
|
15952
|
+
"ThrottledException",
|
|
15953
|
+
"Throttling",
|
|
15954
|
+
"ThrottlingException",
|
|
15955
|
+
"TooManyRequestsException",
|
|
15956
|
+
"TransactionInProgressException"
|
|
15957
|
+
]);
|
|
15958
|
+
/**
|
|
15959
|
+
* Walk the error + its `.cause` chain (bounded) looking for a rate-limit
|
|
15960
|
+
* signal — either an AWS SDK v3 throttling error `name`
|
|
15961
|
+
* ({@link THROTTLING_ERROR_NAMES}) or a retryable HTTP status
|
|
15962
|
+
* ({@link RETRYABLE_HTTP_STATUS_CODES}) on `$metadata`.
|
|
15963
|
+
*
|
|
15964
|
+
* cdkd wraps the original AWS error in a `ProvisioningError`, so the signal is
|
|
15965
|
+
* typically one cause-link deep; the bounded walk also tolerates SDK errors
|
|
15966
|
+
* that nest a `$response`/cause without exploding on a cyclic chain.
|
|
15967
|
+
*
|
|
15968
|
+
* BOTH signals are checked at EVERY depth. An earlier version checked the name
|
|
15969
|
+
* to depth 5 but the HTTP status only at depths 0 and 1, so a 429 nested two
|
|
15970
|
+
* links deep was missed.
|
|
15971
|
+
*/
|
|
15972
|
+
function isThrottlingError(error) {
|
|
15973
|
+
let current = error;
|
|
15974
|
+
for (let depth = 0; depth < 5 && current != null; depth++) {
|
|
15975
|
+
const name = current.name;
|
|
15976
|
+
if (typeof name === "string" && THROTTLING_ERROR_NAMES.has(name)) return true;
|
|
15977
|
+
const status = current.$metadata?.httpStatusCode;
|
|
15978
|
+
if (status !== void 0 && RETRYABLE_HTTP_STATUS_CODES.has(status)) return true;
|
|
15979
|
+
current = current.cause;
|
|
15980
|
+
}
|
|
15981
|
+
return false;
|
|
15982
|
+
}
|
|
15983
|
+
/**
|
|
15984
|
+
* Determine whether an AWS error should be retried.
|
|
15985
|
+
*
|
|
15986
|
+
* Checks (in order):
|
|
15987
|
+
* 1. Rate-limit signal on the error or any wrapped cause — throttling error
|
|
15988
|
+
* `name` or retryable HTTP status (most AWS throttles are HTTP 400, not
|
|
15989
|
+
* 429, so the name check carries most of the weight). See
|
|
15990
|
+
* {@link isThrottlingError}.
|
|
15991
|
+
* 2. Substring match against {@link RETRYABLE_ERROR_MESSAGE_PATTERNS}
|
|
15992
|
+
*/
|
|
15993
|
+
function isRetryableTransientError(error, message) {
|
|
15994
|
+
if (isThrottlingError(error)) return true;
|
|
15995
|
+
return RETRYABLE_ERROR_MESSAGE_PATTERNS.some((p) => message.includes(p));
|
|
15996
|
+
}
|
|
15997
|
+
|
|
15998
|
+
//#endregion
|
|
15999
|
+
//#region src/deployment/retry.ts
|
|
16000
|
+
/**
|
|
16001
|
+
* Retry helper for resource provisioning operations that hit transient
|
|
16002
|
+
* AWS eventual-consistency errors (IAM propagation, Lambda Pending state,
|
|
16003
|
+
* dependency violations, etc.).
|
|
16004
|
+
*
|
|
16005
|
+
* Extracted from DeployEngine so the backoff schedule can be unit-tested
|
|
16006
|
+
* in isolation. The retryable-error classifier itself lives in
|
|
16007
|
+
* `./retryable-errors.ts`.
|
|
16008
|
+
*/
|
|
16009
|
+
const defaultSleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
16010
|
+
/**
|
|
16011
|
+
* Run `operation`, retrying transient failures with exponential backoff
|
|
16012
|
+
* capped at `maxDelayMs`.
|
|
16013
|
+
*
|
|
16014
|
+
* Backoff at the defaults (initialDelayMs=1_000, maxDelayMs=8_000, maxRetries=8):
|
|
16015
|
+
* 1s -> 2s -> 4s -> 8s -> 8s -> 8s -> 8s -> 8s (cumulative 47s)
|
|
16016
|
+
*
|
|
16017
|
+
* Non-retryable errors are rethrown immediately. The transient-error
|
|
16018
|
+
* classifier is `isRetryableTransientError` from ./retryable-errors.ts.
|
|
16019
|
+
*/
|
|
16020
|
+
async function withRetry(operation, logicalId, opts = {}) {
|
|
16021
|
+
const maxRetries = opts.maxRetries ?? 8;
|
|
16022
|
+
const initialDelayMs = opts.initialDelayMs ?? 1e3;
|
|
16023
|
+
const maxDelayMs = opts.maxDelayMs ?? 8e3;
|
|
16024
|
+
const sleep = opts.sleep ?? defaultSleep;
|
|
16025
|
+
let lastError;
|
|
16026
|
+
for (let attempt = 0; attempt <= maxRetries; attempt++) try {
|
|
16027
|
+
return await operation();
|
|
16028
|
+
} catch (error) {
|
|
16029
|
+
lastError = error;
|
|
16030
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
16031
|
+
if (!(opts.isRetryable ? opts.isRetryable(message, error) : isRetryableTransientError(error, message)) || attempt >= maxRetries) throw error;
|
|
16032
|
+
const delay = Math.min(initialDelayMs * Math.pow(2, attempt), maxDelayMs);
|
|
16033
|
+
opts.logger?.debug(` ⏳ Retrying ${logicalId} in ${delay / 1e3}s (attempt ${attempt + 1}/${maxRetries}) - ${message}`);
|
|
16034
|
+
for (let waited = 0; waited < delay; waited += 1e3) {
|
|
16035
|
+
if (opts.isInterrupted?.()) throw opts.onInterrupted ? opts.onInterrupted() : /* @__PURE__ */ new Error("Interrupted");
|
|
16036
|
+
await sleep(Math.min(1e3, delay - waited));
|
|
16037
|
+
}
|
|
16038
|
+
}
|
|
16039
|
+
throw lastError;
|
|
16040
|
+
}
|
|
16041
|
+
|
|
16216
16042
|
//#endregion
|
|
16217
16043
|
//#region src/deployment/resource-deadline.ts
|
|
16218
16044
|
/**
|
|
@@ -17447,8 +17273,9 @@ var DeployEngine = class {
|
|
|
17447
17273
|
const recreateFlagged = recreateViaCcApi || recreateViaSdkProvider;
|
|
17448
17274
|
const needsReplacement = propertyDrivenReplacement || recreateFlagged;
|
|
17449
17275
|
const dependencies = this.extractAllDependencies(template, logicalId);
|
|
17276
|
+
const updateReplacePolicy = template?.Resources?.[logicalId]?.UpdateReplacePolicy;
|
|
17450
17277
|
if (needsReplacement) {
|
|
17451
|
-
if (propertyDrivenReplacement && !recreateFlagged) {
|
|
17278
|
+
if (propertyDrivenReplacement && !recreateFlagged && updateReplacePolicy !== "Retain") {
|
|
17452
17279
|
const statefulReason = isStatefulRecreateTargetForReplace(resourceType, currentProps);
|
|
17453
17280
|
if (statefulReason && this.options.forceStatefulRecreation !== true) {
|
|
17454
17281
|
const immutableProps = change.propertyChanges?.filter((pc) => pc.requiresReplacement).map((pc) => pc.path).join(", ");
|
|
@@ -17468,7 +17295,6 @@ var DeployEngine = class {
|
|
|
17468
17295
|
});
|
|
17469
17296
|
const replaceProvider = replaceDecision.provider;
|
|
17470
17297
|
const replaceProps = replaceDecision.provisionedBy === "cc-api" ? this.preparePropertiesForCcApi(resourceType, resolvedProps, logicalId) : resolvedProps;
|
|
17471
|
-
const updateReplacePolicy = template?.Resources?.[logicalId]?.UpdateReplacePolicy;
|
|
17472
17298
|
const oldDeleteProvider = this.providerRegistry.getProviderFor({
|
|
17473
17299
|
resourceType,
|
|
17474
17300
|
provisionedBy: currentResource.provisionedBy
|
|
@@ -17860,5 +17686,5 @@ var DeployEngine = class {
|
|
|
17860
17686
|
};
|
|
17861
17687
|
|
|
17862
17688
|
//#endregion
|
|
17863
|
-
export {
|
|
17864
|
-
//# sourceMappingURL=deploy-engine-
|
|
17689
|
+
export { getBootstrapMarkerKey as $, formatError as $t, refStateLookupFromResource as A, resolveBucketRegion as At, S3StateBackend as B, LocalMigrateError as Bt, findActionableSilentDrops as C, CFN_TEMPLATE_URL_LIMIT as Ct, isTerminationProtectionPropagationError as D, expectedOwnerParam as Dt, disableInstanceApiTermination as E, uploadCfnTemplate as Et, applyRoleArnIfSet as F, AssetError as Ft, WorkGraph as G, PartialFailureError as Gt, shouldRetainResource as H, LockError as Ht, DiffCalculator as I, CdkdError as It, loadPublishableAssetManifest as J, ResourceUpdateNotSupportedError as Jt, buildAssetRedirectMap as K, ProvisioningError as Kt, DagBuilder as L, ConfigError as Lt, normalizeAwsTagsToCfn as M, getAwsClients as Mt, resolveExplicitPhysicalId as N, resetAwsClients as Nt, IntrinsicFunctionResolver as O, AssemblyReader as Ot, assertRegionMatch as P, setAwsClients as Pt, ensureAssetStorage as Q, SynthesisError as Qt, TemplateParser as R, DependencyError as Rt, ProviderRegistry as S, CFN_TEMPLATE_BODY_LIMIT as St, slowCcOperationTimeoutMs as T, findLargeInlineResources as Tt, AssetPublisher as U, MissingCdkCliError as Ut, rebuildClientForBucketRegion as V, LocalStartServiceError as Vt, stringifyValue as W, NestedStackChildDirectDestroyError as Wt, AssetModeResolver as X, StackTerminationProtectionError as Xt, rewriteTemplateAssetReferences as Y, StackHasActiveImportsError as Yt, BOOTSTRAP_MARKER_PREFIX as Z, StateError as Zt, green as _, resolveSkipPrefix as _t, withRetry as a, getDockerCmd as at, IAMRoleProvider as b, resolveUseCdkBootstrapAssets as bt, computeImplicitDeleteEdges as c, AssetManifestLoader as ct, isStatefulRecreateTargetSync as d, synthesisStatusMessage as dt, isCdkdError as en, parseBootstrapMarker as et, renderStatefulReason as f, getDefaultStateBucketName as ft, gray as g, resolveCaptureObservedState as gt, cyan as h, resolveAutoAssetStorage as ht, withResourceDeadline as i, formatDockerLoginError as it, WAFv2WebACLProvider as j, AwsClients as jt, cfnRefValueFromPhysicalId as k, clearBucketRegionCache as kt, extractDeploymentEventError as l, getDockerImageBySourceHash as lt, bold as m, resolveApp as mt, DEFAULT_RESOURCE_WARN_AFTER_MS as n, withErrorHandling as nn, validateContainerRepoName as nt, isRetryableTransientError as o, runDockerForeground as ot, formatResourceLine as p, getLegacyStateBucketName as pt, createAssetRedirectResolver as q, ResourceTimeoutError as qt, DeployEngine as r, __exportAll as rn, buildDockerImage as rt, IMPLICIT_DELETE_DEPENDENCIES as s, runDockerStreaming as st, DEFAULT_RESOURCE_TIMEOUT_MS as t, normalizeAwsError as tn, validateAssetBucketName as tt, MULTI_REGION_RECREATE_BLOCKED_TYPES as u, Synthesizer as ut, red as v, resolveStateBucketWithDefault as vt, CloudControlProvider as w, MIGRATE_TMP_PREFIX as wt, collectInlinePolicyNamesManagedBySiblings as x, warnDeprecatedNoPrefixCliFlag as xt, yellow as y, resolveStateBucketWithDefaultAndSource as yt, LockManager as z, LocalInvokeBuildError as zt };
|
|
17690
|
+
//# sourceMappingURL=deploy-engine-DMpr3qtv.js.map
|