@fjall/components-infrastructure 2.30.0 → 2.31.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/config/aws/oidcConnector.js +11 -3
- package/dist/lib/lambda-assets/cert-generator/asset/index.js +12 -3
- package/dist/lib/patterns/aws/compute.d.ts +2 -2
- package/dist/lib/patterns/aws/compute.js +26 -8
- package/dist/lib/patterns/aws/computeLambda.d.ts +53 -4
- package/dist/lib/patterns/aws/computeLambda.js +45 -2
- package/dist/lib/patterns/aws/computePropApplicability.d.ts +20 -0
- package/dist/lib/patterns/aws/computePropApplicability.js +87 -0
- package/dist/lib/resources/aws/compute/ecsImages.js +8 -25
- package/dist/lib/resources/aws/compute/imageTagParameter.d.ts +29 -0
- package/dist/lib/resources/aws/compute/imageTagParameter.js +62 -0
- package/dist/lib/resources/aws/compute/lambda.d.ts +1 -1
- package/dist/lib/resources/aws/compute/lambda.js +6 -4
- package/dist/lib/resources/aws/iam/identityCenter/user.js +5 -1
- package/dist/lib/resources/aws/logging/index.d.ts +1 -0
- package/dist/lib/resources/aws/logging/index.js +1 -0
- package/dist/lib/resources/aws/logging/logGroup.d.ts +7 -0
- package/dist/lib/resources/aws/logging/logGroup.js +7 -0
- package/dist/lib/resources/aws/logging/singletonLogGroup.d.ts +15 -0
- package/dist/lib/resources/aws/logging/singletonLogGroup.js +24 -0
- package/dist/lib/resources/aws/networking/vpcPeeringConnection.js +2 -1
- package/dist/lib/resources/aws/storage/bucketDeployment.d.ts +5 -6
- package/dist/lib/resources/aws/storage/bucketDeployment.js +14 -7
- package/dist/lib/resources/aws/utilities/awsCustomResource.js +5 -0
- package/dist/lib/resources/aws/utilities/customResource.js +5 -1
- package/dist/lib/utils/validationLogger.d.ts +2 -2
- package/dist/lib/utils/validationLogger.js +4 -4
- package/package.json +4 -4
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { DEV_BOUNDARY_POLICY_NAME, DEV_DEPLOY_POLICY_NAME, DEV_PROVISIONER_POLICY_NAME, DEV_SYNC_WRITER_POLICY_NAME, FJALL_OIDC_AUDIENCE, FJALL_OIDC_ISSUER_DOMAIN, FJALL_OIDC_PLACEHOLDER_THUMBPRINT, SECURITY_TIER_TO_BOUNDARY, buildDevBoundaryStatements, buildDevDeployPolicyStatements, buildDevDeployTrustStatements, buildDevProvisionerPolicyStatements, buildDevProvisionerTrustStatements, buildDevSyncWriterPolicyStatements, buildDevSyncWriterTrustStatements, buildNarrowedDeployTrustStatements, deployBoundaryName, devDeployRoleName, devProvisionerRoleName, devSyncWriterRoleName } from "@fjall/generator";
|
|
2
|
-
import { CfnOutput, Duration, Stack } from "aws-cdk-lib";
|
|
2
|
+
import { Aws, CfnOutput, Duration, Stack } from "aws-cdk-lib";
|
|
3
3
|
import * as iam from "aws-cdk-lib/aws-iam";
|
|
4
4
|
import { Runtime } from "aws-cdk-lib/aws-lambda";
|
|
5
5
|
import { Construct } from "constructs";
|
|
@@ -80,15 +80,23 @@ export class OidcConnector extends Construct {
|
|
|
80
80
|
resourceType: "Custom::FjallOidcProvider",
|
|
81
81
|
properties: { IssuerUrl: issuerUrl },
|
|
82
82
|
inlinePolicy: [
|
|
83
|
+
// iam:ListOpenIDConnectProviders supports no resource-level
|
|
84
|
+
// permission — the star grants provider-list metadata only.
|
|
85
|
+
new iam.PolicyStatement({
|
|
86
|
+
effect: iam.Effect.ALLOW,
|
|
87
|
+
actions: ["iam:ListOpenIDConnectProviders"],
|
|
88
|
+
resources: ["*"]
|
|
89
|
+
}),
|
|
83
90
|
new iam.PolicyStatement({
|
|
84
91
|
effect: iam.Effect.ALLOW,
|
|
85
92
|
actions: [
|
|
86
|
-
"iam:ListOpenIDConnectProviders",
|
|
87
93
|
"iam:GetOpenIDConnectProvider",
|
|
88
94
|
"iam:CreateOpenIDConnectProvider",
|
|
89
95
|
"iam:AddClientIDToOpenIDConnectProvider"
|
|
90
96
|
],
|
|
91
|
-
resources: [
|
|
97
|
+
resources: [
|
|
98
|
+
`arn:${Aws.PARTITION}:iam::${Aws.ACCOUNT_ID}:oidc-provider/${issuerDomain}`
|
|
99
|
+
]
|
|
92
100
|
})
|
|
93
101
|
],
|
|
94
102
|
inlineCode: OIDC_PROVIDER_HANDLER
|
|
@@ -3,11 +3,20 @@ var __defProp = Object.defineProperty;
|
|
|
3
3
|
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
4
|
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
5
|
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
-
var __esm = (fn, res) => function __init() {
|
|
7
|
-
|
|
6
|
+
var __esm = (fn, res, err) => function __init() {
|
|
7
|
+
if (err) throw err[0];
|
|
8
|
+
try {
|
|
9
|
+
return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
|
|
10
|
+
} catch (e) {
|
|
11
|
+
throw err = [e], e;
|
|
12
|
+
}
|
|
8
13
|
};
|
|
9
14
|
var __commonJS = (cb, mod) => function __require() {
|
|
10
|
-
|
|
15
|
+
try {
|
|
16
|
+
return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports;
|
|
17
|
+
} catch (e) {
|
|
18
|
+
throw mod = 0, e;
|
|
19
|
+
}
|
|
11
20
|
};
|
|
12
21
|
var __export = (target, all) => {
|
|
13
22
|
for (var name in all)
|
|
@@ -4,9 +4,9 @@ import { type SecretImport } from "../../resources/aws/secrets/index.js";
|
|
|
4
4
|
import { type ComputeType, type IEcsCompute, type ILambdaCompute, type IEc2Compute, type AnyCompute, isCompute, isEcsCompute, isLambdaCompute, isEc2Compute } from "./interfaces/compute.js";
|
|
5
5
|
import type App from "../../app.js";
|
|
6
6
|
import { EcsCompute, type EcsComputeProps, type EcsServiceConfig, type ServiceLogAlarm, type EcsContainerConfig, type EcsScalingConfig, type EcsClusterConfig, type EcsRoutingConfig, type EcsCapacityProviderConfig, type ContainerDependency, type EcsMigrationsConfig, ECS_CAPACITY_PROVIDER_CONFIG, getEcsCapacityProviderConfig, ScalingType, type EcsCapacityProvider, type Ec2CapacityConfig, validateEcsProps, buildContainerConfigs, expandMigrationsSugar, type ResolvedScalingConfig, resolveScalingConfig } from "./computeEcs.js";
|
|
7
|
-
import { LambdaCompute, type LambdaComputeProps, type ContainerLambdaProps, type CodeLambdaProps, type FunctionUrlConfig, type ResolvedLambdaDeployment, resolveLambdaDeployment, Architecture, HttpMethod, InvokeMode, type FunctionUrlCorsOptions } from "./computeLambda.js";
|
|
7
|
+
import { LambdaCompute, type LambdaComputeProps, type ContainerLambdaProps, type CodeLambdaProps, type FunctionUrlConfig, type ResolvedLambdaDeployment, resolveLambdaDeployment, lambdaImageKey, Architecture, HttpMethod, InvokeMode, type FunctionUrlCorsOptions } from "./computeLambda.js";
|
|
8
8
|
import { Ec2Compute, type Ec2ComputeProps, type SshConfig } from "./computeEc2.js";
|
|
9
|
-
export { EcsCompute, type EcsComputeProps, type EcsServiceConfig, type ServiceLogAlarm, type EcsContainerConfig, type EcsScalingConfig, type EcsClusterConfig, type EcsRoutingConfig, type EcsCapacityProviderConfig, ECS_CAPACITY_PROVIDER_CONFIG, getEcsCapacityProviderConfig, ScalingType, type EcsCapacityProvider, type Ec2CapacityConfig, type ContainerDependency, type EcsMigrationsConfig, validateEcsProps, buildContainerConfigs, expandMigrationsSugar, type ResolvedScalingConfig, resolveScalingConfig, LambdaCompute, type LambdaComputeProps, type ContainerLambdaProps, type CodeLambdaProps, type FunctionUrlConfig, type ResolvedLambdaDeployment, resolveLambdaDeployment, Architecture, HttpMethod, InvokeMode, type FunctionUrlCorsOptions, Ec2Compute, type Ec2ComputeProps, type SshConfig };
|
|
9
|
+
export { EcsCompute, type EcsComputeProps, type EcsServiceConfig, type ServiceLogAlarm, type EcsContainerConfig, type EcsScalingConfig, type EcsClusterConfig, type EcsRoutingConfig, type EcsCapacityProviderConfig, ECS_CAPACITY_PROVIDER_CONFIG, getEcsCapacityProviderConfig, ScalingType, type EcsCapacityProvider, type Ec2CapacityConfig, type ContainerDependency, type EcsMigrationsConfig, validateEcsProps, buildContainerConfigs, expandMigrationsSugar, type ResolvedScalingConfig, resolveScalingConfig, LambdaCompute, type LambdaComputeProps, type ContainerLambdaProps, type CodeLambdaProps, type FunctionUrlConfig, type ResolvedLambdaDeployment, resolveLambdaDeployment, lambdaImageKey, Architecture, HttpMethod, InvokeMode, type FunctionUrlCorsOptions, Ec2Compute, type Ec2ComputeProps, type SshConfig };
|
|
10
10
|
export type { ComputeType } from "./interfaces/compute.js";
|
|
11
11
|
/**
|
|
12
12
|
* Configuration defaults for each compute type.
|
|
@@ -3,18 +3,19 @@ import { Runtime } from "aws-cdk-lib/aws-lambda";
|
|
|
3
3
|
import { buildParameterPath } from "@fjall/util";
|
|
4
4
|
import { isCompute, isEcsCompute, isLambdaCompute, isEc2Compute } from "./interfaces/compute.js";
|
|
5
5
|
import { warnIfPropertiesIgnored } from "../../utils/validationLogger.js";
|
|
6
|
+
import { COMPUTE_WARN_GROUPS, CONTAINER_LAMBDA_ONLY_PROPS, CODE_LAMBDA_ONLY_PROPS } from "./computePropApplicability.js";
|
|
6
7
|
import { applyInferredPublicBuildArgs } from "./computeBuildArgInference.js";
|
|
7
8
|
import { DEFAULT_ECS_FALLBACK_IMAGE, DEFAULT_EC2_INSTANCE_TYPE } from "../../resources/aws/compute/ecsConstants.js";
|
|
8
9
|
// Import and re-export from per-pattern files
|
|
9
10
|
import { EcsCompute, ECS_CAPACITY_PROVIDER_CONFIG, getEcsCapacityProviderConfig, ScalingType, validateEcsProps, buildContainerConfigs, expandMigrationsSugar, resolveScalingConfig } from "./computeEcs.js";
|
|
10
|
-
import { LambdaCompute, resolveLambdaDeployment, Architecture, HttpMethod, InvokeMode } from "./computeLambda.js";
|
|
11
|
+
import { LambdaCompute, resolveLambdaDeployment, lambdaImageKey, Architecture, HttpMethod, InvokeMode } from "./computeLambda.js";
|
|
11
12
|
import { Ec2Compute } from "./computeEc2.js";
|
|
12
13
|
// Re-export everything from per-pattern files
|
|
13
14
|
export {
|
|
14
15
|
// ECS
|
|
15
16
|
EcsCompute, ECS_CAPACITY_PROVIDER_CONFIG, getEcsCapacityProviderConfig, ScalingType, validateEcsProps, buildContainerConfigs, expandMigrationsSugar, resolveScalingConfig,
|
|
16
17
|
// Lambda
|
|
17
|
-
LambdaCompute, resolveLambdaDeployment, Architecture, HttpMethod, InvokeMode,
|
|
18
|
+
LambdaCompute, resolveLambdaDeployment, lambdaImageKey, Architecture, HttpMethod, InvokeMode,
|
|
18
19
|
// EC2
|
|
19
20
|
Ec2Compute };
|
|
20
21
|
export const COMPUTE_TYPE_CONFIG = {
|
|
@@ -90,12 +91,20 @@ export function getComputeTypeConfig(type) {
|
|
|
90
91
|
* TypeScript's compile-time checks may not apply.
|
|
91
92
|
*/
|
|
92
93
|
function validateComputeProps(props) {
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
//
|
|
98
|
-
|
|
94
|
+
for (const group of COMPUTE_WARN_GROUPS) {
|
|
95
|
+
warnIfPropertiesIgnored(props, group.properties, group.applicableTypes, group.typeName);
|
|
96
|
+
}
|
|
97
|
+
// capacityProvider/ec2Config are EcsServiceConfig-level keys, not top-level
|
|
98
|
+
// EcsComputeProps, so the guard Record in computePropApplicability.ts would
|
|
99
|
+
// reject them — hand-curated misuse hints stay outside it.
|
|
100
|
+
warnIfPropertiesIgnored(props, ["capacityProvider", "ec2Config"], "ecs", "ECS compute");
|
|
101
|
+
if (props.type === "lambda") {
|
|
102
|
+
// warnIfPropertyIgnored reads only `.type` + presence, so a
|
|
103
|
+
// deployment-keyed view reuses the same machinery for the second axis.
|
|
104
|
+
const byDeployment = { ...props, type: props.deployment };
|
|
105
|
+
warnIfPropertiesIgnored(byDeployment, CONTAINER_LAMBDA_ONLY_PROPS, "container", "container-deployment Lambda", "deployment");
|
|
106
|
+
warnIfPropertiesIgnored(byDeployment, CODE_LAMBDA_ONLY_PROPS, "code", "code-deployment Lambda", "deployment");
|
|
107
|
+
}
|
|
99
108
|
// ECS-specific validation - type narrowing handled by discriminated union
|
|
100
109
|
if (props.type === "ecs") {
|
|
101
110
|
validateEcsProps(props);
|
|
@@ -221,6 +230,10 @@ export class ComputeFactory {
|
|
|
221
230
|
...computeProps,
|
|
222
231
|
appName: app.getName()
|
|
223
232
|
};
|
|
233
|
+
// `ecrRepository` is already defaulted to the app's shared default
|
|
234
|
+
// container registry above (line ~323) when omitted — the same
|
|
235
|
+
// repo ECS services push into — so a container Lambda always has
|
|
236
|
+
// one by this point, whether or not `docker` is set.
|
|
224
237
|
// Always register Lambda with manifest collector for CLI discovery
|
|
225
238
|
// This ensures all Lambdas are discoverable for secrets management,
|
|
226
239
|
// even those that don't currently have secrets configured
|
|
@@ -242,6 +255,11 @@ export class ComputeFactory {
|
|
|
242
255
|
if (lambdaImportedSecretNames.size > 0) {
|
|
243
256
|
manifestLambda.importedSecretNames = Array.from(lambdaImportedSecretNames);
|
|
244
257
|
}
|
|
258
|
+
if (lambdaComputeProps.deployment === "container" &&
|
|
259
|
+
lambdaComputeProps.docker !== undefined) {
|
|
260
|
+
manifestLambda.docker = lambdaComputeProps.docker;
|
|
261
|
+
manifestLambda.imageKey = lambdaImageKey(lambdaComputeProps, id);
|
|
262
|
+
}
|
|
245
263
|
collector.addLambda(manifestLambda);
|
|
246
264
|
return new LambdaCompute(scope, id, lambdaComputeProps);
|
|
247
265
|
}
|
|
@@ -8,6 +8,7 @@ import { type ILambdaCompute } from "./interfaces/compute.js";
|
|
|
8
8
|
import { type ConnectionSpec } from "./interfaces/connector.js";
|
|
9
9
|
import { LambdaFunction } from "../../resources/aws/compute/lambda.js";
|
|
10
10
|
import { type SecretImport } from "../../resources/aws/secrets/index.js";
|
|
11
|
+
import { type DockerBuild } from "@fjall/util/manifest/schemas";
|
|
11
12
|
export { Architecture, HttpMethod, InvokeMode, type FunctionUrlCorsOptions } from "aws-cdk-lib/aws-lambda";
|
|
12
13
|
/**
|
|
13
14
|
* Lambda function URL configuration.
|
|
@@ -114,17 +115,47 @@ interface BaseLambdaProps {
|
|
|
114
115
|
* automatically set to FROM_IMAGE.
|
|
115
116
|
*
|
|
116
117
|
* @example
|
|
118
|
+
* // Bring your own image (built/pushed out of band)
|
|
117
119
|
* app.addCompute(ComputeFactory.build("ImageLambda", {
|
|
118
120
|
* type: "lambda",
|
|
119
121
|
* deployment: "container",
|
|
120
122
|
* ecrRepository: app.getDefaultContainerRegistry()
|
|
121
123
|
* }));
|
|
124
|
+
*
|
|
125
|
+
* @example
|
|
126
|
+
* // fjall builds and pushes the image on every `fjall deploy`
|
|
127
|
+
* app.addCompute(ComputeFactory.build("ImageLambda", {
|
|
128
|
+
* type: "lambda",
|
|
129
|
+
* deployment: "container",
|
|
130
|
+
* docker: { path: "lambda.Dockerfile" }
|
|
131
|
+
* }));
|
|
122
132
|
*/
|
|
123
133
|
export interface ContainerLambdaProps extends BaseLambdaProps {
|
|
124
134
|
/** Container-based deployment using ECR image */
|
|
125
135
|
deployment: "container";
|
|
126
|
-
/**
|
|
127
|
-
|
|
136
|
+
/**
|
|
137
|
+
* ECR repository containing the Lambda container image. Optional when
|
|
138
|
+
* `docker` is set — defaults to the app's shared default container
|
|
139
|
+
* registry (the same repo ECS services push into).
|
|
140
|
+
*/
|
|
141
|
+
ecrRepository?: Repository | RepositoryImage;
|
|
142
|
+
/**
|
|
143
|
+
* When set, `fjall deploy` builds and pushes this Dockerfile into the app's
|
|
144
|
+
* shared default container registry, then wires the function to the
|
|
145
|
+
* resulting content-hash-tagged image — the same mechanism ECS services
|
|
146
|
+
* already use. Omit to keep managing the image out of band (the previous
|
|
147
|
+
* behaviour: the function tracks a moving `:latest` tag in `ecrRepository`).
|
|
148
|
+
*/
|
|
149
|
+
docker?: DockerBuild;
|
|
150
|
+
/**
|
|
151
|
+
* Shared build/tag key for Lambda functions that intentionally run the
|
|
152
|
+
* same container image (e.g. several handlers packaged from one
|
|
153
|
+
* Dockerfile). Functions sharing one `image` value build once and share
|
|
154
|
+
* one content-hash tag. Defaults to this Lambda's own construct id, so
|
|
155
|
+
* omitting it is safe when no sharing is intended. Only meaningful when
|
|
156
|
+
* `docker` is set.
|
|
157
|
+
*/
|
|
158
|
+
image?: string;
|
|
128
159
|
}
|
|
129
160
|
/**
|
|
130
161
|
* Code-based Lambda using inline code or S3.
|
|
@@ -154,7 +185,9 @@ export interface CodeLambdaProps extends BaseLambdaProps {
|
|
|
154
185
|
* Lambda compute configuration.
|
|
155
186
|
*
|
|
156
187
|
* Discriminated union ensuring type-safe Lambda configuration:
|
|
157
|
-
* - `deployment: "container"`
|
|
188
|
+
* - `deployment: "container"` takes `ecrRepository` (bring-your-own image)
|
|
189
|
+
* and/or `docker` (fjall builds and pushes; `ecrRepository` then defaults
|
|
190
|
+
* to the app's shared default container registry)
|
|
158
191
|
* - `deployment: "code"` requires `code` and allows `handler`/`runtime`
|
|
159
192
|
*
|
|
160
193
|
* @example
|
|
@@ -174,11 +207,27 @@ export interface ResolvedLambdaDeployment {
|
|
|
174
207
|
handler: string;
|
|
175
208
|
runtime: Runtime;
|
|
176
209
|
}
|
|
210
|
+
/**
|
|
211
|
+
* Shared derivation of a container Lambda's image key. Consumed by both the
|
|
212
|
+
* synth-side CFN image-tag parameter (below) and the manifest `imageKey`
|
|
213
|
+
* (compute.ts) — `fjall deploy` matches manifest imageKey → built image tag →
|
|
214
|
+
* CFN parameter name, so the two derivations must stay byte-identical.
|
|
215
|
+
*
|
|
216
|
+
* An explicit empty-string `image` is treated as absent (falls back to `id`):
|
|
217
|
+
* `??` alone would let `image: ""` through as an empty build/tag key.
|
|
218
|
+
*/
|
|
219
|
+
export declare function lambdaImageKey(props: {
|
|
220
|
+
image?: string;
|
|
221
|
+
}, id: string): string;
|
|
177
222
|
/**
|
|
178
223
|
* Resolve Lambda deployment configuration from props.
|
|
179
224
|
* Handles container vs code deployment types.
|
|
225
|
+
*
|
|
226
|
+
* `scope`/`id` are only used on the container-with-`docker` path, to create
|
|
227
|
+
* (or reuse) the per-key `CfnParameter` that `fjall deploy` overrides with
|
|
228
|
+
* the content-hash tag after building and pushing the image.
|
|
180
229
|
*/
|
|
181
|
-
export declare function resolveLambdaDeployment(props: LambdaComputeProps): ResolvedLambdaDeployment;
|
|
230
|
+
export declare function resolveLambdaDeployment(scope: Construct, id: string, props: LambdaComputeProps): ResolvedLambdaDeployment;
|
|
182
231
|
/**
|
|
183
232
|
* Lambda compute wrapper implementing ILambdaCompute.
|
|
184
233
|
* Provides type-safe access to Lambda-specific resources.
|
|
@@ -4,18 +4,61 @@ import { Repository } from "aws-cdk-lib/aws-ecr";
|
|
|
4
4
|
import { Construct } from "constructs";
|
|
5
5
|
import { processConnections } from "../../utils/connections.js";
|
|
6
6
|
import { LambdaFunction } from "../../resources/aws/compute/lambda.js";
|
|
7
|
+
import { getOrCreateImageTagParameter } from "../../resources/aws/compute/imageTagParameter.js";
|
|
7
8
|
import { COMPUTE_DEFAULTS } from "./compute.js";
|
|
9
|
+
import { evaluateBakeGuard } from "@fjall/util/docker";
|
|
10
|
+
import { toKebab } from "@fjall/util";
|
|
8
11
|
// Re-export Lambda types from CDK for user convenience
|
|
9
12
|
export { Architecture, HttpMethod, InvokeMode } from "aws-cdk-lib/aws-lambda";
|
|
13
|
+
/**
|
|
14
|
+
* Shared derivation of a container Lambda's image key. Consumed by both the
|
|
15
|
+
* synth-side CFN image-tag parameter (below) and the manifest `imageKey`
|
|
16
|
+
* (compute.ts) — `fjall deploy` matches manifest imageKey → built image tag →
|
|
17
|
+
* CFN parameter name, so the two derivations must stay byte-identical.
|
|
18
|
+
*
|
|
19
|
+
* An explicit empty-string `image` is treated as absent (falls back to `id`):
|
|
20
|
+
* `??` alone would let `image: ""` through as an empty build/tag key.
|
|
21
|
+
*/
|
|
22
|
+
export function lambdaImageKey(props, id) {
|
|
23
|
+
return props.image !== undefined && props.image !== "" ? props.image : id;
|
|
24
|
+
}
|
|
10
25
|
/**
|
|
11
26
|
* Resolve Lambda deployment configuration from props.
|
|
12
27
|
* Handles container vs code deployment types.
|
|
28
|
+
*
|
|
29
|
+
* `scope`/`id` are only used on the container-with-`docker` path, to create
|
|
30
|
+
* (or reuse) the per-key `CfnParameter` that `fjall deploy` overrides with
|
|
31
|
+
* the content-hash tag after building and pushing the image.
|
|
13
32
|
*/
|
|
14
|
-
export function resolveLambdaDeployment(props) {
|
|
33
|
+
export function resolveLambdaDeployment(scope, id, props) {
|
|
15
34
|
if (props.deployment === "container") {
|
|
35
|
+
if (!props.ecrRepository) {
|
|
36
|
+
throw new Error(`Lambda '${id}': ecrRepository is required unless \`docker\` is set ` +
|
|
37
|
+
"(ComputeFactory fills it in automatically from the app's default container registry when docker is present).");
|
|
38
|
+
}
|
|
16
39
|
if (!(props.ecrRepository instanceof Repository)) {
|
|
17
40
|
throw new Error("ecrRepository must be a Repository instance for Lambda compute type");
|
|
18
41
|
}
|
|
42
|
+
if (props.docker !== undefined) {
|
|
43
|
+
const appPrefix = props.appName !== undefined ? toKebab(props.appName) : "";
|
|
44
|
+
const findings = evaluateBakeGuard(props.docker.buildArgs, appPrefix).findings;
|
|
45
|
+
if (findings.length > 0) {
|
|
46
|
+
throw new Error(`Lambda '${id}': ${findings.map((f) => f.message).join(" ")}`);
|
|
47
|
+
}
|
|
48
|
+
const tagKey = lambdaImageKey(props, id);
|
|
49
|
+
const param = getOrCreateImageTagParameter(scope, tagKey, {
|
|
50
|
+
kind: "lambda-image",
|
|
51
|
+
declaredBy: id,
|
|
52
|
+
docker: props.docker
|
|
53
|
+
});
|
|
54
|
+
return {
|
|
55
|
+
code: Code.fromEcrImage(props.ecrRepository, {
|
|
56
|
+
tagOrDigest: param.valueAsString
|
|
57
|
+
}),
|
|
58
|
+
handler: Handler.FROM_IMAGE,
|
|
59
|
+
runtime: Runtime.FROM_IMAGE
|
|
60
|
+
};
|
|
61
|
+
}
|
|
19
62
|
return {
|
|
20
63
|
code: Code.fromEcrImage(props.ecrRepository, {
|
|
21
64
|
tagOrDigest: COMPUTE_DEFAULTS.ECS.IMAGE_TAG
|
|
@@ -40,7 +83,7 @@ export class LambdaCompute extends Construct {
|
|
|
40
83
|
lambdaFunction;
|
|
41
84
|
constructor(scope, id, props) {
|
|
42
85
|
super(scope, id);
|
|
43
|
-
const { code, handler, runtime } = resolveLambdaDeployment(props);
|
|
86
|
+
const { code, handler, runtime } = resolveLambdaDeployment(scope, id, props);
|
|
44
87
|
// Convert presence-based functionUrl config to legacy boolean format
|
|
45
88
|
const functionUrlConfig = props.functionUrl;
|
|
46
89
|
const enableFunctionUrl = functionUrlConfig !== undefined && functionUrlConfig !== false;
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { ComputeType } from "./interfaces/compute.js";
|
|
2
|
+
import type { EcsComputeProps } from "./computeEcsTypes.js";
|
|
3
|
+
import type { LambdaComputeProps } from "./computeLambda.js";
|
|
4
|
+
import type { Ec2ComputeProps } from "./computeEc2.js";
|
|
5
|
+
type KeysOfUnion<T> = T extends unknown ? keyof T : never;
|
|
6
|
+
type AllComputePropKeys = keyof EcsComputeProps | KeysOfUnion<LambdaComputeProps> | keyof Ec2ComputeProps;
|
|
7
|
+
export interface ComputeWarnGroup {
|
|
8
|
+
properties: string[];
|
|
9
|
+
applicableTypes: ComputeType[];
|
|
10
|
+
typeName: string;
|
|
11
|
+
}
|
|
12
|
+
export declare const COMPUTE_WARN_GROUPS: readonly ComputeWarnGroup[];
|
|
13
|
+
/**
|
|
14
|
+
* Runtime key set for tests — derived from the compiled guard, never
|
|
15
|
+
* re-listed (typescript-standards § compile-time guards).
|
|
16
|
+
*/
|
|
17
|
+
export declare const ALL_COMPUTE_PROP_KEYS: AllComputePropKeys[];
|
|
18
|
+
export declare const CONTAINER_LAMBDA_ONLY_PROPS: string[];
|
|
19
|
+
export declare const CODE_LAMBDA_ONLY_PROPS: string[];
|
|
20
|
+
export {};
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Compile-time lockstep guard for `validateComputeProps`'s ignored-prop
|
|
3
|
+
* warnings. The Record over the full top-level key union rejects BOTH a
|
|
4
|
+
* missing key (a new prop added to any compute props interface fails TS2741
|
|
5
|
+
* here until classified) and a stale extra key (a removed/renamed prop fails
|
|
6
|
+
* TS2353). `"all"` means the prop is legitimate on every compute type and
|
|
7
|
+
* never warns.
|
|
8
|
+
*/
|
|
9
|
+
const COMPUTE_PROP_APPLICABILITY = {
|
|
10
|
+
type: "all",
|
|
11
|
+
vpc: "all",
|
|
12
|
+
appName: ["ecs", "lambda"],
|
|
13
|
+
docker: ["ecs", "lambda"],
|
|
14
|
+
ecrRepository: ["ecs", "lambda"],
|
|
15
|
+
cluster: ["ecs"],
|
|
16
|
+
services: ["ecs"],
|
|
17
|
+
alertsTopic: ["ecs"],
|
|
18
|
+
applicationId: ["ecs"],
|
|
19
|
+
deployment: ["lambda"],
|
|
20
|
+
functionUrl: ["lambda"],
|
|
21
|
+
handler: ["lambda"],
|
|
22
|
+
runtime: ["lambda"],
|
|
23
|
+
code: ["lambda"],
|
|
24
|
+
image: ["lambda"],
|
|
25
|
+
timeout: ["lambda"],
|
|
26
|
+
memorySize: ["lambda"],
|
|
27
|
+
ephemeralStorageSize: ["lambda"],
|
|
28
|
+
architecture: ["lambda"],
|
|
29
|
+
description: ["lambda"],
|
|
30
|
+
roleDescription: ["lambda"],
|
|
31
|
+
inlinePolicy: ["lambda"],
|
|
32
|
+
environment: ["lambda"],
|
|
33
|
+
secrets: ["lambda"],
|
|
34
|
+
ssmSecretsPath: ["lambda"],
|
|
35
|
+
secretsImport: ["lambda"],
|
|
36
|
+
connections: ["lambda"],
|
|
37
|
+
instanceType: ["ec2"],
|
|
38
|
+
ssh: ["ec2"],
|
|
39
|
+
userData: ["ec2"],
|
|
40
|
+
machineImage: ["ec2"],
|
|
41
|
+
minCapacity: ["ec2"],
|
|
42
|
+
maxCapacity: ["ec2"],
|
|
43
|
+
spotCapacityPercentage: ["ec2"]
|
|
44
|
+
};
|
|
45
|
+
const TYPE_LABELS = {
|
|
46
|
+
ecs: "ECS",
|
|
47
|
+
lambda: "Lambda",
|
|
48
|
+
ec2: "EC2"
|
|
49
|
+
};
|
|
50
|
+
export const COMPUTE_WARN_GROUPS = (() => {
|
|
51
|
+
const groups = new Map();
|
|
52
|
+
for (const [prop, applicability] of Object.entries(COMPUTE_PROP_APPLICABILITY)) {
|
|
53
|
+
if (applicability === "all")
|
|
54
|
+
continue;
|
|
55
|
+
const key = applicability.join("+");
|
|
56
|
+
const existing = groups.get(key);
|
|
57
|
+
if (existing !== undefined) {
|
|
58
|
+
existing.properties.push(prop);
|
|
59
|
+
}
|
|
60
|
+
else {
|
|
61
|
+
groups.set(key, {
|
|
62
|
+
properties: [prop],
|
|
63
|
+
applicableTypes: [...applicability],
|
|
64
|
+
typeName: applicability.map((t) => TYPE_LABELS[t]).join(" or ") + " compute"
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
return [...groups.values()];
|
|
69
|
+
})();
|
|
70
|
+
/**
|
|
71
|
+
* Runtime key set for tests — derived from the compiled guard, never
|
|
72
|
+
* re-listed (typescript-standards § compile-time guards).
|
|
73
|
+
*/
|
|
74
|
+
export const ALL_COMPUTE_PROP_KEYS = Object.keys(COMPUTE_PROP_APPLICABILITY);
|
|
75
|
+
const LAMBDA_DEPLOYMENT_APPLICABILITY = {
|
|
76
|
+
ecrRepository: "container",
|
|
77
|
+
docker: "container",
|
|
78
|
+
image: "container",
|
|
79
|
+
code: "code",
|
|
80
|
+
handler: "code",
|
|
81
|
+
runtime: "code"
|
|
82
|
+
};
|
|
83
|
+
const deploymentProps = (d) => Object.entries(LAMBDA_DEPLOYMENT_APPLICABILITY)
|
|
84
|
+
.filter(([, v]) => v === d)
|
|
85
|
+
.map(([k]) => k);
|
|
86
|
+
export const CONTAINER_LAMBDA_ONLY_PROPS = deploymentProps("container");
|
|
87
|
+
export const CODE_LAMBDA_ONLY_PROPS = deploymentProps("code");
|
|
@@ -1,34 +1,17 @@
|
|
|
1
|
-
import { CfnParameter, Stack } from "aws-cdk-lib";
|
|
2
1
|
import { ContainerImage } from "aws-cdk-lib/aws-ecs";
|
|
3
2
|
import { Repository } from "aws-cdk-lib/aws-ecr";
|
|
4
|
-
import { imageTagParameterName } from "@fjall/util";
|
|
5
3
|
import { DEFAULT_ECS_FALLBACK_IMAGE } from "./ecsConstants.js";
|
|
6
|
-
|
|
7
|
-
return `Image tag for ECS service ${serviceName}. Set by fjall deploy to the content-hash tag.`;
|
|
8
|
-
}
|
|
9
|
-
function getOrCreateImageTagParameter(ctx, serviceName) {
|
|
10
|
-
const stack = Stack.of(ctx.scope);
|
|
11
|
-
const paramLogicalId = imageTagParameterName(serviceName);
|
|
12
|
-
const description = buildImageTagDescription(serviceName);
|
|
13
|
-
const existing = stack.node.tryFindChild(paramLogicalId);
|
|
14
|
-
if (existing instanceof CfnParameter) {
|
|
15
|
-
if (existing.description !== description) {
|
|
16
|
-
throw new Error(`CfnParameter logical-ID collision: "${paramLogicalId}" is already registered for a different service. ` +
|
|
17
|
-
`Distinct service names cannot share a PascalCase identifier — rename one of the conflicting services.`);
|
|
18
|
-
}
|
|
19
|
-
return existing;
|
|
20
|
-
}
|
|
21
|
-
return new CfnParameter(stack, paramLogicalId, {
|
|
22
|
-
type: "String",
|
|
23
|
-
default: "latest",
|
|
24
|
-
description
|
|
25
|
-
});
|
|
26
|
-
}
|
|
4
|
+
import { getOrCreateImageTagParameter } from "./imageTagParameter.js";
|
|
27
5
|
export function getContainerImage(ctx, serviceName, containerConfig, serviceProps) {
|
|
28
6
|
const imageSource = containerConfig.image || serviceProps.image || ctx.props.ecrRepository;
|
|
29
7
|
if (!imageSource) {
|
|
30
8
|
return ContainerImage.fromRegistry(DEFAULT_ECS_FALLBACK_IMAGE);
|
|
31
9
|
}
|
|
10
|
+
const entity = {
|
|
11
|
+
kind: "ecs-service",
|
|
12
|
+
declaredBy: serviceName,
|
|
13
|
+
docker: serviceProps.docker
|
|
14
|
+
};
|
|
32
15
|
if (typeof imageSource === "string") {
|
|
33
16
|
const isFullRegistryUrl = /^(docker\.io|registry\.hub\.docker\.com|ghcr\.io)\//i.test(imageSource) ||
|
|
34
17
|
imageSource.startsWith("public.ecr.aws/") ||
|
|
@@ -36,11 +19,11 @@ export function getContainerImage(ctx, serviceName, containerConfig, serviceProp
|
|
|
36
19
|
if (isFullRegistryUrl) {
|
|
37
20
|
return ContainerImage.fromRegistry(imageSource);
|
|
38
21
|
}
|
|
39
|
-
const param = getOrCreateImageTagParameter(ctx, serviceName);
|
|
22
|
+
const param = getOrCreateImageTagParameter(ctx.scope, serviceName, entity);
|
|
40
23
|
return ContainerImage.fromEcrRepository(Repository.fromRepositoryName(ctx.scope, `${serviceName}${containerConfig.name}EcrRepo`, imageSource), param.valueAsString);
|
|
41
24
|
}
|
|
42
25
|
if (imageSource instanceof Repository) {
|
|
43
|
-
const param = getOrCreateImageTagParameter(ctx, serviceName);
|
|
26
|
+
const param = getOrCreateImageTagParameter(ctx.scope, serviceName, entity);
|
|
44
27
|
return ContainerImage.fromEcrRepository(imageSource, param.valueAsString);
|
|
45
28
|
}
|
|
46
29
|
return imageSource;
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { CfnParameter } from "aws-cdk-lib";
|
|
2
|
+
import { type Construct } from "constructs";
|
|
3
|
+
import { type DockerBuild } from "@fjall/util/manifest/schemas";
|
|
4
|
+
/** Which key namespace is registering the parameter. */
|
|
5
|
+
export type ImageTagEntityKind = "ecs-service" | "lambda-image";
|
|
6
|
+
/** Identity of the entity requesting the parameter, for collision reporting. */
|
|
7
|
+
export interface ImageTagEntity {
|
|
8
|
+
readonly kind: ImageTagEntityKind;
|
|
9
|
+
/** ECS: the service name. Lambda: the function's construct id. */
|
|
10
|
+
readonly declaredBy: string;
|
|
11
|
+
readonly docker: DockerBuild | undefined;
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Get or create the per-key image-tag CfnParameter (logical id from
|
|
15
|
+
* `imageTagParameterName`). Idempotent by logical id: a second call with the
|
|
16
|
+
* same key reuses the existing parameter (needed when several Lambda
|
|
17
|
+
* functions share one `image` build key, or when a service is wired up more
|
|
18
|
+
* than once within a stack). Distinct keys that happen to collide on the same
|
|
19
|
+
* PascalCase logical id fail closed rather than silently aliasing two
|
|
20
|
+
* different things onto one parameter.
|
|
21
|
+
*
|
|
22
|
+
* Cross-entity reuse (an ECS service name and a Lambda `image` key sharing
|
|
23
|
+
* one build key) is allowed only when both declare fingerprint-identical
|
|
24
|
+
* docker configs — the deliberate shared-build case. Any divergence
|
|
25
|
+
* (including docker-vs-no-docker) throws at synth, mirroring deploy-core's
|
|
26
|
+
* `assertNoCrossEntityBuildKeyDivergence`: a shared parameter with divergent
|
|
27
|
+
* builds means a deploy of either entity silently rolls both onto one image.
|
|
28
|
+
*/
|
|
29
|
+
export declare function getOrCreateImageTagParameter(scope: Construct, key: string, entity: ImageTagEntity): CfnParameter;
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { CfnParameter, Stack } from "aws-cdk-lib";
|
|
2
|
+
import { imageTagParameterName } from "@fjall/util";
|
|
3
|
+
import { dockerBuildFingerprint } from "@fjall/util/manifest/schemas";
|
|
4
|
+
/** Per-parameter entity metadata; WeakMap keeps the registry per-stack and test-safe. */
|
|
5
|
+
const registrations = new WeakMap();
|
|
6
|
+
function buildImageTagDescription(key) {
|
|
7
|
+
return `Image tag for ${key}. Set by fjall deploy to the content-hash tag.`;
|
|
8
|
+
}
|
|
9
|
+
function describeEntity(kind, declaredBy) {
|
|
10
|
+
return kind === "ecs-service"
|
|
11
|
+
? `ECS service "${declaredBy}"`
|
|
12
|
+
: `Lambda function "${declaredBy}"`;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Get or create the per-key image-tag CfnParameter (logical id from
|
|
16
|
+
* `imageTagParameterName`). Idempotent by logical id: a second call with the
|
|
17
|
+
* same key reuses the existing parameter (needed when several Lambda
|
|
18
|
+
* functions share one `image` build key, or when a service is wired up more
|
|
19
|
+
* than once within a stack). Distinct keys that happen to collide on the same
|
|
20
|
+
* PascalCase logical id fail closed rather than silently aliasing two
|
|
21
|
+
* different things onto one parameter.
|
|
22
|
+
*
|
|
23
|
+
* Cross-entity reuse (an ECS service name and a Lambda `image` key sharing
|
|
24
|
+
* one build key) is allowed only when both declare fingerprint-identical
|
|
25
|
+
* docker configs — the deliberate shared-build case. Any divergence
|
|
26
|
+
* (including docker-vs-no-docker) throws at synth, mirroring deploy-core's
|
|
27
|
+
* `assertNoCrossEntityBuildKeyDivergence`: a shared parameter with divergent
|
|
28
|
+
* builds means a deploy of either entity silently rolls both onto one image.
|
|
29
|
+
*/
|
|
30
|
+
export function getOrCreateImageTagParameter(scope, key, entity) {
|
|
31
|
+
const stack = Stack.of(scope);
|
|
32
|
+
const paramLogicalId = imageTagParameterName(key);
|
|
33
|
+
const description = buildImageTagDescription(key);
|
|
34
|
+
const existing = stack.node.tryFindChild(paramLogicalId);
|
|
35
|
+
if (existing instanceof CfnParameter) {
|
|
36
|
+
if (existing.description !== description) {
|
|
37
|
+
throw new Error(`CfnParameter logical-ID collision: "${paramLogicalId}" is already registered for a different service. ` +
|
|
38
|
+
`Distinct service/lambda names cannot share a PascalCase identifier — rename one of the conflicting entries.`);
|
|
39
|
+
}
|
|
40
|
+
const prior = registrations.get(existing);
|
|
41
|
+
const fingerprint = dockerBuildFingerprint(entity.docker);
|
|
42
|
+
if (prior !== undefined && prior.fingerprint !== fingerprint) {
|
|
43
|
+
throw new Error(`Image-tag parameter collision: ${describeEntity(prior.kind, prior.declaredBy)} and ` +
|
|
44
|
+
`${describeEntity(entity.kind, entity.declaredBy)} share the build key "${key}" ` +
|
|
45
|
+
`(CfnParameter "${paramLogicalId}") but declare different docker configs — ` +
|
|
46
|
+
`a deploy of either would silently roll both onto one image. Rename one of them, ` +
|
|
47
|
+
`or make the docker configs identical to deliberately share the build.`);
|
|
48
|
+
}
|
|
49
|
+
return existing;
|
|
50
|
+
}
|
|
51
|
+
const param = new CfnParameter(stack, paramLogicalId, {
|
|
52
|
+
type: "String",
|
|
53
|
+
default: "latest",
|
|
54
|
+
description
|
|
55
|
+
});
|
|
56
|
+
registrations.set(param, {
|
|
57
|
+
kind: entity.kind,
|
|
58
|
+
declaredBy: entity.declaredBy,
|
|
59
|
+
fingerprint: dockerBuildFingerprint(entity.docker)
|
|
60
|
+
});
|
|
61
|
+
return param;
|
|
62
|
+
}
|
|
@@ -2,7 +2,7 @@ import { SingletonFunction as singletonFunction, Function, Code, type Runtime, A
|
|
|
2
2
|
import { type Bucket } from "aws-cdk-lib/aws-s3";
|
|
3
3
|
import { PolicyStatement, type IRole } from "aws-cdk-lib/aws-iam";
|
|
4
4
|
import { type IVpc } from "aws-cdk-lib/aws-ec2";
|
|
5
|
-
import { RetentionDays } from "aws-cdk-lib/aws-logs";
|
|
5
|
+
import type { RetentionDays } from "aws-cdk-lib/aws-logs";
|
|
6
6
|
import { type IQueue } from "aws-cdk-lib/aws-sqs";
|
|
7
7
|
import { type ITable } from "aws-cdk-lib/aws-dynamodb";
|
|
8
8
|
import { type Construct } from "constructs";
|
|
@@ -7,8 +7,7 @@ import { createHash } from "node:crypto";
|
|
|
7
7
|
import { SqsEventSource, DynamoEventSource, S3EventSource } from "aws-cdk-lib/aws-lambda-event-sources";
|
|
8
8
|
import { EventType } from "aws-cdk-lib/aws-s3";
|
|
9
9
|
import { PolicyStatement, Effect } from "aws-cdk-lib/aws-iam";
|
|
10
|
-
import {
|
|
11
|
-
import { LogGroup } from "../logging/logGroup.js";
|
|
10
|
+
import { DEFAULT_FRAMEWORK_LOG_RETENTION, LogGroup } from "../logging/logGroup.js";
|
|
12
11
|
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
13
12
|
import { buildParameterPath } from "@fjall/util";
|
|
14
13
|
import { resolveImportedSecret } from "../secrets/index.js";
|
|
@@ -95,7 +94,10 @@ export class SingletonFunction extends singletonFunction {
|
|
|
95
94
|
runtime: props.runtime,
|
|
96
95
|
ephemeralStorageSize: props.ephemeralStorageSize
|
|
97
96
|
? Size.mebibytes(props.ephemeralStorageSize)
|
|
98
|
-
: undefined
|
|
97
|
+
: undefined,
|
|
98
|
+
logGroup: new LogGroup(scope, `${id}LogGroup`, {
|
|
99
|
+
retention: props.logGroupRetention ?? DEFAULT_FRAMEWORK_LOG_RETENTION
|
|
100
|
+
})
|
|
99
101
|
});
|
|
100
102
|
addPoliciesToRole(this, props.inlinePolicy);
|
|
101
103
|
applyRoleDescription(this, props.roleDescription);
|
|
@@ -125,7 +127,7 @@ export class LambdaFunction extends Function {
|
|
|
125
127
|
description: props.lambdaDescription ?? `${id} Lambda`,
|
|
126
128
|
environment: props.environment,
|
|
127
129
|
logGroup: new LogGroup(scope, `${id}LogGroup`, {
|
|
128
|
-
retention: props.logGroupRetention ??
|
|
130
|
+
retention: props.logGroupRetention ?? DEFAULT_FRAMEWORK_LOG_RETENTION
|
|
129
131
|
})
|
|
130
132
|
});
|
|
131
133
|
addPoliciesToRole(this, props.inlinePolicy);
|
|
@@ -6,6 +6,7 @@ import { Code, Runtime } from "aws-cdk-lib/aws-lambda";
|
|
|
6
6
|
import { Provider } from "aws-cdk-lib/custom-resources";
|
|
7
7
|
import { Construct } from "constructs";
|
|
8
8
|
import { SingletonFunction } from "../../compute/lambda.js";
|
|
9
|
+
import { DEFAULT_FRAMEWORK_LOG_RETENTION, LogGroup } from "../../logging/logGroup.js";
|
|
9
10
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
10
11
|
/**
|
|
11
12
|
* Resolves relative to the compiled location too — the build step copies the
|
|
@@ -51,7 +52,10 @@ export class IdentityStoreUserProvider extends Construct {
|
|
|
51
52
|
]
|
|
52
53
|
});
|
|
53
54
|
const provider = new Provider(this, "Provider", {
|
|
54
|
-
onEventHandler: handler
|
|
55
|
+
onEventHandler: handler,
|
|
56
|
+
logGroup: new LogGroup(this, "ProviderLogGroup", {
|
|
57
|
+
retention: DEFAULT_FRAMEWORK_LOG_RETENTION
|
|
58
|
+
})
|
|
55
59
|
});
|
|
56
60
|
this.serviceToken = provider.serviceToken;
|
|
57
61
|
}
|
|
@@ -1,5 +1,12 @@
|
|
|
1
1
|
import * as Logs from "aws-cdk-lib/aws-logs";
|
|
2
2
|
import { type Construct } from "constructs";
|
|
3
|
+
/**
|
|
4
|
+
* Default retention for framework-owned lambda log groups. Coupled across
|
|
5
|
+
* every framework lambda construct (LambdaFunction, SingletonFunction, the
|
|
6
|
+
* custom-resource Provider groups, and the stack-singleton groups) — drift
|
|
7
|
+
* would silently give sibling framework lambdas different retention.
|
|
8
|
+
*/
|
|
9
|
+
export declare const DEFAULT_FRAMEWORK_LOG_RETENTION = Logs.RetentionDays.ONE_WEEK;
|
|
3
10
|
export declare class LogGroup extends Logs.LogGroup {
|
|
4
11
|
constructor(scope: Construct, id: string, props?: Logs.LogGroupProps);
|
|
5
12
|
}
|
|
@@ -1,4 +1,11 @@
|
|
|
1
1
|
import * as Logs from "aws-cdk-lib/aws-logs";
|
|
2
|
+
/**
|
|
3
|
+
* Default retention for framework-owned lambda log groups. Coupled across
|
|
4
|
+
* every framework lambda construct (LambdaFunction, SingletonFunction, the
|
|
5
|
+
* custom-resource Provider groups, and the stack-singleton groups) — drift
|
|
6
|
+
* would silently give sibling framework lambdas different retention.
|
|
7
|
+
*/
|
|
8
|
+
export const DEFAULT_FRAMEWORK_LOG_RETENTION = Logs.RetentionDays.ONE_WEEK;
|
|
2
9
|
export class LogGroup extends Logs.LogGroup {
|
|
3
10
|
constructor(scope, id, props) {
|
|
4
11
|
super(scope, id, props);
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { type Construct } from "constructs";
|
|
2
|
+
import { LogGroup } from "./logGroup.js";
|
|
3
|
+
/**
|
|
4
|
+
* Returns the stack-scoped shared LogGroup for a family of framework lambdas
|
|
5
|
+
* that CDK dedupes to one function per stack (AwsCustomResource's AWS679f…
|
|
6
|
+
* singleton, BucketDeployment's custom-resource handler). Every call site in
|
|
7
|
+
* the stack MUST hand the singleton lambda the SAME log group — CDK applies
|
|
8
|
+
* the first instantiation's props and silently ignores later ones, which
|
|
9
|
+
* would leave per-site groups synthesised but unused.
|
|
10
|
+
*
|
|
11
|
+
* The group is nameless (CDK-generated name), so it never collides with the
|
|
12
|
+
* `/aws/lambda/<fn>` group the Lambda service auto-created on existing
|
|
13
|
+
* deployments; the auto-created group simply stops receiving logs.
|
|
14
|
+
*/
|
|
15
|
+
export declare function stackSingletonLogGroup(scope: Construct, purpose: "AwsCustomResource" | "BucketDeployment"): LogGroup;
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { Stack } from "aws-cdk-lib";
|
|
2
|
+
import { DEFAULT_FRAMEWORK_LOG_RETENTION, LogGroup } from "./logGroup.js";
|
|
3
|
+
/**
|
|
4
|
+
* Returns the stack-scoped shared LogGroup for a family of framework lambdas
|
|
5
|
+
* that CDK dedupes to one function per stack (AwsCustomResource's AWS679f…
|
|
6
|
+
* singleton, BucketDeployment's custom-resource handler). Every call site in
|
|
7
|
+
* the stack MUST hand the singleton lambda the SAME log group — CDK applies
|
|
8
|
+
* the first instantiation's props and silently ignores later ones, which
|
|
9
|
+
* would leave per-site groups synthesised but unused.
|
|
10
|
+
*
|
|
11
|
+
* The group is nameless (CDK-generated name), so it never collides with the
|
|
12
|
+
* `/aws/lambda/<fn>` group the Lambda service auto-created on existing
|
|
13
|
+
* deployments; the auto-created group simply stops receiving logs.
|
|
14
|
+
*/
|
|
15
|
+
export function stackSingletonLogGroup(scope, purpose) {
|
|
16
|
+
const stack = Stack.of(scope);
|
|
17
|
+
const id = `Fjall${purpose}LogGroup`;
|
|
18
|
+
const existing = stack.node.tryFindChild(id);
|
|
19
|
+
if (existing !== undefined)
|
|
20
|
+
return existing;
|
|
21
|
+
return new LogGroup(stack, id, {
|
|
22
|
+
retention: DEFAULT_FRAMEWORK_LOG_RETENTION
|
|
23
|
+
});
|
|
24
|
+
}
|
|
@@ -2,8 +2,9 @@ import { Construct } from "constructs";
|
|
|
2
2
|
import { Stack, Tags } from "aws-cdk-lib";
|
|
3
3
|
import { CfnRoute, CfnVPCPeeringConnection, SubnetType } from "aws-cdk-lib/aws-ec2";
|
|
4
4
|
import { PolicyStatement } from "aws-cdk-lib/aws-iam";
|
|
5
|
-
import {
|
|
5
|
+
import { AwsCustomResourcePolicy, PhysicalResourceId } from "aws-cdk-lib/custom-resources";
|
|
6
6
|
import { COST_ALLOCATION_TAGS, DEFAULT_COST_ALLOCATION_ENVIRONMENT } from "../../../utils/costAllocationTags.js";
|
|
7
|
+
import { AwsCustomResource } from "../utilities/awsCustomResource.js";
|
|
7
8
|
import { CrossAccountReturnRoutes } from "./crossAccountReturnRoutes.js";
|
|
8
9
|
export class VpcPeeringConnection extends Construct {
|
|
9
10
|
peeringConnectionId;
|
|
@@ -3,12 +3,11 @@ import { type Construct } from "constructs";
|
|
|
3
3
|
/**
|
|
4
4
|
* Routing-point wrapper for `aws-cdk-lib/aws-s3-deployment.BucketDeployment`.
|
|
5
5
|
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
* `patterns/infrastructure-wrapper-routing-pattern.md § A3`
|
|
11
|
-
* is a thin pass-through today is still the routing point"), `lib/{config,
|
|
6
|
+
* Applies one default: an explicit, stack-shared log group for the deployment
|
|
7
|
+
* handler lambda, so it is CloudFormation-managed and tagged rather than
|
|
8
|
+
* service-auto-created. Per `.claude/rules/generator-standards.md § Wrapper
|
|
9
|
+
* Routing Discipline` and
|
|
10
|
+
* `patterns/infrastructure-wrapper-routing-pattern.md § A3`, `lib/{config,
|
|
12
11
|
* patterns}/` MUST instantiate this class rather than the raw CDK construct.
|
|
13
12
|
*/
|
|
14
13
|
export declare class BucketDeployment extends CdkBucketDeployment {
|
|
@@ -1,17 +1,24 @@
|
|
|
1
1
|
import { BucketDeployment as CdkBucketDeployment } from "aws-cdk-lib/aws-s3-deployment";
|
|
2
|
+
import { stackSingletonLogGroup } from "../logging/singletonLogGroup.js";
|
|
2
3
|
/**
|
|
3
4
|
* Routing-point wrapper for `aws-cdk-lib/aws-s3-deployment.BucketDeployment`.
|
|
4
5
|
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
* `patterns/infrastructure-wrapper-routing-pattern.md § A3`
|
|
10
|
-
* is a thin pass-through today is still the routing point"), `lib/{config,
|
|
6
|
+
* Applies one default: an explicit, stack-shared log group for the deployment
|
|
7
|
+
* handler lambda, so it is CloudFormation-managed and tagged rather than
|
|
8
|
+
* service-auto-created. Per `.claude/rules/generator-standards.md § Wrapper
|
|
9
|
+
* Routing Discipline` and
|
|
10
|
+
* `patterns/infrastructure-wrapper-routing-pattern.md § A3`, `lib/{config,
|
|
11
11
|
* patterns}/` MUST instantiate this class rather than the raw CDK construct.
|
|
12
12
|
*/
|
|
13
13
|
export class BucketDeployment extends CdkBucketDeployment {
|
|
14
14
|
constructor(scope, id, props) {
|
|
15
|
-
super(scope, id,
|
|
15
|
+
super(scope, id, {
|
|
16
|
+
...props,
|
|
17
|
+
// CDK dedupes the deployment handler to one lambda per stack (per
|
|
18
|
+
// memory/vpc variant), so the explicit log group must be the
|
|
19
|
+
// stack-shared one — a per-site group would only bind for whichever
|
|
20
|
+
// site instantiates first.
|
|
21
|
+
logGroup: props.logGroup ?? stackSingletonLogGroup(scope, "BucketDeployment")
|
|
22
|
+
});
|
|
16
23
|
}
|
|
17
24
|
}
|
|
@@ -1,9 +1,14 @@
|
|
|
1
1
|
import * as customResource from "aws-cdk-lib/custom-resources";
|
|
2
|
+
import { stackSingletonLogGroup } from "../logging/singletonLogGroup.js";
|
|
2
3
|
export class AwsCustomResource extends customResource.AwsCustomResource {
|
|
3
4
|
response;
|
|
4
5
|
constructor(scope, id, props) {
|
|
5
6
|
super(scope, id, {
|
|
6
7
|
...props,
|
|
8
|
+
// Every AwsCustomResource in a stack shares CDK's one singleton lambda,
|
|
9
|
+
// so the explicit log group must be the stack-shared one — a per-site
|
|
10
|
+
// group would only bind for whichever site instantiates first.
|
|
11
|
+
logGroup: props.logGroup ?? stackSingletonLogGroup(scope, "AwsCustomResource"),
|
|
7
12
|
policy: props.policy ||
|
|
8
13
|
customResource.AwsCustomResourcePolicy.fromSdkCalls({
|
|
9
14
|
resources: customResource.AwsCustomResourcePolicy.ANY_RESOURCE
|
|
@@ -4,6 +4,7 @@ import { Code } from "aws-cdk-lib/aws-lambda";
|
|
|
4
4
|
import { Construct } from "constructs";
|
|
5
5
|
import { SingletonFunction, LambdaFunction } from "../compute/lambda.js";
|
|
6
6
|
import { DEFAULT_CUSTOM_RESOURCE_TIMEOUT_SECONDS } from "../compute/ecsConstants.js";
|
|
7
|
+
import { DEFAULT_FRAMEWORK_LOG_RETENTION, LogGroup } from "../logging/logGroup.js";
|
|
7
8
|
export class CustomResource extends Construct {
|
|
8
9
|
response;
|
|
9
10
|
resource;
|
|
@@ -54,7 +55,10 @@ export class CustomResource extends Construct {
|
|
|
54
55
|
inlinePolicy: props.inlinePolicy
|
|
55
56
|
});
|
|
56
57
|
const provider = new Provider(this, `${id}Provider`, {
|
|
57
|
-
onEventHandler: lambdaFunction
|
|
58
|
+
onEventHandler: lambdaFunction,
|
|
59
|
+
logGroup: new LogGroup(this, `${id}ProviderLogGroup`, {
|
|
60
|
+
retention: DEFAULT_FRAMEWORK_LOG_RETENTION
|
|
61
|
+
})
|
|
58
62
|
});
|
|
59
63
|
this.resource = new customResource(this, `${id}Resource`, {
|
|
60
64
|
serviceToken: provider.serviceToken,
|
|
@@ -24,11 +24,11 @@ export declare const FjallLogger: {
|
|
|
24
24
|
*/
|
|
25
25
|
export declare function warnIfPropertyIgnored<T extends {
|
|
26
26
|
type: string;
|
|
27
|
-
}>(props: T, property: string, applicableType: string | string[], typeName: string): void;
|
|
27
|
+
}>(props: T, property: string, applicableType: string | string[], typeName: string, discriminatorLabel?: string): void;
|
|
28
28
|
/**
|
|
29
29
|
* Warn if any of the specified properties are set but will be ignored.
|
|
30
30
|
* Convenience wrapper for checking multiple properties at once.
|
|
31
31
|
*/
|
|
32
32
|
export declare function warnIfPropertiesIgnored<T extends {
|
|
33
33
|
type: string;
|
|
34
|
-
}>(props: T, properties: string[], applicableType: string | string[], typeName: string): void;
|
|
34
|
+
}>(props: T, properties: string[], applicableType: string | string[], typeName: string, discriminatorLabel?: string): void;
|
|
@@ -57,7 +57,7 @@ export const FjallLogger = {
|
|
|
57
57
|
* @param applicableType - The type(s) where this property is used
|
|
58
58
|
* @param typeName - Human-readable type name for the message
|
|
59
59
|
*/
|
|
60
|
-
export function warnIfPropertyIgnored(props, property, applicableType, typeName) {
|
|
60
|
+
export function warnIfPropertyIgnored(props, property, applicableType, typeName, discriminatorLabel = "type") {
|
|
61
61
|
const propsRecord = props;
|
|
62
62
|
const types = Array.isArray(applicableType)
|
|
63
63
|
? applicableType
|
|
@@ -66,15 +66,15 @@ export function warnIfPropertyIgnored(props, property, applicableType, typeName)
|
|
|
66
66
|
property in propsRecord &&
|
|
67
67
|
propsRecord[property] !== undefined) {
|
|
68
68
|
FjallLogger.warn(`'${property}' is only used for ${typeName} type. ` +
|
|
69
|
-
`It will be ignored for
|
|
69
|
+
`It will be ignored for ${discriminatorLabel} '${props.type}'.`);
|
|
70
70
|
}
|
|
71
71
|
}
|
|
72
72
|
/**
|
|
73
73
|
* Warn if any of the specified properties are set but will be ignored.
|
|
74
74
|
* Convenience wrapper for checking multiple properties at once.
|
|
75
75
|
*/
|
|
76
|
-
export function warnIfPropertiesIgnored(props, properties, applicableType, typeName) {
|
|
76
|
+
export function warnIfPropertiesIgnored(props, properties, applicableType, typeName, discriminatorLabel = "type") {
|
|
77
77
|
for (const property of properties) {
|
|
78
|
-
warnIfPropertyIgnored(props, property, applicableType, typeName);
|
|
78
|
+
warnIfPropertyIgnored(props, property, applicableType, typeName, discriminatorLabel);
|
|
79
79
|
}
|
|
80
80
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@fjall/components-infrastructure",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.31.1",
|
|
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": "^2.
|
|
71
|
-
"@fjall/util": "^2.
|
|
70
|
+
"@fjall/generator": "^2.31.1",
|
|
71
|
+
"@fjall/util": "^2.31.1",
|
|
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": "a48cfaf6e050ce1736837802dfcaf517ba39f199"
|
|
86
86
|
}
|