@go-to-k/cdkd 0.250.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/cli.js CHANGED
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
- import { $ as ensureAssetStorage, A as WAFv2WebACLProvider, B as LockManager, Bt as LocalInvokeBuildError$1, C as findActionableSilentDrops, Ct as CFN_TEMPLATE_BODY_LIMIT, D as IntrinsicFunctionResolver, Dt as uploadCfnTemplate, E as isTerminationProtectionPropagationError, Et as findLargeInlineResources, F as assertRegionMatch, Ft as setAwsClients, G as stringifyValue, Gt as NestedStackChildDirectDestroyError, H as rebuildClientForBucketRegion, Ht as LocalStartServiceError, I as applyRoleArnIfSet, J as createAssetRedirectResolver, Jt as ResourceTimeoutError, K as WorkGraph, Kt as PartialFailureError, L as DiffCalculator, Lt as CdkdError, M as matchesCdkPath, Mt as AwsClients, N as normalizeAwsTagsToCfn, Nt as getAwsClients, O as cfnRefValueFromPhysicalId, Ot as expectedOwnerParam, P as resolveExplicitPhysicalId, Pt as resetAwsClients, Q as BOOTSTRAP_MARKER_PREFIX, R as DagBuilder, S as ProviderRegistry, St as warnDeprecatedNoPrefixCliFlag, T as disableInstanceApiTermination, Tt as MIGRATE_TMP_PREFIX, U as shouldRetainResource, V as S3StateBackend, Vt as LocalMigrateError, W as AssetPublisher, Wt as MissingCdkCliError, X as rewriteTemplateAssetReferences, Xt as StackHasActiveImportsError, Y as loadPublishableAssetManifest, Yt as ResourceUpdateNotSupportedError, Z as AssetModeResolver, Zt as StackTerminationProtectionError, _ as green, _t as resolveCaptureObservedState, a as withRetry, at as formatDockerLoginError, b as IAMRoleProvider, bt as resolveStateBucketWithDefaultAndSource, c as computeImplicitDeleteEdges, ct as runDockerStreaming, d as isStatefulRecreateTargetSync, dt as Synthesizer, et as getBootstrapMarkerKey, f as renderStatefulReason, ft as synthesisStatusMessage, g as gray, gt as resolveAutoAssetStorage, h as cyan, ht as resolveApp, i as withResourceDeadline, it as buildDockerImage, j as CDK_PATH_TAG, jt as resolveBucketRegion, k as refStateLookupFromResource, kt as AssemblyReader, l as extractDeploymentEventError, lt as AssetManifestLoader, m as bold, mt as getLegacyStateBucketName, n as DEFAULT_RESOURCE_WARN_AFTER_MS, nn as normalizeAwsError, nt as validateAssetBucketName, o as isRetryableTransientError, ot as getDockerCmd, p as formatResourceLine, pt as getDefaultStateBucketName, q as buildAssetRedirectMap, qt as ProvisioningError, r as DeployEngine, rn as withErrorHandling, rt as validateContainerRepoName, s as IMPLICIT_DELETE_DEPENDENCIES, st as runDockerForeground, t as DEFAULT_RESOURCE_TIMEOUT_MS, tt as parseBootstrapMarker, u as MULTI_REGION_RECREATE_BLOCKED_TYPES, ut as getDockerImageBySourceHash, v as red, vt as resolveSkipPrefix, w as CloudControlProvider, wt as CFN_TEMPLATE_URL_LIMIT, x as collectInlinePolicyNamesManagedBySiblings, xt as resolveUseCdkBootstrapAssets, y as yellow, yt as resolveStateBucketWithDefault, z as TemplateParser } from "./deploy-engine-D-gBbMiZ.js";
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-er3xGYRo.js";
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.250.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
- if (!input.cdkPath) return null;
25815
- let marker;
25816
- do {
25817
- const list = await this.getClient().send(new DescribeDBInstancesCommand$1({ ...marker && { Marker: marker } }));
25818
- for (const inst of list.DBInstances ?? []) {
25819
- if (!inst.DBInstanceIdentifier || !inst.DBInstanceArn) continue;
25820
- if (matchesCdkPath((await this.getClient().send(new ListTagsForResourceCommand$13({ ResourceName: inst.DBInstanceArn }))).TagList, input.cdkPath)) return {
25821
- physicalId: inst.DBInstanceIdentifier,
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
- marker = list.Marker;
25826
- } while (marker);
25827
- return null;
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
- if (!input.cdkPath) return null;
25842
- let marker;
25843
- do {
25844
- const list = await this.getClient().send(new DescribeDBClustersCommand$1({ ...marker && { Marker: marker } }));
25845
- for (const c of list.DBClusters ?? []) {
25846
- if (!c.DBClusterIdentifier || !c.DBClusterArn) continue;
25847
- if (matchesCdkPath((await this.getClient().send(new ListTagsForResourceCommand$13({ ResourceName: c.DBClusterArn }))).TagList, input.cdkPath)) return {
25848
- physicalId: c.DBClusterIdentifier,
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
- marker = list.Marker;
25853
- } while (marker);
25854
- return null;
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
- if (!input.cdkPath) return null;
25869
- let marker;
25870
- do {
25871
- const list = await this.getClient().send(new DescribeDBSubnetGroupsCommand$1({ ...marker && { Marker: marker } }));
25872
- for (const sg of list.DBSubnetGroups ?? []) {
25873
- if (!sg.DBSubnetGroupName || !sg.DBSubnetGroupArn) continue;
25874
- if (matchesCdkPath((await this.getClient().send(new ListTagsForResourceCommand$13({ ResourceName: sg.DBSubnetGroupArn }))).TagList, input.cdkPath)) return {
25875
- physicalId: sg.DBSubnetGroupName,
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
- marker = list.Marker;
25880
- } while (marker);
25881
- return null;
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
 
@@ -34494,6 +34667,11 @@ const VARIANT_CONFIG_KEY = {
34494
34667
  ONTAP: "OntapConfiguration",
34495
34668
  OPENZFS: "OpenZFSConfiguration"
34496
34669
  };
34670
+ /**
34671
+ * Exported so a unit test can assert every declared-mutable sub-property has
34672
+ * a matching `apply*UpdateField` case — the invariant the unreachable
34673
+ * `default:` guard enforces at runtime.
34674
+ */
34497
34675
  const VARIANT_MUTABLE_SUBPROPS = {
34498
34676
  LustreConfiguration: LUSTRE_MUTABLE_SUBPROPS,
34499
34677
  WindowsConfiguration: WINDOWS_MUTABLE_SUBPROPS,
@@ -34501,6 +34679,21 @@ const VARIANT_MUTABLE_SUBPROPS = {
34501
34679
  OpenZFSConfiguration: OPENZFS_MUTABLE_SUBPROPS
34502
34680
  };
34503
34681
  /**
34682
+ * Copy `value` into `target[key]` only when AWS actually returned it, so the
34683
+ * snapshot carries no `undefined`-valued keys.
34684
+ *
34685
+ * The drift comparator walks the BASELINE's keys (`observedProperties`, or
34686
+ * `properties` when a resource predates observed-capture), not the AWS side —
34687
+ * so an omitted key here is not itself phantom drift. It matters because this
34688
+ * snapshot BECOMES the next baseline: `deploy-engine` stores it as
34689
+ * `observedProperties`, and an explicit `Foo: undefined` would then be walked
34690
+ * as a real key on every later comparison. Omitting is also what keeps the
34691
+ * emitted shape a faithful subset of the CFn input shape.
34692
+ */
34693
+ const putIfDefined = (target, key, value) => {
34694
+ if (value !== void 0) target[key] = value;
34695
+ };
34696
+ /**
34504
34697
  * Compute the Add/Remove route-table-id deltas an ONTAP / OpenZFS
34505
34698
  * `UpdateFileSystem` needs from the previous vs. next `RouteTableIds` list.
34506
34699
  * Route table membership is an unordered set, so a pure reorder yields no
@@ -34927,14 +35120,30 @@ var FSxFileSystemProvider = class {
34927
35120
  const prevVal = prev[key];
34928
35121
  if (JSON.stringify(nextVal) === JSON.stringify(prevVal)) continue;
34929
35122
  if (!mutable.has(key)) throw new ResourceUpdateNotSupportedError(resourceType, logicalId, `AWS FSx FileSystem ${configKey}.${key} is immutable on AWS — UpdateFileSystem cannot change it after creation. Re-deploy with cdkd deploy --replace, or destroy + redeploy the stack.`);
34930
- if (configKey === "LustreConfiguration" ? this.applyLustreUpdateField(key, nextVal, diff) : configKey === "WindowsConfiguration" ? this.applyWindowsUpdateField(key, nextVal, diff) : configKey === "OntapConfiguration" ? this.applyOntapUpdateField(key, nextVal, prevVal, diff) : this.applyOpenZFSUpdateField(key, nextVal, prevVal, diff)) hasMutableDiff = true;
35123
+ const ctx = {
35124
+ resourceType,
35125
+ logicalId,
35126
+ configKey
35127
+ };
35128
+ if (configKey === "LustreConfiguration" ? this.applyLustreUpdateField(ctx, key, nextVal, diff) : configKey === "WindowsConfiguration" ? this.applyWindowsUpdateField(ctx, key, nextVal, diff) : configKey === "OntapConfiguration" ? this.applyOntapUpdateField(ctx, key, nextVal, prevVal, diff) : this.applyOpenZFSUpdateField(ctx, key, nextVal, prevVal, diff)) hasMutableDiff = true;
34931
35129
  }
34932
35130
  return {
34933
35131
  diff,
34934
35132
  hasMutableDiff
34935
35133
  };
34936
35134
  }
34937
- applyLustreUpdateField(key, next, diff) {
35135
+ /**
35136
+ * A sub-property listed in a `*_MUTABLE_SUBPROPS` set with no matching
35137
+ * `apply*UpdateField` case. Reaching here is a cdkd bug, not user error:
35138
+ * `computeVariantConfigDiff` already accepted the key as mutable, so
35139
+ * falling through would issue a no-op `UpdateFileSystem` (`hasMutableDiff`
35140
+ * true, empty diff) and silently drop the user's change. Fail loudly
35141
+ * instead so the missing mapping surfaces the moment it is added.
35142
+ */
35143
+ unmappedMutableSubprop(ctx, key) {
35144
+ throw new ProvisioningError(`AWS FSx FileSystem ${ctx.configKey}.${key} is declared mutable but has no UpdateFileSystem mapping in cdkd. This is a cdkd bug — please report it at https://github.com/go-to-k/cdkd/issues`, ctx.resourceType, ctx.logicalId);
35145
+ }
35146
+ applyLustreUpdateField(ctx, key, next, diff) {
34938
35147
  const out = diff;
34939
35148
  switch (key) {
34940
35149
  case "WeeklyMaintenanceStartTime":
@@ -34966,10 +35175,11 @@ var FSxFileSystemProvider = class {
34966
35175
  } : void 0;
34967
35176
  break;
34968
35177
  }
35178
+ default: this.unmappedMutableSubprop(ctx, key);
34969
35179
  }
34970
35180
  return true;
34971
35181
  }
34972
- applyWindowsUpdateField(key, next, diff) {
35182
+ applyWindowsUpdateField(ctx, key, next, diff) {
34973
35183
  const out = diff;
34974
35184
  switch (key) {
34975
35185
  case "WeeklyMaintenanceStartTime":
@@ -34992,10 +35202,11 @@ var FSxFileSystemProvider = class {
34992
35202
  case "FsrmConfiguration":
34993
35203
  out.FsrmConfiguration = this.toWindowsFsrmConfiguration(next);
34994
35204
  break;
35205
+ default: this.unmappedMutableSubprop(ctx, key);
34995
35206
  }
34996
35207
  return true;
34997
35208
  }
34998
- applyOntapUpdateField(key, next, prev, diff) {
35209
+ applyOntapUpdateField(ctx, key, next, prev, diff) {
34999
35210
  const out = diff;
35000
35211
  switch (key) {
35001
35212
  case "DailyAutomaticBackupStartTime":
@@ -35019,10 +35230,11 @@ var FSxFileSystemProvider = class {
35019
35230
  if (remove) out.RemoveRouteTableIds = remove;
35020
35231
  return add !== void 0 || remove !== void 0;
35021
35232
  }
35233
+ default: this.unmappedMutableSubprop(ctx, key);
35022
35234
  }
35023
35235
  return true;
35024
35236
  }
35025
- applyOpenZFSUpdateField(key, next, prev, diff) {
35237
+ applyOpenZFSUpdateField(ctx, key, next, prev, diff) {
35026
35238
  const out = diff;
35027
35239
  switch (key) {
35028
35240
  case "DailyAutomaticBackupStartTime":
@@ -35050,6 +35262,7 @@ var FSxFileSystemProvider = class {
35050
35262
  if (remove) out.RemoveRouteTableIds = remove;
35051
35263
  return add !== void 0 || remove !== void 0;
35052
35264
  }
35265
+ default: this.unmappedMutableSubprop(ctx, key);
35053
35266
  }
35054
35267
  return true;
35055
35268
  }
@@ -35223,28 +35436,122 @@ var FSxFileSystemProvider = class {
35223
35436
  default: return;
35224
35437
  }
35225
35438
  }
35439
+ /** Map the read-side `WindowsConfiguration` back to CFn input shape. */
35440
+ readWindowsConfiguration(windows) {
35441
+ const config = {};
35442
+ putIfDefined(config, "ActiveDirectoryId", windows.ActiveDirectoryId);
35443
+ putIfDefined(config, "DeploymentType", windows.DeploymentType);
35444
+ putIfDefined(config, "PreferredSubnetId", windows.PreferredSubnetId);
35445
+ putIfDefined(config, "ThroughputCapacity", windows.ThroughputCapacity);
35446
+ putIfDefined(config, "WeeklyMaintenanceStartTime", windows.WeeklyMaintenanceStartTime);
35447
+ putIfDefined(config, "DailyAutomaticBackupStartTime", windows.DailyAutomaticBackupStartTime);
35448
+ putIfDefined(config, "AutomaticBackupRetentionDays", windows.AutomaticBackupRetentionDays);
35449
+ putIfDefined(config, "CopyTagsToBackups", windows.CopyTagsToBackups);
35450
+ if (windows.Aliases !== void 0) config["Aliases"] = windows.Aliases.map((alias) => alias.Name).filter((name) => name !== void 0);
35451
+ if (windows.SelfManagedActiveDirectoryConfiguration) {
35452
+ const ad = windows.SelfManagedActiveDirectoryConfiguration;
35453
+ const adConfig = {};
35454
+ putIfDefined(adConfig, "DomainName", ad.DomainName);
35455
+ putIfDefined(adConfig, "OrganizationalUnitDistinguishedName", ad.OrganizationalUnitDistinguishedName);
35456
+ putIfDefined(adConfig, "FileSystemAdministratorsGroup", ad.FileSystemAdministratorsGroup);
35457
+ putIfDefined(adConfig, "UserName", ad.UserName);
35458
+ putIfDefined(adConfig, "DnsIps", ad.DnsIps ? [...ad.DnsIps] : void 0);
35459
+ putIfDefined(adConfig, "DomainJoinServiceAccountSecret", ad.DomainJoinServiceAccountSecret);
35460
+ if (Object.keys(adConfig).length > 0) config["SelfManagedActiveDirectoryConfiguration"] = adConfig;
35461
+ }
35462
+ if (windows.AuditLogConfiguration) {
35463
+ const audit = {};
35464
+ putIfDefined(audit, "FileAccessAuditLogLevel", windows.AuditLogConfiguration.FileAccessAuditLogLevel);
35465
+ putIfDefined(audit, "FileShareAccessAuditLogLevel", windows.AuditLogConfiguration.FileShareAccessAuditLogLevel);
35466
+ putIfDefined(audit, "AuditLogDestination", windows.AuditLogConfiguration.AuditLogDestination);
35467
+ if (Object.keys(audit).length > 0) config["AuditLogConfiguration"] = audit;
35468
+ }
35469
+ const diskIops = this.readDiskIopsConfiguration(windows.DiskIopsConfiguration);
35470
+ if (diskIops) config["DiskIopsConfiguration"] = diskIops;
35471
+ if (windows.FsrmConfiguration) {
35472
+ const fsrm = {};
35473
+ putIfDefined(fsrm, "FsrmServiceEnabled", windows.FsrmConfiguration.FsrmServiceEnabled);
35474
+ putIfDefined(fsrm, "EventLogDestination", windows.FsrmConfiguration.EventLogDestination);
35475
+ if (Object.keys(fsrm).length > 0) config["FsrmConfiguration"] = fsrm;
35476
+ }
35477
+ return config;
35478
+ }
35479
+ /** Map the read-side `OntapConfiguration` back to CFn input shape. */
35480
+ readOntapConfiguration(ontap) {
35481
+ const config = {};
35482
+ putIfDefined(config, "DeploymentType", ontap.DeploymentType);
35483
+ putIfDefined(config, "AutomaticBackupRetentionDays", ontap.AutomaticBackupRetentionDays);
35484
+ putIfDefined(config, "DailyAutomaticBackupStartTime", ontap.DailyAutomaticBackupStartTime);
35485
+ putIfDefined(config, "WeeklyMaintenanceStartTime", ontap.WeeklyMaintenanceStartTime);
35486
+ putIfDefined(config, "EndpointIpAddressRange", ontap.EndpointIpAddressRange);
35487
+ putIfDefined(config, "PreferredSubnetId", ontap.PreferredSubnetId);
35488
+ putIfDefined(config, "ThroughputCapacity", ontap.ThroughputCapacity);
35489
+ putIfDefined(config, "ThroughputCapacityPerHAPair", ontap.ThroughputCapacityPerHAPair);
35490
+ putIfDefined(config, "HAPairs", ontap.HAPairs);
35491
+ putIfDefined(config, "RouteTableIds", ontap.RouteTableIds ? [...ontap.RouteTableIds] : void 0);
35492
+ putIfDefined(config, "EndpointIpv6AddressRange", ontap.EndpointIpv6AddressRange);
35493
+ const diskIops = this.readDiskIopsConfiguration(ontap.DiskIopsConfiguration);
35494
+ if (diskIops) config["DiskIopsConfiguration"] = diskIops;
35495
+ return config;
35496
+ }
35497
+ /** Map the read-side `OpenZFSConfiguration` back to CFn input shape. */
35498
+ readOpenZFSConfiguration(openzfs) {
35499
+ const config = {};
35500
+ putIfDefined(config, "DeploymentType", openzfs.DeploymentType);
35501
+ putIfDefined(config, "AutomaticBackupRetentionDays", openzfs.AutomaticBackupRetentionDays);
35502
+ putIfDefined(config, "CopyTagsToBackups", openzfs.CopyTagsToBackups);
35503
+ putIfDefined(config, "CopyTagsToVolumes", openzfs.CopyTagsToVolumes);
35504
+ putIfDefined(config, "DailyAutomaticBackupStartTime", openzfs.DailyAutomaticBackupStartTime);
35505
+ putIfDefined(config, "WeeklyMaintenanceStartTime", openzfs.WeeklyMaintenanceStartTime);
35506
+ putIfDefined(config, "ThroughputCapacity", openzfs.ThroughputCapacity);
35507
+ putIfDefined(config, "PreferredSubnetId", openzfs.PreferredSubnetId);
35508
+ putIfDefined(config, "EndpointIpAddressRange", openzfs.EndpointIpAddressRange);
35509
+ putIfDefined(config, "RouteTableIds", openzfs.RouteTableIds ? [...openzfs.RouteTableIds] : void 0);
35510
+ putIfDefined(config, "EndpointIpv6AddressRange", openzfs.EndpointIpv6AddressRange);
35511
+ const diskIops = this.readDiskIopsConfiguration(openzfs.DiskIopsConfiguration);
35512
+ if (diskIops) config["DiskIopsConfiguration"] = diskIops;
35513
+ if (openzfs.ReadCacheConfiguration) {
35514
+ const readCache = {};
35515
+ putIfDefined(readCache, "SizingMode", openzfs.ReadCacheConfiguration.SizingMode);
35516
+ putIfDefined(readCache, "SizeGiB", openzfs.ReadCacheConfiguration.SizeGiB);
35517
+ if (Object.keys(readCache).length > 0) config["ReadCacheConfiguration"] = readCache;
35518
+ }
35519
+ return config;
35520
+ }
35521
+ /** Shared `DiskIopsConfiguration` reverse-mapper (Windows / ONTAP / OpenZFS). */
35522
+ readDiskIopsConfiguration(diskIops) {
35523
+ if (!diskIops) return void 0;
35524
+ const out = {};
35525
+ putIfDefined(out, "Mode", diskIops.Mode);
35526
+ putIfDefined(out, "Iops", diskIops.Iops);
35527
+ return Object.keys(out).length > 0 ? out : void 0;
35528
+ }
35226
35529
  /**
35227
35530
  * State property paths this provider cannot read back from AWS:
35228
35531
  * - `SecurityGroupIds` — `DescribeFileSystems` returns network
35229
35532
  * interface ids, never the original security group list.
35230
35533
  * - `BackupId` — creation-source input, not surfaced on the deployed
35231
35534
  * file system.
35232
- * - `WindowsConfiguration` / `OntapConfiguration` / `OpenZFSConfiguration`
35233
- * `readCurrentState` maps only the Lustre block back to CFn shape
35234
- * today, so drift on the non-Lustre variant blocks is not yet computed;
35235
- * marking them unknown avoids a guaranteed false positive on every run
35236
- * (variant-config drift is a follow-up). Only the block matching the
35237
- * file system's type is ever present in state, so listing all three is
35238
- * harmless.
35535
+ * - `WindowsConfiguration.SelfManagedActiveDirectoryConfiguration.Password`
35536
+ * / `OntapConfiguration.FsxAdminPassword` write-only credentials; the
35537
+ * API never echoes them back.
35538
+ * - `OpenZFSConfiguration.RootVolumeConfiguration` configured on the
35539
+ * root VOLUME, not the file system; `DescribeFileSystems` returns only
35540
+ * `RootVolumeId`, so reading it back would need a separate
35541
+ * `DescribeVolumes` call (out of scope here).
35542
+ *
35543
+ * The whole-block `WindowsConfiguration` / `OntapConfiguration` /
35544
+ * `OpenZFSConfiguration` entries are gone as of the variant reverse-mappers
35545
+ * — only the individually-unreadable leaves above stay unknown.
35239
35546
  */
35240
35547
  getDriftUnknownPaths(resourceType) {
35241
35548
  if (resourceType !== "AWS::FSx::FileSystem") return [];
35242
35549
  return [
35243
35550
  "SecurityGroupIds",
35244
35551
  "BackupId",
35245
- "WindowsConfiguration",
35246
- "OntapConfiguration",
35247
- "OpenZFSConfiguration"
35552
+ "WindowsConfiguration.SelfManagedActiveDirectoryConfiguration.Password",
35553
+ "OntapConfiguration.FsxAdminPassword",
35554
+ "OpenZFSConfiguration.RootVolumeConfiguration"
35248
35555
  ];
35249
35556
  }
35250
35557
  /**
@@ -35252,7 +35559,11 @@ var FSxFileSystemProvider = class {
35252
35559
  * via `DescribeFileSystems`. Lustre data-repository fields (`ImportPath`
35253
35560
  * / `ExportPath` / `AutoImportPolicy` / `ImportedFileChunkSize`) are
35254
35561
  * mapped back from the nested `DataRepositoryConfiguration` the API
35255
- * returns. Returns `undefined` when the file system is gone.
35562
+ * returns. The Windows / ONTAP / OpenZFS variant blocks are reverse-mapped
35563
+ * by their respective `read*Configuration` helpers; the few inputs AWS
35564
+ * never returns stay listed in {@link getDriftUnknownPaths}. At most one
35565
+ * variant block is ever present (they key off `FileSystemType`).
35566
+ * Returns `undefined` when the file system is gone.
35256
35567
  */
35257
35568
  async readCurrentState(physicalId, _logicalId, resourceType) {
35258
35569
  if (resourceType !== "AWS::FSx::FileSystem") return void 0;
@@ -35265,47 +35576,59 @@ var FSxFileSystemProvider = class {
35265
35576
  }
35266
35577
  if (!fs) return void 0;
35267
35578
  const result = {};
35268
- if (fs.FileSystemType !== void 0) result["FileSystemType"] = fs.FileSystemType;
35269
- if (fs.StorageCapacity !== void 0) result["StorageCapacity"] = fs.StorageCapacity;
35270
- if (fs.StorageType !== void 0) result["StorageType"] = fs.StorageType;
35271
- if (fs.SubnetIds !== void 0) result["SubnetIds"] = [...fs.SubnetIds];
35272
- if (fs.KmsKeyId !== void 0) result["KmsKeyId"] = fs.KmsKeyId;
35273
- if (fs.FileSystemTypeVersion !== void 0) result["FileSystemTypeVersion"] = fs.FileSystemTypeVersion;
35274
- if (fs.NetworkType !== void 0) result["NetworkType"] = fs.NetworkType;
35579
+ putIfDefined(result, "FileSystemType", fs.FileSystemType);
35580
+ putIfDefined(result, "StorageCapacity", fs.StorageCapacity);
35581
+ putIfDefined(result, "StorageType", fs.StorageType);
35582
+ putIfDefined(result, "SubnetIds", fs.SubnetIds ? [...fs.SubnetIds] : void 0);
35583
+ putIfDefined(result, "KmsKeyId", fs.KmsKeyId);
35584
+ putIfDefined(result, "FileSystemTypeVersion", fs.FileSystemTypeVersion);
35585
+ putIfDefined(result, "NetworkType", fs.NetworkType);
35275
35586
  const lustre = fs.LustreConfiguration;
35276
35587
  if (lustre) {
35277
35588
  const config = {};
35278
- if (lustre.WeeklyMaintenanceStartTime !== void 0) config["WeeklyMaintenanceStartTime"] = lustre.WeeklyMaintenanceStartTime;
35279
- if (lustre.DeploymentType !== void 0) config["DeploymentType"] = lustre.DeploymentType;
35280
- if (lustre.PerUnitStorageThroughput !== void 0) config["PerUnitStorageThroughput"] = lustre.PerUnitStorageThroughput;
35281
- if (lustre.DailyAutomaticBackupStartTime !== void 0) config["DailyAutomaticBackupStartTime"] = lustre.DailyAutomaticBackupStartTime;
35282
- if (lustre.AutomaticBackupRetentionDays !== void 0) config["AutomaticBackupRetentionDays"] = lustre.AutomaticBackupRetentionDays;
35283
- if (lustre.CopyTagsToBackups !== void 0) config["CopyTagsToBackups"] = lustre.CopyTagsToBackups;
35284
- if (lustre.DriveCacheType !== void 0) config["DriveCacheType"] = lustre.DriveCacheType;
35285
- if (lustre.DataCompressionType !== void 0) config["DataCompressionType"] = lustre.DataCompressionType;
35286
- if (lustre.EfaEnabled !== void 0) config["EfaEnabled"] = lustre.EfaEnabled;
35287
- if (lustre.ThroughputCapacity !== void 0) config["ThroughputCapacity"] = lustre.ThroughputCapacity;
35589
+ putIfDefined(config, "WeeklyMaintenanceStartTime", lustre.WeeklyMaintenanceStartTime);
35590
+ putIfDefined(config, "DeploymentType", lustre.DeploymentType);
35591
+ putIfDefined(config, "PerUnitStorageThroughput", lustre.PerUnitStorageThroughput);
35592
+ putIfDefined(config, "DailyAutomaticBackupStartTime", lustre.DailyAutomaticBackupStartTime);
35593
+ putIfDefined(config, "AutomaticBackupRetentionDays", lustre.AutomaticBackupRetentionDays);
35594
+ putIfDefined(config, "CopyTagsToBackups", lustre.CopyTagsToBackups);
35595
+ putIfDefined(config, "DriveCacheType", lustre.DriveCacheType);
35596
+ putIfDefined(config, "DataCompressionType", lustre.DataCompressionType);
35597
+ putIfDefined(config, "EfaEnabled", lustre.EfaEnabled);
35598
+ putIfDefined(config, "ThroughputCapacity", lustre.ThroughputCapacity);
35288
35599
  if (lustre.MetadataConfiguration) {
35289
35600
  const metadata = {};
35290
- if (lustre.MetadataConfiguration.Mode !== void 0) metadata["Mode"] = lustre.MetadataConfiguration.Mode;
35291
- if (lustre.MetadataConfiguration.Iops !== void 0) metadata["Iops"] = lustre.MetadataConfiguration.Iops;
35601
+ putIfDefined(metadata, "Mode", lustre.MetadataConfiguration.Mode);
35602
+ putIfDefined(metadata, "Iops", lustre.MetadataConfiguration.Iops);
35292
35603
  if (Object.keys(metadata).length > 0) config["MetadataConfiguration"] = metadata;
35293
35604
  }
35294
35605
  if (lustre.DataReadCacheConfiguration) {
35295
35606
  const readCache = {};
35296
- if (lustre.DataReadCacheConfiguration.SizingMode !== void 0) readCache["SizingMode"] = lustre.DataReadCacheConfiguration.SizingMode;
35297
- if (lustre.DataReadCacheConfiguration.SizeGiB !== void 0) readCache["SizeGiB"] = lustre.DataReadCacheConfiguration.SizeGiB;
35607
+ putIfDefined(readCache, "SizingMode", lustre.DataReadCacheConfiguration.SizingMode);
35608
+ putIfDefined(readCache, "SizeGiB", lustre.DataReadCacheConfiguration.SizeGiB);
35298
35609
  if (Object.keys(readCache).length > 0) config["DataReadCacheConfiguration"] = readCache;
35299
35610
  }
35300
35611
  const dataRepo = lustre.DataRepositoryConfiguration;
35301
35612
  if (dataRepo) {
35302
- if (dataRepo.ImportPath !== void 0) config["ImportPath"] = dataRepo.ImportPath;
35303
- if (dataRepo.ExportPath !== void 0) config["ExportPath"] = dataRepo.ExportPath;
35304
- if (dataRepo.AutoImportPolicy !== void 0) config["AutoImportPolicy"] = dataRepo.AutoImportPolicy;
35305
- if (dataRepo.ImportedFileChunkSize !== void 0) config["ImportedFileChunkSize"] = dataRepo.ImportedFileChunkSize;
35613
+ putIfDefined(config, "ImportPath", dataRepo.ImportPath);
35614
+ putIfDefined(config, "ExportPath", dataRepo.ExportPath);
35615
+ putIfDefined(config, "AutoImportPolicy", dataRepo.AutoImportPolicy);
35616
+ putIfDefined(config, "ImportedFileChunkSize", dataRepo.ImportedFileChunkSize);
35306
35617
  }
35307
35618
  if (Object.keys(config).length > 0) result["LustreConfiguration"] = config;
35308
35619
  }
35620
+ if (fs.WindowsConfiguration) {
35621
+ const config = this.readWindowsConfiguration(fs.WindowsConfiguration);
35622
+ if (Object.keys(config).length > 0) result["WindowsConfiguration"] = config;
35623
+ }
35624
+ if (fs.OntapConfiguration) {
35625
+ const config = this.readOntapConfiguration(fs.OntapConfiguration);
35626
+ if (Object.keys(config).length > 0) result["OntapConfiguration"] = config;
35627
+ }
35628
+ if (fs.OpenZFSConfiguration) {
35629
+ const config = this.readOpenZFSConfiguration(fs.OpenZFSConfiguration);
35630
+ if (Object.keys(config).length > 0) result["OpenZFSConfiguration"] = config;
35631
+ }
35309
35632
  result["Tags"] = normalizeAwsTagsToCfn(fs.Tags);
35310
35633
  return result;
35311
35634
  }
@@ -35887,45 +36210,58 @@ var EMRClusterProvider = class {
35887
36210
  * 2. Tag-based lookup: `ListClusters` filtered to the non-terminated states
35888
36211
  * (a `TERMINATED*` cluster is gone and must never be adopted), then a
35889
36212
  * `DescribeCluster` per candidate to read `Tags` (the list summaries do
35890
- * 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.
35891
36216
  */
35892
36217
  async import(input) {
35893
36218
  const explicit = resolveExplicitPhysicalId(input, null);
35894
36219
  if (explicit) {
35895
- const cluster = await this.describeClusterOrNull(explicit);
36220
+ const cluster = await this.describeClusterOrUndefined(explicit);
35896
36221
  if (!cluster || cluster.Status?.State && TERMINAL_STATES.has(cluster.Status.State)) return null;
35897
36222
  return {
35898
36223
  physicalId: explicit,
35899
36224
  attributes: this.buildAttributes(cluster)
35900
36225
  };
35901
36226
  }
35902
- if (!input.cdkPath) return null;
35903
- let marker;
35904
- do {
35905
- const list = await this.getClient().send(new ListClustersCommand$1({
35906
- ClusterStates: [...NON_TERMINATED_STATES],
35907
- ...marker && { Marker: marker }
35908
- }));
35909
- for (const summary of list.Clusters ?? []) {
35910
- if (!summary.Id) continue;
35911
- const cluster = await this.describeClusterOrNull(summary.Id);
35912
- if (!cluster) continue;
35913
- if (matchesCdkPath(cluster.Tags, input.cdkPath)) return {
35914
- physicalId: summary.Id,
35915
- 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
35916
36238
  };
35917
- }
35918
- marker = list.Marker;
35919
- } while (marker);
35920
- return null;
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
+ };
35921
36248
  }
35922
36249
  /**
35923
36250
  * `DescribeCluster` that maps a not-found (`InvalidRequestException` — the
35924
- * cluster id is unknown in this region / aged out of Describe) to `null`
35925
- * instead of throwing, so `import()` can treat it as "no match" rather than
35926
- * 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.
35927
36263
  */
35928
- async describeClusterOrNull(clusterId) {
36264
+ async describeClusterOrUndefined(clusterId) {
35929
36265
  try {
35930
36266
  return (await this.getClient().send(new DescribeClusterCommand({ ClusterId: clusterId }))).Cluster;
35931
36267
  } catch (err) {
@@ -35964,7 +36300,7 @@ var EMRClusterProvider = class {
35964
36300
  * optional `import` method's incremental opt-in shape.
35965
36301
  */
35966
36302
  async readCurrentState(physicalId, _logicalId, _resourceType) {
35967
- const cluster = await this.describeClusterOrNull(physicalId);
36303
+ const cluster = await this.describeClusterOrUndefined(physicalId);
35968
36304
  if (!cluster) return void 0;
35969
36305
  let instanceGroups = [];
35970
36306
  let instanceFleets = [];
@@ -36164,14 +36500,21 @@ const DEFAULT_POLL_INTERVAL_MS$1 = 15e3;
36164
36500
  /** Instance-group state that means "the group is up and idle/serving". */
36165
36501
  const READY_STATES$1 = /* @__PURE__ */ new Set(["RUNNING"]);
36166
36502
  /**
36167
- * Instance-group states that mean "the group is gone / failed" — a hard
36168
- * error during a create/resize wait (an ARRESTED group failed to provision;
36169
- * TERMINATED/ENDED mean it will never reach RUNNING).
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.
36170
36512
  */
36171
36513
  const FAILED_STATES$1 = /* @__PURE__ */ new Set([
36172
36514
  "ARRESTED",
36173
- "TERMINATED",
36174
- "ENDED"
36515
+ "ENDED",
36516
+ "SUSPENDED",
36517
+ "TERMINATED"
36175
36518
  ]);
36176
36519
  const toNumber$1 = (v) => {
36177
36520
  if (v === void 0) return void 0;
@@ -36321,7 +36664,7 @@ var EMRInstanceGroupConfigProvider = class {
36321
36664
  InstanceCount: toNumber$1(properties["InstanceCount"])
36322
36665
  }]
36323
36666
  }));
36324
- 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);
36325
36668
  }
36326
36669
  if (autoScalingChanged) {
36327
36670
  if (!jobFlowId) throw new ProvisioningError(`EMR InstanceGroupConfig ${logicalId} AutoScalingPolicy update needs JobFlowId (the cluster id), which is absent`, resourceType, logicalId, physicalId);
@@ -36382,15 +36725,43 @@ var EMRInstanceGroupConfigProvider = class {
36382
36725
  * would return before the resize even starts. Waiting on the running count
36383
36726
  * reaching the requested target settles both create (0 -> N provisioning)
36384
36727
  * and resize (M -> N add/remove) correctly.
36385
- */
36386
- async waitForGroupReady(clusterId, groupId, logicalId, resourceType, expectedCount) {
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) {
36387
36750
  const startTime = Date.now();
36388
36751
  const transientState = { count: 0 };
36752
+ let initialState;
36753
+ let hasLeftInitialState = false;
36389
36754
  while (Date.now() - startTime < this.maxWaitMs) {
36390
36755
  const group = await this.findGroupForPoll(clusterId, groupId, transientState);
36391
36756
  const state = group?.Status?.State;
36757
+ if (state) {
36758
+ if (initialState === void 0) initialState = state;
36759
+ else if (state !== initialState) hasLeftInitialState = true;
36760
+ }
36392
36761
  if (state && READY_STATES$1.has(state) && group?.RunningInstanceCount === expectedCount) return;
36393
- if (state && 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);
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`);
36394
36765
  this.logger.debug(`EMR instance group ${groupId} state: ${state ?? "unknown"}, running ${group?.RunningInstanceCount ?? "?"}/${expectedCount}, waiting...`);
36395
36766
  await new Promise((resolve) => setTimeout(resolve, this.pollIntervalMs));
36396
36767
  }
@@ -36446,10 +36817,20 @@ const DEFAULT_POLL_INTERVAL_MS = 15e3;
36446
36817
  /** Instance-fleet state that means "the fleet is up and idle/serving". */
36447
36818
  const READY_STATES = /* @__PURE__ */ new Set(["RUNNING"]);
36448
36819
  /**
36449
- * Instance-fleet states that mean "the fleet is gone" a hard error during a
36450
- * create/resize wait (it will never reach RUNNING).
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.
36451
36832
  */
36452
- const FAILED_STATES = /* @__PURE__ */ new Set(["TERMINATED"]);
36833
+ const FAILED_STATES = /* @__PURE__ */ new Set(["SUSPENDED", "TERMINATED"]);
36453
36834
  const toNumber = (v) => {
36454
36835
  if (v === void 0) return void 0;
36455
36836
  const n = Number(v);
@@ -36602,7 +36983,7 @@ var EMRInstanceFleetConfigProvider = class {
36602
36983
  }));
36603
36984
  const newTarget = this.targetCapacity(properties);
36604
36985
  const prevTarget = this.targetCapacity(previousProperties);
36605
- await this.waitForFleetReady(clusterId, physicalId, logicalId, resourceType, newTarget, newTarget >= prevTarget);
36986
+ await this.waitForFleetReady(clusterId, physicalId, logicalId, resourceType, newTarget, newTarget >= prevTarget, true);
36606
36987
  this.logger.debug(`Successfully updated EMR instance fleet ${logicalId}`);
36607
36988
  return {
36608
36989
  physicalId,
@@ -36662,17 +37043,45 @@ var EMRInstanceFleetConfigProvider = class {
36662
37043
  *
36663
37044
  * A `targetCapacity` of 0 on the up path (never valid for a real create —
36664
37045
  * at least one target must be > 0) degrades to a State-only wait.
36665
- */
36666
- async waitForFleetReady(clusterId, fleetId, logicalId, resourceType, targetCapacity, atLeast) {
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) {
36667
37068
  const startTime = Date.now();
36668
37069
  const transientState = { count: 0 };
37070
+ let initialState;
37071
+ let hasLeftInitialState = false;
36669
37072
  while (Date.now() - startTime < this.maxWaitMs) {
36670
37073
  const fleet = await this.findFleetForPoll(clusterId, fleetId, transientState);
36671
37074
  const state = fleet?.Status?.State;
36672
37075
  const provisioned = (fleet?.ProvisionedOnDemandCapacity ?? 0) + (fleet?.ProvisionedSpotCapacity ?? 0);
36673
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
+ }
36674
37081
  if (state && READY_STATES.has(state) && capacityReady) return;
36675
- if (state && 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);
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`);
36676
37085
  this.logger.debug(`EMR instance fleet ${fleetId} state: ${state ?? "unknown"}, provisioned ${provisioned}/${targetCapacity}, waiting...`);
36677
37086
  await new Promise((resolve) => setTimeout(resolve, this.pollIntervalMs));
36678
37087
  }
@@ -61608,7 +62017,7 @@ function reorderArgs(argv) {
61608
62017
  async function main() {
61609
62018
  installPipeCloseHandler();
61610
62019
  const program = new Command();
61611
- program.name("cdkd").description("CDK Direct - Deploy AWS CDK apps directly via SDK/Cloud Control API").version("0.250.0");
62020
+ program.name("cdkd").description("CDK Direct - Deploy AWS CDK apps directly via SDK/Cloud Control API").version("0.252.0");
61612
62021
  program.addCommand(createBootstrapCommand());
61613
62022
  program.addCommand(createSynthCommand());
61614
62023
  program.addCommand(createListCommand());