@intentius/chant-lexicon-aws 0.45.0 → 0.49.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/components/cloud-executor.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/lambda-function.d.ts +4 -4
- package/dist/composites/lambda-function.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 +6 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/integrity.json +9 -4
- package/dist/lint/audit-catalog.d.ts.map +1 -1
- package/dist/lint/post-synth/cf-refs.d.ts +24 -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/waw059.d.ts +36 -0
- package/dist/lint/post-synth/waw059.d.ts.map +1 -0
- package/dist/lint/post-synth/waw060.d.ts +16 -0
- package/dist/lint/post-synth/waw060.d.ts.map +1 -0
- 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/manifest.json +1 -1
- package/dist/okf/index.md +5 -0
- package/dist/okf/rules/WAW059.md +25 -0
- package/dist/okf/rules/WAW060.md +17 -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/types/Action.md +2 -0
- package/dist/okf/types/AutoScalingGroup.md +1 -0
- package/dist/okf/types/Bucket.md +2 -0
- package/dist/okf/types/ECRRepository.md +1 -0
- package/dist/okf/types/Function.md +1 -0
- package/dist/okf/types/GlobalTable.md +4 -0
- package/dist/okf/types/IamPolicy.md +3 -0
- package/dist/okf/types/InstanceProfile.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 +4 -0
- package/dist/okf/types/Map.md +2 -0
- package/dist/okf/types/Queue.md +1 -0
- package/dist/okf/types/Role.md +3 -0
- package/dist/okf/types/SecurityGroup.md +1 -0
- package/dist/okf/types/StateMachine.md +4 -0
- package/dist/okf/types/Subnet.md +4 -0
- package/dist/okf/types/Table.md +2 -0
- package/dist/okf/types/Type.md +5 -0
- package/dist/okf/types/Vpc.md +1 -0
- package/dist/op/activities/aws-apply.d.ts +48 -5
- package/dist/op/activities/aws-apply.d.ts.map +1 -1
- package/dist/op/activities/index.d.ts +11 -4
- package/dist/op/activities/index.d.ts.map +1 -1
- package/dist/ownership.d.ts +18 -0
- package/dist/ownership.d.ts.map +1 -1
- 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 +62 -0
- package/dist/rules/waw059.ts +353 -0
- package/dist/rules/waw060.ts +91 -0
- package/dist/rules/waw061.ts +67 -0
- package/dist/rules/waw062.ts +0 -0
- package/dist/rules/waw063.ts +0 -0
- package/dist/serializer.d.ts.map +1 -1
- package/dist/skills/chant-aws-carve-terraform.md +26 -23
- package/dist/teardown.d.ts +85 -0
- package/dist/teardown.d.ts.map +1 -0
- package/package.json +3 -3
- package/src/api/read-client.test.ts +9 -0
- package/src/api/read-client.ts +15 -2
- package/src/components/cloud-executor.ts +10 -1
- 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/index.ts +28 -7
- package/src/lifecycle-integration.test.ts +4 -0
- package/src/lint/audit-catalog.ts +11 -0
- package/src/lint/post-synth/cf-refs.ts +62 -0
- package/src/lint/post-synth/index.ts +10 -0
- package/src/lint/post-synth/waw059.test.ts +309 -0
- package/src/lint/post-synth/waw059.ts +353 -0
- package/src/lint/post-synth/waw060.test.ts +131 -0
- package/src/lint/post-synth/waw060.ts +91 -0
- 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/op/activities/aws-apply.test.ts +111 -0
- package/src/op/activities/aws-apply.ts +100 -6
- package/src/op/activities/index.ts +20 -3
- package/src/ownership.test.ts +24 -1
- package/src/ownership.ts +37 -0
- package/src/plugin.ts +59 -10
- package/src/receipt-store.test.ts +204 -0
- package/src/receipt-store.ts +315 -0
- package/src/serializer-ownership.test.ts +18 -0
- package/src/serializer.ts +114 -3
- package/src/skills/chant-aws-carve-terraform.md +26 -23
- package/src/teardown.test.ts +258 -0
- package/src/teardown.ts +276 -0
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* WAW061: Subnet CIDR Not Contained In VPC CIDR
|
|
3
|
+
*
|
|
4
|
+
* A subnet's CidrBlock must fall entirely inside its VPC's CidrBlock.
|
|
5
|
+
* Stock CDK does not check this — CloudFormation accepts the template and
|
|
6
|
+
* only fails the `AWS::EC2::Subnet` resource at deploy time
|
|
7
|
+
* ("The CIDR ... is invalid" / "does not fall within the CIDR range of the
|
|
8
|
+
* VPC"), after the rest of the stack may have already started rolling out.
|
|
9
|
+
* This is a cheap static graph check: follow the subnet's VpcId Ref to a
|
|
10
|
+
* declared VPC in the same template and do IPv4 CIDR-range math.
|
|
11
|
+
*
|
|
12
|
+
* Stays quiet whenever the graph can't prove it: intrinsic CIDR blocks,
|
|
13
|
+
* IPv6, a VpcId that isn't a simple Ref to a declared AWS::EC2::VPC, or a
|
|
14
|
+
* VPC whose own CidrBlock isn't a literal string.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
import type { PostSynthCheck, PostSynthContext, PostSynthDiagnostic } from "@intentius/chant/lint/post-synth";
|
|
18
|
+
import { parseCFTemplate, findResourceRefs, ipv4CidrContains } from "./cf-refs";
|
|
19
|
+
|
|
20
|
+
export function checkSubnetCidrContainment(ctx: PostSynthContext): PostSynthDiagnostic[] {
|
|
21
|
+
const diagnostics: PostSynthDiagnostic[] = [];
|
|
22
|
+
|
|
23
|
+
for (const [_lexicon, output] of ctx.outputs) {
|
|
24
|
+
const template = parseCFTemplate(output);
|
|
25
|
+
if (!template?.Resources) continue;
|
|
26
|
+
|
|
27
|
+
for (const [logicalId, resource] of Object.entries(template.Resources)) {
|
|
28
|
+
if (resource.Type !== "AWS::EC2::Subnet") continue;
|
|
29
|
+
|
|
30
|
+
const props = resource.Properties ?? {};
|
|
31
|
+
const subnetCidr = props.CidrBlock;
|
|
32
|
+
if (typeof subnetCidr !== "string") continue;
|
|
33
|
+
|
|
34
|
+
const vpcRefs = findResourceRefs(props.VpcId);
|
|
35
|
+
if (vpcRefs.size !== 1) continue;
|
|
36
|
+
const [vpcId] = vpcRefs;
|
|
37
|
+
|
|
38
|
+
const vpc = template.Resources[vpcId];
|
|
39
|
+
if (!vpc || vpc.Type !== "AWS::EC2::VPC") continue;
|
|
40
|
+
|
|
41
|
+
const vpcCidr = vpc.Properties?.CidrBlock;
|
|
42
|
+
if (typeof vpcCidr !== "string") continue;
|
|
43
|
+
|
|
44
|
+
const contained = ipv4CidrContains(vpcCidr, subnetCidr);
|
|
45
|
+
if (contained === false) {
|
|
46
|
+
diagnostics.push({
|
|
47
|
+
checkId: "WAW061",
|
|
48
|
+
severity: "error",
|
|
49
|
+
message: `Subnet "${logicalId}" CidrBlock ${subnetCidr} is not contained within VPC "${vpcId}"'s CidrBlock ${vpcCidr} — CloudFormation rejects this at deploy time`,
|
|
50
|
+
entity: logicalId,
|
|
51
|
+
lexicon: "aws",
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
return diagnostics;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export const waw061: PostSynthCheck = {
|
|
61
|
+
id: "WAW061",
|
|
62
|
+
description: "Subnet CidrBlock falls outside its VPC's CidrBlock — fails at deploy time",
|
|
63
|
+
|
|
64
|
+
check(ctx: PostSynthContext): PostSynthDiagnostic[] {
|
|
65
|
+
return checkSubnetCidrContainment(ctx);
|
|
66
|
+
},
|
|
67
|
+
};
|
|
Binary file
|
|
Binary file
|
package/dist/serializer.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"serializer.d.ts","sourceRoot":"","sources":["../src/serializer.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,UAAU,EAAsC,MAAM,6BAA6B,CAAC;
|
|
1
|
+
{"version":3,"file":"serializer.d.ts","sourceRoot":"","sources":["../src/serializer.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,UAAU,EAAsC,MAAM,6BAA6B,CAAC;AAiflG;;GAEG;AACH,eAAO,MAAM,aAAa,EAAE,UA4E3B,CAAC"}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
skill: chant-aws-carve-terraform
|
|
3
|
-
description: Demo carving a resource out of Terraform into native chant — advise, emit, bridge, apply — fully offline
|
|
3
|
+
description: Demo carving a resource out of Terraform into native chant — advise, emit, audit, bridge, apply — fully offline
|
|
4
4
|
user-invocable: true
|
|
5
5
|
---
|
|
6
6
|
|
|
@@ -15,7 +15,7 @@ graduation.
|
|
|
15
15
|
## When to use
|
|
16
16
|
|
|
17
17
|
- Someone asks "how does chant move things off Terraform?"
|
|
18
|
-
- You want to show the advise → emit → bridge → apply loop end to end.
|
|
18
|
+
- You want to show the advise → emit → audit → bridge → apply loop end to end.
|
|
19
19
|
- You are evaluating whether a Terraform estate is worth carving.
|
|
20
20
|
|
|
21
21
|
## Preconditions
|
|
@@ -27,7 +27,7 @@ graduation.
|
|
|
27
27
|
## The fastest path: run the bundled demo
|
|
28
28
|
|
|
29
29
|
The `examples/terraform-carve-out` example ships a runnable estate + state and a
|
|
30
|
-
script that runs all
|
|
30
|
+
script that runs all five steps with commentary:
|
|
31
31
|
|
|
32
32
|
```bash
|
|
33
33
|
cd examples/terraform-carve-out
|
|
@@ -54,28 +54,30 @@ From `examples/terraform-carve-out`, with `TF=./terraform`:
|
|
|
54
54
|
--state ./terraform/terraform.tfstate --output ./carveout
|
|
55
55
|
```
|
|
56
56
|
Show `./carveout/src/assets.ts` — a real `new Bucket({ BucketName, Tags })` with
|
|
57
|
-
CloudFormation-style properties mapped from the Terraform state attributes
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
57
|
+
CloudFormation-style properties mapped from the Terraform state attributes,
|
|
58
|
+
and the `aws_s3_bucket_versioning` sub-resource folded into
|
|
59
|
+
`VersioningConfiguration`. Emit also scaffolds `./carveout` into a buildable
|
|
60
|
+
chant project (`chant.config.ts`, `package.json`) and persists a carve
|
|
61
|
+
manifest (`*.carve.json`) — bridge and apply read the target from it, so
|
|
62
|
+
`--select` is only needed once. Explain: a Terraform-managed resource is not
|
|
63
|
+
in any CloudFormation stack, so the correct source of its live shape is the
|
|
64
|
+
state file, not a cloud read.
|
|
65
|
+
|
|
66
|
+
3. **Audit the inherited resource.**
|
|
65
67
|
```bash
|
|
66
|
-
chant
|
|
68
|
+
chant build ./carveout/src --lexicon aws
|
|
67
69
|
```
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
70
|
+
The first build fails, deliberately: the post-synth audit refuses the
|
|
71
|
+
adopted bucket because Terraform managed it without a public-access block or
|
|
72
|
+
a TLS-only policy. This is a feature of carving: chant immediately tells the
|
|
73
|
+
person what is wrong with what they adopted. Show the fix (add
|
|
74
|
+
`PublicAccessBlockConfiguration` and a companion `S3BucketPolicy` with an
|
|
75
|
+
`aws:SecureTransport = false` Deny — the tutorial page has the exact code),
|
|
76
|
+
then re-run the build and show the valid CloudFormation template.
|
|
75
77
|
|
|
76
78
|
4. **Bridge — patch the surviving Terraform.**
|
|
77
79
|
```bash
|
|
78
|
-
chant carve bridge --from ./terraform --
|
|
80
|
+
chant carve bridge --from ./terraform --output ./carveout
|
|
79
81
|
```
|
|
80
82
|
Show the generated `data "aws_s3_bucket" "assets"` and the rewired survivor.
|
|
81
83
|
Emphasize it is dry-run: nothing in `./terraform` changed. `--apply-rewrites`
|
|
@@ -83,11 +85,12 @@ From `examples/terraform-carve-out`, with `TF=./terraform`:
|
|
|
83
85
|
|
|
84
86
|
5. **Apply — graduation plan.**
|
|
85
87
|
```bash
|
|
86
|
-
chant carve apply --from ./terraform --
|
|
88
|
+
chant carve apply --from ./terraform --output ./carveout --env prod --stack assets
|
|
87
89
|
```
|
|
88
90
|
Show the ownership marker (`chant:managed-by/stack/env`) and the finalized
|
|
89
|
-
runbook.
|
|
90
|
-
|
|
91
|
+
runbook. `--write-source` additionally stamps the marker tags into the
|
|
92
|
+
emitted source. Stress this makes no cloud call — the apply is the person's
|
|
93
|
+
own lifecycle; chant just plans it.
|
|
91
94
|
|
|
92
95
|
## Key points to land
|
|
93
96
|
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Env teardown for the aws lexicon (chant #1222) — both halves of the
|
|
3
|
+
* `teardownOwned` / `executeTeardown` capability pair, at STACK granularity.
|
|
4
|
+
*
|
|
5
|
+
* Per-resource selection is impossible here by construction: the thin read
|
|
6
|
+
* (`describeResources`) is sourced from `describe-stack-resources`, which
|
|
7
|
+
* returns no tags, so no resource-level marker can be read on the teardown
|
|
8
|
+
* path. The stack is aws's ownership boundary anyway — the applier deploys
|
|
9
|
+
* whole stacks and deletes ride CloudFormation — so teardown enumerates the
|
|
10
|
+
* environment's stacks and verifies the marker on each STACK's own tags
|
|
11
|
+
* (`DescribeStacks` → `Tags`), stamped there by the apply paths from the
|
|
12
|
+
* template's `Metadata["chant:ownership"]` block.
|
|
13
|
+
*
|
|
14
|
+
* Which stacks are the environment's: the project's declared `stacks[]` when
|
|
15
|
+
* it is a multi-stack project, else the single-stack convention — the explicit
|
|
16
|
+
* `stack` option, else the stack named after the environment (the same rule
|
|
17
|
+
* `describeResources` and `exportResources` apply, see chant #932).
|
|
18
|
+
*
|
|
19
|
+
* Verification is tag-reading, never name-trusting. A resolved stack whose
|
|
20
|
+
* tags carry exactly the requested identity (managed-by + stack + env) is a
|
|
21
|
+
* candidate of type `AWS::CloudFormation::Stack`. A stack carrying a DIFFERENT
|
|
22
|
+
* marker identity verifiably belongs to another project or env — out of scope,
|
|
23
|
+
* silently, the way another env's resources are. A stack carrying NO marker at
|
|
24
|
+
* all is unknowable — a legacy chant stack from before stack tagging, or a
|
|
25
|
+
* foreign stack that happens to hold the env's name — and is reported as a
|
|
26
|
+
* hole (`filtered` / unverified-ownership), never deleted. An absent stack is
|
|
27
|
+
* knowledge, not a hole: nothing to tear down.
|
|
28
|
+
*
|
|
29
|
+
* Execution re-reads each candidate stack's tags immediately before deleting
|
|
30
|
+
* (the enumeration is a moment old at best, and a delete is not undoable),
|
|
31
|
+
* then rides the existing Query-API delete — `awsDelete`, DeleteStack polled
|
|
32
|
+
* to DELETE_COMPLETE. An identity that no longer matches is `not-prunable:
|
|
33
|
+
* unverified-ownership`; an already-absent stack is `deleted` (teardown is
|
|
34
|
+
* idempotent).
|
|
35
|
+
*/
|
|
36
|
+
import type { TeardownCandidate, TeardownEnumeration, TeardownExecution } from "@intentius/chant/lexicon";
|
|
37
|
+
import { type OwnershipMarker } from "@intentius/chant/ownership";
|
|
38
|
+
import { type AwsReadClientOptions } from "./api/read-client.js";
|
|
39
|
+
import { type AwsHttp } from "./op/activities/aws-apply.js";
|
|
40
|
+
/** The one candidate type this lexicon's teardown produces: whole stacks. */
|
|
41
|
+
export declare const STACK_TYPE = "AWS::CloudFormation::Stack";
|
|
42
|
+
export interface AwsTeardownOptions {
|
|
43
|
+
environment: string;
|
|
44
|
+
/** The identity to select on: this project's ownership stack + the env being torn down. */
|
|
45
|
+
marker: OwnershipMarker;
|
|
46
|
+
/** Explicit deployed stack name (single-stack override). */
|
|
47
|
+
stack?: string;
|
|
48
|
+
/** Region that stack is deployed in. */
|
|
49
|
+
region?: string;
|
|
50
|
+
/** A multi-stack project's declared stacks (chant.config `stacks[]`). */
|
|
51
|
+
stacks?: Array<{
|
|
52
|
+
name: string;
|
|
53
|
+
region?: string;
|
|
54
|
+
}>;
|
|
55
|
+
}
|
|
56
|
+
/** Injectable transports/timeouts, so tests double the Query API (no network). */
|
|
57
|
+
export interface AwsTeardownDeps {
|
|
58
|
+
/** Options for the read client's `DescribeStacks` calls (http injection, endpoint). */
|
|
59
|
+
read?: AwsReadClientOptions;
|
|
60
|
+
/** The applier transport `awsDelete` polls DeleteStack through. */
|
|
61
|
+
applyHttp?: AwsHttp;
|
|
62
|
+
/** DeleteStack settle timeout in ms (default `awsDelete`'s 300000). */
|
|
63
|
+
timeoutMs?: number;
|
|
64
|
+
/** DeleteStack poll interval in ms (default `awsDelete`'s 3000). */
|
|
65
|
+
intervalMs?: number;
|
|
66
|
+
}
|
|
67
|
+
/** The stacks an env teardown resolves to: `stacks[]`, else the single-stack convention. */
|
|
68
|
+
export declare function resolveTeardownStacks(options: AwsTeardownOptions): Array<{
|
|
69
|
+
name: string;
|
|
70
|
+
region?: string;
|
|
71
|
+
}>;
|
|
72
|
+
/**
|
|
73
|
+
* Enumerate the environment's marker-verified stacks — the aws half of
|
|
74
|
+
* `chant lifecycle teardown <env>` planning. Read-only.
|
|
75
|
+
*/
|
|
76
|
+
export declare function teardownOwned(options: AwsTeardownOptions, deps?: AwsTeardownDeps): Promise<TeardownEnumeration>;
|
|
77
|
+
/**
|
|
78
|
+
* Delete the handed-over stacks: re-verify each stack's own tags, then
|
|
79
|
+
* DeleteStack via `awsDelete`, polled to DELETE_COMPLETE. One outcome per
|
|
80
|
+
* candidate, always.
|
|
81
|
+
*/
|
|
82
|
+
export declare function executeTeardown(options: AwsTeardownOptions & {
|
|
83
|
+
candidates: TeardownCandidate[];
|
|
84
|
+
}, deps?: AwsTeardownDeps): Promise<TeardownExecution>;
|
|
85
|
+
//# sourceMappingURL=teardown.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"teardown.d.ts","sourceRoot":"","sources":["../src/teardown.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAkCG;AAEH,OAAO,KAAK,EACV,iBAAiB,EACjB,mBAAmB,EACnB,iBAAiB,EAGlB,MAAM,0BAA0B,CAAC;AAClC,OAAO,EAAiB,KAAK,eAAe,EAAE,MAAM,4BAA4B,CAAC;AAEjF,OAAO,EAKL,KAAK,oBAAoB,EAC1B,MAAM,mBAAmB,CAAC;AAC3B,OAAO,EAAa,KAAK,OAAO,EAAE,MAAM,2BAA2B,CAAC;AAEpE,6EAA6E;AAC7E,eAAO,MAAM,UAAU,+BAA+B,CAAC;AAEvD,MAAM,WAAW,kBAAkB;IACjC,WAAW,EAAE,MAAM,CAAC;IACpB,2FAA2F;IAC3F,MAAM,EAAE,eAAe,CAAC;IACxB,4DAA4D;IAC5D,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,wCAAwC;IACxC,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,yEAAyE;IACzE,MAAM,CAAC,EAAE,KAAK,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,MAAM,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;CACnD;AAED,kFAAkF;AAClF,MAAM,WAAW,eAAe;IAC9B,uFAAuF;IACvF,IAAI,CAAC,EAAE,oBAAoB,CAAC;IAC5B,mEAAmE;IACnE,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,uEAAuE;IACvE,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,oEAAoE;IACpE,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED,4FAA4F;AAC5F,wBAAgB,qBAAqB,CACnC,OAAO,EAAE,kBAAkB,GAC1B,KAAK,CAAC;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,MAAM,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC,CAQ1C;AAmDD;;;GAGG;AACH,wBAAsB,aAAa,CACjC,OAAO,EAAE,kBAAkB,EAC3B,IAAI,GAAE,eAAoB,GACzB,OAAO,CAAC,mBAAmB,CAAC,CAuD9B;AAED;;;;GAIG;AACH,wBAAsB,eAAe,CACnC,OAAO,EAAE,kBAAkB,GAAG;IAAE,UAAU,EAAE,iBAAiB,EAAE,CAAA;CAAE,EACjE,IAAI,GAAE,eAAoB,GACzB,OAAO,CAAC,iBAAiB,CAAC,CAW5B"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@intentius/chant-lexicon-aws",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.49.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",
|
|
@@ -73,14 +73,14 @@
|
|
|
73
73
|
},
|
|
74
74
|
"dependencies": {
|
|
75
75
|
"fflate": "^0.8.2",
|
|
76
|
-
"js-yaml": "^4.1
|
|
76
|
+
"js-yaml": "^4.3.1"
|
|
77
77
|
},
|
|
78
78
|
"devDependencies": {
|
|
79
79
|
"@intentius/chant": "*",
|
|
80
80
|
"typescript": "^5.9.3"
|
|
81
81
|
},
|
|
82
82
|
"peerDependencies": {
|
|
83
|
-
"@intentius/chant": "^0.
|
|
83
|
+
"@intentius/chant": "^0.49.0",
|
|
84
84
|
"typescript": "^5.9.3"
|
|
85
85
|
}
|
|
86
86
|
}
|
|
@@ -198,6 +198,15 @@ describe("Cloud Control", () => {
|
|
|
198
198
|
expect(await listResources("AWS::S3::Bucket", { http })).toEqual([]);
|
|
199
199
|
});
|
|
200
200
|
|
|
201
|
+
test("listResources scopes a child listing with ResourceModel, JSON-encoded as the API takes it", async () => {
|
|
202
|
+
const { http, calls } = recording(() => respond(JSON.stringify({ ResourceDescriptions: [] })));
|
|
203
|
+
await listResources("AWS::IAM::RolePolicy", { http }, { RoleName: "app-role" });
|
|
204
|
+
expect(JSON.parse(calls[0].body)).toEqual({
|
|
205
|
+
TypeName: "AWS::IAM::RolePolicy",
|
|
206
|
+
ResourceModel: JSON.stringify({ RoleName: "app-role" }),
|
|
207
|
+
});
|
|
208
|
+
});
|
|
209
|
+
|
|
201
210
|
test("parseResourceDescription refuses anything that is not a model object", () => {
|
|
202
211
|
expect(parseResourceDescription(null)).toBeNull();
|
|
203
212
|
expect(parseResourceDescription({ Identifier: "a" })).toBeNull();
|
package/src/api/read-client.ts
CHANGED
|
@@ -434,17 +434,30 @@ export async function getResource(
|
|
|
434
434
|
return parseResourceDescription(body.ResourceDescription);
|
|
435
435
|
}
|
|
436
436
|
|
|
437
|
-
/**
|
|
437
|
+
/**
|
|
438
|
+
* `ListResources` — every live resource of one type, paginated to exhaustion.
|
|
439
|
+
*
|
|
440
|
+
* `resourceModel` is Cloud Control's `ResourceModel`: the scope a child-typed
|
|
441
|
+
* listing requires (`{ RoleName }` for `AWS::IAM::RolePolicy`,
|
|
442
|
+
* `{ TopicArn }` for `AWS::SNS::Subscription`). The API takes it as a JSON
|
|
443
|
+
* *string*, the same double encoding `GetResource` answers with; this takes
|
|
444
|
+
* the object and encodes it so no caller repeats that detail.
|
|
445
|
+
*/
|
|
438
446
|
export async function listResources(
|
|
439
447
|
typeName: string,
|
|
440
448
|
options: AwsReadClientOptions = {},
|
|
449
|
+
resourceModel?: Record<string, unknown>,
|
|
441
450
|
): Promise<CloudControlDescription[]> {
|
|
442
451
|
const out: CloudControlDescription[] = [];
|
|
443
452
|
let nextToken: string | undefined;
|
|
444
453
|
do {
|
|
445
454
|
const body = await cloudControl(
|
|
446
455
|
"ListResources",
|
|
447
|
-
{
|
|
456
|
+
{
|
|
457
|
+
TypeName: typeName,
|
|
458
|
+
...(resourceModel ? { ResourceModel: JSON.stringify(resourceModel) } : {}),
|
|
459
|
+
...(nextToken ? { NextToken: nextToken } : {}),
|
|
460
|
+
},
|
|
448
461
|
options,
|
|
449
462
|
);
|
|
450
463
|
const descriptions = Array.isArray(body.ResourceDescriptions) ? body.ResourceDescriptions : [];
|
|
@@ -24,6 +24,7 @@ import { tmpdir } from "node:os";
|
|
|
24
24
|
import { join } from "node:path";
|
|
25
25
|
import { realDocker, type DockerClient } from "@intentius/chant/components/verbs/cloud-executor";
|
|
26
26
|
import { q } from "@intentius/chant/components/verbs/process-runner";
|
|
27
|
+
import { ownershipStackTagsForBody } from "../ownership.js";
|
|
27
28
|
|
|
28
29
|
export type { DockerClient };
|
|
29
30
|
|
|
@@ -432,10 +433,18 @@ const realCloudFormation: CloudFormationClient = {
|
|
|
432
433
|
body = readFileSync(args.templatePath, "utf8");
|
|
433
434
|
} catch { /* unreadable template — create-change-set reports it */ }
|
|
434
435
|
const capabilities = awsDeployCapabilitiesForBody(body).join(" ");
|
|
436
|
+
// The template's ownership marker becomes the STACK's own tags (#1222) —
|
|
437
|
+
// the identity stack-level teardown verifies before a DeleteStack. Same
|
|
438
|
+
// source as the applier's `Tags.member.N`: `Metadata["chant:ownership"]`.
|
|
439
|
+
const stackTags = Object.entries(ownershipStackTagsForBody(body))
|
|
440
|
+
.sort(([a], [b]) => a.localeCompare(b))
|
|
441
|
+
.map(([k, v]) => q(`Key=${k},Value=${v}`))
|
|
442
|
+
.join(" ");
|
|
443
|
+
const tagsFlag = stackTags ? ` --tags ${stackTags}` : "";
|
|
435
444
|
await run(
|
|
436
445
|
`aws cloudformation create-change-set --stack-name ${q(args.stackName)} --change-set-name ${q(changeSetName)} ` +
|
|
437
446
|
`--change-set-type ${changeSetType} ` +
|
|
438
|
-
`--template-body file://${args.templatePath} --capabilities ${capabilities}${paramFlag}`,
|
|
447
|
+
`--template-body file://${args.templatePath} --capabilities ${capabilities}${paramFlag}${tagsFlag}`,
|
|
439
448
|
);
|
|
440
449
|
await run(
|
|
441
450
|
`aws cloudformation wait change-set-create-complete --stack-name ${q(args.stackName)} --change-set-name ${q(changeSetName)}`,
|
|
@@ -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
|
+
});
|