@fjall/components-infrastructure 2.21.0 → 2.22.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/lib/patterns/aws/compute.js +6 -0
- package/dist/lib/patterns/aws/computeBuildArgInference.d.ts +43 -0
- package/dist/lib/patterns/aws/computeBuildArgInference.js +73 -0
- package/dist/lib/patterns/aws/computeEcs.js +12 -0
- package/dist/lib/resources/aws/compute/ecsValidation.js +14 -0
- package/package.json +4 -4
|
@@ -2,6 +2,7 @@ import { Token } from "aws-cdk-lib";
|
|
|
2
2
|
import { Runtime } from "aws-cdk-lib/aws-lambda";
|
|
3
3
|
import { isCompute, isEcsCompute, isLambdaCompute, isEc2Compute } from "./interfaces/compute.js";
|
|
4
4
|
import { warnIfPropertiesIgnored } from "../../utils/validationLogger.js";
|
|
5
|
+
import { applyInferredPublicBuildArgs } from "./computeBuildArgInference.js";
|
|
5
6
|
import { DEFAULT_ECS_FALLBACK_IMAGE, DEFAULT_EC2_INSTANCE_TYPE } from "../../resources/aws/compute/ecsConstants.js";
|
|
6
7
|
// Import and re-export from per-pattern files
|
|
7
8
|
import { EcsCompute, ECS_CAPACITY_PROVIDER_CONFIG, getEcsCapacityProviderConfig, ScalingType, validateEcsProps, buildContainerConfigs, expandMigrationsSugar, resolveScalingConfig } from "./computeEcs.js";
|
|
@@ -163,6 +164,11 @@ export class ComputeFactory {
|
|
|
163
164
|
const clusterName = id;
|
|
164
165
|
const appName = computeProps.appName ?? app.getName();
|
|
165
166
|
for (const service of computeProps.services) {
|
|
167
|
+
// Promote public-prefixed declared-env vars into docker.buildArgs
|
|
168
|
+
// (with their values) BEFORE the manifest copy and before the
|
|
169
|
+
// synth-time bake-guard runs in the EcsCompute constructor below —
|
|
170
|
+
// a secret-shaped inferred value must be caught by that guard.
|
|
171
|
+
applyInferredPublicBuildArgs(service);
|
|
166
172
|
const manifestService = {
|
|
167
173
|
name: service.name,
|
|
168
174
|
clusterName
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Synth-time public build-arg inference (Phase 3, source 1).
|
|
3
|
+
*
|
|
4
|
+
* A Vite/Next service that lists `VITE_*` / `NEXT_PUBLIC_*` / `PUBLIC_*`
|
|
5
|
+
* variables in its container `environment` needs those values baked into the
|
|
6
|
+
* client bundle at BUILD time — container ENV cannot reach the client. Without
|
|
7
|
+
* inference the author has to duplicate every public var into `docker.buildArgs`
|
|
8
|
+
* by hand; a forgotten one silently ships an `undefined` to the browser.
|
|
9
|
+
*
|
|
10
|
+
* This helper promotes each public-prefixed declared-environment key into a
|
|
11
|
+
* `docker.buildArgs` entry, WRITING THROUGH its declared value (P3-D1) so it
|
|
12
|
+
* bakes even with no `.env` file present. It is a PROMOTE, not a move — the key
|
|
13
|
+
* legitimately stays a runtime env var too (harmless for client-public vars).
|
|
14
|
+
*
|
|
15
|
+
* Explicit `buildArgs` always win: an inferred key never overwrites an existing
|
|
16
|
+
* entry (which may carry an acknowledged public-but-sensitive object form).
|
|
17
|
+
*
|
|
18
|
+
* Pure — reads only the in-memory service config; no fs, no env reads. The
|
|
19
|
+
* returned `docker.buildArgs` is mutated in place on `service.docker` by the
|
|
20
|
+
* caller so the synth-time bake-guard (which reads `service.docker.buildArgs`)
|
|
21
|
+
* inspects the inferred values too.
|
|
22
|
+
*/
|
|
23
|
+
import type { DockerBuildArgValue } from "@fjall/util/manifest/schemas";
|
|
24
|
+
interface ServiceWithDocker {
|
|
25
|
+
containers?: ReadonlyArray<{
|
|
26
|
+
environment?: Record<string, string>;
|
|
27
|
+
}>;
|
|
28
|
+
docker?: {
|
|
29
|
+
buildArgs?: Record<string, DockerBuildArgValue>;
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Mutate `service.docker.buildArgs` in place to include every public-prefixed
|
|
34
|
+
* declared-environment key not already explicitly declared, writing through its
|
|
35
|
+
* declared value. No-op when the service has no `docker` config (nothing is
|
|
36
|
+
* built, so there are no build-args).
|
|
37
|
+
*
|
|
38
|
+
* Returns the (possibly mutated) `service.docker.buildArgs` for convenience;
|
|
39
|
+
* mutation is the load-bearing effect so the synth-time bake-guard sees the
|
|
40
|
+
* inferred values.
|
|
41
|
+
*/
|
|
42
|
+
export declare function applyInferredPublicBuildArgs(service: ServiceWithDocker): void;
|
|
43
|
+
export {};
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Synth-time public build-arg inference (Phase 3, source 1).
|
|
3
|
+
*
|
|
4
|
+
* A Vite/Next service that lists `VITE_*` / `NEXT_PUBLIC_*` / `PUBLIC_*`
|
|
5
|
+
* variables in its container `environment` needs those values baked into the
|
|
6
|
+
* client bundle at BUILD time — container ENV cannot reach the client. Without
|
|
7
|
+
* inference the author has to duplicate every public var into `docker.buildArgs`
|
|
8
|
+
* by hand; a forgotten one silently ships an `undefined` to the browser.
|
|
9
|
+
*
|
|
10
|
+
* This helper promotes each public-prefixed declared-environment key into a
|
|
11
|
+
* `docker.buildArgs` entry, WRITING THROUGH its declared value (P3-D1) so it
|
|
12
|
+
* bakes even with no `.env` file present. It is a PROMOTE, not a move — the key
|
|
13
|
+
* legitimately stays a runtime env var too (harmless for client-public vars).
|
|
14
|
+
*
|
|
15
|
+
* Explicit `buildArgs` always win: an inferred key never overwrites an existing
|
|
16
|
+
* entry (which may carry an acknowledged public-but-sensitive object form).
|
|
17
|
+
*
|
|
18
|
+
* Pure — reads only the in-memory service config; no fs, no env reads. The
|
|
19
|
+
* returned `docker.buildArgs` is mutated in place on `service.docker` by the
|
|
20
|
+
* caller so the synth-time bake-guard (which reads `service.docker.buildArgs`)
|
|
21
|
+
* inspects the inferred values too.
|
|
22
|
+
*/
|
|
23
|
+
import { inferPublicBuildArgKeys } from "@fjall/util/docker";
|
|
24
|
+
/**
|
|
25
|
+
* Aggregate the declared environment a service exposes across all its
|
|
26
|
+
* containers. Later containers win on key collision — the union is only used to
|
|
27
|
+
* discover public-prefixed KEYS and their values for write-through, so collision
|
|
28
|
+
* order is not load-bearing (a public var with two different values across
|
|
29
|
+
* containers is already an authoring error).
|
|
30
|
+
*/
|
|
31
|
+
function aggregateDeclaredEnv(service) {
|
|
32
|
+
const merged = {};
|
|
33
|
+
for (const container of service.containers ?? []) {
|
|
34
|
+
if (container.environment === undefined)
|
|
35
|
+
continue;
|
|
36
|
+
for (const [key, value] of Object.entries(container.environment)) {
|
|
37
|
+
merged[key] = value;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
return merged;
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Mutate `service.docker.buildArgs` in place to include every public-prefixed
|
|
44
|
+
* declared-environment key not already explicitly declared, writing through its
|
|
45
|
+
* declared value. No-op when the service has no `docker` config (nothing is
|
|
46
|
+
* built, so there are no build-args).
|
|
47
|
+
*
|
|
48
|
+
* Returns the (possibly mutated) `service.docker.buildArgs` for convenience;
|
|
49
|
+
* mutation is the load-bearing effect so the synth-time bake-guard sees the
|
|
50
|
+
* inferred values.
|
|
51
|
+
*/
|
|
52
|
+
export function applyInferredPublicBuildArgs(service) {
|
|
53
|
+
if (service.docker === undefined)
|
|
54
|
+
return;
|
|
55
|
+
const declaredEnv = aggregateDeclaredEnv(service);
|
|
56
|
+
const explicitBuildArgs = service.docker.buildArgs;
|
|
57
|
+
const inferredKeys = inferPublicBuildArgKeys({
|
|
58
|
+
declaredEnv,
|
|
59
|
+
...(explicitBuildArgs !== undefined && { explicitBuildArgs })
|
|
60
|
+
});
|
|
61
|
+
if (inferredKeys.length === 0)
|
|
62
|
+
return;
|
|
63
|
+
const merged = {
|
|
64
|
+
...(explicitBuildArgs ?? {})
|
|
65
|
+
};
|
|
66
|
+
for (const key of inferredKeys) {
|
|
67
|
+
// P3-D1: write through the declared value so it bakes even without a .env
|
|
68
|
+
// file. inferPublicBuildArgKeys already excluded explicit keys, so this
|
|
69
|
+
// never overwrites an explicit (possibly acknowledged) entry.
|
|
70
|
+
merged[key] = declaredEnv[key];
|
|
71
|
+
}
|
|
72
|
+
service.docker.buildArgs = merged;
|
|
73
|
+
}
|
|
@@ -22,6 +22,8 @@ import { vpcHasNatGateways } from "../../utils/vpcUtils.js";
|
|
|
22
22
|
import { toPascalCase } from "../../utils/capitaliseString.js";
|
|
23
23
|
import { FjallLogger } from "../../utils/validationLogger.js";
|
|
24
24
|
import { VALIDATION_PATTERNS } from "@fjall/generator";
|
|
25
|
+
import { evaluateBakeGuard } from "@fjall/util/docker";
|
|
26
|
+
import { toKebab } from "@fjall/util";
|
|
25
27
|
import { COMPUTE_DEFAULTS, collectImportedSecretNames } from "./compute.js";
|
|
26
28
|
import { isHookMigrations } from "./computeEcsTypes.js";
|
|
27
29
|
export { ScalingType } from "./computeEcsTypes.js";
|
|
@@ -93,6 +95,16 @@ export function validateEcsProps(props) {
|
|
|
93
95
|
throw new Error(`Invalid service names: ${invalidNames.map((s) => s.name).join(", ")}. ` +
|
|
94
96
|
"Service names must start with a letter and contain only letters, numbers, and hyphens.");
|
|
95
97
|
}
|
|
98
|
+
// Reject secret-shaped buildArgs at synth time (mirror of the resources-layer
|
|
99
|
+
// validateEcsClusterProps check — direct `new EcsCluster(...)` consumers hit
|
|
100
|
+
// that one, EcsCompute/ComputeFactory consumers hit this one).
|
|
101
|
+
const appPrefix = props.appName !== undefined ? toKebab(props.appName) : "";
|
|
102
|
+
for (const service of props.services) {
|
|
103
|
+
const findings = evaluateBakeGuard(service.docker?.buildArgs, appPrefix).findings;
|
|
104
|
+
if (findings.length > 0) {
|
|
105
|
+
throw new Error(`Service '${service.name}': ${findings.map((f) => f.message).join(" ")}`);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
96
108
|
// Validate routing when multiple services have ports
|
|
97
109
|
const servicesWithPorts = props.services.filter((s) => s.containers?.some((c) => c.port));
|
|
98
110
|
if (servicesWithPorts.length > 1) {
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import { NetworkMode } from "aws-cdk-lib/aws-ecs";
|
|
2
|
+
import { evaluateBakeGuard } from "@fjall/util/docker";
|
|
3
|
+
import { toKebab } from "@fjall/util";
|
|
2
4
|
import { ScalingType } from "./ecsTypes.js";
|
|
3
5
|
/**
|
|
4
6
|
* Validates ECS cluster props before construction.
|
|
@@ -36,6 +38,18 @@ export function validateEcsClusterProps(props) {
|
|
|
36
38
|
if (duplicateServices.length > 0) {
|
|
37
39
|
throw new Error(`Duplicate service names: ${[...new Set(duplicateServices)].join(", ")}`);
|
|
38
40
|
}
|
|
41
|
+
// Reject secret-shaped buildArgs at synth time — a credential baked into a
|
|
42
|
+
// build arg lands in the image layer / docker history / provenance and the
|
|
43
|
+
// shared cache repo, where it survives long after the build. Fails the synth
|
|
44
|
+
// rather than deferring to the pre-build bake-guard, so a bad manifest never
|
|
45
|
+
// reaches a deploy. appName scopes the masking heuristic's prefix warnings.
|
|
46
|
+
const appPrefix = props.appName !== undefined ? toKebab(props.appName) : "";
|
|
47
|
+
for (const service of props.services) {
|
|
48
|
+
const findings = evaluateBakeGuard(service.docker?.buildArgs, appPrefix).findings;
|
|
49
|
+
if (findings.length > 0) {
|
|
50
|
+
throw new Error(`Service '${service.name}': ${findings.map((f) => f.message).join(" ")}`);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
39
53
|
// Validate routing when multiple services have ports
|
|
40
54
|
const servicesWithPorts = props.services.filter((s) => s.containers.some((c) => c.port !== undefined));
|
|
41
55
|
if (servicesWithPorts.length > 1 && !loadBalancerDisabled) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@fjall/components-infrastructure",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.22.1",
|
|
4
4
|
"license": "SEE LICENSE IN LICENSE",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -63,8 +63,8 @@
|
|
|
63
63
|
},
|
|
64
64
|
"dependencies": {
|
|
65
65
|
"@aws-sdk/client-organizations": "^3.1038.0",
|
|
66
|
-
"@fjall/generator": "^2.
|
|
67
|
-
"@fjall/util": "^2.
|
|
66
|
+
"@fjall/generator": "^2.22.1",
|
|
67
|
+
"@fjall/util": "^2.22.1",
|
|
68
68
|
"constructs": "^10.6.0"
|
|
69
69
|
},
|
|
70
70
|
"overrides": {
|
|
@@ -78,5 +78,5 @@
|
|
|
78
78
|
"engines": {
|
|
79
79
|
"node": ">=18.0.0"
|
|
80
80
|
},
|
|
81
|
-
"gitHead": "
|
|
81
|
+
"gitHead": "ec04f1739f19e4b5212899df7f733ee0d1f06ec3"
|
|
82
82
|
}
|