@intentius/chant-lexicon-terraform 0.75.1 → 0.76.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/behaviour/index.d.ts +1 -1
- package/dist/behaviour/index.d.ts.map +1 -1
- package/dist/behaviour/predict.d.ts +67 -9
- package/dist/behaviour/predict.d.ts.map +1 -1
- package/dist/behaviour/request.d.ts +1 -1
- package/dist/behaviour/request.d.ts.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/integrity.json +2 -2
- package/dist/manifest.json +1 -1
- package/dist/plugin.d.ts.map +1 -1
- package/package.json +2 -2
- package/src/behaviour/index.ts +3 -0
- package/src/behaviour/predict.test.ts +171 -3
- package/src/behaviour/predict.ts +171 -9
- package/src/behaviour/request.test.ts +8 -1
- package/src/behaviour/request.ts +2 -1
- package/src/index.ts +3 -1
- package/src/plugin.test.ts +23 -1
- package/src/plugin.ts +13 -0
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
export { blockAddressOf, edgeCoverageOf, regionOfArn, substituteReferences, terraformBehaviourRequest, type LiveResourceFacts, type LiveResourceStatus, type TerraformBehaviourEntity, type TerraformBehaviourRequest, type TerraformBehaviourRequestOptions, type TerraformLiveRead, } from "./request.js";
|
|
2
|
-
export { liveRootsOf, predictTerraformBehaviour, TERRAFORM, type TerraformBehaviourDeps, type TerraformPredictOptions, } from "./predict.js";
|
|
2
|
+
export { liveRootsOf, predictTerraformBehaviour, readTerraformDeclaration, TERRAFORM, terraformPredictOptionsFrom, type TerraformBehaviourDeps, type TerraformDeclarationReader, type TerraformPredictOptions, } from "./predict.js";
|
|
3
3
|
export { CATALOGUED_KINDS, isUnresolvedKind, REFERENCES_NOTHING, TERRAFORM_REFERENCE_CATALOG, } from "./reference-catalog.js";
|
|
4
4
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/behaviour/index.ts"],"names":[],"mappings":"AAGA,OAAO,EACL,cAAc,EACd,cAAc,EACd,WAAW,EACX,oBAAoB,EACpB,yBAAyB,EACzB,KAAK,iBAAiB,EACtB,KAAK,kBAAkB,EACvB,KAAK,wBAAwB,EAC7B,KAAK,yBAAyB,EAC9B,KAAK,gCAAgC,EACrC,KAAK,iBAAiB,GACvB,MAAM,WAAW,CAAC;AACnB,OAAO,EACL,WAAW,EACX,yBAAyB,EACzB,SAAS,EACT,KAAK,sBAAsB,EAC3B,KAAK,uBAAuB,GAC7B,MAAM,WAAW,CAAC;AACnB,OAAO,EACL,gBAAgB,EAChB,gBAAgB,EAChB,kBAAkB,EAClB,2BAA2B,GAC5B,MAAM,qBAAqB,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/behaviour/index.ts"],"names":[],"mappings":"AAGA,OAAO,EACL,cAAc,EACd,cAAc,EACd,WAAW,EACX,oBAAoB,EACpB,yBAAyB,EACzB,KAAK,iBAAiB,EACtB,KAAK,kBAAkB,EACvB,KAAK,wBAAwB,EAC7B,KAAK,yBAAyB,EAC9B,KAAK,gCAAgC,EACrC,KAAK,iBAAiB,GACvB,MAAM,WAAW,CAAC;AACnB,OAAO,EACL,WAAW,EACX,yBAAyB,EACzB,wBAAwB,EACxB,SAAS,EACT,2BAA2B,EAC3B,KAAK,sBAAsB,EAC3B,KAAK,0BAA0B,EAC/B,KAAK,uBAAuB,GAC7B,MAAM,WAAW,CAAC;AACnB,OAAO,EACL,gBAAgB,EAChB,gBAAgB,EAChB,kBAAkB,EAClB,2BAA2B,GAC5B,MAAM,qBAAqB,CAAC"}
|
|
@@ -88,25 +88,83 @@ export interface TerraformPredictOptions extends Omit<PredictBehaviourOptions, "
|
|
|
88
88
|
/** Directory the activities start the `chant.config.*` search from. Default: the process cwd. */
|
|
89
89
|
cwd?: string;
|
|
90
90
|
/**
|
|
91
|
-
* Which estate to predict
|
|
92
|
-
*
|
|
93
|
-
*
|
|
94
|
-
*
|
|
95
|
-
*
|
|
96
|
-
*
|
|
97
|
-
*
|
|
91
|
+
* Which estate to predict, as the contract's own `from` (#2494) and required
|
|
92
|
+
* for the same reason: a default is a guess about which estate somebody
|
|
93
|
+
* meant, and the report that comes back from a wrong guess is well-formed.
|
|
94
|
+
*
|
|
95
|
+
* `"live"` is this path's whole point: the account as it stands, drift
|
|
96
|
+
* included. `"declared"` runs the same producer over the declaration alone
|
|
97
|
+
* and reads no account, which is the other half of the delta the epic asks
|
|
98
|
+
* for (#2355) — a caller wanting that delta calls this twice and differences
|
|
99
|
+
* the two reports, and gets two reports assembled by one producer rather
|
|
100
|
+
* than two shapes that have each been through a different translation.
|
|
98
101
|
*
|
|
99
102
|
* `"declared"` on a project with no live root is the same request `"live"`
|
|
100
103
|
* builds, because there is no account to read; the option exists so that a
|
|
101
104
|
* caller can ask for the declaration of a root that *is* live.
|
|
102
105
|
*/
|
|
103
|
-
from
|
|
106
|
+
from: "live" | "declared";
|
|
104
107
|
}
|
|
108
|
+
/** Reads the project's declaration afresh. Injectable so a test parses nothing from disk. */
|
|
109
|
+
export type TerraformDeclarationReader = (cwd?: string) => Promise<ReadonlyMap<string, TerraformBehaviourEntity>>;
|
|
110
|
+
/**
|
|
111
|
+
* The build's own entities, read again from the project's config: what
|
|
112
|
+
* `buildRoots()` renders, with `mode`, `body` and `references` as the parse
|
|
113
|
+
* stamps them. The config is found the way `observeAmbient` and the
|
|
114
|
+
* activities find it, so `terraform.roots` means one thing to all of them.
|
|
115
|
+
*/
|
|
116
|
+
export declare const readTerraformDeclaration: TerraformDeclarationReader;
|
|
117
|
+
/**
|
|
118
|
+
* Read the contract's options as this producer's (#2495): the plugin method's
|
|
119
|
+
* whole adaptation. Two things are missing from an entity by the time a graph
|
|
120
|
+
* has carried it here, and which one depends on the graph.
|
|
121
|
+
*
|
|
122
|
+
* ## A declared graph has lost its `references`
|
|
123
|
+
*
|
|
124
|
+
* `root` and `mode` need no work: they are on `props`, the graph projects
|
|
125
|
+
* `props` onto a node's `attrs` as they stand, and `behaviourRequestFromIr`
|
|
126
|
+
* hands `attrs` back as `props`. `references` are left off `attrs` on
|
|
127
|
+
* purpose, because an edge already says the same thing (`graph-ir.ts`'s
|
|
128
|
+
* `SKIP_KEYS`), and the contract's `edges` arrive in their place. An `IREdge`
|
|
129
|
+
* is an `EntityReference` plus the entity it leaves from, so grouping the
|
|
130
|
+
* edges by `from` gives every entity its references back, field for field.
|
|
131
|
+
* Without this a block whose body holds a `${…}` is counted as an unresolved
|
|
132
|
+
* kind on a graph that resolved it. An entity that still carries its own
|
|
133
|
+
* `references` keeps them: the edges are the copy.
|
|
134
|
+
*
|
|
135
|
+
* ## A live graph has lost the declaration
|
|
136
|
+
*
|
|
137
|
+
* `chant graph --live` hands over observed nodes: an address, a root, the
|
|
138
|
+
* provider's type, and no `mode`, no `body` and no edge. Read as they stand,
|
|
139
|
+
* no root is live, so the account is never read here; no block has a size,
|
|
140
|
+
* because `body.instance_type` went with the body; and containment is empty.
|
|
141
|
+
* The figures that come back are well-formed and differ from the declared
|
|
142
|
+
* side's for reasons that have nothing to do with the account, which is the
|
|
143
|
+
* delta this producer exists to keep honest.
|
|
144
|
+
*
|
|
145
|
+
* So under `from: "live"`, a root any of whose entities arrived with no
|
|
146
|
+
* `mode` is declared again from the project's own config, and the producer
|
|
147
|
+
* then reads the account itself, as it does for any other caller. The
|
|
148
|
+
* observed entities of that root are set aside, the undeclared ones
|
|
149
|
+
* included: an owned orphan is the live read's to list, under the key
|
|
150
|
+
* `observeAmbient` gave it, and one the account no longer holds is not in a
|
|
151
|
+
* prediction of the account.
|
|
152
|
+
*
|
|
153
|
+
* A root the config does not render is left as it arrived. That is a worse
|
|
154
|
+
* reading and the only one available.
|
|
155
|
+
*
|
|
156
|
+
* ## What stops here
|
|
157
|
+
*
|
|
158
|
+
* The caller's `edges` and `edgeCoverage`. Both are this producer's to
|
|
159
|
+
* compute, from whichever reads it ends up making (see
|
|
160
|
+
* {@link TerraformPredictOptions}).
|
|
161
|
+
*/
|
|
162
|
+
export declare function terraformPredictOptionsFrom(options: PredictBehaviourOptions, redeclare?: TerraformDeclarationReader): Promise<TerraformPredictOptions>;
|
|
105
163
|
/** Which roots the request would read live: those whose entities the parse stamped `mode: "live"`. */
|
|
106
164
|
export declare function liveRootsOf(entityNames: readonly string[], entities: ReadonlyMap<string, TerraformBehaviourEntity>): string[];
|
|
107
165
|
/**
|
|
108
166
|
* Predict from the account for every live root, and from the declaration for
|
|
109
167
|
* every other, through the engine `deps.predict` fronts.
|
|
110
168
|
*/
|
|
111
|
-
export declare function predictTerraformBehaviour(options: TerraformPredictOptions, deps
|
|
169
|
+
export declare function predictTerraformBehaviour(options: TerraformPredictOptions, deps?: TerraformBehaviourDeps): Promise<BehaviourResult>;
|
|
112
170
|
//# sourceMappingURL=predict.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"predict.d.ts","sourceRoot":"","sources":["../../src/behaviour/predict.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAoDG;AAEH,OAAO,EAIL,KAAK,eAAe,EACpB,KAAK,uBAAuB,EAE7B,MAAM,4BAA4B,CAAC;
|
|
1
|
+
{"version":3,"file":"predict.d.ts","sourceRoot":"","sources":["../../src/behaviour/predict.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAoDG;AAEH,OAAO,EAIL,KAAK,eAAe,EACpB,KAAK,uBAAuB,EAE7B,MAAM,4BAA4B,CAAC;AAGpC,OAAO,EAA6B,KAAK,iBAAiB,EAAE,MAAM,uBAAuB,CAAC;AAK1F,OAAO,EAEL,KAAK,wBAAwB,EAE9B,MAAM,WAAW,CAAC;AAEnB,oDAAoD;AACpD,eAAO,MAAM,SAAS,cAAc,CAAC;AAErC,wFAAwF;AACxF,MAAM,WAAW,sBAAsB;IACrC,MAAM,EAAE,iBAAiB,CAAC,QAAQ,CAAC,CAAC;IACpC,QAAQ,EAAE,iBAAiB,CAAC,UAAU,CAAC,CAAC;IACxC;;;;OAIG;IACH,OAAO,EAAE,CAAC,OAAO,EAAE,uBAAuB,KAAK,OAAO,CAAC,eAAe,CAAC,CAAC;CACzE;AAED;;;;;;;;;;;;;;;GAeG;AACH,MAAM,WAAW,uBACf,SAAQ,IAAI,CAAC,uBAAuB,EAAE,UAAU,GAAG,OAAO,GAAG,cAAc,CAAC;IAC5E,QAAQ,EAAE,WAAW,CAAC,MAAM,EAAE,wBAAwB,CAAC,CAAC;IACxD,iGAAiG;IACjG,GAAG,CAAC,EAAE,MAAM,CAAC;IACb;;;;;;;;;;;;;;;OAeG;IACH,IAAI,EAAE,MAAM,GAAG,UAAU,CAAC;CAC3B;AAmBD,6FAA6F;AAC7F,MAAM,MAAM,0BAA0B,GAAG,CAAC,GAAG,CAAC,EAAE,MAAM,KAAK,OAAO,CAAC,WAAW,CAAC,MAAM,EAAE,wBAAwB,CAAC,CAAC,CAAC;AAElH;;;;;GAKG;AACH,eAAO,MAAM,wBAAwB,EAAE,0BAyBtC,CAAC;AAKF;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4CG;AACH,wBAAsB,2BAA2B,CAC/C,OAAO,EAAE,uBAAuB,EAChC,SAAS,GAAE,0BAAqD,GAC/D,OAAO,CAAC,uBAAuB,CAAC,CA+ClC;AAED,sGAAsG;AACtG,wBAAgB,WAAW,CACzB,WAAW,EAAE,SAAS,MAAM,EAAE,EAC9B,QAAQ,EAAE,WAAW,CAAC,MAAM,EAAE,wBAAwB,CAAC,GACtD,MAAM,EAAE,CAQV;AAQD;;;GAGG;AACH,wBAAsB,yBAAyB,CAC7C,OAAO,EAAE,uBAAuB,EAChC,IAAI,GAAE,sBAAkC,GACvC,OAAO,CAAC,eAAe,CAAC,CA4E1B"}
|
|
@@ -107,7 +107,7 @@ export interface LiveResourceFacts {
|
|
|
107
107
|
/** The marker tags the listing read off an undeclared resource. */
|
|
108
108
|
tags?: Record<string, string>;
|
|
109
109
|
}
|
|
110
|
-
export interface TerraformBehaviourRequestOptions extends Pick<PredictBehaviourOptions, "environment" | "buildOutput" | "traffic" | "region" | "stack" | "owned"> {
|
|
110
|
+
export interface TerraformBehaviourRequestOptions extends Pick<PredictBehaviourOptions, "environment" | "buildOutput" | "traffic" | "from" | "region" | "stack" | "owned"> {
|
|
111
111
|
entityNames: readonly string[];
|
|
112
112
|
entities: ReadonlyMap<string, TerraformBehaviourEntity>;
|
|
113
113
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"request.d.ts","sourceRoot":"","sources":["../../src/behaviour/request.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8DG;AAEH,OAAO,KAAK,EACV,qBAAqB,EACrB,uBAAuB,EACvB,iBAAiB,EAClB,MAAM,4BAA4B,CAAC;AACpC,OAAO,KAAK,EAAE,eAAe,EAAU,MAAM,2BAA2B,CAAC;AACzE,OAAO,EAAoB,KAAK,kBAAkB,EAAE,MAAM,6BAA6B,CAAC;AACxF,OAAO,EAML,KAAK,aAAa,EAClB,KAAK,aAAa,EAEnB,MAAM,uBAAuB,CAAC;AAI/B;;;;;;;GAOG;AACH,MAAM,WAAW,wBAAwB;IACvC,UAAU,EAAE,MAAM,CAAC;IACnB,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAC/B,UAAU,CAAC,EAAE,SAAS,eAAe,EAAE,CAAC;CACzC;AAED,qDAAqD;AACrD,MAAM,WAAW,iBAAiB;IAChC,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,aAAa,CAAC;IACvB,IAAI,EAAE,aAAa,CAAC;CACrB;AAED,6CAA6C;AAC7C,MAAM,MAAM,kBAAkB,GAAG,OAAO,GAAG,WAAW,GAAG,SAAS,GAAG,QAAQ,CAAC;AAE9E;;;;GAIG;AACH,MAAM,WAAW,iBAAiB;IAChC,MAAM,EAAE,kBAAkB,CAAC;IAC3B,SAAS,EAAE,OAAO,GAAG,SAAS,GAAG,SAAS,CAAC;IAC3C,+FAA+F;IAC/F,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,4DAA4D;IAC5D,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,oDAAoD;IACpD,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,sGAAsG;IACtG,SAAS,CAAC,EAAE,MAAM,EAAE,CAAC;IACrB,wEAAwE;IACxE,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,mEAAmE;IACnE,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CAC/B;AAED,MAAM,WAAW,gCACf,SAAQ,IAAI,CAAC,uBAAuB,EAAE,aAAa,GAAG,aAAa,GAAG,SAAS,GAAG,QAAQ,GAAG,OAAO,GAAG,OAAO,CAAC;
|
|
1
|
+
{"version":3,"file":"request.d.ts","sourceRoot":"","sources":["../../src/behaviour/request.ts"],"names":[],"mappings":"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"}
|
package/dist/index.d.ts
CHANGED
|
@@ -12,5 +12,5 @@ export type { TerraformWatchOpConfig, TerraformWatchOpResources, TerraformFindin
|
|
|
12
12
|
export { TerraformAdoptOp } from "./composites/terraform-adopt-op.js";
|
|
13
13
|
export type { TerraformAdoptOpConfig, TerraformAdoptOpResources } from "./composites/terraform-adopt-op.js";
|
|
14
14
|
export { TERRAFORM_STATE_OWNERSHIP_KEYS } from "./state-ownership.js";
|
|
15
|
-
export { predictTerraformBehaviour, terraformBehaviourRequest, TERRAFORM_REFERENCE_CATALOG, type TerraformBehaviourDeps, type TerraformBehaviourEntity, type TerraformBehaviourRequest, type TerraformBehaviourRequestOptions, type TerraformLiveRead, type TerraformPredictOptions, } from "./behaviour/index.js";
|
|
15
|
+
export { predictTerraformBehaviour, terraformBehaviourRequest, terraformPredictOptionsFrom, TERRAFORM_REFERENCE_CATALOG, type TerraformBehaviourDeps, type TerraformBehaviourEntity, type TerraformBehaviourRequest, type TerraformBehaviourRequestOptions, type TerraformLiveRead, type TerraformPredictOptions, } from "./behaviour/index.js";
|
|
16
16
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,eAAe,EAAE,MAAM,UAAU,CAAC;AAG3C,OAAO,EAAE,mBAAmB,EAAE,MAAM,cAAc,CAAC;AAKnD,OAAO,EACL,qBAAqB,EACrB,mBAAmB,EACnB,KAAK,eAAe,EACpB,KAAK,mBAAmB,EACxB,KAAK,wBAAwB,GAC9B,MAAM,UAAU,CAAC;AAIlB,OAAO,EACL,gBAAgB,EAChB,yBAAyB,EACzB,qBAAqB,EACrB,mBAAmB,EACnB,kBAAkB,EAClB,eAAe,EACf,SAAS,EACT,qBAAqB,EACrB,SAAS,EACT,WAAW,EACX,WAAW,EACX,WAAW,EACX,aAAa,EACb,aAAa,EACb,cAAc,EACd,aAAa,EACb,KAAK,SAAS,EACd,KAAK,eAAe,EACpB,KAAK,aAAa,EAClB,KAAK,iBAAiB,EACtB,KAAK,wBAAwB,GAC9B,MAAM,aAAa,CAAC;AAErB,OAAO,EAAE,oBAAoB,EAAE,KAAK,oBAAoB,EAAE,MAAM,aAAa,CAAC;AAK9E,OAAO,EACL,aAAa,EACb,aAAa,EACb,cAAc,EACd,aAAa,EACb,iBAAiB,EACjB,eAAe,EACf,kBAAkB,EAClB,cAAc,GACf,MAAM,eAAe,CAAC;AAKvB,OAAO,EACL,qBAAqB,EACrB,kBAAkB,EAClB,oBAAoB,EACpB,KAAK,iBAAiB,EACtB,KAAK,cAAc,EACnB,KAAK,aAAa,GACnB,MAAM,eAAe,CAAC;AAGvB,OAAO,EAAE,gBAAgB,EAAE,MAAM,iCAAiC,CAAC;AACnE,YAAY,EACV,sBAAsB,EACtB,yBAAyB,EACzB,iBAAiB,GAClB,MAAM,iCAAiC,CAAC;AAGzC,OAAO,EAAE,gBAAgB,EAAE,MAAM,iCAAiC,CAAC;AACnE,YAAY,EACV,sBAAsB,EACtB,yBAAyB,EACzB,oBAAoB,GACrB,MAAM,iCAAiC,CAAC;AAIzC,OAAO,EAAE,gBAAgB,EAAE,MAAM,iCAAiC,CAAC;AACnE,YAAY,EAAE,sBAAsB,EAAE,yBAAyB,EAAE,MAAM,iCAAiC,CAAC;AAKzG,OAAO,EAAE,8BAA8B,EAAE,MAAM,mBAAmB,CAAC;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,eAAe,EAAE,MAAM,UAAU,CAAC;AAG3C,OAAO,EAAE,mBAAmB,EAAE,MAAM,cAAc,CAAC;AAKnD,OAAO,EACL,qBAAqB,EACrB,mBAAmB,EACnB,KAAK,eAAe,EACpB,KAAK,mBAAmB,EACxB,KAAK,wBAAwB,GAC9B,MAAM,UAAU,CAAC;AAIlB,OAAO,EACL,gBAAgB,EAChB,yBAAyB,EACzB,qBAAqB,EACrB,mBAAmB,EACnB,kBAAkB,EAClB,eAAe,EACf,SAAS,EACT,qBAAqB,EACrB,SAAS,EACT,WAAW,EACX,WAAW,EACX,WAAW,EACX,aAAa,EACb,aAAa,EACb,cAAc,EACd,aAAa,EACb,KAAK,SAAS,EACd,KAAK,eAAe,EACpB,KAAK,aAAa,EAClB,KAAK,iBAAiB,EACtB,KAAK,wBAAwB,GAC9B,MAAM,aAAa,CAAC;AAErB,OAAO,EAAE,oBAAoB,EAAE,KAAK,oBAAoB,EAAE,MAAM,aAAa,CAAC;AAK9E,OAAO,EACL,aAAa,EACb,aAAa,EACb,cAAc,EACd,aAAa,EACb,iBAAiB,EACjB,eAAe,EACf,kBAAkB,EAClB,cAAc,GACf,MAAM,eAAe,CAAC;AAKvB,OAAO,EACL,qBAAqB,EACrB,kBAAkB,EAClB,oBAAoB,EACpB,KAAK,iBAAiB,EACtB,KAAK,cAAc,EACnB,KAAK,aAAa,GACnB,MAAM,eAAe,CAAC;AAGvB,OAAO,EAAE,gBAAgB,EAAE,MAAM,iCAAiC,CAAC;AACnE,YAAY,EACV,sBAAsB,EACtB,yBAAyB,EACzB,iBAAiB,GAClB,MAAM,iCAAiC,CAAC;AAGzC,OAAO,EAAE,gBAAgB,EAAE,MAAM,iCAAiC,CAAC;AACnE,YAAY,EACV,sBAAsB,EACtB,yBAAyB,EACzB,oBAAoB,GACrB,MAAM,iCAAiC,CAAC;AAIzC,OAAO,EAAE,gBAAgB,EAAE,MAAM,iCAAiC,CAAC;AACnE,YAAY,EAAE,sBAAsB,EAAE,yBAAyB,EAAE,MAAM,iCAAiC,CAAC;AAKzG,OAAO,EAAE,8BAA8B,EAAE,MAAM,mBAAmB,CAAC;AAQnE,OAAO,EACL,yBAAyB,EACzB,yBAAyB,EACzB,2BAA2B,EAC3B,2BAA2B,EAC3B,KAAK,sBAAsB,EAC3B,KAAK,wBAAwB,EAC7B,KAAK,yBAAyB,EAC9B,KAAK,gCAAgC,EACrC,KAAK,iBAAiB,EACtB,KAAK,uBAAuB,GAC7B,MAAM,aAAa,CAAC"}
|
package/dist/integrity.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"algorithm": "sha256",
|
|
3
3
|
"artifacts": {
|
|
4
|
-
"manifest.json": "
|
|
4
|
+
"manifest.json": "44cab69b3c02baa1d70326bb19709c04bd2f46ae14f50910edd223a3e57c4965",
|
|
5
5
|
"meta.json": "ca3d163bab055381827226140568f3bef7eaac187cebd76878e0b63e9e442356",
|
|
6
6
|
"types/index.d.ts": "022927e6a7b351cc4cf52d5f973f60ccd140e5810e17b32fb8868965aa1db161",
|
|
7
7
|
"rules/plan-before-apply.ts": "31b1b3176799fd365b2583fe3947edd187c2a863e3dc0094ed9e91ece2464c99",
|
|
@@ -38,5 +38,5 @@
|
|
|
38
38
|
"rules/tf029.ts": "52e02b4921a64f3f928836896b8b80682400adb50bd7a0645f3fe2e8819433c2",
|
|
39
39
|
"skills/chant-terraform.md": "d9ca7b7c28aed0038ce775a3491150298492eed2858fd3651ed9bfca0e577ae8"
|
|
40
40
|
},
|
|
41
|
-
"composite": "
|
|
41
|
+
"composite": "afae4e4b1c71fef38af254f5566aba669d275f61de38544d8bf27fa6294721cd"
|
|
42
42
|
}
|
package/dist/manifest.json
CHANGED
package/dist/plugin.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"plugin.d.ts","sourceRoot":"","sources":["../src/plugin.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAsB,aAAa,EAAE,MAAM,0BAA0B,CAAC;AAgElF;;;;;;;GAOG;AACH,eAAO,MAAM,eAAe,EAAE,
|
|
1
|
+
{"version":3,"file":"plugin.d.ts","sourceRoot":"","sources":["../src/plugin.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAsB,aAAa,EAAE,MAAM,0BAA0B,CAAC;AAgElF;;;;;;;GAOG;AACH,eAAO,MAAM,eAAe,EAAE,aAgQ7B,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@intentius/chant-lexicon-terraform",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.76.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"files": [
|
|
6
6
|
"src/",
|
|
@@ -56,7 +56,7 @@
|
|
|
56
56
|
},
|
|
57
57
|
"peerDependencies": {
|
|
58
58
|
"zod": "^4.3.6",
|
|
59
|
-
"@intentius/chant": "^0.
|
|
59
|
+
"@intentius/chant": "^0.76.0",
|
|
60
60
|
"typescript": "^5.9.3"
|
|
61
61
|
},
|
|
62
62
|
"description": "terraform lexicon for chant, existing Terraform root modules as declarable entities",
|
package/src/behaviour/index.ts
CHANGED
|
@@ -17,8 +17,11 @@ export {
|
|
|
17
17
|
export {
|
|
18
18
|
liveRootsOf,
|
|
19
19
|
predictTerraformBehaviour,
|
|
20
|
+
readTerraformDeclaration,
|
|
20
21
|
TERRAFORM,
|
|
22
|
+
terraformPredictOptionsFrom,
|
|
21
23
|
type TerraformBehaviourDeps,
|
|
24
|
+
type TerraformDeclarationReader,
|
|
22
25
|
type TerraformPredictOptions,
|
|
23
26
|
} from "./predict";
|
|
24
27
|
export {
|
|
@@ -33,10 +33,12 @@ import {
|
|
|
33
33
|
type PredictedBehaviour,
|
|
34
34
|
type UnpredictedEntity,
|
|
35
35
|
} from "@intentius/chant/behaviour";
|
|
36
|
+
import { behaviourRequestFromIr } from "@intentius/chant/behaviour-graph";
|
|
36
37
|
import type { Declarable } from "@intentius/chant/declarable";
|
|
38
|
+
import { buildGraphIr } from "@intentius/chant/graph-ir";
|
|
37
39
|
import type { TerraformReadDeps } from "../describe-resources";
|
|
38
40
|
import { renderTerraformRoots } from "../hcl/roots";
|
|
39
|
-
import { liveRootsOf, predictTerraformBehaviour, TERRAFORM } from "./predict";
|
|
41
|
+
import { liveRootsOf, predictTerraformBehaviour, TERRAFORM, terraformPredictOptionsFrom } from "./predict";
|
|
40
42
|
import type { TerraformBehaviourEntity } from "./request";
|
|
41
43
|
|
|
42
44
|
const fixtures = join(dirname(fileURLToPath(import.meta.url)), "..", "__fixtures__");
|
|
@@ -49,6 +51,8 @@ const key = (address: string): string => `${ROOT}/${address}`;
|
|
|
49
51
|
|
|
50
52
|
let entities: Map<string, TerraformBehaviourEntity>;
|
|
51
53
|
let entityNames: string[];
|
|
54
|
+
/** The build's own entities, as `chant graph` is handed them. */
|
|
55
|
+
let built: Map<string, Declarable>;
|
|
52
56
|
|
|
53
57
|
beforeAll(async () => {
|
|
54
58
|
const rendered = await renderTerraformRoots({
|
|
@@ -56,6 +60,7 @@ beforeAll(async () => {
|
|
|
56
60
|
roots: { [ROOT]: { dir: "./live-estate" } },
|
|
57
61
|
binary: "choudoufu",
|
|
58
62
|
});
|
|
63
|
+
built = rendered.entities;
|
|
59
64
|
entities = new Map();
|
|
60
65
|
for (const [name, entity] of rendered.entities) {
|
|
61
66
|
const e = entity as Declarable & { props: Record<string, unknown>; references?: readonly never[] };
|
|
@@ -183,7 +188,7 @@ async function predict(
|
|
|
183
188
|
): Promise<{ result: BehaviourResult; calls: Call[] }> {
|
|
184
189
|
const d = deps(overrides?.deps);
|
|
185
190
|
const result = await predictTerraformBehaviour(
|
|
186
|
-
{ ...common, entityNames, entities, ...(overrides?.owned ? { owned: true } : {}) },
|
|
191
|
+
{ ...common, from: "live", entityNames, entities, ...(overrides?.owned ? { owned: true } : {}) },
|
|
187
192
|
{ liveLs: d.deps.liveLs, livePlan: d.deps.livePlan, predict: tariff },
|
|
188
193
|
);
|
|
189
194
|
return { result, calls: d.calls };
|
|
@@ -325,7 +330,7 @@ describe("the screen runs before the engine", () => {
|
|
|
325
330
|
});
|
|
326
331
|
const d = deps();
|
|
327
332
|
const result = await predictTerraformBehaviour(
|
|
328
|
-
{ ...common, entityNames, entities: tainted },
|
|
333
|
+
{ ...common, from: "live", entityNames, entities: tainted },
|
|
329
334
|
{
|
|
330
335
|
liveLs: d.deps.liveLs,
|
|
331
336
|
livePlan: d.deps.livePlan,
|
|
@@ -378,4 +383,167 @@ describe("the delta the epic asks for", () => {
|
|
|
378
383
|
// not computed over a graph the other side does not have.
|
|
379
384
|
expect(live.meta.edgeCoverage.verdict).toBe(declared.meta.edgeCoverage.verdict);
|
|
380
385
|
});
|
|
386
|
+
|
|
387
|
+
it("tells the engine which estate each side is about (#2494)", async () => {
|
|
388
|
+
const asked: Array<PredictBehaviourOptions["from"]> = [];
|
|
389
|
+
const listening = async (options: PredictBehaviourOptions): Promise<BehaviourResult> => {
|
|
390
|
+
asked.push(options.from);
|
|
391
|
+
return tariff(options);
|
|
392
|
+
};
|
|
393
|
+
const d = deps();
|
|
394
|
+
for (const from of ["live", "declared"] as const) {
|
|
395
|
+
await predictTerraformBehaviour(
|
|
396
|
+
{ ...common, from, entityNames, entities },
|
|
397
|
+
{ liveLs: d.deps.liveLs, livePlan: d.deps.livePlan, predict: listening },
|
|
398
|
+
);
|
|
399
|
+
}
|
|
400
|
+
expect(asked).toEqual(["live", "declared"]);
|
|
401
|
+
});
|
|
402
|
+
});
|
|
403
|
+
|
|
404
|
+
describe("the contract's options, read as this producer's (#2495)", () => {
|
|
405
|
+
/** What `chant graph --traffic` hands a plugin: the graph reshaped, references left behind on the edges. */
|
|
406
|
+
const throughTheGraph = (from: "live" | "declared"): PredictBehaviourOptions =>
|
|
407
|
+
behaviourRequestFromIr(buildGraphIr(built, fixtures), {
|
|
408
|
+
...common,
|
|
409
|
+
from,
|
|
410
|
+
edgeCoverage: { verdict: "unknown" },
|
|
411
|
+
});
|
|
412
|
+
|
|
413
|
+
/** Run the producer and keep the request the engine was sent. */
|
|
414
|
+
async function sent(options: Parameters<typeof predictTerraformBehaviour>[0]): Promise<PredictBehaviourOptions> {
|
|
415
|
+
let request: PredictBehaviourOptions | undefined;
|
|
416
|
+
const d = deps();
|
|
417
|
+
await predictTerraformBehaviour(options, {
|
|
418
|
+
liveLs: d.deps.liveLs,
|
|
419
|
+
livePlan: d.deps.livePlan,
|
|
420
|
+
predict: async (o) => {
|
|
421
|
+
request = o;
|
|
422
|
+
return tariff(o);
|
|
423
|
+
},
|
|
424
|
+
});
|
|
425
|
+
if (!request) throw new Error("the engine was never asked");
|
|
426
|
+
return request;
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
/** A reader that must not be reached: nothing on the declared side, or on a live side that kept its `mode`, reads the project again. */
|
|
430
|
+
const neverRedeclared = async (): Promise<never> => {
|
|
431
|
+
throw new Error("the declaration was read again");
|
|
432
|
+
};
|
|
433
|
+
|
|
434
|
+
it("the graph drops every reference, and the edges give each one back", async () => {
|
|
435
|
+
const graphed = throughTheGraph("declared");
|
|
436
|
+
const referencing = [...entities].filter(([, e]) => (e.references ?? []).length > 0);
|
|
437
|
+
expect(referencing.length).toBeGreaterThan(0);
|
|
438
|
+
for (const [name] of referencing) {
|
|
439
|
+
expect(Object.prototype.hasOwnProperty.call(graphed.entities.get(name), "references"), name).toBe(false);
|
|
440
|
+
}
|
|
441
|
+
const read = await terraformPredictOptionsFrom(graphed, neverRedeclared);
|
|
442
|
+
for (const [name, entity] of referencing) {
|
|
443
|
+
expect(read.entities.get(name)?.references, name).toEqual(entity.references);
|
|
444
|
+
}
|
|
445
|
+
});
|
|
446
|
+
|
|
447
|
+
it.each(["live", "declared"] as const)("sends the engine the %s request the build's own entities would have", async (from) => {
|
|
448
|
+
const direct = await sent({ ...common, from, entityNames, entities });
|
|
449
|
+
const graphed = await sent(await terraformPredictOptionsFrom(throughTheGraph(from), neverRedeclared));
|
|
450
|
+
expect(graphed.edges).toEqual(direct.edges);
|
|
451
|
+
expect(graphed.edgeCoverage).toEqual(direct.edgeCoverage);
|
|
452
|
+
expect(graphed.entityNames).toEqual(direct.entityNames);
|
|
453
|
+
expect(graphed.from).toBe(from);
|
|
454
|
+
});
|
|
455
|
+
|
|
456
|
+
it("keeps the references an entity still carries over the edges' copy", async () => {
|
|
457
|
+
const [name, entity] = [...entities].find(([, e]) => (e.references ?? []).length > 0)!;
|
|
458
|
+
const read = await terraformPredictOptionsFrom({
|
|
459
|
+
...common,
|
|
460
|
+
from: "declared",
|
|
461
|
+
entityNames,
|
|
462
|
+
entities: entities as PredictBehaviourOptions["entities"],
|
|
463
|
+
edges: [{ from: name, to: "somewhere/else", kind: "ref" }],
|
|
464
|
+
edgeCoverage: { verdict: "unknown" },
|
|
465
|
+
}, neverRedeclared);
|
|
466
|
+
expect(read.entities.get(name)?.references).toEqual(entity.references);
|
|
467
|
+
});
|
|
468
|
+
|
|
469
|
+
it("does not pass the caller's edges or coverage claim on", async () => {
|
|
470
|
+
const read = (await terraformPredictOptionsFrom(throughTheGraph("declared"), neverRedeclared)) as unknown as Record<
|
|
471
|
+
string,
|
|
472
|
+
unknown
|
|
473
|
+
>;
|
|
474
|
+
expect(Object.prototype.hasOwnProperty.call(read, "edges")).toBe(false);
|
|
475
|
+
expect(Object.prototype.hasOwnProperty.call(read, "edgeCoverage")).toBe(false);
|
|
476
|
+
});
|
|
477
|
+
|
|
478
|
+
/**
|
|
479
|
+
* What `chant graph --live` hands over: observed nodes. An address, a root
|
|
480
|
+
* and the provider's type; no `mode`, no `body`, no edge. Plus one the
|
|
481
|
+
* account holds that nothing declares.
|
|
482
|
+
*/
|
|
483
|
+
const ORPHAN = key("aws_sqs_queue.nobody_declared");
|
|
484
|
+
function throughTheLiveGraph(): PredictBehaviourOptions {
|
|
485
|
+
const observed = new Map<string, { entityType: string; props: Record<string, unknown> }>();
|
|
486
|
+
for (const [name, entity] of entities) {
|
|
487
|
+
if (entity.entityType !== "Terraform::Resource") continue;
|
|
488
|
+
const address = String(entity.props.address);
|
|
489
|
+
observed.set(name, {
|
|
490
|
+
entityType: entity.entityType,
|
|
491
|
+
props: { address, root: ROOT, estate: ESTATE, resourceType: address.slice(0, address.indexOf(".")) },
|
|
492
|
+
});
|
|
493
|
+
}
|
|
494
|
+
observed.set(ORPHAN, {
|
|
495
|
+
entityType: "Terraform::Resource",
|
|
496
|
+
props: { address: "aws_sqs_queue.nobody_declared", root: ROOT, estate: ESTATE, resourceType: "aws_sqs_queue" },
|
|
497
|
+
});
|
|
498
|
+
return {
|
|
499
|
+
...common,
|
|
500
|
+
from: "live",
|
|
501
|
+
entityNames: [...observed.keys()],
|
|
502
|
+
entities: observed,
|
|
503
|
+
edges: [],
|
|
504
|
+
edgeCoverage: { verdict: "unknown" },
|
|
505
|
+
};
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
it("declares a live graph's root again, so the account is read and the engine is sent what the build would send", async () => {
|
|
509
|
+
const direct = await sent({ ...common, from: "live", entityNames, entities });
|
|
510
|
+
const graphed = await sent(await terraformPredictOptionsFrom(throughTheLiveGraph(), async () => entities));
|
|
511
|
+
expect(graphed.entityNames).toEqual(direct.entityNames);
|
|
512
|
+
expect(graphed.edges).toEqual(direct.edges);
|
|
513
|
+
expect(graphed.edgeCoverage).toEqual(direct.edgeCoverage);
|
|
514
|
+
for (const name of direct.entityNames) {
|
|
515
|
+
expect(graphed.entities.get(name)?.props, name).toEqual(direct.entities.get(name)?.props);
|
|
516
|
+
}
|
|
517
|
+
});
|
|
518
|
+
|
|
519
|
+
it("read as it arrived, the same live graph reads no account at all", async () => {
|
|
520
|
+
// The hole the test above closes, pinned so it cannot close by accident:
|
|
521
|
+
// no observed entity says `mode: "live"`, so no root is live.
|
|
522
|
+
const asArrived = throughTheLiveGraph();
|
|
523
|
+
expect(liveRootsOf(asArrived.entityNames, asArrived.entities)).toEqual([]);
|
|
524
|
+
const read = await terraformPredictOptionsFrom(asArrived, async () => entities);
|
|
525
|
+
expect(liveRootsOf(read.entityNames, read.entities)).toEqual([ROOT]);
|
|
526
|
+
});
|
|
527
|
+
|
|
528
|
+
it("sets an observed orphan aside for the live read to list", async () => {
|
|
529
|
+
const read = await terraformPredictOptionsFrom(throughTheLiveGraph(), async () => entities);
|
|
530
|
+
expect(read.entityNames).not.toContain(ORPHAN);
|
|
531
|
+
expect(read.entities.has(ORPHAN)).toBe(false);
|
|
532
|
+
expect([...read.entityNames].sort()).toEqual(entityNames);
|
|
533
|
+
});
|
|
534
|
+
|
|
535
|
+
it("leaves a root the config does not render as it arrived", async () => {
|
|
536
|
+
const asArrived = throughTheLiveGraph();
|
|
537
|
+
const read = await terraformPredictOptionsFrom(asArrived, async () => new Map());
|
|
538
|
+
expect(read.entityNames).toEqual(asArrived.entityNames);
|
|
539
|
+
expect(read.entities.get(ORPHAN)).toEqual(asArrived.entities.get(ORPHAN));
|
|
540
|
+
});
|
|
541
|
+
|
|
542
|
+
it("reads the declaration again only for a live graph", async () => {
|
|
543
|
+
const declaredSide = { ...throughTheLiveGraph(), from: "declared" as const };
|
|
544
|
+
await expect(terraformPredictOptionsFrom(declaredSide, neverRedeclared)).resolves.toBeDefined();
|
|
545
|
+
await expect(terraformPredictOptionsFrom(throughTheLiveGraph(), neverRedeclared)).rejects.toThrow(
|
|
546
|
+
"the declaration was read again",
|
|
547
|
+
);
|
|
548
|
+
});
|
|
381
549
|
});
|
package/src/behaviour/predict.ts
CHANGED
|
@@ -60,8 +60,13 @@ import {
|
|
|
60
60
|
type PredictBehaviourOptions,
|
|
61
61
|
type UnpredictedEntity,
|
|
62
62
|
} from "@intentius/chant/behaviour";
|
|
63
|
+
import { createBehaviourPredict } from "@intentius/chant/behaviour-predict";
|
|
64
|
+
import type { EntityReference } from "@intentius/chant/graph-ir";
|
|
63
65
|
import { indexLivePlan, readLiveLs, type TerraformReadDeps } from "../describe-resources";
|
|
66
|
+
import type { TerraformConfig } from "../config";
|
|
64
67
|
import { RESOURCE_TYPE } from "../hcl/parse";
|
|
68
|
+
import { choudoufuLiveLs, choudoufuLivePlan } from "../op/activities/terraform";
|
|
69
|
+
import { terraformBehaviourKinds } from "./kinds";
|
|
65
70
|
import {
|
|
66
71
|
terraformBehaviourRequest,
|
|
67
72
|
type TerraformBehaviourEntity,
|
|
@@ -105,19 +110,175 @@ export interface TerraformPredictOptions
|
|
|
105
110
|
/** Directory the activities start the `chant.config.*` search from. Default: the process cwd. */
|
|
106
111
|
cwd?: string;
|
|
107
112
|
/**
|
|
108
|
-
* Which estate to predict
|
|
109
|
-
*
|
|
110
|
-
*
|
|
111
|
-
*
|
|
112
|
-
*
|
|
113
|
-
*
|
|
114
|
-
*
|
|
113
|
+
* Which estate to predict, as the contract's own `from` (#2494) and required
|
|
114
|
+
* for the same reason: a default is a guess about which estate somebody
|
|
115
|
+
* meant, and the report that comes back from a wrong guess is well-formed.
|
|
116
|
+
*
|
|
117
|
+
* `"live"` is this path's whole point: the account as it stands, drift
|
|
118
|
+
* included. `"declared"` runs the same producer over the declaration alone
|
|
119
|
+
* and reads no account, which is the other half of the delta the epic asks
|
|
120
|
+
* for (#2355) — a caller wanting that delta calls this twice and differences
|
|
121
|
+
* the two reports, and gets two reports assembled by one producer rather
|
|
122
|
+
* than two shapes that have each been through a different translation.
|
|
115
123
|
*
|
|
116
124
|
* `"declared"` on a project with no live root is the same request `"live"`
|
|
117
125
|
* builds, because there is no account to read; the option exists so that a
|
|
118
126
|
* caller can ask for the declaration of a root that *is* live.
|
|
119
127
|
*/
|
|
120
|
-
from
|
|
128
|
+
from: "live" | "declared";
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* What the plugin method runs with, where a caller injects nothing (#2495).
|
|
133
|
+
*
|
|
134
|
+
* The two reads are the activities `describeResources` already drives. The
|
|
135
|
+
* engine front is core's, handed this lexicon's coverage rows and no other's:
|
|
136
|
+
* a plugin method takes options alone and cannot see which other lexicons the
|
|
137
|
+
* project configured, and every name this producer sends is a terraform block
|
|
138
|
+
* or a name it passed through untouched. A passed-through name of another
|
|
139
|
+
* lexicon's type comes back `unknown-type`, which is true of what this path
|
|
140
|
+
* knows about it.
|
|
141
|
+
*/
|
|
142
|
+
const REAL_DEPS: TerraformBehaviourDeps = {
|
|
143
|
+
liveLs: choudoufuLiveLs,
|
|
144
|
+
livePlan: choudoufuLivePlan,
|
|
145
|
+
predict: createBehaviourPredict({ kinds: [terraformBehaviourKinds] }),
|
|
146
|
+
};
|
|
147
|
+
|
|
148
|
+
/** Reads the project's declaration afresh. Injectable so a test parses nothing from disk. */
|
|
149
|
+
export type TerraformDeclarationReader = (cwd?: string) => Promise<ReadonlyMap<string, TerraformBehaviourEntity>>;
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* The build's own entities, read again from the project's config: what
|
|
153
|
+
* `buildRoots()` renders, with `mode`, `body` and `references` as the parse
|
|
154
|
+
* stamps them. The config is found the way `observeAmbient` and the
|
|
155
|
+
* activities find it, so `terraform.roots` means one thing to all of them.
|
|
156
|
+
*/
|
|
157
|
+
export const readTerraformDeclaration: TerraformDeclarationReader = async (cwd) => {
|
|
158
|
+
const { loadChantConfigUpward } = await import("@intentius/chant/config");
|
|
159
|
+
const { renderTerraformRoots } = await import("../hcl/roots");
|
|
160
|
+
const { dirname, resolve } = await import("node:path");
|
|
161
|
+
|
|
162
|
+
const start = resolve(cwd ?? process.cwd());
|
|
163
|
+
const { config, configPath } = await loadChantConfigUpward(start);
|
|
164
|
+
const namespace = (config as { terraform?: TerraformConfig }).terraform;
|
|
165
|
+
const entities = new Map<string, TerraformBehaviourEntity>();
|
|
166
|
+
if (!namespace?.roots || Object.keys(namespace.roots).length === 0) return entities;
|
|
167
|
+
const rendered = await renderTerraformRoots({
|
|
168
|
+
projectRoot: configPath ? dirname(configPath) : start,
|
|
169
|
+
roots: namespace.roots,
|
|
170
|
+
binary: namespace.binary,
|
|
171
|
+
callModuleType: namespace.callModuleType,
|
|
172
|
+
});
|
|
173
|
+
for (const [name, entity] of rendered.entities) {
|
|
174
|
+
const e = entity as unknown as TerraformBehaviourEntity;
|
|
175
|
+
entities.set(name, {
|
|
176
|
+
entityType: e.entityType,
|
|
177
|
+
props: e.props ?? {},
|
|
178
|
+
...(Array.isArray(e.references) ? { references: e.references } : {}),
|
|
179
|
+
});
|
|
180
|
+
}
|
|
181
|
+
return entities;
|
|
182
|
+
};
|
|
183
|
+
|
|
184
|
+
const rootOf = (entity: TerraformBehaviourEntity | undefined): string | undefined =>
|
|
185
|
+
typeof entity?.props.root === "string" ? entity.props.root : undefined;
|
|
186
|
+
|
|
187
|
+
/**
|
|
188
|
+
* Read the contract's options as this producer's (#2495): the plugin method's
|
|
189
|
+
* whole adaptation. Two things are missing from an entity by the time a graph
|
|
190
|
+
* has carried it here, and which one depends on the graph.
|
|
191
|
+
*
|
|
192
|
+
* ## A declared graph has lost its `references`
|
|
193
|
+
*
|
|
194
|
+
* `root` and `mode` need no work: they are on `props`, the graph projects
|
|
195
|
+
* `props` onto a node's `attrs` as they stand, and `behaviourRequestFromIr`
|
|
196
|
+
* hands `attrs` back as `props`. `references` are left off `attrs` on
|
|
197
|
+
* purpose, because an edge already says the same thing (`graph-ir.ts`'s
|
|
198
|
+
* `SKIP_KEYS`), and the contract's `edges` arrive in their place. An `IREdge`
|
|
199
|
+
* is an `EntityReference` plus the entity it leaves from, so grouping the
|
|
200
|
+
* edges by `from` gives every entity its references back, field for field.
|
|
201
|
+
* Without this a block whose body holds a `${…}` is counted as an unresolved
|
|
202
|
+
* kind on a graph that resolved it. An entity that still carries its own
|
|
203
|
+
* `references` keeps them: the edges are the copy.
|
|
204
|
+
*
|
|
205
|
+
* ## A live graph has lost the declaration
|
|
206
|
+
*
|
|
207
|
+
* `chant graph --live` hands over observed nodes: an address, a root, the
|
|
208
|
+
* provider's type, and no `mode`, no `body` and no edge. Read as they stand,
|
|
209
|
+
* no root is live, so the account is never read here; no block has a size,
|
|
210
|
+
* because `body.instance_type` went with the body; and containment is empty.
|
|
211
|
+
* The figures that come back are well-formed and differ from the declared
|
|
212
|
+
* side's for reasons that have nothing to do with the account, which is the
|
|
213
|
+
* delta this producer exists to keep honest.
|
|
214
|
+
*
|
|
215
|
+
* So under `from: "live"`, a root any of whose entities arrived with no
|
|
216
|
+
* `mode` is declared again from the project's own config, and the producer
|
|
217
|
+
* then reads the account itself, as it does for any other caller. The
|
|
218
|
+
* observed entities of that root are set aside, the undeclared ones
|
|
219
|
+
* included: an owned orphan is the live read's to list, under the key
|
|
220
|
+
* `observeAmbient` gave it, and one the account no longer holds is not in a
|
|
221
|
+
* prediction of the account.
|
|
222
|
+
*
|
|
223
|
+
* A root the config does not render is left as it arrived. That is a worse
|
|
224
|
+
* reading and the only one available.
|
|
225
|
+
*
|
|
226
|
+
* ## What stops here
|
|
227
|
+
*
|
|
228
|
+
* The caller's `edges` and `edgeCoverage`. Both are this producer's to
|
|
229
|
+
* compute, from whichever reads it ends up making (see
|
|
230
|
+
* {@link TerraformPredictOptions}).
|
|
231
|
+
*/
|
|
232
|
+
export async function terraformPredictOptionsFrom(
|
|
233
|
+
options: PredictBehaviourOptions,
|
|
234
|
+
redeclare: TerraformDeclarationReader = readTerraformDeclaration,
|
|
235
|
+
): Promise<TerraformPredictOptions> {
|
|
236
|
+
const { entities: given, edges, edgeCoverage: _edgeCoverage, ...rest } = options;
|
|
237
|
+
const leaving = new Map<string, EntityReference[]>();
|
|
238
|
+
for (const edge of edges ?? []) {
|
|
239
|
+
if (edge.kind !== "ref") continue;
|
|
240
|
+
const reference: EntityReference = {
|
|
241
|
+
to: edge.to,
|
|
242
|
+
...(edge.viaAttr !== undefined ? { viaAttr: edge.viaAttr } : {}),
|
|
243
|
+
...(edge.toAttr !== undefined ? { toAttr: edge.toAttr } : {}),
|
|
244
|
+
};
|
|
245
|
+
(leaving.get(edge.from) ?? leaving.set(edge.from, []).get(edge.from)!).push(reference);
|
|
246
|
+
}
|
|
247
|
+
const entities = new Map<string, TerraformBehaviourEntity>();
|
|
248
|
+
for (const [name, entity] of given) {
|
|
249
|
+
const own = (entity as TerraformBehaviourEntity).references;
|
|
250
|
+
const references = Array.isArray(own) ? own : leaving.get(name);
|
|
251
|
+
entities.set(name, references === undefined ? entity : { ...entity, references });
|
|
252
|
+
}
|
|
253
|
+
if (options.from !== "live") return { ...rest, entities };
|
|
254
|
+
|
|
255
|
+
const observed = new Set<string>();
|
|
256
|
+
for (const name of options.entityNames) {
|
|
257
|
+
const entity = entities.get(name);
|
|
258
|
+
const root = rootOf(entity);
|
|
259
|
+
if (root !== undefined && entity?.props.mode === undefined) observed.add(root);
|
|
260
|
+
}
|
|
261
|
+
if (observed.size === 0) return { ...rest, entities };
|
|
262
|
+
|
|
263
|
+
const declared = new Map<string, Map<string, TerraformBehaviourEntity>>();
|
|
264
|
+
for (const [name, entity] of await redeclare()) {
|
|
265
|
+
const root = rootOf(entity);
|
|
266
|
+
if (root === undefined || !observed.has(root)) continue;
|
|
267
|
+
(declared.get(root) ?? declared.set(root, new Map()).get(root)!).set(name, entity);
|
|
268
|
+
}
|
|
269
|
+
const entityNames = options.entityNames.filter((name) => {
|
|
270
|
+
const root = rootOf(entities.get(name));
|
|
271
|
+
if (root === undefined || !declared.has(root)) return true;
|
|
272
|
+
entities.delete(name);
|
|
273
|
+
return false;
|
|
274
|
+
});
|
|
275
|
+
for (const again of declared.values()) {
|
|
276
|
+
for (const [name, entity] of again) {
|
|
277
|
+
entities.set(name, entity);
|
|
278
|
+
entityNames.push(name);
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
return { ...rest, entityNames, entities };
|
|
121
282
|
}
|
|
122
283
|
|
|
123
284
|
/** Which roots the request would read live: those whose entities the parse stamped `mode: "live"`. */
|
|
@@ -146,7 +307,7 @@ function readsAsCredentials(text: string): boolean {
|
|
|
146
307
|
*/
|
|
147
308
|
export async function predictTerraformBehaviour(
|
|
148
309
|
options: TerraformPredictOptions,
|
|
149
|
-
deps: TerraformBehaviourDeps,
|
|
310
|
+
deps: TerraformBehaviourDeps = REAL_DEPS,
|
|
150
311
|
): Promise<BehaviourResult> {
|
|
151
312
|
const { cwd, from: _from, ...contract } = options;
|
|
152
313
|
const where = cwd ? { cwd } : {};
|
|
@@ -191,6 +352,7 @@ export async function predictTerraformBehaviour(
|
|
|
191
352
|
environment: contract.environment,
|
|
192
353
|
buildOutput: contract.buildOutput,
|
|
193
354
|
traffic: contract.traffic,
|
|
355
|
+
from: options.from,
|
|
194
356
|
...(contract.region !== undefined ? { region: contract.region } : {}),
|
|
195
357
|
...(contract.stack !== undefined ? { stack: contract.stack } : {}),
|
|
196
358
|
...(contract.owned !== undefined ? { owned: contract.owned } : {}),
|
|
@@ -65,11 +65,12 @@ beforeAll(async () => {
|
|
|
65
65
|
const common = { environment: "prod", buildOutput: "/tmp/build", traffic: "100 rps, p50" };
|
|
66
66
|
|
|
67
67
|
const declaredSide = (): TerraformBehaviourRequest =>
|
|
68
|
-
terraformBehaviourRequest({ ...common, entityNames, entities });
|
|
68
|
+
terraformBehaviourRequest({ ...common, from: "declared", entityNames, entities });
|
|
69
69
|
|
|
70
70
|
const liveSide = (): TerraformBehaviourRequest =>
|
|
71
71
|
terraformBehaviourRequest({
|
|
72
72
|
...common,
|
|
73
|
+
from: "live",
|
|
73
74
|
entityNames,
|
|
74
75
|
entities,
|
|
75
76
|
live: [{ root: ROOT, listing: LISTING, plan: PLAN }],
|
|
@@ -79,6 +80,11 @@ const factsOf = (built: TerraformBehaviourRequest, address: string): LiveResourc
|
|
|
79
80
|
(built.request.entities.get(key(address))!.props as { live: LiveResourceFacts }).live;
|
|
80
81
|
|
|
81
82
|
describe("the declared side", () => {
|
|
83
|
+
it("says which estate the request is about, on both sides (#2494)", () => {
|
|
84
|
+
expect(declaredSide().request.from).toBe("declared");
|
|
85
|
+
expect(liveSide().request.from).toBe("live");
|
|
86
|
+
});
|
|
87
|
+
|
|
82
88
|
it("builds every declared block, from the file and nothing else", () => {
|
|
83
89
|
const built = declaredSide();
|
|
84
90
|
expect(built.request.entityNames).toEqual(entityNames);
|
|
@@ -179,6 +185,7 @@ describe("the live side", () => {
|
|
|
179
185
|
it("withholds what is not this estate's when --owned was asked for", () => {
|
|
180
186
|
const built = terraformBehaviourRequest({
|
|
181
187
|
...common,
|
|
188
|
+
from: "live",
|
|
182
189
|
entityNames,
|
|
183
190
|
entities,
|
|
184
191
|
owned: true,
|
package/src/behaviour/request.ts
CHANGED
|
@@ -129,7 +129,7 @@ export interface LiveResourceFacts {
|
|
|
129
129
|
}
|
|
130
130
|
|
|
131
131
|
export interface TerraformBehaviourRequestOptions
|
|
132
|
-
extends Pick<PredictBehaviourOptions, "environment" | "buildOutput" | "traffic" | "region" | "stack" | "owned"> {
|
|
132
|
+
extends Pick<PredictBehaviourOptions, "environment" | "buildOutput" | "traffic" | "from" | "region" | "stack" | "owned"> {
|
|
133
133
|
entityNames: readonly string[];
|
|
134
134
|
entities: ReadonlyMap<string, TerraformBehaviourEntity>;
|
|
135
135
|
/**
|
|
@@ -613,6 +613,7 @@ export function terraformBehaviourRequest(options: TerraformBehaviourRequestOpti
|
|
|
613
613
|
...(options.stack !== undefined ? { stack: options.stack } : {}),
|
|
614
614
|
...(options.region !== undefined ? { region: options.region } : {}),
|
|
615
615
|
...(options.owned !== undefined ? { owned: options.owned } : {}),
|
|
616
|
+
from: options.from,
|
|
616
617
|
traffic: options.traffic,
|
|
617
618
|
edges: reconstructed.edges,
|
|
618
619
|
edgeCoverage: edgeCoverageOf(reconstructed, [...unresolved]),
|
package/src/index.ts
CHANGED
|
@@ -99,10 +99,12 @@ export { TERRAFORM_STATE_OWNERSHIP_KEYS } from "./state-ownership";
|
|
|
99
99
|
// path for a choudoufu root, reading `live-ls -json` and `live-plan -json`
|
|
100
100
|
// through the same activities `describeResources` runs, and the same producer
|
|
101
101
|
// over the declaration so the two sides of the delta are built alike. The
|
|
102
|
-
// engine is injected: this lexicon
|
|
102
|
+
// engine is injected, and defaults to core's front (#2495): this lexicon
|
|
103
|
+
// dials none.
|
|
103
104
|
export {
|
|
104
105
|
predictTerraformBehaviour,
|
|
105
106
|
terraformBehaviourRequest,
|
|
107
|
+
terraformPredictOptionsFrom,
|
|
106
108
|
TERRAFORM_REFERENCE_CATALOG,
|
|
107
109
|
type TerraformBehaviourDeps,
|
|
108
110
|
type TerraformBehaviourEntity,
|
package/src/plugin.test.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { dirname, join } from "node:path";
|
|
2
2
|
import { fileURLToPath } from "node:url";
|
|
3
|
-
import { describe, expect, it } from "vitest";
|
|
3
|
+
import { afterEach, describe, expect, it, vi } from "vitest";
|
|
4
|
+
import { behaviourEngineVariables, isBehaviourRefusalReport } from "@intentius/chant/behaviour";
|
|
4
5
|
import { isLexiconPlugin } from "@intentius/chant/lexicon";
|
|
5
6
|
import { validateLexiconConfig } from "@intentius/chant/lexicon-config";
|
|
6
7
|
import type { ChantConfig } from "@intentius/chant/config";
|
|
@@ -79,6 +80,27 @@ describe("terraform plugin", () => {
|
|
|
79
80
|
});
|
|
80
81
|
});
|
|
81
82
|
|
|
83
|
+
describe("predictBehaviour on the plugin (#2495)", () => {
|
|
84
|
+
afterEach(() => vi.unstubAllEnvs());
|
|
85
|
+
|
|
86
|
+
it("reaches core's engine front with nothing injected, and refuses by name when no engine is set", async () => {
|
|
87
|
+
for (const name of behaviourEngineVariables("chant")) vi.stubEnv(name, "");
|
|
88
|
+
const result = await terraformPlugin.predictBehaviour!({
|
|
89
|
+
environment: "prod",
|
|
90
|
+
buildOutput: "",
|
|
91
|
+
from: "declared",
|
|
92
|
+
traffic: "100 rps, p50",
|
|
93
|
+
entityNames: ["net/aws_vpc.main"],
|
|
94
|
+
entities: new Map([
|
|
95
|
+
["net/aws_vpc.main", { entityType: "Terraform::Resource", props: { root: "net", address: "aws_vpc.main", body: {} } }],
|
|
96
|
+
]),
|
|
97
|
+
edges: [],
|
|
98
|
+
edgeCoverage: { verdict: "unknown" },
|
|
99
|
+
});
|
|
100
|
+
expect(isBehaviourRefusalReport(result) && result.refusal.cause).toBe("no-engine");
|
|
101
|
+
});
|
|
102
|
+
});
|
|
103
|
+
|
|
82
104
|
describe("terraform config schema", () => {
|
|
83
105
|
it("accepts a root with every optional field", () => {
|
|
84
106
|
const parsed = terraformConfigSchema.parse({
|
package/src/plugin.ts
CHANGED
|
@@ -135,6 +135,19 @@ export const terraformPlugin: LexiconPlugin = {
|
|
|
135
135
|
return describeResources(options);
|
|
136
136
|
},
|
|
137
137
|
|
|
138
|
+
/**
|
|
139
|
+
* The prediction, for whichever estate `options.from` names (#2495). The
|
|
140
|
+
* producer is `predictTerraformBehaviour` and this is its second caller, not
|
|
141
|
+
* a second producer: the contract's options are read as its own and handed
|
|
142
|
+
* over, and it runs with the real reads and core's engine front. What that
|
|
143
|
+
* reading has to put back, for a declared graph and for a live one, is on
|
|
144
|
+
* `terraformPredictOptionsFrom`.
|
|
145
|
+
*/
|
|
146
|
+
async predictBehaviour(options) {
|
|
147
|
+
const { predictTerraformBehaviour, terraformPredictOptionsFrom } = await import("./behaviour");
|
|
148
|
+
return predictTerraformBehaviour(await terraformPredictOptionsFrom(options));
|
|
149
|
+
},
|
|
150
|
+
|
|
138
151
|
/**
|
|
139
152
|
* The kinds a live root can enumerate beyond its declaration (#1278,
|
|
140
153
|
* #2104). Empty on a project with no live root, because `live-ls` is the
|