@fjall/components-infrastructure 0.96.0 → 0.99.3
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 +68 -1
- package/dist/lib/app.js +113 -4
- 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 +2 -4
- 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 +173 -0
- package/dist/lib/patterns/aws/clickhouseDatabase.js +601 -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 +93 -5
- package/dist/lib/patterns/aws/computeEcs.js +867 -37
- package/dist/lib/patterns/aws/computeEcsTypes.d.ts +528 -25
- package/dist/lib/patterns/aws/computeEcsTypes.js +10 -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 +6 -4
- package/dist/lib/patterns/aws/index.d.ts +1 -0
- package/dist/lib/patterns/aws/index.js +1 -0
- package/dist/lib/patterns/aws/interfaces/compute.d.ts +7 -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 +2 -1
- package/dist/lib/patterns/aws/interfaces/index.js +3 -1
- 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/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 +22 -4
- 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.js +3 -1
- package/dist/lib/resources/aws/analytics/clickhouse.js +18 -9
- package/dist/lib/resources/aws/analytics/clickhouseAlarms.d.ts +24 -9
- package/dist/lib/resources/aws/analytics/clickhouseAlarms.js +61 -10
- package/dist/lib/resources/aws/analytics/clickhouseConstants.d.ts +3 -3
- package/dist/lib/resources/aws/analytics/clickhouseConstants.js +3 -3
- package/dist/lib/resources/aws/analytics/clickhouseTypes.d.ts +7 -1
- package/dist/lib/resources/aws/analytics/clickhouseUserData.d.ts +1 -1
- package/dist/lib/resources/aws/analytics/clickhouseUserData.js +53 -3
- 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/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 +102 -6
- package/dist/lib/resources/aws/compute/ecsTypes.d.ts +173 -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 +160 -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 +1 -1
- 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 -4
- package/dist/lib/resources/aws/networking/crossAccountReturnRoutes.js +17 -13
- package/dist/lib/resources/aws/networking/dnsRecord/dnsRecordBase.js +7 -5
- package/dist/lib/resources/aws/networking/domainCertificate.d.ts +2 -2
- package/dist/lib/resources/aws/networking/domainCertificate.js +6 -4
- package/dist/lib/resources/aws/networking/hostedZone.js +6 -5
- 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 +4 -1
- package/dist/lib/resources/aws/networking/vpcPeeringConnection.js +21 -3
- 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/cdkContext.d.ts +2 -0
- package/dist/lib/utils/cdkContext.js +4 -2
- package/dist/lib/utils/connections.js +6 -0
- package/dist/lib/utils/connector.d.ts +12 -0
- package/dist/lib/utils/costAllocationTags.d.ts +9 -0
- package/dist/lib/utils/costAllocationTags.js +11 -1
- 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 +1 -0
- package/dist/lib/utils/index.js +1 -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/package.json +22 -19
|
@@ -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
|
});
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { type AutoScalingGroup } from "aws-cdk-lib/aws-autoscaling";
|
|
2
|
+
import { Construct } from "constructs";
|
|
3
|
+
import { LambdaFunction } from "./lambda.js";
|
|
4
|
+
import { SQSQueue } from "../messaging/sqs.js";
|
|
5
|
+
/**
|
|
6
|
+
* Description prefix baked into the Lambda + execution-role descriptions.
|
|
7
|
+
* Tests grep CFN output for this needle to locate the function across
|
|
8
|
+
* stacks that may contain unrelated Lambdas — keep it here as the single
|
|
9
|
+
* source of truth so a rename does not silently break the test query.
|
|
10
|
+
*/
|
|
11
|
+
export declare const EC2_GRACEFUL_TERMINATION_HANDLER_DESCRIPTION = "EC2 graceful termination handler";
|
|
12
|
+
export interface Ec2GracefulTerminationHandlerProps {
|
|
13
|
+
/** ASG to attach the EC2_INSTANCE_TERMINATING hook to. */
|
|
14
|
+
autoScalingGroup: AutoScalingGroup;
|
|
15
|
+
/**
|
|
16
|
+
* ECS cluster ARN — when set, the Lambda drains and deregisters the
|
|
17
|
+
* container instance before generic cleanup. Empty string is normalised
|
|
18
|
+
* to `undefined`; consumers should pass `undefined` for bare-EC2
|
|
19
|
+
* deployments (bastion, Fivetran).
|
|
20
|
+
*/
|
|
21
|
+
ecsClusterArn?: string;
|
|
22
|
+
/**
|
|
23
|
+
* `fjall:OwnerLogicalId` of a paired `PersistentDataVolume`. When set, the
|
|
24
|
+
* Lambda detaches the tagged volume from the terminating instance before
|
|
25
|
+
* `CompleteLifecycleAction`. Empty string is normalised to `undefined`.
|
|
26
|
+
* Pass the `PersistentDataVolume.ownerLogicalId` so the TERMINATING and
|
|
27
|
+
* LAUNCHING handlers locate the same volume.
|
|
28
|
+
*/
|
|
29
|
+
dataVolumeOwnerLogicalId?: string;
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* ASG `EC2_INSTANCE_TERMINATING` hook + Lambda that drains the instance
|
|
33
|
+
* cleanly. Drain ownership lives at the EC2 layer; ECS-specific drain plugs
|
|
34
|
+
* in via `ecsClusterArn` so a single hook handles both bare-EC2 (bastion,
|
|
35
|
+
* Fivetran) and ECS-wired ASGs (ClickHouse).
|
|
36
|
+
*/
|
|
37
|
+
export declare class Ec2GracefulTerminationHandler extends Construct {
|
|
38
|
+
readonly lambda: LambdaFunction;
|
|
39
|
+
readonly queue: SQSQueue;
|
|
40
|
+
constructor(scope: Construct, id: string, props: Ec2GracefulTerminationHandlerProps);
|
|
41
|
+
}
|
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
import { readFileSync } from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { fileURLToPath } from "node:url";
|
|
4
|
+
import { Aws, Stack } from "aws-cdk-lib";
|
|
5
|
+
import { Code, Runtime } from "aws-cdk-lib/aws-lambda";
|
|
6
|
+
import { PolicyStatement, Effect } from "aws-cdk-lib/aws-iam";
|
|
7
|
+
import { RetentionDays } from "aws-cdk-lib/aws-logs";
|
|
8
|
+
import { LifecycleTransition, DefaultResult } from "aws-cdk-lib/aws-autoscaling";
|
|
9
|
+
import { Construct } from "constructs";
|
|
10
|
+
import { attachInlineAsgLifecycleHook } from "./asgInlineLifecycleHook.js";
|
|
11
|
+
import { LambdaFunction } from "./lambda.js";
|
|
12
|
+
import { PERSISTENT_DATA_VOLUME_TAG_LIFECYCLE, PERSISTENT_DATA_VOLUME_TAG_LIFECYCLE_VALUE, PERSISTENT_DATA_VOLUME_TAG_STACK_ID } from "./persistentDataVolume.js";
|
|
13
|
+
import { SQSQueue } from "../messaging/sqs.js";
|
|
14
|
+
import { Subscription } from "../messaging/subscription.js";
|
|
15
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
16
|
+
const LAMBDA_SOURCE_FILE = "ec2GracefulTerminationLambda.source.cjs";
|
|
17
|
+
/**
|
|
18
|
+
* Description prefix baked into the Lambda + execution-role descriptions.
|
|
19
|
+
* Tests grep CFN output for this needle to locate the function across
|
|
20
|
+
* stacks that may contain unrelated Lambdas — keep it here as the single
|
|
21
|
+
* source of truth so a rename does not silently break the test query.
|
|
22
|
+
*/
|
|
23
|
+
export const EC2_GRACEFUL_TERMINATION_HANDLER_DESCRIPTION = "EC2 graceful termination handler";
|
|
24
|
+
const LAMBDA_TIMEOUT_SECONDS = 300;
|
|
25
|
+
// Heartbeat must comfortably exceed the Lambda's drain (240s) + detach (60s)
|
|
26
|
+
// budget so the hook does not time out mid-cleanup when PDV is wired.
|
|
27
|
+
const HOOK_HEARTBEAT_SECONDS = 600;
|
|
28
|
+
const QUEUE_VISIBILITY_TIMEOUT_SECONDS = 360;
|
|
29
|
+
/**
|
|
30
|
+
* ASG `EC2_INSTANCE_TERMINATING` hook + Lambda that drains the instance
|
|
31
|
+
* cleanly. Drain ownership lives at the EC2 layer; ECS-specific drain plugs
|
|
32
|
+
* in via `ecsClusterArn` so a single hook handles both bare-EC2 (bastion,
|
|
33
|
+
* Fivetran) and ECS-wired ASGs (ClickHouse).
|
|
34
|
+
*/
|
|
35
|
+
export class Ec2GracefulTerminationHandler extends Construct {
|
|
36
|
+
lambda;
|
|
37
|
+
queue;
|
|
38
|
+
constructor(scope, id, props) {
|
|
39
|
+
super(scope, id);
|
|
40
|
+
const sourcePath = path.resolve(__dirname, LAMBDA_SOURCE_FILE);
|
|
41
|
+
const source = readFileSync(sourcePath, "utf-8");
|
|
42
|
+
const ecsClusterArn = resolveOptionalString(props.ecsClusterArn);
|
|
43
|
+
const dataVolumeOwnerLogicalId = resolveOptionalString(props.dataVolumeOwnerLogicalId);
|
|
44
|
+
this.queue = new SQSQueue(this, `${id}Queue`, {
|
|
45
|
+
visibilityTimeout: QUEUE_VISIBILITY_TIMEOUT_SECONDS,
|
|
46
|
+
deadLetterQueue: { enabled: true, maxReceiveCount: 5 }
|
|
47
|
+
});
|
|
48
|
+
const ecsPolicies = ecsClusterArn !== undefined
|
|
49
|
+
? [
|
|
50
|
+
new PolicyStatement({
|
|
51
|
+
effect: Effect.ALLOW,
|
|
52
|
+
actions: ["ecs:ListContainerInstances"],
|
|
53
|
+
resources: [ecsClusterArn]
|
|
54
|
+
}),
|
|
55
|
+
new PolicyStatement({
|
|
56
|
+
effect: Effect.ALLOW,
|
|
57
|
+
actions: [
|
|
58
|
+
"ecs:DescribeContainerInstances",
|
|
59
|
+
"ecs:UpdateContainerInstancesState",
|
|
60
|
+
"ecs:DeregisterContainerInstance"
|
|
61
|
+
],
|
|
62
|
+
resources: ["*"],
|
|
63
|
+
conditions: {
|
|
64
|
+
ArnEquals: { "ecs:cluster": ecsClusterArn }
|
|
65
|
+
}
|
|
66
|
+
})
|
|
67
|
+
]
|
|
68
|
+
: [];
|
|
69
|
+
const ec2Policies = new PolicyStatement({
|
|
70
|
+
effect: Effect.ALLOW,
|
|
71
|
+
actions: [
|
|
72
|
+
"ec2:DescribeAddresses",
|
|
73
|
+
"ec2:DisassociateAddress",
|
|
74
|
+
"ec2:DescribeNetworkInterfaces",
|
|
75
|
+
"ec2:DetachNetworkInterface"
|
|
76
|
+
],
|
|
77
|
+
resources: ["*"]
|
|
78
|
+
});
|
|
79
|
+
const elbReadPolicy = new PolicyStatement({
|
|
80
|
+
effect: Effect.ALLOW,
|
|
81
|
+
actions: [
|
|
82
|
+
"elasticloadbalancing:DescribeTargetGroups",
|
|
83
|
+
"elasticloadbalancing:DescribeTargetHealth"
|
|
84
|
+
],
|
|
85
|
+
resources: ["*"]
|
|
86
|
+
});
|
|
87
|
+
const elbDeregisterPolicy = new PolicyStatement({
|
|
88
|
+
effect: Effect.ALLOW,
|
|
89
|
+
actions: ["elasticloadbalancing:DeregisterTargets"],
|
|
90
|
+
resources: ["*"],
|
|
91
|
+
conditions: {
|
|
92
|
+
StringEquals: {
|
|
93
|
+
"aws:ResourceTag/aws:cloudformation:stack-id": Aws.STACK_ID
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
});
|
|
97
|
+
const stack = Stack.of(this);
|
|
98
|
+
// Account/region-scoped wildcard rather than the specific ASG ARN —
|
|
99
|
+
// see PersistentDataVolume for the full deadlock writeup. Same gotcha:
|
|
100
|
+
// a specific ARN creates a CFN Ref to the ASG, the Lambda's IAM Policy
|
|
101
|
+
// waits for ASG CREATE_COMPLETE, the ASG can't complete without this
|
|
102
|
+
// Lambda's CompleteLifecycleAction call on its first terminating instance.
|
|
103
|
+
const asgArnWildcard = `arn:${stack.partition}:autoscaling:${stack.region}:${stack.account}:autoScalingGroup:*:autoScalingGroupName/*`;
|
|
104
|
+
const asgPolicy = new PolicyStatement({
|
|
105
|
+
effect: Effect.ALLOW,
|
|
106
|
+
actions: ["autoscaling:CompleteLifecycleAction"],
|
|
107
|
+
resources: [asgArnWildcard]
|
|
108
|
+
});
|
|
109
|
+
const volumeArnPattern = `arn:${stack.partition}:ec2:${stack.region}:${stack.account}:volume/*`;
|
|
110
|
+
const instanceArnPattern = `arn:${stack.partition}:ec2:${stack.region}:${stack.account}:instance/*`;
|
|
111
|
+
const dataVolumePolicies = dataVolumeOwnerLogicalId !== undefined
|
|
112
|
+
? [
|
|
113
|
+
new PolicyStatement({
|
|
114
|
+
effect: Effect.ALLOW,
|
|
115
|
+
actions: ["ec2:DescribeVolumes"],
|
|
116
|
+
resources: ["*"]
|
|
117
|
+
}),
|
|
118
|
+
new PolicyStatement({
|
|
119
|
+
effect: Effect.ALLOW,
|
|
120
|
+
actions: ["ec2:DetachVolume"],
|
|
121
|
+
resources: [volumeArnPattern],
|
|
122
|
+
conditions: {
|
|
123
|
+
StringEquals: {
|
|
124
|
+
[`aws:ResourceTag/${PERSISTENT_DATA_VOLUME_TAG_LIFECYCLE}`]: PERSISTENT_DATA_VOLUME_TAG_LIFECYCLE_VALUE,
|
|
125
|
+
[`aws:ResourceTag/${PERSISTENT_DATA_VOLUME_TAG_STACK_ID}`]: Aws.STACK_ID
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
}),
|
|
129
|
+
new PolicyStatement({
|
|
130
|
+
effect: Effect.ALLOW,
|
|
131
|
+
actions: ["ec2:DetachVolume"],
|
|
132
|
+
resources: [instanceArnPattern]
|
|
133
|
+
})
|
|
134
|
+
]
|
|
135
|
+
: [];
|
|
136
|
+
this.lambda = new LambdaFunction(this, `${id}Fn`, {
|
|
137
|
+
runtime: Runtime.NODEJS_22_X,
|
|
138
|
+
handler: "index.handler",
|
|
139
|
+
code: Code.fromInline(source),
|
|
140
|
+
lambdaDescription: `${id} ${EC2_GRACEFUL_TERMINATION_HANDLER_DESCRIPTION}`,
|
|
141
|
+
roleDescription: `Execution role for ${id} ${EC2_GRACEFUL_TERMINATION_HANDLER_DESCRIPTION}`,
|
|
142
|
+
timeout: LAMBDA_TIMEOUT_SECONDS,
|
|
143
|
+
memorySize: 256,
|
|
144
|
+
logGroupRetention: RetentionDays.ONE_MONTH,
|
|
145
|
+
environment: {
|
|
146
|
+
...(ecsClusterArn !== undefined && { ECS_CLUSTER_ARN: ecsClusterArn }),
|
|
147
|
+
...(dataVolumeOwnerLogicalId !== undefined && {
|
|
148
|
+
DATA_VOLUME_OWNER_LOGICAL_ID: dataVolumeOwnerLogicalId,
|
|
149
|
+
DATA_VOLUME_STACK_ID: Aws.STACK_ID
|
|
150
|
+
})
|
|
151
|
+
},
|
|
152
|
+
inlinePolicy: [
|
|
153
|
+
...ecsPolicies,
|
|
154
|
+
ec2Policies,
|
|
155
|
+
elbReadPolicy,
|
|
156
|
+
elbDeregisterPolicy,
|
|
157
|
+
asgPolicy,
|
|
158
|
+
...dataVolumePolicies
|
|
159
|
+
]
|
|
160
|
+
});
|
|
161
|
+
this.lambda.addSqsEventSource(this.queue.getQueue());
|
|
162
|
+
// Hook name embeds Aws.STACK_NAME (pseudo-parameter Ref, NOT a resource
|
|
163
|
+
// Ref — no CFN dep) so LifecycleHookName alone discriminates across
|
|
164
|
+
// stacks in the EB pattern. Sibling LAUNCHING hook uses "-launching".
|
|
165
|
+
const terminatingHookName = `${Aws.STACK_NAME}-${id}-terminating`;
|
|
166
|
+
attachInlineAsgLifecycleHook(this, `${id}TerminatingHook`, {
|
|
167
|
+
autoScalingGroup: props.autoScalingGroup,
|
|
168
|
+
hookName: terminatingHookName,
|
|
169
|
+
lifecycleTransition: LifecycleTransition.INSTANCE_TERMINATING,
|
|
170
|
+
defaultResult: DefaultResult.CONTINUE,
|
|
171
|
+
heartbeatTimeoutSeconds: HOOK_HEARTBEAT_SECONDS
|
|
172
|
+
});
|
|
173
|
+
// No AutoScalingGroupName in the pattern — that would create a CFN Ref to
|
|
174
|
+
// the ASG and block the EB rule on ASG CREATE_COMPLETE (deadlock — see
|
|
175
|
+
// wildcard ARN comment above and the PersistentDataVolume sibling).
|
|
176
|
+
new Subscription(this, `${id}TerminatingSub`, {
|
|
177
|
+
pattern: {
|
|
178
|
+
source: ["aws.autoscaling"],
|
|
179
|
+
detailType: ["EC2 Instance-terminate Lifecycle Action"],
|
|
180
|
+
detail: {
|
|
181
|
+
LifecycleHookName: [terminatingHookName]
|
|
182
|
+
}
|
|
183
|
+
},
|
|
184
|
+
target: this.queue
|
|
185
|
+
});
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
function resolveOptionalString(value) {
|
|
189
|
+
if (value === undefined)
|
|
190
|
+
return undefined;
|
|
191
|
+
if (value === "")
|
|
192
|
+
return undefined;
|
|
193
|
+
return value;
|
|
194
|
+
}
|