@ossy/deployment-tools 3.4.0 → 3.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +14 -0
- package/cdk.context.json +13 -0
- package/package.json +2 -2
- package/src/config/platform-config.js +3 -0
- package/src/ecs/platform-ecs-services.js +15 -2
- package/src/ecs/platform-ecs-services.spec.js +28 -0
- package/src/edge/platform-edge-domains.js +25 -3
- package/src/edge/platform-edge-domains.spec.js +3 -6
- package/src/infrastructure/platform-ci-stack.js +9 -6
- package/src/infrastructure/platform-ecs-stack.js +33 -13
package/CHANGELOG.md
CHANGED
|
@@ -3,6 +3,20 @@
|
|
|
3
3
|
All notable changes to this project will be documented in this file.
|
|
4
4
|
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
|
5
5
|
|
|
6
|
+
# [3.7.0](https://github.com/ossy-se/ossy/compare/v3.6.2...v3.7.0) (2026-08-22)
|
|
7
|
+
|
|
8
|
+
### Features
|
|
9
|
+
|
|
10
|
+
* **deployment-tools:** give website-ossy 1 vCPU, 4 GB, two tasks, and a larger Node heap ([#667](https://github.com/ossy-se/ossy/issues/667)) ([3e81436](https://github.com/ossy-se/ossy/commit/3e814363a7f80f37a1b90f64677371cfa2f2fe44))
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
# [3.6.0](https://github.com/ossy-se/ossy/compare/v3.5.1...v3.6.0) (2026-08-16)
|
|
14
|
+
|
|
15
|
+
### Bug Fixes
|
|
16
|
+
|
|
17
|
+
* **deployment-tools:** stabilize ECS cutover edge, OIDC, and ALB rules ([#657](https://github.com/ossy-se/ossy/issues/657)) ([257bd79](https://github.com/ossy-se/ossy/commit/257bd7965d86a09aae2b8f285c5e22e2d29160bb))
|
|
18
|
+
|
|
19
|
+
|
|
6
20
|
# [3.4.0](https://github.com/ossy-se/ossy/compare/v3.3.0...v3.4.0) (2026-08-14)
|
|
7
21
|
|
|
8
22
|
### Features
|
package/cdk.context.json
CHANGED
|
@@ -84,5 +84,18 @@
|
|
|
84
84
|
"hosted-zone:account=858451553223:domainName=tepit.se:region=eu-north-1": {
|
|
85
85
|
"Id": "/hostedzone/Z07859112SU1GH5SKMUXD",
|
|
86
86
|
"Name": "tepit.se."
|
|
87
|
+
},
|
|
88
|
+
"availability-zones:account=858451553223:region=eu-north-1": [
|
|
89
|
+
"eu-north-1a",
|
|
90
|
+
"eu-north-1b",
|
|
91
|
+
"eu-north-1c"
|
|
92
|
+
],
|
|
93
|
+
"hosted-zone:account=858451553223:domainName=ossy.se:region=us-east-1": {
|
|
94
|
+
"Id": "/hostedzone/Z09424571CVOTIJV8Z50C",
|
|
95
|
+
"Name": "ossy.se."
|
|
96
|
+
},
|
|
97
|
+
"hosted-zone:account=858451553223:domainName=plexus-sanitas.com:region=us-east-1": {
|
|
98
|
+
"Id": "/hostedzone/Z0222639MQIJ8EOL9VAG",
|
|
99
|
+
"Name": "plexus-sanitas.com."
|
|
87
100
|
}
|
|
88
101
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ossy/deployment-tools",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.7.0",
|
|
4
4
|
"description": "Collection of scripts and tools to aid deployment of containers and static files to Amazon Web Services through GitHub Actions",
|
|
5
5
|
"source": "./src/index.js",
|
|
6
6
|
"main": "./src/index.js",
|
|
@@ -24,5 +24,5 @@
|
|
|
24
24
|
"devDependencies": {
|
|
25
25
|
"jest": "^30.4.2"
|
|
26
26
|
},
|
|
27
|
-
"gitHead": "
|
|
27
|
+
"gitHead": "622d97535abad08415963405ace2f5aaaecd7ca4"
|
|
28
28
|
}
|
|
@@ -21,6 +21,9 @@
|
|
|
21
21
|
* @property {string} domain - primary hostname (default ALB host when `hosts` omitted)
|
|
22
22
|
* @property {string[]=} hosts - ALB host-header values (#557); defaults to `[domain]`
|
|
23
23
|
* @property {string} image - Docker image (e.g. ghcr.io/ossy-se/website-ossy:latest)
|
|
24
|
+
* @property {number=} cpu - Fargate CPU units; default 512
|
|
25
|
+
* @property {number=} memoryLimitMiB - Fargate memory MiB; default 1024
|
|
26
|
+
* @property {number=} desiredCount - ECS desired tasks; default 1
|
|
24
27
|
*
|
|
25
28
|
* @typedef {Object} TcpServiceConfig
|
|
26
29
|
* @property {'tcp'} type - raw socket service; not deployed on the ECS path (#560)
|
|
@@ -42,6 +42,10 @@ const CONTAINER_ENVIRONMENT_KEYS = Object.freeze([
|
|
|
42
42
|
* @property {string[]} hosts - ALB host-header values for this service (empty = default/catch-all)
|
|
43
43
|
* @property {boolean} isDefault - when true, ALB default action forwards here (runtime)
|
|
44
44
|
* @property {number} containerPort - container listen port
|
|
45
|
+
* @property {number} cpu - Fargate task CPU units (256, 512, 1024, …)
|
|
46
|
+
* @property {number} memoryLimitMiB - Fargate task memory (must be valid for `cpu`)
|
|
47
|
+
* @property {number} desiredCount - ECS service desired task count
|
|
48
|
+
* @property {string} [nodeOptions] - optional `NODE_OPTIONS` for the container
|
|
45
49
|
*/
|
|
46
50
|
|
|
47
51
|
/**
|
|
@@ -55,7 +59,7 @@ function secretEnvKeysForEcsContainer(envKeys = []) {
|
|
|
55
59
|
if (!Array.isArray(envKeys)) {
|
|
56
60
|
throw new Error('[platform-ecs-services] envKeys must be an array')
|
|
57
61
|
}
|
|
58
|
-
const reserved = new Set(CONTAINER_ENVIRONMENT_KEYS)
|
|
62
|
+
const reserved = new Set([...CONTAINER_ENVIRONMENT_KEYS, 'NODE_OPTIONS'])
|
|
59
63
|
return envKeys.filter((key) => typeof key === 'string' && key && !reserved.has(key))
|
|
60
64
|
}
|
|
61
65
|
|
|
@@ -78,6 +82,7 @@ function containerEnvironment({
|
|
|
78
82
|
containerPort = CONTAINER_PORT,
|
|
79
83
|
mediaRepository,
|
|
80
84
|
mediaCdnDomainName,
|
|
85
|
+
nodeOptions,
|
|
81
86
|
}) {
|
|
82
87
|
if (!serviceKey) {
|
|
83
88
|
throw new Error('[platform-ecs-services] serviceKey is required')
|
|
@@ -91,6 +96,7 @@ function containerEnvironment({
|
|
|
91
96
|
OSSY_SERVICE_NAME: serviceKey,
|
|
92
97
|
MEDIA_REPOSITORY: mediaRepository,
|
|
93
98
|
MEDIA_CDN_DOMAIN_NAME: mediaCdnDomainName,
|
|
99
|
+
...(nodeOptions ? { NODE_OPTIONS: nodeOptions } : {}),
|
|
94
100
|
}
|
|
95
101
|
}
|
|
96
102
|
|
|
@@ -151,7 +157,7 @@ function runtimeHosts(config, httpEntries) {
|
|
|
151
157
|
* @param {{
|
|
152
158
|
* platformName: string,
|
|
153
159
|
* domains?: string[],
|
|
154
|
-
* services?: Array<{ name: string, type?: string, domain?: string, hosts?: string[], image?: string }>
|
|
160
|
+
* services?: Array<{ name: string, type?: string, domain?: string, hosts?: string[], image?: string, cpu?: number, memoryLimitMiB?: number, desiredCount?: number }>
|
|
155
161
|
* }} config
|
|
156
162
|
* @returns {PlatformEcsService[]}
|
|
157
163
|
*/
|
|
@@ -173,6 +179,9 @@ function listPlatformEcsServices(config) {
|
|
|
173
179
|
hosts: runtimeHostList,
|
|
174
180
|
isDefault: true,
|
|
175
181
|
containerPort: CONTAINER_PORT,
|
|
182
|
+
cpu: 512,
|
|
183
|
+
memoryLimitMiB: 1024,
|
|
184
|
+
desiredCount: 1,
|
|
176
185
|
}
|
|
177
186
|
}
|
|
178
187
|
|
|
@@ -190,6 +199,10 @@ function listPlatformEcsServices(config) {
|
|
|
190
199
|
hosts: hostsFromServiceEntry(service.entry),
|
|
191
200
|
isDefault: false,
|
|
192
201
|
containerPort: CONTAINER_PORT,
|
|
202
|
+
cpu: Number.isFinite(service.entry.cpu) ? service.entry.cpu : 512,
|
|
203
|
+
memoryLimitMiB: Number.isFinite(service.entry.memoryLimitMiB) ? service.entry.memoryLimitMiB : 1024,
|
|
204
|
+
desiredCount: Number.isFinite(service.entry.desiredCount) ? service.entry.desiredCount : 1,
|
|
205
|
+
nodeOptions: typeof service.entry.nodeOptions === 'string' ? service.entry.nodeOptions : undefined,
|
|
193
206
|
}
|
|
194
207
|
})
|
|
195
208
|
}
|
|
@@ -63,6 +63,16 @@ describe('containerEnvironment', () => {
|
|
|
63
63
|
})
|
|
64
64
|
})
|
|
65
65
|
|
|
66
|
+
it('adds NODE_OPTIONS when provided', () => {
|
|
67
|
+
const env = containerEnvironment({
|
|
68
|
+
serviceKey: 'website-ossy',
|
|
69
|
+
mediaRepository: 'media-bucket',
|
|
70
|
+
mediaCdnDomainName: 'https://cdn.example',
|
|
71
|
+
nodeOptions: '--max-old-space-size=3072',
|
|
72
|
+
})
|
|
73
|
+
expect(env.NODE_OPTIONS).toBe('--max-old-space-size=3072')
|
|
74
|
+
})
|
|
75
|
+
|
|
66
76
|
it('requires serviceKey and media fields', () => {
|
|
67
77
|
expect(() => containerEnvironment({
|
|
68
78
|
mediaRepository: 'b',
|
|
@@ -82,6 +92,7 @@ describe('secretEnvKeysForEcsContainer', () => {
|
|
|
82
92
|
'TOKEN_SECRET',
|
|
83
93
|
'NODE_ENV',
|
|
84
94
|
'MEDIA_REPOSITORY',
|
|
95
|
+
'NODE_OPTIONS',
|
|
85
96
|
'DB_URL',
|
|
86
97
|
])).toEqual(['TOKEN_SECRET', 'DB_URL'])
|
|
87
98
|
})
|
|
@@ -121,6 +132,10 @@ describe('listPlatformEcsServices', () => {
|
|
|
121
132
|
domain: 'ossy.se',
|
|
122
133
|
hosts: ['ossy.se', 'api.ossy.se'],
|
|
123
134
|
image: 'ghcr.io/ossy-se/website-ossy:latest',
|
|
135
|
+
cpu: 1024,
|
|
136
|
+
memoryLimitMiB: 4096,
|
|
137
|
+
desiredCount: 2,
|
|
138
|
+
nodeOptions: '--max-old-space-size=3072',
|
|
124
139
|
},
|
|
125
140
|
{
|
|
126
141
|
name: 'ossy-website-plexus-sanitas',
|
|
@@ -153,6 +168,19 @@ describe('listPlatformEcsServices', () => {
|
|
|
153
168
|
expect(website.hosts).toEqual(['ossy.se', 'api.ossy.se'])
|
|
154
169
|
expect(website.image).toBe('ghcr.io/ossy-se/website-ossy:latest')
|
|
155
170
|
expect(website.secretName).toBe('ossybot/website-ossy')
|
|
171
|
+
expect(website.cpu).toBe(1024)
|
|
172
|
+
expect(website.memoryLimitMiB).toBe(4096)
|
|
173
|
+
expect(website.desiredCount).toBe(2)
|
|
174
|
+
expect(website.nodeOptions).toBe('--max-old-space-size=3072')
|
|
175
|
+
|
|
176
|
+
const plexus = services.find(s => s.key === 'website-plexus-sanitas')
|
|
177
|
+
expect(plexus.cpu).toBe(512)
|
|
178
|
+
expect(plexus.memoryLimitMiB).toBe(1024)
|
|
179
|
+
expect(plexus.desiredCount).toBe(1)
|
|
180
|
+
expect(plexus.nodeOptions).toBeUndefined()
|
|
181
|
+
|
|
182
|
+
expect(runtime.cpu).toBe(512)
|
|
183
|
+
expect(runtime.desiredCount).toBe(1)
|
|
156
184
|
|
|
157
185
|
expect(services.some(s => s.key.includes('api'))).toBe(false)
|
|
158
186
|
})
|
|
@@ -28,8 +28,29 @@ function rootDomainOf(domain) {
|
|
|
28
28
|
return parts.slice(-2).join('.')
|
|
29
29
|
}
|
|
30
30
|
|
|
31
|
+
/**
|
|
32
|
+
* Whether `hostname` is already covered by a `*.root` wildcard in `aliases`.
|
|
33
|
+
* CloudFront rejects overlapping CNAMEs (e.g. both `*.ossy.se` and `api.ossy.se`).
|
|
34
|
+
*
|
|
35
|
+
* @param {string} hostname
|
|
36
|
+
* @param {string[]} aliases
|
|
37
|
+
* @returns {boolean}
|
|
38
|
+
*/
|
|
39
|
+
function coveredByWildcardAlias(hostname, aliases) {
|
|
40
|
+
if (!hostname || hostname.includes('*')) return false
|
|
41
|
+
return aliases.some(alias => {
|
|
42
|
+
if (!alias.startsWith('*.')) return false
|
|
43
|
+
const suffix = alias.slice(1) // e.g. ".ossy.se"
|
|
44
|
+
if (!hostname.endsWith(suffix)) return false
|
|
45
|
+
const prefix = hostname.slice(0, -suffix.length)
|
|
46
|
+
// single-label subdomain only (*.ossy.se covers api.ossy.se, not a.b.ossy.se)
|
|
47
|
+
return prefix.length > 0 && !prefix.includes('.')
|
|
48
|
+
})
|
|
49
|
+
}
|
|
50
|
+
|
|
31
51
|
/**
|
|
32
52
|
* Unique, stable sorted list of CloudFront aliases from `domains` + HTTP service hosts.
|
|
53
|
+
* Drops hostnames covered by a sibling wildcard so ACM/CloudFront accept the set.
|
|
33
54
|
*
|
|
34
55
|
* @param {{
|
|
35
56
|
* domains?: string[],
|
|
@@ -46,9 +67,10 @@ function listEdgeAliases(config) {
|
|
|
46
67
|
return entry.domain ? [entry.domain] : []
|
|
47
68
|
})
|
|
48
69
|
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
70
|
+
const all = [...new Set([...fromDomains, ...fromServices])]
|
|
71
|
+
return all
|
|
72
|
+
.filter(alias => !coveredByWildcardAlias(alias, all))
|
|
73
|
+
.sort((a, b) => a.localeCompare(b))
|
|
52
74
|
}
|
|
53
75
|
|
|
54
76
|
/**
|
|
@@ -14,7 +14,7 @@ describe('rootDomainOf', () => {
|
|
|
14
14
|
})
|
|
15
15
|
|
|
16
16
|
describe('listEdgeAliases', () => {
|
|
17
|
-
it('unions domains and HTTP service hosts; skips tcp', () => {
|
|
17
|
+
it('unions domains and HTTP service hosts; skips tcp; drops names under wildcards', () => {
|
|
18
18
|
expect(listEdgeAliases({
|
|
19
19
|
domains: ['ossy.se', '*.ossy.se', 'worker.ossy.se'],
|
|
20
20
|
services: [
|
|
@@ -30,7 +30,7 @@ describe('listEdgeAliases', () => {
|
|
|
30
30
|
ports: [25565],
|
|
31
31
|
},
|
|
32
32
|
],
|
|
33
|
-
})).toEqual(['*.ossy.se', '
|
|
33
|
+
})).toEqual(['*.ossy.se', 'ossy.se'])
|
|
34
34
|
})
|
|
35
35
|
})
|
|
36
36
|
|
|
@@ -59,17 +59,14 @@ describe('planEdgeDomains', () => {
|
|
|
59
59
|
})
|
|
60
60
|
|
|
61
61
|
expect(plan.primaryDomain).toBe('ossy.se')
|
|
62
|
+
// api/worker covered by *.ossy.se — CloudFront forbids overlapping CNAMEs
|
|
62
63
|
expect(plan.aliases).toEqual([
|
|
63
64
|
'*.ossy.se',
|
|
64
|
-
'api.ossy.se',
|
|
65
65
|
'ossy.se',
|
|
66
|
-
'worker.ossy.se',
|
|
67
66
|
'www.plexus-sanitas.com',
|
|
68
67
|
])
|
|
69
68
|
expect(plan.subjectAlternativeNames).toEqual([
|
|
70
69
|
'*.ossy.se',
|
|
71
|
-
'api.ossy.se',
|
|
72
|
-
'worker.ossy.se',
|
|
73
70
|
'www.plexus-sanitas.com',
|
|
74
71
|
])
|
|
75
72
|
expect(plan.validationDomainToRoot.get('*.ossy.se')).toBe('ossy.se')
|
|
@@ -23,8 +23,9 @@ const {
|
|
|
23
23
|
* Trust is limited to `ref:refs/heads/main` for `config.githubDeployRepos`
|
|
24
24
|
* (set in platforms.json — add a repo there to allow it to assume this role).
|
|
25
25
|
*
|
|
26
|
-
* Note: AWS allows only one OIDC provider per URL per account. This stack
|
|
27
|
-
*
|
|
26
|
+
* Note: AWS allows only one OIDC provider per URL per account. This stack
|
|
27
|
+
* references the account provider (often created earlier by `trust-ci`) instead
|
|
28
|
+
* of creating a second one.
|
|
28
29
|
*/
|
|
29
30
|
class PlatformCiStack extends Stack {
|
|
30
31
|
/**
|
|
@@ -61,10 +62,12 @@ class PlatformCiStack extends Stack {
|
|
|
61
62
|
return `repo:${repo}:ref:refs/heads/main`
|
|
62
63
|
})
|
|
63
64
|
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
65
|
+
// One provider per issuer URL per account — reuse the existing GitHub OIDC IdP.
|
|
66
|
+
const provider = OpenIdConnectProvider.fromOpenIdConnectProviderArn(
|
|
67
|
+
this,
|
|
68
|
+
'GithubOidcProvider',
|
|
69
|
+
`arn:aws:iam::${account}:oidc-provider/token.actions.githubusercontent.com`
|
|
70
|
+
)
|
|
68
71
|
|
|
69
72
|
const deployRole = new Role(this, 'GithubDeployRole', {
|
|
70
73
|
roleName,
|
|
@@ -166,8 +166,8 @@ class PlatformEcsStack extends Stack {
|
|
|
166
166
|
|
|
167
167
|
const taskDefinition = new FargateTaskDefinition(this, `TaskDef${idSuffix}`, {
|
|
168
168
|
family: `${platformName}-${service.key}`,
|
|
169
|
-
cpu:
|
|
170
|
-
memoryLimitMiB:
|
|
169
|
+
cpu: service.cpu,
|
|
170
|
+
memoryLimitMiB: service.memoryLimitMiB,
|
|
171
171
|
runtimePlatform: {
|
|
172
172
|
cpuArchitecture: CpuArchitecture.X86_64,
|
|
173
173
|
operatingSystemFamily: OperatingSystemFamily.LINUX,
|
|
@@ -199,6 +199,7 @@ class PlatformEcsStack extends Stack {
|
|
|
199
199
|
containerPort: service.containerPort,
|
|
200
200
|
mediaRepository: props.bucket.bucketName,
|
|
201
201
|
mediaCdnDomainName: `https://${props.cdnDomainName}`,
|
|
202
|
+
nodeOptions: service.nodeOptions,
|
|
202
203
|
}),
|
|
203
204
|
secrets: containerSecrets,
|
|
204
205
|
// Node fetch → GET /health (no curl in node:*-slim; no image-local script required).
|
|
@@ -222,7 +223,7 @@ class PlatformEcsStack extends Stack {
|
|
|
222
223
|
serviceName: truncateAwsName(`${platformName}-${service.key}`, 255),
|
|
223
224
|
cluster,
|
|
224
225
|
taskDefinition,
|
|
225
|
-
desiredCount:
|
|
226
|
+
desiredCount: service.desiredCount,
|
|
226
227
|
assignPublicIp: false,
|
|
227
228
|
securityGroups: [serviceSecurityGroup],
|
|
228
229
|
vpcSubnets: { subnetType: SubnetType.PRIVATE_WITH_EGRESS },
|
|
@@ -281,23 +282,18 @@ class PlatformEcsStack extends Stack {
|
|
|
281
282
|
|
|
282
283
|
// Lower priority number = evaluated first. Specific hosts before wildcards
|
|
283
284
|
// so `api.ossy.se` is not swallowed by `*.ossy.se`.
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
})
|
|
289
|
-
|
|
290
|
-
let rulePriority = 10
|
|
291
|
-
for (const rule of orderedHostRules) {
|
|
285
|
+
// Priorities are hash-stable per host — sequential 10,11,12… renumbers
|
|
286
|
+
// every insert and CloudFormation cannot swap ALB priorities in one update.
|
|
287
|
+
const usedPriorities = new Set()
|
|
288
|
+
for (const rule of hostRules) {
|
|
292
289
|
listener.addTargetGroups(
|
|
293
290
|
`Rule${rule.idSuffix}${toConstructId(rule.host)}`,
|
|
294
291
|
{
|
|
295
|
-
priority:
|
|
292
|
+
priority: albHostRulePriority(rule.host, usedPriorities),
|
|
296
293
|
conditions: [ListenerCondition.hostHeaders([rule.host])],
|
|
297
294
|
targetGroups: [rule.targetGroup],
|
|
298
295
|
}
|
|
299
296
|
)
|
|
300
|
-
rulePriority += 1
|
|
301
297
|
}
|
|
302
298
|
|
|
303
299
|
this.vpc = vpc
|
|
@@ -325,6 +321,29 @@ class PlatformEcsStack extends Stack {
|
|
|
325
321
|
}
|
|
326
322
|
}
|
|
327
323
|
|
|
324
|
+
/**
|
|
325
|
+
* Stable ALB listener-rule priority for a host header (1–50000).
|
|
326
|
+
* Specific hosts: 10000–29999; wildcards: 40000–49999 (evaluated later).
|
|
327
|
+
* @param {string} host
|
|
328
|
+
* @param {Set<number>} used
|
|
329
|
+
* @returns {number}
|
|
330
|
+
*/
|
|
331
|
+
function albHostRulePriority(host, used) {
|
|
332
|
+
const band = host.includes('*') ? 40000 : 10000
|
|
333
|
+
let hash = 2166136261
|
|
334
|
+
for (let i = 0; i < host.length; i += 1) {
|
|
335
|
+
hash ^= host.charCodeAt(i)
|
|
336
|
+
hash = Math.imul(hash, 16777619) >>> 0
|
|
337
|
+
}
|
|
338
|
+
let priority = band + (hash % 20000)
|
|
339
|
+
while (used.has(priority)) {
|
|
340
|
+
priority += 1
|
|
341
|
+
if (priority >= band + 20000) priority = band
|
|
342
|
+
}
|
|
343
|
+
used.add(priority)
|
|
344
|
+
return priority
|
|
345
|
+
}
|
|
346
|
+
|
|
328
347
|
/**
|
|
329
348
|
* CloudFormation-safe construct id fragment.
|
|
330
349
|
* @param {string} key
|
|
@@ -346,4 +365,5 @@ function truncateAwsName(name, max) {
|
|
|
346
365
|
|
|
347
366
|
module.exports = {
|
|
348
367
|
PlatformEcsStack,
|
|
368
|
+
albHostRulePriority,
|
|
349
369
|
}
|