@intentius/chant-lexicon-aws 0.0.8 → 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.
Files changed (115) hide show
  1. package/dist/integrity.json +25 -10
  2. package/dist/manifest.json +1 -1
  3. package/dist/meta.json +5743 -896
  4. package/dist/rules/cf-refs.ts +99 -0
  5. package/dist/rules/ext001.ts +30 -21
  6. package/dist/rules/hardcoded-region.ts +1 -0
  7. package/dist/rules/iam-wildcard.ts +1 -0
  8. package/dist/rules/s3-encryption.ts +1 -0
  9. package/dist/rules/waw016.ts +86 -0
  10. package/dist/rules/waw017.ts +53 -0
  11. package/dist/rules/waw018.ts +71 -0
  12. package/dist/rules/waw019.ts +82 -0
  13. package/dist/rules/waw020.ts +64 -0
  14. package/dist/rules/waw021.ts +53 -0
  15. package/dist/rules/waw022.ts +43 -0
  16. package/dist/rules/waw023.ts +47 -0
  17. package/dist/rules/waw024.ts +54 -0
  18. package/dist/rules/waw025.ts +43 -0
  19. package/dist/rules/waw026.ts +46 -0
  20. package/dist/rules/waw027.ts +50 -0
  21. package/dist/rules/waw028.ts +47 -0
  22. package/dist/rules/waw029.ts +62 -0
  23. package/dist/rules/waw030.ts +246 -0
  24. package/dist/skills/chant-aws.md +388 -30
  25. package/dist/types/index.d.ts +1552 -1528
  26. package/package.json +2 -2
  27. package/src/actions/actions.test.ts +75 -0
  28. package/src/actions/dynamodb.ts +36 -0
  29. package/src/actions/ecr.ts +9 -0
  30. package/src/actions/ecs.ts +5 -0
  31. package/src/actions/iam.ts +3 -0
  32. package/src/actions/index.ts +9 -0
  33. package/src/actions/lambda.ts +11 -0
  34. package/src/actions/logs.ts +4 -0
  35. package/src/actions/s3.ts +34 -0
  36. package/src/actions/sns.ts +5 -0
  37. package/src/actions/sqs.ts +15 -0
  38. package/src/codegen/__snapshots__/snapshot.test.ts.snap +2 -2
  39. package/src/codegen/docs-links.test.ts +143 -0
  40. package/src/codegen/docs.ts +247 -132
  41. package/src/codegen/generate-lexicon.ts +8 -0
  42. package/src/codegen/generate-typescript.ts +25 -1
  43. package/src/composites/composites.test.ts +442 -0
  44. package/src/composites/fargate-alb.ts +253 -0
  45. package/src/composites/index.ts +20 -0
  46. package/src/composites/lambda-api.ts +20 -0
  47. package/src/composites/lambda-dynamodb.ts +64 -0
  48. package/src/composites/lambda-eventbridge.ts +36 -0
  49. package/src/composites/lambda-function.ts +76 -0
  50. package/src/composites/lambda-s3.ts +72 -0
  51. package/src/composites/lambda-sns.ts +30 -0
  52. package/src/composites/lambda-sqs.ts +44 -0
  53. package/src/composites/scheduled-lambda.ts +37 -0
  54. package/src/composites/vpc-default.ts +148 -0
  55. package/src/default-tags.test.ts +38 -0
  56. package/src/default-tags.ts +77 -0
  57. package/src/generated/index.d.ts +1552 -1528
  58. package/src/generated/lexicon-aws.json +5743 -896
  59. package/src/import/roundtrip-fixtures.test.ts +1 -1
  60. package/src/index.ts +21 -0
  61. package/src/integration.test.ts +71 -0
  62. package/src/intrinsics.ts +24 -13
  63. package/src/lint/post-synth/cf-refs.ts +99 -0
  64. package/src/lint/post-synth/ext001.test.ts +214 -31
  65. package/src/lint/post-synth/ext001.ts +30 -21
  66. package/src/lint/post-synth/waw013.test.ts +120 -0
  67. package/src/lint/post-synth/waw014.test.ts +121 -0
  68. package/src/lint/post-synth/waw015.test.ts +147 -0
  69. package/src/lint/post-synth/waw016.test.ts +141 -0
  70. package/src/lint/post-synth/waw016.ts +86 -0
  71. package/src/lint/post-synth/waw017.test.ts +130 -0
  72. package/src/lint/post-synth/waw017.ts +53 -0
  73. package/src/lint/post-synth/waw018.test.ts +109 -0
  74. package/src/lint/post-synth/waw018.ts +71 -0
  75. package/src/lint/post-synth/waw019.test.ts +138 -0
  76. package/src/lint/post-synth/waw019.ts +82 -0
  77. package/src/lint/post-synth/waw020.test.ts +125 -0
  78. package/src/lint/post-synth/waw020.ts +64 -0
  79. package/src/lint/post-synth/waw021.test.ts +81 -0
  80. package/src/lint/post-synth/waw021.ts +53 -0
  81. package/src/lint/post-synth/waw022.test.ts +54 -0
  82. package/src/lint/post-synth/waw022.ts +43 -0
  83. package/src/lint/post-synth/waw023.test.ts +53 -0
  84. package/src/lint/post-synth/waw023.ts +47 -0
  85. package/src/lint/post-synth/waw024.test.ts +64 -0
  86. package/src/lint/post-synth/waw024.ts +54 -0
  87. package/src/lint/post-synth/waw025.test.ts +42 -0
  88. package/src/lint/post-synth/waw025.ts +43 -0
  89. package/src/lint/post-synth/waw026.test.ts +54 -0
  90. package/src/lint/post-synth/waw026.ts +46 -0
  91. package/src/lint/post-synth/waw027.test.ts +63 -0
  92. package/src/lint/post-synth/waw027.ts +50 -0
  93. package/src/lint/post-synth/waw028.test.ts +68 -0
  94. package/src/lint/post-synth/waw028.ts +47 -0
  95. package/src/lint/post-synth/waw029.test.ts +179 -0
  96. package/src/lint/post-synth/waw029.ts +62 -0
  97. package/src/lint/post-synth/waw030.test.ts +800 -0
  98. package/src/lint/post-synth/waw030.ts +246 -0
  99. package/src/lint/rules/hardcoded-region.ts +1 -0
  100. package/src/lint/rules/iam-wildcard.ts +1 -0
  101. package/src/lint/rules/s3-encryption.ts +1 -0
  102. package/src/lsp/hover.ts +15 -0
  103. package/src/nested-stack-integration.test.ts +100 -0
  104. package/src/nested-stack.ts +1 -1
  105. package/src/plugin.ts +468 -36
  106. package/src/serializer.test.ts +330 -2
  107. package/src/serializer.ts +62 -1
  108. package/src/spec/fetch.ts +10 -0
  109. package/src/spec/parse.test.ts +141 -0
  110. package/src/spec/parse.ts +40 -0
  111. package/src/taggable.ts +44 -0
  112. package/src/testdata/nested-stacks/app.ts +26 -0
  113. package/src/testdata/nested-stacks/network/outputs.ts +17 -0
  114. package/src/testdata/nested-stacks/network/security.ts +17 -0
  115. package/src/testdata/nested-stacks/network/vpc.ts +54 -0
@@ -0,0 +1,20 @@
1
+ export { LambdaFunction, LambdaNode, LambdaPython, NodeLambda, PythonLambda } from "./lambda-function";
2
+ export type { LambdaFunctionProps } from "./lambda-function";
3
+ export { LambdaApi } from "./lambda-api";
4
+ export type { LambdaApiProps } from "./lambda-api";
5
+ export { LambdaScheduled, ScheduledLambda } from "./scheduled-lambda";
6
+ export type { ScheduledLambdaProps } from "./scheduled-lambda";
7
+ export { LambdaSqs } from "./lambda-sqs";
8
+ export type { LambdaSqsProps } from "./lambda-sqs";
9
+ export { LambdaEventBridge } from "./lambda-eventbridge";
10
+ export type { LambdaEventBridgeProps } from "./lambda-eventbridge";
11
+ export { LambdaDynamoDB } from "./lambda-dynamodb";
12
+ export type { LambdaDynamoDBProps } from "./lambda-dynamodb";
13
+ export { LambdaS3 } from "./lambda-s3";
14
+ export type { LambdaS3Props } from "./lambda-s3";
15
+ export { LambdaSns } from "./lambda-sns";
16
+ export type { LambdaSnsProps } from "./lambda-sns";
17
+ export { VpcDefault } from "./vpc-default";
18
+ export type { VpcDefaultProps } from "./vpc-default";
19
+ export { FargateAlb } from "./fargate-alb";
20
+ export type { FargateAlbProps } from "./fargate-alb";
@@ -0,0 +1,20 @@
1
+ import { Composite } from "@intentius/chant";
2
+ import { Permission } from "../generated";
3
+ import { LambdaFunction, type LambdaFunctionProps } from "./lambda-function";
4
+
5
+ export interface LambdaApiProps extends LambdaFunctionProps {
6
+ sourceArn?: string;
7
+ }
8
+
9
+ export const LambdaApi = Composite<LambdaApiProps>((props) => {
10
+ const { role, func } = LambdaFunction(props);
11
+
12
+ const permission = new Permission({
13
+ FunctionName: func.Arn,
14
+ Action: "lambda:InvokeFunction",
15
+ Principal: "apigateway.amazonaws.com",
16
+ SourceArn: props.sourceArn,
17
+ });
18
+
19
+ return { role, func, permission };
20
+ }, "LambdaApi");
@@ -0,0 +1,64 @@
1
+ import { Composite } from "@intentius/chant";
2
+ import { Table, Table_AttributeDefinition, Table_KeySchema, Role_Policy } from "../generated";
3
+ import { DynamoDBActions } from "../actions/dynamodb";
4
+ import { LambdaFunction, type LambdaFunctionProps } from "./lambda-function";
5
+
6
+ export interface LambdaDynamoDBProps extends LambdaFunctionProps {
7
+ tableName?: string;
8
+ partitionKey: string;
9
+ sortKey?: string;
10
+ access?: "ReadOnly" | "ReadWrite" | "Full";
11
+ }
12
+
13
+ export const LambdaDynamoDB = Composite<LambdaDynamoDBProps>((props) => {
14
+ const attributeDefinitions = [
15
+ new Table_AttributeDefinition({ AttributeName: props.partitionKey, AttributeType: "S" }),
16
+ ];
17
+ const keySchema: InstanceType<typeof Table_KeySchema>[] = [
18
+ new Table_KeySchema({ AttributeName: props.partitionKey, KeyType: "HASH" }),
19
+ ];
20
+
21
+ if (props.sortKey) {
22
+ attributeDefinitions.push(
23
+ new Table_AttributeDefinition({ AttributeName: props.sortKey, AttributeType: "S" }),
24
+ );
25
+ keySchema.push(
26
+ new Table_KeySchema({ AttributeName: props.sortKey, KeyType: "RANGE" }),
27
+ );
28
+ }
29
+
30
+ const table = new Table({
31
+ TableName: props.tableName,
32
+ BillingMode: "PAY_PER_REQUEST",
33
+ AttributeDefinitions: attributeDefinitions,
34
+ KeySchema: keySchema,
35
+ });
36
+
37
+ const access = props.access ?? "ReadWrite";
38
+ const dynamoPolicyDocument = {
39
+ Version: "2012-10-17",
40
+ Statement: [
41
+ {
42
+ Effect: "Allow",
43
+ Action: DynamoDBActions[access],
44
+ Resource: table.Arn,
45
+ },
46
+ ],
47
+ };
48
+
49
+ const dynamoPolicy = new Role_Policy({
50
+ PolicyName: `DynamoDB${access}`,
51
+ PolicyDocument: dynamoPolicyDocument,
52
+ });
53
+
54
+ const policies = props.Policies ? [dynamoPolicy, ...props.Policies] : [dynamoPolicy];
55
+ const env = props.Environment ?? { Variables: {} };
56
+ const variables = { ...((env as any).Variables ?? {}), TABLE_NAME: table.Ref };
57
+ const { role, func } = LambdaFunction({
58
+ ...props,
59
+ Policies: policies,
60
+ Environment: { Variables: variables },
61
+ });
62
+
63
+ return { table, role, func };
64
+ }, "LambdaDynamoDB");
@@ -0,0 +1,36 @@
1
+ import { Composite } from "@intentius/chant";
2
+ import { EventRule, EventRule_Target, Permission } from "../generated";
3
+ import { LambdaFunction, type LambdaFunctionProps } from "./lambda-function";
4
+
5
+ export interface LambdaEventBridgeProps extends LambdaFunctionProps {
6
+ ruleName?: string;
7
+ schedule?: string;
8
+ eventPattern?: Record<string, unknown>;
9
+ enabled?: boolean;
10
+ }
11
+
12
+ export const LambdaEventBridge = Composite<LambdaEventBridgeProps>((props) => {
13
+ const { role, func } = LambdaFunction(props);
14
+
15
+ const rule = new EventRule({
16
+ Name: props.ruleName,
17
+ ScheduleExpression: props.schedule,
18
+ EventPattern: props.eventPattern,
19
+ State: (props.enabled ?? true) ? "ENABLED" : "DISABLED",
20
+ Targets: [
21
+ new EventRule_Target({
22
+ Arn: func.Arn,
23
+ Id: "Target0",
24
+ }),
25
+ ],
26
+ });
27
+
28
+ const permission = new Permission({
29
+ FunctionName: func.Arn,
30
+ Action: "lambda:InvokeFunction",
31
+ Principal: "events.amazonaws.com",
32
+ SourceArn: rule.Arn,
33
+ });
34
+
35
+ return { rule, role, func, permission };
36
+ }, "LambdaEventBridge");
@@ -0,0 +1,76 @@
1
+ import { Composite, withDefaults } from "@intentius/chant";
2
+ import { Role, Function, Function_VpcConfig, Role_Policy } from "../generated";
3
+
4
+ const lambdaTrustPolicy = {
5
+ Version: "2012-10-17" as const,
6
+ Statement: [
7
+ {
8
+ Effect: "Allow" as const,
9
+ Principal: { Service: "lambda.amazonaws.com" },
10
+ Action: "sts:AssumeRole",
11
+ },
12
+ ],
13
+ };
14
+
15
+ const BASIC_EXECUTION_ARN =
16
+ "arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole";
17
+ const VPC_ACCESS_ARN =
18
+ "arn:aws:iam::aws:policy/service-role/AWSLambdaVPCAccessExecutionRole";
19
+
20
+ export interface LambdaFunctionProps {
21
+ name: string;
22
+ Runtime: string;
23
+ Handler: string;
24
+ Code: ConstructorParameters<typeof Function>[0]["Code"];
25
+ Timeout?: number;
26
+ MemorySize?: number;
27
+ Environment?: ConstructorParameters<typeof Function>[0]["Environment"];
28
+ ManagedPolicyArns?: string[];
29
+ Policies?: InstanceType<typeof Role_Policy>[];
30
+ VpcConfig?: ConstructorParameters<typeof Function_VpcConfig>[0];
31
+ }
32
+
33
+ export const LambdaFunction = Composite<LambdaFunctionProps>((props) => {
34
+ const managedPolicies = [BASIC_EXECUTION_ARN];
35
+ if (props.VpcConfig) {
36
+ managedPolicies.push(VPC_ACCESS_ARN);
37
+ }
38
+ if (props.ManagedPolicyArns) {
39
+ managedPolicies.push(...props.ManagedPolicyArns);
40
+ }
41
+
42
+ const role = new Role({
43
+ AssumeRolePolicyDocument: lambdaTrustPolicy,
44
+ ManagedPolicyArns: managedPolicies,
45
+ Policies: props.Policies,
46
+ });
47
+
48
+ const func = new Function({
49
+ FunctionName: props.name,
50
+ Runtime: props.Runtime as any,
51
+ Handler: props.Handler,
52
+ Code: props.Code,
53
+ Role: role.Arn,
54
+ Timeout: props.Timeout ?? 30,
55
+ MemorySize: props.MemorySize,
56
+ Environment: props.Environment,
57
+ VpcConfig: props.VpcConfig ? new Function_VpcConfig(props.VpcConfig) : undefined,
58
+ });
59
+
60
+ return { role, func };
61
+ }, "LambdaFunction");
62
+
63
+ export const LambdaNode = withDefaults(LambdaFunction, {
64
+ Runtime: "nodejs20.x",
65
+ Handler: "index.handler",
66
+ });
67
+
68
+ export const LambdaPython = withDefaults(LambdaFunction, {
69
+ Runtime: "python3.12",
70
+ Handler: "handler.handler",
71
+ });
72
+
73
+ /** @deprecated Use `LambdaNode` instead */
74
+ export const NodeLambda = LambdaNode;
75
+ /** @deprecated Use `LambdaPython` instead */
76
+ export const PythonLambda = LambdaPython;
@@ -0,0 +1,72 @@
1
+ import { Composite } from "@intentius/chant";
2
+ import {
3
+ Bucket,
4
+ Bucket_BucketEncryption,
5
+ Bucket_ServerSideEncryptionRule,
6
+ Bucket_ServerSideEncryptionByDefault,
7
+ Bucket_PublicAccessBlockConfiguration,
8
+ Role_Policy,
9
+ } from "../generated";
10
+ import { Sub } from "../intrinsics";
11
+ import { S3Actions } from "../actions/s3";
12
+ import { LambdaFunction, type LambdaFunctionProps } from "./lambda-function";
13
+
14
+ export interface LambdaS3Props extends LambdaFunctionProps {
15
+ bucketName?: string;
16
+ access?: "ReadOnly" | "ReadWrite" | "Full";
17
+ }
18
+
19
+ export const LambdaS3 = Composite<LambdaS3Props>((props) => {
20
+ const encryptionDefault = new Bucket_ServerSideEncryptionByDefault({
21
+ SSEAlgorithm: "AES256",
22
+ });
23
+
24
+ const encryptionRule = new Bucket_ServerSideEncryptionRule({
25
+ ServerSideEncryptionByDefault: encryptionDefault,
26
+ });
27
+
28
+ const bucketEncryption = new Bucket_BucketEncryption({
29
+ ServerSideEncryptionConfiguration: [encryptionRule],
30
+ });
31
+
32
+ const publicAccessBlock = new Bucket_PublicAccessBlockConfiguration({
33
+ BlockPublicAcls: true,
34
+ BlockPublicPolicy: true,
35
+ IgnorePublicAcls: true,
36
+ RestrictPublicBuckets: true,
37
+ });
38
+
39
+ const bucket = new Bucket({
40
+ BucketName: props.bucketName,
41
+ BucketEncryption: bucketEncryption,
42
+ PublicAccessBlockConfiguration: publicAccessBlock,
43
+ });
44
+
45
+ const access = props.access ?? "ReadWrite";
46
+ const s3PolicyDocument = {
47
+ Version: "2012-10-17",
48
+ Statement: [
49
+ {
50
+ Effect: "Allow",
51
+ Action: S3Actions[access],
52
+ Resource: [bucket.Arn, Sub`${bucket.Arn}/*`],
53
+ },
54
+ ],
55
+ };
56
+
57
+ const s3Policy = new Role_Policy({
58
+ PolicyName: `S3${access}`,
59
+ PolicyDocument: s3PolicyDocument,
60
+ });
61
+
62
+ const policies = props.Policies ? [s3Policy, ...props.Policies] : [s3Policy];
63
+ const env = props.Environment ?? { Variables: {} };
64
+ const variables = { ...((env as any).Variables ?? {}), BUCKET_NAME: bucket.Ref };
65
+ const { role, func } = LambdaFunction({
66
+ ...props,
67
+ Policies: policies,
68
+ Environment: { Variables: variables },
69
+ });
70
+
71
+ return { bucket, role, func };
72
+ }, "LambdaS3");
@@ -0,0 +1,30 @@
1
+ import { Composite } from "@intentius/chant";
2
+ import { Topic, Subscription, Permission } from "../generated";
3
+ import { LambdaFunction, type LambdaFunctionProps } from "./lambda-function";
4
+
5
+ export interface LambdaSnsProps extends LambdaFunctionProps {
6
+ topicName?: string;
7
+ }
8
+
9
+ export const LambdaSns = Composite<LambdaSnsProps>((props) => {
10
+ const { role, func } = LambdaFunction(props);
11
+
12
+ const topic = new Topic({
13
+ TopicName: props.topicName,
14
+ });
15
+
16
+ const subscription = new Subscription({
17
+ TopicArn: topic.TopicArn,
18
+ Protocol: "lambda",
19
+ Endpoint: func.Arn,
20
+ });
21
+
22
+ const permission = new Permission({
23
+ FunctionName: func.Arn,
24
+ Action: "lambda:InvokeFunction",
25
+ Principal: "sns.amazonaws.com",
26
+ SourceArn: topic.TopicArn,
27
+ });
28
+
29
+ return { topic, role, func, subscription, permission };
30
+ }, "LambdaSns");
@@ -0,0 +1,44 @@
1
+ import { Composite } from "@intentius/chant";
2
+ import { Queue, EventSourceMapping, Role_Policy } from "../generated";
3
+ import { SQSActions } from "../actions/sqs";
4
+ import { LambdaFunction, type LambdaFunctionProps } from "./lambda-function";
5
+
6
+ export interface LambdaSqsProps extends LambdaFunctionProps {
7
+ queueName?: string;
8
+ batchSize?: number;
9
+ maxBatchingWindow?: number;
10
+ }
11
+
12
+ export const LambdaSqs = Composite<LambdaSqsProps>((props) => {
13
+ const queue = new Queue({
14
+ QueueName: props.queueName,
15
+ });
16
+
17
+ const sqsPolicyDocument = {
18
+ Version: "2012-10-17",
19
+ Statement: [
20
+ {
21
+ Effect: "Allow",
22
+ Action: SQSActions.ReceiveMessage,
23
+ Resource: queue.Arn,
24
+ },
25
+ ],
26
+ };
27
+
28
+ const sqsPolicy = new Role_Policy({
29
+ PolicyName: "SQSReceive",
30
+ PolicyDocument: sqsPolicyDocument,
31
+ });
32
+
33
+ const policies = props.Policies ? [sqsPolicy, ...props.Policies] : [sqsPolicy];
34
+ const { role, func } = LambdaFunction({ ...props, Policies: policies });
35
+
36
+ new EventSourceMapping({
37
+ EventSourceArn: queue.Arn,
38
+ FunctionName: func.Arn,
39
+ BatchSize: props.batchSize ?? 10,
40
+ MaximumBatchingWindowInSeconds: props.maxBatchingWindow,
41
+ });
42
+
43
+ return { queue, role, func };
44
+ }, "LambdaSqs");
@@ -0,0 +1,37 @@
1
+ import { Composite } from "@intentius/chant";
2
+ import { EventRule, EventRule_Target, Permission } from "../generated";
3
+ import { LambdaFunction, type LambdaFunctionProps } from "./lambda-function";
4
+
5
+ export interface ScheduledLambdaProps extends LambdaFunctionProps {
6
+ ruleName?: string;
7
+ schedule: string;
8
+ enabled?: boolean;
9
+ }
10
+
11
+ export const LambdaScheduled = Composite<ScheduledLambdaProps>((props) => {
12
+ const { role, func } = LambdaFunction(props);
13
+
14
+ const rule = new EventRule({
15
+ Name: props.ruleName,
16
+ ScheduleExpression: props.schedule,
17
+ State: (props.enabled ?? true) ? "ENABLED" : "DISABLED",
18
+ Targets: [
19
+ new EventRule_Target({
20
+ Arn: func.Arn,
21
+ Id: "Target0",
22
+ }),
23
+ ],
24
+ });
25
+
26
+ const permission = new Permission({
27
+ FunctionName: func.Arn,
28
+ Action: "lambda:InvokeFunction",
29
+ Principal: "events.amazonaws.com",
30
+ SourceArn: rule.Arn,
31
+ });
32
+
33
+ return { role, func, rule, permission };
34
+ }, "LambdaScheduled");
35
+
36
+ /** @deprecated Use `LambdaScheduled` instead */
37
+ export const ScheduledLambda = LambdaScheduled;
@@ -0,0 +1,148 @@
1
+ import { Composite } from "@intentius/chant";
2
+ import {
3
+ Vpc,
4
+ Subnet,
5
+ InternetGateway,
6
+ VPCGatewayAttachment,
7
+ RouteTable,
8
+ EC2Route,
9
+ SubnetRouteTableAssociation,
10
+ EIP,
11
+ NatGateway,
12
+ } from "../generated";
13
+ import { Select, GetAZs } from "../intrinsics";
14
+
15
+ export interface VpcDefaultProps {
16
+ cidr?: string;
17
+ publicSubnet1Cidr?: string;
18
+ publicSubnet2Cidr?: string;
19
+ privateSubnet1Cidr?: string;
20
+ privateSubnet2Cidr?: string;
21
+ }
22
+
23
+ export const VpcDefault = Composite<VpcDefaultProps>((props) => {
24
+ const cidr = props.cidr ?? "10.0.0.0/16";
25
+ const publicSubnet1Cidr = props.publicSubnet1Cidr ?? "10.0.0.0/20";
26
+ const publicSubnet2Cidr = props.publicSubnet2Cidr ?? "10.0.16.0/20";
27
+ const privateSubnet1Cidr = props.privateSubnet1Cidr ?? "10.0.128.0/20";
28
+ const privateSubnet2Cidr = props.privateSubnet2Cidr ?? "10.0.144.0/20";
29
+
30
+ const az1 = Select(0, GetAZs(""));
31
+ const az2 = Select(1, GetAZs(""));
32
+
33
+ const vpc = new Vpc({
34
+ CidrBlock: cidr,
35
+ EnableDnsSupport: true,
36
+ EnableDnsHostnames: true,
37
+ });
38
+
39
+ const igw = new InternetGateway({});
40
+
41
+ const igwAttachment = new VPCGatewayAttachment({
42
+ VpcId: vpc.VpcId,
43
+ InternetGatewayId: igw.InternetGatewayId,
44
+ });
45
+
46
+ // Public subnets
47
+ const publicSubnet1 = new Subnet({
48
+ VpcId: vpc.VpcId,
49
+ CidrBlock: publicSubnet1Cidr,
50
+ AvailabilityZone: az1,
51
+ MapPublicIpOnLaunch: true,
52
+ });
53
+
54
+ const publicSubnet2 = new Subnet({
55
+ VpcId: vpc.VpcId,
56
+ CidrBlock: publicSubnet2Cidr,
57
+ AvailabilityZone: az2,
58
+ MapPublicIpOnLaunch: true,
59
+ });
60
+
61
+ // Private subnets
62
+ const privateSubnet1 = new Subnet({
63
+ VpcId: vpc.VpcId,
64
+ CidrBlock: privateSubnet1Cidr,
65
+ AvailabilityZone: az1,
66
+ });
67
+
68
+ const privateSubnet2 = new Subnet({
69
+ VpcId: vpc.VpcId,
70
+ CidrBlock: privateSubnet2Cidr,
71
+ AvailabilityZone: az2,
72
+ });
73
+
74
+ // Public route table
75
+ const publicRouteTable = new RouteTable({
76
+ VpcId: vpc.VpcId,
77
+ });
78
+
79
+ const publicRoute = new EC2Route(
80
+ {
81
+ RouteTableId: publicRouteTable.RouteTableId,
82
+ DestinationCidrBlock: "0.0.0.0/0",
83
+ GatewayId: igw.InternetGatewayId,
84
+ },
85
+ { DependsOn: [igwAttachment] },
86
+ );
87
+
88
+ const publicRta1 = new SubnetRouteTableAssociation({
89
+ SubnetId: publicSubnet1.SubnetId,
90
+ RouteTableId: publicRouteTable.RouteTableId,
91
+ });
92
+
93
+ const publicRta2 = new SubnetRouteTableAssociation({
94
+ SubnetId: publicSubnet2.SubnetId,
95
+ RouteTableId: publicRouteTable.RouteTableId,
96
+ });
97
+
98
+ // NAT gateway
99
+ const natEip = new EIP({
100
+ Domain: "vpc",
101
+ });
102
+
103
+ const natGateway = new NatGateway({
104
+ AllocationId: natEip.AllocationId,
105
+ SubnetId: publicSubnet1.SubnetId,
106
+ });
107
+
108
+ // Private route table
109
+ const privateRouteTable = new RouteTable({
110
+ VpcId: vpc.VpcId,
111
+ });
112
+
113
+ const privateRoute = new EC2Route({
114
+ RouteTableId: privateRouteTable.RouteTableId,
115
+ DestinationCidrBlock: "0.0.0.0/0",
116
+ NatGatewayId: natGateway.NatGatewayId,
117
+ });
118
+
119
+ const privateRta1 = new SubnetRouteTableAssociation({
120
+ SubnetId: privateSubnet1.SubnetId,
121
+ RouteTableId: privateRouteTable.RouteTableId,
122
+ });
123
+
124
+ const privateRta2 = new SubnetRouteTableAssociation({
125
+ SubnetId: privateSubnet2.SubnetId,
126
+ RouteTableId: privateRouteTable.RouteTableId,
127
+ });
128
+
129
+ return {
130
+ vpc,
131
+ igw,
132
+ igwAttachment,
133
+ publicSubnet1,
134
+ publicSubnet2,
135
+ privateSubnet1,
136
+ privateSubnet2,
137
+ publicRouteTable,
138
+ publicRoute,
139
+ publicRta1,
140
+ publicRta2,
141
+ privateRouteTable,
142
+ privateRta1,
143
+ privateRta2,
144
+ natEip,
145
+ natGateway,
146
+ privateRoute,
147
+ };
148
+ }, "VpcDefault");
@@ -0,0 +1,38 @@
1
+ import { describe, test, expect } from "bun:test";
2
+ import { DECLARABLE_MARKER } from "@intentius/chant/declarable";
3
+ import { defaultTags, isDefaultTags, DEFAULT_TAGS_MARKER } from "./default-tags";
4
+
5
+ describe("defaultTags", () => {
6
+ test("factory returns correct markers and tags", () => {
7
+ const tags = defaultTags([
8
+ { Key: "Env", Value: "prod" },
9
+ { Key: "Team", Value: "platform" },
10
+ ]);
11
+
12
+ expect(tags[DEFAULT_TAGS_MARKER]).toBe(true);
13
+ expect(tags[DECLARABLE_MARKER]).toBe(true);
14
+ expect(tags.lexicon).toBe("aws");
15
+ expect(tags.entityType).toBe("chant:aws:defaultTags");
16
+ expect(tags.tags).toHaveLength(2);
17
+ expect(tags.tags[0]).toEqual({ Key: "Env", Value: "prod" });
18
+ expect(tags.tags[1]).toEqual({ Key: "Team", Value: "platform" });
19
+ });
20
+
21
+ test("factory accepts empty tags array", () => {
22
+ const tags = defaultTags([]);
23
+ expect(tags.tags).toHaveLength(0);
24
+ });
25
+
26
+ test("isDefaultTags returns true for DefaultTags", () => {
27
+ const tags = defaultTags([{ Key: "k", Value: "v" }]);
28
+ expect(isDefaultTags(tags)).toBe(true);
29
+ });
30
+
31
+ test("isDefaultTags returns false for non-DefaultTags", () => {
32
+ expect(isDefaultTags(null)).toBe(false);
33
+ expect(isDefaultTags(undefined)).toBe(false);
34
+ expect(isDefaultTags({})).toBe(false);
35
+ expect(isDefaultTags("string")).toBe(false);
36
+ expect(isDefaultTags({ lexicon: "aws" })).toBe(false);
37
+ });
38
+ });
@@ -0,0 +1,77 @@
1
+ /**
2
+ * Default Tags — declares project-wide tags for all taggable resources.
3
+ *
4
+ * When a project exports a `defaultTags(...)` declaration, the serializer
5
+ * automatically injects those tags into every taggable resource at synthesis
6
+ * time. Explicit tags on individual resources take precedence.
7
+ */
8
+
9
+ import { DECLARABLE_MARKER, type Declarable } from "@intentius/chant/declarable";
10
+
11
+ /**
12
+ * Marker symbol for default tags identification.
13
+ */
14
+ export const DEFAULT_TAGS_MARKER = Symbol.for("chant.aws.defaultTags");
15
+
16
+ /**
17
+ * A single tag entry — Key is always a string, Value supports strings,
18
+ * Parameters, intrinsics, or any value the serializer can resolve.
19
+ */
20
+ export interface TagEntry {
21
+ readonly Key: string;
22
+ readonly Value: unknown;
23
+ }
24
+
25
+ /**
26
+ * A default tags declaration — wraps a tag array into a Declarable
27
+ * that the serializer uses to inject tags into all taggable resources.
28
+ */
29
+ export interface DefaultTags extends Declarable {
30
+ readonly [DEFAULT_TAGS_MARKER]: true;
31
+ readonly [DECLARABLE_MARKER]: true;
32
+ readonly lexicon: "aws";
33
+ readonly entityType: "chant:aws:defaultTags";
34
+ readonly tags: readonly TagEntry[];
35
+ }
36
+
37
+ /**
38
+ * Type guard for DefaultTags.
39
+ */
40
+ export function isDefaultTags(value: unknown): value is DefaultTags {
41
+ return (
42
+ typeof value === "object" &&
43
+ value !== null &&
44
+ DEFAULT_TAGS_MARKER in value &&
45
+ (value as Record<symbol, unknown>)[DEFAULT_TAGS_MARKER] === true
46
+ );
47
+ }
48
+
49
+ /**
50
+ * Declare project-wide default tags for all taggable resources.
51
+ *
52
+ * Tags are injected at synthesis time into every resource that supports
53
+ * tagging (per the CloudFormation Registry metadata). If a resource has
54
+ * an explicit Tag with the same Key, the explicit value wins.
55
+ *
56
+ * @param tags - Array of { Key, Value } tag entries
57
+ * @returns A DefaultTags Declarable
58
+ *
59
+ * @example
60
+ * ```ts
61
+ * import { defaultTags, Sub, AWS } from "@intentius/chant-lexicon-aws";
62
+ *
63
+ * export const tags = defaultTags([
64
+ * { Key: "Project", Value: "my-app" },
65
+ * { Key: "Environment", Value: Sub`${AWS.StackName}` },
66
+ * ]);
67
+ * ```
68
+ */
69
+ export function defaultTags(tags: TagEntry[]): DefaultTags {
70
+ return {
71
+ [DEFAULT_TAGS_MARKER]: true,
72
+ [DECLARABLE_MARKER]: true,
73
+ lexicon: "aws",
74
+ entityType: "chant:aws:defaultTags",
75
+ tags,
76
+ };
77
+ }