@intentius/chant-lexicon-aws 0.33.1 → 0.34.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/ambient.d.ts +39 -0
- package/dist/ambient.d.ts.map +1 -0
- package/dist/deep-observe.d.ts +5 -0
- package/dist/deep-observe.d.ts.map +1 -1
- package/dist/defaults.d.ts +44 -0
- package/dist/defaults.d.ts.map +1 -0
- package/dist/dependencies.d.ts +60 -0
- package/dist/dependencies.d.ts.map +1 -0
- package/dist/generated/index.d.ts +2 -0
- package/dist/generated/index.d.ts.map +1 -1
- package/dist/integrity.json +4 -4
- package/dist/manifest.json +1 -1
- package/dist/meta.json +62 -1
- package/dist/plugin.d.ts.map +1 -1
- package/dist/properties.d.ts +48 -0
- package/dist/properties.d.ts.map +1 -0
- package/dist/reference-catalog.d.ts.map +1 -1
- package/dist/types/index.d.ts +56 -4
- package/package.json +2 -2
- package/src/ambient.test.ts +71 -0
- package/src/ambient.ts +147 -0
- package/src/deep-observe.ts +9 -0
- package/src/defaults.test.ts +73 -0
- package/src/defaults.ts +97 -0
- package/src/dependencies.test.ts +208 -0
- package/src/dependencies.ts +323 -0
- package/src/generated/index.d.ts +56 -4
- package/src/generated/index.ts +2 -0
- package/src/generated/lexicon-aws.json +62 -1
- package/src/plugin.ts +65 -64
- package/src/properties.test.ts +113 -0
- package/src/properties.ts +166 -0
- package/src/reference-catalog.ts +24 -3
package/src/generated/index.d.ts
CHANGED
|
@@ -16403,6 +16403,38 @@ export declare class Harness {
|
|
|
16403
16403
|
readonly Version: string;
|
|
16404
16404
|
}
|
|
16405
16405
|
|
|
16406
|
+
export declare class HarnessEndpoint {
|
|
16407
|
+
constructor(props: {
|
|
16408
|
+
/** The name of the endpoint. Must start with a letter and contain only alphanumeric characters and underscores. */
|
|
16409
|
+
EndpointName: string;
|
|
16410
|
+
/** The ID of the harness that the endpoint belongs to. */
|
|
16411
|
+
HarnessId: string;
|
|
16412
|
+
/** The Amazon Resource Name (ARN) of the endpoint. */
|
|
16413
|
+
Arn?: string;
|
|
16414
|
+
/** The timestamp when the endpoint was created. */
|
|
16415
|
+
CreatedAt?: string;
|
|
16416
|
+
/** The description of the endpoint. */
|
|
16417
|
+
Description?: string;
|
|
16418
|
+
/** The name of the harness that the endpoint belongs to. */
|
|
16419
|
+
HarnessName?: string;
|
|
16420
|
+
/** The harness version that the endpoint is currently serving. */
|
|
16421
|
+
LiveVersion?: string;
|
|
16422
|
+
Status?: HarnessEndpoint_HarnessEndpointStatus;
|
|
16423
|
+
/** Tags to apply to the harness endpoint resource. */
|
|
16424
|
+
Tags?: HarnessEndpoint_Tag[];
|
|
16425
|
+
/** The harness version that the endpoint points to and serves invocations from. */
|
|
16426
|
+
TargetVersion?: string;
|
|
16427
|
+
/** The timestamp when the endpoint was last updated. */
|
|
16428
|
+
UpdatedAt?: string;
|
|
16429
|
+
}, attributes?: CFResourceAttributes);
|
|
16430
|
+
readonly Arn: string;
|
|
16431
|
+
readonly CreatedAt: string;
|
|
16432
|
+
readonly HarnessName: string;
|
|
16433
|
+
readonly LiveVersion: string;
|
|
16434
|
+
readonly Status: HarnessEndpoint_HarnessEndpointStatus;
|
|
16435
|
+
readonly UpdatedAt: string;
|
|
16436
|
+
}
|
|
16437
|
+
|
|
16406
16438
|
export declare class HealthCheck {
|
|
16407
16439
|
constructor(props: {
|
|
16408
16440
|
/** A complex type that contains information about the health check. */
|
|
@@ -57089,7 +57121,7 @@ export declare class ContainerRecipe_EbsInstanceBlockDeviceSpecification {
|
|
|
57089
57121
|
/** Use to override the device's volume size. */
|
|
57090
57122
|
VolumeSize?: number;
|
|
57091
57123
|
/** Use to override the device's volume type. */
|
|
57092
|
-
VolumeType?: "gp2" | "gp3" | "io1" | "io2" | "sc1" | "st1" | "standard";
|
|
57124
|
+
VolumeType?: "gp2" | "gp3" | "gp3a" | "io1" | "io2" | "io2a" | "sc1" | "st1" | "standard";
|
|
57093
57125
|
});
|
|
57094
57126
|
}
|
|
57095
57127
|
|
|
@@ -75141,7 +75173,7 @@ export declare class GlobalTable_ReplicaSSESpecification {
|
|
|
75141
75173
|
|
|
75142
75174
|
export declare class GlobalTable_ReplicaStreamSpecification {
|
|
75143
75175
|
constructor(props: {
|
|
75144
|
-
ResourcePolicy
|
|
75176
|
+
ResourcePolicy: GlobalTable_ResourcePolicy;
|
|
75145
75177
|
});
|
|
75146
75178
|
}
|
|
75147
75179
|
|
|
@@ -76693,6 +76725,13 @@ export declare class HarnessBedrockModelConfig {
|
|
|
76693
76725
|
});
|
|
76694
76726
|
}
|
|
76695
76727
|
|
|
76728
|
+
export declare class HarnessEndpoint_Tag {
|
|
76729
|
+
constructor(props: {
|
|
76730
|
+
Key: string;
|
|
76731
|
+
Value: string;
|
|
76732
|
+
});
|
|
76733
|
+
}
|
|
76734
|
+
|
|
76696
76735
|
export declare class HarnessEnvironmentArtifact {
|
|
76697
76736
|
constructor(props: {
|
|
76698
76737
|
ContainerConfiguration?: Harness_ContainerConfiguration;
|
|
@@ -78724,7 +78763,7 @@ export declare class ImageRecipe_EbsInstanceBlockDeviceSpecification {
|
|
|
78724
78763
|
/** Use to override the device's volume size. */
|
|
78725
78764
|
VolumeSize?: number;
|
|
78726
78765
|
/** Use to override the device's volume type. */
|
|
78727
|
-
VolumeType?: "gp2" | "gp3" | "io1" | "io2" | "sc1" | "st1" | "standard";
|
|
78766
|
+
VolumeType?: "gp2" | "gp3" | "gp3a" | "io1" | "io2" | "io2a" | "sc1" | "st1" | "standard";
|
|
78728
78767
|
});
|
|
78729
78768
|
}
|
|
78730
78769
|
|
|
@@ -86435,7 +86474,9 @@ export declare class Listener_RedirectConfig {
|
|
|
86435
86474
|
|
|
86436
86475
|
export declare class Listener_Tag {
|
|
86437
86476
|
constructor(props: {
|
|
86477
|
+
/** The key of the tag. */
|
|
86438
86478
|
Key: string;
|
|
86479
|
+
/** The value of the tag. */
|
|
86439
86480
|
Value: string;
|
|
86440
86481
|
});
|
|
86441
86482
|
}
|
|
@@ -106283,7 +106324,7 @@ export declare class ReplicaSSESpecification {
|
|
|
106283
106324
|
|
|
106284
106325
|
export declare class ReplicaStreamSpecification {
|
|
106285
106326
|
constructor(props: {
|
|
106286
|
-
ResourcePolicy
|
|
106327
|
+
ResourcePolicy: GlobalTable_ResourcePolicy;
|
|
106287
106328
|
});
|
|
106288
106329
|
}
|
|
106289
106330
|
|
|
@@ -112163,6 +112204,8 @@ export declare class Scraper_Destination {
|
|
|
112163
112204
|
constructor(props: {
|
|
112164
112205
|
/** Configuration for Amazon Managed Prometheus metrics destination */
|
|
112165
112206
|
AmpConfiguration?: Record<string, unknown>;
|
|
112207
|
+
/** Configuration for CloudWatch metrics destination */
|
|
112208
|
+
CloudWatchConfiguration?: Record<string, unknown>;
|
|
112166
112209
|
});
|
|
112167
112210
|
}
|
|
112168
112211
|
|
|
@@ -130350,6 +130393,15 @@ export type Harness_HarnessStatus =
|
|
|
130350
130393
|
| "UPDATE_FAILED"
|
|
130351
130394
|
| "UPDATING";
|
|
130352
130395
|
|
|
130396
|
+
export type HarnessEndpoint_HarnessEndpointStatus =
|
|
130397
|
+
| "CREATE_FAILED"
|
|
130398
|
+
| "CREATING"
|
|
130399
|
+
| "DELETE_FAILED"
|
|
130400
|
+
| "DELETING"
|
|
130401
|
+
| "READY"
|
|
130402
|
+
| "UPDATE_FAILED"
|
|
130403
|
+
| "UPDATING";
|
|
130404
|
+
|
|
130353
130405
|
export type HealthImagingDatastore_DatastoreStatus =
|
|
130354
130406
|
| "ACTIVE"
|
|
130355
130407
|
| "CREATE_FAILED"
|
package/src/generated/index.ts
CHANGED
|
@@ -640,6 +640,7 @@ export const GuardHook = createResource("AWS::CloudFormation::GuardHook", "aws",
|
|
|
640
640
|
export const Guardrail = createResource("AWS::Bedrock::Guardrail", "aws", {"CreatedAt":"CreatedAt","FailureRecommendations":"FailureRecommendations","GuardrailArn":"GuardrailArn","GuardrailId":"GuardrailId","Status":"Status","StatusReasons":"StatusReasons","UpdatedAt":"UpdatedAt","Version":"Version"});
|
|
641
641
|
export const GuardrailVersion = createResource("AWS::Bedrock::GuardrailVersion", "aws", {"GuardrailArn":"GuardrailArn","GuardrailId":"GuardrailId","Version":"Version"});
|
|
642
642
|
export const Harness = createResource("AWS::BedrockAgentCore::Harness", "aws", {"Arn":"Arn","HarnessId":"HarnessId","Status":"Status","Version":"Version","CreatedAt":"CreatedAt","UpdatedAt":"UpdatedAt","Memory_ManagedMemoryConfiguration_Arn":"Memory.ManagedMemoryConfiguration.Arn","Environment_AgentCoreRuntimeEnvironment_AgentRuntimeArn":"Environment.AgentCoreRuntimeEnvironment.AgentRuntimeArn","Environment_AgentCoreRuntimeEnvironment_AgentRuntimeName":"Environment.AgentCoreRuntimeEnvironment.AgentRuntimeName","Environment_AgentCoreRuntimeEnvironment_AgentRuntimeId":"Environment.AgentCoreRuntimeEnvironment.AgentRuntimeId"});
|
|
643
|
+
export const HarnessEndpoint = createResource("AWS::BedrockAgentCore::HarnessEndpoint", "aws", {"Arn":"Arn","HarnessName":"HarnessName","Status":"Status","LiveVersion":"LiveVersion","CreatedAt":"CreatedAt","UpdatedAt":"UpdatedAt"});
|
|
643
644
|
export const HealthCheck = createResource("AWS::Route53::HealthCheck", "aws", {"HealthCheckId":"HealthCheckId"});
|
|
644
645
|
export const HealthImagingDatastore = createResource("AWS::HealthImaging::Datastore", "aws", {"DatastoreArn":"DatastoreArn","CreatedAt":"CreatedAt","UpdatedAt":"UpdatedAt","DatastoreId":"DatastoreId","DatastoreStatus":"DatastoreStatus"});
|
|
645
646
|
export const HookDefaultVersion = createResource("AWS::CloudFormation::HookDefaultVersion", "aws", {"Arn":"Arn"});
|
|
@@ -5495,6 +5496,7 @@ export const HarnessAgentCoreGatewayConfig = createProperty("AWS::BedrockAgentCo
|
|
|
5495
5496
|
export const HarnessAgentCoreMemoryConfiguration = createProperty("AWS::BedrockAgentCore::Harness.HarnessAgentCoreMemoryConfiguration", "aws");
|
|
5496
5497
|
export const HarnessAgentCoreRuntimeEnvironment = createProperty("AWS::BedrockAgentCore::Harness.HarnessAgentCoreRuntimeEnvironment", "aws");
|
|
5497
5498
|
export const HarnessBedrockModelConfig = createProperty("AWS::BedrockAgentCore::Harness.HarnessBedrockModelConfig", "aws");
|
|
5499
|
+
export const HarnessEndpoint_Tag = createProperty("AWS::BedrockAgentCore::HarnessEndpoint.Tag", "aws");
|
|
5498
5500
|
export const HarnessEnvironmentArtifact = createProperty("AWS::BedrockAgentCore::Harness.HarnessEnvironmentArtifact", "aws");
|
|
5499
5501
|
export const HarnessEnvironmentProvider = createProperty("AWS::BedrockAgentCore::Harness.HarnessEnvironmentProvider", "aws");
|
|
5500
5502
|
export const HarnessGeminiModelConfig = createProperty("AWS::BedrockAgentCore::Harness.HarnessGeminiModelConfig", "aws");
|
|
@@ -44553,6 +44553,67 @@
|
|
|
44553
44553
|
"kind": "property",
|
|
44554
44554
|
"lexicon": "aws"
|
|
44555
44555
|
},
|
|
44556
|
+
"HarnessEndpoint": {
|
|
44557
|
+
"resourceType": "AWS::BedrockAgentCore::HarnessEndpoint",
|
|
44558
|
+
"kind": "resource",
|
|
44559
|
+
"lexicon": "aws",
|
|
44560
|
+
"attrs": {
|
|
44561
|
+
"Arn": "Arn",
|
|
44562
|
+
"HarnessName": "HarnessName",
|
|
44563
|
+
"Status": "Status",
|
|
44564
|
+
"LiveVersion": "LiveVersion",
|
|
44565
|
+
"CreatedAt": "CreatedAt",
|
|
44566
|
+
"UpdatedAt": "UpdatedAt"
|
|
44567
|
+
},
|
|
44568
|
+
"propertyConstraints": {
|
|
44569
|
+
"HarnessId": {
|
|
44570
|
+
"pattern": "^[a-zA-Z][a-zA-Z0-9_]{0,39}-[a-zA-Z0-9]{10}$"
|
|
44571
|
+
},
|
|
44572
|
+
"EndpointName": {
|
|
44573
|
+
"pattern": "^[a-zA-Z][a-zA-Z0-9_]{0,47}$"
|
|
44574
|
+
},
|
|
44575
|
+
"TargetVersion": {
|
|
44576
|
+
"pattern": "^([1-9][0-9]{0,4})$",
|
|
44577
|
+
"minLength": 1,
|
|
44578
|
+
"maxLength": 5
|
|
44579
|
+
},
|
|
44580
|
+
"LiveVersion": {
|
|
44581
|
+
"pattern": "^([1-9][0-9]{0,4})$",
|
|
44582
|
+
"minLength": 1,
|
|
44583
|
+
"maxLength": 5
|
|
44584
|
+
},
|
|
44585
|
+
"Description": {
|
|
44586
|
+
"minLength": 1,
|
|
44587
|
+
"maxLength": 256
|
|
44588
|
+
},
|
|
44589
|
+
"CreatedAt": {
|
|
44590
|
+
"format": "date-time"
|
|
44591
|
+
},
|
|
44592
|
+
"UpdatedAt": {
|
|
44593
|
+
"format": "date-time"
|
|
44594
|
+
}
|
|
44595
|
+
},
|
|
44596
|
+
"createOnly": [
|
|
44597
|
+
"HarnessId",
|
|
44598
|
+
"EndpointName"
|
|
44599
|
+
],
|
|
44600
|
+
"writeOnly": [
|
|
44601
|
+
"TargetVersion"
|
|
44602
|
+
],
|
|
44603
|
+
"primaryIdentifier": [
|
|
44604
|
+
"Arn"
|
|
44605
|
+
],
|
|
44606
|
+
"tagging": {
|
|
44607
|
+
"taggable": true,
|
|
44608
|
+
"tagOnCreate": true,
|
|
44609
|
+
"tagUpdatable": true
|
|
44610
|
+
}
|
|
44611
|
+
},
|
|
44612
|
+
"HarnessEndpoint_Tag": {
|
|
44613
|
+
"resourceType": "AWS::BedrockAgentCore::HarnessEndpoint.Tag",
|
|
44614
|
+
"kind": "property",
|
|
44615
|
+
"lexicon": "aws"
|
|
44616
|
+
},
|
|
44556
44617
|
"HarnessEnvironmentArtifact": {
|
|
44557
44618
|
"resourceType": "AWS::BedrockAgentCore::Harness.HarnessEnvironmentArtifact",
|
|
44558
44619
|
"kind": "property",
|
|
@@ -70884,7 +70945,7 @@
|
|
|
70884
70945
|
"maxLength": 256
|
|
70885
70946
|
},
|
|
70886
70947
|
"FunctionName": {
|
|
70887
|
-
"pattern": "^(arn:(aws[a-zA-Z-]*)?:lambda:)?((eusc-)?[a-z]{2}((-gov)|(-iso([a-z]?)))?-[a-z]+-\\d{1}:)?(\\d{12}:)?(function:)?([a-zA-Z0-9-_]+)(:(\\$LATEST
|
|
70948
|
+
"pattern": "^(arn:(aws[a-zA-Z-]*)?:lambda:)?((eusc-)?[a-z]{2}((-gov)|(-iso([a-z]?)))?-[a-z]+-\\d{1}:)?(\\d{12}:)?(function:)?([a-zA-Z0-9-_]+)(:(\\$LATEST(\\.PUBLISHED)?|[a-zA-Z0-9-_]+))?$",
|
|
70888
70949
|
"minLength": 1,
|
|
70889
70950
|
"maxLength": 140
|
|
70890
70951
|
},
|
package/src/plugin.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { createRequire } from "module";
|
|
2
2
|
import { detectTemplate } from "./detect";
|
|
3
|
-
import type { LexiconPlugin, IntrinsicDef, ObservationResult, DeepObservationResult, ResourceMetadata, ExportedTemplate, ResourceSelector, InitTemplateSet, StackStatusObservation } from "@intentius/chant/lexicon";
|
|
3
|
+
import type { LexiconPlugin, IntrinsicDef, ObservationResult, DeepObservationResult, DependencyObservation, ResourceMetadata, ExportedTemplate, ResourceSelector, InitTemplateSet, StackStatusObservation } from "@intentius/chant/lexicon";
|
|
4
4
|
const require = createRequire(import.meta.url);
|
|
5
5
|
import type { LintRule } from "@intentius/chant/lint/rule";
|
|
6
6
|
import type { TemplateParser } from "@intentius/chant/import/parser";
|
|
@@ -19,6 +19,9 @@ import { applyAwsEndpointArgv } from "./components/cloud-executor";
|
|
|
19
19
|
import { stackDoesNotExist } from "./stack-errors";
|
|
20
20
|
import { awsDeepNormalizationHooks, observeResourcesDeepAws } from "./deep-observe";
|
|
21
21
|
import { awsReferenceCatalog } from "./reference-catalog";
|
|
22
|
+
import { AMBIENT_KINDS } from "./ambient";
|
|
23
|
+
import { describeOwnProperties, stampRegion } from "./properties";
|
|
24
|
+
import { stampProviderDefaults } from "./defaults";
|
|
22
25
|
import { resolveTemplateAttrs } from "./live-attrs";
|
|
23
26
|
import { CFParser } from "./import/parser";
|
|
24
27
|
import { CFGenerator } from "./import/generator";
|
|
@@ -36,58 +39,8 @@ export { stackDoesNotExist } from "./stack-errors";
|
|
|
36
39
|
* Provides serializer, lint rules, template detection,
|
|
37
40
|
* import parsing, and code generation for AWS CloudFormation.
|
|
38
41
|
*/
|
|
39
|
-
/**
|
|
40
|
-
|
|
41
|
-
* subnet is internet-facing iff its route table (an explicit association, else
|
|
42
|
-
* the VPC's main table) has a default route to an Internet Gateway. This covers
|
|
43
|
-
* the account's default VPC, whose routing chant does not model declaratively
|
|
44
|
-
* (the instance references it through a parameter). Best-effort: any failure
|
|
45
|
-
* returns what it has, so search still works on the declared topology.
|
|
46
|
-
*/
|
|
47
|
-
async function liveInternetFacing(regionArgs: string[], stackName: string): Promise<Record<string, string>> {
|
|
48
|
-
const { getRuntime } = await import("@intentius/chant/runtime-adapter");
|
|
49
|
-
const rt = getRuntime();
|
|
50
|
-
const run = (args: string[]) =>
|
|
51
|
-
rt.spawn(applyAwsEndpointArgv(["aws", ...args, ...regionArgs, "--output", "json"], process.env.AWS_ENDPOINT_URL));
|
|
52
|
-
const out: Record<string, string> = {};
|
|
53
|
-
try {
|
|
54
|
-
const res = await run(["cloudformation", "describe-stack-resources", "--stack-name", stackName]);
|
|
55
|
-
if (res.exitCode !== 0) return out;
|
|
56
|
-
const stackRes = (JSON.parse(res.stdout).StackResources ?? []) as Array<{ LogicalResourceId: string; ResourceType: string; PhysicalResourceId: string }>;
|
|
57
|
-
const instByLogical = new Map<string, string>();
|
|
58
|
-
for (const r of stackRes) if (r.ResourceType === "AWS::EC2::Instance") instByLogical.set(r.LogicalResourceId, r.PhysicalResourceId);
|
|
59
|
-
if (instByLogical.size === 0) return out;
|
|
60
|
-
|
|
61
|
-
const di = await run(["ec2", "describe-instances", "--instance-ids", ...instByLogical.values()]);
|
|
62
|
-
if (di.exitCode !== 0) return out;
|
|
63
|
-
const locByInst = new Map<string, { subnet?: string; vpc?: string }>();
|
|
64
|
-
for (const rsv of (JSON.parse(di.stdout).Reservations ?? []) as Array<{ Instances?: Array<{ InstanceId: string; SubnetId?: string; VpcId?: string }> }>)
|
|
65
|
-
for (const i of rsv.Instances ?? []) locByInst.set(i.InstanceId, { subnet: i.SubnetId, vpc: i.VpcId });
|
|
66
|
-
|
|
67
|
-
const rtRes = await run(["ec2", "describe-route-tables"]);
|
|
68
|
-
if (rtRes.exitCode !== 0) return out;
|
|
69
|
-
const subnetIgw = new Map<string, string>();
|
|
70
|
-
const vpcMainIgw = new Map<string, string>();
|
|
71
|
-
for (const t of (JSON.parse(rtRes.stdout).RouteTables ?? []) as Array<{ RouteTableId?: string; VpcId?: string; Routes?: Array<{ GatewayId?: string }>; Associations?: Array<{ SubnetId?: string; Main?: boolean }> }>) {
|
|
72
|
-
const igwRoute = (t.Routes ?? []).find((r) => typeof r.GatewayId === "string" && r.GatewayId.startsWith("igw-"));
|
|
73
|
-
if (!igwRoute) continue;
|
|
74
|
-
const ev = `${t.RouteTableId ?? "rtb"} → ${igwRoute.GatewayId}`;
|
|
75
|
-
for (const a of t.Associations ?? []) {
|
|
76
|
-
if (a.SubnetId) subnetIgw.set(a.SubnetId, ev);
|
|
77
|
-
if (a.Main && t.VpcId) vpcMainIgw.set(t.VpcId, ev);
|
|
78
|
-
}
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
for (const [logical, instId] of instByLogical) {
|
|
82
|
-
const loc = locByInst.get(instId);
|
|
83
|
-
const ev = loc ? ((loc.subnet && subnetIgw.get(loc.subnet)) || (loc.vpc && vpcMainIgw.get(loc.vpc))) : undefined;
|
|
84
|
-
if (ev) out[logical] = ev;
|
|
85
|
-
}
|
|
86
|
-
} catch {
|
|
87
|
-
/* best-effort */
|
|
88
|
-
}
|
|
89
|
-
return out;
|
|
90
|
-
}
|
|
42
|
+
/** #1265 — the ownership notice is about the environment, so it is said once. */
|
|
43
|
+
let warnedOwnership = false;
|
|
91
44
|
|
|
92
45
|
export const awsPlugin: LexiconPlugin = {
|
|
93
46
|
name: "aws",
|
|
@@ -598,10 +551,17 @@ aws cloudformation wait stack-update-complete --stack-name my-app-prod`,
|
|
|
598
551
|
if (options.owned) {
|
|
599
552
|
// describe-stack-resources does not return tags, so ownership cannot be
|
|
600
553
|
// determined here. Degrade to detect-only rather than silently filtering.
|
|
601
|
-
//
|
|
602
|
-
|
|
554
|
+
//
|
|
555
|
+
// Once per process, not once per stack (#1265). It is a property of the
|
|
556
|
+
// environment, not of each stack, and a four-stack project printed four
|
|
557
|
+
// identical copies ahead of every answer — enough that an agent piping
|
|
558
|
+
// `graph --format ir` with `2>&1` had to skip lines to find the JSON.
|
|
559
|
+
warnedOwnership ||
|
|
560
|
+
// eslint-disable-next-line no-console
|
|
561
|
+
console.warn(
|
|
603
562
|
"[aws] ownership filter unavailable on describeResources (no tags from describe-stack-resources) — returning all, each with an explicit `unknown` verdict; use `chant import --from <env> --owned` for ownership-filtered export",
|
|
604
|
-
|
|
563
|
+
);
|
|
564
|
+
warnedOwnership = true;
|
|
605
565
|
}
|
|
606
566
|
|
|
607
567
|
// Derive stack name. A multi-stack project passes the explicit CloudFormation
|
|
@@ -708,10 +668,17 @@ aws cloudformation wait stack-update-complete --stack-name my-app-prod`,
|
|
|
708
668
|
};
|
|
709
669
|
}
|
|
710
670
|
|
|
671
|
+
// Each resource's OWN properties, on top of the stack outputs above (#1279).
|
|
672
|
+
// Until this, a node's `attrs` were the stack's exports replicated onto
|
|
673
|
+
// every member, so no instance carried its own `VpcId`.
|
|
674
|
+
const withProperties = stampProviderDefaults(
|
|
675
|
+
stampRegion(await describeOwnProperties(resources, options.region), options.region),
|
|
676
|
+
);
|
|
677
|
+
|
|
711
678
|
// Every entity the stack answered for was answered for: an entity the
|
|
712
679
|
// template doesn't carry is genuinely not in this stack, which is an
|
|
713
680
|
// absence, not a hole.
|
|
714
|
-
return observation(
|
|
681
|
+
return observation(withProperties);
|
|
715
682
|
},
|
|
716
683
|
|
|
717
684
|
/**
|
|
@@ -719,12 +686,52 @@ aws cloudformation wait stack-update-complete --stack-name my-app-prod`,
|
|
|
719
686
|
* CloudFormation's view of the world, into the resource as the service
|
|
720
687
|
* actually holds it. Implementation in ./deep-observe.ts.
|
|
721
688
|
*/
|
|
689
|
+
/**
|
|
690
|
+
* The routing this estate depends on but does not declare (#1273) — an
|
|
691
|
+
* instance in the account's default VPC routes through a table nobody wrote.
|
|
692
|
+
* Reporting the resources lets the graph fold derive `internetFacing`, rather
|
|
693
|
+
* than this lexicon computing it and injecting the conclusion.
|
|
694
|
+
*/
|
|
695
|
+
async observeDependencies(options: {
|
|
696
|
+
environment: string;
|
|
697
|
+
entities: Map<string, { entityType: string; props: Record<string, unknown> }>;
|
|
698
|
+
observed: Record<string, ResourceMetadata>;
|
|
699
|
+
stack?: string;
|
|
700
|
+
region?: string;
|
|
701
|
+
}): Promise<DependencyObservation> {
|
|
702
|
+
const { observeAwsDependencies } = await import("./dependencies");
|
|
703
|
+
return observeAwsDependencies({ observed: options.observed, region: options.region });
|
|
704
|
+
},
|
|
705
|
+
|
|
706
|
+
/**
|
|
707
|
+
* Resources of a managed kind that exist without being declared or
|
|
708
|
+
* referenced (#1278) — the account's default security groups, an unattached
|
|
709
|
+
* one someone left behind. Nothing else in the observation can see them,
|
|
710
|
+
* because everything else resolves outward from what is declared.
|
|
711
|
+
*/
|
|
712
|
+
/** #1278 — the kinds `observeAmbient` can enumerate, from the same source. */
|
|
713
|
+
ambientKinds(): string[] {
|
|
714
|
+
return AMBIENT_KINDS;
|
|
715
|
+
},
|
|
716
|
+
|
|
717
|
+
async observeAmbient(options: {
|
|
718
|
+
environment: string;
|
|
719
|
+
kinds: string[];
|
|
720
|
+
observed: Record<string, ResourceMetadata>;
|
|
721
|
+
stack?: string;
|
|
722
|
+
region?: string;
|
|
723
|
+
}): Promise<Record<string, ResourceMetadata>> {
|
|
724
|
+
const { observeAwsAmbient } = await import("./ambient");
|
|
725
|
+
return observeAwsAmbient({ kinds: options.kinds, observed: options.observed, region: options.region });
|
|
726
|
+
},
|
|
727
|
+
|
|
722
728
|
async observeResourcesDeep(options: {
|
|
723
729
|
environment: string;
|
|
724
730
|
buildOutput: string;
|
|
725
731
|
entityNames: string[];
|
|
726
732
|
entities: Map<string, { entityType: string; props: Record<string, unknown> }>;
|
|
727
733
|
stack?: string;
|
|
734
|
+
region?: string;
|
|
728
735
|
owned?: boolean;
|
|
729
736
|
}): Promise<DeepObservationResult> {
|
|
730
737
|
return observeResourcesDeepAws({
|
|
@@ -732,6 +739,7 @@ aws cloudformation wait stack-update-complete --stack-name my-app-prod`,
|
|
|
732
739
|
entityNames: options.entityNames,
|
|
733
740
|
entities: options.entities,
|
|
734
741
|
stack: options.stack,
|
|
742
|
+
region: options.region,
|
|
735
743
|
owned: options.owned,
|
|
736
744
|
});
|
|
737
745
|
},
|
|
@@ -827,13 +835,6 @@ aws cloudformation wait stack-update-complete --stack-name my-app-prod`,
|
|
|
827
835
|
for (const [logicalId, v] of Object.entries(attrs)) {
|
|
828
836
|
merged[multi ? `${ref.name}::${logicalId}` : logicalId] = v;
|
|
829
837
|
}
|
|
830
|
-
// Resolve internetFacing from live route tables (covers the default VPC),
|
|
831
|
-
// carrying the route-table → IGW evidence so search can justify the match.
|
|
832
|
-
const facing = await liveInternetFacing(ref.region ? ["--region", ref.region] : [], ref.name);
|
|
833
|
-
for (const [logicalId, via] of Object.entries(facing)) {
|
|
834
|
-
const key = multi ? `${ref.name}::${logicalId}` : logicalId;
|
|
835
|
-
merged[key] = { ...merged[key], internetFacing: true, internetFacingVia: via };
|
|
836
|
-
}
|
|
837
838
|
} catch {
|
|
838
839
|
// A stack that isn't deployed yet contributes no live attrs — skip it.
|
|
839
840
|
}
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
import { describe, it, expect, vi, beforeEach } from "vitest";
|
|
2
|
+
|
|
3
|
+
const spawnMock = vi.fn();
|
|
4
|
+
vi.mock("@intentius/chant/runtime-adapter", async (importOriginal) => {
|
|
5
|
+
const actual = await importOriginal<typeof import("@intentius/chant/runtime-adapter")>();
|
|
6
|
+
return { ...actual, getRuntime: () => ({ ...actual.getRuntime(), spawn: spawnMock }) };
|
|
7
|
+
});
|
|
8
|
+
|
|
9
|
+
const { describeOwnProperties, canDescribe, stampRegion } = await import("./properties");
|
|
10
|
+
|
|
11
|
+
const ok = (body: unknown) => ({ stdout: JSON.stringify(body), stderr: "", exitCode: 0 });
|
|
12
|
+
const fail = { stdout: "", stderr: "InvalidInstanceID.NotFound", exitCode: 255 };
|
|
13
|
+
|
|
14
|
+
const instance = (id: string, vpc: string) => ({ InstanceId: id, VpcId: vpc });
|
|
15
|
+
const reservations = (...rows: Array<Record<string, unknown>>) => ({
|
|
16
|
+
Reservations: [{ Instances: rows }],
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
// #1279 — `describe-stack-resources` returns identity and nothing about the
|
|
20
|
+
// resource, so the observation had been filling `attributes` with the *stack's*
|
|
21
|
+
// outputs, copied onto every member. No node carried its own VpcId.
|
|
22
|
+
describe("describeOwnProperties (#1279)", () => {
|
|
23
|
+
beforeEach(() => spawnMock.mockReset());
|
|
24
|
+
|
|
25
|
+
const observed = {
|
|
26
|
+
web: { type: "AWS::EC2::Instance", status: "OK", physicalId: "i-1" },
|
|
27
|
+
api: { type: "AWS::EC2::Instance", status: "OK", physicalId: "i-2" },
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
it("joins each resource's own properties back by physical id", async () => {
|
|
31
|
+
spawnMock.mockResolvedValue(ok(reservations(instance("i-1", "vpc-a"), instance("i-2", "vpc-b"))));
|
|
32
|
+
const merged = await describeOwnProperties(observed);
|
|
33
|
+
expect(merged.web.attributes?.VpcId).toBe("vpc-a");
|
|
34
|
+
expect(merged.api.attributes?.VpcId).toBe("vpc-b");
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
it("reads a kind once for the whole observation, not once per resource", async () => {
|
|
38
|
+
spawnMock.mockResolvedValue(ok(reservations(instance("i-1", "vpc-a"), instance("i-2", "vpc-b"))));
|
|
39
|
+
await describeOwnProperties(observed);
|
|
40
|
+
expect(spawnMock).toHaveBeenCalledTimes(1);
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
it("keeps stack outputs, and lets the resource's own property win the name", async () => {
|
|
44
|
+
const withOutputs = {
|
|
45
|
+
web: {
|
|
46
|
+
type: "AWS::EC2::Instance",
|
|
47
|
+
status: "OK",
|
|
48
|
+
physicalId: "i-1",
|
|
49
|
+
attributes: { expVpcId: "vpc-exported", VpcId: "stale" },
|
|
50
|
+
},
|
|
51
|
+
};
|
|
52
|
+
spawnMock.mockResolvedValue(ok(reservations(instance("i-1", "vpc-a"))));
|
|
53
|
+
const merged = await describeOwnProperties(withOutputs);
|
|
54
|
+
expect(merged.web.attributes?.expVpcId).toBe("vpc-exported");
|
|
55
|
+
expect(merged.web.attributes?.VpcId).toBe("vpc-a");
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
it("falls back to one call per id when the batch fails on a single bad id", async () => {
|
|
59
|
+
// AWS fails the whole call on one unknown id. A snapshot naming an instance
|
|
60
|
+
// that has since been terminated would otherwise take every other
|
|
61
|
+
// instance's properties down with it — and the empty result is
|
|
62
|
+
// indistinguishable from "the account has nothing to say".
|
|
63
|
+
spawnMock
|
|
64
|
+
.mockResolvedValueOnce(fail) // the batch, killed by i-2
|
|
65
|
+
.mockResolvedValueOnce(ok(reservations(instance("i-1", "vpc-a"))))
|
|
66
|
+
.mockResolvedValueOnce(fail); // i-2 really is gone
|
|
67
|
+
const merged = await describeOwnProperties(observed);
|
|
68
|
+
expect(merged.web.attributes?.VpcId).toBe("vpc-a");
|
|
69
|
+
expect(merged.api.attributes).toBeUndefined();
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
it("leaves the observation untouched when the kind cannot be read at all", async () => {
|
|
73
|
+
spawnMock.mockResolvedValue(fail);
|
|
74
|
+
const merged = await describeOwnProperties({ web: observed.web });
|
|
75
|
+
expect(merged.web.attributes).toBeUndefined();
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
it("does not call out for a kind it cannot describe", async () => {
|
|
79
|
+
const merged = await describeOwnProperties({
|
|
80
|
+
fn: { type: "AWS::Lambda::Function", status: "OK", physicalId: "fn-1" },
|
|
81
|
+
});
|
|
82
|
+
expect(spawnMock).not.toHaveBeenCalled();
|
|
83
|
+
expect(merged.fn.attributes).toBeUndefined();
|
|
84
|
+
expect(canDescribe("AWS::Lambda::Function")).toBe(false);
|
|
85
|
+
});
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
// #1279 — the observation is scoped per stack and each stack declares its
|
|
89
|
+
// region, so the reader knew this and threw it away.
|
|
90
|
+
describe("stampRegion (#1279)", () => {
|
|
91
|
+
const one = { web: { type: "AWS::EC2::Instance", status: "OK", physicalId: "i-1" } };
|
|
92
|
+
|
|
93
|
+
it("records the region the resource was observed in", () => {
|
|
94
|
+
expect(stampRegion(one, "us-west-2").web.attributes?.region).toBe("us-west-2");
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
it("keeps the properties already read", () => {
|
|
98
|
+
const withProps = { web: { ...one.web, attributes: { VpcId: "vpc-a" } } };
|
|
99
|
+
const out = stampRegion(withProps, "us-west-2").web.attributes;
|
|
100
|
+
expect(out).toMatchObject({ VpcId: "vpc-a", region: "us-west-2" });
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
it("falls back to the region the call would have used", () => {
|
|
104
|
+
const prev = process.env.AWS_REGION;
|
|
105
|
+
process.env.AWS_REGION = "eu-west-1";
|
|
106
|
+
try {
|
|
107
|
+
expect(stampRegion(one).web.attributes?.region).toBe("eu-west-1");
|
|
108
|
+
} finally {
|
|
109
|
+
if (prev === undefined) delete process.env.AWS_REGION;
|
|
110
|
+
else process.env.AWS_REGION = prev;
|
|
111
|
+
}
|
|
112
|
+
});
|
|
113
|
+
});
|