@fjall/components-infrastructure 4.1.0 → 4.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/lib/patterns/aws/computeEcs.js +2 -1
- package/dist/lib/resources/aws/compute/ecsTaskDefinition.js +3 -0
- package/dist/lib/resources/aws/compute/ecsTypes.d.ts +18 -3
- package/dist/lib/resources/aws/compute/ecsValidation.d.ts +56 -1
- package/dist/lib/resources/aws/compute/ecsValidation.js +66 -0
- package/package.json +4 -4
|
@@ -25,7 +25,7 @@ import { FjallLogger } from "../../utils/validationLogger.js";
|
|
|
25
25
|
import { VALIDATION_PATTERNS } from "@fjall/generator";
|
|
26
26
|
import { evaluateBakeGuard } from "@fjall/util/docker";
|
|
27
27
|
import { toKebab, buildParameterPath } from "@fjall/util";
|
|
28
|
-
import { validateEcsDomainConfig, validateSecretName } from "../../resources/aws/compute/ecsValidation.js";
|
|
28
|
+
import { validateEc2ServiceSizing, validateEcsDomainConfig, validateSecretName } from "../../resources/aws/compute/ecsValidation.js";
|
|
29
29
|
import { COMPUTE_DEFAULTS, collectImportedSecretNames } from "./compute.js";
|
|
30
30
|
import { isHookMigrations } from "./computeEcsTypes.js";
|
|
31
31
|
export { ScalingType } from "./computeEcsTypes.js";
|
|
@@ -150,6 +150,7 @@ export function validateEcsProps(props) {
|
|
|
150
150
|
FjallLogger.warn(`Service '${service.name}' has ec2Config but capacityProvider is not 'EC2'. ` +
|
|
151
151
|
"The ec2Config will be ignored unless capacityProvider is set to 'EC2'.");
|
|
152
152
|
}
|
|
153
|
+
validateEc2ServiceSizing(service);
|
|
153
154
|
if (service.deployment !== undefined) {
|
|
154
155
|
const min = service.deployment.minHealthyPercent;
|
|
155
156
|
const max = service.deployment.maxHealthyPercent;
|
|
@@ -214,6 +214,9 @@ export function addContainersToTask(ctx, serviceName, serviceProps, taskDefiniti
|
|
|
214
214
|
stopTimeout: containerConfig.stopTimeout !== undefined
|
|
215
215
|
? Duration.seconds(containerConfig.stopTimeout)
|
|
216
216
|
: undefined,
|
|
217
|
+
// EC2 container memory is ec2Config-only by design — do NOT add a
|
|
218
|
+
// `?? serviceProps.memoryLimitMiB` fallback (that is the silent-ignore
|
|
219
|
+
// footgun `validateEc2ServiceSizing` exists to reject).
|
|
217
220
|
...(isServiceEc2(serviceProps) && {
|
|
218
221
|
memoryLimitMiB: serviceProps.ec2Config?.memoryLimitMiB ??
|
|
219
222
|
DEFAULT_EC2_CONTAINER_MEMORY_MIB
|
|
@@ -102,7 +102,12 @@ export interface Ec2CapacityConfig {
|
|
|
102
102
|
minCapacity?: number;
|
|
103
103
|
/** Maximum number of instances. Default: 3 */
|
|
104
104
|
maxCapacity?: number;
|
|
105
|
-
/**
|
|
105
|
+
/**
|
|
106
|
+
* Memory limit in MiB for the container. Default: 1024. This is the ONLY
|
|
107
|
+
* memory knob for an EC2 service — the service-level `memoryLimitMiB` on
|
|
108
|
+
* `EcsServiceProps` is Fargate-only and is rejected at synth for EC2 capacity
|
|
109
|
+
* (see `validateEc2ServiceSizing`).
|
|
110
|
+
*/
|
|
106
111
|
memoryLimitMiB?: number;
|
|
107
112
|
/** Warm pool keeps stopped instances for faster start (10-15s vs 60-90s).
|
|
108
113
|
* Mirrors generator WarmPool type (generator/src/schemas/computeSchemas.ts). */
|
|
@@ -442,9 +447,19 @@ export interface EcsServiceProps {
|
|
|
442
447
|
* The first container with a port is the **primary container** (receives ALB traffic).
|
|
443
448
|
*/
|
|
444
449
|
containers: EcsClusterContainerConfig[];
|
|
445
|
-
/**
|
|
450
|
+
/**
|
|
451
|
+
* CPU units for this service's tasks (256-4096). Fargate only — EC2 task
|
|
452
|
+
* definitions carry no task-level CPU reservation (CPU is bounded by the
|
|
453
|
+
* instance type), so a value here is rejected at synth when `capacityProvider`
|
|
454
|
+
* is 'EC2' (see `validateEc2ServiceSizing`).
|
|
455
|
+
*/
|
|
446
456
|
cpu?: number;
|
|
447
|
-
/**
|
|
457
|
+
/**
|
|
458
|
+
* Memory in MiB for this service's tasks (512-30720). Fargate only — for EC2
|
|
459
|
+
* capacity the container memory comes from `ec2Config.memoryLimitMiB`; a
|
|
460
|
+
* service-level value here is rejected at synth unless it exactly mirrors
|
|
461
|
+
* that field (see `validateEc2ServiceSizing`).
|
|
462
|
+
*/
|
|
448
463
|
memoryLimitMiB?: number;
|
|
449
464
|
/** Desired number of tasks. Default: 2 */
|
|
450
465
|
desiredCount?: number;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { DomainConfig, EcsClusterProps } from "./ecsTypes.js";
|
|
1
|
+
import type { DomainConfig, EcsCapacityProvider, EcsClusterProps } from "./ecsTypes.js";
|
|
2
2
|
/**
|
|
3
3
|
* Validates ECS cluster props before construction.
|
|
4
4
|
* Pure function — does not depend on class state.
|
|
@@ -23,6 +23,60 @@ import type { DomainConfig, EcsClusterProps } from "./ecsTypes.js";
|
|
|
23
23
|
* @throws Error if validation fails
|
|
24
24
|
*/
|
|
25
25
|
export declare function validateEcsClusterProps(props: EcsClusterProps): void;
|
|
26
|
+
/**
|
|
27
|
+
* Minimal structural input for {@link validateEc2ServiceSizing} — exactly the
|
|
28
|
+
* fields the guard reads. Both the resources-layer `EcsServiceProps` and the
|
|
29
|
+
* patterns-layer `EcsServiceConfig` structurally satisfy it, so one guard serves
|
|
30
|
+
* both validation layers without coupling to either full service type
|
|
31
|
+
* (typescript-standards § "Helpers consume resolved state, not the dispatch enum":
|
|
32
|
+
* the parameter type is the fields the body actually reads, nothing wider).
|
|
33
|
+
*/
|
|
34
|
+
interface Ec2ServiceSizingInput {
|
|
35
|
+
name: string;
|
|
36
|
+
capacityProvider?: EcsCapacityProvider;
|
|
37
|
+
cpu?: number;
|
|
38
|
+
memoryLimitMiB?: number;
|
|
39
|
+
ec2Config?: {
|
|
40
|
+
memoryLimitMiB?: number;
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Rejects service-level task sizing that is silently ignored for EC2 capacity.
|
|
45
|
+
*
|
|
46
|
+
* For an EC2-capacity ECS service the container's hard memory limit is derived
|
|
47
|
+
* from `ec2Config.memoryLimitMiB` (default {@link DEFAULT_EC2_CONTAINER_MEMORY_MIB}),
|
|
48
|
+
* NOT from the service-level `memoryLimitMiB` — that field only feeds the
|
|
49
|
+
* Fargate task definition (`createTaskDefinition` in `ecsTaskDefinition.ts`: the
|
|
50
|
+
* EC2 branch passes neither `cpu` nor `memoryLimitMiB`; the container's memory
|
|
51
|
+
* comes solely from the `ec2Config.memoryLimitMiB ?? default` spread). A
|
|
52
|
+
* service-level `cpu` has no EC2 analogue at all — EC2 task definitions carry no
|
|
53
|
+
* task-level CPU reservation and `Ec2CapacityConfig` has no `cpu` field — so it
|
|
54
|
+
* is always dead on an EC2 service. Both fields read as "size the task to N"
|
|
55
|
+
* while the deployed container ignores the value.
|
|
56
|
+
*
|
|
57
|
+
* Fail loud at synth rather than silently honour the value: silently honouring
|
|
58
|
+
* memory would rewrite deployed container memory in BOTH directions — raise it
|
|
59
|
+
* past the EC2 instance's capacity (failure-to-place mid-rollout) or lower it
|
|
60
|
+
* into the service-level [512,1023] band below the 1024 EC2 default (fresh OOM).
|
|
61
|
+
* Rejecting changes nothing deployed; it converts two silent misconfigs into a
|
|
62
|
+
* synth error with a named cure.
|
|
63
|
+
*
|
|
64
|
+
* `both-set-and-equal` is allowed for memory — a config that mirrors
|
|
65
|
+
* `ec2Config.memoryLimitMiB` into the service-level field is self-consistent and
|
|
66
|
+
* the derived container memory is unchanged. `cpu` has no such carve-out (there
|
|
67
|
+
* is no `ec2Config.cpu` to agree with); it always throws on EC2.
|
|
68
|
+
*
|
|
69
|
+
* Called from BOTH validation layers — resources `validateEcsClusterProps` and
|
|
70
|
+
* patterns `validateEcsProps` — so the guard cannot drift between them
|
|
71
|
+
* (code-quality § "Coupled values: shared source at 2 occurrences"; mirrors the
|
|
72
|
+
* `validateEcsDomainConfig` shared-helper precedent below).
|
|
73
|
+
*
|
|
74
|
+
* @param service - The service props to validate (no-op unless EC2 capacity)
|
|
75
|
+
* @throws Error if an EC2 service carries an ignored service-level `cpu`, or a
|
|
76
|
+
* service-level `memoryLimitMiB` that is absent from / conflicts with
|
|
77
|
+
* `ec2Config.memoryLimitMiB`
|
|
78
|
+
*/
|
|
79
|
+
export declare function validateEc2ServiceSizing(service: Ec2ServiceSizingInput): void;
|
|
26
80
|
/**
|
|
27
81
|
* Domain-config constraints shared by the resources-layer hook
|
|
28
82
|
* (`validateEcsClusterProps`) and the patterns-layer mirror
|
|
@@ -52,3 +106,4 @@ export declare function validateSsmPathComponent(component: string, fieldName: s
|
|
|
52
106
|
* reference the fjall secrets tooling can never write.
|
|
53
107
|
*/
|
|
54
108
|
export declare function validateSecretName(name: string, context: string): void;
|
|
109
|
+
export {};
|
|
@@ -2,6 +2,7 @@ import { NetworkMode } from "aws-cdk-lib/aws-ecs";
|
|
|
2
2
|
import { evaluateBakeGuard } from "@fjall/util/docker";
|
|
3
3
|
import { toKebab, SSM_COMPONENT_PATTERN, SSM_COMPONENT_ERROR, SECRET_NAME_PATTERN, SECRET_NAME_ERROR } from "@fjall/util";
|
|
4
4
|
import { ScalingType } from "./ecsTypes.js";
|
|
5
|
+
import { DEFAULT_EC2_CONTAINER_MEMORY_MIB } from "./ecsConstants.js";
|
|
5
6
|
/**
|
|
6
7
|
* Validates ECS cluster props before construction.
|
|
7
8
|
* Pure function — does not depend on class state.
|
|
@@ -121,6 +122,7 @@ export function validateEcsClusterProps(props) {
|
|
|
121
122
|
throw new Error(`Service '${service.name}' uses EC2 capacity provider but no ec2Config is defined. ` +
|
|
122
123
|
"Provide ec2Config on the service.");
|
|
123
124
|
}
|
|
125
|
+
validateEc2ServiceSizing(service);
|
|
124
126
|
if (service.deployment !== undefined) {
|
|
125
127
|
const min = service.deployment.minHealthyPercent;
|
|
126
128
|
const max = service.deployment.maxHealthyPercent;
|
|
@@ -188,6 +190,70 @@ export function validateEcsClusterProps(props) {
|
|
|
188
190
|
}
|
|
189
191
|
}
|
|
190
192
|
}
|
|
193
|
+
/**
|
|
194
|
+
* Rejects service-level task sizing that is silently ignored for EC2 capacity.
|
|
195
|
+
*
|
|
196
|
+
* For an EC2-capacity ECS service the container's hard memory limit is derived
|
|
197
|
+
* from `ec2Config.memoryLimitMiB` (default {@link DEFAULT_EC2_CONTAINER_MEMORY_MIB}),
|
|
198
|
+
* NOT from the service-level `memoryLimitMiB` — that field only feeds the
|
|
199
|
+
* Fargate task definition (`createTaskDefinition` in `ecsTaskDefinition.ts`: the
|
|
200
|
+
* EC2 branch passes neither `cpu` nor `memoryLimitMiB`; the container's memory
|
|
201
|
+
* comes solely from the `ec2Config.memoryLimitMiB ?? default` spread). A
|
|
202
|
+
* service-level `cpu` has no EC2 analogue at all — EC2 task definitions carry no
|
|
203
|
+
* task-level CPU reservation and `Ec2CapacityConfig` has no `cpu` field — so it
|
|
204
|
+
* is always dead on an EC2 service. Both fields read as "size the task to N"
|
|
205
|
+
* while the deployed container ignores the value.
|
|
206
|
+
*
|
|
207
|
+
* Fail loud at synth rather than silently honour the value: silently honouring
|
|
208
|
+
* memory would rewrite deployed container memory in BOTH directions — raise it
|
|
209
|
+
* past the EC2 instance's capacity (failure-to-place mid-rollout) or lower it
|
|
210
|
+
* into the service-level [512,1023] band below the 1024 EC2 default (fresh OOM).
|
|
211
|
+
* Rejecting changes nothing deployed; it converts two silent misconfigs into a
|
|
212
|
+
* synth error with a named cure.
|
|
213
|
+
*
|
|
214
|
+
* `both-set-and-equal` is allowed for memory — a config that mirrors
|
|
215
|
+
* `ec2Config.memoryLimitMiB` into the service-level field is self-consistent and
|
|
216
|
+
* the derived container memory is unchanged. `cpu` has no such carve-out (there
|
|
217
|
+
* is no `ec2Config.cpu` to agree with); it always throws on EC2.
|
|
218
|
+
*
|
|
219
|
+
* Called from BOTH validation layers — resources `validateEcsClusterProps` and
|
|
220
|
+
* patterns `validateEcsProps` — so the guard cannot drift between them
|
|
221
|
+
* (code-quality § "Coupled values: shared source at 2 occurrences"; mirrors the
|
|
222
|
+
* `validateEcsDomainConfig` shared-helper precedent below).
|
|
223
|
+
*
|
|
224
|
+
* @param service - The service props to validate (no-op unless EC2 capacity)
|
|
225
|
+
* @throws Error if an EC2 service carries an ignored service-level `cpu`, or a
|
|
226
|
+
* service-level `memoryLimitMiB` that is absent from / conflicts with
|
|
227
|
+
* `ec2Config.memoryLimitMiB`
|
|
228
|
+
*/
|
|
229
|
+
export function validateEc2ServiceSizing(service) {
|
|
230
|
+
if (service.capacityProvider !== "EC2")
|
|
231
|
+
return;
|
|
232
|
+
if (service.memoryLimitMiB !== undefined) {
|
|
233
|
+
const ec2Memory = service.ec2Config?.memoryLimitMiB;
|
|
234
|
+
if (ec2Memory === undefined) {
|
|
235
|
+
throw new Error(`Service '${service.name}': service-level memoryLimitMiB (${service.memoryLimitMiB}) is ignored ` +
|
|
236
|
+
"for EC2 capacity — the container's hard memory limit comes from ec2Config.memoryLimitMiB " +
|
|
237
|
+
`(default ${DEFAULT_EC2_CONTAINER_MEMORY_MIB} MiB), not the service-level field. Set ` +
|
|
238
|
+
`ec2Config.memoryLimitMiB: ${service.memoryLimitMiB} to size the container, or remove the ` +
|
|
239
|
+
"service-level memoryLimitMiB. (Service-level memoryLimitMiB only applies to Fargate services.)");
|
|
240
|
+
}
|
|
241
|
+
if (ec2Memory !== service.memoryLimitMiB) {
|
|
242
|
+
throw new Error(`Service '${service.name}': conflicting memory limits — service-level memoryLimitMiB ` +
|
|
243
|
+
`(${service.memoryLimitMiB}) disagrees with ec2Config.memoryLimitMiB (${ec2Memory}). For EC2 ` +
|
|
244
|
+
"capacity only ec2Config.memoryLimitMiB sizes the container; the service-level value is ignored. " +
|
|
245
|
+
"Set both to the same value or remove the service-level memoryLimitMiB.");
|
|
246
|
+
}
|
|
247
|
+
// both-set-and-equal falls through untouched (allowed): the config is
|
|
248
|
+
// self-consistent and the derived container memory is unchanged.
|
|
249
|
+
}
|
|
250
|
+
if (service.cpu !== undefined) {
|
|
251
|
+
throw new Error(`Service '${service.name}': service-level cpu (${service.cpu}) is ignored for EC2 capacity — ` +
|
|
252
|
+
"EC2 task definitions carry no task-level CPU reservation (CPU is bounded by the instance type) " +
|
|
253
|
+
"and there is no ec2Config.cpu field. Remove the service-level cpu. " +
|
|
254
|
+
"(Service-level cpu only applies to Fargate services.)");
|
|
255
|
+
}
|
|
256
|
+
}
|
|
191
257
|
/**
|
|
192
258
|
* Domain-config constraints shared by the resources-layer hook
|
|
193
259
|
* (`validateEcsClusterProps`) and the patterns-layer mirror
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@fjall/components-infrastructure",
|
|
3
|
-
"version": "4.
|
|
3
|
+
"version": "4.2.0",
|
|
4
4
|
"license": "SEE LICENSE IN LICENSE",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -67,8 +67,8 @@
|
|
|
67
67
|
},
|
|
68
68
|
"dependencies": {
|
|
69
69
|
"@aws-sdk/client-organizations": "^3.1038.0",
|
|
70
|
-
"@fjall/generator": "^4.
|
|
71
|
-
"@fjall/util": "^4.
|
|
70
|
+
"@fjall/generator": "^4.2.0",
|
|
71
|
+
"@fjall/util": "^4.2.0",
|
|
72
72
|
"constructs": "^10.6.0"
|
|
73
73
|
},
|
|
74
74
|
"overrides": {
|
|
@@ -82,5 +82,5 @@
|
|
|
82
82
|
"engines": {
|
|
83
83
|
"node": ">=18.0.0"
|
|
84
84
|
},
|
|
85
|
-
"gitHead": "
|
|
85
|
+
"gitHead": "c06f7ad6b4b63a99cc8e2fcf4f216ff1c14748c6"
|
|
86
86
|
}
|