@intentius/chant-lexicon-aws 0.37.2 → 0.40.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/ambient.d.ts.map +1 -1
- package/dist/api/read-client.d.ts.map +1 -1
- package/dist/codegen/generate.d.ts.map +1 -1
- package/dist/codegen/naming.d.ts +18 -1
- package/dist/codegen/naming.d.ts.map +1 -1
- package/dist/composites/alb-shared.d.ts +7 -1
- package/dist/composites/alb-shared.d.ts.map +1 -1
- package/dist/composites/ec2-instance-role.d.ts +4 -1
- package/dist/composites/ec2-instance-role.d.ts.map +1 -1
- package/dist/composites/efs-with-access-point.d.ts +5 -1
- package/dist/composites/efs-with-access-point.d.ts.map +1 -1
- package/dist/composites/fargate-alb.d.ts +14 -1
- package/dist/composites/fargate-alb.d.ts.map +1 -1
- package/dist/composites/fargate-service.d.ts +19 -8
- package/dist/composites/fargate-service.d.ts.map +1 -1
- package/dist/composites/index.d.ts +2 -2
- package/dist/composites/index.d.ts.map +1 -1
- package/dist/composites/lambda-api.d.ts +3 -1
- package/dist/composites/lambda-api.d.ts.map +1 -1
- package/dist/composites/lambda-dynamodb.d.ts +9 -2
- package/dist/composites/lambda-dynamodb.d.ts.map +1 -1
- package/dist/composites/lambda-eventbridge.d.ts +9 -2
- package/dist/composites/lambda-eventbridge.d.ts.map +1 -1
- package/dist/composites/lambda-s3.d.ts +3 -1
- package/dist/composites/lambda-s3.d.ts.map +1 -1
- package/dist/composites/lambda-sns.d.ts +10 -2
- package/dist/composites/lambda-sns.d.ts.map +1 -1
- package/dist/composites/lambda-sqs.d.ts +9 -2
- package/dist/composites/lambda-sqs.d.ts.map +1 -1
- package/dist/composites/microvm-app.d.ts +25 -4
- package/dist/composites/microvm-app.d.ts.map +1 -1
- package/dist/composites/minimal-vpc.d.ts +10 -1
- package/dist/composites/minimal-vpc.d.ts.map +1 -1
- package/dist/composites/rds-instance.d.ts +1 -1
- package/dist/composites/rds-instance.d.ts.map +1 -1
- package/dist/composites/scheduled-lambda.d.ts +15 -3
- package/dist/composites/scheduled-lambda.d.ts.map +1 -1
- package/dist/composites/solr-fargate-service.d.ts +27 -1
- package/dist/composites/solr-fargate-service.d.ts.map +1 -1
- package/dist/generated/index.d.ts +57 -49
- package/dist/generated/index.d.ts.map +1 -1
- package/dist/import/generator.d.ts.map +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/integrity.json +4 -4
- package/dist/manifest.json +1 -1
- package/dist/meta.json +722 -666
- package/dist/op/activities/floci.d.ts.map +1 -1
- package/dist/plugin.d.ts.map +1 -1
- package/dist/reference-catalog.d.ts.map +1 -1
- package/dist/spec/pin.d.ts +116 -0
- package/dist/spec/pin.d.ts.map +1 -0
- package/dist/spec/pinned-types.json +1652 -0
- package/dist/types/index.d.ts +679 -555
- package/dist/validate.d.ts.map +1 -1
- package/package.json +2 -2
- package/src/ambient.test.ts +21 -0
- package/src/ambient.ts +16 -0
- package/src/api/read-client.test.ts +31 -0
- package/src/api/read-client.ts +43 -1
- package/src/codegen/generate.ts +17 -3
- package/src/codegen/naming.ts +31 -2
- package/src/composites/alb-shared.ts +1 -1
- package/src/composites/ec2-instance-role.ts +1 -1
- package/src/composites/efs-with-access-point.ts +1 -1
- package/src/composites/fargate-alb.ts +1 -1
- package/src/composites/fargate-service.ts +15 -10
- package/src/composites/index.ts +2 -2
- package/src/composites/lambda-api.ts +3 -2
- package/src/composites/lambda-dynamodb.ts +4 -3
- package/src/composites/lambda-eventbridge.ts +4 -3
- package/src/composites/lambda-s3.ts +3 -2
- package/src/composites/lambda-sns.ts +4 -3
- package/src/composites/lambda-sqs.ts +4 -3
- package/src/composites/microvm-app.test.ts +39 -1
- package/src/composites/microvm-app.ts +32 -11
- package/src/composites/minimal-vpc.ts +1 -1
- package/src/composites/rds-instance.ts +1 -1
- package/src/composites/scheduled-lambda.ts +4 -3
- package/src/composites/solr-fargate-service.ts +7 -1
- package/src/generated/index.d.ts +679 -555
- package/src/generated/index.ts +58 -50
- package/src/generated/lexicon-aws.json +722 -666
- package/src/import/generator.ts +5 -4
- package/src/index.ts +2 -2
- package/src/lifecycle-integration.test.ts +25 -0
- package/src/op/activities/floci.test.ts +2 -2
- package/src/op/activities/floci.ts +6 -1
- package/src/plugin.ts +4 -0
- package/src/reference-catalog.test.ts +28 -0
- package/src/reference-catalog.ts +7 -0
- package/src/spec/pin.test.ts +248 -0
- package/src/spec/pin.ts +247 -0
- package/src/spec/pinned-types.json +1652 -0
- package/src/validate.ts +5 -0
package/dist/validate.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"validate.d.ts","sourceRoot":"","sources":["../src/validate.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAIH,OAAO,EAA4B,KAAK,cAAc,EAAE,MAAM,mCAAmC,CAAC;AAElG,YAAY,EAAE,aAAa,EAAE,cAAc,EAAE,MAAM,mCAAmC,CAAC;AAIvF;;;;GAIG;AACH,wBAAsB,QAAQ,CAAC,IAAI,CAAC,EAAE;IAAE,QAAQ,CAAC,EAAE,MAAM,CAAA;CAAE,GAAG,OAAO,CAAC,cAAc,CAAC,
|
|
1
|
+
{"version":3,"file":"validate.d.ts","sourceRoot":"","sources":["../src/validate.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAIH,OAAO,EAA4B,KAAK,cAAc,EAAE,MAAM,mCAAmC,CAAC;AAElG,YAAY,EAAE,aAAa,EAAE,cAAc,EAAE,MAAM,mCAAmC,CAAC;AAIvF;;;;GAIG;AACH,wBAAsB,QAAQ,CAAC,IAAI,CAAC,EAAE;IAAE,QAAQ,CAAC,EAAE,MAAM,CAAA;CAAE,GAAG,OAAO,CAAC,cAAc,CAAC,CAkBpF"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@intentius/chant-lexicon-aws",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.40.0",
|
|
4
4
|
"description": "AWS CloudFormation lexicon for chant — declarative IaC in TypeScript",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"homepage": "https://intentius.io/chant",
|
|
@@ -80,7 +80,7 @@
|
|
|
80
80
|
"typescript": "^5.9.3"
|
|
81
81
|
},
|
|
82
82
|
"peerDependencies": {
|
|
83
|
-
"@intentius/chant": "^0.
|
|
83
|
+
"@intentius/chant": "^0.39.0",
|
|
84
84
|
"typescript": "^5.9.3"
|
|
85
85
|
}
|
|
86
86
|
}
|
package/src/ambient.test.ts
CHANGED
|
@@ -42,6 +42,27 @@ describe("observeAwsAmbient (#1278)", () => {
|
|
|
42
42
|
expect(found["sg-default"].attributes).toMatchObject({ GroupName: "default", VpcId: "vpc-1" });
|
|
43
43
|
});
|
|
44
44
|
|
|
45
|
+
// The empty ones are the answer, and they were the ones being dropped: both
|
|
46
|
+
// other observers resolve outward from declarations, so a subnet is recorded
|
|
47
|
+
// only when something in it is.
|
|
48
|
+
it("reports subnets nothing occupies", async () => {
|
|
49
|
+
spawnMock.mockResolvedValue(
|
|
50
|
+
ok({
|
|
51
|
+
Subnets: [
|
|
52
|
+
{ SubnetId: "subnet-used", VpcId: "vpc-1" },
|
|
53
|
+
{ SubnetId: "subnet-empty-a", VpcId: "vpc-default" },
|
|
54
|
+
{ SubnetId: "subnet-empty-b", VpcId: "vpc-default" },
|
|
55
|
+
],
|
|
56
|
+
}),
|
|
57
|
+
);
|
|
58
|
+
const found = await observeAwsAmbient({
|
|
59
|
+
kinds: ["AWS::EC2::Subnet"],
|
|
60
|
+
observed: { public: { type: "AWS::EC2::Subnet", status: "OK", physicalId: "subnet-used" } },
|
|
61
|
+
});
|
|
62
|
+
expect(Object.keys(found).sort()).toEqual(["subnet-empty-a", "subnet-empty-b"]);
|
|
63
|
+
expect(found["subnet-empty-a"].attributes).toMatchObject({ VpcId: "vpc-default" });
|
|
64
|
+
});
|
|
65
|
+
|
|
45
66
|
it("is bounded by the kinds the project declares", async () => {
|
|
46
67
|
// A project managing security groups is not made to enumerate the account.
|
|
47
68
|
const found = await observeAwsAmbient({ kinds: ["AWS::S3::Bucket"], observed: {} });
|
package/src/ambient.ts
CHANGED
|
@@ -44,6 +44,22 @@ const ENUMERABLE: Record<string, { argv: string[]; key: string; id: string; name
|
|
|
44
44
|
key: "NetworkInterfaces",
|
|
45
45
|
id: "NetworkInterfaceId",
|
|
46
46
|
},
|
|
47
|
+
// A subnet holding nothing is the same shape as an unused security group, and
|
|
48
|
+
// it was reachable by neither of the other paths. Both resolve outward from
|
|
49
|
+
// what is declared, so a subnet gets recorded only when something in it does —
|
|
50
|
+
// and the empty ones are exactly what "which of my subnets are empty" asks
|
|
51
|
+
// for.
|
|
52
|
+
//
|
|
53
|
+
// Found by measuring a snapshot rather than by reading the code: an estate
|
|
54
|
+
// with seven subnets recorded five, and the two it dropped were the two with
|
|
55
|
+
// nothing in them. The default VPC's subnets are the archetype again — nothing
|
|
56
|
+
// declares them, and the only one ever recorded was the one that happened to
|
|
57
|
+
// hold an instance.
|
|
58
|
+
"AWS::EC2::Subnet": {
|
|
59
|
+
argv: ["ec2", "describe-subnets"],
|
|
60
|
+
key: "Subnets",
|
|
61
|
+
id: "SubnetId",
|
|
62
|
+
},
|
|
47
63
|
};
|
|
48
64
|
|
|
49
65
|
/**
|
|
@@ -201,3 +201,34 @@ describe("Cloud Control", () => {
|
|
|
201
201
|
expect(parseResourceDescription({ Properties: JSON.stringify(["not", "an", "object"]) })).toBeNull();
|
|
202
202
|
});
|
|
203
203
|
});
|
|
204
|
+
|
|
205
|
+
describe("the region an endpoint override cannot carry in its host", () => {
|
|
206
|
+
// An override is one host for every region, so the hostname says nothing and
|
|
207
|
+
// the region has to travel in the request. It did not, and a stack declared
|
|
208
|
+
// `region: "us-west-1"` was read against the emulator's default region —
|
|
209
|
+
// where it genuinely does not exist, so the read came back empty and the
|
|
210
|
+
// snapshot recorded that region as holding nothing.
|
|
211
|
+
test("the request names the region in its credential scope", async () => {
|
|
212
|
+
const { http, calls } = recording(() => respond(stackXml));
|
|
213
|
+
await describeStackResources("web", { endpoint: "http://localhost:4566", region: "us-west-1", http });
|
|
214
|
+
expect(calls[0].headers.authorization).toContain("/us-west-1/cloudformation/aws4_request");
|
|
215
|
+
});
|
|
216
|
+
|
|
217
|
+
test("Cloud Control carries it too, scoped to its own service", async () => {
|
|
218
|
+
const { http, calls } = recording(() => respond(JSON.stringify({ ResourceDescription: {} })));
|
|
219
|
+
await getResource("AWS::EC2::VPC", "vpc-01", { endpoint: "http://localhost:4566", region: "eu-west-1", http });
|
|
220
|
+
expect(calls[0].headers.authorization).toContain("/eu-west-1/cloudcontrolapi/aws4_request");
|
|
221
|
+
});
|
|
222
|
+
|
|
223
|
+
test("no region, no header — nothing invents one", async () => {
|
|
224
|
+
const { http, calls } = recording(() => respond(stackXml));
|
|
225
|
+
await describeStackResources("web", { endpoint: "http://localhost:4566", http });
|
|
226
|
+
expect(calls[0].headers.authorization).toBeUndefined();
|
|
227
|
+
});
|
|
228
|
+
|
|
229
|
+
test("it is a scope, not a signature — the placeholder says so", async () => {
|
|
230
|
+
const { http, calls } = recording(() => respond(stackXml));
|
|
231
|
+
await describeStackResources("web", { region: "us-west-1", http });
|
|
232
|
+
expect(calls[0].headers.authorization).toContain("Signature=unsigned");
|
|
233
|
+
});
|
|
234
|
+
});
|
package/src/api/read-client.ts
CHANGED
|
@@ -69,6 +69,37 @@ export function serviceUrl(service: string, endpoint?: string, region = DEFAULT_
|
|
|
69
69
|
return `${(endpoint ?? `https://${service}.${region}.amazonaws.com`).replace(/\/$/, "")}/`;
|
|
70
70
|
}
|
|
71
71
|
|
|
72
|
+
/**
|
|
73
|
+
* How the request says which region it is for.
|
|
74
|
+
*
|
|
75
|
+
* Against real AWS the region is the hostname, which is why `serviceUrl` takes
|
|
76
|
+
* it. Against an endpoint override there is only one host for every region, so
|
|
77
|
+
* the hostname carries nothing and the region has to travel in the request —
|
|
78
|
+
* and every emulator reads it where a real SDK puts it, in the credential scope
|
|
79
|
+
* of the `Authorization` header.
|
|
80
|
+
*
|
|
81
|
+
* Without this, a stack observed with `region: "us-west-1"` was read against
|
|
82
|
+
* whatever region the emulator defaults to. The result is not an error: the
|
|
83
|
+
* stack genuinely does not exist in us-east-1, so `describeResources` returned
|
|
84
|
+
* an empty observation and the snapshot recorded the region as holding nothing.
|
|
85
|
+
* A three-region estate snapshotted as one region and nothing said so.
|
|
86
|
+
*
|
|
87
|
+
* This is NOT SigV4. The signature is a placeholder and real AWS rejects it —
|
|
88
|
+
* as it already rejects every request from this module, which is unsigned by
|
|
89
|
+
* design (see the header comment). It carries the scope, nothing more, and it
|
|
90
|
+
* must not be mistaken for the signed read path that would replace it.
|
|
91
|
+
*/
|
|
92
|
+
function regionScope(service: string, region?: string): Record<string, string> {
|
|
93
|
+
if (!region) return {};
|
|
94
|
+
const day = new Date().toISOString().slice(0, 10).replace(/-/g, "");
|
|
95
|
+
const key = process.env.AWS_ACCESS_KEY_ID || "chant";
|
|
96
|
+
return {
|
|
97
|
+
authorization:
|
|
98
|
+
`AWS4-HMAC-SHA256 Credential=${key}/${day}/${region}/${service}/aws4_request, ` +
|
|
99
|
+
"SignedHeaders=host, Signature=unsigned",
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
|
|
72
103
|
/* ── CloudFormation Query ─────────────────────────────────────────────────── */
|
|
73
104
|
|
|
74
105
|
/**
|
|
@@ -128,7 +159,17 @@ export async function cfnQuery(
|
|
|
128
159
|
const http = options.http ?? defaultHttp;
|
|
129
160
|
const url = serviceUrl("cloudformation", options.endpoint, options.region);
|
|
130
161
|
const body = new URLSearchParams({ Action: action, Version: CFN_API_VERSION, ...params }).toString();
|
|
131
|
-
const res = await http(
|
|
162
|
+
const res = await http(
|
|
163
|
+
url,
|
|
164
|
+
{
|
|
165
|
+
headers: {
|
|
166
|
+
"content-type": "application/x-www-form-urlencoded",
|
|
167
|
+
...regionScope("cloudformation", options.region),
|
|
168
|
+
},
|
|
169
|
+
body,
|
|
170
|
+
},
|
|
171
|
+
options.signal,
|
|
172
|
+
);
|
|
132
173
|
const err = xmlError(res.text);
|
|
133
174
|
if (err || res.status >= 400) {
|
|
134
175
|
throw new AwsReadError(
|
|
@@ -203,6 +244,7 @@ async function cloudControl(
|
|
|
203
244
|
headers: {
|
|
204
245
|
"content-type": "application/x-amz-json-1.0",
|
|
205
246
|
"x-amz-target": `${CLOUD_CONTROL_TARGET_PREFIX}.${operation}`,
|
|
247
|
+
...regionScope("cloudcontrolapi", options.region),
|
|
206
248
|
},
|
|
207
249
|
body: JSON.stringify(payload),
|
|
208
250
|
},
|
package/src/codegen/generate.ts
CHANGED
|
@@ -14,12 +14,13 @@ import {
|
|
|
14
14
|
type AugmentResult,
|
|
15
15
|
} from "@intentius/chant/codegen/generate";
|
|
16
16
|
import { fetchSchemaZip } from "../spec/fetch";
|
|
17
|
+
import { assertPinnedSpec } from "../spec/pin";
|
|
17
18
|
import { parseCFNSchema, cfnShortName, type SchemaParseResult } from "../spec/parse";
|
|
18
19
|
import { fetchCfnLintPatches, applyPatches } from "./patches";
|
|
19
20
|
import { fetchCfnLintExtensions, loadExtensionSchemas, type ExtensionConstraint } from "./extensions";
|
|
20
21
|
import { samResources } from "./sam";
|
|
21
22
|
import { fallbackResources } from "./fallback";
|
|
22
|
-
import { NamingStrategy, propertyTypeName, extractDefName } from "./naming";
|
|
23
|
+
import { NamingStrategy, publishedNames, propertyTypeName, extractDefName } from "./naming";
|
|
23
24
|
import { generateLexiconJSON, lambdaRuntimeDeprecations } from "./generate-lexicon";
|
|
24
25
|
import { generateTypeScriptDeclarations } from "./generate-typescript";
|
|
25
26
|
import {
|
|
@@ -32,10 +33,17 @@ export type { GenerateOptions, GenerateResult };
|
|
|
32
33
|
|
|
33
34
|
// AWS-specific state shared between pipeline callbacks
|
|
34
35
|
let awsConstraints = new Map<string, ExtensionConstraint[]>();
|
|
36
|
+
/** chant #1459 — spec type → already-published TS name, reset per `generate()` call. */
|
|
37
|
+
let awsReservedNames: Record<string, string> = {};
|
|
35
38
|
|
|
36
39
|
const awsPipelineConfig: GeneratePipelineConfig<SchemaParseResult> = {
|
|
37
40
|
fetchSchemas: async (opts) => {
|
|
38
|
-
|
|
41
|
+
const schemas = await fetchSchemaZip(opts.force);
|
|
42
|
+
// #1390 — the registry schema has no version in its URL and is republished
|
|
43
|
+
// constantly, so without this any prepack regenerates against whatever
|
|
44
|
+
// CloudFormation shipped that morning.
|
|
45
|
+
assertPinnedSpec(schemas);
|
|
46
|
+
return schemas;
|
|
39
47
|
},
|
|
40
48
|
|
|
41
49
|
parseSchema: (_typeName, data) => {
|
|
@@ -44,7 +52,7 @@ const awsPipelineConfig: GeneratePipelineConfig<SchemaParseResult> = {
|
|
|
44
52
|
return result;
|
|
45
53
|
},
|
|
46
54
|
|
|
47
|
-
createNaming: (results) => new NamingStrategy(results),
|
|
55
|
+
createNaming: (results) => new NamingStrategy(results, awsReservedNames),
|
|
48
56
|
|
|
49
57
|
augmentSchemas: async (schemas, opts, log) => {
|
|
50
58
|
const warnings: Array<{ file: string; error: string }> = [];
|
|
@@ -131,6 +139,12 @@ const awsPipelineConfig: GeneratePipelineConfig<SchemaParseResult> = {
|
|
|
131
139
|
export async function generate(opts: GenerateOptions = {}): Promise<GenerateResult> {
|
|
132
140
|
// Reset shared state
|
|
133
141
|
awsConstraints = new Map();
|
|
142
|
+
// chant #1459 — names already published keep pointing at the types that
|
|
143
|
+
// published them. Skipped for a caller-supplied schema set, exactly as
|
|
144
|
+
// `augmentSchemas` is (see the pipeline's `opts.schemaSource` guard): a
|
|
145
|
+
// fixture run must depend on its fixtures, not on what the lexicon last
|
|
146
|
+
// shipped.
|
|
147
|
+
awsReservedNames = opts.schemaSource ? {} : publishedNames();
|
|
134
148
|
return generatePipeline(awsPipelineConfig, opts);
|
|
135
149
|
}
|
|
136
150
|
|
package/src/codegen/naming.ts
CHANGED
|
@@ -5,8 +5,11 @@
|
|
|
5
5
|
* for extracting names from AWS CloudFormation types are defined here.
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
9
|
+
import { join } from "node:path";
|
|
8
10
|
import {
|
|
9
11
|
NamingStrategy as CoreNamingStrategy,
|
|
12
|
+
reservedNamesFromSnapshot,
|
|
10
13
|
type NamingConfig,
|
|
11
14
|
type NamingInput,
|
|
12
15
|
} from "@intentius/chant/codegen/naming";
|
|
@@ -110,11 +113,37 @@ const awsNamingConfig: NamingConfig = {
|
|
|
110
113
|
* AWS-specific NamingStrategy — wraps the core algorithm with AWS data tables.
|
|
111
114
|
*/
|
|
112
115
|
export class NamingStrategy extends CoreNamingStrategy {
|
|
113
|
-
|
|
116
|
+
/**
|
|
117
|
+
* @param reservedNames chant #1459 — spec type → already-published TS name,
|
|
118
|
+
* normally {@link publishedNames}. Passed in rather than read here on
|
|
119
|
+
* purpose: a fixture-driven codegen run (`./snapshot.test.ts`) must not
|
|
120
|
+
* inherit the repo's real 1600-entry surface, or its output would depend on
|
|
121
|
+
* whatever the lexicon last shipped instead of on its fixtures.
|
|
122
|
+
*/
|
|
123
|
+
constructor(results: SchemaParseResult[], reservedNames: Record<string, string> = {}) {
|
|
114
124
|
const inputs: NamingInput[] = results.map((r) => ({
|
|
115
125
|
typeName: r.resource.typeName,
|
|
116
126
|
propertyTypes: r.propertyTypes,
|
|
117
127
|
}));
|
|
118
|
-
super(inputs, awsNamingConfig);
|
|
128
|
+
super(inputs, { ...awsNamingConfig, reservedNames });
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* Reserved names from the committed surface snapshot, or none when it cannot
|
|
134
|
+
* be read.
|
|
135
|
+
*
|
|
136
|
+
* Defensive: codegen must still run for a lexicon with no snapshot yet, and a
|
|
137
|
+
* missing or malformed snapshot should degrade to the previous naming
|
|
138
|
+
* behaviour rather than fail generation outright. `surface-diff` makes the
|
|
139
|
+
* consequence visible either way.
|
|
140
|
+
*/
|
|
141
|
+
export function publishedNames(): Record<string, string> {
|
|
142
|
+
try {
|
|
143
|
+
const snapshotPath = join(import.meta.dirname, "..", "..", "surface.snapshot.json");
|
|
144
|
+
if (!existsSync(snapshotPath)) return {};
|
|
145
|
+
return reservedNamesFromSnapshot(JSON.parse(readFileSync(snapshotPath, "utf-8")));
|
|
146
|
+
} catch {
|
|
147
|
+
return {};
|
|
119
148
|
}
|
|
120
149
|
}
|
|
@@ -29,7 +29,7 @@ export interface AlbSharedProps {
|
|
|
29
29
|
};
|
|
30
30
|
}
|
|
31
31
|
|
|
32
|
-
export const AlbShared = Composite
|
|
32
|
+
export const AlbShared = Composite((props: AlbSharedProps) => {
|
|
33
33
|
const listenerPort = props.listenerPort ?? 80;
|
|
34
34
|
const protocol = props.protocol ?? "HTTP";
|
|
35
35
|
const { defaults: defs } = props;
|
|
@@ -22,7 +22,7 @@ const EC2_ASSUME_ROLE = {
|
|
|
22
22
|
],
|
|
23
23
|
};
|
|
24
24
|
|
|
25
|
-
export const Ec2InstanceRole = Composite
|
|
25
|
+
export const Ec2InstanceRole = Composite((props: Ec2InstanceRoleProps) => {
|
|
26
26
|
const { defaults } = props;
|
|
27
27
|
|
|
28
28
|
const role = new Role(mergeDefaults({
|
|
@@ -28,7 +28,7 @@ export interface EfsWithAccessPointProps {
|
|
|
28
28
|
throughputMode?: "bursting" | "provisioned" | "elastic";
|
|
29
29
|
}
|
|
30
30
|
|
|
31
|
-
export const EfsWithAccessPoint = Composite
|
|
31
|
+
export const EfsWithAccessPoint = Composite((props: EfsWithAccessPointProps) => {
|
|
32
32
|
// Ternary (not push-inside-if) keeps the `new`s out of control flow (EVL002).
|
|
33
33
|
const ingressRules: InstanceType<typeof SecurityGroup_Ingress>[] = props.sourceSecurityGroupId
|
|
34
34
|
? [new SecurityGroup_Ingress({ IpProtocol: "tcp", FromPort: 2049, ToPort: 2049, SourceSecurityGroupId: props.sourceSecurityGroupId })]
|
|
@@ -68,7 +68,7 @@ export interface FargateAlbProps {
|
|
|
68
68
|
};
|
|
69
69
|
}
|
|
70
70
|
|
|
71
|
-
export const FargateAlb = Composite
|
|
71
|
+
export const FargateAlb = Composite((props: FargateAlbProps) => {
|
|
72
72
|
const containerPort = props.containerPort ?? 80;
|
|
73
73
|
const cpu = props.cpu ?? "256";
|
|
74
74
|
const memory = props.memory ?? "512";
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Composite, mergeDefaults } from "@intentius/chant";
|
|
1
|
+
import { Composite, mergeDefaults, type Value } from "@intentius/chant";
|
|
2
2
|
import {
|
|
3
3
|
EcsService,
|
|
4
4
|
EcsService_LoadBalancer,
|
|
@@ -33,11 +33,16 @@ import { Sub, Join, Select, Split } from "../intrinsics";
|
|
|
33
33
|
import { ecsTrustPolicy } from "./ecs-trust-policy";
|
|
34
34
|
|
|
35
35
|
export interface FargateServiceProps {
|
|
36
|
-
// Wiring to shared ALB
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
36
|
+
// Wiring to shared ALB.
|
|
37
|
+
//
|
|
38
|
+
// `Value<string>` rather than `string`: these are exactly the props a
|
|
39
|
+
// consuming stack fills from a cross-stack reference, and the documented way
|
|
40
|
+
// to do that is `Ref(param)` — an Intrinsic, not a literal. Typing them
|
|
41
|
+
// `string` made the composite's own examples a type error (#1366).
|
|
42
|
+
clusterArn: Value<string>;
|
|
43
|
+
listenerArn: Value<string>;
|
|
44
|
+
albSecurityGroupId: Value<string>;
|
|
45
|
+
executionRoleArn: Value<string>;
|
|
41
46
|
|
|
42
47
|
// Routing — at least one required
|
|
43
48
|
priority: number;
|
|
@@ -45,7 +50,7 @@ export interface FargateServiceProps {
|
|
|
45
50
|
hostHeaders?: string[];
|
|
46
51
|
|
|
47
52
|
// Container
|
|
48
|
-
image: string
|
|
53
|
+
image: Value<string>;
|
|
49
54
|
containerPort?: number;
|
|
50
55
|
cpu?: string;
|
|
51
56
|
memory?: string;
|
|
@@ -71,8 +76,8 @@ export interface FargateServiceProps {
|
|
|
71
76
|
}>;
|
|
72
77
|
|
|
73
78
|
// Networking
|
|
74
|
-
vpcId: string
|
|
75
|
-
privateSubnetIds: string
|
|
79
|
+
vpcId: Value<string>;
|
|
80
|
+
privateSubnetIds: Array<Value<string>>;
|
|
76
81
|
healthCheckPath?: string;
|
|
77
82
|
|
|
78
83
|
// Ulimits (container-level)
|
|
@@ -90,7 +95,7 @@ export interface FargateServiceProps {
|
|
|
90
95
|
};
|
|
91
96
|
}
|
|
92
97
|
|
|
93
|
-
export const FargateService = Composite
|
|
98
|
+
export const FargateService = Composite((props: FargateServiceProps) => {
|
|
94
99
|
if (!props.pathPatterns && !props.hostHeaders) {
|
|
95
100
|
throw new Error("FargateService requires at least one of pathPatterns or hostHeaders");
|
|
96
101
|
}
|
package/src/composites/index.ts
CHANGED
|
@@ -32,7 +32,7 @@ export { Ec2InstanceRole } from "./ec2-instance-role";
|
|
|
32
32
|
export type { Ec2InstanceRoleProps } from "./ec2-instance-role";
|
|
33
33
|
export { MinimalVpc } from "./minimal-vpc";
|
|
34
34
|
export type { MinimalVpcProps } from "./minimal-vpc";
|
|
35
|
-
export { MicrovmApp } from "./microvm-app";
|
|
36
|
-
export type { MicrovmAppProps, MicrovmAppResult, MicrovmAppBuildConnectorProps } from "./microvm-app";
|
|
35
|
+
export { MicrovmApp, MICROVM_LIMITS } from "./microvm-app";
|
|
36
|
+
export type { MicrovmAppProps, MicrovmAppResult, MicrovmAppBuildConnectorProps, MicrovmMemoryMiB } from "./microvm-app";
|
|
37
37
|
export { AgentCoreAgent } from "./agentcore-agent";
|
|
38
38
|
export type { AgentCoreAgentProps, AgentCoreAgentResult } from "./agentcore-agent";
|
|
@@ -1,9 +1,10 @@
|
|
|
1
|
-
import { Composite, mergeDefaults } from "@intentius/chant";
|
|
1
|
+
import { Composite, mergeDefaults, type Value } from "@intentius/chant";
|
|
2
2
|
import { Permission } from "../generated";
|
|
3
3
|
import { LambdaFunction, type LambdaFunctionProps, type LambdaFunctionResult } from "./lambda-function";
|
|
4
4
|
|
|
5
5
|
export interface LambdaApiProps extends LambdaFunctionProps {
|
|
6
|
-
|
|
6
|
+
/** `Value<string>`: an ARN is routinely built with `Sub` from pseudo-parameters (#1366). */
|
|
7
|
+
sourceArn?: Value<string>;
|
|
7
8
|
defaults?: LambdaFunctionProps["defaults"] & {
|
|
8
9
|
permission?: Partial<ConstructorParameters<typeof Permission>[0]>;
|
|
9
10
|
};
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Composite, mergeDefaults } from "@intentius/chant";
|
|
1
|
+
import { Composite, mergeDefaults, type Value } from "@intentius/chant";
|
|
2
2
|
import {
|
|
3
3
|
Table,
|
|
4
4
|
Table_AttributeDefinition,
|
|
@@ -11,7 +11,8 @@ import { dynamoDBActionsFor } from "../actions/dynamodb";
|
|
|
11
11
|
import { LambdaFunction, type LambdaFunctionProps } from "./lambda-function";
|
|
12
12
|
|
|
13
13
|
export interface LambdaDynamoDBProps extends LambdaFunctionProps {
|
|
14
|
-
|
|
14
|
+
/** `Value<string>`: a name is routinely built with `Sub`/`Ref` (#1366). */
|
|
15
|
+
tableName?: Value<string>;
|
|
15
16
|
partitionKey: string;
|
|
16
17
|
sortKey?: string;
|
|
17
18
|
access?: "ReadOnly" | "ReadWrite" | "Full" | "None";
|
|
@@ -27,7 +28,7 @@ export interface LambdaDynamoDBProps extends LambdaFunctionProps {
|
|
|
27
28
|
};
|
|
28
29
|
}
|
|
29
30
|
|
|
30
|
-
export const LambdaDynamoDB = Composite
|
|
31
|
+
export const LambdaDynamoDB = Composite((props: LambdaDynamoDBProps) => {
|
|
31
32
|
// Conditional entries via spread keep the `new`s out of the `if` (EVL002).
|
|
32
33
|
const attributeDefinitions = [
|
|
33
34
|
new Table_AttributeDefinition({ AttributeName: props.partitionKey, AttributeType: "S" }),
|
|
@@ -1,9 +1,10 @@
|
|
|
1
|
-
import { Composite, mergeDefaults } from "@intentius/chant";
|
|
1
|
+
import { Composite, mergeDefaults, type Value } from "@intentius/chant";
|
|
2
2
|
import { EventRule, EventRule_Target, Permission } from "../generated";
|
|
3
3
|
import { LambdaFunction, type LambdaFunctionProps } from "./lambda-function";
|
|
4
4
|
|
|
5
5
|
export interface LambdaEventBridgeProps extends LambdaFunctionProps {
|
|
6
|
-
|
|
6
|
+
/** `Value<string>`: a name is routinely built with `Sub`/`Ref` (#1366). */
|
|
7
|
+
ruleName?: Value<string>;
|
|
7
8
|
schedule?: string;
|
|
8
9
|
eventPattern?: Record<string, unknown>;
|
|
9
10
|
enabled?: boolean;
|
|
@@ -13,7 +14,7 @@ export interface LambdaEventBridgeProps extends LambdaFunctionProps {
|
|
|
13
14
|
};
|
|
14
15
|
}
|
|
15
16
|
|
|
16
|
-
export const LambdaEventBridge = Composite
|
|
17
|
+
export const LambdaEventBridge = Composite((props: LambdaEventBridgeProps) => {
|
|
17
18
|
const { defaults } = props;
|
|
18
19
|
const { role, func } = LambdaFunction(props);
|
|
19
20
|
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Composite, mergeDefaults } from "@intentius/chant";
|
|
1
|
+
import { Composite, mergeDefaults, type Value } from "@intentius/chant";
|
|
2
2
|
import {
|
|
3
3
|
Bucket,
|
|
4
4
|
Bucket_BucketEncryption,
|
|
@@ -15,7 +15,8 @@ import { s3ActionsFor } from "../actions/s3";
|
|
|
15
15
|
import { LambdaFunction, type LambdaFunctionProps, type LambdaFunctionResult } from "./lambda-function";
|
|
16
16
|
|
|
17
17
|
export interface LambdaS3Props extends LambdaFunctionProps {
|
|
18
|
-
|
|
18
|
+
/** `Value<string>`: a name is routinely built with `Sub`/`Ref` (#1366). */
|
|
19
|
+
bucketName?: Value<string>;
|
|
19
20
|
access?: "ReadOnly" | "ReadWrite" | "Full";
|
|
20
21
|
trigger?: {
|
|
21
22
|
events?: string[];
|
|
@@ -1,9 +1,10 @@
|
|
|
1
|
-
import { Composite, mergeDefaults } from "@intentius/chant";
|
|
1
|
+
import { Composite, mergeDefaults, type Value } from "@intentius/chant";
|
|
2
2
|
import { Topic, Subscription, Permission } from "../generated";
|
|
3
3
|
import { LambdaFunction, type LambdaFunctionProps } from "./lambda-function";
|
|
4
4
|
|
|
5
5
|
export interface LambdaSnsProps extends LambdaFunctionProps {
|
|
6
|
-
|
|
6
|
+
/** `Value<string>`: a name is routinely built with `Sub`/`Ref` (#1366). */
|
|
7
|
+
topicName?: Value<string>;
|
|
7
8
|
defaults?: LambdaFunctionProps["defaults"] & {
|
|
8
9
|
topic?: Partial<ConstructorParameters<typeof Topic>[0]>;
|
|
9
10
|
subscription?: Partial<ConstructorParameters<typeof Subscription>[0]>;
|
|
@@ -11,7 +12,7 @@ export interface LambdaSnsProps extends LambdaFunctionProps {
|
|
|
11
12
|
};
|
|
12
13
|
}
|
|
13
14
|
|
|
14
|
-
export const LambdaSns = Composite
|
|
15
|
+
export const LambdaSns = Composite((props: LambdaSnsProps) => {
|
|
15
16
|
const { defaults } = props;
|
|
16
17
|
const { role, func } = LambdaFunction(props);
|
|
17
18
|
|
|
@@ -1,10 +1,11 @@
|
|
|
1
|
-
import { Composite, mergeDefaults } from "@intentius/chant";
|
|
1
|
+
import { Composite, mergeDefaults, type Value } from "@intentius/chant";
|
|
2
2
|
import { Queue, EventSourceMapping, Role_Policy } from "../generated";
|
|
3
3
|
import { SQSActions } from "../actions/sqs";
|
|
4
4
|
import { LambdaFunction, type LambdaFunctionProps } from "./lambda-function";
|
|
5
5
|
|
|
6
6
|
export interface LambdaSqsProps extends LambdaFunctionProps {
|
|
7
|
-
|
|
7
|
+
/** `Value<string>`: a name is routinely built with `Sub`/`Ref` (#1366). */
|
|
8
|
+
queueName?: Value<string>;
|
|
8
9
|
batchSize?: number;
|
|
9
10
|
maxBatchingWindow?: number;
|
|
10
11
|
defaults?: LambdaFunctionProps["defaults"] & {
|
|
@@ -13,7 +14,7 @@ export interface LambdaSqsProps extends LambdaFunctionProps {
|
|
|
13
14
|
};
|
|
14
15
|
}
|
|
15
16
|
|
|
16
|
-
export const LambdaSqs = Composite
|
|
17
|
+
export const LambdaSqs = Composite((props: LambdaSqsProps) => {
|
|
17
18
|
const { defaults } = props;
|
|
18
19
|
|
|
19
20
|
const queue = new Queue(mergeDefaults({
|
|
@@ -3,7 +3,7 @@ import { expandComposite } from "@intentius/chant";
|
|
|
3
3
|
import { AttrRef } from "@intentius/chant/attrref";
|
|
4
4
|
import { resolveAttrRefs } from "@intentius/chant/discovery/resolve";
|
|
5
5
|
import { awsSerializer } from "../serializer";
|
|
6
|
-
import { MicrovmApp } from "./microvm-app";
|
|
6
|
+
import { MicrovmApp, MICROVM_LIMITS } from "./microvm-app";
|
|
7
7
|
|
|
8
8
|
const baseProps = {
|
|
9
9
|
name: "worker-image",
|
|
@@ -232,3 +232,41 @@ describe("MicrovmApp", () => {
|
|
|
232
232
|
});
|
|
233
233
|
});
|
|
234
234
|
});
|
|
235
|
+
|
|
236
|
+
describe("MICROVM_LIMITS", () => {
|
|
237
|
+
// The composite validates against these; a consumer driving the same service
|
|
238
|
+
// through a different control plane needs the same numbers, and copying them
|
|
239
|
+
// is how two sources of truth start (#1374).
|
|
240
|
+
test("is reachable from the package root", async () => {
|
|
241
|
+
const root = await import("../index");
|
|
242
|
+
expect(root.MICROVM_LIMITS).toBe(MICROVM_LIMITS);
|
|
243
|
+
});
|
|
244
|
+
|
|
245
|
+
test("is what the composite actually enforces", () => {
|
|
246
|
+
// Not a restatement of the constants — a probe through the public API, so
|
|
247
|
+
// the two cannot drift apart while both look right.
|
|
248
|
+
expect(() => MicrovmApp({ ...baseProps, memoryMiB: 3072 as never })).toThrow(
|
|
249
|
+
new RegExp(MICROVM_LIMITS.memoryMiB.join(", ")),
|
|
250
|
+
);
|
|
251
|
+
expect(() => MicrovmApp({ ...baseProps, name: "no spaces allowed" })).toThrow(/name must match/);
|
|
252
|
+
expect(() =>
|
|
253
|
+
MicrovmApp({ ...baseProps, name: "x".repeat(MICROVM_LIMITS.maxNameLength + 1) }),
|
|
254
|
+
).toThrow(/≤64 chars/);
|
|
255
|
+
expect(() =>
|
|
256
|
+
MicrovmApp({ ...baseProps, environment: { AWS_REGION: "us-east-1" } }),
|
|
257
|
+
).toThrow(/AWS_REGION/);
|
|
258
|
+
});
|
|
259
|
+
|
|
260
|
+
test("names every limit the composite checks", () => {
|
|
261
|
+
// A limit enforced and not named here is one a consumer cannot see.
|
|
262
|
+
expect(Object.keys(MICROVM_LIMITS).sort()).toEqual([
|
|
263
|
+
"connectorSubnets",
|
|
264
|
+
"maxEgressConnectors",
|
|
265
|
+
"maxEnvironmentVariables",
|
|
266
|
+
"maxNameLength",
|
|
267
|
+
"memoryMiB",
|
|
268
|
+
"namePattern",
|
|
269
|
+
"reservedEnvironmentKeys",
|
|
270
|
+
]);
|
|
271
|
+
});
|
|
272
|
+
});
|
|
@@ -47,17 +47,38 @@ import { Sub } from "../intrinsics";
|
|
|
47
47
|
const MICROVM_SERVICE_PRINCIPAL = "lambda.amazonaws.com";
|
|
48
48
|
const CONNECTOR_MANAGED_RESOURCE_OPERATOR = "network-connectors.lambda.amazonaws.com";
|
|
49
49
|
|
|
50
|
-
/**
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
const
|
|
60
|
-
|
|
50
|
+
/**
|
|
51
|
+
* The Lambda MicroVMs service's real limits, verified against the upstream
|
|
52
|
+
* `ran-isenberg/lambda-microvm-cdk-python` construct and the AWS docs.
|
|
53
|
+
*
|
|
54
|
+
* Exported because a consumer driving the same service through a different
|
|
55
|
+
* control plane needs the same numbers, and copying them is how two sources of
|
|
56
|
+
* truth start (#1374). The CFN schema types most of these as open ints and
|
|
57
|
+
* strings, so nothing but this object knows them.
|
|
58
|
+
*/
|
|
59
|
+
export const MICROVM_LIMITS = {
|
|
60
|
+
/** Documented baseline memory tiers (MiB). The schema says int; the service accepts five values. vCPU auto-scales with the tier. */
|
|
61
|
+
memoryMiB: [512, 1024, 2048, 4096, 8192],
|
|
62
|
+
namePattern: /^[a-zA-Z0-9-_]+$/,
|
|
63
|
+
maxNameLength: 64,
|
|
64
|
+
/** Set by the service on every MicroVM; supplying it is rejected. */
|
|
65
|
+
reservedEnvironmentKeys: ["AWS_REGION"],
|
|
66
|
+
maxEnvironmentVariables: 50,
|
|
67
|
+
maxEgressConnectors: 10,
|
|
68
|
+
connectorSubnets: { min: 1, max: 16 },
|
|
69
|
+
} as const;
|
|
70
|
+
|
|
71
|
+
/** One of the five memory tiers {@link MICROVM_LIMITS} names. */
|
|
72
|
+
export type MicrovmMemoryMiB = (typeof MICROVM_LIMITS.memoryMiB)[number];
|
|
73
|
+
|
|
74
|
+
const VALID_MEMORY_MIB = MICROVM_LIMITS.memoryMiB;
|
|
75
|
+
const NAME_PATTERN = MICROVM_LIMITS.namePattern;
|
|
76
|
+
const MAX_NAME_LENGTH = MICROVM_LIMITS.maxNameLength;
|
|
77
|
+
const RESERVED_ENV_KEYS = new Set<string>(MICROVM_LIMITS.reservedEnvironmentKeys);
|
|
78
|
+
const MAX_ENVIRONMENT_VARIABLES = MICROVM_LIMITS.maxEnvironmentVariables;
|
|
79
|
+
const MAX_EGRESS_CONNECTORS = MICROVM_LIMITS.maxEgressConnectors;
|
|
80
|
+
const MIN_CONNECTOR_SUBNETS = MICROVM_LIMITS.connectorSubnets.min;
|
|
81
|
+
const MAX_CONNECTOR_SUBNETS = MICROVM_LIMITS.connectorSubnets.max;
|
|
61
82
|
|
|
62
83
|
/**
|
|
63
84
|
* `sts:AssumeRole` + `sts:TagSession` trust for `lambda.amazonaws.com` — the
|
|
@@ -21,7 +21,7 @@ export interface MinimalVpcProps {
|
|
|
21
21
|
};
|
|
22
22
|
}
|
|
23
23
|
|
|
24
|
-
export const MinimalVpc = Composite
|
|
24
|
+
export const MinimalVpc = Composite((props: MinimalVpcProps) => {
|
|
25
25
|
const { defaults } = props;
|
|
26
26
|
const cidr = props.cidr ?? "10.0.0.0/24";
|
|
27
27
|
const subnetCidr = props.subnetCidr ?? "10.0.0.0/25";
|
|
@@ -76,7 +76,7 @@ export interface RdsInstanceProps {
|
|
|
76
76
|
};
|
|
77
77
|
}
|
|
78
78
|
|
|
79
|
-
export const RdsInstance = Composite
|
|
79
|
+
export const RdsInstance = Composite((props: RdsInstanceProps) => {
|
|
80
80
|
const engine = props.engine ?? "postgres";
|
|
81
81
|
const defaults = ENGINE_DEFAULTS.get(engine)!;
|
|
82
82
|
const port = props.port ?? defaults.port;
|