@go-to-k/cdkd 0.244.0 → 0.246.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
@@ -2,7 +2,7 @@
2
2
  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-BzO-joNR.js";
3
3
  import { A as withErrorHandling, C as StackHasActiveImportsError, S as ResourceUpdateNotSupportedError, _ as MissingCdkCliError, a as normalizeAwsTagsToCfn, b as ProvisioningError, f as LocalInvokeBuildError$1, i as matchesCdkPath, k as normalizeAwsError, l as CdkdError, m as LocalStartServiceError, n as isTerminationProtectionPropagationError, o as resolveExplicitPhysicalId, p as LocalMigrateError, r as CDK_PATH_TAG, s as assertRegionMatch, t as disableInstanceApiTermination, v as NestedStackChildDirectDestroyError, w as StackTerminationProtectionError, x as ResourceTimeoutError, y as PartialFailureError } from "./ec2-termination-protection-q-WGWgxa.js";
4
4
  import { a as setAwsClients, i as resetAwsClients, r as getAwsClients, t as AwsClients } from "./aws-clients-B15NAPbL.js";
5
- import { $ as getDockerCmd, A as DiffCalculator, B as buildAssetRedirectMap, C as findActionableSilentDrops, Ct as resolveBucketRegion, D as refStateLookupFromResource, E as cfnRefValueFromPhysicalId, F as rebuildClientForBucketRegion, G as BOOTSTRAP_MARKER_PREFIX, H as loadPublishableAssetManifest, I as shouldRetainResource, J as parseBootstrapMarker, K as ensureAssetStorage, L as AssetPublisher, M as TemplateParser, N as LockManager, O as WAFv2WebACLProvider, P as S3StateBackend, Q as formatDockerLoginError, R as stringifyValue, S as ProviderRegistry, T as IntrinsicFunctionResolver, U as rewriteTemplateAssetReferences, V as createAssetRedirectResolver, W as AssetModeResolver, X as validateContainerRepoName, Y as validateAssetBucketName, Z as buildDockerImage, _ as green, _t as CFN_TEMPLATE_URL_LIMIT, a as withRetry, at as synthesisStatusMessage, b as IAMRoleProvider, bt as uploadCfnTemplate, c as computeImplicitDeleteEdges, ct as resolveApp, d as isStatefulRecreateTargetSync, dt as resolveSkipPrefix, et as runDockerForeground, f as renderStatefulReason, ft as resolveStateBucketWithDefault, g as gray, gt as CFN_TEMPLATE_BODY_LIMIT, h as cyan, ht as warnDeprecatedNoPrefixCliFlag, i as withResourceDeadline, it as Synthesizer, j as DagBuilder, k as applyRoleArnIfSet, l as extractDeploymentEventError, lt as resolveAutoAssetStorage, m as bold, mt as resolveUseCdkBootstrapAssets, n as DEFAULT_RESOURCE_WARN_AFTER_MS, nt as AssetManifestLoader, o as isRetryableTransientError, ot as getDefaultStateBucketName, p as formatResourceLine, pt as resolveStateBucketWithDefaultAndSource, q as getBootstrapMarkerKey, r as DeployEngine, rt as getDockerImageBySourceHash, s as IMPLICIT_DELETE_DEPENDENCIES, st as getLegacyStateBucketName, t as DEFAULT_RESOURCE_TIMEOUT_MS, tt as runDockerStreaming, u as MULTI_REGION_RECREATE_BLOCKED_TYPES, ut as resolveCaptureObservedState, v as red, vt as MIGRATE_TMP_PREFIX, w as CloudControlProvider, x as collectInlinePolicyNamesManagedBySiblings, xt as AssemblyReader, y as yellow, yt as findLargeInlineResources, z as WorkGraph } from "./deploy-engine-3KNTmr1u.js";
5
+ import { $ as getDockerCmd, A as DiffCalculator, B as buildAssetRedirectMap, C as findActionableSilentDrops, Ct as resolveBucketRegion, D as refStateLookupFromResource, E as cfnRefValueFromPhysicalId, F as rebuildClientForBucketRegion, G as BOOTSTRAP_MARKER_PREFIX, H as loadPublishableAssetManifest, I as shouldRetainResource, J as parseBootstrapMarker, K as ensureAssetStorage, L as AssetPublisher, M as TemplateParser, N as LockManager, O as WAFv2WebACLProvider, P as S3StateBackend, Q as formatDockerLoginError, R as stringifyValue, S as ProviderRegistry, T as IntrinsicFunctionResolver, U as rewriteTemplateAssetReferences, V as createAssetRedirectResolver, W as AssetModeResolver, X as validateContainerRepoName, Y as validateAssetBucketName, Z as buildDockerImage, _ as green, _t as CFN_TEMPLATE_URL_LIMIT, a as withRetry, at as synthesisStatusMessage, b as IAMRoleProvider, bt as uploadCfnTemplate, c as computeImplicitDeleteEdges, ct as resolveApp, d as isStatefulRecreateTargetSync, dt as resolveSkipPrefix, et as runDockerForeground, f as renderStatefulReason, ft as resolveStateBucketWithDefault, g as gray, gt as CFN_TEMPLATE_BODY_LIMIT, h as cyan, ht as warnDeprecatedNoPrefixCliFlag, i as withResourceDeadline, it as Synthesizer, j as DagBuilder, k as applyRoleArnIfSet, l as extractDeploymentEventError, lt as resolveAutoAssetStorage, m as bold, mt as resolveUseCdkBootstrapAssets, n as DEFAULT_RESOURCE_WARN_AFTER_MS, nt as AssetManifestLoader, o as isRetryableTransientError, ot as getDefaultStateBucketName, p as formatResourceLine, pt as resolveStateBucketWithDefaultAndSource, q as getBootstrapMarkerKey, r as DeployEngine, rt as getDockerImageBySourceHash, s as IMPLICIT_DELETE_DEPENDENCIES, st as getLegacyStateBucketName, t as DEFAULT_RESOURCE_TIMEOUT_MS, tt as runDockerStreaming, u as MULTI_REGION_RECREATE_BLOCKED_TYPES, ut as resolveCaptureObservedState, v as red, vt as MIGRATE_TMP_PREFIX, w as CloudControlProvider, x as collectInlinePolicyNamesManagedBySiblings, xt as AssemblyReader, y as yellow, yt as findLargeInlineResources, z as WorkGraph } from "./deploy-engine-Ci_7Y2ol.js";
6
6
  import { t as expectedOwnerParam } from "./expected-bucket-owner-BjEfUnkA.js";
7
7
  import { t as ASGProvider } from "./asg-provider-Dbl1_mwT.js";
8
8
  import { AsyncLocalStorage } from "node:async_hooks";
@@ -60,8 +60,9 @@ import { AddTagsToStreamCommand, CreateStreamCommand, DecreaseStreamRetentionPer
60
60
  import { CreateScheduleCommand, DeleteScheduleCommand, GetScheduleCommand, ResourceNotFoundException as ResourceNotFoundException$9, SchedulerClient, UpdateScheduleCommand } from "@aws-sdk/client-scheduler";
61
61
  import { AccessPointNotFound, CreateAccessPointCommand, CreateFileSystemCommand, CreateMountTargetCommand, DeleteAccessPointCommand, DeleteFileSystemCommand, DeleteMountTargetCommand, DescribeAccessPointsCommand, DescribeBackupPolicyCommand, DescribeFileSystemPolicyCommand, DescribeFileSystemsCommand, DescribeLifecycleConfigurationCommand, DescribeMountTargetSecurityGroupsCommand, DescribeMountTargetsCommand, EFSClient, FileSystemNotFound, ModifyMountTargetSecurityGroupsCommand, MountTargetNotFound, PutBackupPolicyCommand, PutFileSystemPolicyCommand, PutLifecycleConfigurationCommand, UpdateFileSystemCommand, UpdateFileSystemProtectionCommand } from "@aws-sdk/client-efs";
62
62
  import { CreateFileSystemCommand as CreateFileSystemCommand$1, CreateFileSystemFromBackupCommand, DeleteFileSystemCommand as DeleteFileSystemCommand$1, DescribeFileSystemsCommand as DescribeFileSystemsCommand$1, FSxClient, FileSystemNotFound as FileSystemNotFound$1, TagResourceCommand as TagResourceCommand$18, UntagResourceCommand as UntagResourceCommand$18, UpdateFileSystemCommand as UpdateFileSystemCommand$1 } from "@aws-sdk/client-fsx";
63
+ import { AddTagsCommand as AddTagsCommand$1, DescribeClusterCommand, EMRClient, InvalidRequestException, ModifyClusterCommand, PutAutoTerminationPolicyCommand, PutManagedScalingPolicyCommand, RemoveAutoTerminationPolicyCommand, RemoveManagedScalingPolicyCommand, RemoveTagsCommand as RemoveTagsCommand$1, RunJobFlowCommand, SetTerminationProtectionCommand, SetVisibleToAllUsersCommand, TerminateJobFlowsCommand } from "@aws-sdk/client-emr";
63
64
  import { CreateDeliveryStreamCommand, DeleteDeliveryStreamCommand, DescribeDeliveryStreamCommand, FirehoseClient, ListDeliveryStreamsCommand, ListTagsForDeliveryStreamCommand, ResourceNotFoundException as ResourceNotFoundException$10, TagDeliveryStreamCommand, UntagDeliveryStreamCommand, UpdateDestinationCommand } from "@aws-sdk/client-firehose";
64
- import { AddTagsCommand as AddTagsCommand$1, CloudTrailClient, CreateTrailCommand, DeleteTrailCommand, GetEventSelectorsCommand, GetInsightSelectorsCommand, GetTrailCommand, GetTrailStatusCommand, ListTagsCommand as ListTagsCommand$1, ListTrailsCommand, PutEventSelectorsCommand, PutInsightSelectorsCommand, RemoveTagsCommand as RemoveTagsCommand$1, StartLoggingCommand, StopLoggingCommand, TrailNotFoundException, UpdateTrailCommand } from "@aws-sdk/client-cloudtrail";
65
+ import { AddTagsCommand as AddTagsCommand$2, CloudTrailClient, CreateTrailCommand, DeleteTrailCommand, GetEventSelectorsCommand, GetInsightSelectorsCommand, GetTrailCommand, GetTrailStatusCommand, ListTagsCommand as ListTagsCommand$1, ListTrailsCommand, PutEventSelectorsCommand, PutInsightSelectorsCommand, RemoveTagsCommand as RemoveTagsCommand$2, StartLoggingCommand, StopLoggingCommand, TrailNotFoundException, UpdateTrailCommand } from "@aws-sdk/client-cloudtrail";
65
66
  import { BatchGetProjectsCommand, CodeBuildClient, CreateProjectCommand, DeleteProjectCommand, ListProjectsCommand, ResourceNotFoundException as ResourceNotFoundException$11, UpdateProjectCommand } from "@aws-sdk/client-codebuild";
66
67
  import { CodeCommitClient, CreateRepositoryCommand as CreateRepositoryCommand$1, DeleteRepositoryCommand as DeleteRepositoryCommand$1, GetRepositoryCommand, ListRepositoriesCommand, ListTagsForResourceCommand as ListTagsForResourceCommand$19, RepositoryDoesNotExistException, TagResourceCommand as TagResourceCommand$19, UntagResourceCommand as UntagResourceCommand$19, UpdateRepositoryDescriptionCommand, UpdateRepositoryEncryptionKeyCommand, UpdateRepositoryNameCommand } from "@aws-sdk/client-codecommit";
67
68
  import { CreateLifecyclePolicyCommand, DLMClient, DeleteLifecyclePolicyCommand as DeleteLifecyclePolicyCommand$1, GetLifecyclePoliciesCommand, GetLifecyclePolicyCommand as GetLifecyclePolicyCommand$1, ResourceNotFoundException as ResourceNotFoundException$12, TagResourceCommand as TagResourceCommand$20, UntagResourceCommand as UntagResourceCommand$20, UpdateLifecyclePolicyCommand } from "@aws-sdk/client-dlm";
@@ -1889,7 +1890,7 @@ const FLUSH_INTERVAL_MS = 2e3;
1889
1890
  const FLUSH_EVENT_THRESHOLD = 50;
1890
1891
  /** Build-time cdkd version, with a dev fallback for non-built contexts. */
1891
1892
  function getCdkdVersion() {
1892
- return "0.244.0";
1893
+ return "0.246.0";
1893
1894
  }
1894
1895
  /**
1895
1896
  * Generate a time-sortable unique run id, e.g.
@@ -34411,8 +34412,8 @@ var EFSProvider = class {
34411
34412
  * storage-capacity growth and deletes can take substantially longer —
34412
34413
  * mirror the Custom Resource provider's 1-hour ceiling.
34413
34414
  */
34414
- const DEFAULT_MAX_WAIT_MS = 3600 * 1e3;
34415
- const DEFAULT_POLL_INTERVAL_MS = 15e3;
34415
+ const DEFAULT_MAX_WAIT_MS$1 = 3600 * 1e3;
34416
+ const DEFAULT_POLL_INTERVAL_MS$1 = 15e3;
34416
34417
  /**
34417
34418
  * Lustre sub-properties that `UpdateFileSystem` accepts (the mutable
34418
34419
  * subset of the CFn `LustreConfiguration` block). Everything else in the
@@ -34443,8 +34444,8 @@ const TOP_LEVEL_IMMUTABLE_PROPS = [
34443
34444
  "KmsKeyId",
34444
34445
  "BackupId"
34445
34446
  ];
34446
- const toNumber = (v) => v === void 0 ? void 0 : Number(v);
34447
- const toBoolean = (v) => {
34447
+ const toNumber$1 = (v) => v === void 0 ? void 0 : Number(v);
34448
+ const toBoolean$1 = (v) => {
34448
34449
  if (v === void 0) return void 0;
34449
34450
  if (typeof v === "string") return v === "true";
34450
34451
  return Boolean(v);
@@ -34495,8 +34496,8 @@ var FSxFileSystemProvider = class {
34495
34496
  pollIntervalMs;
34496
34497
  maxWaitMs;
34497
34498
  constructor(options) {
34498
- this.pollIntervalMs = options?.pollIntervalMs ?? DEFAULT_POLL_INTERVAL_MS;
34499
- this.maxWaitMs = options?.maxWaitMs ?? DEFAULT_MAX_WAIT_MS;
34499
+ this.pollIntervalMs = options?.pollIntervalMs ?? DEFAULT_POLL_INTERVAL_MS$1;
34500
+ this.maxWaitMs = options?.maxWaitMs ?? DEFAULT_MAX_WAIT_MS$1;
34500
34501
  }
34501
34502
  handledProperties = new Map([["AWS::FSx::FileSystem", new Set([
34502
34503
  "BackupId",
@@ -34550,7 +34551,7 @@ var FSxFileSystemProvider = class {
34550
34551
  SecurityGroupIds: properties["SecurityGroupIds"],
34551
34552
  KmsKeyId: properties["KmsKeyId"],
34552
34553
  StorageType: properties["StorageType"],
34553
- StorageCapacity: toNumber(properties["StorageCapacity"]),
34554
+ StorageCapacity: toNumber$1(properties["StorageCapacity"]),
34554
34555
  FileSystemTypeVersion: properties["FileSystemTypeVersion"],
34555
34556
  NetworkType: properties["NetworkType"],
34556
34557
  LustreConfiguration: this.toCreateLustreConfiguration(properties["LustreConfiguration"]),
@@ -34603,24 +34604,24 @@ var FSxFileSystemProvider = class {
34603
34604
  WeeklyMaintenanceStartTime: config["WeeklyMaintenanceStartTime"],
34604
34605
  ImportPath: config["ImportPath"],
34605
34606
  ExportPath: config["ExportPath"],
34606
- ImportedFileChunkSize: toNumber(config["ImportedFileChunkSize"]),
34607
+ ImportedFileChunkSize: toNumber$1(config["ImportedFileChunkSize"]),
34607
34608
  DeploymentType: config["DeploymentType"],
34608
34609
  AutoImportPolicy: config["AutoImportPolicy"],
34609
- PerUnitStorageThroughput: toNumber(config["PerUnitStorageThroughput"]),
34610
+ PerUnitStorageThroughput: toNumber$1(config["PerUnitStorageThroughput"]),
34610
34611
  DailyAutomaticBackupStartTime: config["DailyAutomaticBackupStartTime"],
34611
- AutomaticBackupRetentionDays: toNumber(config["AutomaticBackupRetentionDays"]),
34612
- CopyTagsToBackups: toBoolean(config["CopyTagsToBackups"]),
34612
+ AutomaticBackupRetentionDays: toNumber$1(config["AutomaticBackupRetentionDays"]),
34613
+ CopyTagsToBackups: toBoolean$1(config["CopyTagsToBackups"]),
34613
34614
  DriveCacheType: config["DriveCacheType"],
34614
34615
  DataCompressionType: config["DataCompressionType"],
34615
- EfaEnabled: toBoolean(config["EfaEnabled"]),
34616
- ThroughputCapacity: toNumber(config["ThroughputCapacity"]),
34616
+ EfaEnabled: toBoolean$1(config["EfaEnabled"]),
34617
+ ThroughputCapacity: toNumber$1(config["ThroughputCapacity"]),
34617
34618
  MetadataConfiguration: metadata ? {
34618
34619
  Mode: metadata["Mode"],
34619
- Iops: toNumber(metadata["Iops"])
34620
+ Iops: toNumber$1(metadata["Iops"])
34620
34621
  } : void 0,
34621
34622
  DataReadCacheConfiguration: readCache ? {
34622
34623
  SizingMode: readCache["SizingMode"],
34623
- SizeGiB: toNumber(readCache["SizeGiB"])
34624
+ SizeGiB: toNumber$1(readCache["SizeGiB"])
34624
34625
  } : void 0
34625
34626
  };
34626
34627
  }
@@ -34650,7 +34651,7 @@ var FSxFileSystemProvider = class {
34650
34651
  case "AutomaticBackupRetentionDays":
34651
34652
  case "PerUnitStorageThroughput":
34652
34653
  case "ThroughputCapacity":
34653
- lustreMutableDiff[key] = toNumber(next);
34654
+ lustreMutableDiff[key] = toNumber$1(next);
34654
34655
  break;
34655
34656
  case "AutoImportPolicy":
34656
34657
  case "DataCompressionType":
@@ -34660,7 +34661,7 @@ var FSxFileSystemProvider = class {
34660
34661
  const metadata = next;
34661
34662
  lustreMutableDiff.MetadataConfiguration = metadata ? {
34662
34663
  Mode: metadata["Mode"],
34663
- Iops: toNumber(metadata["Iops"])
34664
+ Iops: toNumber$1(metadata["Iops"])
34664
34665
  } : void 0;
34665
34666
  break;
34666
34667
  }
@@ -34668,7 +34669,7 @@ var FSxFileSystemProvider = class {
34668
34669
  const readCache = next;
34669
34670
  lustreMutableDiff.DataReadCacheConfiguration = readCache ? {
34670
34671
  SizingMode: readCache["SizingMode"],
34671
- SizeGiB: toNumber(readCache["SizeGiB"])
34672
+ SizeGiB: toNumber$1(readCache["SizeGiB"])
34672
34673
  } : void 0;
34673
34674
  break;
34674
34675
  }
@@ -34694,7 +34695,7 @@ var FSxFileSystemProvider = class {
34694
34695
  const callTimeMs = Date.now();
34695
34696
  const respPendingActions = ((await this.getClient().send(new UpdateFileSystemCommand$1({
34696
34697
  FileSystemId: physicalId,
34697
- ...storageCapacityChanged && { StorageCapacity: toNumber(properties["StorageCapacity"]) },
34698
+ ...storageCapacityChanged && { StorageCapacity: toNumber$1(properties["StorageCapacity"]) },
34698
34699
  ...storageTypeChanged && { StorageType: properties["StorageType"] },
34699
34700
  ...typeVersionChanged && { FileSystemTypeVersion: properties["FileSystemTypeVersion"] },
34700
34701
  ...networkTypeChanged && { NetworkType: properties["NetworkType"] },
@@ -34998,6 +34999,528 @@ var FSxFileSystemProvider = class {
34998
34999
  }
34999
35000
  };
35000
35001
 
35002
+ //#endregion
35003
+ //#region src/provisioning/providers/emr-cluster-provider.ts
35004
+ /**
35005
+ * Default polling budget for EMR cluster lifecycle transitions. A cluster
35006
+ * create (RunJobFlow → WAITING/RUNNING) typically takes 5-15 minutes
35007
+ * (instance provisioning + bootstrap + application install), and a delete
35008
+ * (TerminateJobFlows → TERMINATED) 5-10 minutes. Mirror the Custom Resource /
35009
+ * FSx providers' 1-hour ceiling so the slowest realistic create/terminate
35010
+ * still fits inside the per-resource deadline.
35011
+ */
35012
+ const DEFAULT_MAX_WAIT_MS = 3600 * 1e3;
35013
+ const DEFAULT_POLL_INTERVAL_MS = 15e3;
35014
+ /** Cluster states that mean "create succeeded, the cluster is up". */
35015
+ const CREATE_READY_STATES = new Set(["WAITING", "RUNNING"]);
35016
+ /**
35017
+ * Cluster states that mean "the cluster is gone" (for delete polling and
35018
+ * delete idempotency). `TERMINATED_WITH_ERRORS` still counts as gone — the
35019
+ * cluster no longer bills — but is logged so a failed terminate is visible.
35020
+ */
35021
+ const TERMINAL_STATES = new Set(["TERMINATED", "TERMINATED_WITH_ERRORS"]);
35022
+ /**
35023
+ * Top-level CFn properties that map to a MUTABLE EMR API surface. Every other
35024
+ * property is either registry-createOnly (routed through DELETE+CREATE by the
35025
+ * replacement-detection layer) or lives inside the `Instances` block (handled
35026
+ * specially in `update()` — only `TerminationProtected` is mutable there).
35027
+ */
35028
+ const MUTABLE_TOP_LEVEL_PROPS = new Set([
35029
+ "Tags",
35030
+ "VisibleToAllUsers",
35031
+ "StepConcurrencyLevel",
35032
+ "ManagedScalingPolicy",
35033
+ "AutoTerminationPolicy"
35034
+ ]);
35035
+ const toNumber = (v) => {
35036
+ if (v === void 0) return void 0;
35037
+ const n = Number(v);
35038
+ return Number.isNaN(n) ? void 0 : n;
35039
+ };
35040
+ const toBoolean = (v) => {
35041
+ if (v === void 0) return void 0;
35042
+ if (typeof v === "string") return v === "true";
35043
+ return Boolean(v);
35044
+ };
35045
+ const jsonEqual = (a, b) => JSON.stringify(a) === JSON.stringify(b);
35046
+ /**
35047
+ * SDK Provider for `AWS::EMR::Cluster` (EMR on EC2).
35048
+ *
35049
+ * The type is `ProvisioningType: NON_PROVISIONABLE` in the CFn registry, so
35050
+ * cdkd's Cloud Control fallback cannot handle it (issue #1043) — pre-flight
35051
+ * would otherwise reject it via `unsupported-types.generated.ts`.
35052
+ *
35053
+ * Lifecycle — a cluster is a stateful, per-instance-hour-billed resource, so
35054
+ * every path is polled to completion:
35055
+ * - `create` → `RunJobFlow` + poll `DescribeCluster` until `WAITING`/`RUNNING`
35056
+ * (the cluster is up and idle / running steps). A `TERMINATED*` terminal
35057
+ * during create is a hard error; the partially-created cluster is
35058
+ * best-effort terminated so it does not bill.
35059
+ * - `update` → the limited mutable surface only: `SetTerminationProtection`
35060
+ * (`Instances.TerminationProtected`), `SetVisibleToAllUsers`,
35061
+ * `ModifyCluster` (`StepConcurrencyLevel`), managed-scaling / auto-
35062
+ * termination policy APIs, and `AddTags`/`RemoveTags`. Everything else
35063
+ * (instance topology, applications, release label, ...) is createOnly →
35064
+ * replacement via the schema fallback; a change that reaches `update()`
35065
+ * anyway is refused with a `--replace` pointer.
35066
+ * - `delete` → `TerminateJobFlows` + poll until `TERMINATED`. Idempotent on
35067
+ * an already-gone cluster (`assertRegionMatch` guards the region). Honors
35068
+ * termination protection: under `--remove-protection` it flips
35069
+ * `SetTerminationProtection(false)` first (mirroring the EC2/ASG pattern).
35070
+ *
35071
+ * `getMinResourceTimeoutMs()` lifts the deploy engine's per-resource deadline
35072
+ * to the polling ceiling (mirrors `CustomResourceProvider` / `FSxFileSystem
35073
+ * Provider`), so slow EMR creates/terminates don't require `--resource-timeout`.
35074
+ */
35075
+ var EMRClusterProvider = class {
35076
+ /**
35077
+ * Cloud Control has NO handlers for this type (`ProvisioningType:
35078
+ * NON_PROVISIONABLE`), so the deploy engine's #614 silent-drop auto-route
35079
+ * MUST NOT send an unhandled-property EMR template to CC — it would fail at
35080
+ * provisioning time with an opaque UnsupportedActionException. With this
35081
+ * opt-out the ProviderRegistry rejects such templates pre-flight with a
35082
+ * clear error instead.
35083
+ */
35084
+ disableCcApiFallback = true;
35085
+ client;
35086
+ providerRegion = process.env["AWS_REGION"];
35087
+ logger = getLogger().child("EMRClusterProvider");
35088
+ pollIntervalMs;
35089
+ maxWaitMs;
35090
+ constructor(options) {
35091
+ this.pollIntervalMs = options?.pollIntervalMs ?? DEFAULT_POLL_INTERVAL_MS;
35092
+ this.maxWaitMs = options?.maxWaitMs ?? DEFAULT_MAX_WAIT_MS;
35093
+ }
35094
+ handledProperties = new Map([["AWS::EMR::Cluster", new Set([
35095
+ "AdditionalInfo",
35096
+ "Applications",
35097
+ "AutoScalingRole",
35098
+ "AutoTerminationPolicy",
35099
+ "BootstrapActions",
35100
+ "Configurations",
35101
+ "CustomAmiId",
35102
+ "EbsRootVolumeIops",
35103
+ "EbsRootVolumeSize",
35104
+ "EbsRootVolumeThroughput",
35105
+ "Instances",
35106
+ "JobFlowRole",
35107
+ "KerberosAttributes",
35108
+ "LogEncryptionKmsKeyId",
35109
+ "LogUri",
35110
+ "ManagedScalingPolicy",
35111
+ "Name",
35112
+ "OSReleaseLabel",
35113
+ "PlacementGroupConfigs",
35114
+ "ReleaseLabel",
35115
+ "ScaleDownBehavior",
35116
+ "SecurityConfiguration",
35117
+ "ServiceRole",
35118
+ "StepConcurrencyLevel",
35119
+ "Steps",
35120
+ "Tags",
35121
+ "VisibleToAllUsers"
35122
+ ])]]);
35123
+ getClient() {
35124
+ if (!this.client) this.client = new EMRClient(this.providerRegion ? { region: this.providerRegion } : {});
35125
+ return this.client;
35126
+ }
35127
+ /**
35128
+ * Self-reported minimum per-resource timeout: the deploy engine resolves
35129
+ * `max(getMinResourceTimeoutMs(), globalCliDefault)` so EMR's slow
35130
+ * create/terminate polling fits inside the resource deadline without the
35131
+ * user passing `--resource-timeout`.
35132
+ *
35133
+ * Return the poll ceiling PLUS one poll interval (not exactly `maxWaitMs`):
35134
+ * the deploy engine's `withResourceDeadline` is a non-cancelling
35135
+ * `Promise.race`, so if the external deadline were exactly equal to the
35136
+ * internal poll ceiling the two could fire together and the external one
35137
+ * could win — leaving the internal timeout + best-effort rollback terminate
35138
+ * un-run and (if the CLI then exits) a live cluster billing. The extra
35139
+ * interval guarantees the internal `waitForCluster*` timeout fires first and
35140
+ * the rollback path always runs.
35141
+ */
35142
+ getMinResourceTimeoutMs() {
35143
+ return this.maxWaitMs + this.pollIntervalMs;
35144
+ }
35145
+ async create(logicalId, resourceType, properties) {
35146
+ if (resourceType !== "AWS::EMR::Cluster") throw new ProvisioningError(`Unsupported resource type: ${resourceType}`, resourceType, logicalId);
35147
+ this.logger.debug(`Creating EMR Cluster ${logicalId}`);
35148
+ let clusterId;
35149
+ try {
35150
+ const tags = properties["Tags"];
35151
+ const input = {
35152
+ Name: properties["Name"],
35153
+ ReleaseLabel: properties["ReleaseLabel"],
35154
+ ServiceRole: properties["ServiceRole"],
35155
+ JobFlowRole: properties["JobFlowRole"],
35156
+ LogUri: properties["LogUri"],
35157
+ LogEncryptionKmsKeyId: properties["LogEncryptionKmsKeyId"],
35158
+ AdditionalInfo: properties["AdditionalInfo"],
35159
+ AutoScalingRole: properties["AutoScalingRole"],
35160
+ ScaleDownBehavior: properties["ScaleDownBehavior"],
35161
+ CustomAmiId: properties["CustomAmiId"],
35162
+ OSReleaseLabel: properties["OSReleaseLabel"],
35163
+ SecurityConfiguration: properties["SecurityConfiguration"],
35164
+ EbsRootVolumeSize: toNumber(properties["EbsRootVolumeSize"]),
35165
+ EbsRootVolumeIops: toNumber(properties["EbsRootVolumeIops"]),
35166
+ EbsRootVolumeThroughput: toNumber(properties["EbsRootVolumeThroughput"]),
35167
+ StepConcurrencyLevel: toNumber(properties["StepConcurrencyLevel"]),
35168
+ VisibleToAllUsers: toBoolean(properties["VisibleToAllUsers"]),
35169
+ Applications: properties["Applications"],
35170
+ Configurations: properties["Configurations"],
35171
+ BootstrapActions: properties["BootstrapActions"],
35172
+ Steps: properties["Steps"],
35173
+ KerberosAttributes: properties["KerberosAttributes"],
35174
+ PlacementGroupConfigs: properties["PlacementGroupConfigs"],
35175
+ ManagedScalingPolicy: this.toManagedScalingPolicy(properties["ManagedScalingPolicy"]),
35176
+ AutoTerminationPolicy: this.toAutoTerminationPolicy(properties["AutoTerminationPolicy"]),
35177
+ Tags: tags?.map((t) => ({
35178
+ Key: t.Key,
35179
+ Value: t.Value
35180
+ })),
35181
+ Instances: this.toJobFlowInstancesConfig(properties["Instances"])
35182
+ };
35183
+ clusterId = (await this.getClient().send(new RunJobFlowCommand(input))).JobFlowId;
35184
+ if (!clusterId) throw new ProvisioningError(`EMR RunJobFlow for ${logicalId} returned no JobFlowId`, resourceType, logicalId);
35185
+ const cluster = await this.waitForClusterReady(clusterId, logicalId, resourceType);
35186
+ this.logger.debug(`Successfully created EMR Cluster ${logicalId}: ${clusterId}`);
35187
+ return {
35188
+ physicalId: clusterId,
35189
+ attributes: this.buildAttributes(cluster)
35190
+ };
35191
+ } catch (error) {
35192
+ if (clusterId !== void 0) try {
35193
+ await this.getClient().send(new SetTerminationProtectionCommand({
35194
+ JobFlowIds: [clusterId],
35195
+ TerminationProtected: false
35196
+ }));
35197
+ await this.getClient().send(new TerminateJobFlowsCommand({ JobFlowIds: [clusterId] }));
35198
+ this.logger.warn(`Rolled back partially-created EMR Cluster ${clusterId}`);
35199
+ } catch (cleanupError) {
35200
+ this.logger.warn(`Failed to roll back partially-created EMR Cluster ${clusterId}: ${cleanupError instanceof Error ? cleanupError.message : String(cleanupError)} — terminate it manually to stop billing`);
35201
+ }
35202
+ if (error instanceof ProvisioningError) throw error;
35203
+ const cause = error instanceof Error ? error : void 0;
35204
+ throw new ProvisioningError(`Failed to create EMR Cluster ${logicalId}: ${error instanceof Error ? error.message : String(error)}`, resourceType, logicalId, void 0, cause);
35205
+ }
35206
+ }
35207
+ /**
35208
+ * Map the CFn `Instances` (`JobFlowInstancesConfig`) block to the SDK
35209
+ * `RunJobFlow.Instances` shape. Most field names are identical; the one
35210
+ * structural difference is the role-keyed CFn groups/fleets
35211
+ * (`MasterInstanceGroup` / `CoreInstanceGroup` / `TaskInstanceGroups` and
35212
+ * the `*InstanceFleet(s)` siblings) which the SDK expresses as flat
35213
+ * `InstanceGroups` / `InstanceFleets` arrays with an explicit
35214
+ * `InstanceRole` / `InstanceFleetType` discriminator per entry.
35215
+ */
35216
+ toJobFlowInstancesConfig(config) {
35217
+ if (!config) return void 0;
35218
+ const instanceGroups = [];
35219
+ const pushGroup = (raw, role) => {
35220
+ if (raw === void 0 || raw === null) return;
35221
+ instanceGroups.push(this.toInstanceGroupConfig(raw, role));
35222
+ };
35223
+ pushGroup(config["MasterInstanceGroup"], "MASTER");
35224
+ pushGroup(config["CoreInstanceGroup"], "CORE");
35225
+ for (const task of config["TaskInstanceGroups"] ?? []) pushGroup(task, "TASK");
35226
+ const instanceFleets = [];
35227
+ const pushFleet = (raw, type) => {
35228
+ if (raw === void 0 || raw === null) return;
35229
+ instanceFleets.push(this.toInstanceFleetConfig(raw, type));
35230
+ };
35231
+ pushFleet(config["MasterInstanceFleet"], "MASTER");
35232
+ pushFleet(config["CoreInstanceFleet"], "CORE");
35233
+ for (const task of config["TaskInstanceFleets"] ?? []) pushFleet(task, "TASK");
35234
+ return {
35235
+ InstanceGroups: instanceGroups.length > 0 ? instanceGroups : void 0,
35236
+ InstanceFleets: instanceFleets.length > 0 ? instanceFleets : void 0,
35237
+ Ec2KeyName: config["Ec2KeyName"],
35238
+ Ec2SubnetId: config["Ec2SubnetId"],
35239
+ Ec2SubnetIds: config["Ec2SubnetIds"],
35240
+ HadoopVersion: config["HadoopVersion"],
35241
+ Placement: config["Placement"],
35242
+ KeepJobFlowAliveWhenNoSteps: toBoolean(config["KeepJobFlowAliveWhenNoSteps"]),
35243
+ TerminationProtected: toBoolean(config["TerminationProtected"]),
35244
+ UnhealthyNodeReplacement: toBoolean(config["UnhealthyNodeReplacement"]),
35245
+ EmrManagedMasterSecurityGroup: config["EmrManagedMasterSecurityGroup"],
35246
+ EmrManagedSlaveSecurityGroup: config["EmrManagedSlaveSecurityGroup"],
35247
+ ServiceAccessSecurityGroup: config["ServiceAccessSecurityGroup"],
35248
+ AdditionalMasterSecurityGroups: config["AdditionalMasterSecurityGroups"],
35249
+ AdditionalSlaveSecurityGroups: config["AdditionalSlaveSecurityGroups"]
35250
+ };
35251
+ }
35252
+ toInstanceGroupConfig(raw, role) {
35253
+ return {
35254
+ InstanceRole: role,
35255
+ InstanceType: raw["InstanceType"],
35256
+ InstanceCount: toNumber(raw["InstanceCount"]),
35257
+ Name: raw["Name"],
35258
+ Market: raw["Market"],
35259
+ BidPrice: raw["BidPrice"],
35260
+ Configurations: raw["Configurations"],
35261
+ EbsConfiguration: raw["EbsConfiguration"],
35262
+ AutoScalingPolicy: raw["AutoScalingPolicy"],
35263
+ CustomAmiId: raw["CustomAmiId"]
35264
+ };
35265
+ }
35266
+ toInstanceFleetConfig(raw, type) {
35267
+ return {
35268
+ InstanceFleetType: type,
35269
+ Name: raw["Name"],
35270
+ TargetOnDemandCapacity: toNumber(raw["TargetOnDemandCapacity"]),
35271
+ TargetSpotCapacity: toNumber(raw["TargetSpotCapacity"]),
35272
+ InstanceTypeConfigs: raw["InstanceTypeConfigs"],
35273
+ LaunchSpecifications: raw["LaunchSpecifications"],
35274
+ ResizeSpecifications: raw["ResizeSpecifications"]
35275
+ };
35276
+ }
35277
+ toManagedScalingPolicy(config) {
35278
+ if (!config) return void 0;
35279
+ const limits = config["ComputeLimits"];
35280
+ if (!limits) return void 0;
35281
+ return {
35282
+ ComputeLimits: {
35283
+ UnitType: limits["UnitType"],
35284
+ MinimumCapacityUnits: toNumber(limits["MinimumCapacityUnits"]),
35285
+ MaximumCapacityUnits: toNumber(limits["MaximumCapacityUnits"]),
35286
+ MaximumOnDemandCapacityUnits: toNumber(limits["MaximumOnDemandCapacityUnits"]),
35287
+ MaximumCoreCapacityUnits: toNumber(limits["MaximumCoreCapacityUnits"])
35288
+ },
35289
+ UtilizationPerformanceIndex: toNumber(config["UtilizationPerformanceIndex"]),
35290
+ ScalingStrategy: config["ScalingStrategy"]
35291
+ };
35292
+ }
35293
+ toAutoTerminationPolicy(config) {
35294
+ if (!config) return void 0;
35295
+ return { IdleTimeout: toNumber(config["IdleTimeout"]) };
35296
+ }
35297
+ async update(logicalId, physicalId, resourceType, properties, previousProperties) {
35298
+ const changed = (key) => !jsonEqual(properties[key], previousProperties[key]);
35299
+ const nextInstances = properties["Instances"] ?? {};
35300
+ const prevInstances = previousProperties["Instances"] ?? {};
35301
+ let terminationProtectedChanged = false;
35302
+ if (changed("Instances")) {
35303
+ const instanceKeys = new Set([...Object.keys(nextInstances), ...Object.keys(prevInstances)]);
35304
+ for (const key of instanceKeys) {
35305
+ if (jsonEqual(nextInstances[key], prevInstances[key])) continue;
35306
+ if (key === "TerminationProtected") {
35307
+ terminationProtectedChanged = true;
35308
+ continue;
35309
+ }
35310
+ throw new ResourceUpdateNotSupportedError(resourceType, logicalId, `AWS EMR Cluster Instances.${key} is immutable on AWS — a running cluster's instance topology / networking cannot be changed in place. Re-deploy with cdkd deploy --replace, or destroy + redeploy the stack.`);
35311
+ }
35312
+ }
35313
+ for (const key of Object.keys({
35314
+ ...properties,
35315
+ ...previousProperties
35316
+ })) {
35317
+ if (key === "Instances") continue;
35318
+ if (!changed(key)) continue;
35319
+ if (!MUTABLE_TOP_LEVEL_PROPS.has(key)) throw new ResourceUpdateNotSupportedError(resourceType, logicalId, `AWS EMR Cluster ${key} is immutable on AWS — it is fixed at cluster creation. Re-deploy with cdkd deploy --replace, or destroy + redeploy the stack.`);
35320
+ }
35321
+ const visibleChanged = changed("VisibleToAllUsers");
35322
+ const stepConcurrencyChanged = changed("StepConcurrencyLevel");
35323
+ const managedScalingChanged = changed("ManagedScalingPolicy");
35324
+ const autoTerminationChanged = changed("AutoTerminationPolicy");
35325
+ const tagsChanged = changed("Tags");
35326
+ if (!terminationProtectedChanged && !visibleChanged && !stepConcurrencyChanged && !managedScalingChanged && !autoTerminationChanged && !tagsChanged) {
35327
+ this.logger.debug(`No mutable diff for EMR Cluster ${logicalId}, skipping update`);
35328
+ return {
35329
+ physicalId,
35330
+ wasReplaced: false
35331
+ };
35332
+ }
35333
+ this.logger.debug(`Updating EMR Cluster ${logicalId}: ${physicalId}`);
35334
+ try {
35335
+ if (terminationProtectedChanged) await this.getClient().send(new SetTerminationProtectionCommand({
35336
+ JobFlowIds: [physicalId],
35337
+ TerminationProtected: toBoolean(nextInstances["TerminationProtected"]) ?? false
35338
+ }));
35339
+ if (visibleChanged) await this.getClient().send(new SetVisibleToAllUsersCommand({
35340
+ JobFlowIds: [physicalId],
35341
+ VisibleToAllUsers: toBoolean(properties["VisibleToAllUsers"]) ?? false
35342
+ }));
35343
+ if (stepConcurrencyChanged) await this.getClient().send(new ModifyClusterCommand({
35344
+ ClusterId: physicalId,
35345
+ StepConcurrencyLevel: toNumber(properties["StepConcurrencyLevel"])
35346
+ }));
35347
+ if (managedScalingChanged) {
35348
+ const next = this.toManagedScalingPolicy(properties["ManagedScalingPolicy"]);
35349
+ if (next) await this.getClient().send(new PutManagedScalingPolicyCommand({
35350
+ ClusterId: physicalId,
35351
+ ManagedScalingPolicy: next
35352
+ }));
35353
+ else await this.getClient().send(new RemoveManagedScalingPolicyCommand({ ClusterId: physicalId }));
35354
+ }
35355
+ if (autoTerminationChanged) {
35356
+ const next = this.toAutoTerminationPolicy(properties["AutoTerminationPolicy"]);
35357
+ if (next && next.IdleTimeout !== void 0) await this.getClient().send(new PutAutoTerminationPolicyCommand({
35358
+ ClusterId: physicalId,
35359
+ AutoTerminationPolicy: next
35360
+ }));
35361
+ else await this.getClient().send(new RemoveAutoTerminationPolicyCommand({ ClusterId: physicalId }));
35362
+ }
35363
+ if (tagsChanged) await this.applyTagDiff(physicalId, properties["Tags"], previousProperties["Tags"]);
35364
+ let cluster;
35365
+ try {
35366
+ cluster = (await this.getClient().send(new DescribeClusterCommand({ ClusterId: physicalId }))).Cluster;
35367
+ } catch (describeError) {
35368
+ this.logger.debug(`Post-update attribute refresh for ${physicalId} failed (returning without attributes): ${describeError instanceof Error ? describeError.message : String(describeError)}`);
35369
+ }
35370
+ this.logger.debug(`Successfully updated EMR Cluster ${logicalId}`);
35371
+ return {
35372
+ physicalId,
35373
+ wasReplaced: false,
35374
+ ...cluster && { attributes: this.buildAttributes(cluster) }
35375
+ };
35376
+ } catch (error) {
35377
+ if (error instanceof ProvisioningError) throw error;
35378
+ const cause = error instanceof Error ? error : void 0;
35379
+ throw new ProvisioningError(`Failed to update EMR Cluster ${logicalId}: ${error instanceof Error ? error.message : String(error)}`, resourceType, logicalId, physicalId, cause);
35380
+ }
35381
+ }
35382
+ /**
35383
+ * Apply a `Tags` diff via `AddTags` / `RemoveTags`. Full-tag-removal is
35384
+ * handled explicitly (a tag present before and absent now must be removed
35385
+ * via `RemoveTags` — mirrors the #981 ECR regression class where an empty
35386
+ * desired tag set silently left the old tags in place).
35387
+ */
35388
+ async applyTagDiff(physicalId, nextTags, prevTags) {
35389
+ const next = new Map((nextTags ?? []).map((t) => [t.Key, t.Value]));
35390
+ const prev = new Map((prevTags ?? []).map((t) => [t.Key, t.Value]));
35391
+ const toSet = [];
35392
+ for (const [key, value] of next) {
35393
+ if (key === void 0) continue;
35394
+ if (prev.get(key) !== value) toSet.push({
35395
+ Key: key,
35396
+ Value: value
35397
+ });
35398
+ }
35399
+ const toRemove = [];
35400
+ for (const key of prev.keys()) if (key !== void 0 && !next.has(key)) toRemove.push(key);
35401
+ if (toRemove.length > 0) await this.getClient().send(new RemoveTagsCommand$1({
35402
+ ResourceId: physicalId,
35403
+ TagKeys: toRemove
35404
+ }));
35405
+ if (toSet.length > 0) await this.getClient().send(new AddTagsCommand$1({
35406
+ ResourceId: physicalId,
35407
+ Tags: toSet
35408
+ }));
35409
+ }
35410
+ async delete(logicalId, physicalId, resourceType, _properties, context) {
35411
+ this.logger.debug(`Deleting EMR Cluster ${logicalId}: ${physicalId}`);
35412
+ let current;
35413
+ try {
35414
+ current = (await this.getClient().send(new DescribeClusterCommand({ ClusterId: physicalId }))).Cluster;
35415
+ } catch (error) {
35416
+ if (error instanceof InvalidRequestException) {
35417
+ assertRegionMatch(await this.getClient().config.region(), context?.expectedRegion, resourceType, logicalId, physicalId);
35418
+ this.logger.debug(`EMR Cluster ${physicalId} does not exist, skipping deletion`);
35419
+ return;
35420
+ }
35421
+ const cause = error instanceof Error ? error : void 0;
35422
+ throw new ProvisioningError(`Failed to describe EMR Cluster ${logicalId} before deletion: ${error instanceof Error ? error.message : String(error)}`, resourceType, logicalId, physicalId, cause);
35423
+ }
35424
+ const currentState = current?.Status?.State;
35425
+ if (currentState && TERMINAL_STATES.has(currentState)) {
35426
+ this.logger.debug(`EMR Cluster ${physicalId} already ${currentState}, skipping deletion`);
35427
+ return;
35428
+ }
35429
+ try {
35430
+ if (context?.removeProtection) {
35431
+ await this.getClient().send(new SetTerminationProtectionCommand({
35432
+ JobFlowIds: [physicalId],
35433
+ TerminationProtected: false
35434
+ }));
35435
+ this.logger.debug(`Disabled termination protection on EMR Cluster ${physicalId} before deletion`);
35436
+ }
35437
+ await this.getClient().send(new TerminateJobFlowsCommand({ JobFlowIds: [physicalId] }));
35438
+ } catch (error) {
35439
+ const cause = error instanceof Error ? error : void 0;
35440
+ throw new ProvisioningError(`Failed to terminate EMR Cluster ${logicalId}: ${error instanceof Error ? error.message : String(error)}`, resourceType, logicalId, physicalId, cause);
35441
+ }
35442
+ await this.waitForClusterTerminated(physicalId, logicalId, resourceType);
35443
+ this.logger.debug(`Successfully deleted EMR Cluster ${logicalId}`);
35444
+ }
35445
+ /**
35446
+ * Issue the polling `DescribeCluster` with bounded tolerance for TRANSIENT
35447
+ * errors (throttling / 5xx / connection resets): up to
35448
+ * `maxConsecutiveTransient` consecutive failures are absorbed before the
35449
+ * error propagates. A 10-minute poll at 15s intervals would otherwise turn
35450
+ * a single throttle into a spurious failure + rollback cycle. Non-transient
35451
+ * errors propagate immediately.
35452
+ */
35453
+ async describeForPoll(clusterId, transientState, maxConsecutiveTransient = 5) {
35454
+ try {
35455
+ const response = await this.getClient().send(new DescribeClusterCommand({ ClusterId: clusterId }));
35456
+ transientState.count = 0;
35457
+ return response.Cluster;
35458
+ } catch (error) {
35459
+ const name = error instanceof Error ? error.name : "";
35460
+ const msg = error instanceof Error ? error.message : String(error);
35461
+ if ((name === "ThrottlingException" || name === "InternalServerException" || name === "InternalServerError" || name === "TimeoutError" || /rate exceeded|too many requests|throttl|timed? ?out|ECONNRESET|EPIPE|socket hang up/i.test(msg)) && transientState.count < maxConsecutiveTransient) {
35462
+ transientState.count += 1;
35463
+ this.logger.debug(`Transient DescribeCluster error while polling ${clusterId} (${transientState.count}/${maxConsecutiveTransient}): ${msg} — retrying`);
35464
+ return;
35465
+ }
35466
+ throw error;
35467
+ }
35468
+ }
35469
+ async waitForClusterReady(clusterId, logicalId, resourceType) {
35470
+ const startTime = Date.now();
35471
+ const transientState = { count: 0 };
35472
+ while (Date.now() - startTime < this.maxWaitMs) {
35473
+ const cluster = await this.describeForPoll(clusterId, transientState);
35474
+ const state = cluster?.Status?.State;
35475
+ if (cluster && state && CREATE_READY_STATES.has(state)) return cluster;
35476
+ if (state && TERMINAL_STATES.has(state)) throw new ProvisioningError(`EMR Cluster ${clusterId} entered terminal state ${state} during creation: ${cluster?.Status?.StateChangeReason?.Message ?? "no state-change reason reported"}`, resourceType, logicalId, clusterId);
35477
+ this.logger.debug(`EMR Cluster ${clusterId} state: ${state ?? "unknown"}, waiting...`);
35478
+ await new Promise((resolve) => setTimeout(resolve, this.pollIntervalMs));
35479
+ }
35480
+ throw new ProvisioningError(`Timed out waiting for EMR Cluster ${clusterId} to reach WAITING/RUNNING (${Math.round(this.maxWaitMs / 6e4)} min)`, resourceType, logicalId, clusterId);
35481
+ }
35482
+ async waitForClusterTerminated(clusterId, logicalId, resourceType) {
35483
+ const startTime = Date.now();
35484
+ const transientState = { count: 0 };
35485
+ while (Date.now() - startTime < this.maxWaitMs) {
35486
+ let cluster;
35487
+ try {
35488
+ cluster = await this.describeForPoll(clusterId, transientState);
35489
+ } catch (error) {
35490
+ if (error instanceof InvalidRequestException) return;
35491
+ const cause = error instanceof Error ? error : void 0;
35492
+ throw new ProvisioningError(`Failed to poll EMR Cluster ${clusterId} termination: ${error instanceof Error ? error.message : String(error)}`, resourceType, logicalId, clusterId, cause);
35493
+ }
35494
+ const state = cluster?.Status?.State;
35495
+ if (state && TERMINAL_STATES.has(state)) {
35496
+ if (state === "TERMINATED_WITH_ERRORS") {
35497
+ const reason = cluster?.Status?.StateChangeReason?.Message ?? "no state-change reason reported";
35498
+ this.logger.warn(`EMR Cluster ${clusterId} terminated with errors: ${reason} (the cluster is gone and no longer bills)`);
35499
+ }
35500
+ return;
35501
+ }
35502
+ this.logger.debug(`EMR Cluster ${clusterId} state: ${state ?? "unknown"}, waiting for termination...`);
35503
+ await new Promise((resolve) => setTimeout(resolve, this.pollIntervalMs));
35504
+ }
35505
+ throw new ProvisioningError(`Timed out waiting for EMR Cluster ${clusterId} termination (${Math.round(this.maxWaitMs / 6e4)} min) — verify and terminate it manually to stop billing`, resourceType, logicalId, clusterId);
35506
+ }
35507
+ buildAttributes(cluster) {
35508
+ const attributes = {};
35509
+ if (cluster?.Id !== void 0) attributes["Id"] = cluster.Id;
35510
+ if (cluster?.MasterPublicDnsName !== void 0) attributes["MasterPublicDNS"] = cluster.MasterPublicDnsName;
35511
+ return attributes;
35512
+ }
35513
+ async getAttribute(physicalId, _resourceType, attributeName) {
35514
+ if (attributeName === "Id") return physicalId;
35515
+ const cluster = (await this.getClient().send(new DescribeClusterCommand({ ClusterId: physicalId }))).Cluster;
35516
+ if (!cluster) return void 0;
35517
+ switch (attributeName) {
35518
+ case "MasterPublicDNS": return cluster.MasterPublicDnsName;
35519
+ default: return;
35520
+ }
35521
+ }
35522
+ };
35523
+
35001
35524
  //#endregion
35002
35525
  //#region src/provisioning/providers/firehose-provider.ts
35003
35526
  /**
@@ -36479,14 +37002,14 @@ var CloudTrailProvider = class {
36479
37002
  Value: v
36480
37003
  });
36481
37004
  if (tagsToRemove.length > 0) {
36482
- await this.getClient().send(new RemoveTagsCommand$1({
37005
+ await this.getClient().send(new RemoveTagsCommand$2({
36483
37006
  ResourceId: trailArn,
36484
37007
  TagsList: tagsToRemove
36485
37008
  }));
36486
37009
  this.logger.debug(`Removed ${tagsToRemove.length} tag(s) from CloudTrail Trail ${trailArn}`);
36487
37010
  }
36488
37011
  if (tagsToAdd.length > 0) {
36489
- await this.getClient().send(new AddTagsCommand$1({
37012
+ await this.getClient().send(new AddTagsCommand$2({
36490
37013
  ResourceId: trailArn,
36491
37014
  TagsList: tagsToAdd
36492
37015
  }));
@@ -37295,6 +37818,81 @@ var CodeCommitRepositoryProvider = class {
37295
37818
  }
37296
37819
  }
37297
37820
  /**
37821
+ * Read the currently-deployed properties for `cdkd drift`.
37822
+ *
37823
+ * Maps the CodeCommit read side back to the flat CFn inputs cdkd stores
37824
+ * in state:
37825
+ * - `repositoryName` -> `RepositoryName`
37826
+ * - `repositoryDescription` -> `RepositoryDescription` (placeholder `''`)
37827
+ * - `kmsKeyId` -> `KmsKeyId`
37828
+ * - `ListTagsForResource` -> `Tags` (CFn `[{Key, Value}]` list)
37829
+ *
37830
+ * Every user-controllable top-level key `update()` can mutate is emitted
37831
+ * ALWAYS — with a `?? ''` / `?? []` placeholder when AWS returns the field
37832
+ * as undefined / empty (docs/provider-development.md §3b). Omitting the key
37833
+ * on the empty path would let a resource deployed WITHOUT a description
37834
+ * never carry `RepositoryDescription` in `observedProperties`, making a
37835
+ * console-side ADD of a description invisible to drift forever. `Tags` are
37836
+ * returned in the CFn list shape and the comparator canonicalizes tag lists
37837
+ * order-independently (`drift-normalize.ts`), so a tag reorder never
37838
+ * surfaces as phantom drift. `aws:*` tags (CDK's `aws:cdk:path` etc.) are
37839
+ * dropped by `normalizeAwsTagsToCfn` so a CDK-deployed repository does not
37840
+ * report drift on the metadata tag cdkd never templated.
37841
+ *
37842
+ * Returns `undefined` when the repository no longer exists (or
37843
+ * `GetRepository` returns no metadata) so the caller reports it as
37844
+ * drift-unknown rather than throwing — mirrors the optional `import`
37845
+ * method's incremental opt-in shape. A repository deleted BETWEEN the
37846
+ * `GetRepository` and `ListTagsForResource` calls (a race with a
37847
+ * concurrent destroy) is handled the same way rather than aborting the
37848
+ * whole `cdkd drift` run.
37849
+ *
37850
+ * Caveat: `KmsKeyId` is returned as AWS resolves it — the full key ARN.
37851
+ * On the normal drift path the baseline is `observedProperties` (captured
37852
+ * via this same method at deploy time), so ARN == ARN and there is no
37853
+ * phantom drift; but on the `properties`-fallback path (older state with
37854
+ * no `observedProperties`) a template that set `KmsKeyId` as an alias or
37855
+ * bare key id would phantom-drift against the returned ARN. This is a
37856
+ * general fallback-path limitation shared with other providers.
37857
+ */
37858
+ async readCurrentState(physicalId, _logicalId, _resourceType) {
37859
+ let metadata;
37860
+ try {
37861
+ metadata = await this.getRepositoryMetadata(physicalId);
37862
+ } catch (err) {
37863
+ if (err instanceof RepositoryDoesNotExistException) return void 0;
37864
+ throw err;
37865
+ }
37866
+ if (!metadata) return void 0;
37867
+ let tags = [];
37868
+ if (metadata.Arn) try {
37869
+ tags = normalizeAwsTagsToCfn((await this.getClient().send(new ListTagsForResourceCommand$19({ resourceArn: metadata.Arn }))).tags);
37870
+ } catch (err) {
37871
+ if (err instanceof RepositoryDoesNotExistException) return void 0;
37872
+ throw err;
37873
+ }
37874
+ return {
37875
+ RepositoryName: metadata.repositoryName ?? "",
37876
+ RepositoryDescription: metadata.repositoryDescription ?? "",
37877
+ KmsKeyId: metadata.kmsKeyId ?? "",
37878
+ Tags: tags
37879
+ };
37880
+ }
37881
+ /**
37882
+ * State property paths this provider cannot read back from AWS, skipped by
37883
+ * the drift comparator to avoid a guaranteed false positive.
37884
+ *
37885
+ * `Code` (create-only S3-zip seed content unpacked into an initial commit)
37886
+ * and `Triggers` (repository trigger management, not wired) are both
37887
+ * `unhandledByDesign` — normally rejected pre-flight, but a state written
37888
+ * under `--allow-unsupported-properties` could still carry them, and neither
37889
+ * is read back by `readCurrentState`, so exclude both defensively to avoid a
37890
+ * guaranteed false-positive drift on every run.
37891
+ */
37892
+ getDriftUnknownPaths(_resourceType) {
37893
+ return ["Code", "Triggers"];
37894
+ }
37895
+ /**
37298
37896
  * Adopt an existing CodeCommit repository into cdkd state.
37299
37897
  *
37300
37898
  * Lookup order:
@@ -40387,6 +40985,7 @@ function registerAllProviders(registry) {
40387
40985
  registry.register("AWS::EFS::MountTarget", efsProvider);
40388
40986
  registry.register("AWS::EFS::AccessPoint", efsProvider);
40389
40987
  registry.register("AWS::FSx::FileSystem", new FSxFileSystemProvider());
40988
+ registry.register("AWS::EMR::Cluster", new EMRClusterProvider());
40390
40989
  registry.register("AWS::KinesisFirehose::DeliveryStream", new FirehoseProvider());
40391
40990
  registry.register("AWS::CloudTrail::Trail", new CloudTrailProvider());
40392
40991
  registry.register("AWS::CodeBuild::Project", new CodeBuildProvider());
@@ -59665,7 +60264,7 @@ function reorderArgs(argv) {
59665
60264
  async function main() {
59666
60265
  installPipeCloseHandler();
59667
60266
  const program = new Command();
59668
- program.name("cdkd").description("CDK Direct - Deploy AWS CDK apps directly via SDK/Cloud Control API").version("0.244.0");
60267
+ program.name("cdkd").description("CDK Direct - Deploy AWS CDK apps directly via SDK/Cloud Control API").version("0.246.0");
59669
60268
  program.addCommand(createBootstrapCommand());
59670
60269
  program.addCommand(createSynthCommand());
59671
60270
  program.addCommand(createListCommand());