@intentius/chant-lexicon-aws 0.0.6 → 0.0.9
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/integrity.json +25 -10
- package/dist/manifest.json +1 -1
- package/dist/meta.json +9444 -4597
- package/dist/rules/cf-refs.ts +99 -0
- package/dist/rules/ext001.ts +32 -25
- package/dist/rules/hardcoded-region.ts +1 -0
- package/dist/rules/iam-wildcard.ts +1 -0
- package/dist/rules/s3-encryption.ts +3 -3
- package/dist/rules/waw016.ts +86 -0
- package/dist/rules/waw017.ts +53 -0
- package/dist/rules/waw018.ts +71 -0
- package/dist/rules/waw019.ts +82 -0
- package/dist/rules/waw020.ts +64 -0
- package/dist/rules/waw021.ts +53 -0
- package/dist/rules/waw022.ts +43 -0
- package/dist/rules/waw023.ts +47 -0
- package/dist/rules/waw024.ts +54 -0
- package/dist/rules/waw025.ts +43 -0
- package/dist/rules/waw026.ts +46 -0
- package/dist/rules/waw027.ts +50 -0
- package/dist/rules/waw028.ts +47 -0
- package/dist/rules/waw029.ts +62 -0
- package/dist/rules/waw030.ts +246 -0
- package/dist/skills/chant-aws.md +430 -0
- package/dist/types/index.d.ts +58525 -58501
- package/package.json +2 -2
- package/src/actions/actions.test.ts +75 -0
- package/src/actions/dynamodb.ts +36 -0
- package/src/actions/ecr.ts +9 -0
- package/src/actions/ecs.ts +5 -0
- package/src/actions/iam.ts +3 -0
- package/src/actions/index.ts +9 -0
- package/src/actions/lambda.ts +11 -0
- package/src/actions/logs.ts +4 -0
- package/src/actions/s3.ts +34 -0
- package/src/actions/sns.ts +5 -0
- package/src/actions/sqs.ts +15 -0
- package/src/codegen/__snapshots__/snapshot.test.ts.snap +20 -20
- package/src/codegen/docs-links.test.ts +143 -0
- package/src/codegen/docs.ts +294 -124
- package/src/codegen/generate-lexicon.ts +8 -0
- package/src/codegen/generate-typescript.ts +25 -1
- package/src/codegen/generate.ts +1 -13
- package/src/codegen/package.ts +2 -0
- package/src/codegen/typecheck.test.ts +1 -1
- package/src/composites/composites.test.ts +442 -0
- package/src/composites/fargate-alb.ts +253 -0
- package/src/composites/index.ts +20 -0
- package/src/composites/lambda-api.ts +20 -0
- package/src/composites/lambda-dynamodb.ts +64 -0
- package/src/composites/lambda-eventbridge.ts +36 -0
- package/src/composites/lambda-function.ts +76 -0
- package/src/composites/lambda-s3.ts +72 -0
- package/src/composites/lambda-sns.ts +30 -0
- package/src/composites/lambda-sqs.ts +44 -0
- package/src/composites/scheduled-lambda.ts +37 -0
- package/src/composites/vpc-default.ts +148 -0
- package/src/default-tags.test.ts +38 -0
- package/src/default-tags.ts +77 -0
- package/src/generated/index.d.ts +58525 -58501
- package/src/generated/index.ts +1351 -1351
- package/src/generated/lexicon-aws.json +9444 -4597
- package/src/import/generator.test.ts +5 -5
- package/src/import/generator.ts +4 -4
- package/src/import/roundtrip-fixtures.test.ts +2 -1
- package/src/import/roundtrip.test.ts +5 -5
- package/src/index.ts +21 -0
- package/src/integration.test.ts +92 -21
- package/src/intrinsics.ts +24 -13
- package/src/lint/post-synth/cf-refs.ts +99 -0
- package/src/lint/post-synth/ext001.test.ts +214 -31
- package/src/lint/post-synth/ext001.ts +32 -25
- package/src/lint/post-synth/waw013.test.ts +120 -0
- package/src/lint/post-synth/waw014.test.ts +121 -0
- package/src/lint/post-synth/waw015.test.ts +147 -0
- package/src/lint/post-synth/waw016.test.ts +141 -0
- package/src/lint/post-synth/waw016.ts +86 -0
- package/src/lint/post-synth/waw017.test.ts +130 -0
- package/src/lint/post-synth/waw017.ts +53 -0
- package/src/lint/post-synth/waw018.test.ts +109 -0
- package/src/lint/post-synth/waw018.ts +71 -0
- package/src/lint/post-synth/waw019.test.ts +138 -0
- package/src/lint/post-synth/waw019.ts +82 -0
- package/src/lint/post-synth/waw020.test.ts +125 -0
- package/src/lint/post-synth/waw020.ts +64 -0
- package/src/lint/post-synth/waw021.test.ts +81 -0
- package/src/lint/post-synth/waw021.ts +53 -0
- package/src/lint/post-synth/waw022.test.ts +54 -0
- package/src/lint/post-synth/waw022.ts +43 -0
- package/src/lint/post-synth/waw023.test.ts +53 -0
- package/src/lint/post-synth/waw023.ts +47 -0
- package/src/lint/post-synth/waw024.test.ts +64 -0
- package/src/lint/post-synth/waw024.ts +54 -0
- package/src/lint/post-synth/waw025.test.ts +42 -0
- package/src/lint/post-synth/waw025.ts +43 -0
- package/src/lint/post-synth/waw026.test.ts +54 -0
- package/src/lint/post-synth/waw026.ts +46 -0
- package/src/lint/post-synth/waw027.test.ts +63 -0
- package/src/lint/post-synth/waw027.ts +50 -0
- package/src/lint/post-synth/waw028.test.ts +68 -0
- package/src/lint/post-synth/waw028.ts +47 -0
- package/src/lint/post-synth/waw029.test.ts +179 -0
- package/src/lint/post-synth/waw029.ts +62 -0
- package/src/lint/post-synth/waw030.test.ts +800 -0
- package/src/lint/post-synth/waw030.ts +246 -0
- package/src/lint/rules/hardcoded-region.ts +1 -0
- package/src/lint/rules/iam-wildcard.ts +1 -0
- package/src/lint/rules/rules.test.ts +8 -8
- package/src/lint/rules/s3-encryption.ts +3 -3
- package/src/lsp/completions.ts +2 -0
- package/src/lsp/hover.ts +17 -0
- package/src/nested-stack-integration.test.ts +100 -0
- package/src/nested-stack.ts +2 -2
- package/src/plugin.test.ts +13 -15
- package/src/plugin.ts +552 -114
- package/src/serializer.test.ts +370 -43
- package/src/serializer.ts +69 -17
- package/src/spec/fetch.ts +10 -0
- package/src/spec/parse.test.ts +141 -0
- package/src/spec/parse.ts +40 -0
- package/src/taggable.ts +44 -0
- package/src/testdata/nested-stacks/app.ts +26 -0
- package/src/testdata/nested-stacks/network/outputs.ts +17 -0
- package/src/testdata/nested-stacks/network/security.ts +17 -0
- package/src/testdata/nested-stacks/network/vpc.ts +54 -0
- package/dist/skills/aws-cloudformation.md +0 -41
- package/src/codegen/rollback.test.ts +0 -80
- package/src/codegen/rollback.ts +0 -20
|
@@ -126,7 +126,7 @@ export function generateTypeScriptDeclarations(
|
|
|
126
126
|
lines.push("");
|
|
127
127
|
lines.push("// --- Resource classes ---");
|
|
128
128
|
for (const re of resources) {
|
|
129
|
-
writeResourceClass(lines, re.tsName, re.properties, re.attributes, re.remap);
|
|
129
|
+
writeResourceClass(lines, re.tsName, re.properties, re.attributes, re.remap, "CFResourceAttributes");
|
|
130
130
|
}
|
|
131
131
|
|
|
132
132
|
// Section 2: Property classes
|
|
@@ -224,6 +224,30 @@ function staticTypeScript(): string {
|
|
|
224
224
|
lines.push("");
|
|
225
225
|
lines.push("// --- Type interfaces ---");
|
|
226
226
|
lines.push("");
|
|
227
|
+
lines.push("export interface Declarable {");
|
|
228
|
+
lines.push(" readonly entityType: string;");
|
|
229
|
+
lines.push("}");
|
|
230
|
+
lines.push("");
|
|
231
|
+
lines.push("export interface CFResourceAttributes {");
|
|
232
|
+
lines.push(" DependsOn?: Declarable | Declarable[] | string | string[];");
|
|
233
|
+
lines.push(" Condition?: string;");
|
|
234
|
+
lines.push(' DeletionPolicy?: "Delete" | "Retain" | "RetainExceptOnCreate" | "Snapshot";');
|
|
235
|
+
lines.push(' UpdateReplacePolicy?: "Delete" | "Retain" | "Snapshot";');
|
|
236
|
+
lines.push(" UpdatePolicy?: {");
|
|
237
|
+
lines.push(" AutoScalingReplacingUpdate?: { WillReplace?: boolean };");
|
|
238
|
+
lines.push(" AutoScalingRollingUpdate?: {");
|
|
239
|
+
lines.push(" MaxBatchSize?: number;");
|
|
240
|
+
lines.push(" MinInstancesInService?: number;");
|
|
241
|
+
lines.push(" PauseTime?: string;");
|
|
242
|
+
lines.push(" WaitOnResourceSignals?: boolean;");
|
|
243
|
+
lines.push(" };");
|
|
244
|
+
lines.push(" };");
|
|
245
|
+
lines.push(" CreationPolicy?: {");
|
|
246
|
+
lines.push(" ResourceSignal?: { Count?: number; Timeout?: string };");
|
|
247
|
+
lines.push(" };");
|
|
248
|
+
lines.push(" Metadata?: Record<string, unknown>;");
|
|
249
|
+
lines.push("}");
|
|
250
|
+
lines.push("");
|
|
227
251
|
lines.push("export interface PolicyDocument {");
|
|
228
252
|
lines.push(' Version?: "2012-10-17" | "2008-10-17";');
|
|
229
253
|
lines.push(" Id?: string;");
|
package/src/codegen/generate.ts
CHANGED
|
@@ -152,17 +152,6 @@ export function writeGeneratedFiles(result: GenerateResult, baseDir: string): vo
|
|
|
152
152
|
"",
|
|
153
153
|
].join("\n"),
|
|
154
154
|
},
|
|
155
|
-
snapshot: (generatedDir) => {
|
|
156
|
-
const { snapshotArtifacts, saveSnapshot } = require("./rollback");
|
|
157
|
-
const lexiconPath = join(generatedDir, "lexicon-aws.json");
|
|
158
|
-
if (existsSync(lexiconPath)) {
|
|
159
|
-
const snapshot = snapshotArtifacts(generatedDir);
|
|
160
|
-
if (Object.keys(snapshot.files).length > 0) {
|
|
161
|
-
const snapshotsDir = join(baseDir, ".snapshots");
|
|
162
|
-
saveSnapshot(snapshot, snapshotsDir);
|
|
163
|
-
}
|
|
164
|
-
}
|
|
165
|
-
},
|
|
166
155
|
});
|
|
167
156
|
}
|
|
168
157
|
|
|
@@ -184,8 +173,7 @@ function generateRuntimeIndex(
|
|
|
184
173
|
// Build attrs map
|
|
185
174
|
const attrs: Record<string, string> = {};
|
|
186
175
|
for (const a of r.resource.attributes) {
|
|
187
|
-
|
|
188
|
-
attrs[camelName] = a.name;
|
|
176
|
+
attrs[a.name] = a.name;
|
|
189
177
|
}
|
|
190
178
|
|
|
191
179
|
resourceEntries.push({ tsName, resourceType: cfnType, attrs });
|
package/src/codegen/package.ts
CHANGED
|
@@ -3,7 +3,9 @@
|
|
|
3
3
|
* with AWS-specific manifest building and skill collection.
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
+
import { createRequire } from "module";
|
|
6
7
|
import { readFileSync } from "fs";
|
|
8
|
+
const require = createRequire(import.meta.url);
|
|
7
9
|
import { join, dirname } from "path";
|
|
8
10
|
import { fileURLToPath } from "url";
|
|
9
11
|
import type { IntrinsicDef } from "@intentius/chant/lexicon";
|
|
@@ -0,0 +1,442 @@
|
|
|
1
|
+
import { describe, test, expect, beforeEach } from "bun:test";
|
|
2
|
+
import { expandComposite, CompositeRegistry, isCompositeInstance } from "@intentius/chant";
|
|
3
|
+
import { AttrRef } from "@intentius/chant/attrref";
|
|
4
|
+
import { LambdaFunction, LambdaNode, LambdaPython, NodeLambda, PythonLambda } from "./lambda-function";
|
|
5
|
+
import { LambdaApi } from "./lambda-api";
|
|
6
|
+
import { LambdaScheduled, ScheduledLambda } from "./scheduled-lambda";
|
|
7
|
+
import { LambdaSqs } from "./lambda-sqs";
|
|
8
|
+
import { LambdaEventBridge } from "./lambda-eventbridge";
|
|
9
|
+
import { LambdaDynamoDB } from "./lambda-dynamodb";
|
|
10
|
+
import { LambdaS3 } from "./lambda-s3";
|
|
11
|
+
import { LambdaSns } from "./lambda-sns";
|
|
12
|
+
import { VpcDefault } from "./vpc-default";
|
|
13
|
+
import { FargateAlb } from "./fargate-alb";
|
|
14
|
+
|
|
15
|
+
const baseProps = {
|
|
16
|
+
name: "TestFunc",
|
|
17
|
+
Runtime: "nodejs20.x",
|
|
18
|
+
Handler: "index.handler",
|
|
19
|
+
Code: { ZipFile: "exports.handler = async () => ({statusCode:200})" },
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
describe("LambdaFunction", () => {
|
|
23
|
+
test("returns role and func members", () => {
|
|
24
|
+
const instance = LambdaFunction(baseProps);
|
|
25
|
+
expect(instance.role).toBeDefined();
|
|
26
|
+
expect(instance.func).toBeDefined();
|
|
27
|
+
expect(Object.keys(instance.members)).toEqual(["role", "func"]);
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
test("func.Role references role.Arn via AttrRef", () => {
|
|
31
|
+
const instance = LambdaFunction(baseProps);
|
|
32
|
+
const funcProps = (instance.func as any).props;
|
|
33
|
+
expect(funcProps.Role).toBeInstanceOf(AttrRef);
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
test("expandComposite produces correct logical names", () => {
|
|
37
|
+
const instance = LambdaFunction(baseProps);
|
|
38
|
+
const expanded = expandComposite("myLambda", instance);
|
|
39
|
+
expect(expanded.has("myLambdaRole")).toBe(true);
|
|
40
|
+
expect(expanded.has("myLambdaFunc")).toBe(true);
|
|
41
|
+
expect(expanded.size).toBe(2);
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
test("default timeout is 30", () => {
|
|
45
|
+
const instance = LambdaFunction(baseProps);
|
|
46
|
+
const funcProps = (instance.func as any).props;
|
|
47
|
+
expect(funcProps.Timeout).toBe(30);
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
test("VpcConfig auto-attaches VPCAccessExecutionRole", () => {
|
|
51
|
+
const instance = LambdaFunction({
|
|
52
|
+
...baseProps,
|
|
53
|
+
VpcConfig: { SubnetIds: ["subnet-1"], SecurityGroupIds: ["sg-1"] },
|
|
54
|
+
});
|
|
55
|
+
const roleProps = (instance.role as any).props;
|
|
56
|
+
expect(roleProps.ManagedPolicyArns).toContain(
|
|
57
|
+
"arn:aws:iam::aws:policy/service-role/AWSLambdaVPCAccessExecutionRole",
|
|
58
|
+
);
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
test("without VpcConfig, no VPCAccessExecutionRole", () => {
|
|
62
|
+
const instance = LambdaFunction(baseProps);
|
|
63
|
+
const roleProps = (instance.role as any).props;
|
|
64
|
+
expect(roleProps.ManagedPolicyArns).not.toContain(
|
|
65
|
+
"arn:aws:iam::aws:policy/service-role/AWSLambdaVPCAccessExecutionRole",
|
|
66
|
+
);
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
test("additional ManagedPolicyArns are appended", () => {
|
|
70
|
+
const customArn = "arn:aws:iam::aws:policy/AmazonS3ReadOnlyAccess";
|
|
71
|
+
const instance = LambdaFunction({
|
|
72
|
+
...baseProps,
|
|
73
|
+
ManagedPolicyArns: [customArn],
|
|
74
|
+
});
|
|
75
|
+
const roleProps = (instance.role as any).props;
|
|
76
|
+
expect(roleProps.ManagedPolicyArns).toContain(customArn);
|
|
77
|
+
expect(roleProps.ManagedPolicyArns).toContain(
|
|
78
|
+
"arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole",
|
|
79
|
+
);
|
|
80
|
+
});
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
describe("LambdaNode / LambdaPython presets", () => {
|
|
84
|
+
test("LambdaNode defaults Runtime and Handler", () => {
|
|
85
|
+
const instance = LambdaNode({
|
|
86
|
+
name: "TestNode",
|
|
87
|
+
Code: { ZipFile: "exports.handler = async () => ({})" },
|
|
88
|
+
});
|
|
89
|
+
const funcProps = (instance.func as any).props;
|
|
90
|
+
expect(funcProps.Runtime).toBe("nodejs20.x");
|
|
91
|
+
expect(funcProps.Handler).toBe("index.handler");
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
test("LambdaPython defaults Runtime and Handler", () => {
|
|
95
|
+
const instance = LambdaPython({
|
|
96
|
+
name: "TestPython",
|
|
97
|
+
Code: { ZipFile: "def handler(event, context): return {}" },
|
|
98
|
+
});
|
|
99
|
+
const funcProps = (instance.func as any).props;
|
|
100
|
+
expect(funcProps.Runtime).toBe("python3.12");
|
|
101
|
+
expect(funcProps.Handler).toBe("handler.handler");
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
test("preset defaults can be overridden", () => {
|
|
105
|
+
const instance = LambdaNode({
|
|
106
|
+
name: "TestOverride",
|
|
107
|
+
Runtime: "nodejs18.x",
|
|
108
|
+
Code: { ZipFile: "" },
|
|
109
|
+
});
|
|
110
|
+
const funcProps = (instance.func as any).props;
|
|
111
|
+
expect(funcProps.Runtime).toBe("nodejs18.x");
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
test("deprecated aliases still work", () => {
|
|
115
|
+
expect(NodeLambda).toBe(LambdaNode);
|
|
116
|
+
expect(PythonLambda).toBe(LambdaPython);
|
|
117
|
+
});
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
describe("LambdaApi", () => {
|
|
121
|
+
test("returns role, func, and permission members", () => {
|
|
122
|
+
const instance = LambdaApi(baseProps);
|
|
123
|
+
expect(Object.keys(instance.members)).toEqual(["role", "func", "permission"]);
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
test("permission references func.Arn", () => {
|
|
127
|
+
const instance = LambdaApi(baseProps);
|
|
128
|
+
const permProps = (instance.permission as any).props;
|
|
129
|
+
expect(permProps.FunctionName).toBeInstanceOf(AttrRef);
|
|
130
|
+
expect(permProps.Principal).toBe("apigateway.amazonaws.com");
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
test("sourceArn is passed through", () => {
|
|
134
|
+
const instance = LambdaApi({
|
|
135
|
+
...baseProps,
|
|
136
|
+
sourceArn: "arn:aws:execute-api:us-east-1:123:api/*",
|
|
137
|
+
});
|
|
138
|
+
const permProps = (instance.permission as any).props;
|
|
139
|
+
expect(permProps.SourceArn).toBe("arn:aws:execute-api:us-east-1:123:api/*");
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
test("expandComposite produces 3 entries", () => {
|
|
143
|
+
const expanded = expandComposite("api", LambdaApi(baseProps));
|
|
144
|
+
expect(expanded.size).toBe(3);
|
|
145
|
+
expect(expanded.has("apiRole")).toBe(true);
|
|
146
|
+
expect(expanded.has("apiFunc")).toBe(true);
|
|
147
|
+
expect(expanded.has("apiPermission")).toBe(true);
|
|
148
|
+
});
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
describe("LambdaScheduled", () => {
|
|
152
|
+
const scheduledProps = { ...baseProps, schedule: "rate(5 minutes)" };
|
|
153
|
+
|
|
154
|
+
test("returns role, func, rule, and permission members", () => {
|
|
155
|
+
const instance = LambdaScheduled(scheduledProps);
|
|
156
|
+
expect(Object.keys(instance.members)).toEqual(["role", "func", "rule", "permission"]);
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
test("rule has ScheduleExpression and targets func", () => {
|
|
160
|
+
const instance = LambdaScheduled(scheduledProps);
|
|
161
|
+
const ruleProps = (instance.rule as any).props;
|
|
162
|
+
expect(ruleProps.ScheduleExpression).toBe("rate(5 minutes)");
|
|
163
|
+
expect(ruleProps.State).toBe("ENABLED");
|
|
164
|
+
expect(ruleProps.Targets).toHaveLength(1);
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
test("enabled: false sets State to DISABLED", () => {
|
|
168
|
+
const instance = LambdaScheduled({ ...scheduledProps, enabled: false });
|
|
169
|
+
const ruleProps = (instance.rule as any).props;
|
|
170
|
+
expect(ruleProps.State).toBe("DISABLED");
|
|
171
|
+
});
|
|
172
|
+
|
|
173
|
+
test("permission principal is events.amazonaws.com", () => {
|
|
174
|
+
const instance = LambdaScheduled(scheduledProps);
|
|
175
|
+
const permProps = (instance.permission as any).props;
|
|
176
|
+
expect(permProps.Principal).toBe("events.amazonaws.com");
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
test("expandComposite produces 4 entries", () => {
|
|
180
|
+
const expanded = expandComposite("cron", LambdaScheduled(scheduledProps));
|
|
181
|
+
expect(expanded.size).toBe(4);
|
|
182
|
+
expect(expanded.has("cronRole")).toBe(true);
|
|
183
|
+
expect(expanded.has("cronFunc")).toBe(true);
|
|
184
|
+
expect(expanded.has("cronRule")).toBe(true);
|
|
185
|
+
expect(expanded.has("cronPermission")).toBe(true);
|
|
186
|
+
});
|
|
187
|
+
|
|
188
|
+
test("deprecated ScheduledLambda alias still works", () => {
|
|
189
|
+
expect(ScheduledLambda).toBe(LambdaScheduled);
|
|
190
|
+
});
|
|
191
|
+
});
|
|
192
|
+
|
|
193
|
+
describe("LambdaSqs", () => {
|
|
194
|
+
test("returns queue, role, func members", () => {
|
|
195
|
+
const instance = LambdaSqs(baseProps);
|
|
196
|
+
expect(Object.keys(instance.members)).toEqual(["queue", "role", "func"]);
|
|
197
|
+
});
|
|
198
|
+
|
|
199
|
+
test("expandComposite produces 4 entries (queue + role + func + eventSourceMapping)", () => {
|
|
200
|
+
const expanded = expandComposite("worker", LambdaSqs(baseProps));
|
|
201
|
+
expect(expanded.has("workerQueue")).toBe(true);
|
|
202
|
+
expect(expanded.has("workerRole")).toBe(true);
|
|
203
|
+
expect(expanded.has("workerFunc")).toBe(true);
|
|
204
|
+
});
|
|
205
|
+
});
|
|
206
|
+
|
|
207
|
+
describe("LambdaEventBridge", () => {
|
|
208
|
+
test("returns rule, role, func, permission members", () => {
|
|
209
|
+
const instance = LambdaEventBridge({ ...baseProps, schedule: "rate(1 hour)" });
|
|
210
|
+
expect(Object.keys(instance.members)).toEqual(["rule", "role", "func", "permission"]);
|
|
211
|
+
});
|
|
212
|
+
|
|
213
|
+
test("supports eventPattern", () => {
|
|
214
|
+
const instance = LambdaEventBridge({
|
|
215
|
+
...baseProps,
|
|
216
|
+
eventPattern: { source: ["aws.s3"] },
|
|
217
|
+
});
|
|
218
|
+
const ruleProps = (instance.rule as any).props;
|
|
219
|
+
expect(ruleProps.EventPattern).toEqual({ source: ["aws.s3"] });
|
|
220
|
+
});
|
|
221
|
+
});
|
|
222
|
+
|
|
223
|
+
describe("LambdaDynamoDB", () => {
|
|
224
|
+
test("returns table, role, func members", () => {
|
|
225
|
+
const instance = LambdaDynamoDB({ ...baseProps, partitionKey: "pk" });
|
|
226
|
+
expect(Object.keys(instance.members)).toEqual(["table", "role", "func"]);
|
|
227
|
+
});
|
|
228
|
+
|
|
229
|
+
test("creates sort key when specified", () => {
|
|
230
|
+
const instance = LambdaDynamoDB({ ...baseProps, partitionKey: "pk", sortKey: "sk" });
|
|
231
|
+
const tableProps = (instance.table as any).props;
|
|
232
|
+
expect(tableProps.AttributeDefinitions).toHaveLength(2);
|
|
233
|
+
expect(tableProps.KeySchema).toHaveLength(2);
|
|
234
|
+
});
|
|
235
|
+
});
|
|
236
|
+
|
|
237
|
+
describe("LambdaS3", () => {
|
|
238
|
+
test("returns bucket, role, func members", () => {
|
|
239
|
+
const instance = LambdaS3(baseProps);
|
|
240
|
+
expect(Object.keys(instance.members)).toEqual(["bucket", "role", "func"]);
|
|
241
|
+
});
|
|
242
|
+
|
|
243
|
+
test("bucket has encryption and public access block", () => {
|
|
244
|
+
const instance = LambdaS3(baseProps);
|
|
245
|
+
const bucketProps = (instance.bucket as any).props;
|
|
246
|
+
expect(bucketProps.BucketEncryption).toBeDefined();
|
|
247
|
+
expect(bucketProps.PublicAccessBlockConfiguration).toBeDefined();
|
|
248
|
+
});
|
|
249
|
+
});
|
|
250
|
+
|
|
251
|
+
describe("LambdaSns", () => {
|
|
252
|
+
test("returns topic, role, func, subscription, permission members", () => {
|
|
253
|
+
const instance = LambdaSns(baseProps);
|
|
254
|
+
expect(Object.keys(instance.members)).toEqual(["topic", "role", "func", "subscription", "permission"]);
|
|
255
|
+
});
|
|
256
|
+
|
|
257
|
+
test("subscription uses lambda protocol", () => {
|
|
258
|
+
const instance = LambdaSns(baseProps);
|
|
259
|
+
const subProps = (instance.subscription as any).props;
|
|
260
|
+
expect(subProps.Protocol).toBe("lambda");
|
|
261
|
+
});
|
|
262
|
+
|
|
263
|
+
test("permission principal is sns.amazonaws.com", () => {
|
|
264
|
+
const instance = LambdaSns(baseProps);
|
|
265
|
+
const permProps = (instance.permission as any).props;
|
|
266
|
+
expect(permProps.Principal).toBe("sns.amazonaws.com");
|
|
267
|
+
});
|
|
268
|
+
});
|
|
269
|
+
|
|
270
|
+
describe("VpcDefault", () => {
|
|
271
|
+
test("returns 17 members", () => {
|
|
272
|
+
const instance = VpcDefault({});
|
|
273
|
+
expect(Object.keys(instance.members)).toHaveLength(17);
|
|
274
|
+
});
|
|
275
|
+
|
|
276
|
+
test("has all expected member names", () => {
|
|
277
|
+
const instance = VpcDefault({});
|
|
278
|
+
const names = Object.keys(instance.members);
|
|
279
|
+
expect(names).toContain("vpc");
|
|
280
|
+
expect(names).toContain("igw");
|
|
281
|
+
expect(names).toContain("igwAttachment");
|
|
282
|
+
expect(names).toContain("publicSubnet1");
|
|
283
|
+
expect(names).toContain("publicSubnet2");
|
|
284
|
+
expect(names).toContain("privateSubnet1");
|
|
285
|
+
expect(names).toContain("privateSubnet2");
|
|
286
|
+
expect(names).toContain("publicRouteTable");
|
|
287
|
+
expect(names).toContain("publicRoute");
|
|
288
|
+
expect(names).toContain("publicRta1");
|
|
289
|
+
expect(names).toContain("publicRta2");
|
|
290
|
+
expect(names).toContain("privateRouteTable");
|
|
291
|
+
expect(names).toContain("privateRta1");
|
|
292
|
+
expect(names).toContain("privateRta2");
|
|
293
|
+
expect(names).toContain("natEip");
|
|
294
|
+
expect(names).toContain("natGateway");
|
|
295
|
+
expect(names).toContain("privateRoute");
|
|
296
|
+
});
|
|
297
|
+
|
|
298
|
+
test("VPC has DNS enabled", () => {
|
|
299
|
+
const instance = VpcDefault({});
|
|
300
|
+
const vpcProps = (instance.vpc as any).props;
|
|
301
|
+
expect(vpcProps.EnableDnsSupport).toBe(true);
|
|
302
|
+
expect(vpcProps.EnableDnsHostnames).toBe(true);
|
|
303
|
+
});
|
|
304
|
+
|
|
305
|
+
test("public subnets have MapPublicIpOnLaunch", () => {
|
|
306
|
+
const instance = VpcDefault({});
|
|
307
|
+
const pub1Props = (instance.publicSubnet1 as any).props;
|
|
308
|
+
const pub2Props = (instance.publicSubnet2 as any).props;
|
|
309
|
+
expect(pub1Props.MapPublicIpOnLaunch).toBe(true);
|
|
310
|
+
expect(pub2Props.MapPublicIpOnLaunch).toBe(true);
|
|
311
|
+
});
|
|
312
|
+
|
|
313
|
+
test("vpc.VpcId is wired to subnets", () => {
|
|
314
|
+
const instance = VpcDefault({});
|
|
315
|
+
const sub1Props = (instance.publicSubnet1 as any).props;
|
|
316
|
+
expect(sub1Props.VpcId).toBeInstanceOf(AttrRef);
|
|
317
|
+
});
|
|
318
|
+
|
|
319
|
+
test("NAT gateway is present", () => {
|
|
320
|
+
const instance = VpcDefault({});
|
|
321
|
+
expect(instance.natGateway).toBeDefined();
|
|
322
|
+
expect(instance.natEip).toBeDefined();
|
|
323
|
+
});
|
|
324
|
+
|
|
325
|
+
test("expandComposite produces 17 entries", () => {
|
|
326
|
+
const expanded = expandComposite("net", VpcDefault({}));
|
|
327
|
+
expect(expanded.size).toBe(17);
|
|
328
|
+
expect(expanded.has("netVpc")).toBe(true);
|
|
329
|
+
expect(expanded.has("netIgw")).toBe(true);
|
|
330
|
+
expect(expanded.has("netNatGateway")).toBe(true);
|
|
331
|
+
});
|
|
332
|
+
|
|
333
|
+
test("custom CIDR overrides defaults", () => {
|
|
334
|
+
const instance = VpcDefault({ cidr: "172.16.0.0/16" });
|
|
335
|
+
const vpcProps = (instance.vpc as any).props;
|
|
336
|
+
expect(vpcProps.CidrBlock).toBe("172.16.0.0/16");
|
|
337
|
+
});
|
|
338
|
+
});
|
|
339
|
+
|
|
340
|
+
describe("FargateAlb", () => {
|
|
341
|
+
const fargateProps = {
|
|
342
|
+
image: "nginx:latest",
|
|
343
|
+
vpcId: "vpc-123",
|
|
344
|
+
publicSubnetIds: ["subnet-pub1", "subnet-pub2"],
|
|
345
|
+
privateSubnetIds: ["subnet-priv1", "subnet-priv2"],
|
|
346
|
+
};
|
|
347
|
+
|
|
348
|
+
test("returns 11 members", () => {
|
|
349
|
+
const instance = FargateAlb(fargateProps);
|
|
350
|
+
expect(Object.keys(instance.members)).toHaveLength(11);
|
|
351
|
+
});
|
|
352
|
+
|
|
353
|
+
test("has all expected member names", () => {
|
|
354
|
+
const instance = FargateAlb(fargateProps);
|
|
355
|
+
const names = Object.keys(instance.members);
|
|
356
|
+
expect(names).toEqual([
|
|
357
|
+
"cluster", "executionRole", "taskRole", "logGroup", "taskDef",
|
|
358
|
+
"albSg", "taskSg", "alb", "targetGroup", "listener", "service",
|
|
359
|
+
]);
|
|
360
|
+
});
|
|
361
|
+
|
|
362
|
+
test("expandComposite produces correct logical names", () => {
|
|
363
|
+
const expanded = expandComposite("web", FargateAlb(fargateProps));
|
|
364
|
+
expect(expanded.has("webCluster")).toBe(true);
|
|
365
|
+
expect(expanded.has("webExecutionRole")).toBe(true);
|
|
366
|
+
expect(expanded.has("webTaskRole")).toBe(true);
|
|
367
|
+
expect(expanded.has("webLogGroup")).toBe(true);
|
|
368
|
+
expect(expanded.has("webTaskDef")).toBe(true);
|
|
369
|
+
expect(expanded.has("webAlbSg")).toBe(true);
|
|
370
|
+
expect(expanded.has("webTaskSg")).toBe(true);
|
|
371
|
+
expect(expanded.has("webAlb")).toBe(true);
|
|
372
|
+
expect(expanded.has("webTargetGroup")).toBe(true);
|
|
373
|
+
expect(expanded.has("webListener")).toBe(true);
|
|
374
|
+
expect(expanded.has("webService")).toBe(true);
|
|
375
|
+
});
|
|
376
|
+
|
|
377
|
+
test("execution role has ECR and Logs policies", () => {
|
|
378
|
+
const instance = FargateAlb(fargateProps);
|
|
379
|
+
const roleProps = (instance.executionRole as any).props;
|
|
380
|
+
expect(roleProps.Policies).toHaveLength(1);
|
|
381
|
+
const policyDoc = (roleProps.Policies[0] as any).props.PolicyDocument;
|
|
382
|
+
expect(policyDoc.Statement).toHaveLength(2);
|
|
383
|
+
expect(policyDoc.Statement[0].Action).toContain("ecr:GetAuthorizationToken");
|
|
384
|
+
expect(policyDoc.Statement[1].Action).toContain("logs:CreateLogStream");
|
|
385
|
+
});
|
|
386
|
+
|
|
387
|
+
test("task role receives custom policies", () => {
|
|
388
|
+
const { Role_Policy } = require("../generated");
|
|
389
|
+
const customPolicy = new Role_Policy({
|
|
390
|
+
PolicyName: "Custom",
|
|
391
|
+
PolicyDocument: { Version: "2012-10-17", Statement: [] },
|
|
392
|
+
});
|
|
393
|
+
const instance = FargateAlb({ ...fargateProps, Policies: [customPolicy] });
|
|
394
|
+
const roleProps = (instance.taskRole as any).props;
|
|
395
|
+
expect(roleProps.Policies).toHaveLength(1);
|
|
396
|
+
});
|
|
397
|
+
|
|
398
|
+
test("task definition has awsvpc and FARGATE", () => {
|
|
399
|
+
const instance = FargateAlb(fargateProps);
|
|
400
|
+
const tdProps = (instance.taskDef as any).props;
|
|
401
|
+
expect(tdProps.NetworkMode).toBe("awsvpc");
|
|
402
|
+
expect(tdProps.RequiresCompatibilities).toEqual(["FARGATE"]);
|
|
403
|
+
});
|
|
404
|
+
|
|
405
|
+
test("ALB SG allows ingress on listener port", () => {
|
|
406
|
+
const instance = FargateAlb(fargateProps);
|
|
407
|
+
const sgProps = (instance.albSg as any).props;
|
|
408
|
+
expect(sgProps.SecurityGroupIngress).toHaveLength(1);
|
|
409
|
+
const ingress = (sgProps.SecurityGroupIngress[0] as any).props;
|
|
410
|
+
expect(ingress.FromPort).toBe(80);
|
|
411
|
+
expect(ingress.CidrIp).toBe("0.0.0.0/0");
|
|
412
|
+
});
|
|
413
|
+
|
|
414
|
+
test("task SG references ALB SG GroupId", () => {
|
|
415
|
+
const instance = FargateAlb(fargateProps);
|
|
416
|
+
const sgProps = (instance.taskSg as any).props;
|
|
417
|
+
const ingress = (sgProps.SecurityGroupIngress[0] as any).props;
|
|
418
|
+
expect(ingress.SourceSecurityGroupId).toBeInstanceOf(AttrRef);
|
|
419
|
+
});
|
|
420
|
+
|
|
421
|
+
test("default container port is 80", () => {
|
|
422
|
+
const instance = FargateAlb(fargateProps);
|
|
423
|
+
const tdProps = (instance.taskDef as any).props;
|
|
424
|
+
const containerDef = (tdProps.ContainerDefinitions[0] as any).props;
|
|
425
|
+
const portMapping = (containerDef.PortMappings[0] as any).props;
|
|
426
|
+
expect(portMapping.ContainerPort).toBe(80);
|
|
427
|
+
});
|
|
428
|
+
|
|
429
|
+
test("default desired count is 2", () => {
|
|
430
|
+
const instance = FargateAlb(fargateProps);
|
|
431
|
+
const svcProps = (instance.service as any).props;
|
|
432
|
+
expect(svcProps.DesiredCount).toBe(2);
|
|
433
|
+
});
|
|
434
|
+
|
|
435
|
+
test("custom listener port is applied", () => {
|
|
436
|
+
const instance = FargateAlb({ ...fargateProps, listenerPort: 8080 });
|
|
437
|
+
const sgProps = (instance.albSg as any).props;
|
|
438
|
+
const ingress = (sgProps.SecurityGroupIngress[0] as any).props;
|
|
439
|
+
expect(ingress.FromPort).toBe(8080);
|
|
440
|
+
expect(ingress.ToPort).toBe(8080);
|
|
441
|
+
});
|
|
442
|
+
});
|