@fjall/components-infrastructure 0.79.3 → 0.80.4
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 +39 -2
- package/dist/lib/app.js +85 -4
- package/dist/lib/aspects/resourceInventory.d.ts +41 -0
- package/dist/lib/aspects/resourceInventory.js +56 -0
- package/dist/lib/config/audit.d.ts +18 -0
- package/dist/lib/config/audit.js +22 -0
- package/dist/lib/config/aws/accountId.js +3 -3
- package/dist/lib/config/aws/ecrDefaultImage.js +7 -7
- package/dist/lib/config/aws/identityCenter.js +7 -8
- package/dist/lib/config/aws/identityCenterGroupMembership.js +6 -6
- package/dist/lib/config/aws/ipam.js +3 -3
- package/dist/lib/config/aws/ipamPoolId.js +4 -4
- package/dist/lib/config/aws/organisation.js +2 -3
- package/dist/lib/config/aws/organisationId.js +4 -4
- package/dist/lib/patterns/aws/auditRole.d.ts +44 -0
- package/dist/lib/patterns/aws/auditRole.js +58 -0
- package/dist/lib/patterns/aws/buildkite.js +12 -12
- package/dist/lib/patterns/aws/cdn.d.ts +133 -0
- package/dist/lib/patterns/aws/cdn.js +216 -0
- package/dist/lib/patterns/aws/compute.d.ts +32 -13
- package/dist/lib/patterns/aws/compute.js +24 -14
- package/dist/lib/patterns/aws/database.d.ts +2 -1
- package/dist/lib/patterns/aws/database.js +7 -2
- package/dist/lib/patterns/aws/dynamodb.d.ts +66 -0
- package/dist/lib/patterns/aws/dynamodb.js +106 -0
- package/dist/lib/patterns/aws/hostedZone.js +1 -1
- package/dist/lib/patterns/aws/index.d.ts +1 -0
- package/dist/lib/patterns/aws/index.js +2 -1
- package/dist/lib/patterns/aws/loadBalancer.d.ts +163 -0
- package/dist/lib/patterns/aws/loadBalancer.js +278 -0
- package/dist/lib/patterns/aws/managedAccount.js +3 -4
- package/dist/lib/patterns/aws/queue.d.ts +61 -0
- package/dist/lib/patterns/aws/queue.js +103 -0
- package/dist/lib/patterns/aws/storage.js +3 -2
- package/dist/lib/patterns/aws/subdomainHostedZone.js +2 -2
- package/dist/lib/resources/aws/audit/auditRole.d.ts +32 -0
- package/dist/lib/resources/aws/audit/auditRole.js +44 -0
- package/dist/lib/resources/aws/backup/backupPlan.js +3 -4
- package/dist/lib/resources/aws/backup/backupVault.js +3 -4
- package/dist/lib/resources/aws/base/awsStack.js +1 -2
- package/dist/lib/resources/aws/cdn/cloudFront.d.ts +65 -0
- package/dist/lib/resources/aws/cdn/cloudFront.js +135 -0
- package/dist/lib/resources/aws/cdn/index.d.ts +1 -0
- package/dist/lib/resources/aws/cdn/index.js +18 -0
- package/dist/lib/resources/aws/compute/capacityProviderDrainWaiter.d.ts +20 -0
- package/dist/lib/resources/aws/compute/capacityProviderDrainWaiter.js +180 -0
- package/dist/lib/resources/aws/compute/ecs.d.ts +65 -11
- package/dist/lib/resources/aws/compute/ecs.js +293 -124
- package/dist/lib/resources/aws/compute/ecsFreeTier.d.ts +1 -2
- package/dist/lib/resources/aws/compute/ecsFreeTier.js +1 -4
- package/dist/lib/resources/aws/compute/ecsSpot.d.ts +1 -2
- package/dist/lib/resources/aws/compute/ecsSpot.js +1 -4
- package/dist/lib/resources/aws/compute/lambda.d.ts +10 -1
- package/dist/lib/resources/aws/compute/lambda.js +71 -4
- package/dist/lib/resources/aws/database/database.js +1 -1
- package/dist/lib/resources/aws/database/dynamodb.d.ts +70 -0
- package/dist/lib/resources/aws/database/dynamodb.js +170 -0
- package/dist/lib/resources/aws/database/rdsAurora.js +5 -5
- package/dist/lib/resources/aws/database/rdsAuroraGlobal.js +7 -7
- package/dist/lib/resources/aws/database/rdsDeletionWaiter.d.ts +33 -0
- package/dist/lib/resources/aws/database/rdsDeletionWaiter.js +74 -0
- package/dist/lib/resources/aws/database/rdsInstance.js +3 -3
- package/dist/lib/resources/aws/iam/identityCenter/assignment.js +2 -2
- package/dist/lib/resources/aws/iam/identityCenter/attachManagedPolicy.js +1 -1
- package/dist/lib/resources/aws/iam/identityCenter/group.js +1 -1
- package/dist/lib/resources/aws/iam/identityCenter/permissionSet.js +1 -1
- package/dist/lib/resources/aws/logging/cloudTrail.js +1 -1
- package/dist/lib/resources/aws/messaging/index.d.ts +1 -0
- package/dist/lib/resources/aws/messaging/index.js +18 -0
- package/dist/lib/resources/aws/messaging/sqs.d.ts +65 -0
- package/dist/lib/resources/aws/messaging/sqs.js +195 -0
- package/dist/lib/resources/aws/monitoring/monitoringRole.js +2 -3
- package/dist/lib/resources/aws/networking/ipamPool.js +1 -1
- package/dist/lib/resources/aws/networking/vpc.d.ts +1 -0
- package/dist/lib/resources/aws/networking/vpc.js +7 -3
- package/dist/lib/resources/aws/networking/vpcEndpoint.d.ts +20 -0
- package/dist/lib/resources/aws/networking/vpcEndpoint.js +59 -0
- package/dist/lib/resources/aws/networking/vpcEndpoints.d.ts +71 -0
- package/dist/lib/resources/aws/networking/vpcEndpoints.js +125 -0
- package/dist/lib/resources/aws/secrets/secret.js +1 -1
- package/dist/lib/resources/aws/storage/ecr.d.ts +1 -2
- package/dist/lib/resources/aws/storage/ecr.js +2 -2
- package/dist/lib/resources/aws/utilities/cfnOutput.test.d.ts +1 -0
- package/dist/lib/resources/aws/utilities/cfnOutput.test.js +102 -0
- package/dist/lib/resources/aws/utilities/codeBuild.d.ts +1 -2
- package/dist/lib/resources/aws/utilities/codeBuild.js +1 -1
- package/dist/lib/resources/aws/utilities/customResource.d.ts +2 -1
- package/dist/lib/resources/aws/utilities/customResource.js +1 -1
- package/dist/lib/utils/getStackOutput.js +2 -2
- package/dist/lib/utils/sanitizeCfnKey.d.ts +5 -0
- package/dist/lib/utils/sanitizeCfnKey.js +11 -0
- package/dist/lib/utils/tagResource.d.ts +24 -0
- package/dist/lib/utils/tagResource.js +30 -0
- package/package.json +6 -5
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.CapacityProviderDrainWaiter = void 0;
|
|
4
|
+
const aws_cdk_lib_1 = require("aws-cdk-lib");
|
|
5
|
+
const aws_iam_1 = require("aws-cdk-lib/aws-iam");
|
|
6
|
+
const aws_lambda_1 = require("aws-cdk-lib/aws-lambda");
|
|
7
|
+
const constructs_1 = require("constructs");
|
|
8
|
+
const customResource_1 = require("../utilities/customResource");
|
|
9
|
+
/**
|
|
10
|
+
* Custom resource that waits for ECS services to drain before allowing
|
|
11
|
+
* capacity provider disassociation.
|
|
12
|
+
*
|
|
13
|
+
* @see https://github.com/aws/aws-cdk/issues/14732
|
|
14
|
+
*/
|
|
15
|
+
class CapacityProviderDrainWaiter extends constructs_1.Construct {
|
|
16
|
+
constructor(scope, id, props) {
|
|
17
|
+
super(scope, id);
|
|
18
|
+
const { clusterName, capacityProviderName } = props;
|
|
19
|
+
// Lambda inline code for the custom resource handler
|
|
20
|
+
const handlerCode = `
|
|
21
|
+
const { ECSClient, ListServicesCommand, DescribeServicesCommand, PutClusterCapacityProvidersCommand, DescribeClustersCommand } = require("@aws-sdk/client-ecs");
|
|
22
|
+
|
|
23
|
+
const ecsClient = new ECSClient({});
|
|
24
|
+
const POLL_INTERVAL_MS = 5000;
|
|
25
|
+
const MAX_WAIT_MS = 300000; // 5 minutes
|
|
26
|
+
|
|
27
|
+
async function sleep(ms) {
|
|
28
|
+
return new Promise(resolve => setTimeout(resolve, ms));
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
async function getClusterCapacityProviders(clusterName) {
|
|
32
|
+
const response = await ecsClient.send(new DescribeClustersCommand({
|
|
33
|
+
clusters: [clusterName]
|
|
34
|
+
}));
|
|
35
|
+
return response.clusters?.[0]?.capacityProviders || [];
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
async function waitForServicesDrained(clusterName, capacityProviderName) {
|
|
39
|
+
const startTime = Date.now();
|
|
40
|
+
|
|
41
|
+
while (Date.now() - startTime < MAX_WAIT_MS) {
|
|
42
|
+
// List all services in the cluster
|
|
43
|
+
const listResponse = await ecsClient.send(new ListServicesCommand({
|
|
44
|
+
cluster: clusterName,
|
|
45
|
+
maxResults: 100
|
|
46
|
+
}));
|
|
47
|
+
|
|
48
|
+
const serviceArns = listResponse.serviceArns || [];
|
|
49
|
+
|
|
50
|
+
if (serviceArns.length === 0) {
|
|
51
|
+
console.log("No services found in cluster - drain complete");
|
|
52
|
+
return true;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// Describe services to check running counts
|
|
56
|
+
const describeResponse = await ecsClient.send(new DescribeServicesCommand({
|
|
57
|
+
cluster: clusterName,
|
|
58
|
+
services: serviceArns
|
|
59
|
+
}));
|
|
60
|
+
|
|
61
|
+
const activeServices = (describeResponse.services || []).filter(svc => {
|
|
62
|
+
// Check if service uses this capacity provider
|
|
63
|
+
const usesCapacityProvider = (svc.capacityProviderStrategy || [])
|
|
64
|
+
.some(strategy => strategy.capacityProvider === capacityProviderName);
|
|
65
|
+
|
|
66
|
+
// Only care about services using this capacity provider that still have running tasks
|
|
67
|
+
return usesCapacityProvider && svc.runningCount > 0;
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
if (activeServices.length === 0) {
|
|
71
|
+
console.log("All services using capacity provider have drained");
|
|
72
|
+
return true;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
console.log(\`Waiting for \${activeServices.length} services to drain: \${activeServices.map(s => s.serviceName).join(", ")}\`);
|
|
76
|
+
await sleep(POLL_INTERVAL_MS);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
console.log("Timeout waiting for services to drain, proceeding anyway");
|
|
80
|
+
return false;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
async function disassociateCapacityProvider(clusterName, capacityProviderName) {
|
|
84
|
+
try {
|
|
85
|
+
// Get current capacity providers
|
|
86
|
+
const currentProviders = await getClusterCapacityProviders(clusterName);
|
|
87
|
+
|
|
88
|
+
// Filter out the one we want to remove
|
|
89
|
+
const remainingProviders = currentProviders.filter(cp => cp !== capacityProviderName);
|
|
90
|
+
|
|
91
|
+
if (remainingProviders.length === currentProviders.length) {
|
|
92
|
+
console.log(\`Capacity provider \${capacityProviderName} not associated with cluster\`);
|
|
93
|
+
return true;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// Ensure we keep at least FARGATE as a provider (required by ECS)
|
|
97
|
+
if (remainingProviders.length === 0) {
|
|
98
|
+
remainingProviders.push("FARGATE");
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
console.log(\`Disassociating \${capacityProviderName} from cluster. Remaining: \${remainingProviders.join(", ")}\`);
|
|
102
|
+
|
|
103
|
+
await ecsClient.send(new PutClusterCapacityProvidersCommand({
|
|
104
|
+
cluster: clusterName,
|
|
105
|
+
capacityProviders: remainingProviders,
|
|
106
|
+
defaultCapacityProviderStrategy: []
|
|
107
|
+
}));
|
|
108
|
+
|
|
109
|
+
console.log("Successfully disassociated capacity provider");
|
|
110
|
+
return true;
|
|
111
|
+
} catch (error) {
|
|
112
|
+
// If cluster or capacity provider doesn't exist, that's fine (already deleted)
|
|
113
|
+
if (error.name === "ClusterNotFoundException" ||
|
|
114
|
+
error.name === "ResourceNotFoundException" ||
|
|
115
|
+
error.message?.includes("does not exist")) {
|
|
116
|
+
console.log("Cluster or capacity provider not found - already cleaned up");
|
|
117
|
+
return true;
|
|
118
|
+
}
|
|
119
|
+
throw error;
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
exports.handler = async (event) => {
|
|
124
|
+
console.log("Event:", JSON.stringify(event, null, 2));
|
|
125
|
+
|
|
126
|
+
const clusterName = event.ResourceProperties.ClusterName;
|
|
127
|
+
const capacityProviderName = event.ResourceProperties.CapacityProviderName;
|
|
128
|
+
const requestType = event.RequestType;
|
|
129
|
+
|
|
130
|
+
try {
|
|
131
|
+
if (requestType === "Delete") {
|
|
132
|
+
console.log(\`Handling DELETE for capacity provider \${capacityProviderName} in cluster \${clusterName}\`);
|
|
133
|
+
|
|
134
|
+
// Wait for services to drain
|
|
135
|
+
await waitForServicesDrained(clusterName, capacityProviderName);
|
|
136
|
+
|
|
137
|
+
// Disassociate capacity provider from cluster
|
|
138
|
+
await disassociateCapacityProvider(clusterName, capacityProviderName);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
// Return success for all request types
|
|
142
|
+
return {
|
|
143
|
+
PhysicalResourceId: \`\${clusterName}-\${capacityProviderName}-drain-waiter\`,
|
|
144
|
+
Data: {
|
|
145
|
+
Message: \`\${requestType} completed successfully\`
|
|
146
|
+
}
|
|
147
|
+
};
|
|
148
|
+
} catch (error) {
|
|
149
|
+
console.error("Error:", error);
|
|
150
|
+
throw error;
|
|
151
|
+
}
|
|
152
|
+
};
|
|
153
|
+
`;
|
|
154
|
+
new customResource_1.CustomResource(this, "DrainWaiter", {
|
|
155
|
+
runtime: aws_lambda_1.Runtime.NODEJS_22_X,
|
|
156
|
+
inlineCode: handlerCode,
|
|
157
|
+
timeout: aws_cdk_lib_1.Duration.minutes(6),
|
|
158
|
+
lambdaDescription: `Waits for ECS services to drain before capacity provider disassociation`,
|
|
159
|
+
roleDescription: `Role for ${clusterName} capacity provider drain waiter`,
|
|
160
|
+
inlinePolicy: [
|
|
161
|
+
new aws_iam_1.PolicyStatement({
|
|
162
|
+
effect: aws_iam_1.Effect.ALLOW,
|
|
163
|
+
actions: [
|
|
164
|
+
"ecs:ListServices",
|
|
165
|
+
"ecs:DescribeServices",
|
|
166
|
+
"ecs:DescribeClusters",
|
|
167
|
+
"ecs:PutClusterCapacityProviders"
|
|
168
|
+
],
|
|
169
|
+
resources: ["*"]
|
|
170
|
+
})
|
|
171
|
+
],
|
|
172
|
+
properties: {
|
|
173
|
+
ClusterName: clusterName,
|
|
174
|
+
CapacityProviderName: capacityProviderName
|
|
175
|
+
}
|
|
176
|
+
});
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
exports.CapacityProviderDrainWaiter = CapacityProviderDrainWaiter;
|
|
180
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiY2FwYWNpdHlQcm92aWRlckRyYWluV2FpdGVyLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vLi4vLi4vLi4vbGliL3Jlc291cmNlcy9hd3MvY29tcHV0ZS9jYXBhY2l0eVByb3ZpZGVyRHJhaW5XYWl0ZXIudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6Ijs7O0FBQUEsNkNBQXVDO0FBQ3ZDLGlEQUE4RDtBQUM5RCx1REFBaUQ7QUFDakQsMkNBQXVDO0FBQ3ZDLGdFQUE2RDtBQVk3RDs7Ozs7R0FLRztBQUNILE1BQWEsMkJBQTRCLFNBQVEsc0JBQVM7SUFDeEQsWUFDRSxLQUFnQixFQUNoQixFQUFVLEVBQ1YsS0FBdUM7UUFFdkMsS0FBSyxDQUFDLEtBQUssRUFBRSxFQUFFLENBQUMsQ0FBQztRQUVqQixNQUFNLEVBQUUsV0FBVyxFQUFFLG9CQUFvQixFQUFFLEdBQUcsS0FBSyxDQUFDO1FBRXBELHFEQUFxRDtRQUNyRCxNQUFNLFdBQVcsR0FBRzs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7OztDQXFJdkIsQ0FBQztRQUVFLElBQUksK0JBQWMsQ0FBQyxJQUFJLEVBQUUsYUFBYSxFQUFFO1lBQ3RDLE9BQU8sRUFBRSxvQkFBTyxDQUFDLFdBQVc7WUFDNUIsVUFBVSxFQUFFLFdBQVc7WUFDdkIsT0FBTyxFQUFFLHNCQUFRLENBQUMsT0FBTyxDQUFDLENBQUMsQ0FBQztZQUM1QixpQkFBaUIsRUFBRSx5RUFBeUU7WUFDNUYsZUFBZSxFQUFFLFlBQVksV0FBVyxpQ0FBaUM7WUFDekUsWUFBWSxFQUFFO2dCQUNaLElBQUkseUJBQWUsQ0FBQztvQkFDbEIsTUFBTSxFQUFFLGdCQUFNLENBQUMsS0FBSztvQkFDcEIsT0FBTyxFQUFFO3dCQUNQLGtCQUFrQjt3QkFDbEIsc0JBQXNCO3dCQUN0QixzQkFBc0I7d0JBQ3RCLGlDQUFpQztxQkFDbEM7b0JBQ0QsU0FBUyxFQUFFLENBQUMsR0FBRyxDQUFDO2lCQUNqQixDQUFDO2FBQ0g7WUFDRCxVQUFVLEVBQUU7Z0JBQ1YsV0FBVyxFQUFFLFdBQVc7Z0JBQ3hCLG9CQUFvQixFQUFFLG9CQUFvQjthQUMzQztTQUNGLENBQUMsQ0FBQztJQUNMLENBQUM7Q0FDRjtBQTFLRCxrRUEwS0MiLCJzb3VyY2VzQ29udGVudCI6WyJpbXBvcnQgeyBEdXJhdGlvbiB9IGZyb20gXCJhd3MtY2RrLWxpYlwiO1xuaW1wb3J0IHsgUG9saWN5U3RhdGVtZW50LCBFZmZlY3QgfSBmcm9tIFwiYXdzLWNkay1saWIvYXdzLWlhbVwiO1xuaW1wb3J0IHsgUnVudGltZSB9IGZyb20gXCJhd3MtY2RrLWxpYi9hd3MtbGFtYmRhXCI7XG5pbXBvcnQgeyBDb25zdHJ1Y3QgfSBmcm9tIFwiY29uc3RydWN0c1wiO1xuaW1wb3J0IHsgQ3VzdG9tUmVzb3VyY2UgfSBmcm9tIFwiLi4vdXRpbGl0aWVzL2N1c3RvbVJlc291cmNlXCI7XG5cbi8qKlxuICogUHJvcHMgZm9yIENhcGFjaXR5UHJvdmlkZXJEcmFpbldhaXRlclxuICovXG5pbnRlcmZhY2UgQ2FwYWNpdHlQcm92aWRlckRyYWluV2FpdGVyUHJvcHMge1xuICAvKiogVGhlIEVDUyBjbHVzdGVyIG5hbWUgKi9cbiAgY2x1c3Rlck5hbWU6IHN0cmluZztcbiAgLyoqIFRoZSBjYXBhY2l0eSBwcm92aWRlciBuYW1lIHRvIHdhaXQgZm9yICovXG4gIGNhcGFjaXR5UHJvdmlkZXJOYW1lOiBzdHJpbmc7XG59XG5cbi8qKlxuICogQ3VzdG9tIHJlc291cmNlIHRoYXQgd2FpdHMgZm9yIEVDUyBzZXJ2aWNlcyB0byBkcmFpbiBiZWZvcmUgYWxsb3dpbmdcbiAqIGNhcGFjaXR5IHByb3ZpZGVyIGRpc2Fzc29jaWF0aW9uLlxuICpcbiAqIEBzZWUgaHR0cHM6Ly9naXRodWIuY29tL2F3cy9hd3MtY2RrL2lzc3Vlcy8xNDczMlxuICovXG5leHBvcnQgY2xhc3MgQ2FwYWNpdHlQcm92aWRlckRyYWluV2FpdGVyIGV4dGVuZHMgQ29uc3RydWN0IHtcbiAgY29uc3RydWN0b3IoXG4gICAgc2NvcGU6IENvbnN0cnVjdCxcbiAgICBpZDogc3RyaW5nLFxuICAgIHByb3BzOiBDYXBhY2l0eVByb3ZpZGVyRHJhaW5XYWl0ZXJQcm9wc1xuICApIHtcbiAgICBzdXBlcihzY29wZSwgaWQpO1xuXG4gICAgY29uc3QgeyBjbHVzdGVyTmFtZSwgY2FwYWNpdHlQcm92aWRlck5hbWUgfSA9IHByb3BzO1xuXG4gICAgLy8gTGFtYmRhIGlubGluZSBjb2RlIGZvciB0aGUgY3VzdG9tIHJlc291cmNlIGhhbmRsZXJcbiAgICBjb25zdCBoYW5kbGVyQ29kZSA9IGBcbmNvbnN0IHsgRUNTQ2xpZW50LCBMaXN0U2VydmljZXNDb21tYW5kLCBEZXNjcmliZVNlcnZpY2VzQ29tbWFuZCwgUHV0Q2x1c3RlckNhcGFjaXR5UHJvdmlkZXJzQ29tbWFuZCwgRGVzY3JpYmVDbHVzdGVyc0NvbW1hbmQgfSA9IHJlcXVpcmUoXCJAYXdzLXNkay9jbGllbnQtZWNzXCIpO1xuXG5jb25zdCBlY3NDbGllbnQgPSBuZXcgRUNTQ2xpZW50KHt9KTtcbmNvbnN0IFBPTExfSU5URVJWQUxfTVMgPSA1MDAwO1xuY29uc3QgTUFYX1dBSVRfTVMgPSAzMDAwMDA7IC8vIDUgbWludXRlc1xuXG5hc3luYyBmdW5jdGlvbiBzbGVlcChtcykge1xuICByZXR1cm4gbmV3IFByb21pc2UocmVzb2x2ZSA9PiBzZXRUaW1lb3V0KHJlc29sdmUsIG1zKSk7XG59XG5cbmFzeW5jIGZ1bmN0aW9uIGdldENsdXN0ZXJDYXBhY2l0eVByb3ZpZGVycyhjbHVzdGVyTmFtZSkge1xuICBjb25zdCByZXNwb25zZSA9IGF3YWl0IGVjc0NsaWVudC5zZW5kKG5ldyBEZXNjcmliZUNsdXN0ZXJzQ29tbWFuZCh7XG4gICAgY2x1c3RlcnM6IFtjbHVzdGVyTmFtZV1cbiAgfSkpO1xuICByZXR1cm4gcmVzcG9uc2UuY2x1c3RlcnM/LlswXT8uY2FwYWNpdHlQcm92aWRlcnMgfHwgW107XG59XG5cbmFzeW5jIGZ1bmN0aW9uIHdhaXRGb3JTZXJ2aWNlc0RyYWluZWQoY2x1c3Rlck5hbWUsIGNhcGFjaXR5UHJvdmlkZXJOYW1lKSB7XG4gIGNvbnN0IHN0YXJ0VGltZSA9IERhdGUubm93KCk7XG5cbiAgd2hpbGUgKERhdGUubm93KCkgLSBzdGFydFRpbWUgPCBNQVhfV0FJVF9NUykge1xuICAgIC8vIExpc3QgYWxsIHNlcnZpY2VzIGluIHRoZSBjbHVzdGVyXG4gICAgY29uc3QgbGlzdFJlc3BvbnNlID0gYXdhaXQgZWNzQ2xpZW50LnNlbmQobmV3IExpc3RTZXJ2aWNlc0NvbW1hbmQoe1xuICAgICAgY2x1c3RlcjogY2x1c3Rlck5hbWUsXG4gICAgICBtYXhSZXN1bHRzOiAxMDBcbiAgICB9KSk7XG5cbiAgICBjb25zdCBzZXJ2aWNlQXJucyA9IGxpc3RSZXNwb25zZS5zZXJ2aWNlQXJucyB8fCBbXTtcblxuICAgIGlmIChzZXJ2aWNlQXJucy5sZW5ndGggPT09IDApIHtcbiAgICAgIGNvbnNvbGUubG9nKFwiTm8gc2VydmljZXMgZm91bmQgaW4gY2x1c3RlciAtIGRyYWluIGNvbXBsZXRlXCIpO1xuICAgICAgcmV0dXJuIHRydWU7XG4gICAgfVxuXG4gICAgLy8gRGVzY3JpYmUgc2VydmljZXMgdG8gY2hlY2sgcnVubmluZyBjb3VudHNcbiAgICBjb25zdCBkZXNjcmliZVJlc3BvbnNlID0gYXdhaXQgZWNzQ2xpZW50LnNlbmQobmV3IERlc2NyaWJlU2VydmljZXNDb21tYW5kKHtcbiAgICAgIGNsdXN0ZXI6IGNsdXN0ZXJOYW1lLFxuICAgICAgc2VydmljZXM6IHNlcnZpY2VBcm5zXG4gICAgfSkpO1xuXG4gICAgY29uc3QgYWN0aXZlU2VydmljZXMgPSAoZGVzY3JpYmVSZXNwb25zZS5zZXJ2aWNlcyB8fCBbXSkuZmlsdGVyKHN2YyA9PiB7XG4gICAgICAvLyBDaGVjayBpZiBzZXJ2aWNlIHVzZXMgdGhpcyBjYXBhY2l0eSBwcm92aWRlclxuICAgICAgY29uc3QgdXNlc0NhcGFjaXR5UHJvdmlkZXIgPSAoc3ZjLmNhcGFjaXR5UHJvdmlkZXJTdHJhdGVneSB8fCBbXSlcbiAgICAgICAgLnNvbWUoc3RyYXRlZ3kgPT4gc3RyYXRlZ3kuY2FwYWNpdHlQcm92aWRlciA9PT0gY2FwYWNpdHlQcm92aWRlck5hbWUpO1xuXG4gICAgICAvLyBPbmx5IGNhcmUgYWJvdXQgc2VydmljZXMgdXNpbmcgdGhpcyBjYXBhY2l0eSBwcm92aWRlciB0aGF0IHN0aWxsIGhhdmUgcnVubmluZyB0YXNrc1xuICAgICAgcmV0dXJuIHVzZXNDYXBhY2l0eVByb3ZpZGVyICYmIHN2Yy5ydW5uaW5nQ291bnQgPiAwO1xuICAgIH0pO1xuXG4gICAgaWYgKGFjdGl2ZVNlcnZpY2VzLmxlbmd0aCA9PT0gMCkge1xuICAgICAgY29uc29sZS5sb2coXCJBbGwgc2VydmljZXMgdXNpbmcgY2FwYWNpdHkgcHJvdmlkZXIgaGF2ZSBkcmFpbmVkXCIpO1xuICAgICAgcmV0dXJuIHRydWU7XG4gICAgfVxuXG4gICAgY29uc29sZS5sb2coXFxgV2FpdGluZyBmb3IgXFwke2FjdGl2ZVNlcnZpY2VzLmxlbmd0aH0gc2VydmljZXMgdG8gZHJhaW46IFxcJHthY3RpdmVTZXJ2aWNlcy5tYXAocyA9PiBzLnNlcnZpY2VOYW1lKS5qb2luKFwiLCBcIil9XFxgKTtcbiAgICBhd2FpdCBzbGVlcChQT0xMX0lOVEVSVkFMX01TKTtcbiAgfVxuXG4gIGNvbnNvbGUubG9nKFwiVGltZW91dCB3YWl0aW5nIGZvciBzZXJ2aWNlcyB0byBkcmFpbiwgcHJvY2VlZGluZyBhbnl3YXlcIik7XG4gIHJldHVybiBmYWxzZTtcbn1cblxuYXN5bmMgZnVuY3Rpb24gZGlzYXNzb2NpYXRlQ2FwYWNpdHlQcm92aWRlcihjbHVzdGVyTmFtZSwgY2FwYWNpdHlQcm92aWRlck5hbWUpIHtcbiAgdHJ5IHtcbiAgICAvLyBHZXQgY3VycmVudCBjYXBhY2l0eSBwcm92aWRlcnNcbiAgICBjb25zdCBjdXJyZW50UHJvdmlkZXJzID0gYXdhaXQgZ2V0Q2x1c3RlckNhcGFjaXR5UHJvdmlkZXJzKGNsdXN0ZXJOYW1lKTtcblxuICAgIC8vIEZpbHRlciBvdXQgdGhlIG9uZSB3ZSB3YW50IHRvIHJlbW92ZVxuICAgIGNvbnN0IHJlbWFpbmluZ1Byb3ZpZGVycyA9IGN1cnJlbnRQcm92aWRlcnMuZmlsdGVyKGNwID0+IGNwICE9PSBjYXBhY2l0eVByb3ZpZGVyTmFtZSk7XG5cbiAgICBpZiAocmVtYWluaW5nUHJvdmlkZXJzLmxlbmd0aCA9PT0gY3VycmVudFByb3ZpZGVycy5sZW5ndGgpIHtcbiAgICAgIGNvbnNvbGUubG9nKFxcYENhcGFjaXR5IHByb3ZpZGVyIFxcJHtjYXBhY2l0eVByb3ZpZGVyTmFtZX0gbm90IGFzc29jaWF0ZWQgd2l0aCBjbHVzdGVyXFxgKTtcbiAgICAgIHJldHVybiB0cnVlO1xuICAgIH1cblxuICAgIC8vIEVuc3VyZSB3ZSBrZWVwIGF0IGxlYXN0IEZBUkdBVEUgYXMgYSBwcm92aWRlciAocmVxdWlyZWQgYnkgRUNTKVxuICAgIGlmIChyZW1haW5pbmdQcm92aWRlcnMubGVuZ3RoID09PSAwKSB7XG4gICAgICByZW1haW5pbmdQcm92aWRlcnMucHVzaChcIkZBUkdBVEVcIik7XG4gICAgfVxuXG4gICAgY29uc29sZS5sb2coXFxgRGlzYXNzb2NpYXRpbmcgXFwke2NhcGFjaXR5UHJvdmlkZXJOYW1lfSBmcm9tIGNsdXN0ZXIuIFJlbWFpbmluZzogXFwke3JlbWFpbmluZ1Byb3ZpZGVycy5qb2luKFwiLCBcIil9XFxgKTtcblxuICAgIGF3YWl0IGVjc0NsaWVudC5zZW5kKG5ldyBQdXRDbHVzdGVyQ2FwYWNpdHlQcm92aWRlcnNDb21tYW5kKHtcbiAgICAgIGNsdXN0ZXI6IGNsdXN0ZXJOYW1lLFxuICAgICAgY2FwYWNpdHlQcm92aWRlcnM6IHJlbWFpbmluZ1Byb3ZpZGVycyxcbiAgICAgIGRlZmF1bHRDYXBhY2l0eVByb3ZpZGVyU3RyYXRlZ3k6IFtdXG4gICAgfSkpO1xuXG4gICAgY29uc29sZS5sb2coXCJTdWNjZXNzZnVsbHkgZGlzYXNzb2NpYXRlZCBjYXBhY2l0eSBwcm92aWRlclwiKTtcbiAgICByZXR1cm4gdHJ1ZTtcbiAgfSBjYXRjaCAoZXJyb3IpIHtcbiAgICAvLyBJZiBjbHVzdGVyIG9yIGNhcGFjaXR5IHByb3ZpZGVyIGRvZXNuJ3QgZXhpc3QsIHRoYXQncyBmaW5lIChhbHJlYWR5IGRlbGV0ZWQpXG4gICAgaWYgKGVycm9yLm5hbWUgPT09IFwiQ2x1c3Rlck5vdEZvdW5kRXhjZXB0aW9uXCIgfHxcbiAgICAgICAgZXJyb3IubmFtZSA9PT0gXCJSZXNvdXJjZU5vdEZvdW5kRXhjZXB0aW9uXCIgfHxcbiAgICAgICAgZXJyb3IubWVzc2FnZT8uaW5jbHVkZXMoXCJkb2VzIG5vdCBleGlzdFwiKSkge1xuICAgICAgY29uc29sZS5sb2coXCJDbHVzdGVyIG9yIGNhcGFjaXR5IHByb3ZpZGVyIG5vdCBmb3VuZCAtIGFscmVhZHkgY2xlYW5lZCB1cFwiKTtcbiAgICAgIHJldHVybiB0cnVlO1xuICAgIH1cbiAgICB0aHJvdyBlcnJvcjtcbiAgfVxufVxuXG5leHBvcnRzLmhhbmRsZXIgPSBhc3luYyAoZXZlbnQpID0+IHtcbiAgY29uc29sZS5sb2coXCJFdmVudDpcIiwgSlNPTi5zdHJpbmdpZnkoZXZlbnQsIG51bGwsIDIpKTtcblxuICBjb25zdCBjbHVzdGVyTmFtZSA9IGV2ZW50LlJlc291cmNlUHJvcGVydGllcy5DbHVzdGVyTmFtZTtcbiAgY29uc3QgY2FwYWNpdHlQcm92aWRlck5hbWUgPSBldmVudC5SZXNvdXJjZVByb3BlcnRpZXMuQ2FwYWNpdHlQcm92aWRlck5hbWU7XG4gIGNvbnN0IHJlcXVlc3RUeXBlID0gZXZlbnQuUmVxdWVzdFR5cGU7XG5cbiAgdHJ5IHtcbiAgICBpZiAocmVxdWVzdFR5cGUgPT09IFwiRGVsZXRlXCIpIHtcbiAgICAgIGNvbnNvbGUubG9nKFxcYEhhbmRsaW5nIERFTEVURSBmb3IgY2FwYWNpdHkgcHJvdmlkZXIgXFwke2NhcGFjaXR5UHJvdmlkZXJOYW1lfSBpbiBjbHVzdGVyIFxcJHtjbHVzdGVyTmFtZX1cXGApO1xuXG4gICAgICAvLyBXYWl0IGZvciBzZXJ2aWNlcyB0byBkcmFpblxuICAgICAgYXdhaXQgd2FpdEZvclNlcnZpY2VzRHJhaW5lZChjbHVzdGVyTmFtZSwgY2FwYWNpdHlQcm92aWRlck5hbWUpO1xuXG4gICAgICAvLyBEaXNhc3NvY2lhdGUgY2FwYWNpdHkgcHJvdmlkZXIgZnJvbSBjbHVzdGVyXG4gICAgICBhd2FpdCBkaXNhc3NvY2lhdGVDYXBhY2l0eVByb3ZpZGVyKGNsdXN0ZXJOYW1lLCBjYXBhY2l0eVByb3ZpZGVyTmFtZSk7XG4gICAgfVxuXG4gICAgLy8gUmV0dXJuIHN1Y2Nlc3MgZm9yIGFsbCByZXF1ZXN0IHR5cGVzXG4gICAgcmV0dXJuIHtcbiAgICAgIFBoeXNpY2FsUmVzb3VyY2VJZDogXFxgXFwke2NsdXN0ZXJOYW1lfS1cXCR7Y2FwYWNpdHlQcm92aWRlck5hbWV9LWRyYWluLXdhaXRlclxcYCxcbiAgICAgIERhdGE6IHtcbiAgICAgICAgTWVzc2FnZTogXFxgXFwke3JlcXVlc3RUeXBlfSBjb21wbGV0ZWQgc3VjY2Vzc2Z1bGx5XFxgXG4gICAgICB9XG4gICAgfTtcbiAgfSBjYXRjaCAoZXJyb3IpIHtcbiAgICBjb25zb2xlLmVycm9yKFwiRXJyb3I6XCIsIGVycm9yKTtcbiAgICB0aHJvdyBlcnJvcjtcbiAgfVxufTtcbmA7XG5cbiAgICBuZXcgQ3VzdG9tUmVzb3VyY2UodGhpcywgXCJEcmFpbldhaXRlclwiLCB7XG4gICAgICBydW50aW1lOiBSdW50aW1lLk5PREVKU18yMl9YLFxuICAgICAgaW5saW5lQ29kZTogaGFuZGxlckNvZGUsXG4gICAgICB0aW1lb3V0OiBEdXJhdGlvbi5taW51dGVzKDYpLFxuICAgICAgbGFtYmRhRGVzY3JpcHRpb246IGBXYWl0cyBmb3IgRUNTIHNlcnZpY2VzIHRvIGRyYWluIGJlZm9yZSBjYXBhY2l0eSBwcm92aWRlciBkaXNhc3NvY2lhdGlvbmAsXG4gICAgICByb2xlRGVzY3JpcHRpb246IGBSb2xlIGZvciAke2NsdXN0ZXJOYW1lfSBjYXBhY2l0eSBwcm92aWRlciBkcmFpbiB3YWl0ZXJgLFxuICAgICAgaW5saW5lUG9saWN5OiBbXG4gICAgICAgIG5ldyBQb2xpY3lTdGF0ZW1lbnQoe1xuICAgICAgICAgIGVmZmVjdDogRWZmZWN0LkFMTE9XLFxuICAgICAgICAgIGFjdGlvbnM6IFtcbiAgICAgICAgICAgIFwiZWNzOkxpc3RTZXJ2aWNlc1wiLFxuICAgICAgICAgICAgXCJlY3M6RGVzY3JpYmVTZXJ2aWNlc1wiLFxuICAgICAgICAgICAgXCJlY3M6RGVzY3JpYmVDbHVzdGVyc1wiLFxuICAgICAgICAgICAgXCJlY3M6UHV0Q2x1c3RlckNhcGFjaXR5UHJvdmlkZXJzXCJcbiAgICAgICAgICBdLFxuICAgICAgICAgIHJlc291cmNlczogW1wiKlwiXVxuICAgICAgICB9KVxuICAgICAgXSxcbiAgICAgIHByb3BlcnRpZXM6IHtcbiAgICAgICAgQ2x1c3Rlck5hbWU6IGNsdXN0ZXJOYW1lLFxuICAgICAgICBDYXBhY2l0eVByb3ZpZGVyTmFtZTogY2FwYWNpdHlQcm92aWRlck5hbWVcbiAgICAgIH1cbiAgICB9KTtcbiAgfVxufVxuIl19
|
|
@@ -3,7 +3,7 @@ import { Connections, type IConnectable, type IVpc } from "aws-cdk-lib/aws-ec2";
|
|
|
3
3
|
import { Construct } from "constructs";
|
|
4
4
|
import { type StackBuilder } from "../base/awsStack";
|
|
5
5
|
import { type ApplicationListener, ApplicationLoadBalancer } from "aws-cdk-lib/aws-elasticloadbalancingv2";
|
|
6
|
-
import { type IManagedPolicy, PolicyDocument } from "aws-cdk-lib/aws-iam";
|
|
6
|
+
import { type IManagedPolicy, type PolicyDocument } from "aws-cdk-lib/aws-iam";
|
|
7
7
|
import { Certificate } from "aws-cdk-lib/aws-certificatemanager";
|
|
8
8
|
import { type GeoLocation } from "aws-cdk-lib/aws-route53";
|
|
9
9
|
import { Repository } from "aws-cdk-lib/aws-ecr";
|
|
@@ -210,6 +210,17 @@ export interface EcsServiceProps {
|
|
|
210
210
|
* Creates security group rules to allow traffic from this specific service only.
|
|
211
211
|
*/
|
|
212
212
|
connections?: IConnectable[];
|
|
213
|
+
/**
|
|
214
|
+
* Capacity provider for this service. REQUIRED.
|
|
215
|
+
* Each service specifies its own capacity provider.
|
|
216
|
+
*/
|
|
217
|
+
capacityProvider: EcsCapacityProvider;
|
|
218
|
+
/**
|
|
219
|
+
* EC2 capacity configuration for this service.
|
|
220
|
+
* Only used when service capacityProvider is "EC2".
|
|
221
|
+
* Services with matching ec2Config share an ASG for efficiency.
|
|
222
|
+
*/
|
|
223
|
+
ec2Config?: Ec2CapacityConfig;
|
|
213
224
|
}
|
|
214
225
|
/**
|
|
215
226
|
* Props for creating an ECS cluster with multiple services.
|
|
@@ -221,10 +232,6 @@ export type EcsClusterProps = {
|
|
|
221
232
|
vpc?: IVpc;
|
|
222
233
|
/** Default ECR repository or container image */
|
|
223
234
|
ecrRepository: Repository | RepositoryImage | string;
|
|
224
|
-
/** Capacity provider determines Fargate vs EC2 infrastructure */
|
|
225
|
-
capacityProvider?: EcsCapacityProvider;
|
|
226
|
-
/** EC2-specific configuration. Only used when capacityProvider is "EC2" */
|
|
227
|
-
ec2Config?: Ec2CapacityConfig;
|
|
228
235
|
/**
|
|
229
236
|
* Cluster configuration.
|
|
230
237
|
* Controls the shared ALB for all services.
|
|
@@ -233,6 +240,7 @@ export type EcsClusterProps = {
|
|
|
233
240
|
/**
|
|
234
241
|
* Services in this cluster.
|
|
235
242
|
* Each service gets its own task definition, scaling, and target group.
|
|
243
|
+
* Each service MUST specify its own capacityProvider.
|
|
236
244
|
* All services share the cluster's ALB (unless disabled).
|
|
237
245
|
* Task role policies are configured per-service for least-privilege.
|
|
238
246
|
*/
|
|
@@ -286,12 +294,10 @@ export default class EcsCluster extends Construct implements IConnectable {
|
|
|
286
294
|
private asgSecurityGroup?;
|
|
287
295
|
private asgCapacityProvider?;
|
|
288
296
|
private loadBalancerSecurityGroup?;
|
|
289
|
-
private drainWaiter?;
|
|
290
|
-
private fargateCapacityProviderAssociations?;
|
|
291
297
|
private services;
|
|
298
|
+
private asgCapacityProviders;
|
|
292
299
|
private scope;
|
|
293
300
|
private props;
|
|
294
|
-
private capacityProvider;
|
|
295
301
|
private loadBalancerDisabled;
|
|
296
302
|
private directAccessEnabled;
|
|
297
303
|
private nextPriority;
|
|
@@ -334,10 +340,58 @@ export default class EcsCluster extends Construct implements IConnectable {
|
|
|
334
340
|
private registerServiceWithALB;
|
|
335
341
|
private buildRoutingConditions;
|
|
336
342
|
private addServiceScaling;
|
|
337
|
-
|
|
338
|
-
|
|
343
|
+
/**
|
|
344
|
+
* Checks if any service in the cluster uses EC2 capacity provider.
|
|
345
|
+
*/
|
|
346
|
+
private hasAnyEc2Service;
|
|
347
|
+
/**
|
|
348
|
+
* Checks if any service in the cluster uses Fargate capacity provider.
|
|
349
|
+
*/
|
|
350
|
+
private hasAnyFargateService;
|
|
351
|
+
/**
|
|
352
|
+
* Check if the VPC has NAT gateways.
|
|
353
|
+
* - For Fjall Vpc: uses hasNatGateways property
|
|
354
|
+
* - For other VPCs: checks if private subnets exist (assumes NAT if present)
|
|
355
|
+
*/
|
|
356
|
+
private vpcHasNatGateways;
|
|
357
|
+
/**
|
|
358
|
+
* Create DeployableService outputs for deployment automation.
|
|
359
|
+
* Each service gets a DeployableService output so the deployment service
|
|
360
|
+
* can find and deploy all services in the cluster.
|
|
361
|
+
*/
|
|
362
|
+
addDeployableServiceOutputs(props: EcsClusterProps): void;
|
|
363
|
+
/**
|
|
364
|
+
* Gets the capacity provider for a service.
|
|
365
|
+
* Each service MUST specify its own capacityProvider.
|
|
366
|
+
*/
|
|
367
|
+
private getServiceCapacityProvider;
|
|
368
|
+
/**
|
|
369
|
+
* Checks if a service uses a Fargate capacity provider.
|
|
370
|
+
*/
|
|
371
|
+
private isServiceFargate;
|
|
372
|
+
/**
|
|
373
|
+
* Checks if a service uses an EC2 capacity provider.
|
|
374
|
+
*/
|
|
375
|
+
private isServiceEc2;
|
|
376
|
+
/**
|
|
377
|
+
* Generates a unique key for EC2 config (for ASG deduplication).
|
|
378
|
+
* Services with matching keys share an ASG.
|
|
379
|
+
*/
|
|
380
|
+
private getEc2ConfigKey;
|
|
381
|
+
/**
|
|
382
|
+
* Gets or creates an ASG capacity provider for a service.
|
|
383
|
+
* Services with matching EC2 configs share the same ASG.
|
|
384
|
+
*/
|
|
385
|
+
private getOrCreateAsgCapacityProvider;
|
|
386
|
+
/**
|
|
387
|
+
* Checks if any service in the cluster uses a Fargate capacity provider.
|
|
388
|
+
*/
|
|
389
|
+
private anyServiceUsesFargate;
|
|
390
|
+
/**
|
|
391
|
+
* Checks if any service in the cluster uses an EC2 capacity provider.
|
|
392
|
+
*/
|
|
393
|
+
private anyServiceUsesEc2;
|
|
339
394
|
addCluster(props: EcsClusterProps): void;
|
|
340
|
-
addAutoScalingGroup(props: EcsClusterProps): void;
|
|
341
395
|
addLoadBalancer(props: EcsClusterProps): void;
|
|
342
396
|
private addDirectAccessOutputs;
|
|
343
397
|
addLoadBalancerListener(props: EcsClusterProps): void;
|