@intentius/chant-lexicon-aws 0.46.0 → 0.50.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/api/read-client.d.ts +10 -2
- package/dist/api/read-client.d.ts.map +1 -1
- package/dist/codegen/generate-lexicon.d.ts +5 -0
- package/dist/codegen/generate-lexicon.d.ts.map +1 -1
- package/dist/components/host-delivery.d.ts +4 -3
- package/dist/components/host-delivery.d.ts.map +1 -1
- package/dist/composites/agentcore-agent.d.ts +26 -1
- package/dist/composites/agentcore-agent.d.ts.map +1 -1
- package/dist/composites/bucket-deployment.d.ts +82 -0
- package/dist/composites/bucket-deployment.d.ts.map +1 -0
- package/dist/composites/dynamodb-table.d.ts +45 -0
- package/dist/composites/dynamodb-table.d.ts.map +1 -0
- package/dist/composites/ec2-instance-bundle.d.ts +61 -0
- package/dist/composites/ec2-instance-bundle.d.ts.map +1 -0
- package/dist/composites/ecr-repository.d.ts +49 -0
- package/dist/composites/ecr-repository.d.ts.map +1 -0
- package/dist/composites/fargate-service.d.ts +16 -0
- package/dist/composites/fargate-service.d.ts.map +1 -1
- package/dist/composites/index.d.ts +16 -2
- package/dist/composites/index.d.ts.map +1 -1
- package/dist/composites/monitoring-stack.d.ts +99 -0
- package/dist/composites/monitoring-stack.d.ts.map +1 -0
- package/dist/composites/nlb-service.d.ts +48 -0
- package/dist/composites/nlb-service.d.ts.map +1 -0
- package/dist/composites/step-functions-workflow.d.ts +36 -0
- package/dist/composites/step-functions-workflow.d.ts.map +1 -0
- package/dist/deep-observe.d.ts +45 -0
- package/dist/deep-observe.d.ts.map +1 -1
- package/dist/deep-topology.d.ts +104 -0
- package/dist/deep-topology.d.ts.map +1 -0
- package/dist/effect-receipt-row.d.ts +92 -0
- package/dist/effect-receipt-row.d.ts.map +1 -0
- package/dist/index.d.ts +7 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/integrity.json +14 -6
- package/dist/lint/audit-catalog.d.ts.map +1 -1
- package/dist/lint/post-synth/cf-refs.d.ts +17 -0
- package/dist/lint/post-synth/cf-refs.d.ts.map +1 -1
- package/dist/lint/post-synth/index.d.ts.map +1 -1
- package/dist/lint/post-synth/waw016.d.ts +10 -2
- package/dist/lint/post-synth/waw016.d.ts.map +1 -1
- package/dist/lint/post-synth/waw061.d.ts +19 -0
- package/dist/lint/post-synth/waw061.d.ts.map +1 -0
- package/dist/lint/post-synth/waw062.d.ts +19 -0
- package/dist/lint/post-synth/waw062.d.ts.map +1 -0
- package/dist/lint/post-synth/waw063.d.ts +35 -0
- package/dist/lint/post-synth/waw063.d.ts.map +1 -0
- package/dist/lint/post-synth/waw064.d.ts +17 -0
- package/dist/lint/post-synth/waw064.d.ts.map +1 -0
- package/dist/lint/post-synth/waw065.d.ts +23 -0
- package/dist/lint/post-synth/waw065.d.ts.map +1 -0
- package/dist/lint/post-synth/waw066.d.ts +25 -0
- package/dist/lint/post-synth/waw066.d.ts.map +1 -0
- package/dist/lint/post-synth/waw067.d.ts +24 -0
- package/dist/lint/post-synth/waw067.d.ts.map +1 -0
- package/dist/lint/post-synth/waw068.d.ts +20 -0
- package/dist/lint/post-synth/waw068.d.ts.map +1 -0
- package/dist/manifest.json +1 -1
- package/dist/meta.json +34 -0
- package/dist/okf/index.md +8 -0
- package/dist/okf/rules/WAW061.md +17 -0
- package/dist/okf/rules/WAW062.md +27 -0
- package/dist/okf/rules/WAW063.md +19 -0
- package/dist/okf/rules/WAW064.md +18 -0
- package/dist/okf/rules/WAW065.md +25 -0
- package/dist/okf/rules/WAW066.md +22 -0
- package/dist/okf/rules/WAW067.md +20 -0
- package/dist/okf/rules/WAW068.md +19 -0
- package/dist/okf/types/Action.md +1 -0
- package/dist/okf/types/AutoScalingGroup.md +1 -0
- package/dist/okf/types/Bucket.md +1 -0
- package/dist/okf/types/EC2Route.md +2 -0
- package/dist/okf/types/ECRRepository.md +1 -0
- package/dist/okf/types/Function.md +1 -0
- package/dist/okf/types/IamPolicy.md +1 -0
- package/dist/okf/types/InternetGateway.md +4 -0
- package/dist/okf/types/LaunchTemplate.md +1 -0
- package/dist/okf/types/LoadBalancer.md +1 -0
- package/dist/okf/types/ManagedPolicy.md +2 -0
- package/dist/okf/types/Map.md +5 -0
- package/dist/okf/types/NatGateway.md +4 -0
- package/dist/okf/types/Role.md +2 -0
- package/dist/okf/types/RouteTable.md +4 -0
- package/dist/okf/types/SecurityGroup.md +1 -0
- package/dist/okf/types/StateMachine.md +4 -0
- package/dist/okf/types/Subnet.md +6 -0
- package/dist/okf/types/SubnetRouteTableAssociation.md +5 -0
- package/dist/okf/types/Table.md +4 -0
- package/dist/okf/types/TransitGateway.md +6 -0
- package/dist/okf/types/TransitGatewayAttachment.md +4 -0
- package/dist/okf/types/TransitGatewayPeeringAttachment.md +4 -0
- package/dist/okf/types/TransitGatewayRoute.md +5 -0
- package/dist/okf/types/TransitGatewayRouteTable.md +4 -0
- package/dist/okf/types/TransitGatewayRouteTableAssociation.md +4 -0
- package/dist/okf/types/TransitGatewayRouteTablePropagation.md +4 -0
- package/dist/okf/types/TransitGatewayVpcAttachment.md +4 -0
- package/dist/okf/types/Type.md +8 -0
- package/dist/okf/types/VPNConnection.md +4 -0
- package/dist/okf/types/VPNGateway.md +4 -0
- package/dist/okf/types/Vpc.md +1 -0
- package/dist/op/activities/index.d.ts +6 -0
- package/dist/op/activities/index.d.ts.map +1 -1
- package/dist/op/builders.d.ts +47 -0
- package/dist/op/builders.d.ts.map +1 -0
- package/dist/plugin.d.ts.map +1 -1
- package/dist/receipt-store.d.ts +109 -0
- package/dist/receipt-store.d.ts.map +1 -0
- package/dist/rules/cf-refs.ts +40 -0
- package/dist/rules/waw016.ts +37 -16
- package/dist/rules/waw061.ts +67 -0
- package/dist/rules/waw062.ts +0 -0
- package/dist/rules/waw063.ts +0 -0
- package/dist/rules/waw064.ts +52 -0
- package/dist/rules/waw065.ts +110 -0
- package/dist/rules/waw066.ts +155 -0
- package/dist/rules/waw067.ts +114 -0
- package/dist/rules/waw068.ts +78 -0
- package/dist/serializer.d.ts.map +1 -1
- package/dist/skills/chant-aws-carve-terraform.md +26 -23
- package/dist/spec/parse.d.ts +7 -0
- package/dist/spec/parse.d.ts.map +1 -1
- package/package.json +3 -3
- package/src/api/read-client.test.ts +9 -0
- package/src/api/read-client.ts +15 -2
- package/src/codegen/fallback.ts +1 -0
- package/src/codegen/generate-lexicon.ts +6 -0
- package/src/codegen/sam.ts +9 -0
- package/src/components/host-delivery.ts +4 -3
- package/src/composites/agentcore-agent.ts +35 -13
- package/src/composites/bucket-deployment.test.ts +84 -0
- package/src/composites/bucket-deployment.ts +153 -0
- package/src/composites/composites.test.ts +325 -0
- package/src/composites/dynamodb-table.test.ts +154 -0
- package/src/composites/dynamodb-table.ts +124 -0
- package/src/composites/ec2-instance-bundle.ts +134 -0
- package/src/composites/ecr-repository.test.ts +118 -0
- package/src/composites/ecr-repository.ts +117 -0
- package/src/composites/fargate-service.ts +19 -3
- package/src/composites/index.ts +21 -2
- package/src/composites/monitoring-stack.test.ts +130 -0
- package/src/composites/monitoring-stack.ts +164 -0
- package/src/composites/nlb-service.ts +117 -0
- package/src/composites/step-functions-workflow.ts +127 -0
- package/src/deep-observe.test.ts +201 -1
- package/src/deep-observe.ts +204 -0
- package/src/deep-topology.test.ts +392 -0
- package/src/deep-topology.ts +237 -0
- package/src/effect-receipt-row.test.ts +164 -0
- package/src/effect-receipt-row.ts +147 -0
- package/src/generated/lexicon-aws.json +34 -0
- package/src/index.ts +36 -7
- package/src/lint/audit-catalog.ts +16 -0
- package/src/lint/post-synth/cf-refs.ts +40 -0
- package/src/lint/post-synth/index.ts +16 -0
- package/src/lint/post-synth/waw016.test.ts +61 -4
- package/src/lint/post-synth/waw016.ts +37 -16
- package/src/lint/post-synth/waw061.test.ts +99 -0
- package/src/lint/post-synth/waw061.ts +67 -0
- package/src/lint/post-synth/waw062.test.ts +109 -0
- package/src/lint/post-synth/waw062.ts +0 -0
- package/src/lint/post-synth/waw063.test.ts +220 -0
- package/src/lint/post-synth/waw063.ts +0 -0
- package/src/lint/post-synth/waw064.test.ts +79 -0
- package/src/lint/post-synth/waw064.ts +52 -0
- package/src/lint/post-synth/waw065.test.ts +90 -0
- package/src/lint/post-synth/waw065.ts +110 -0
- package/src/lint/post-synth/waw066.test.ts +108 -0
- package/src/lint/post-synth/waw066.ts +155 -0
- package/src/lint/post-synth/waw067.test.ts +79 -0
- package/src/lint/post-synth/waw067.ts +114 -0
- package/src/lint/post-synth/waw068.test.ts +74 -0
- package/src/lint/post-synth/waw068.ts +78 -0
- package/src/lsp/hover.ts +17 -2
- package/src/op/activities/index.ts +15 -0
- package/src/op/builders.test.ts +61 -0
- package/src/op/builders.ts +72 -0
- package/src/plugin.ts +41 -10
- package/src/receipt-store.test.ts +204 -0
- package/src/receipt-store.ts +315 -0
- package/src/serializer.ts +105 -2
- package/src/skills/chant-aws-carve-terraform.md +26 -23
- package/src/spec/parse.test.ts +74 -0
- package/src/spec/parse.ts +26 -6
|
@@ -26,6 +26,11 @@ export interface LexiconEntry {
|
|
|
26
26
|
writeOnly?: string[];
|
|
27
27
|
primaryIdentifier?: string[];
|
|
28
28
|
deprecatedProperties?: string[];
|
|
29
|
+
/**
|
|
30
|
+
* Subset of `deprecatedProperties` that only a regex over the property
|
|
31
|
+
* description supports; the rest is declared by the Registry schema (#1701).
|
|
32
|
+
*/
|
|
33
|
+
inferredDeprecations?: string[];
|
|
29
34
|
conditionalCreateOnly?: string[];
|
|
30
35
|
replacementStrategy?: "delete_then_create" | "create_then_delete";
|
|
31
36
|
tagging?: { taggable: boolean; tagOnCreate: boolean; tagUpdatable: boolean };
|
|
@@ -72,6 +77,7 @@ export function generateLexiconJSON(
|
|
|
72
77
|
...(r.resource.writeOnly.length > 0 && { writeOnly: r.resource.writeOnly }),
|
|
73
78
|
...(r.resource.primaryIdentifier.length > 0 && { primaryIdentifier: r.resource.primaryIdentifier }),
|
|
74
79
|
...(r.resource.deprecatedProperties?.length && { deprecatedProperties: r.resource.deprecatedProperties }),
|
|
80
|
+
...(r.resource.inferredDeprecations?.length && { inferredDeprecations: r.resource.inferredDeprecations }),
|
|
75
81
|
...(r.resource.conditionalCreateOnly?.length && { conditionalCreateOnly: r.resource.conditionalCreateOnly }),
|
|
76
82
|
...(r.resource.replacementStrategy && { replacementStrategy: r.resource.replacementStrategy }),
|
|
77
83
|
...(r.resource.tagging && { tagging: r.resource.tagging }),
|
package/src/codegen/sam.ts
CHANGED
|
@@ -59,6 +59,7 @@ function samFunction(): SchemaParseResult {
|
|
|
59
59
|
writeOnly: [],
|
|
60
60
|
primaryIdentifier: [],
|
|
61
61
|
deprecatedProperties: [],
|
|
62
|
+
inferredDeprecations: [],
|
|
62
63
|
conditionalCreateOnly: [],
|
|
63
64
|
},
|
|
64
65
|
propertyTypes: [
|
|
@@ -147,6 +148,7 @@ function samApi(): SchemaParseResult {
|
|
|
147
148
|
writeOnly: [],
|
|
148
149
|
primaryIdentifier: [],
|
|
149
150
|
deprecatedProperties: [],
|
|
151
|
+
inferredDeprecations: [],
|
|
150
152
|
conditionalCreateOnly: [],
|
|
151
153
|
},
|
|
152
154
|
propertyTypes: [
|
|
@@ -200,6 +202,7 @@ function samHttpApi(): SchemaParseResult {
|
|
|
200
202
|
writeOnly: [],
|
|
201
203
|
primaryIdentifier: [],
|
|
202
204
|
deprecatedProperties: [],
|
|
205
|
+
inferredDeprecations: [],
|
|
203
206
|
conditionalCreateOnly: [],
|
|
204
207
|
},
|
|
205
208
|
propertyTypes: [
|
|
@@ -236,6 +239,7 @@ function samSimpleTable(): SchemaParseResult {
|
|
|
236
239
|
writeOnly: [],
|
|
237
240
|
primaryIdentifier: [],
|
|
238
241
|
deprecatedProperties: [],
|
|
242
|
+
inferredDeprecations: [],
|
|
239
243
|
conditionalCreateOnly: [],
|
|
240
244
|
},
|
|
241
245
|
propertyTypes: [
|
|
@@ -273,6 +277,7 @@ function samLayerVersion(): SchemaParseResult {
|
|
|
273
277
|
writeOnly: [],
|
|
274
278
|
primaryIdentifier: [],
|
|
275
279
|
deprecatedProperties: [],
|
|
280
|
+
inferredDeprecations: [],
|
|
276
281
|
conditionalCreateOnly: [],
|
|
277
282
|
},
|
|
278
283
|
propertyTypes: [],
|
|
@@ -306,6 +311,7 @@ function samStateMachine(): SchemaParseResult {
|
|
|
306
311
|
writeOnly: [],
|
|
307
312
|
primaryIdentifier: [],
|
|
308
313
|
deprecatedProperties: [],
|
|
314
|
+
inferredDeprecations: [],
|
|
309
315
|
conditionalCreateOnly: [],
|
|
310
316
|
},
|
|
311
317
|
propertyTypes: [
|
|
@@ -339,6 +345,7 @@ function samApplication(): SchemaParseResult {
|
|
|
339
345
|
writeOnly: [],
|
|
340
346
|
primaryIdentifier: [],
|
|
341
347
|
deprecatedProperties: [],
|
|
348
|
+
inferredDeprecations: [],
|
|
342
349
|
conditionalCreateOnly: [],
|
|
343
350
|
},
|
|
344
351
|
propertyTypes: [],
|
|
@@ -360,6 +367,7 @@ function samConnector(): SchemaParseResult {
|
|
|
360
367
|
writeOnly: [],
|
|
361
368
|
primaryIdentifier: [],
|
|
362
369
|
deprecatedProperties: [],
|
|
370
|
+
inferredDeprecations: [],
|
|
363
371
|
conditionalCreateOnly: [],
|
|
364
372
|
},
|
|
365
373
|
propertyTypes: [],
|
|
@@ -397,6 +405,7 @@ function samGraphQLApi(): SchemaParseResult {
|
|
|
397
405
|
writeOnly: [],
|
|
398
406
|
primaryIdentifier: [],
|
|
399
407
|
deprecatedProperties: [],
|
|
408
|
+
inferredDeprecations: [],
|
|
400
409
|
conditionalCreateOnly: [],
|
|
401
410
|
},
|
|
402
411
|
propertyTypes: [],
|
|
@@ -73,9 +73,10 @@ function resolveTargets(
|
|
|
73
73
|
* Deploy a revision to a host (fleet) via AWS CodeDeploy: create the
|
|
74
74
|
* deployment, then wait for a terminal status. Rollback re-invokes
|
|
75
75
|
* `stopAndRollback` against the deployment id this step created (captured in
|
|
76
|
-
* a closure-scoped map keyed by application/deploymentGroup
|
|
77
|
-
*
|
|
78
|
-
*
|
|
76
|
+
* a closure-scoped map keyed by application/deploymentGroup; the driver now
|
|
77
|
+
* also passes `run`'s output as `rollback`'s optional third arg (#1944), but
|
|
78
|
+
* this capability's input-derived key needs neither channel to resolve
|
|
79
|
+
* identity) — riding CodeDeploy's own native automatic rollback to the
|
|
79
80
|
* last known-good revision rather than a chant-scripted compensation, the
|
|
80
81
|
* "auto" rollback style the Neo4j pilot's docs comment contrasts with the
|
|
81
82
|
* ALB/ECS pilot's component-declared one.
|
|
@@ -45,19 +45,36 @@ export function agentCoreDefaultEndpointArn(runtime: InstanceType<typeof Runtime
|
|
|
45
45
|
}
|
|
46
46
|
|
|
47
47
|
/**
|
|
48
|
-
* AgentCore's
|
|
49
|
-
*
|
|
50
|
-
*
|
|
51
|
-
*
|
|
52
|
-
*
|
|
53
|
-
*
|
|
54
|
-
*
|
|
55
|
-
|
|
48
|
+
* Bedrock AgentCore's real limits, verified against the CloudFormation
|
|
49
|
+
* registry schema for `AWS::BedrockAgentCore::{Runtime,RuntimeEndpoint,Memory}`
|
|
50
|
+
* (#882).
|
|
51
|
+
*
|
|
52
|
+
* Exported for the same reason as `MICROVM_LIMITS` (#1374, #1420): a
|
|
53
|
+
* consumer driving AgentCore through another control plane needs the same
|
|
54
|
+
* numbers, and copying them is how two sources of truth start.
|
|
55
|
+
*/
|
|
56
|
+
export const AGENTCORE_LIMITS = {
|
|
57
|
+
/**
|
|
58
|
+
* `Runtime`/`RuntimeEndpoint`/`Memory` `Name`/`AgentRuntimeName` fields
|
|
59
|
+
* are bound to this pattern — no hyphens, unlike chant's usual kebab-case
|
|
60
|
+
* component names. `Gateway`/`GatewayTarget`/`WorkloadIdentity` names
|
|
61
|
+
* accept hyphens, so only the Runtime family needs sanitizing.
|
|
62
|
+
*/
|
|
63
|
+
runtimeNamePattern: /^[a-zA-Z][a-zA-Z0-9_]{0,47}$/,
|
|
64
|
+
maxRuntimeNameLength: 48,
|
|
65
|
+
/** `Memory` event retention bounds, in days. */
|
|
66
|
+
memoryEventExpiryDays: { min: 3, max: 365, default: 30 },
|
|
67
|
+
} as const;
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Non-identifier characters become `_`, and a leading digit/underscore gets
|
|
71
|
+
* an `A` prefix so the result always starts with a letter — then truncated
|
|
72
|
+
* to {@link AGENTCORE_LIMITS}'s `maxRuntimeNameLength`.
|
|
56
73
|
*/
|
|
57
74
|
function toRuntimeIdentifier(name: string): string {
|
|
58
75
|
const cleaned = name.replace(/[^a-zA-Z0-9_]/g, "_");
|
|
59
76
|
const identifier = /^[a-zA-Z]/.test(cleaned) ? cleaned : `A${cleaned}`;
|
|
60
|
-
return identifier.slice(0,
|
|
77
|
+
return identifier.slice(0, AGENTCORE_LIMITS.maxRuntimeNameLength);
|
|
61
78
|
}
|
|
62
79
|
|
|
63
80
|
/**
|
|
@@ -117,7 +134,7 @@ export interface AgentCoreAgentProps {
|
|
|
117
134
|
* managed one and fails on a real apply (#978, see the composite doc).
|
|
118
135
|
*/
|
|
119
136
|
endpointName?: string;
|
|
120
|
-
/** Memory event retention, in days.
|
|
137
|
+
/** Memory event retention, in days. Bounds: {@link AGENTCORE_LIMITS.memoryEventExpiryDays}. */
|
|
121
138
|
memoryEventExpiryDays?: number;
|
|
122
139
|
/** Gateway authorizer. Mirrors the generated `BedrockAgentCoreGateway_AuthorizerType` CFN enum. Default: "AWS_IAM". */
|
|
123
140
|
gatewayAuthorizerType?: "CUSTOM_JWT" | "AWS_IAM" | "NONE" | "AUTHENTICATE_ONLY";
|
|
@@ -194,9 +211,14 @@ export const AgentCoreAgent = Composite<AgentCoreAgentProps, AgentCoreAgentResul
|
|
|
194
211
|
if (networkMode === "VPC" && (props.vpcSubnetIds === undefined || props.vpcSecurityGroupIds === undefined)) {
|
|
195
212
|
throw new Error("AgentCoreAgent requires vpcSubnetIds and vpcSecurityGroupIds when networkMode is \"VPC\"");
|
|
196
213
|
}
|
|
197
|
-
const memoryEventExpiryDays = props.memoryEventExpiryDays ??
|
|
198
|
-
if (
|
|
199
|
-
|
|
214
|
+
const memoryEventExpiryDays = props.memoryEventExpiryDays ?? AGENTCORE_LIMITS.memoryEventExpiryDays.default;
|
|
215
|
+
if (
|
|
216
|
+
memoryEventExpiryDays < AGENTCORE_LIMITS.memoryEventExpiryDays.min ||
|
|
217
|
+
memoryEventExpiryDays > AGENTCORE_LIMITS.memoryEventExpiryDays.max
|
|
218
|
+
) {
|
|
219
|
+
throw new Error(
|
|
220
|
+
`AgentCoreAgent memoryEventExpiryDays must be between ${AGENTCORE_LIMITS.memoryEventExpiryDays.min} and ${AGENTCORE_LIMITS.memoryEventExpiryDays.max}`,
|
|
221
|
+
);
|
|
200
222
|
}
|
|
201
223
|
|
|
202
224
|
const runtimeName = toRuntimeIdentifier(props.name);
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import { describe, test, expect } from "vitest";
|
|
2
|
+
import { BucketDeployment } from "./bucket-deployment";
|
|
3
|
+
|
|
4
|
+
describe("BucketDeployment", () => {
|
|
5
|
+
test("private-by-default: encrypted, public access fully blocked, retained on delete, no policy member", () => {
|
|
6
|
+
const c = BucketDeployment({});
|
|
7
|
+
expect(c.bucket).toBeDefined();
|
|
8
|
+
expect((c as unknown as Record<string, unknown>).bucketPolicy).toBeUndefined();
|
|
9
|
+
|
|
10
|
+
const props = (c.bucket as any).props;
|
|
11
|
+
expect(props.BucketEncryption.props.ServerSideEncryptionConfiguration).toHaveLength(1);
|
|
12
|
+
expect(props.PublicAccessBlockConfiguration.props).toEqual({
|
|
13
|
+
BlockPublicAcls: true,
|
|
14
|
+
BlockPublicPolicy: true,
|
|
15
|
+
IgnorePublicAcls: true,
|
|
16
|
+
RestrictPublicBuckets: true,
|
|
17
|
+
});
|
|
18
|
+
expect(props.VersioningConfiguration).toBeUndefined();
|
|
19
|
+
expect(props.WebsiteConfiguration).toBeUndefined();
|
|
20
|
+
|
|
21
|
+
const attrs = (c.bucket as any).attributes;
|
|
22
|
+
expect(attrs.DeletionPolicy).toBe("Retain");
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
test("bucketName threads through, versioning enables Status: Enabled", () => {
|
|
26
|
+
const c = BucketDeployment({ bucketName: "my-content-bucket", versioned: true });
|
|
27
|
+
const props = (c.bucket as any).props;
|
|
28
|
+
expect(props.BucketName).toBe("my-content-bucket");
|
|
29
|
+
expect(props.VersioningConfiguration.props).toEqual({ Status: "Enabled" });
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
test("removalPolicy: destroy maps to DeletionPolicy: Delete", () => {
|
|
33
|
+
const c = BucketDeployment({ removalPolicy: "destroy" });
|
|
34
|
+
expect((c.bucket as any).attributes.DeletionPolicy).toBe("Delete");
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
test("website shape: opens the public-access block, sets WebsiteConfiguration, and attaches a scoped public-read bucketPolicy", () => {
|
|
38
|
+
const c = BucketDeployment({
|
|
39
|
+
website: { indexDocument: "index.html", errorDocument: "error.html" },
|
|
40
|
+
}) as unknown as { bucket: any; bucketPolicy: any };
|
|
41
|
+
|
|
42
|
+
const bucketProps = c.bucket.props;
|
|
43
|
+
expect(bucketProps.PublicAccessBlockConfiguration.props).toEqual({
|
|
44
|
+
BlockPublicAcls: false,
|
|
45
|
+
BlockPublicPolicy: false,
|
|
46
|
+
IgnorePublicAcls: false,
|
|
47
|
+
RestrictPublicBuckets: false,
|
|
48
|
+
});
|
|
49
|
+
expect(bucketProps.WebsiteConfiguration.props).toEqual({
|
|
50
|
+
IndexDocument: "index.html",
|
|
51
|
+
ErrorDocument: "error.html",
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
expect(c.bucketPolicy).toBeDefined();
|
|
55
|
+
expect(c.bucketPolicy.props.Bucket).toBe(c.bucket); // bucket.Ref is the resource instance itself
|
|
56
|
+
const statement = c.bucketPolicy.props.PolicyDocument.Statement[0];
|
|
57
|
+
expect(statement).toEqual({ Effect: "Allow", Principal: "*", Action: "s3:GetObject", Resource: expect.anything() });
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
test("website without errorDocument omits it rather than writing undefined", () => {
|
|
61
|
+
const c = BucketDeployment({ website: { indexDocument: "index.html" } });
|
|
62
|
+
const websiteProps = (c.bucket as any).props.WebsiteConfiguration.props;
|
|
63
|
+
expect(websiteProps).toEqual({ IndexDocument: "index.html" });
|
|
64
|
+
expect("ErrorDocument" in websiteProps).toBe(false);
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
test("tags pass straight through to the bucket", () => {
|
|
68
|
+
const c = BucketDeployment({ tags: [{ Key: "env", Value: "prod" }] });
|
|
69
|
+
expect((c.bucket as any).props.Tags).toEqual([{ Key: "env", Value: "prod" }]);
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
test("defaults escape hatch reaches the bucket (e.g. object-lock)", () => {
|
|
73
|
+
const c = BucketDeployment({ defaults: { bucket: { ObjectLockEnabled: true } } });
|
|
74
|
+
expect((c.bucket as any).props.ObjectLockEnabled).toBe(true);
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
test("defaults escape hatch reaches the bucket policy", () => {
|
|
78
|
+
const c = BucketDeployment({
|
|
79
|
+
website: { indexDocument: "index.html" },
|
|
80
|
+
defaults: { bucketPolicy: { Bucket: "explicit-bucket-name" } },
|
|
81
|
+
}) as unknown as { bucketPolicy: any };
|
|
82
|
+
expect(c.bucketPolicy.props.Bucket).toBe("explicit-bucket-name");
|
|
83
|
+
});
|
|
84
|
+
});
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* BucketDeployment — the seeded-bucket shape: an S3 bucket sized to receive
|
|
3
|
+
* deploy-time content, encrypted and access-blocked by default, its
|
|
4
|
+
* deletion behaviour and (optionally) its public-read policy declared up
|
|
5
|
+
* front rather than left to CloudFormation's own defaults.
|
|
6
|
+
*
|
|
7
|
+
* `s3deploy.BucketDeployment` is the most-used CDK pattern across the
|
|
8
|
+
* aws-bench corpus (37 instantiations across 8 apps, chant#1139) and it
|
|
9
|
+
* names two things CDK bundles together that chant keeps apart on purpose:
|
|
10
|
+
*
|
|
11
|
+
* - The bucket itself: `AWS::S3::Bucket` plus its encryption, access-block
|
|
12
|
+
* and (for the static-site shape) website/public-policy configuration.
|
|
13
|
+
* That is a fixed set of CloudFormation resources, so it is what this
|
|
14
|
+
* composite declares — the same shape `LambdaS3` (../composites) already
|
|
15
|
+
* gives a bucket, minus the Lambda trigger wiring.
|
|
16
|
+
* - Uploading local files into it at deploy time. CDK does this with a
|
|
17
|
+
* Lambda-backed custom resource (`cr.AwsCustomResource`, the same family
|
|
18
|
+
* the epic notes maps to chant Ops rather than composites — chant has no
|
|
19
|
+
* CloudFormation custom-resource machinery, and inventing one just to
|
|
20
|
+
* reproduce a `PutObject` loop would be new plumbing for a solved
|
|
21
|
+
* problem). chant already ships this as a deploy-time **capability**,
|
|
22
|
+
* `s3-sync` (`../components/apply.ts`'s `createS3SyncCapability`, backed
|
|
23
|
+
* by the real `aws s3 sync`/`aws s3 cp` CLI through `CloudExecutor`, with
|
|
24
|
+
* the typed step-builder `s3Sync` in `../components/builders.ts`) — it
|
|
25
|
+
* was defined for exactly this and, before this composite, had no
|
|
26
|
+
* composite pairing it with a bucket to sync into. A component composes
|
|
27
|
+
* the two:
|
|
28
|
+
*
|
|
29
|
+
* const seeded = BucketDeployment({ bucketName: Sub`${AWS.StackName}-site` });
|
|
30
|
+
* // ...component `deploy` phase:
|
|
31
|
+
* s3Sync({ from: "archive:site-build", to: Sub`s3://${seeded.bucket.Ref}` })
|
|
32
|
+
*
|
|
33
|
+
* That split — graph-time declaration here, deploy-time action in the
|
|
34
|
+
* component layer — is the existing precedent (`s3-sync` shipped in #566
|
|
35
|
+
* unused by any composite until this one), not a new one invented for this
|
|
36
|
+
* composite.
|
|
37
|
+
*
|
|
38
|
+
* "Cleanup" (the epic's third word) is `removalPolicy`, CloudFormation's own
|
|
39
|
+
* `DeletionPolicy` on the bucket resource. CDK's `autoDeleteObjects` — the
|
|
40
|
+
* Lambda-backed hook that empties a bucket before CloudFormation is allowed
|
|
41
|
+
* to delete it — has the same custom-resource shape as content upload and
|
|
42
|
+
* the same answer: out of scope here, and worth an `s3 rm --recursive`
|
|
43
|
+
* step ahead of a stack teardown in the component that owns the deploy.
|
|
44
|
+
*/
|
|
45
|
+
import { Composite, mergeDefaults, type Value } from "@intentius/chant";
|
|
46
|
+
import {
|
|
47
|
+
Bucket,
|
|
48
|
+
Bucket_BucketEncryption,
|
|
49
|
+
Bucket_ServerSideEncryptionRule,
|
|
50
|
+
Bucket_ServerSideEncryptionByDefault,
|
|
51
|
+
Bucket_PublicAccessBlockConfiguration,
|
|
52
|
+
Bucket_VersioningConfiguration,
|
|
53
|
+
Bucket_WebsiteConfiguration,
|
|
54
|
+
S3BucketPolicy,
|
|
55
|
+
} from "../generated";
|
|
56
|
+
import { Sub } from "../intrinsics";
|
|
57
|
+
|
|
58
|
+
export interface BucketDeploymentProps {
|
|
59
|
+
/** `Value<string>`: a name is routinely built with `Sub`/`Ref` (#1366). Omitted lets CloudFormation generate one. */
|
|
60
|
+
bucketName?: Value<string>;
|
|
61
|
+
/** Enable S3 object versioning. Default: false. */
|
|
62
|
+
versioned?: boolean;
|
|
63
|
+
/**
|
|
64
|
+
* Static-website hosting: sets `WebsiteConfiguration` and — since a
|
|
65
|
+
* website bucket is read by anonymous visitors, not IAM principals —
|
|
66
|
+
* opens the public-access block and attaches a public-read
|
|
67
|
+
* `S3BucketPolicy` scoped to `s3:GetObject`. Omitted, the bucket stays
|
|
68
|
+
* fully private, matching every other composite bucket's default
|
|
69
|
+
* (`LambdaS3`, ../composites).
|
|
70
|
+
*/
|
|
71
|
+
website?: {
|
|
72
|
+
indexDocument: string;
|
|
73
|
+
errorDocument?: string;
|
|
74
|
+
};
|
|
75
|
+
/** What CloudFormation does to the bucket when the stack (or the resource) is removed. Default: "retain" — CloudFormation's own default, and the safe one for a bucket a deploy has just put content into. */
|
|
76
|
+
removalPolicy?: "retain" | "destroy";
|
|
77
|
+
tags?: Array<{ Key: string; Value: string }>;
|
|
78
|
+
defaults?: {
|
|
79
|
+
bucket?: Partial<ConstructorParameters<typeof Bucket>[0]>;
|
|
80
|
+
bucketPolicy?: Partial<ConstructorParameters<typeof S3BucketPolicy>[0]>;
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
export type BucketDeploymentResult =
|
|
85
|
+
| { bucket: InstanceType<typeof Bucket> }
|
|
86
|
+
| { bucket: InstanceType<typeof Bucket>; bucketPolicy: InstanceType<typeof S3BucketPolicy> };
|
|
87
|
+
|
|
88
|
+
export const BucketDeployment = Composite<BucketDeploymentProps>((props) => {
|
|
89
|
+
const { defaults } = props;
|
|
90
|
+
const publicRead = props.website !== undefined;
|
|
91
|
+
|
|
92
|
+
const encryptionDefault = new Bucket_ServerSideEncryptionByDefault({ SSEAlgorithm: "AES256" });
|
|
93
|
+
const encryptionRule = new Bucket_ServerSideEncryptionRule({ ServerSideEncryptionByDefault: encryptionDefault });
|
|
94
|
+
const bucketEncryption = new Bucket_BucketEncryption({ ServerSideEncryptionConfiguration: [encryptionRule] });
|
|
95
|
+
|
|
96
|
+
// A ternary, not an `if`, so the block-scoped `new` stays an expression
|
|
97
|
+
// (EVL002) — the same reasoning eks-cluster.ts's addon slots document.
|
|
98
|
+
const publicAccessBlock = new Bucket_PublicAccessBlockConfiguration(
|
|
99
|
+
publicRead
|
|
100
|
+
? { BlockPublicAcls: false, BlockPublicPolicy: false, IgnorePublicAcls: false, RestrictPublicBuckets: false }
|
|
101
|
+
: { BlockPublicAcls: true, BlockPublicPolicy: true, IgnorePublicAcls: true, RestrictPublicBuckets: true },
|
|
102
|
+
);
|
|
103
|
+
|
|
104
|
+
const versioningConfiguration = props.versioned
|
|
105
|
+
? new Bucket_VersioningConfiguration({ Status: "Enabled" })
|
|
106
|
+
: undefined;
|
|
107
|
+
|
|
108
|
+
const websiteConfiguration = props.website
|
|
109
|
+
? new Bucket_WebsiteConfiguration({
|
|
110
|
+
IndexDocument: props.website.indexDocument,
|
|
111
|
+
...(props.website.errorDocument ? { ErrorDocument: props.website.errorDocument } : {}),
|
|
112
|
+
})
|
|
113
|
+
: undefined;
|
|
114
|
+
|
|
115
|
+
const bucket = new Bucket(
|
|
116
|
+
mergeDefaults(
|
|
117
|
+
{
|
|
118
|
+
BucketName: props.bucketName,
|
|
119
|
+
BucketEncryption: bucketEncryption,
|
|
120
|
+
PublicAccessBlockConfiguration: publicAccessBlock,
|
|
121
|
+
...(versioningConfiguration ? { VersioningConfiguration: versioningConfiguration } : {}),
|
|
122
|
+
...(websiteConfiguration ? { WebsiteConfiguration: websiteConfiguration } : {}),
|
|
123
|
+
Tags: props.tags,
|
|
124
|
+
},
|
|
125
|
+
defaults?.bucket,
|
|
126
|
+
),
|
|
127
|
+
{ DeletionPolicy: props.removalPolicy === "destroy" ? "Delete" : "Retain" },
|
|
128
|
+
);
|
|
129
|
+
|
|
130
|
+
const bucketPolicy = publicRead
|
|
131
|
+
? new S3BucketPolicy(
|
|
132
|
+
mergeDefaults(
|
|
133
|
+
{
|
|
134
|
+
Bucket: bucket.Ref,
|
|
135
|
+
PolicyDocument: {
|
|
136
|
+
Version: "2012-10-17",
|
|
137
|
+
Statement: [
|
|
138
|
+
{ Effect: "Allow", Principal: "*", Action: "s3:GetObject", Resource: Sub`${bucket.Arn}/*` },
|
|
139
|
+
],
|
|
140
|
+
},
|
|
141
|
+
},
|
|
142
|
+
defaults?.bucketPolicy,
|
|
143
|
+
),
|
|
144
|
+
)
|
|
145
|
+
: undefined;
|
|
146
|
+
|
|
147
|
+
// Spread rather than a ternary between two object shapes (EksCluster's
|
|
148
|
+
// addon slots, ../eks-cluster.ts, hit the same union-return typing wall):
|
|
149
|
+
// a key that is sometimes present, never a key whose value is sometimes
|
|
150
|
+
// `undefined` — `CompositeMembers`' index signature requires `Declarable`,
|
|
151
|
+
// not `Declarable | undefined`.
|
|
152
|
+
return { bucket, ...(bucketPolicy ? { bucketPolicy } : {}) };
|
|
153
|
+
}, "BucketDeployment");
|