@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-
|
|
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$
|
|
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.
|
|
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$
|
|
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$
|
|
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.
|
|
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());
|