@go-to-k/cdkd 0.251.0 → 0.252.0
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-er3xGYRo.js → asg-provider-BzBdoSYp.js} +2 -2
- package/dist/{asg-provider-er3xGYRo.js.map → asg-provider-BzBdoSYp.js.map} +1 -1
- package/dist/cli.js +343 -84
- package/dist/cli.js.map +1 -1
- package/dist/{deploy-engine-D-gBbMiZ.js → deploy-engine-IkhnJXWR.js} +25 -17
- package/dist/deploy-engine-IkhnJXWR.js.map +1 -0
- package/dist/index.js +1 -1
- package/package.json +1 -1
- package/dist/deploy-engine-D-gBbMiZ.js.map +0 -1
package/dist/cli.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { $ as
|
|
2
|
+
import { $ as BOOTSTRAP_MARKER_PREFIX, A as refStateLookupFromResource, At as AssemblyReader, B as TemplateParser, C as ProviderRegistry, Ct as warnDeprecatedNoPrefixCliFlag, D as isTerminationProtectionPropagationError, Dt as findLargeInlineResources, E as disableInstanceApiTermination, Et as MIGRATE_TMP_PREFIX, F as resolveExplicitPhysicalId, Ft as resetAwsClients, G as AssetPublisher, Gt as MissingCdkCliError, H as S3StateBackend, Ht as LocalMigrateError, I as assertRegionMatch, It as setAwsClients, J as buildAssetRedirectMap, Jt as ProvisioningError, K as stringifyValue, Kt as NestedStackChildDirectDestroyError, L as applyRoleArnIfSet, M as CDK_PATH_TAG, Mt as resolveBucketRegion, N as matchesCdkPath, Nt as AwsClients, O as IntrinsicFunctionResolver, Ot as uploadCfnTemplate, P as normalizeAwsTagsToCfn, Pt as getAwsClients, Q as AssetModeResolver, Qt as StackTerminationProtectionError, R as DiffCalculator, Rt as CdkdError, S as collectInlinePolicyNamesManagedBySiblings, St as resolveUseCdkBootstrapAssets, T as CloudControlProvider, Tt as CFN_TEMPLATE_URL_LIMIT, U as rebuildClientForBucketRegion, Ut as LocalStartServiceError, V as LockManager, Vt as LocalInvokeBuildError$1, W as shouldRetainResource, X as loadPublishableAssetManifest, Xt as ResourceUpdateNotSupportedError, Y as createAssetRedirectResolver, Yt as ResourceTimeoutError, Z as rewriteTemplateAssetReferences, Zt as StackHasActiveImportsError, _ as gray, _t as resolveAutoAssetStorage, a as withRetry, at as buildDockerImage, b as yellow, bt as resolveStateBucketWithDefault, c as IMPLICIT_DELETE_DEPENDENCIES, ct as runDockerForeground, d as MULTI_REGION_RECREATE_BLOCKED_TYPES, dt as getDockerImageBySourceHash, et as ensureAssetStorage, f as isStatefulRecreateTargetSync, ft as Synthesizer, g as cyan, gt as resolveApp, h as bold, ht as getLegacyStateBucketName, i as withResourceDeadline, in as withErrorHandling, it as validateContainerRepoName, j as WAFv2WebACLProvider, k as cfnRefValueFromPhysicalId, kt as expectedOwnerParam, l as computeImplicitDeleteEdges, lt as runDockerStreaming, m as formatResourceLine, mt as getDefaultStateBucketName, n as DEFAULT_RESOURCE_WARN_AFTER_MS, nt as parseBootstrapMarker, o as isRetryableTransientError, ot as formatDockerLoginError, p as renderStatefulReason, pt as synthesisStatusMessage, q as WorkGraph, qt as PartialFailureError, r as DeployEngine, rn as normalizeAwsError, rt as validateAssetBucketName, s as isThrottlingError, st as getDockerCmd, t as DEFAULT_RESOURCE_TIMEOUT_MS, tt as getBootstrapMarkerKey, u as extractDeploymentEventError, ut as AssetManifestLoader, v as green, vt as resolveCaptureObservedState, w as findActionableSilentDrops, wt as CFN_TEMPLATE_BODY_LIMIT, x as IAMRoleProvider, xt as resolveStateBucketWithDefaultAndSource, y as red, yt as resolveSkipPrefix, z as DagBuilder } from "./deploy-engine-IkhnJXWR.js";
|
|
3
3
|
import { a as getLiveRenderer, c as PATTERN_B_RESOURCE_TYPES, d as generateResourceNameWithFallback, f as withSkipPrefix, i as runStackBuffered, n as getLogger, o as PATTERN_B_NAME_OPTIONS, p as withStackName, s as PATTERN_B_NAME_PROPERTIES, u as generateResourceName } from "./logger-BYMEE-BS.js";
|
|
4
|
-
import { t as ASGProvider } from "./asg-provider-
|
|
4
|
+
import { t as ASGProvider } from "./asg-provider-BzBdoSYp.js";
|
|
5
5
|
import { AsyncLocalStorage } from "node:async_hooks";
|
|
6
6
|
import { createHash, randomBytes, randomUUID } from "node:crypto";
|
|
7
7
|
import { CopyObjectCommand, CreateBucketCommand, DeleteBucketAnalyticsConfigurationCommand, DeleteBucketCommand, DeleteBucketCorsCommand, DeleteBucketIntelligentTieringConfigurationCommand, DeleteBucketInventoryConfigurationCommand, DeleteBucketLifecycleCommand, DeleteBucketMetricsConfigurationCommand, DeleteBucketPolicyCommand, DeleteBucketReplicationCommand, DeleteBucketTaggingCommand, DeleteBucketWebsiteCommand, DeleteObjectsCommand, GetBucketAccelerateConfigurationCommand, GetBucketCorsCommand, GetBucketEncryptionCommand, GetBucketLifecycleConfigurationCommand, GetBucketLocationCommand, GetBucketLoggingCommand, GetBucketNotificationConfigurationCommand, GetBucketPolicyCommand, GetBucketReplicationCommand, GetBucketTaggingCommand, GetBucketVersioningCommand, GetBucketWebsiteCommand, GetObjectCommand, GetObjectLockConfigurationCommand, GetPublicAccessBlockCommand, HeadBucketCommand, ListBucketAnalyticsConfigurationsCommand, ListBucketIntelligentTieringConfigurationsCommand, ListBucketInventoryConfigurationsCommand, ListBucketMetricsConfigurationsCommand, ListBucketsCommand, ListDirectoryBucketsCommand, ListObjectVersionsCommand, ListObjectsV2Command, NoSuchBucket, PutBucketAccelerateConfigurationCommand, PutBucketAnalyticsConfigurationCommand, PutBucketCorsCommand, PutBucketEncryptionCommand, PutBucketIntelligentTieringConfigurationCommand, PutBucketInventoryConfigurationCommand, PutBucketLifecycleConfigurationCommand, PutBucketLoggingCommand, PutBucketMetricsConfigurationCommand, PutBucketNotificationConfigurationCommand, PutBucketOwnershipControlsCommand, PutBucketPolicyCommand, PutBucketReplicationCommand, PutBucketTaggingCommand, PutBucketVersioningCommand, PutBucketWebsiteCommand, PutObjectCommand, PutObjectLockConfigurationCommand, PutPublicAccessBlockCommand, S3Client, S3ServiceException } from "@aws-sdk/client-s3";
|
|
@@ -1888,7 +1888,7 @@ const FLUSH_INTERVAL_MS = 2e3;
|
|
|
1888
1888
|
const FLUSH_EVENT_THRESHOLD = 50;
|
|
1889
1889
|
/** Build-time cdkd version, with a dev fallback for non-built contexts. */
|
|
1890
1890
|
function getCdkdVersion() {
|
|
1891
|
-
return "0.
|
|
1891
|
+
return "0.252.0";
|
|
1892
1892
|
}
|
|
1893
1893
|
/**
|
|
1894
1894
|
* Generate a time-sortable unique run id, e.g.
|
|
@@ -25221,6 +25221,155 @@ var RDSDBProxyTargetGroupProvider = class {
|
|
|
25221
25221
|
}
|
|
25222
25222
|
};
|
|
25223
25223
|
|
|
25224
|
+
//#endregion
|
|
25225
|
+
//#region src/provisioning/import-tag-walk.ts
|
|
25226
|
+
/**
|
|
25227
|
+
* Shared, throttle-tolerant `aws:cdk:path` tag walk for `ResourceProvider.import`.
|
|
25228
|
+
*
|
|
25229
|
+
* Providers that adopt a resource without an explicit name property fall back
|
|
25230
|
+
* to step 3 of the lookup order documented in `./import-helpers.ts`: enumerate
|
|
25231
|
+
* the service's `List*`/`Describe*` pages, then issue ONE per-candidate read
|
|
25232
|
+
* (`DescribeX` / `ListTagsForResource`) to obtain the tag set — the list
|
|
25233
|
+
* summaries usually do not carry tags. That is an inherent **N+1** read
|
|
25234
|
+
* pattern: an account with many resources of the type produces one API call
|
|
25235
|
+
* per candidate in a tight loop, which is exactly the shape AWS rate-limits.
|
|
25236
|
+
*
|
|
25237
|
+
* Every provider previously hand-rolled this loop with NO backoff, so a single
|
|
25238
|
+
* throttled `Describe*` aborted the whole `cdkd import` run. This helper
|
|
25239
|
+
* centralises the loop and wraps BOTH the list page fetch and the per-candidate
|
|
25240
|
+
* describe in the deploy engine's `withRetry` with exponential backoff.
|
|
25241
|
+
*
|
|
25242
|
+
* ## Why a narrower classifier than `isRetryableTransientError`
|
|
25243
|
+
*
|
|
25244
|
+
* The deploy engine's classifier is tuned for the WRITE path: it also treats
|
|
25245
|
+
* eventual-consistency phrasings like `does not exist` / `not authorized to
|
|
25246
|
+
* perform` as transient, because a just-created dependency legitimately needs a
|
|
25247
|
+
* moment to propagate. On a read-only import walk those messages mean the
|
|
25248
|
+
* opposite — the candidate really is gone, or the caller's credentials really
|
|
25249
|
+
* lack the permission — and retrying them burns the full backoff budget per
|
|
25250
|
+
* candidate before surfacing the true error.
|
|
25251
|
+
*
|
|
25252
|
+
* So the walk retries throttling ONLY, reusing the deploy engine's throttle
|
|
25253
|
+
* tables verbatim ({@link THROTTLING_ERROR_NAMES} /
|
|
25254
|
+
* {@link RETRYABLE_HTTP_STATUS_CODES}) rather than maintaining a second copy.
|
|
25255
|
+
*
|
|
25256
|
+
* ## Batching
|
|
25257
|
+
*
|
|
25258
|
+
* Batched tag reads are deliberately NOT modelled here: the services this
|
|
25259
|
+
* helper currently serves (EMR `DescribeCluster`, DocDB
|
|
25260
|
+
* `ListTagsForResource`) expose only single-resource reads. A service that
|
|
25261
|
+
* genuinely offers a batch API (e.g. CodeCommit `BatchGetRepositories`) can
|
|
25262
|
+
* satisfy several candidates from one call inside its own `describe` callback,
|
|
25263
|
+
* or bypass the helper entirely.
|
|
25264
|
+
*/
|
|
25265
|
+
/** Max number of retries after the first attempt, per API call in the walk. */
|
|
25266
|
+
const DEFAULT_MAX_RETRIES = 5;
|
|
25267
|
+
/** Initial backoff; each retry doubles it up to {@link DEFAULT_MAX_DELAY_MS}. */
|
|
25268
|
+
const DEFAULT_INITIAL_DELAY_MS = 500;
|
|
25269
|
+
/** Cap for the per-retry delay (0.5s -> 1s -> 2s -> 4s -> 5s, ~12.5s total). */
|
|
25270
|
+
const DEFAULT_MAX_DELAY_MS = 5e3;
|
|
25271
|
+
/**
|
|
25272
|
+
* Wall-clock ceiling for the WHOLE walk (all pages + all candidates), not just
|
|
25273
|
+
* one call. Backoff is per-call, so without this a sustained throttle against a
|
|
25274
|
+
* large account degrades into `(pages + candidates) x ~12.5s` of near-silent
|
|
25275
|
+
* retrying — ~42 minutes for 200 candidates, with no way to tell a slow walk
|
|
25276
|
+
* from a hung one. 10 minutes is generous for a healthy walk (a 200-candidate
|
|
25277
|
+
* DocDB account completes in seconds when AWS is not throttling) and bounds the
|
|
25278
|
+
* pathological case to something a user will wait through.
|
|
25279
|
+
*/
|
|
25280
|
+
const DEFAULT_MAX_WALK_MS = 10 * 6e4;
|
|
25281
|
+
/**
|
|
25282
|
+
* Ceiling on pages fetched. A service that returns a non-advancing pagination
|
|
25283
|
+
* token (a bug, or a marker cdkd echoes back wrongly) would otherwise spin
|
|
25284
|
+
* forever. This is now the shared path every migrated provider runs on, so the
|
|
25285
|
+
* guard belongs here rather than in each caller.
|
|
25286
|
+
*/
|
|
25287
|
+
const DEFAULT_MAX_PAGES = 1e3;
|
|
25288
|
+
/**
|
|
25289
|
+
* Whether an error hit during the read-only tag walk is a rate-limit rejection
|
|
25290
|
+
* worth backing off on.
|
|
25291
|
+
*
|
|
25292
|
+
* Delegates the error + `.cause` chain traversal to the deploy engine's
|
|
25293
|
+
* {@link isThrottlingError} (throttling error names + retryable HTTP statuses,
|
|
25294
|
+
* at every cause depth) and adds the canonical `Rate exceeded` message, which
|
|
25295
|
+
* several services return with an HTTP 400 and a service-specific error name.
|
|
25296
|
+
*
|
|
25297
|
+
* This is deliberately NARROWER than `isRetryableTransientError`: that
|
|
25298
|
+
* classifier also treats write-path eventual-consistency phrasings (`does not
|
|
25299
|
+
* exist`, `not authorized to perform`) as transient, because a just-created
|
|
25300
|
+
* dependency legitimately needs a moment to propagate. On a read-only walk
|
|
25301
|
+
* those mean the candidate really is gone or the credentials really lack the
|
|
25302
|
+
* permission, and retrying them burns the full backoff budget per candidate
|
|
25303
|
+
* before surfacing the true error.
|
|
25304
|
+
*
|
|
25305
|
+
* Exported for direct unit testing and for providers whose tag walk cannot use
|
|
25306
|
+
* {@link importTagWalk} verbatim.
|
|
25307
|
+
*/
|
|
25308
|
+
function isThrottlingLikeError(error, message) {
|
|
25309
|
+
return isThrottlingError(error) || message.includes("Rate exceeded");
|
|
25310
|
+
}
|
|
25311
|
+
/** Thrown when the walk exceeds its wall-clock budget or page ceiling. */
|
|
25312
|
+
var ImportTagWalkLimitError = class extends Error {
|
|
25313
|
+
constructor(message) {
|
|
25314
|
+
super(message);
|
|
25315
|
+
this.name = "ImportTagWalkLimitError";
|
|
25316
|
+
}
|
|
25317
|
+
};
|
|
25318
|
+
/**
|
|
25319
|
+
* Paginate `listPage`, `describe` each candidate, and return the first one
|
|
25320
|
+
* whose tags carry the requested `aws:cdk:path`. Returns `null` when no
|
|
25321
|
+
* candidate matches (or when `cdkPath` is empty).
|
|
25322
|
+
*
|
|
25323
|
+
* Both callbacks are individually retried with exponential backoff on
|
|
25324
|
+
* throttling errors, so one rate-limited call mid-walk no longer aborts the
|
|
25325
|
+
* whole import.
|
|
25326
|
+
*/
|
|
25327
|
+
async function importTagWalk(options) {
|
|
25328
|
+
const { cdkPath, listPage, describe, tagsOf } = options;
|
|
25329
|
+
if (!cdkPath) return null;
|
|
25330
|
+
const logicalId = options.logicalId ?? "import";
|
|
25331
|
+
const logger = options.retry?.logger ?? getLogger();
|
|
25332
|
+
const maxWalkMs = options.retry?.maxWalkMs ?? DEFAULT_MAX_WALK_MS;
|
|
25333
|
+
const maxPages = options.retry?.maxPages ?? DEFAULT_MAX_PAGES;
|
|
25334
|
+
const retryOpts = {
|
|
25335
|
+
maxRetries: options.retry?.maxRetries ?? DEFAULT_MAX_RETRIES,
|
|
25336
|
+
initialDelayMs: options.retry?.initialDelayMs ?? DEFAULT_INITIAL_DELAY_MS,
|
|
25337
|
+
maxDelayMs: options.retry?.maxDelayMs ?? DEFAULT_MAX_DELAY_MS,
|
|
25338
|
+
isRetryable: (message, error) => isThrottlingLikeError(error, message),
|
|
25339
|
+
logger,
|
|
25340
|
+
...options.retry?.isInterrupted && { isInterrupted: options.retry.isInterrupted },
|
|
25341
|
+
...options.retry?.onInterrupted && { onInterrupted: options.retry.onInterrupted },
|
|
25342
|
+
...options.retry?.sleep && { sleep: options.retry.sleep }
|
|
25343
|
+
};
|
|
25344
|
+
const startedAt = Date.now();
|
|
25345
|
+
const assertWithinBudget = (stage) => {
|
|
25346
|
+
const elapsed = Date.now() - startedAt;
|
|
25347
|
+
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>.`);
|
|
25348
|
+
};
|
|
25349
|
+
let marker;
|
|
25350
|
+
let pages = 0;
|
|
25351
|
+
do {
|
|
25352
|
+
assertWithinBudget("listing candidates");
|
|
25353
|
+
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>.`);
|
|
25354
|
+
const currentMarker = marker;
|
|
25355
|
+
const page = await withRetry(() => listPage(currentMarker), logicalId, retryOpts);
|
|
25356
|
+
for (const summary of page.items ?? []) {
|
|
25357
|
+
assertWithinBudget("reading candidate tags");
|
|
25358
|
+
const detail = await withRetry(() => describe(summary), logicalId, retryOpts);
|
|
25359
|
+
if (detail === void 0) {
|
|
25360
|
+
logger.debug(` ↷ Skipped an ${logicalId} import candidate: its detail lookup returned no result`);
|
|
25361
|
+
continue;
|
|
25362
|
+
}
|
|
25363
|
+
if (matchesCdkPath(tagsOf(detail, summary), cdkPath)) return {
|
|
25364
|
+
summary,
|
|
25365
|
+
detail
|
|
25366
|
+
};
|
|
25367
|
+
}
|
|
25368
|
+
marker = page.nextMarker;
|
|
25369
|
+
} while (marker);
|
|
25370
|
+
return null;
|
|
25371
|
+
}
|
|
25372
|
+
|
|
25224
25373
|
//#endregion
|
|
25225
25374
|
//#region src/provisioning/providers/docdb-provider.ts
|
|
25226
25375
|
/**
|
|
@@ -25696,6 +25845,9 @@ var DocDBProvider = class {
|
|
|
25696
25845
|
* Identifier` / `DBClusterIdentifier` / `DBSubnetGroupName`) are usually
|
|
25697
25846
|
* present in CDK templates; fall back to `aws:cdk:path` tag lookup via
|
|
25698
25847
|
* the corresponding `Describe*` + `ListTagsForResource` pair otherwise.
|
|
25848
|
+
* The fallback walk runs through the shared `importTagWalk` helper, so the
|
|
25849
|
+
* N+1 `ListTagsForResource` burst is retried with exponential backoff when
|
|
25850
|
+
* AWS throttles it instead of aborting the whole import.
|
|
25699
25851
|
*/
|
|
25700
25852
|
async import(input) {
|
|
25701
25853
|
switch (input.resourceType) {
|
|
@@ -25811,20 +25963,24 @@ var DocDBProvider = class {
|
|
|
25811
25963
|
if (err.name === "DBInstanceNotFoundFault") return null;
|
|
25812
25964
|
throw err;
|
|
25813
25965
|
}
|
|
25814
|
-
|
|
25815
|
-
|
|
25816
|
-
|
|
25817
|
-
|
|
25818
|
-
|
|
25819
|
-
|
|
25820
|
-
|
|
25821
|
-
|
|
25822
|
-
attributes: {}
|
|
25966
|
+
const match = await importTagWalk({
|
|
25967
|
+
cdkPath: input.cdkPath,
|
|
25968
|
+
logicalId: input.logicalId,
|
|
25969
|
+
listPage: async (marker) => {
|
|
25970
|
+
const list = await this.getClient().send(new DescribeDBInstancesCommand$1({ ...marker && { Marker: marker } }));
|
|
25971
|
+
return {
|
|
25972
|
+
items: list.DBInstances,
|
|
25973
|
+
nextMarker: list.Marker
|
|
25823
25974
|
};
|
|
25824
|
-
}
|
|
25825
|
-
|
|
25826
|
-
|
|
25827
|
-
|
|
25975
|
+
},
|
|
25976
|
+
describe: async (inst) => inst.DBInstanceIdentifier && inst.DBInstanceArn ? await this.listTagsForResource(inst.DBInstanceArn) : void 0,
|
|
25977
|
+
tagsOf: (tags) => tags
|
|
25978
|
+
});
|
|
25979
|
+
if (!match) return null;
|
|
25980
|
+
return {
|
|
25981
|
+
physicalId: match.summary.DBInstanceIdentifier,
|
|
25982
|
+
attributes: {}
|
|
25983
|
+
};
|
|
25828
25984
|
}
|
|
25829
25985
|
async importDBCluster(input) {
|
|
25830
25986
|
const explicit = resolveExplicitPhysicalId(input, "DBClusterIdentifier");
|
|
@@ -25838,20 +25994,24 @@ var DocDBProvider = class {
|
|
|
25838
25994
|
if (err.name === "DBClusterNotFoundFault") return null;
|
|
25839
25995
|
throw err;
|
|
25840
25996
|
}
|
|
25841
|
-
|
|
25842
|
-
|
|
25843
|
-
|
|
25844
|
-
|
|
25845
|
-
|
|
25846
|
-
|
|
25847
|
-
|
|
25848
|
-
|
|
25849
|
-
attributes: {}
|
|
25997
|
+
const match = await importTagWalk({
|
|
25998
|
+
cdkPath: input.cdkPath,
|
|
25999
|
+
logicalId: input.logicalId,
|
|
26000
|
+
listPage: async (marker) => {
|
|
26001
|
+
const list = await this.getClient().send(new DescribeDBClustersCommand$1({ ...marker && { Marker: marker } }));
|
|
26002
|
+
return {
|
|
26003
|
+
items: list.DBClusters,
|
|
26004
|
+
nextMarker: list.Marker
|
|
25850
26005
|
};
|
|
25851
|
-
}
|
|
25852
|
-
|
|
25853
|
-
|
|
25854
|
-
|
|
26006
|
+
},
|
|
26007
|
+
describe: async (c) => c.DBClusterIdentifier && c.DBClusterArn ? await this.listTagsForResource(c.DBClusterArn) : void 0,
|
|
26008
|
+
tagsOf: (tags) => tags
|
|
26009
|
+
});
|
|
26010
|
+
if (!match) return null;
|
|
26011
|
+
return {
|
|
26012
|
+
physicalId: match.summary.DBClusterIdentifier,
|
|
26013
|
+
attributes: {}
|
|
26014
|
+
};
|
|
25855
26015
|
}
|
|
25856
26016
|
async importDBSubnetGroup(input) {
|
|
25857
26017
|
const explicit = resolveExplicitPhysicalId(input, "DBSubnetGroupName");
|
|
@@ -25865,20 +26025,33 @@ var DocDBProvider = class {
|
|
|
25865
26025
|
if (err.name === "DBSubnetGroupNotFoundFault") return null;
|
|
25866
26026
|
throw err;
|
|
25867
26027
|
}
|
|
25868
|
-
|
|
25869
|
-
|
|
25870
|
-
|
|
25871
|
-
|
|
25872
|
-
|
|
25873
|
-
|
|
25874
|
-
|
|
25875
|
-
|
|
25876
|
-
attributes: {}
|
|
26028
|
+
const match = await importTagWalk({
|
|
26029
|
+
cdkPath: input.cdkPath,
|
|
26030
|
+
logicalId: input.logicalId,
|
|
26031
|
+
listPage: async (marker) => {
|
|
26032
|
+
const list = await this.getClient().send(new DescribeDBSubnetGroupsCommand$1({ ...marker && { Marker: marker } }));
|
|
26033
|
+
return {
|
|
26034
|
+
items: list.DBSubnetGroups,
|
|
26035
|
+
nextMarker: list.Marker
|
|
25877
26036
|
};
|
|
25878
|
-
}
|
|
25879
|
-
|
|
25880
|
-
|
|
25881
|
-
|
|
26037
|
+
},
|
|
26038
|
+
describe: async (sg) => sg.DBSubnetGroupName && sg.DBSubnetGroupArn ? await this.listTagsForResource(sg.DBSubnetGroupArn) : void 0,
|
|
26039
|
+
tagsOf: (tags) => tags
|
|
26040
|
+
});
|
|
26041
|
+
if (!match) return null;
|
|
26042
|
+
return {
|
|
26043
|
+
physicalId: match.summary.DBSubnetGroupName,
|
|
26044
|
+
attributes: {}
|
|
26045
|
+
};
|
|
26046
|
+
}
|
|
26047
|
+
/**
|
|
26048
|
+
* `ListTagsForResource` for the tag-walk import path. Returns the tag list
|
|
26049
|
+
* (possibly empty) so `importTagWalk` can match `aws:cdk:path` against it;
|
|
26050
|
+
* `undefined` is reserved for "skip this candidate", which the callers signal
|
|
26051
|
+
* before calling this.
|
|
26052
|
+
*/
|
|
26053
|
+
async listTagsForResource(resourceArn) {
|
|
26054
|
+
return (await this.getClient().send(new ListTagsForResourceCommand$13({ ResourceName: resourceArn }))).TagList ?? [];
|
|
25882
26055
|
}
|
|
25883
26056
|
};
|
|
25884
26057
|
|
|
@@ -36037,45 +36210,58 @@ var EMRClusterProvider = class {
|
|
|
36037
36210
|
* 2. Tag-based lookup: `ListClusters` filtered to the non-terminated states
|
|
36038
36211
|
* (a `TERMINATED*` cluster is gone and must never be adopted), then a
|
|
36039
36212
|
* `DescribeCluster` per candidate to read `Tags` (the list summaries do
|
|
36040
|
-
* NOT carry tags) and match `aws:cdk:path`.
|
|
36213
|
+
* NOT carry tags) and match `aws:cdk:path`. The walk runs through the
|
|
36214
|
+
* shared `importTagWalk` helper, so each list page / describe is retried
|
|
36215
|
+
* with exponential backoff when AWS throttles the N+1 read burst.
|
|
36041
36216
|
*/
|
|
36042
36217
|
async import(input) {
|
|
36043
36218
|
const explicit = resolveExplicitPhysicalId(input, null);
|
|
36044
36219
|
if (explicit) {
|
|
36045
|
-
const cluster = await this.
|
|
36220
|
+
const cluster = await this.describeClusterOrUndefined(explicit);
|
|
36046
36221
|
if (!cluster || cluster.Status?.State && TERMINAL_STATES.has(cluster.Status.State)) return null;
|
|
36047
36222
|
return {
|
|
36048
36223
|
physicalId: explicit,
|
|
36049
36224
|
attributes: this.buildAttributes(cluster)
|
|
36050
36225
|
};
|
|
36051
36226
|
}
|
|
36052
|
-
|
|
36053
|
-
|
|
36054
|
-
|
|
36055
|
-
|
|
36056
|
-
|
|
36057
|
-
|
|
36058
|
-
|
|
36059
|
-
|
|
36060
|
-
|
|
36061
|
-
|
|
36062
|
-
|
|
36063
|
-
if (matchesCdkPath(cluster.Tags, input.cdkPath)) return {
|
|
36064
|
-
physicalId: summary.Id,
|
|
36065
|
-
attributes: this.buildAttributes(cluster)
|
|
36227
|
+
const match = await importTagWalk({
|
|
36228
|
+
cdkPath: input.cdkPath,
|
|
36229
|
+
logicalId: input.logicalId,
|
|
36230
|
+
listPage: async (marker) => {
|
|
36231
|
+
const list = await this.getClient().send(new ListClustersCommand$1({
|
|
36232
|
+
ClusterStates: [...NON_TERMINATED_STATES],
|
|
36233
|
+
...marker && { Marker: marker }
|
|
36234
|
+
}));
|
|
36235
|
+
return {
|
|
36236
|
+
items: list.Clusters,
|
|
36237
|
+
nextMarker: list.Marker
|
|
36066
36238
|
};
|
|
36067
|
-
}
|
|
36068
|
-
|
|
36069
|
-
|
|
36070
|
-
|
|
36239
|
+
},
|
|
36240
|
+
describe: async (summary) => summary.Id ? await this.describeClusterOrUndefined(summary.Id) : void 0,
|
|
36241
|
+
tagsOf: (cluster) => cluster.Tags
|
|
36242
|
+
});
|
|
36243
|
+
if (!match) return null;
|
|
36244
|
+
return {
|
|
36245
|
+
physicalId: match.summary.Id,
|
|
36246
|
+
attributes: this.buildAttributes(match.detail)
|
|
36247
|
+
};
|
|
36071
36248
|
}
|
|
36072
36249
|
/**
|
|
36073
36250
|
* `DescribeCluster` that maps a not-found (`InvalidRequestException` — the
|
|
36074
|
-
* cluster id is unknown in this region / aged out of Describe) to
|
|
36075
|
-
* instead of throwing, so `import()` can treat it as "no match"
|
|
36076
|
-
* aborting the whole adoption run.
|
|
36251
|
+
* cluster id is unknown in this region / aged out of Describe) to
|
|
36252
|
+
* `undefined` instead of throwing, so `import()` can treat it as "no match"
|
|
36253
|
+
* rather than aborting the whole adoption run.
|
|
36254
|
+
*
|
|
36255
|
+
* `undefined`, NOT `null`, is load-bearing: `importTagWalk` skips a candidate
|
|
36256
|
+
* on `detail === undefined`, and an actual `null` would flow into `tagsOf`
|
|
36257
|
+
* and `TypeError`. Also note the mapping is BROAD — `InvalidRequestException`
|
|
36258
|
+
* covers more than not-found, so a genuine failure here degrades to "no
|
|
36259
|
+
* match" and a subsequent deploy would CREATE a duplicate cluster rather than
|
|
36260
|
+
* adopt the existing one. The walk logs each skip at debug so the case is at
|
|
36261
|
+
* least visible under `--verbose`; narrowing the mapping needs a
|
|
36262
|
+
* message/code-level discriminator AWS does not currently document.
|
|
36077
36263
|
*/
|
|
36078
|
-
async
|
|
36264
|
+
async describeClusterOrUndefined(clusterId) {
|
|
36079
36265
|
try {
|
|
36080
36266
|
return (await this.getClient().send(new DescribeClusterCommand({ ClusterId: clusterId }))).Cluster;
|
|
36081
36267
|
} catch (err) {
|
|
@@ -36114,7 +36300,7 @@ var EMRClusterProvider = class {
|
|
|
36114
36300
|
* optional `import` method's incremental opt-in shape.
|
|
36115
36301
|
*/
|
|
36116
36302
|
async readCurrentState(physicalId, _logicalId, _resourceType) {
|
|
36117
|
-
const cluster = await this.
|
|
36303
|
+
const cluster = await this.describeClusterOrUndefined(physicalId);
|
|
36118
36304
|
if (!cluster) return void 0;
|
|
36119
36305
|
let instanceGroups = [];
|
|
36120
36306
|
let instanceFleets = [];
|
|
@@ -36314,14 +36500,21 @@ const DEFAULT_POLL_INTERVAL_MS$1 = 15e3;
|
|
|
36314
36500
|
/** Instance-group state that means "the group is up and idle/serving". */
|
|
36315
36501
|
const READY_STATES$1 = /* @__PURE__ */ new Set(["RUNNING"]);
|
|
36316
36502
|
/**
|
|
36317
|
-
* Instance-group states that mean "the group
|
|
36318
|
-
* error during a create/resize wait
|
|
36319
|
-
*
|
|
36503
|
+
* Instance-group states that mean "the group will never reach the requested
|
|
36504
|
+
* size" — a hard error during a create/resize wait.
|
|
36505
|
+
*
|
|
36506
|
+
* `ARRESTED` means the group failed to provision; `TERMINATED`/`ENDED` mean it
|
|
36507
|
+
* is gone. `SUSPENDED` means a resize could not complete: the existing
|
|
36508
|
+
* instances keep running but AWS can no longer add or remove any, so the wait
|
|
36509
|
+
* would poll to the full `maxWaitMs` timeout instead of failing fast with the
|
|
36510
|
+
* service's own state-change reason — the exact defect fixed on the
|
|
36511
|
+
* instance-fleet side in issue #1092 item 2, which applies verbatim here.
|
|
36320
36512
|
*/
|
|
36321
36513
|
const FAILED_STATES$1 = /* @__PURE__ */ new Set([
|
|
36322
36514
|
"ARRESTED",
|
|
36323
|
-
"
|
|
36324
|
-
"
|
|
36515
|
+
"ENDED",
|
|
36516
|
+
"SUSPENDED",
|
|
36517
|
+
"TERMINATED"
|
|
36325
36518
|
]);
|
|
36326
36519
|
const toNumber$1 = (v) => {
|
|
36327
36520
|
if (v === void 0) return void 0;
|
|
@@ -36471,7 +36664,7 @@ var EMRInstanceGroupConfigProvider = class {
|
|
|
36471
36664
|
InstanceCount: toNumber$1(properties["InstanceCount"])
|
|
36472
36665
|
}]
|
|
36473
36666
|
}));
|
|
36474
|
-
if (jobFlowId) await this.waitForGroupReady(jobFlowId, physicalId, logicalId, resourceType, toNumber$1(properties["InstanceCount"]) ?? 0);
|
|
36667
|
+
if (jobFlowId) await this.waitForGroupReady(jobFlowId, physicalId, logicalId, resourceType, toNumber$1(properties["InstanceCount"]) ?? 0, true);
|
|
36475
36668
|
}
|
|
36476
36669
|
if (autoScalingChanged) {
|
|
36477
36670
|
if (!jobFlowId) throw new ProvisioningError(`EMR InstanceGroupConfig ${logicalId} AutoScalingPolicy update needs JobFlowId (the cluster id), which is absent`, resourceType, logicalId, physicalId);
|
|
@@ -36532,15 +36725,43 @@ var EMRInstanceGroupConfigProvider = class {
|
|
|
36532
36725
|
* would return before the resize even starts. Waiting on the running count
|
|
36533
36726
|
* reaching the requested target settles both create (0 -> N provisioning)
|
|
36534
36727
|
* and resize (M -> N add/remove) correctly.
|
|
36535
|
-
|
|
36536
|
-
|
|
36728
|
+
*
|
|
36729
|
+
* `toleratesStaleFailedState` MUST be true on the resize path and false on
|
|
36730
|
+
* create. It exists for the RECOVERY case: `SUSPENDED` (a resize that could
|
|
36731
|
+
* not complete) is precisely the state a user re-runs `cdkd deploy` to fix,
|
|
36732
|
+
* but the same pre-resize read lag documented above means the first poll
|
|
36733
|
+
* after `ModifyInstanceGroups` still reports `SUSPENDED`. Enforcing
|
|
36734
|
+
* {@link FAILED_STATES} on that stale read would abort the very resize that
|
|
36735
|
+
* recovers the group, on every attempt, making the state permanently
|
|
36736
|
+
* unrecoverable through cdkd.
|
|
36737
|
+
*
|
|
36738
|
+
* So on a resize the failed-state check is suppressed until the group is
|
|
36739
|
+
* observed LEAVING whatever state it started in; from then on it is enforced
|
|
36740
|
+
* normally (a resize that transitions RUNNING -> RESIZING -> SUSPENDED still
|
|
36741
|
+
* fails fast, which is the case #1092 item 2 is about). A group that never
|
|
36742
|
+
* leaves its initial state falls through to the `maxWaitMs` timeout — the
|
|
36743
|
+
* pre-fix behavior, and strictly better than an unrecoverable resource.
|
|
36744
|
+
*
|
|
36745
|
+
* Note the asymmetry that makes this specific to failed states: a stale
|
|
36746
|
+
* pre-resize `RUNNING` is harmless because the instance-count mismatch keeps
|
|
36747
|
+
* the loop polling. A stale `SUSPENDED` is terminal.
|
|
36748
|
+
*/
|
|
36749
|
+
async waitForGroupReady(clusterId, groupId, logicalId, resourceType, expectedCount, toleratesStaleFailedState = false) {
|
|
36537
36750
|
const startTime = Date.now();
|
|
36538
36751
|
const transientState = { count: 0 };
|
|
36752
|
+
let initialState;
|
|
36753
|
+
let hasLeftInitialState = false;
|
|
36539
36754
|
while (Date.now() - startTime < this.maxWaitMs) {
|
|
36540
36755
|
const group = await this.findGroupForPoll(clusterId, groupId, transientState);
|
|
36541
36756
|
const state = group?.Status?.State;
|
|
36757
|
+
if (state) {
|
|
36758
|
+
if (initialState === void 0) initialState = state;
|
|
36759
|
+
else if (state !== initialState) hasLeftInitialState = true;
|
|
36760
|
+
}
|
|
36542
36761
|
if (state && READY_STATES$1.has(state) && group?.RunningInstanceCount === expectedCount) return;
|
|
36543
|
-
|
|
36762
|
+
const enforceFailedStates = !toleratesStaleFailedState || hasLeftInitialState;
|
|
36763
|
+
if (state && enforceFailedStates && FAILED_STATES$1.has(state)) throw new ProvisioningError(`EMR instance group ${groupId} entered failed state ${state}: ${group?.Status?.StateChangeReason?.Message ?? "no state-change reason reported"}`, resourceType, logicalId, groupId);
|
|
36764
|
+
if (state && !enforceFailedStates && FAILED_STATES$1.has(state)) this.logger.debug(`EMR instance group ${groupId} still reads ${state} from before the resize; waiting for it to leave that state before treating it as failed`);
|
|
36544
36765
|
this.logger.debug(`EMR instance group ${groupId} state: ${state ?? "unknown"}, running ${group?.RunningInstanceCount ?? "?"}/${expectedCount}, waiting...`);
|
|
36545
36766
|
await new Promise((resolve) => setTimeout(resolve, this.pollIntervalMs));
|
|
36546
36767
|
}
|
|
@@ -36596,10 +36817,20 @@ const DEFAULT_POLL_INTERVAL_MS = 15e3;
|
|
|
36596
36817
|
/** Instance-fleet state that means "the fleet is up and idle/serving". */
|
|
36597
36818
|
const READY_STATES = /* @__PURE__ */ new Set(["RUNNING"]);
|
|
36598
36819
|
/**
|
|
36599
|
-
* Instance-fleet states that mean "the fleet
|
|
36600
|
-
*
|
|
36820
|
+
* Instance-fleet states that mean "the fleet will never reach the requested
|
|
36821
|
+
* capacity" — a hard error during a create/resize wait.
|
|
36822
|
+
*
|
|
36823
|
+
* `TERMINATED` means the fleet is gone. `SUSPENDED` means a resize could not
|
|
36824
|
+
* complete: the existing instances keep running but AWS can no longer add or
|
|
36825
|
+
* remove any, so the wait would poll to the full `maxWaitMs` timeout instead of
|
|
36826
|
+
* failing fast with the service's own state-change reason (issue #1092 item 2).
|
|
36827
|
+
*
|
|
36828
|
+
* This is the fleet-state ANALOGUE of the instance-group provider's failed set,
|
|
36829
|
+
* not a copy of it: the two enums differ. `ARRESTED` and `ENDED` are
|
|
36830
|
+
* `InstanceGroupState` members that do not exist for fleets, so `TERMINATED` is
|
|
36831
|
+
* the only state the two sets share.
|
|
36601
36832
|
*/
|
|
36602
|
-
const FAILED_STATES = /* @__PURE__ */ new Set(["TERMINATED"]);
|
|
36833
|
+
const FAILED_STATES = /* @__PURE__ */ new Set(["SUSPENDED", "TERMINATED"]);
|
|
36603
36834
|
const toNumber = (v) => {
|
|
36604
36835
|
if (v === void 0) return void 0;
|
|
36605
36836
|
const n = Number(v);
|
|
@@ -36752,7 +36983,7 @@ var EMRInstanceFleetConfigProvider = class {
|
|
|
36752
36983
|
}));
|
|
36753
36984
|
const newTarget = this.targetCapacity(properties);
|
|
36754
36985
|
const prevTarget = this.targetCapacity(previousProperties);
|
|
36755
|
-
await this.waitForFleetReady(clusterId, physicalId, logicalId, resourceType, newTarget, newTarget >= prevTarget);
|
|
36986
|
+
await this.waitForFleetReady(clusterId, physicalId, logicalId, resourceType, newTarget, newTarget >= prevTarget, true);
|
|
36756
36987
|
this.logger.debug(`Successfully updated EMR instance fleet ${logicalId}`);
|
|
36757
36988
|
return {
|
|
36758
36989
|
physicalId,
|
|
@@ -36812,17 +37043,45 @@ var EMRInstanceFleetConfigProvider = class {
|
|
|
36812
37043
|
*
|
|
36813
37044
|
* A `targetCapacity` of 0 on the up path (never valid for a real create —
|
|
36814
37045
|
* at least one target must be > 0) degrades to a State-only wait.
|
|
36815
|
-
|
|
36816
|
-
|
|
37046
|
+
*
|
|
37047
|
+
* `toleratesStaleFailedState` MUST be true on the resize path and false on
|
|
37048
|
+
* create. It exists for the RECOVERY case: `SUSPENDED` (a resize that could
|
|
37049
|
+
* not complete) is precisely the state a user re-runs `cdkd deploy` to fix,
|
|
37050
|
+
* but the same pre-resize read lag documented above means the first poll
|
|
37051
|
+
* after `ModifyInstanceFleet` still reports `SUSPENDED`. Enforcing
|
|
37052
|
+
* {@link FAILED_STATES} on that stale read would abort the very resize that
|
|
37053
|
+
* recovers the fleet, on every attempt, making the state permanently
|
|
37054
|
+
* unrecoverable through cdkd.
|
|
37055
|
+
*
|
|
37056
|
+
* So on a resize the failed-state check is suppressed until the fleet is
|
|
37057
|
+
* observed LEAVING whatever state it started in; from then on it is enforced
|
|
37058
|
+
* normally (a resize that transitions RUNNING -> RESIZING -> SUSPENDED still
|
|
37059
|
+
* fails fast, which is the case #1092 item 2 is about). A fleet that never
|
|
37060
|
+
* leaves its initial state falls through to the `maxWaitMs` timeout — the
|
|
37061
|
+
* pre-fix behavior, and strictly better than an unrecoverable resource.
|
|
37062
|
+
*
|
|
37063
|
+
* Note the asymmetry that makes this specific to failed states: a stale
|
|
37064
|
+
* pre-resize `RUNNING` is harmless because the capacity check keeps the loop
|
|
37065
|
+
* polling. A stale `SUSPENDED` is terminal.
|
|
37066
|
+
*/
|
|
37067
|
+
async waitForFleetReady(clusterId, fleetId, logicalId, resourceType, targetCapacity, atLeast, toleratesStaleFailedState = false) {
|
|
36817
37068
|
const startTime = Date.now();
|
|
36818
37069
|
const transientState = { count: 0 };
|
|
37070
|
+
let initialState;
|
|
37071
|
+
let hasLeftInitialState = false;
|
|
36819
37072
|
while (Date.now() - startTime < this.maxWaitMs) {
|
|
36820
37073
|
const fleet = await this.findFleetForPoll(clusterId, fleetId, transientState);
|
|
36821
37074
|
const state = fleet?.Status?.State;
|
|
36822
37075
|
const provisioned = (fleet?.ProvisionedOnDemandCapacity ?? 0) + (fleet?.ProvisionedSpotCapacity ?? 0);
|
|
36823
37076
|
const capacityReady = atLeast ? provisioned >= targetCapacity : provisioned <= targetCapacity;
|
|
37077
|
+
if (state) {
|
|
37078
|
+
if (initialState === void 0) initialState = state;
|
|
37079
|
+
else if (state !== initialState) hasLeftInitialState = true;
|
|
37080
|
+
}
|
|
36824
37081
|
if (state && READY_STATES.has(state) && capacityReady) return;
|
|
36825
|
-
|
|
37082
|
+
const enforceFailedStates = !toleratesStaleFailedState || hasLeftInitialState;
|
|
37083
|
+
if (state && enforceFailedStates && FAILED_STATES.has(state)) throw new ProvisioningError(`EMR instance fleet ${fleetId} entered failed state ${state}: ${fleet?.Status?.StateChangeReason?.Message ?? "no state-change reason reported"}`, resourceType, logicalId, fleetId);
|
|
37084
|
+
if (state && !enforceFailedStates && FAILED_STATES.has(state)) this.logger.debug(`EMR instance fleet ${fleetId} still reads ${state} from before the resize; waiting for it to leave that state before treating it as failed`);
|
|
36826
37085
|
this.logger.debug(`EMR instance fleet ${fleetId} state: ${state ?? "unknown"}, provisioned ${provisioned}/${targetCapacity}, waiting...`);
|
|
36827
37086
|
await new Promise((resolve) => setTimeout(resolve, this.pollIntervalMs));
|
|
36828
37087
|
}
|
|
@@ -61758,7 +62017,7 @@ function reorderArgs(argv) {
|
|
|
61758
62017
|
async function main() {
|
|
61759
62018
|
installPipeCloseHandler();
|
|
61760
62019
|
const program = new Command();
|
|
61761
|
-
program.name("cdkd").description("CDK Direct - Deploy AWS CDK apps directly via SDK/Cloud Control API").version("0.
|
|
62020
|
+
program.name("cdkd").description("CDK Direct - Deploy AWS CDK apps directly via SDK/Cloud Control API").version("0.252.0");
|
|
61762
62021
|
program.addCommand(createBootstrapCommand());
|
|
61763
62022
|
program.addCommand(createSynthCommand());
|
|
61764
62023
|
program.addCommand(createListCommand());
|