@fjall/components-infrastructure 0.95.0 → 0.99.1
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/lib/app.d.ts +90 -107
- package/dist/lib/app.js +149 -139
- package/dist/lib/config/aws/__t17fixture.d.ts +1 -0
- package/dist/lib/config/aws/__t17fixture.js +3 -0
- package/dist/lib/config/aws/__t17fixtureType.d.ts +2 -0
- package/dist/lib/config/aws/__t17fixtureType.js +1 -0
- package/dist/lib/config/aws/alarmTopic.js +8 -4
- package/dist/lib/config/aws/cloudTrail.js +1 -1
- package/dist/lib/config/aws/disasterRecovery.js +11 -16
- package/dist/lib/config/aws/ecrDefaultImage.d.ts +0 -1
- package/dist/lib/config/aws/ecrDefaultImage.js +13 -23
- package/dist/lib/config/aws/identityCenter.d.ts +10 -3
- package/dist/lib/config/aws/identityCenter.js +101 -37
- package/dist/lib/config/aws/identityCenterGroupMembership.js +8 -2
- package/dist/lib/config/aws/identityCenterMembership.d.ts +11 -0
- package/dist/lib/config/aws/identityCenterMembership.js +61 -0
- package/dist/lib/config/aws/index.d.ts +1 -1
- package/dist/lib/config/aws/index.js +1 -1
- package/dist/lib/config/aws/ipam.js +6 -11
- package/dist/lib/config/aws/oidcConnector.js +5 -1
- package/dist/lib/config/aws/scpPreset.js +4 -1
- package/dist/lib/patterns/aws/_eslint_test_tmp/leak.d.ts +1 -0
- package/dist/lib/patterns/aws/_eslint_test_tmp/leak.js +4 -0
- package/dist/lib/patterns/aws/account.js +7 -8
- package/dist/lib/patterns/aws/apexDomainPattern.js +10 -10
- package/dist/lib/patterns/aws/bastionFactory.d.ts +10 -0
- package/dist/lib/patterns/aws/bastionFactory.js +29 -0
- package/dist/lib/patterns/aws/buildkite.d.ts +2 -2
- package/dist/lib/patterns/aws/buildkite.js +51 -97
- package/dist/lib/patterns/aws/cdn.js +1 -1
- package/dist/lib/patterns/aws/clickhouseDatabase.d.ts +172 -0
- package/dist/lib/patterns/aws/clickhouseDatabase.js +600 -0
- package/dist/lib/patterns/aws/compute.d.ts +4 -6
- package/dist/lib/patterns/aws/compute.js +7 -13
- package/dist/lib/patterns/aws/computeEcs.d.ts +95 -396
- package/dist/lib/patterns/aws/computeEcs.js +880 -46
- package/dist/lib/patterns/aws/computeEcsTypes.d.ts +889 -0
- package/dist/lib/patterns/aws/computeEcsTypes.js +12 -0
- package/dist/lib/patterns/aws/computeLambda.d.ts +0 -5
- package/dist/lib/patterns/aws/computeLambda.js +1 -2
- package/dist/lib/patterns/aws/database.d.ts +50 -8
- package/dist/lib/patterns/aws/database.js +183 -27
- package/dist/lib/patterns/aws/domain.js +8 -7
- package/dist/lib/patterns/aws/index.d.ts +3 -0
- package/dist/lib/patterns/aws/index.js +3 -0
- package/dist/lib/patterns/aws/interfaces/compute.d.ts +13 -1
- package/dist/lib/patterns/aws/interfaces/connector.d.ts +1 -1
- package/dist/lib/patterns/aws/interfaces/connector.js +1 -1
- package/dist/lib/patterns/aws/interfaces/database.d.ts +187 -8
- package/dist/lib/patterns/aws/interfaces/database.js +17 -3
- package/dist/lib/patterns/aws/interfaces/index.d.ts +4 -2
- package/dist/lib/patterns/aws/interfaces/index.js +4 -2
- package/dist/lib/patterns/aws/interfaces/messaging.d.ts +7 -0
- package/dist/lib/patterns/aws/interfaces/migrationContributor.d.ts +47 -0
- package/dist/lib/patterns/aws/interfaces/migrationContributor.js +9 -0
- package/dist/lib/patterns/aws/interfaces/vpcPeer.d.ts +7 -0
- package/dist/lib/patterns/aws/interfaces/vpcPeer.js +1 -0
- package/dist/lib/patterns/aws/messaging.d.ts +66 -10
- package/dist/lib/patterns/aws/messaging.js +115 -20
- package/dist/lib/patterns/aws/network.js +16 -7
- package/dist/lib/patterns/aws/organisation.d.ts +4 -0
- package/dist/lib/patterns/aws/organisation.js +24 -5
- package/dist/lib/patterns/aws/storage.d.ts +1 -2
- package/dist/lib/patterns/aws/storage.js +3 -2
- package/dist/lib/patterns/aws/vpcPeer.d.ts +34 -0
- package/dist/lib/patterns/aws/vpcPeer.js +38 -0
- package/dist/lib/patterns/aws/vpcPeerAccepter.d.ts +29 -0
- package/dist/lib/patterns/aws/vpcPeerAccepter.js +196 -0
- package/dist/lib/resources/aws/analytics/clickhouse.js +25 -7
- package/dist/lib/resources/aws/analytics/clickhouseAlarms.d.ts +49 -0
- package/dist/lib/resources/aws/analytics/clickhouseAlarms.js +140 -0
- package/dist/lib/resources/aws/analytics/clickhouseConstants.d.ts +4 -4
- package/dist/lib/resources/aws/analytics/clickhouseConstants.js +6 -4
- package/dist/lib/resources/aws/analytics/clickhouseTypes.d.ts +12 -0
- package/dist/lib/resources/aws/analytics/clickhouseUserData.d.ts +1 -0
- package/dist/lib/resources/aws/analytics/clickhouseUserData.js +56 -5
- package/dist/lib/resources/aws/analytics/index.d.ts +2 -0
- package/dist/lib/resources/aws/analytics/index.js +1 -0
- package/dist/lib/resources/aws/base/awsStack.js +4 -2
- package/dist/lib/resources/aws/compute/__tmp__/regression-shape.d.ts +2 -0
- package/dist/lib/resources/aws/compute/__tmp__/regression-shape.js +11 -0
- package/dist/lib/resources/aws/compute/asgInlineLifecycleHook.d.ts +52 -0
- package/dist/lib/resources/aws/compute/asgInlineLifecycleHook.js +60 -0
- package/dist/lib/resources/aws/compute/blockDeviceVolume.d.ts +8 -0
- package/dist/lib/resources/aws/compute/blockDeviceVolume.js +10 -0
- package/dist/lib/resources/aws/compute/ec2.d.ts +132 -12
- package/dist/lib/resources/aws/compute/ec2.js +163 -23
- package/dist/lib/resources/aws/compute/ec2GracefulTerminationHandler.d.ts +41 -0
- package/dist/lib/resources/aws/compute/ec2GracefulTerminationHandler.js +194 -0
- package/dist/lib/resources/aws/compute/ec2GracefulTerminationLambda.source.cjs +458 -0
- package/dist/lib/resources/aws/compute/ecs.d.ts +27 -1
- package/dist/lib/resources/aws/compute/ecs.js +42 -2
- package/dist/lib/resources/aws/compute/ecsConstants.d.ts +9 -0
- package/dist/lib/resources/aws/compute/ecsConstants.js +16 -0
- package/dist/lib/resources/aws/compute/ecsImages.js +32 -20
- package/dist/lib/resources/aws/compute/ecsLifecycleHookMigration.d.ts +96 -0
- package/dist/lib/resources/aws/compute/ecsLifecycleHookMigration.js +113 -0
- package/dist/lib/resources/aws/compute/ecsNetworking.d.ts +2 -1
- package/dist/lib/resources/aws/compute/ecsNetworking.js +18 -6
- package/dist/lib/resources/aws/compute/ecsRemoteConnections.d.ts +38 -0
- package/dist/lib/resources/aws/compute/ecsRemoteConnections.js +80 -0
- package/dist/lib/resources/aws/compute/ecsServiceFactory.d.ts +13 -4
- package/dist/lib/resources/aws/compute/ecsServiceFactory.js +155 -33
- package/dist/lib/resources/aws/compute/ecsTaskDefinition.d.ts +31 -1
- package/dist/lib/resources/aws/compute/ecsTaskDefinition.js +110 -6
- package/dist/lib/resources/aws/compute/ecsTypes.d.ts +180 -13
- package/dist/lib/resources/aws/compute/ecsValidation.d.ts +9 -0
- package/dist/lib/resources/aws/compute/ecsValidation.js +63 -0
- package/dist/lib/resources/aws/compute/index.d.ts +2 -0
- package/dist/lib/resources/aws/compute/index.js +2 -0
- package/dist/lib/resources/aws/compute/lambda.d.ts +7 -13
- package/dist/lib/resources/aws/compute/lambda.js +30 -38
- package/dist/lib/resources/aws/compute/lifecycleHookLambda.source.cjs +192 -0
- package/dist/lib/resources/aws/compute/persistentDataVolume.d.ts +104 -0
- package/dist/lib/resources/aws/compute/persistentDataVolume.js +245 -0
- package/dist/lib/resources/aws/compute/persistentDataVolumeLambda.source.cjs +398 -0
- package/dist/lib/resources/aws/compute/samApplication.d.ts +15 -0
- package/dist/lib/resources/aws/compute/samApplication.js +27 -0
- package/dist/lib/resources/aws/database/clickhouseConstants.d.ts +159 -0
- package/dist/lib/resources/aws/database/clickhouseConstants.js +181 -0
- package/dist/lib/resources/aws/database/clickhouseSchemas.d.ts +71 -0
- package/dist/lib/resources/aws/database/clickhouseSchemas.js +157 -0
- package/dist/lib/resources/aws/database/clickhouseSecurityGroup.d.ts +14 -0
- package/dist/lib/resources/aws/database/clickhouseSecurityGroup.js +23 -0
- package/dist/lib/resources/aws/database/clickhouseUserData.d.ts +69 -0
- package/dist/lib/resources/aws/database/clickhouseUserData.js +371 -0
- package/dist/lib/resources/aws/database/clickhouseXmlRenderer.d.ts +56 -0
- package/dist/lib/resources/aws/database/clickhouseXmlRenderer.js +112 -0
- package/dist/lib/resources/aws/database/rdsAurora.d.ts +8 -1
- package/dist/lib/resources/aws/database/rdsAurora.js +42 -32
- package/dist/lib/resources/aws/database/rdsAuroraGlobal.d.ts +15 -2
- package/dist/lib/resources/aws/database/rdsAuroraGlobal.js +39 -43
- package/dist/lib/resources/aws/database/rdsDefaults.d.ts +6 -0
- package/dist/lib/resources/aws/database/rdsDefaults.js +7 -1
- package/dist/lib/resources/aws/database/rdsHelpers.d.ts +3 -3
- package/dist/lib/resources/aws/database/rdsHelpers.js +1 -0
- package/dist/lib/resources/aws/database/rdsInstance.d.ts +8 -1
- package/dist/lib/resources/aws/database/rdsInstance.js +51 -34
- package/dist/lib/resources/aws/database/rdsProxyOutput.d.ts +1 -1
- package/dist/lib/resources/aws/database/rdsProxyOutput.js +1 -1
- package/dist/lib/resources/aws/iam/delegationRole.js +12 -5
- package/dist/lib/resources/aws/iam/identityCenter/groupMembership.d.ts +9 -0
- package/dist/lib/resources/aws/iam/identityCenter/groupMembership.js +12 -0
- package/dist/lib/resources/aws/iam/identityCenter/index.d.ts +1 -0
- package/dist/lib/resources/aws/iam/identityCenter/index.js +1 -0
- package/dist/lib/resources/aws/iam/identityCenter/permissionSet.d.ts +1 -0
- package/dist/lib/resources/aws/iam/identityCenter/permissionSet.js +1 -0
- package/dist/lib/resources/aws/logging/logGroup.d.ts +0 -8
- package/dist/lib/resources/aws/logging/logGroup.js +0 -11
- package/dist/lib/resources/aws/messaging/defaultEventBus.d.ts +7 -0
- package/dist/lib/resources/aws/messaging/defaultEventBus.js +21 -0
- package/dist/lib/resources/aws/messaging/eventBridgeRule.d.ts +96 -0
- package/dist/lib/resources/aws/messaging/eventBridgeRule.js +110 -0
- package/dist/lib/resources/aws/messaging/eventTargets.d.ts +84 -0
- package/dist/lib/resources/aws/messaging/eventTargets.js +152 -0
- package/dist/lib/resources/aws/messaging/eventbridge.d.ts +25 -2
- package/dist/lib/resources/aws/messaging/eventbridge.js +22 -10
- package/dist/lib/resources/aws/messaging/index.d.ts +5 -0
- package/dist/lib/resources/aws/messaging/index.js +2 -0
- package/dist/lib/resources/aws/messaging/schedule.d.ts +118 -0
- package/dist/lib/resources/aws/messaging/schedule.js +64 -0
- package/dist/lib/resources/aws/messaging/sns.d.ts +2 -1
- package/dist/lib/resources/aws/messaging/sqs.d.ts +2 -1
- package/dist/lib/resources/aws/messaging/subscription.d.ts +112 -0
- package/dist/lib/resources/aws/messaging/subscription.js +67 -0
- package/dist/lib/resources/aws/messaging/utils.d.ts +6 -0
- package/dist/lib/resources/aws/messaging/utils.js +10 -0
- package/dist/lib/resources/aws/monitoring/clickhouseAlarms.d.ts +60 -0
- package/dist/lib/resources/aws/monitoring/clickhouseAlarms.js +139 -0
- package/dist/lib/resources/aws/monitoring/index.d.ts +2 -0
- package/dist/lib/resources/aws/monitoring/index.js +2 -0
- package/dist/lib/resources/aws/monitoring/scheduleAlarms.d.ts +47 -0
- package/dist/lib/resources/aws/monitoring/scheduleAlarms.js +106 -0
- package/dist/lib/resources/aws/networking/crossAccountDelegationRecord.js +6 -3
- package/dist/lib/resources/aws/networking/crossAccountReturnRoutes.d.ts +40 -0
- package/dist/lib/resources/aws/networking/crossAccountReturnRoutes.js +158 -0
- package/dist/lib/resources/aws/networking/dnsRecord/dnsRecordBase.js +7 -4
- package/dist/lib/resources/aws/networking/domainCertificate.d.ts +2 -2
- package/dist/lib/resources/aws/networking/domainCertificate.js +6 -3
- package/dist/lib/resources/aws/networking/hostedZone.js +6 -4
- package/dist/lib/resources/aws/networking/index.d.ts +3 -0
- package/dist/lib/resources/aws/networking/index.js +3 -0
- package/dist/lib/resources/aws/networking/serviceDiscovery.d.ts +96 -0
- package/dist/lib/resources/aws/networking/serviceDiscovery.js +96 -0
- package/dist/lib/resources/aws/networking/vpc.d.ts +4 -1
- package/dist/lib/resources/aws/networking/vpc.js +10 -3
- package/dist/lib/resources/aws/networking/vpcPeeringAccepterRole.d.ts +18 -0
- package/dist/lib/resources/aws/networking/vpcPeeringAccepterRole.js +61 -0
- package/dist/lib/resources/aws/networking/vpcPeeringConnection.d.ts +49 -0
- package/dist/lib/resources/aws/networking/vpcPeeringConnection.js +106 -0
- package/dist/lib/resources/aws/organisation/costAllocationTagActivator.d.ts +16 -5
- package/dist/lib/resources/aws/organisation/costAllocationTagActivator.js +17 -3
- package/dist/lib/resources/aws/organisation/index.d.ts +1 -1
- package/dist/lib/resources/aws/organisation/organisationPolicy.d.ts +2 -0
- package/dist/lib/resources/aws/organisation/organisationPolicy.js +3 -2
- package/dist/lib/resources/aws/secrets/secret.d.ts +7 -0
- package/dist/lib/resources/aws/secrets/secret.js +4 -3
- package/dist/lib/resources/aws/storage/bucketDeployment.d.ts +16 -0
- package/dist/lib/resources/aws/storage/bucketDeployment.js +17 -0
- package/dist/lib/resources/aws/storage/ecr.js +5 -5
- package/dist/lib/resources/aws/storage/index.d.ts +1 -0
- package/dist/lib/resources/aws/storage/index.js +1 -0
- package/dist/lib/resources/aws/storage/s3.js +10 -3
- package/dist/lib/resources/aws/utilities/customResource.js +18 -9
- package/dist/lib/synth_dump.d.ts +1 -0
- package/dist/lib/synth_dump.js +42 -0
- package/dist/lib/utils/bastionFactory.d.ts +10 -0
- package/dist/lib/utils/bastionFactory.js +29 -0
- package/dist/lib/utils/capitaliseString.d.ts +1 -1
- package/dist/lib/utils/capitaliseString.js +1 -1
- package/dist/lib/utils/cdkContext.d.ts +10 -0
- package/dist/lib/utils/cdkContext.js +13 -0
- package/dist/lib/utils/connections.d.ts +7 -1
- package/dist/lib/utils/connections.js +21 -0
- package/dist/lib/utils/connector.d.ts +30 -2
- package/dist/lib/utils/connector.js +6 -1
- package/dist/lib/utils/costAllocationTags.d.ts +15 -0
- package/dist/lib/utils/costAllocationTags.js +16 -0
- package/dist/lib/utils/databaseTypes.d.ts +14 -0
- package/dist/lib/utils/getConfig.d.ts +2 -0
- package/dist/lib/utils/getConfig.js +2 -0
- package/dist/lib/utils/index.d.ts +4 -0
- package/dist/lib/utils/index.js +4 -0
- package/dist/lib/utils/manifestWriter.d.ts +6 -89
- package/dist/lib/utils/manifestWriter.js +36 -23
- package/dist/lib/utils/migrationVersionResolvers.d.ts +2 -0
- package/dist/lib/utils/migrationVersionResolvers.js +2 -0
- package/dist/lib/utils/orgConfigParser.js +2 -1
- package/dist/lib/utils/resolveAlertsTopic.d.ts +14 -0
- package/dist/lib/utils/resolveAlertsTopic.js +30 -0
- package/dist/lib/utils/validationLogger.js +6 -3
- package/dist/lib/utils/vpcPeerInterface.d.ts +22 -0
- package/dist/lib/utils/vpcPeerInterface.js +1 -0
- package/package.json +22 -18
|
@@ -1,2 +1,4 @@
|
|
|
1
1
|
export { default as ClickHouse } from "./clickhouse.js";
|
|
2
2
|
export type { ClickHouseProps, ClickHouseR2Config, ClickHouseOutputs } from "./clickhouseTypes.js";
|
|
3
|
+
export { createClickHouseAlarms } from "./clickhouseAlarms.js";
|
|
4
|
+
export type { ClickHouseAlarmThresholds, ClickHouseAlarmsProps } from "./clickhouseAlarms.js";
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Stack } from "aws-cdk-lib";
|
|
1
|
+
import { Annotations, Stack } from "aws-cdk-lib";
|
|
2
2
|
import { Port } from "aws-cdk-lib/aws-ec2";
|
|
3
3
|
import { Construct } from "constructs";
|
|
4
4
|
import App from "../../../app.js";
|
|
@@ -27,7 +27,9 @@ export class AwsStack {
|
|
|
27
27
|
}
|
|
28
28
|
}
|
|
29
29
|
getCdkStack(id, props) {
|
|
30
|
-
|
|
30
|
+
const stack = new Stack(App.getInstance(), id, this.getStackProps(props));
|
|
31
|
+
Annotations.of(stack).acknowledgeWarning("@aws-cdk/aws-ec2:ipv4IgnoreEgressRule");
|
|
32
|
+
return stack;
|
|
31
33
|
}
|
|
32
34
|
getStackProps(props) {
|
|
33
35
|
// If no explicit props are provided, fall back to the account/region that
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { DefaultResult, LifecycleTransition } from "aws-cdk-lib/aws-autoscaling";
|
|
2
|
+
import { QueueHook } from "aws-cdk-lib/aws-autoscaling-hooktargets";
|
|
3
|
+
import { Duration } from "aws-cdk-lib";
|
|
4
|
+
export function regression(asg, queue, id) {
|
|
5
|
+
asg.addLifecycleHook(`${id}LaunchingHook`, {
|
|
6
|
+
lifecycleTransition: LifecycleTransition.INSTANCE_LAUNCHING,
|
|
7
|
+
defaultResult: DefaultResult.ABANDON,
|
|
8
|
+
heartbeatTimeout: Duration.seconds(300),
|
|
9
|
+
notificationTarget: new QueueHook(queue)
|
|
10
|
+
});
|
|
11
|
+
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { type AutoScalingGroup, type DefaultResult, type LifecycleTransition } from "aws-cdk-lib/aws-autoscaling";
|
|
2
|
+
import { type Construct } from "constructs";
|
|
3
|
+
export interface InlineAsgLifecycleHookProps {
|
|
4
|
+
/** ASG to attach the hook to. */
|
|
5
|
+
autoScalingGroup: AutoScalingGroup;
|
|
6
|
+
/** Lifecycle hook name — must be unique within the ASG. */
|
|
7
|
+
hookName: string;
|
|
8
|
+
/** EC2_INSTANCE_LAUNCHING or EC2_INSTANCE_TERMINATING. */
|
|
9
|
+
lifecycleTransition: LifecycleTransition;
|
|
10
|
+
/** Action when heartbeat elapses. */
|
|
11
|
+
defaultResult: DefaultResult;
|
|
12
|
+
/** Heartbeat window before defaultResult fires, in seconds. */
|
|
13
|
+
heartbeatTimeoutSeconds: number;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Atomically attach an ASG lifecycle hook by appending a
|
|
17
|
+
* `LifecycleHookSpecification` to the ASG's `LifecycleHookSpecificationList`
|
|
18
|
+
* (CFN property on `AWS::AutoScaling::AutoScalingGroup`) instead of emitting
|
|
19
|
+
* a standalone `AWS::AutoScaling::LifecycleHook` resource.
|
|
20
|
+
*
|
|
21
|
+
* Standalone `AWS::AutoScaling::LifecycleHook` resources are created AFTER
|
|
22
|
+
* the ASG. CFN starts the ASG's desiredCapacity ramp as part of ASG creation,
|
|
23
|
+
* not as a separate step, so on a fresh stack the first instance launches
|
|
24
|
+
* BEFORE the hook is attached and the hook fires zero notifications for that
|
|
25
|
+
* instance. `LifecycleHookSpecificationList` is part of the ASG's own CFN
|
|
26
|
+
* payload — the ASG is never in a state where it has instances but no hooks.
|
|
27
|
+
*
|
|
28
|
+
* No `NotificationTargetARN` / `RoleARN` is set. AWS rejects ASG creation when
|
|
29
|
+
* the inline `LifecycleHookSpecificationList` contains two entries with
|
|
30
|
+
* different `NotificationTargetARN` values:
|
|
31
|
+
*
|
|
32
|
+
* "NotificationTargetARN should be the same for all Lifecycle Hooks"
|
|
33
|
+
*
|
|
34
|
+
* The standalone-hook form permits per-hook targets, but inline does not. Two
|
|
35
|
+
* Fjall consumers (`PersistentDataVolume` LAUNCHING + `Ec2GracefulTerminationHandler`
|
|
36
|
+
* TERMINATING) each own their own SQS queue, so a shared target is impossible.
|
|
37
|
+
*
|
|
38
|
+
* Routing is therefore delegated to EventBridge. ASG natively emits
|
|
39
|
+
* `EC2 Instance-launch Lifecycle Action` / `EC2 Instance-terminate Lifecycle Action`
|
|
40
|
+
* events on the account+region default bus for every lifecycle hook regardless
|
|
41
|
+
* of whether a notification target is configured. Each consumer attaches a
|
|
42
|
+
* `Subscription` (from `lib/resources/aws/messaging/subscription.ts`) whose
|
|
43
|
+
* event pattern discriminates by `AutoScalingGroupName` + `LifecycleHookName`,
|
|
44
|
+
* targeting the consumer's own SQS queue. The Lambda sees the EventBridge event
|
|
45
|
+
* envelope and reads `detail.LifecycleActionToken` etc. from the unwrapped
|
|
46
|
+
* detail.
|
|
47
|
+
*
|
|
48
|
+
* Multiple consumers may call this helper against the same ASG — the existing
|
|
49
|
+
* spec list is read, the new entry appended, and the merged array assigned
|
|
50
|
+
* back. Synth is sequential per scope, so the merge is race-free.
|
|
51
|
+
*/
|
|
52
|
+
export declare function attachInlineAsgLifecycleHook(_scope: Construct, _id: string, props: InlineAsgLifecycleHookProps): void;
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Atomically attach an ASG lifecycle hook by appending a
|
|
3
|
+
* `LifecycleHookSpecification` to the ASG's `LifecycleHookSpecificationList`
|
|
4
|
+
* (CFN property on `AWS::AutoScaling::AutoScalingGroup`) instead of emitting
|
|
5
|
+
* a standalone `AWS::AutoScaling::LifecycleHook` resource.
|
|
6
|
+
*
|
|
7
|
+
* Standalone `AWS::AutoScaling::LifecycleHook` resources are created AFTER
|
|
8
|
+
* the ASG. CFN starts the ASG's desiredCapacity ramp as part of ASG creation,
|
|
9
|
+
* not as a separate step, so on a fresh stack the first instance launches
|
|
10
|
+
* BEFORE the hook is attached and the hook fires zero notifications for that
|
|
11
|
+
* instance. `LifecycleHookSpecificationList` is part of the ASG's own CFN
|
|
12
|
+
* payload — the ASG is never in a state where it has instances but no hooks.
|
|
13
|
+
*
|
|
14
|
+
* No `NotificationTargetARN` / `RoleARN` is set. AWS rejects ASG creation when
|
|
15
|
+
* the inline `LifecycleHookSpecificationList` contains two entries with
|
|
16
|
+
* different `NotificationTargetARN` values:
|
|
17
|
+
*
|
|
18
|
+
* "NotificationTargetARN should be the same for all Lifecycle Hooks"
|
|
19
|
+
*
|
|
20
|
+
* The standalone-hook form permits per-hook targets, but inline does not. Two
|
|
21
|
+
* Fjall consumers (`PersistentDataVolume` LAUNCHING + `Ec2GracefulTerminationHandler`
|
|
22
|
+
* TERMINATING) each own their own SQS queue, so a shared target is impossible.
|
|
23
|
+
*
|
|
24
|
+
* Routing is therefore delegated to EventBridge. ASG natively emits
|
|
25
|
+
* `EC2 Instance-launch Lifecycle Action` / `EC2 Instance-terminate Lifecycle Action`
|
|
26
|
+
* events on the account+region default bus for every lifecycle hook regardless
|
|
27
|
+
* of whether a notification target is configured. Each consumer attaches a
|
|
28
|
+
* `Subscription` (from `lib/resources/aws/messaging/subscription.ts`) whose
|
|
29
|
+
* event pattern discriminates by `AutoScalingGroupName` + `LifecycleHookName`,
|
|
30
|
+
* targeting the consumer's own SQS queue. The Lambda sees the EventBridge event
|
|
31
|
+
* envelope and reads `detail.LifecycleActionToken` etc. from the unwrapped
|
|
32
|
+
* detail.
|
|
33
|
+
*
|
|
34
|
+
* Multiple consumers may call this helper against the same ASG — the existing
|
|
35
|
+
* spec list is read, the new entry appended, and the merged array assigned
|
|
36
|
+
* back. Synth is sequential per scope, so the merge is race-free.
|
|
37
|
+
*/
|
|
38
|
+
export function attachInlineAsgLifecycleHook(_scope, _id, props) {
|
|
39
|
+
const cfnAsg = props.autoScalingGroup.node
|
|
40
|
+
.defaultChild;
|
|
41
|
+
const existing = readLifecycleHookSpecList(cfnAsg);
|
|
42
|
+
cfnAsg.lifecycleHookSpecificationList = [
|
|
43
|
+
...existing,
|
|
44
|
+
{
|
|
45
|
+
lifecycleHookName: props.hookName,
|
|
46
|
+
lifecycleTransition: props.lifecycleTransition,
|
|
47
|
+
defaultResult: props.defaultResult,
|
|
48
|
+
heartbeatTimeout: props.heartbeatTimeoutSeconds
|
|
49
|
+
}
|
|
50
|
+
];
|
|
51
|
+
}
|
|
52
|
+
function readLifecycleHookSpecList(cfnAsg) {
|
|
53
|
+
const current = cfnAsg.lifecycleHookSpecificationList;
|
|
54
|
+
if (current === undefined)
|
|
55
|
+
return [];
|
|
56
|
+
if (Array.isArray(current)) {
|
|
57
|
+
return current;
|
|
58
|
+
}
|
|
59
|
+
throw new Error("Cannot append to lifecycleHookSpecificationList: existing value is an IResolvable — refactor the override to call attachInlineAsgLifecycleHook");
|
|
60
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { BlockDeviceVolume, EbsDeviceVolumeType } from "aws-cdk-lib/aws-ec2";
|
|
2
|
+
export interface SafeEbsOptions {
|
|
3
|
+
volumeType?: EbsDeviceVolumeType;
|
|
4
|
+
iops?: number;
|
|
5
|
+
throughput?: number;
|
|
6
|
+
deleteOnTermination?: boolean;
|
|
7
|
+
}
|
|
8
|
+
export declare function safeEbs(sizeGiB: number, opts?: SafeEbsOptions): BlockDeviceVolume;
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { BlockDeviceVolume, EbsDeviceVolumeType } from "aws-cdk-lib/aws-ec2";
|
|
2
|
+
export function safeEbs(sizeGiB, opts = {}) {
|
|
3
|
+
return BlockDeviceVolume.ebs(sizeGiB, {
|
|
4
|
+
encrypted: true,
|
|
5
|
+
volumeType: opts.volumeType ?? EbsDeviceVolumeType.GP3,
|
|
6
|
+
iops: opts.iops,
|
|
7
|
+
throughput: opts.throughput,
|
|
8
|
+
deleteOnTermination: opts.deleteOnTermination
|
|
9
|
+
});
|
|
10
|
+
}
|
|
@@ -1,9 +1,39 @@
|
|
|
1
|
-
import { type BlockDevice, type IMachineImage, type IVpc, type UserData,
|
|
1
|
+
import { type BlockDevice, type IMachineImage, type IVpc, type UserData, type ISecurityGroup, type IConnectable, Connections, type SubnetConfiguration, type SubnetSelection } from "aws-cdk-lib/aws-ec2";
|
|
2
2
|
import { Construct } from "constructs";
|
|
3
|
-
import { Stack, type StackProps } from "aws-cdk-lib";
|
|
3
|
+
import { Duration, Stack, type StackProps } from "aws-cdk-lib";
|
|
4
4
|
import { type Role } from "aws-cdk-lib/aws-iam";
|
|
5
|
-
import { AutoScalingGroup } from "aws-cdk-lib/aws-autoscaling";
|
|
6
|
-
|
|
5
|
+
import { AutoScalingGroup, Monitoring, type WarmPoolOptions } from "aws-cdk-lib/aws-autoscaling";
|
|
6
|
+
import { type PersistentDataVolumeProps } from "./persistentDataVolume.js";
|
|
7
|
+
export type Ec2InstancePersistentDataVolumeConfig = Omit<PersistentDataVolumeProps, "autoScalingGroup">;
|
|
8
|
+
/**
|
|
9
|
+
* Caller-supplied ASG `UpdatePolicy` resolution. Default (`undefined`) →
|
|
10
|
+
* `rollingUpdate({ minInstancesInService: 0, maxBatchSize: 1, pauseTime:
|
|
11
|
+
* Duration.minutes(5) })`. The rolling shape propagates userdata mutations
|
|
12
|
+
* to running instances on every `LaunchTemplateVersion` change; data
|
|
13
|
+
* continuity for stateful patterns is preserved by `persistentDataVolume`.
|
|
14
|
+
*
|
|
15
|
+
* Variants:
|
|
16
|
+
* - `rollingUpdate` — single-instance roll; `pauseTime` overridable.
|
|
17
|
+
* `minInstancesInService` and `maxBatchSize` are fixed at `0` and `1`.
|
|
18
|
+
* - `replacingUpdate` — explicit opt-in to the legacy full-replacement shape.
|
|
19
|
+
* No Fjall consumer uses this today; retained for future bare-EC2 patterns.
|
|
20
|
+
* - `none` — no caller-driven `UpdatePolicy` (omits both
|
|
21
|
+
* `AutoScalingRollingUpdate` and `AutoScalingReplacingUpdate`). CDK still
|
|
22
|
+
* auto-emits `AutoScalingScheduledAction.IgnoreUnmodifiedGroupSizeProperties`
|
|
23
|
+
* regardless. For callers owning rollout externally.
|
|
24
|
+
*
|
|
25
|
+
* See `aiDocs/troubleshooting/clickhouse-rolling-update-window.md` for the
|
|
26
|
+
* ClickHouse-specific downtime expectations.
|
|
27
|
+
*/
|
|
28
|
+
export type Ec2InstanceUpdatePolicyConfig = {
|
|
29
|
+
type: "rollingUpdate";
|
|
30
|
+
pauseTime?: Duration;
|
|
31
|
+
} | {
|
|
32
|
+
type: "replacingUpdate";
|
|
33
|
+
} | {
|
|
34
|
+
type: "none";
|
|
35
|
+
};
|
|
36
|
+
export interface Ec2InstanceProps extends StackProps {
|
|
7
37
|
spotCapacityPercentage?: number;
|
|
8
38
|
blockDevices?: BlockDevice[];
|
|
9
39
|
accountId?: string;
|
|
@@ -12,32 +42,122 @@ interface Ec2InstanceProps extends StackProps {
|
|
|
12
42
|
subnetConfiguration?: SubnetConfiguration[];
|
|
13
43
|
minCapacity?: number;
|
|
14
44
|
maxCapacity?: number;
|
|
45
|
+
/** CDK `AutoScalingGroupProps.desiredCapacity` — initial instance count. */
|
|
46
|
+
desiredCapacity?: number;
|
|
15
47
|
instanceType: string;
|
|
16
48
|
machineImage?: IMachineImage;
|
|
17
49
|
userData?: UserData;
|
|
18
50
|
role?: Role;
|
|
19
51
|
enableSSH?: boolean;
|
|
20
52
|
defaultPort?: number;
|
|
53
|
+
/**
|
|
54
|
+
* Caller-supplied EC2 instance monitoring resolution. Routes through the
|
|
55
|
+
* LaunchTemplate's `detailedMonitoring` field (the AWS-side source of truth
|
|
56
|
+
* for instances launched through a launch template — the ASG-level
|
|
57
|
+
* `instanceMonitoring` prop is silently ignored by CDK whenever a
|
|
58
|
+
* `launchTemplate` or `mixedInstancesPolicy` is set, which is always the
|
|
59
|
+
* case in this construct). Translation: `Monitoring.DETAILED` → `true`,
|
|
60
|
+
* `Monitoring.BASIC` → `false`. Absent → existing default of `true`
|
|
61
|
+
* (1-minute metrics) is preserved.
|
|
62
|
+
*/
|
|
63
|
+
instanceMonitoring?: Monitoring;
|
|
64
|
+
/**
|
|
65
|
+
* Externally-supplied security group. When provided, `Ec2Instance` does not
|
|
66
|
+
* create its own `AsgSecurityGroup`; the supplied SG is used by the launch
|
|
67
|
+
* template, exposed via `this.asgSecurityGroup`, and threaded into the
|
|
68
|
+
* `IConnectable` view. Callers retain full ownership.
|
|
69
|
+
*/
|
|
70
|
+
securityGroup?: ISecurityGroup;
|
|
71
|
+
/**
|
|
72
|
+
* CDK `AutoScalingGroupProps.vpcSubnets`. Overrides the default
|
|
73
|
+
* `enableSSH ? PUBLIC : resolvePrivateSubnetType(vpc)` inference. Use when
|
|
74
|
+
* the caller needs a precise subnet selection (e.g. AZ pinning).
|
|
75
|
+
*/
|
|
76
|
+
vpcSubnets?: SubnetSelection;
|
|
77
|
+
/** CDK `AutoScalingGroupProps.capacityRebalance`. Absent → CDK default. */
|
|
78
|
+
capacityRebalance?: boolean;
|
|
79
|
+
/**
|
|
80
|
+
* CDK `aws-cdk-lib/aws-autoscaling.WarmPoolOptions` verbatim — `minSize`,
|
|
81
|
+
* `maxGroupPreparedCapacity`, `poolState`, `reuseOnScaleIn`. When present,
|
|
82
|
+
* `asg.addWarmPool(warmPool)` is called once after ASG construction.
|
|
83
|
+
*/
|
|
84
|
+
warmPool?: WarmPoolOptions;
|
|
85
|
+
/**
|
|
86
|
+
* CDK `LaunchTemplateProps.associatePublicIpAddress`. When defined,
|
|
87
|
+
* overrides the `!!keyPair` auto-derivation; otherwise the existing
|
|
88
|
+
* keyPair-driven default applies.
|
|
89
|
+
*/
|
|
90
|
+
associatePublicIpAddress?: boolean;
|
|
91
|
+
/**
|
|
92
|
+
* ECS cluster ARN — when set, the graceful-termination Lambda also drains
|
|
93
|
+
* and deregisters the container instance before generic cleanup. Empty
|
|
94
|
+
* string is treated as unset (rejecting Pitfall 9 / env-var-truthy traps).
|
|
95
|
+
* Bare-EC2 consumers (bastion, Fivetran) leave this unset.
|
|
96
|
+
*/
|
|
97
|
+
ecsClusterArn?: string;
|
|
98
|
+
/**
|
|
99
|
+
* Pairs the ASG with a standalone EBS data volume that re-attaches across
|
|
100
|
+
* instance refreshes. When set, requires `vpcSubnets.availabilityZones` to
|
|
101
|
+
* be exactly one entry (matching `persistentDataVolume.availabilityZone`);
|
|
102
|
+
* the wrapper's volume is AZ-local and cannot follow a multi-AZ ASG.
|
|
103
|
+
* Forwards the wrapper's `ownerLogicalId` into the graceful-termination
|
|
104
|
+
* Lambda so the TERMINATING and LAUNCHING handlers locate the same volume.
|
|
105
|
+
*/
|
|
106
|
+
persistentDataVolume?: Ec2InstancePersistentDataVolumeConfig;
|
|
107
|
+
/**
|
|
108
|
+
* ASG `UpdatePolicy` resolution. Absent →
|
|
109
|
+
* `UpdatePolicy.rollingUpdate({ minInstancesInService: 0, maxBatchSize: 1,
|
|
110
|
+
* pauseTime: Duration.minutes(5) })`. Userdata mutations propagate to
|
|
111
|
+
* running instances via a single-batch rolling roll. See
|
|
112
|
+
* `Ec2InstanceUpdatePolicyConfig` for the variant menu and
|
|
113
|
+
* `aiDocs/troubleshooting/clickhouse-rolling-update-window.md` for the
|
|
114
|
+
* downtime-window runbook.
|
|
115
|
+
*/
|
|
116
|
+
updatePolicy?: Ec2InstanceUpdatePolicyConfig;
|
|
117
|
+
/**
|
|
118
|
+
* Tags applied to the underlying ASG with
|
|
119
|
+
* `applyToLaunchedInstances: true` so every launched EC2 instance carries
|
|
120
|
+
* the tags. Used for tag-based SSM `SendCommand` targeting
|
|
121
|
+
* (`Targets: [{ Key: "tag:<name>", Values: [<value>] }]`). Empty-string
|
|
122
|
+
* keys or values are rejected by `validateEc2InstanceProps`.
|
|
123
|
+
*/
|
|
124
|
+
tags?: Record<string, string>;
|
|
21
125
|
}
|
|
22
126
|
export declare class Ec2Instance extends Construct implements IConnectable {
|
|
23
127
|
private launchTemplate;
|
|
24
128
|
vpc: IVpc;
|
|
25
|
-
asgSecurityGroup:
|
|
129
|
+
asgSecurityGroup: ISecurityGroup;
|
|
26
130
|
private autoScalingGroup;
|
|
27
131
|
private keyPair;
|
|
28
|
-
|
|
132
|
+
private persistentDataVolume?;
|
|
133
|
+
readonly connections: Connections;
|
|
29
134
|
constructor(scope: Construct, id: string, props: Ec2InstanceProps);
|
|
30
|
-
addVpc
|
|
31
|
-
addKeyPair
|
|
32
|
-
addLaunchTemplate
|
|
33
|
-
addAutoScalingGroup
|
|
135
|
+
private addVpc;
|
|
136
|
+
private addKeyPair;
|
|
137
|
+
private addLaunchTemplate;
|
|
138
|
+
private addAutoScalingGroup;
|
|
139
|
+
/**
|
|
140
|
+
* Apply `props.tags` to the underlying ASG with
|
|
141
|
+
* `applyToLaunchedInstances: true` so the CFN ASG `Tags` array carries
|
|
142
|
+
* `{ Key, Value, PropagateAtLaunch: true }` for each entry. Enables
|
|
143
|
+
* tag-based SSM `SendCommand` targeting on the launched EC2 instances.
|
|
144
|
+
*/
|
|
145
|
+
private applyInstanceTags;
|
|
34
146
|
/**
|
|
35
147
|
* Get the Auto Scaling Group.
|
|
36
148
|
*/
|
|
37
149
|
getAutoScalingGroup(): AutoScalingGroup;
|
|
38
|
-
|
|
150
|
+
/**
|
|
151
|
+
* Wire an ASG `EC2_INSTANCE_TERMINATING` hook + Lambda so instances
|
|
152
|
+
* terminate cleanly during stack updates and stack deletes. Drain ownership
|
|
153
|
+
* lives at the EC2 layer; ECS-specific drain plugs in via `ecsClusterArn`
|
|
154
|
+
* so a single hook handles bare-EC2 (bastion, Fivetran) and ECS-wired
|
|
155
|
+
* ASGs (ClickHouse) without duplicate plumbing in the ECS layer.
|
|
156
|
+
*/
|
|
157
|
+
private addGracefulTerminationHandler;
|
|
158
|
+
private addPersistentDataVolume;
|
|
159
|
+
private suspendAutoScaling;
|
|
39
160
|
}
|
|
40
161
|
export declare class Ec2InstanceStack extends Stack {
|
|
41
162
|
constructor(scope: Construct, id: string, props: Ec2InstanceProps);
|
|
42
163
|
}
|
|
43
|
-
export {};
|
|
@@ -1,27 +1,75 @@
|
|
|
1
1
|
import { InstanceType, LaunchTemplate, SubnetType, MachineImage, LaunchTemplateHttpTokens, KeyPair, SecurityGroup, Peer, Port, Connections } from "aws-cdk-lib/aws-ec2";
|
|
2
2
|
import { Construct } from "constructs";
|
|
3
3
|
import { resolvePrivateSubnetType } from "../../../utils/vpcUtils.js";
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
4
|
+
import { safeEbs } from "./blockDeviceVolume.js";
|
|
5
|
+
import { Duration, Stack, Tags } from "aws-cdk-lib";
|
|
6
|
+
import { AutoScalingGroup, GroupMetrics, Monitoring, TerminationPolicy, UpdatePolicy, OnDemandAllocationStrategy, SpotAllocationStrategy } from "aws-cdk-lib/aws-autoscaling";
|
|
6
7
|
import { AwsCustomResource } from "../utilities/awsCustomResource.js";
|
|
7
8
|
import { PhysicalResourceId } from "aws-cdk-lib/custom-resources";
|
|
8
9
|
import { Vpc } from "../networking/vpc.js";
|
|
10
|
+
import { Ec2GracefulTerminationHandler } from "./ec2GracefulTerminationHandler.js";
|
|
11
|
+
import { PersistentDataVolume } from "./persistentDataVolume.js";
|
|
12
|
+
/**
|
|
13
|
+
* SOC2-uniform default root device when callers omit `blockDevices`.
|
|
14
|
+
* `/dev/xvda` is the AL2023 / EcsOptimizedImage root device name and matches
|
|
15
|
+
* every Linux AMI consumed by `Ec2Instance` today (ECS, Buildkite, ClickHouse).
|
|
16
|
+
* `encrypted: true` enforces EBS encryption at the wrapper layer rather than
|
|
17
|
+
* relying on the AWS account-level "encryption by default" setting — the
|
|
18
|
+
* propagation contract documented in
|
|
19
|
+
* `.claude/rules/generator-standards.md § "Wrapper Routing Discipline"`.
|
|
20
|
+
*/
|
|
21
|
+
const DEFAULT_ROOT_DEVICE_NAME = "/dev/xvda";
|
|
22
|
+
const DEFAULT_ROOT_VOLUME_SIZE_GIB = 30;
|
|
23
|
+
/**
|
|
24
|
+
* Resolve the caller's `updatePolicy` prop to a CDK `UpdatePolicy`. Default
|
|
25
|
+
* (`config` absent) → single-instance rolling roll on every
|
|
26
|
+
* `LaunchTemplateVersion` change. The `none` branch returns `undefined` so
|
|
27
|
+
* `addAutoScalingGroup` can conditionally spread the field — passing
|
|
28
|
+
* `updatePolicy: undefined` directly emits an empty `UpdatePolicy: {}` block
|
|
29
|
+
* on some CDK versions.
|
|
30
|
+
*
|
|
31
|
+
* Free function rather than a private method: `this` state is not read, and
|
|
32
|
+
* a free function is easier to reach from focused tests.
|
|
33
|
+
*/
|
|
34
|
+
function resolveUpdatePolicy(config) {
|
|
35
|
+
const resolved = config ?? { type: "rollingUpdate" };
|
|
36
|
+
switch (resolved.type) {
|
|
37
|
+
case "none":
|
|
38
|
+
return undefined;
|
|
39
|
+
case "replacingUpdate":
|
|
40
|
+
return UpdatePolicy.replacingUpdate();
|
|
41
|
+
case "rollingUpdate":
|
|
42
|
+
return UpdatePolicy.rollingUpdate({
|
|
43
|
+
minInstancesInService: 0,
|
|
44
|
+
maxBatchSize: 1,
|
|
45
|
+
pauseTime: resolved.pauseTime ?? Duration.minutes(5)
|
|
46
|
+
});
|
|
47
|
+
default:
|
|
48
|
+
return assertNever(resolved);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
function assertNever(value) {
|
|
52
|
+
throw new Error(`Ec2Instance.updatePolicy: unrecognised config ${JSON.stringify(value)}`);
|
|
53
|
+
}
|
|
9
54
|
export class Ec2Instance extends Construct {
|
|
10
55
|
launchTemplate;
|
|
11
56
|
vpc;
|
|
12
57
|
asgSecurityGroup;
|
|
13
58
|
autoScalingGroup;
|
|
14
59
|
keyPair;
|
|
60
|
+
persistentDataVolume;
|
|
15
61
|
connections;
|
|
16
62
|
constructor(scope, id, props) {
|
|
17
63
|
super(scope, id);
|
|
64
|
+
validateEc2InstanceProps(props);
|
|
18
65
|
this.addVpc(props);
|
|
19
66
|
this.addKeyPair(props);
|
|
20
67
|
this.addLaunchTemplate(props);
|
|
21
68
|
this.addAutoScalingGroup(props);
|
|
69
|
+
this.applyInstanceTags(props);
|
|
22
70
|
this.suspendAutoScaling(props);
|
|
23
|
-
|
|
24
|
-
|
|
71
|
+
this.addPersistentDataVolume(props);
|
|
72
|
+
this.addGracefulTerminationHandler(props);
|
|
25
73
|
this.connections = new Connections({
|
|
26
74
|
securityGroups: [this.asgSecurityGroup],
|
|
27
75
|
defaultPort: props.defaultPort ? Port.tcp(props.defaultPort) : undefined
|
|
@@ -43,52 +91,76 @@ export class Ec2Instance extends Construct {
|
|
|
43
91
|
this.keyPair = undefined;
|
|
44
92
|
}
|
|
45
93
|
else {
|
|
46
|
-
// TODO: Break out into a separate construct for use with better prop handling
|
|
47
94
|
this.keyPair = new KeyPair(this, "KeyPair", {
|
|
48
95
|
keyPairName: `${props.serviceName}KeyPair`
|
|
49
96
|
});
|
|
50
97
|
}
|
|
51
98
|
}
|
|
52
99
|
addLaunchTemplate(props) {
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
100
|
+
if (props.securityGroup !== undefined) {
|
|
101
|
+
this.asgSecurityGroup = props.securityGroup;
|
|
102
|
+
}
|
|
103
|
+
else {
|
|
104
|
+
this.asgSecurityGroup = new SecurityGroup(this, `AsgSecurityGroup`, {
|
|
105
|
+
vpc: this.vpc,
|
|
106
|
+
description: `Security group for the ${props.serviceName} auto scaling group`
|
|
107
|
+
});
|
|
108
|
+
}
|
|
57
109
|
if (props.enableSSH) {
|
|
58
110
|
this.asgSecurityGroup.addIngressRule(Peer.anyIpv4(), Port.tcp(22), "Allow SSH");
|
|
59
111
|
}
|
|
60
112
|
this.launchTemplate = new LaunchTemplate(this, "LaunchTemplate", {
|
|
61
113
|
launchTemplateName: `${props.serviceName}LaunchTemplate`,
|
|
62
|
-
instanceType: new InstanceType(
|
|
63
|
-
machineImage: props.machineImage
|
|
114
|
+
instanceType: new InstanceType(props.instanceType),
|
|
115
|
+
machineImage: props.machineImage ?? MachineImage.latestAmazonLinux2023(),
|
|
64
116
|
userData: props.userData,
|
|
65
117
|
role: props.role,
|
|
66
|
-
blockDevices: props
|
|
118
|
+
blockDevices: props.blockDevices ?? [
|
|
119
|
+
{
|
|
120
|
+
deviceName: DEFAULT_ROOT_DEVICE_NAME,
|
|
121
|
+
volume: safeEbs(DEFAULT_ROOT_VOLUME_SIZE_GIB)
|
|
122
|
+
}
|
|
123
|
+
],
|
|
67
124
|
securityGroup: this.asgSecurityGroup,
|
|
68
|
-
detailedMonitoring:
|
|
125
|
+
detailedMonitoring: props.instanceMonitoring === undefined
|
|
126
|
+
? true
|
|
127
|
+
: props.instanceMonitoring === Monitoring.DETAILED,
|
|
69
128
|
requireImdsv2: true,
|
|
70
129
|
httpPutResponseHopLimit: 2,
|
|
71
130
|
httpTokens: LaunchTemplateHttpTokens.REQUIRED,
|
|
72
131
|
instanceMetadataTags: true,
|
|
73
132
|
keyPair: this.keyPair,
|
|
74
|
-
associatePublicIpAddress: !!this.keyPair
|
|
133
|
+
associatePublicIpAddress: props.associatePublicIpAddress ?? !!this.keyPair
|
|
75
134
|
});
|
|
76
135
|
}
|
|
77
136
|
addAutoScalingGroup(props) {
|
|
78
|
-
|
|
137
|
+
const vpcSubnets = props.vpcSubnets ?? {
|
|
138
|
+
subnetType: props.enableSSH
|
|
139
|
+
? SubnetType.PUBLIC
|
|
140
|
+
: resolvePrivateSubnetType(this.vpc)
|
|
141
|
+
};
|
|
142
|
+
const resolvedUpdatePolicy = resolveUpdatePolicy(props.updatePolicy);
|
|
79
143
|
const baseAsgProps = {
|
|
80
144
|
vpc: this.vpc,
|
|
81
|
-
vpcSubnets
|
|
82
|
-
subnetType: props.enableSSH
|
|
83
|
-
? SubnetType.PUBLIC
|
|
84
|
-
: resolvePrivateSubnetType(this.vpc)
|
|
85
|
-
},
|
|
145
|
+
vpcSubnets,
|
|
86
146
|
minCapacity: props.minCapacity,
|
|
87
147
|
maxCapacity: props.maxCapacity,
|
|
148
|
+
...(props.desiredCapacity !== undefined && {
|
|
149
|
+
desiredCapacity: props.desiredCapacity
|
|
150
|
+
}),
|
|
151
|
+
...(props.capacityRebalance !== undefined && {
|
|
152
|
+
capacityRebalance: props.capacityRebalance
|
|
153
|
+
}),
|
|
88
154
|
cooldown: Duration.seconds(60),
|
|
89
155
|
groupMetrics: [GroupMetrics.all()],
|
|
90
|
-
|
|
91
|
-
|
|
156
|
+
...(resolvedUpdatePolicy !== undefined && {
|
|
157
|
+
updatePolicy: resolvedUpdatePolicy
|
|
158
|
+
}),
|
|
159
|
+
// ECS task drain on terminate is handled by the EC2_INSTANCE_TERMINATING
|
|
160
|
+
// lifecycle hook + Ec2GracefulTerminationHandler (300s heartbeat), not by
|
|
161
|
+
// managed termination protection. Leaving instances scale-in-protected
|
|
162
|
+
// wedges CFN rollback because ASG cannot terminate the instance.
|
|
163
|
+
newInstancesProtectedFromScaleIn: false,
|
|
92
164
|
terminationPolicies: [
|
|
93
165
|
TerminationPolicy.OLDEST_LAUNCH_CONFIGURATION,
|
|
94
166
|
TerminationPolicy.CLOSEST_TO_NEXT_INSTANCE_HOUR
|
|
@@ -98,7 +170,7 @@ export class Ec2Instance extends Construct {
|
|
|
98
170
|
// Support spot instances via mixed instances policy
|
|
99
171
|
if (props.spotCapacityPercentage !== undefined &&
|
|
100
172
|
props.spotCapacityPercentage > 0) {
|
|
101
|
-
const spotPercentage = Math.min(100,
|
|
173
|
+
const spotPercentage = Math.min(100, props.spotCapacityPercentage);
|
|
102
174
|
const onDemandPercentage = 100 - spotPercentage;
|
|
103
175
|
this.autoScalingGroup = new AutoScalingGroup(this, "AutoScalingGroup", {
|
|
104
176
|
...baseAsgProps,
|
|
@@ -119,6 +191,24 @@ export class Ec2Instance extends Construct {
|
|
|
119
191
|
launchTemplate: this.launchTemplate
|
|
120
192
|
});
|
|
121
193
|
}
|
|
194
|
+
if (props.warmPool !== undefined) {
|
|
195
|
+
this.autoScalingGroup.addWarmPool(props.warmPool);
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
/**
|
|
199
|
+
* Apply `props.tags` to the underlying ASG with
|
|
200
|
+
* `applyToLaunchedInstances: true` so the CFN ASG `Tags` array carries
|
|
201
|
+
* `{ Key, Value, PropagateAtLaunch: true }` for each entry. Enables
|
|
202
|
+
* tag-based SSM `SendCommand` targeting on the launched EC2 instances.
|
|
203
|
+
*/
|
|
204
|
+
applyInstanceTags(props) {
|
|
205
|
+
if (props.tags === undefined)
|
|
206
|
+
return;
|
|
207
|
+
for (const [key, value] of Object.entries(props.tags)) {
|
|
208
|
+
Tags.of(this.autoScalingGroup).add(key, value, {
|
|
209
|
+
applyToLaunchedInstances: true
|
|
210
|
+
});
|
|
211
|
+
}
|
|
122
212
|
}
|
|
123
213
|
/**
|
|
124
214
|
* Get the Auto Scaling Group.
|
|
@@ -126,6 +216,34 @@ export class Ec2Instance extends Construct {
|
|
|
126
216
|
getAutoScalingGroup() {
|
|
127
217
|
return this.autoScalingGroup;
|
|
128
218
|
}
|
|
219
|
+
/**
|
|
220
|
+
* Wire an ASG `EC2_INSTANCE_TERMINATING` hook + Lambda so instances
|
|
221
|
+
* terminate cleanly during stack updates and stack deletes. Drain ownership
|
|
222
|
+
* lives at the EC2 layer; ECS-specific drain plugs in via `ecsClusterArn`
|
|
223
|
+
* so a single hook handles bare-EC2 (bastion, Fivetran) and ECS-wired
|
|
224
|
+
* ASGs (ClickHouse) without duplicate plumbing in the ECS layer.
|
|
225
|
+
*/
|
|
226
|
+
addGracefulTerminationHandler(props) {
|
|
227
|
+
const dataVolumeOwnerLogicalId = this.persistentDataVolume?.ownerLogicalId;
|
|
228
|
+
new Ec2GracefulTerminationHandler(this, `${props.serviceName}GracefulTermination`, {
|
|
229
|
+
autoScalingGroup: this.autoScalingGroup,
|
|
230
|
+
...(props.ecsClusterArn !== undefined &&
|
|
231
|
+
props.ecsClusterArn !== "" && {
|
|
232
|
+
ecsClusterArn: props.ecsClusterArn
|
|
233
|
+
}),
|
|
234
|
+
...(dataVolumeOwnerLogicalId !== undefined && {
|
|
235
|
+
dataVolumeOwnerLogicalId
|
|
236
|
+
})
|
|
237
|
+
});
|
|
238
|
+
}
|
|
239
|
+
addPersistentDataVolume(props) {
|
|
240
|
+
if (props.persistentDataVolume === undefined)
|
|
241
|
+
return;
|
|
242
|
+
this.persistentDataVolume = new PersistentDataVolume(this, `${props.serviceName}PersistentDataVolume`, {
|
|
243
|
+
autoScalingGroup: this.autoScalingGroup,
|
|
244
|
+
...props.persistentDataVolume
|
|
245
|
+
});
|
|
246
|
+
}
|
|
129
247
|
suspendAutoScaling(_props) {
|
|
130
248
|
const suspendCall = {
|
|
131
249
|
service: "AutoScaling",
|
|
@@ -143,9 +261,31 @@ export class Ec2Instance extends Construct {
|
|
|
143
261
|
});
|
|
144
262
|
}
|
|
145
263
|
}
|
|
264
|
+
function validateEc2InstanceProps(props) {
|
|
265
|
+
if (props.persistentDataVolume !== undefined) {
|
|
266
|
+
const azs = props.vpcSubnets?.availabilityZones;
|
|
267
|
+
if (azs === undefined || azs.length !== 1) {
|
|
268
|
+
throw new Error(`Ec2Instance.persistentDataVolume requires vpcSubnets.availabilityZones to be exactly 1 entry; got ${azs?.length ?? 0}`);
|
|
269
|
+
}
|
|
270
|
+
if (azs[0] !== props.persistentDataVolume.availabilityZone) {
|
|
271
|
+
throw new Error(`Ec2Instance.persistentDataVolume.availabilityZone must match vpcSubnets.availabilityZones[0]; got '${props.persistentDataVolume.availabilityZone}' vs '${azs[0]}'`);
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
if (props.tags !== undefined) {
|
|
275
|
+
for (const [key, value] of Object.entries(props.tags)) {
|
|
276
|
+
if (key === "") {
|
|
277
|
+
throw new Error("Ec2Instance.tags: empty-string keys are not permitted");
|
|
278
|
+
}
|
|
279
|
+
if (value === "") {
|
|
280
|
+
throw new Error(`Ec2Instance.tags['${key}']: empty-string values are not permitted`);
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
}
|
|
146
285
|
export class Ec2InstanceStack extends Stack {
|
|
147
286
|
constructor(scope, id, props) {
|
|
148
287
|
super(scope, id, props);
|
|
288
|
+
validateEc2InstanceProps(props);
|
|
149
289
|
new Ec2Instance(this, id, {
|
|
150
290
|
...props
|
|
151
291
|
});
|