@intentius/chant-lexicon-terraform 0.124.51 → 0.124.54

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (338) hide show
  1. package/README.md +7 -6
  2. package/dist/__fixtures__/pr-loop/estate.component.d.ts +7 -0
  3. package/dist/__fixtures__/pr-loop/estate.component.d.ts.map +1 -0
  4. package/dist/__fixtures__/pr-loop/estate.component.js +7 -0
  5. package/dist/__fixtures__/report/generate.d.ts +26 -0
  6. package/dist/__fixtures__/report/generate.d.ts.map +1 -0
  7. package/dist/__fixtures__/report/generate.js +58 -0
  8. package/dist/applied-state.d.ts +27 -0
  9. package/dist/applied-state.d.ts.map +1 -0
  10. package/dist/applied-state.js +44 -0
  11. package/dist/backend-config.d.ts +53 -0
  12. package/dist/backend-config.d.ts.map +1 -0
  13. package/dist/backend-config.js +136 -0
  14. package/dist/behaviour/kinds.d.ts.map +1 -1
  15. package/dist/behaviour/kinds.js +4 -0
  16. package/dist/behaviour/predict.d.ts.map +1 -1
  17. package/dist/behaviour/predict.js +6 -2
  18. package/dist/behaviour/reference-catalog.d.ts +16 -5
  19. package/dist/behaviour/reference-catalog.d.ts.map +1 -1
  20. package/dist/behaviour/reference-catalog.js +16 -5
  21. package/dist/behaviour/request.d.ts.map +1 -1
  22. package/dist/behaviour/request.js +3 -2
  23. package/dist/changed-roots.d.ts +6 -4
  24. package/dist/changed-roots.d.ts.map +1 -1
  25. package/dist/changed-roots.js +14 -18
  26. package/dist/commands.d.ts +5 -0
  27. package/dist/commands.d.ts.map +1 -1
  28. package/dist/commands.js +87 -4
  29. package/dist/components/capability-plugin.d.ts +65 -3
  30. package/dist/components/capability-plugin.d.ts.map +1 -1
  31. package/dist/components/capability-plugin.js +197 -13
  32. package/dist/components/compose-group.d.ts +145 -0
  33. package/dist/components/compose-group.d.ts.map +1 -0
  34. package/dist/components/compose-group.js +407 -0
  35. package/dist/components/placeholder-preview.d.ts +78 -0
  36. package/dist/components/placeholder-preview.d.ts.map +1 -0
  37. package/dist/components/placeholder-preview.js +254 -0
  38. package/dist/compose/apply-events.d.ts +46 -0
  39. package/dist/compose/apply-events.d.ts.map +1 -0
  40. package/dist/compose/apply-events.js +77 -0
  41. package/dist/compose/estate.d.ts +194 -0
  42. package/dist/compose/estate.d.ts.map +1 -0
  43. package/dist/compose/estate.js +363 -0
  44. package/dist/compose/hcl.d.ts +99 -0
  45. package/dist/compose/hcl.d.ts.map +1 -0
  46. package/dist/compose/hcl.js +302 -0
  47. package/dist/compose/module-address.d.ts +24 -0
  48. package/dist/compose/module-address.d.ts.map +1 -0
  49. package/dist/compose/module-address.js +43 -0
  50. package/dist/compose/root.d.ts +127 -0
  51. package/dist/compose/root.d.ts.map +1 -0
  52. package/dist/compose/root.js +586 -0
  53. package/dist/compose/run.d.ts +183 -0
  54. package/dist/compose/run.d.ts.map +1 -0
  55. package/dist/compose/run.js +447 -0
  56. package/dist/compose/slice.d.ts +102 -0
  57. package/dist/compose/slice.d.ts.map +1 -0
  58. package/dist/compose/slice.js +249 -0
  59. package/dist/compose/state-address.d.ts +28 -0
  60. package/dist/compose/state-address.d.ts.map +1 -0
  61. package/dist/compose/state-address.js +154 -0
  62. package/dist/compose/state-merge.d.ts +52 -0
  63. package/dist/compose/state-merge.d.ts.map +1 -0
  64. package/dist/compose/state-merge.js +135 -0
  65. package/dist/compose.d.ts +179 -0
  66. package/dist/compose.d.ts.map +1 -0
  67. package/dist/compose.js +482 -0
  68. package/dist/composites/terraform-apply-op.d.ts.map +1 -1
  69. package/dist/composites/terraform-apply-op.js +7 -3
  70. package/dist/config.d.ts +59 -13
  71. package/dist/config.d.ts.map +1 -1
  72. package/dist/config.js +43 -9
  73. package/dist/describe-resources.d.ts +74 -12
  74. package/dist/describe-resources.d.ts.map +1 -1
  75. package/dist/describe-resources.js +374 -66
  76. package/dist/drift-read.d.ts +33 -0
  77. package/dist/drift-read.d.ts.map +1 -0
  78. package/dist/drift-read.js +243 -0
  79. package/dist/drift-record.d.ts +147 -0
  80. package/dist/drift-record.d.ts.map +1 -0
  81. package/dist/drift-record.js +383 -0
  82. package/dist/hcl/edges.d.ts.map +1 -1
  83. package/dist/hcl/edges.js +24 -1
  84. package/dist/hcl/files.d.ts +61 -0
  85. package/dist/hcl/files.d.ts.map +1 -0
  86. package/dist/hcl/files.js +221 -0
  87. package/dist/hcl/instances.d.ts +82 -0
  88. package/dist/hcl/instances.d.ts.map +1 -0
  89. package/dist/hcl/instances.js +390 -0
  90. package/dist/hcl/parse.d.ts +81 -9
  91. package/dist/hcl/parse.d.ts.map +1 -1
  92. package/dist/hcl/parse.js +114 -16
  93. package/dist/hcl/roots.d.ts.map +1 -1
  94. package/dist/hcl/roots.js +5 -0
  95. package/dist/hcl/suppressions.d.ts.map +1 -1
  96. package/dist/hcl/suppressions.js +8 -2
  97. package/dist/index.d.ts +11 -2
  98. package/dist/index.d.ts.map +1 -1
  99. package/dist/index.js +21 -2
  100. package/dist/integrity.json +3 -3
  101. package/dist/live-plan-reasons.d.ts +21 -0
  102. package/dist/live-plan-reasons.d.ts.map +1 -0
  103. package/dist/live-plan-reasons.js +47 -0
  104. package/dist/lsp/option-keys.d.ts.map +1 -1
  105. package/dist/lsp/option-keys.js +2 -0
  106. package/dist/manifest.json +1 -1
  107. package/dist/op/activities/index.d.ts +6 -2
  108. package/dist/op/activities/index.d.ts.map +1 -1
  109. package/dist/op/activities/index.js +5 -1
  110. package/dist/op/activities/live-detect.d.ts +4 -3
  111. package/dist/op/activities/live-detect.d.ts.map +1 -1
  112. package/dist/op/activities/live-detect.js +57 -32
  113. package/dist/op/activities/pin-rollout.d.ts +2 -0
  114. package/dist/op/activities/pin-rollout.d.ts.map +1 -1
  115. package/dist/op/activities/pin-rollout.js +1 -0
  116. package/dist/op/activities/terraform.d.ts +153 -5
  117. package/dist/op/activities/terraform.d.ts.map +1 -1
  118. package/dist/op/activities/terraform.js +205 -20
  119. package/dist/op/activity-contracts.d.ts +27 -0
  120. package/dist/op/activity-contracts.d.ts.map +1 -1
  121. package/dist/op/activity-contracts.js +19 -1
  122. package/dist/op/builders.d.ts +7 -1
  123. package/dist/op/builders.d.ts.map +1 -1
  124. package/dist/op/builders.js +9 -0
  125. package/dist/pin/edit.d.ts.map +1 -1
  126. package/dist/pin/edit.js +35 -0
  127. package/dist/pin/rollout.d.ts +2 -0
  128. package/dist/pin/rollout.d.ts.map +1 -1
  129. package/dist/pin/rollout.js +14 -11
  130. package/dist/plan-shape.d.ts +30 -0
  131. package/dist/plan-shape.d.ts.map +1 -0
  132. package/dist/plan-shape.js +125 -0
  133. package/dist/plugin.d.ts.map +1 -1
  134. package/dist/plugin.js +32 -18
  135. package/dist/report.d.ts +216 -0
  136. package/dist/report.d.ts.map +1 -0
  137. package/dist/report.js +666 -0
  138. package/dist/skills/chant-terraform.md +1 -1
  139. package/dist/state/azure-blob.d.ts +68 -0
  140. package/dist/state/azure-blob.d.ts.map +1 -0
  141. package/dist/state/azure-blob.js +201 -0
  142. package/dist/state/backend.d.ts +158 -0
  143. package/dist/state/backend.d.ts.map +1 -0
  144. package/dist/state/backend.js +406 -0
  145. package/dist/state/exec.d.ts +39 -0
  146. package/dist/state/exec.d.ts.map +1 -0
  147. package/dist/state/exec.js +80 -0
  148. package/dist/state/gcs.d.ts +62 -0
  149. package/dist/state/gcs.d.ts.map +1 -0
  150. package/dist/state/gcs.js +224 -0
  151. package/dist/state/index.d.ts +15 -0
  152. package/dist/state/index.d.ts.map +1 -0
  153. package/dist/state/index.js +14 -0
  154. package/dist/state/locks.d.ts +105 -0
  155. package/dist/state/locks.d.ts.map +1 -0
  156. package/dist/state/locks.js +130 -0
  157. package/dist/state/object-store.d.ts +37 -0
  158. package/dist/state/object-store.d.ts.map +1 -0
  159. package/dist/state/object-store.js +20 -0
  160. package/dist/state/root-states.d.ts +131 -0
  161. package/dist/state/root-states.d.ts.map +1 -0
  162. package/dist/state/root-states.js +241 -0
  163. package/dist/state/s3.d.ts +68 -0
  164. package/dist/state/s3.d.ts.map +1 -0
  165. package/dist/state/s3.js +170 -0
  166. package/dist/state/store.d.ts +51 -0
  167. package/dist/state/store.d.ts.map +1 -0
  168. package/dist/state/store.js +185 -0
  169. package/dist/terragrunt/affected.d.ts.map +1 -1
  170. package/dist/terragrunt/affected.js +2 -1
  171. package/package.json +7 -3
  172. package/src/__fixtures__/ACCEPTANCE.md +69 -7
  173. package/src/__fixtures__/compose/apply-failed-in-app.jsonl +12 -0
  174. package/src/__fixtures__/compose/state-app.json +66 -0
  175. package/src/__fixtures__/compose/state-edge.json +40 -0
  176. package/src/__fixtures__/compose/state-net.json +58 -0
  177. package/src/__fixtures__/compose/three-app.plan.json +184 -0
  178. package/src/__fixtures__/compose/three-composed.plan.json +509 -0
  179. package/src/__fixtures__/compose/three-edge.plan.json +144 -0
  180. package/src/__fixtures__/compose/three-net.plan.json +174 -0
  181. package/src/__fixtures__/compose/unknown-composed.plan.json +228 -0
  182. package/src/__fixtures__/compose/working-after-apply.json +145 -0
  183. package/src/__fixtures__/config-blocks/app/main.tf +61 -0
  184. package/src/__fixtures__/config-blocks/app/modules/network/main.tf +4 -0
  185. package/src/__fixtures__/config-blocks/app-json/main.tf.json +39 -0
  186. package/src/__fixtures__/config-files/shadow/main.tf +3 -0
  187. package/src/__fixtures__/config-files/shadow/main.tofu +3 -0
  188. package/src/__fixtures__/config-files/shadow/other.tf +3 -0
  189. package/src/__fixtures__/config-files/tf/main.tf +11 -0
  190. package/src/__fixtures__/config-files/tf-json/main.tf.json +18 -0
  191. package/src/__fixtures__/config-files/tofu/main.tofu +11 -0
  192. package/src/__fixtures__/config-files/tofu-json/main.tofu.json +18 -0
  193. package/src/__fixtures__/config-files/tofu-only/main.tofu +3 -0
  194. package/src/__fixtures__/drift-record/README.md +11 -0
  195. package/src/__fixtures__/drift-record/live-plan-sections.json +27 -0
  196. package/src/__fixtures__/instance-forms/app/main.tf +110 -0
  197. package/src/__fixtures__/instance-forms/app/modules/svc/main.tf +3 -0
  198. package/src/__fixtures__/instance-vars/app/main.tf +52 -0
  199. package/src/__fixtures__/instance-vars/app/modules/app/main.tf +12 -0
  200. package/src/__fixtures__/instance-vars/app/modules/edge/main.tf +3 -0
  201. package/src/__fixtures__/instance-vars/app/terraform.tfvars +1 -0
  202. package/src/__fixtures__/live-module-keyed/live-graph.json +259 -0
  203. package/src/__fixtures__/live-module-keyed/live-plan.json +87 -0
  204. package/src/__fixtures__/live-module-keyed/root/estate.chdf.hcl +1 -0
  205. package/src/__fixtures__/live-module-keyed/root/main.tf +21 -0
  206. package/src/__fixtures__/live-module-keyed/root/modules/app/main.tf +17 -0
  207. package/src/__fixtures__/live-module-keyed/root/modules/edge/main.tf +7 -0
  208. package/src/__fixtures__/live-module-keyed/root/versions.tf +30 -0
  209. package/src/__fixtures__/live-module-keyed/show-state.json +146 -0
  210. package/src/__fixtures__/pr-loop/README.md +50 -0
  211. package/src/__fixtures__/pr-loop/chant.config.json +13 -0
  212. package/src/__fixtures__/pr-loop/estate.component.ts +10 -0
  213. package/src/__fixtures__/pr-loop/roots/a/main.tf +11 -0
  214. package/src/__fixtures__/pr-loop/roots/app/main.tf +19 -0
  215. package/src/__fixtures__/pr-loop/roots/b/main.tf +7 -0
  216. package/src/__fixtures__/pr-loop/roots/dns/main.tf +3 -0
  217. package/src/__fixtures__/pr-loop/roots/net/main.tf +7 -0
  218. package/src/__fixtures__/report/applied.show.json +51 -0
  219. package/src/__fixtures__/report/generate.ts +63 -0
  220. package/src/__fixtures__/report/plan.show.json +200 -0
  221. package/src/__fixtures__/report/tf-apply/change-set.json +170 -0
  222. package/src/__fixtures__/report/tf-apply/report.json +338 -0
  223. package/src/__fixtures__/report/tf-plan/change-set.json +170 -0
  224. package/src/__fixtures__/report/tf-plan/report.json +320 -0
  225. package/src/__fixtures__/three-cloud-azure.graph.json +290 -0
  226. package/src/applied-state.ts +52 -0
  227. package/src/backend-config.test.ts +105 -0
  228. package/src/backend-config.ts +133 -0
  229. package/src/behaviour/kinds.ts +4 -0
  230. package/src/behaviour/predict.ts +5 -2
  231. package/src/behaviour/reference-catalog.plugin.test.ts +65 -0
  232. package/src/behaviour/reference-catalog.ts +16 -5
  233. package/src/behaviour/request.ts +3 -2
  234. package/src/change-set-instances.test.ts +98 -0
  235. package/src/changed-roots.ts +14 -16
  236. package/src/commands.ts +84 -4
  237. package/src/components/capability-plugin.test.ts +172 -5
  238. package/src/components/capability-plugin.ts +212 -11
  239. package/src/components/change-set-graph.e2e.test.ts +11 -5
  240. package/src/components/compose-group.test.ts +486 -0
  241. package/src/components/compose-group.ts +489 -0
  242. package/src/components/demo-composed-roots.e2e.test.ts +440 -0
  243. package/src/components/member-outputs.test.ts +76 -0
  244. package/src/components/placeholder-preview.test.ts +147 -0
  245. package/src/components/placeholder-preview.ts +257 -0
  246. package/src/components/pr-loop-compose.tofu.e2e.test.ts +363 -0
  247. package/src/components/pr-loop.tofu.test.ts +112 -56
  248. package/src/compose/apply-events.test.ts +62 -0
  249. package/src/compose/apply-events.ts +100 -0
  250. package/src/compose/estate.choudoufu.e2e.test.ts +112 -0
  251. package/src/compose/estate.test.ts +421 -0
  252. package/src/compose/estate.ts +443 -0
  253. package/src/compose/hcl.ts +311 -0
  254. package/src/compose/module-address.ts +49 -0
  255. package/src/compose/root.test.ts +416 -0
  256. package/src/compose/root.ts +592 -0
  257. package/src/compose/run.test.ts +440 -0
  258. package/src/compose/run.tofu.test.ts +176 -0
  259. package/src/compose/run.ts +527 -0
  260. package/src/compose/slice.test.ts +184 -0
  261. package/src/compose/slice.ts +290 -0
  262. package/src/compose/state-address.ts +157 -0
  263. package/src/compose/state-merge.test.ts +131 -0
  264. package/src/compose/state-merge.ts +148 -0
  265. package/src/compose.test.ts +248 -0
  266. package/src/compose.ts +627 -0
  267. package/src/composites/terraform-apply-op.test.ts +9 -0
  268. package/src/composites/terraform-apply-op.ts +7 -3
  269. package/src/composites/terraform-pin-rollout-op.test.ts +2 -2
  270. package/src/config-file-listers.test.ts +265 -0
  271. package/src/config.ts +44 -9
  272. package/src/describe-resources.engine-facts.test.ts +412 -0
  273. package/src/describe-resources.live-module-keyed.test.ts +290 -0
  274. package/src/describe-resources.ts +416 -70
  275. package/src/drift-read.ts +284 -0
  276. package/src/drift-record.test.ts +223 -0
  277. package/src/drift-record.ts +455 -0
  278. package/src/hcl/config-blocks.test.ts +174 -0
  279. package/src/hcl/edges.ts +21 -1
  280. package/src/hcl/files.ts +234 -0
  281. package/src/hcl/instance-forms.test.ts +94 -0
  282. package/src/hcl/instances.test.ts +137 -0
  283. package/src/hcl/instances.ts +422 -0
  284. package/src/hcl/parse.test.ts +139 -1
  285. package/src/hcl/parse.ts +149 -16
  286. package/src/hcl/roots.ts +6 -0
  287. package/src/hcl/suppressions.ts +8 -2
  288. package/src/index.ts +76 -0
  289. package/src/live-plan-reasons.ts +54 -0
  290. package/src/lsp/completions.test.ts +1 -1
  291. package/src/lsp/option-keys.ts +2 -0
  292. package/src/op/activities/choudoufu-plan-set-op.test.ts +111 -0
  293. package/src/op/activities/index.ts +9 -0
  294. package/src/op/activities/live-detect.ts +55 -26
  295. package/src/op/activities/pin-rollout.ts +3 -0
  296. package/src/op/activities/registry.test.ts +2 -1
  297. package/src/op/activities/terraform.test.ts +55 -0
  298. package/src/op/activities/terraform.ts +266 -22
  299. package/src/op/activity-contracts.test.ts +1 -0
  300. package/src/op/activity-contracts.ts +24 -1
  301. package/src/op/builders.ts +14 -0
  302. package/src/pin/bundle.test.ts +2 -0
  303. package/src/pin/edit.ts +30 -0
  304. package/src/pin/rollout.ts +16 -11
  305. package/src/plan-shape.test.ts +148 -0
  306. package/src/plan-shape.ts +128 -0
  307. package/src/plugin.ts +33 -19
  308. package/src/report.test.ts +423 -0
  309. package/src/report.ts +752 -0
  310. package/src/signal-only-root.test.ts +72 -0
  311. package/src/skills/chant-terraform.md +1 -1
  312. package/src/state/azure-blob.ts +223 -0
  313. package/src/state/backend-verdict.test.ts +187 -0
  314. package/src/state/backend.test.ts +127 -0
  315. package/src/state/backend.ts +488 -0
  316. package/src/state/exec.ts +111 -0
  317. package/src/state/gcs.ts +254 -0
  318. package/src/state/index.ts +15 -0
  319. package/src/state/locks.test.ts +84 -0
  320. package/src/state/locks.ts +201 -0
  321. package/src/state/object-store.ts +35 -0
  322. package/src/state/root-states-floci.e2e.test.ts +66 -0
  323. package/src/state/root-states.test.ts +462 -0
  324. package/src/state/root-states.ts +309 -0
  325. package/src/state/s3.ts +193 -0
  326. package/src/state/store.ts +225 -0
  327. package/src/terragrunt/affected.ts +2 -1
  328. package/src/workspace-kinds.test.ts +4 -17
  329. package/workspace-kinds.json +1 -24
  330. package/dist/workspace-member.d.ts +0 -30
  331. package/dist/workspace-member.d.ts.map +0 -1
  332. package/dist/workspace-member.js +0 -91
  333. package/schemas/pulumi.schema.json +0 -34
  334. package/src/testdata/pulumi-member/Pulumi.yaml +0 -2
  335. package/src/testdata/pulumi-member/stack-export.json +0 -53
  336. package/src/testdata/pulumi-unexported/Pulumi.yaml +0 -2
  337. package/src/workspace-member.test.ts +0 -89
  338. package/src/workspace-member.ts +0 -126
@@ -0,0 +1,290 @@
1
+ /**
2
+ * Module-keyed addresses through describe-resources.ts, from documents
3
+ * (chant #4513).
4
+ *
5
+ * #4488 gave live instances under an expanded module call a `scope`, and its
6
+ * core test (packages/core/src/graph-ir-live.test.ts) feeds that metadata in
7
+ * by hand. This file reads it out of a live-plan document and a state
8
+ * document whose every address is module-keyed (`module.app[0].aws_eip.pool[1]`,
9
+ * `module.edge["east"]...`), then checks that the live graph's instance ids
10
+ * are the ones the declared graph lists.
11
+ *
12
+ * The declared side is real: `src/__fixtures__/live-module-keyed/root/`,
13
+ * rendered as `buildRoots()` renders it, with an `estate.chdf.hcl` sidecar
14
+ * that makes the root live under `binary: "choudoufu"`.
15
+ *
16
+ * The two documents were built by hand. No binary was run, so these are not
17
+ * recordings like `live-plan-depth/`. Where each part comes from, all read
18
+ * on choudoufu `origin/integration/consolidation-graph` (b47315e7c):
19
+ *
20
+ * - `live-plan.json`'s keys and their order: `LivePlanDocument`,
21
+ * `LivePlanBound`, `LiveOmission` and `LiveUnowned` in
22
+ * internal/command/views/live_plan.go. The addresses are
23
+ * `addrs.AbsResourceInstance.String()`, which `liveBoundReport`,
24
+ * `liveOmissions` and `liveUnownedReport` in internal/command/live_plan.go
25
+ * write as `addr`.
26
+ * - `source`: `derived` for the log group, whose name is its identity;
27
+ * `marker` for the EIPs, whose allocation id EC2 assigns
28
+ * (`liveBoundReport`'s switch, the same split as the recorded
29
+ * `live-plan-depth/live-plan.json`).
30
+ * - `adopt_tofu_address`: `markers.EscapeAddress`
31
+ * (internal/live/markers/markers.go) and the escaping table in
32
+ * live/MARKERS.md, `module.subnets["a"].aws_subnet.this` ->
33
+ * `module.subnets:a.aws_subnet.this`.
34
+ * - The omission `detail` strings: the ABSENT template in
35
+ * internal/live/projection/build.go, the no-marker UNOWNED template and
36
+ * `anotherEstateDetail` in internal/live/projection/ownership.go. The
37
+ * paired "Live resource outside this estate" diagnostics, and
38
+ * `upstream_version`, follow the recorded v0.30.0 document in
39
+ * `live-plan-depth/`. `choudoufu_version` is empty, which the struct
40
+ * documents as a development build, because no release produced this
41
+ * document.
42
+ * - That a module call's count or for_each reaches a child's identity
43
+ * through a variable: `TestModuleCallCountVar` and `TestModuleCallForEachVar`
44
+ * in internal/live/identity/moduleforeachvar_test.go.
45
+ * - `show-state.json`: `jsonstate.Module` and `jsonstate.Resource` in
46
+ * internal/command/jsonstate/state.go. A child module's `address` is
47
+ * `addrs.ModuleInstance.String()` (`module.app[0]`, `marshalModules`), and
48
+ * a resource's address carries the module prefix, as the recorded
49
+ * `src/__fixtures__/show-state.json` does for an unkeyed call.
50
+ * - The EIP allocation ids and the account id in the ARNs are made up.
51
+ *
52
+ * Nothing here runs choudoufu or tofu: the activities are injected.
53
+ */
54
+
55
+ import { readFileSync } from "node:fs";
56
+ import { dirname, join } from "node:path";
57
+ import { fileURLToPath } from "node:url";
58
+ import { describe, expect, it } from "vitest";
59
+ import { normalizeObservation } from "@intentius/chant/observation";
60
+ import type { Declarable } from "@intentius/chant/declarable";
61
+ import { buildGraphIr, buildLiveGraphIr, overlayGraphs, withInstanceNodes } from "@intentius/chant/graph-ir";
62
+ import { describeResources, indexLivePlan, indexStateResources, type TerraformReadDeps } from "./describe-resources";
63
+ import { renderTerraformRoots } from "./hcl/roots";
64
+
65
+ const fixtures = join(dirname(fileURLToPath(import.meta.url)), "__fixtures__");
66
+ const FIXTURE = join(fixtures, "live-module-keyed");
67
+ const ROOT_DIR = join(FIXTURE, "root");
68
+ const LIVE_PLAN: unknown = JSON.parse(readFileSync(join(FIXTURE, "live-plan.json"), "utf-8"));
69
+ const STATE: unknown = JSON.parse(readFileSync(join(FIXTURE, "show-state.json"), "utf-8"));
70
+ const ESTATE = "module-keyed-estate";
71
+
72
+ type Entities = Map<string, { entityType: string; props: Record<string, unknown> }>;
73
+
74
+ /** The fixture root rendered under `binary`: `"choudoufu"` makes it live, `"tofu"` stock. */
75
+ async function render(binary: "choudoufu" | "tofu"): Promise<{ declarables: Map<string, Declarable>; entities: Entities }> {
76
+ const { entities: declarables } = await renderTerraformRoots({
77
+ projectRoot: FIXTURE,
78
+ roots: { estate: { dir: "./root" } },
79
+ binary,
80
+ });
81
+ const entities: Entities = new Map();
82
+ for (const [key, entity] of declarables) {
83
+ entities.set(key, { entityType: entity.entityType, props: (entity as Declarable & { props: Record<string, unknown> }).props });
84
+ }
85
+ return { declarables, entities };
86
+ }
87
+
88
+ function options(entities: Entities) {
89
+ return { environment: "prod", buildOutput: "", entityNames: [...entities.keys()].sort(), entities };
90
+ }
91
+
92
+ const never = ((args: { root: string }) => {
93
+ throw new Error(`unexpected activity for root "${args.root}"`);
94
+ }) as never;
95
+
96
+ /** The live root's activities: `live-plan -json` answers, nothing else may run. */
97
+ function liveDeps(): TerraformReadDeps {
98
+ return {
99
+ init: never,
100
+ show: never,
101
+ liveLs: never,
102
+ livePlan: (async () => ({ json: LIVE_PLAN, dir: ROOT_DIR, estate: ESTATE })) as unknown as TerraformReadDeps["livePlan"],
103
+ };
104
+ }
105
+
106
+ /** The stock root's activities: `init`, then `show -json` over the state document. */
107
+ function stateDeps(): TerraformReadDeps {
108
+ return {
109
+ init: (async () => ({ dir: ROOT_DIR })) as TerraformReadDeps["init"],
110
+ show: (async () => ({ source: "state" as const, json: STATE, text: "", dir: ROOT_DIR, adds: 0, changes: 0, destroys: 0 })) as TerraformReadDeps["show"],
111
+ livePlan: never,
112
+ liveLs: never,
113
+ };
114
+ }
115
+
116
+ const POOL = [
117
+ "module.app[0].aws_eip.pool[0]",
118
+ "module.app[0].aws_eip.pool[1]",
119
+ "module.app[1].aws_eip.pool[0]",
120
+ "module.app[1].aws_eip.pool[1]",
121
+ ];
122
+ const EAST = 'module.edge["east"].aws_cloudwatch_log_group.front';
123
+ const WEST = 'module.edge["west"].aws_cloudwatch_log_group.front';
124
+
125
+ describe("the module-keyed fixture root (#4513)", () => {
126
+ it("declares each block once, under its unkeyed module call, and live", async () => {
127
+ const { entities } = await render("choudoufu");
128
+ for (const key of [
129
+ "estate/module.app",
130
+ "estate/module.edge",
131
+ "estate/module.app/aws_eip.pool",
132
+ "estate/module.app/aws_cloudwatch_log_group.logs",
133
+ "estate/module.edge/aws_cloudwatch_log_group.front",
134
+ ]) {
135
+ expect(entities.has(key), key).toBe(true);
136
+ expect(entities.get(key)!.props.mode, key).toBe("live");
137
+ expect(entities.get(key)!.props.estate, key).toBe(ESTATE);
138
+ }
139
+ expect([...entities.keys()].some((k) => k.includes("["))).toBe(false);
140
+ });
141
+
142
+ it("indexes the live-plan document by module-keyed instance address", () => {
143
+ const index = indexLivePlan(LIVE_PLAN);
144
+ expect(index.estate).toBe(ESTATE);
145
+ expect([...index.bound.keys()].sort()).toEqual(["module.app[0].aws_cloudwatch_log_group.logs", ...POOL]);
146
+ expect(index.omissions.get("module.app[1].aws_cloudwatch_log_group.logs")!.reason).toBe("ABSENT");
147
+ expect(index.unowned.get(EAST)).toMatchObject({ adoptEstate: ESTATE, adoptAddress: "module.edge:east.aws_cloudwatch_log_group.front" });
148
+ expect(index.unowned.get(WEST)).toMatchObject({ heldBy: "other-estate" });
149
+ });
150
+
151
+ it("indexes the state document's keyed child modules and their rows", () => {
152
+ const index = indexStateResources(STATE);
153
+ expect([...index.modules].sort()).toEqual(["module.app[0]", "module.app[1]", 'module.edge["east"]', 'module.edge["west"]']);
154
+ expect(index.rows.get("module.app[1].aws_eip.pool[0]")).toMatchObject({ type: "aws_eip", id: "eipalloc-0c63e9f40bd5172a3" });
155
+ // The module prefix is already on each row, so none gets a second one.
156
+ expect([...index.rows.keys()].filter((k) => k.startsWith("module.app[0].module.app"))).toEqual([]);
157
+ });
158
+ });
159
+
160
+ describe("describeResources over a module-keyed live-plan document (#4513)", () => {
161
+ it("rolls a block expanded under an expanded call up to owned, with key and scope per instance", async () => {
162
+ const { entities } = await render("choudoufu");
163
+ const { resources, queried } = normalizeObservation(await describeResources(options(entities), liveDeps()));
164
+ const pool = resources["estate/module.app/aws_eip.pool"]!;
165
+ expect(queried["estate/module.app/aws_eip.pool"]).toBe(
166
+ `choudoufu live-plan -json (root "estate", estate "${ESTATE}", address "module.app.aws_eip.pool")`,
167
+ );
168
+ expect(pool).toMatchObject({
169
+ type: "Terraform::Resource",
170
+ status: "bound",
171
+ ownership: "owned",
172
+ physicalId: "module.app.aws_eip.pool",
173
+ marker: { stack: ESTATE },
174
+ });
175
+ expect(pool.attributes).toMatchObject({ address: "module.app.aws_eip.pool", root: "estate", estate: ESTATE, boundBy: "marker", resourceType: "aws_eip" });
176
+ expect(pool.attributes!.instances).toEqual(POOL);
177
+ expect(pool.instances).toEqual([
178
+ { key: "[0]", scope: "module.app[0]", address: POOL[0], verdict: "present", ownership: "owned", status: "bound", physicalId: "eipalloc-0a41c7d2e9b3f5061" },
179
+ { key: "[1]", scope: "module.app[0]", address: POOL[1], verdict: "present", ownership: "owned", status: "bound", physicalId: "eipalloc-0b52d8e3fac4061f2" },
180
+ { key: "[0]", scope: "module.app[1]", address: POOL[2], verdict: "present", ownership: "owned", status: "bound", physicalId: "eipalloc-0c63e9f40bd5172a3" },
181
+ { key: "[1]", scope: "module.app[1]", address: POOL[3], verdict: "present", ownership: "owned", status: "bound", physicalId: "eipalloc-0d74fa051ce6283b4" },
182
+ ]);
183
+ });
184
+
185
+ it("gives a block only the call expands a scope and no key, and keeps an absent instance", async () => {
186
+ const { entities } = await render("choudoufu");
187
+ const { resources } = normalizeObservation(await describeResources(options(entities), liveDeps()));
188
+ const logs = resources["estate/module.app/aws_cloudwatch_log_group.logs"]!;
189
+ // One instance bound by derivation, the other ABSENT: owned, with the one
190
+ // bound identity, and no marker, because a derived bind read no tag.
191
+ expect(logs).toMatchObject({ status: "bound", ownership: "owned", physicalId: `/${ESTATE}/app-0` });
192
+ expect(logs.marker).toBeUndefined();
193
+ expect(logs.attributes).toMatchObject({ boundBy: "derived" });
194
+ expect(logs.instances).toEqual([
195
+ {
196
+ scope: "module.app[0]",
197
+ address: "module.app[0].aws_cloudwatch_log_group.logs",
198
+ verdict: "present",
199
+ ownership: "owned",
200
+ status: "bound",
201
+ physicalId: `/${ESTATE}/app-0`,
202
+ },
203
+ { scope: "module.app[1]", address: "module.app[1].aws_cloudwatch_log_group.logs", verdict: "absent" },
204
+ ]);
205
+ });
206
+
207
+ it("reads a for_each call's string keys, foreign over adoptable", async () => {
208
+ const { entities } = await render("choudoufu");
209
+ const { resources } = normalizeObservation(await describeResources(options(entities), liveDeps()));
210
+ const front = resources["estate/module.edge/aws_cloudwatch_log_group.front"]!;
211
+ expect(front).toMatchObject({ status: "unowned", ownership: "foreign", physicalId: `/${ESTATE}/edge-west` });
212
+ expect(front.attributes).toMatchObject({ heldBy: "other-estate", instances: [EAST, WEST] });
213
+ expect(front.instances).toEqual([
214
+ { scope: 'module.edge["east"]', address: EAST, verdict: "present", ownership: "unknown", status: "adoptable", physicalId: `/${ESTATE}/edge-east` },
215
+ { scope: 'module.edge["west"]', address: WEST, verdict: "present", ownership: "foreign", status: "unowned", physicalId: `/${ESTATE}/edge-west` },
216
+ ]);
217
+ });
218
+
219
+ it("finds a keyed module call's members by its unkeyed address", async () => {
220
+ const { entities } = await render("choudoufu");
221
+ const { resources } = normalizeObservation(await describeResources(options(entities), liveDeps()));
222
+ expect(resources["estate/module.app"]).toMatchObject({ type: "Terraform::Module", status: "module", ownership: "unknown" });
223
+ expect(resources["estate/module.app"]!.attributes!.instances).toEqual([
224
+ "module.app[0].aws_cloudwatch_log_group.logs",
225
+ ...POOL.slice(0, 2),
226
+ "module.app[1].aws_cloudwatch_log_group.logs",
227
+ ...POOL.slice(2),
228
+ ]);
229
+ expect(resources["estate/module.edge"]!.attributes!.instances).toEqual([EAST, WEST]);
230
+ });
231
+
232
+ it("puts every live instance on a node the declared graph lists", async () => {
233
+ const { declarables, entities } = await render("choudoufu");
234
+ const { resources } = normalizeObservation(await describeResources(options(entities), liveDeps()));
235
+ const live = buildLiveGraphIr([{ lexicon: "terraform", resources }]);
236
+ const declared = withInstanceNodes(buildGraphIr(declarables), declarables);
237
+ const declaredIds = new Set(declared.nodes.map((n) => n.id));
238
+
239
+ const liveInstances = live.nodes.filter((n) => n.instanceOf).map((n) => n.id).sort();
240
+ expect(liveInstances).toEqual([
241
+ "estate/module.app[0]/aws_cloudwatch_log_group.logs",
242
+ "estate/module.app[0]/aws_eip.pool[0]",
243
+ "estate/module.app[0]/aws_eip.pool[1]",
244
+ "estate/module.app[1]/aws_eip.pool[0]",
245
+ "estate/module.app[1]/aws_eip.pool[1]",
246
+ 'estate/module.edge["east"]/aws_cloudwatch_log_group.front',
247
+ 'estate/module.edge["west"]/aws_cloudwatch_log_group.front',
248
+ ]);
249
+ for (const id of liveInstances) expect(declaredIds.has(id), id).toBe(true);
250
+
251
+ // The absent instance draws no live node, so the overlay leaves the
252
+ // declared one unmatched while its bound sibling matches.
253
+ const status = new Map(overlayGraphs(live, declared).nodes.map((n) => [n.id, n.attrs._status]));
254
+ expect(status.get("estate/module.app[0]/aws_cloudwatch_log_group.logs")).toBe("good");
255
+ expect(status.get("estate/module.app[1]/aws_cloudwatch_log_group.logs")).toBe("accent");
256
+ });
257
+ });
258
+
259
+ describe("describeResources over a module-keyed state document (#4513)", () => {
260
+ it("reads each block's rows under keyed child modules as its instances", async () => {
261
+ const { entities } = await render("tofu");
262
+ const { resources, queried } = normalizeObservation(await describeResources(options(entities), stateDeps()));
263
+ expect(queried["estate/module.app/aws_eip.pool"]).toBe('terraform show -json (root "estate", address "module.app.aws_eip.pool")');
264
+ const pool = resources["estate/module.app/aws_eip.pool"]!;
265
+ expect(pool).toMatchObject({ status: "managed", ownership: "owned", physicalId: "module.app.aws_eip.pool" });
266
+ expect(pool.attributes!.instances).toEqual(POOL);
267
+ expect(pool.instances!.map((i) => [i.key, i.scope, i.physicalId])).toEqual([
268
+ ["[0]", "module.app[0]", "eipalloc-0a41c7d2e9b3f5061"],
269
+ ["[1]", "module.app[0]", "eipalloc-0b52d8e3fac4061f2"],
270
+ ["[0]", "module.app[1]", "eipalloc-0c63e9f40bd5172a3"],
271
+ ["[1]", "module.app[1]", "eipalloc-0d74fa051ce6283b4"],
272
+ ]);
273
+
274
+ const front = resources["estate/module.edge/aws_cloudwatch_log_group.front"]!;
275
+ expect(front.instances!.map((i) => [i.key, i.scope])).toEqual([
276
+ [undefined, 'module.edge["east"]'],
277
+ [undefined, 'module.edge["west"]'],
278
+ ]);
279
+ });
280
+
281
+ it("reports a keyed module call present from its child_modules entries", async () => {
282
+ // A keyed call's child_modules[] entries are `module.app[0]`, never
283
+ // `module.app`, and neither its rows nor they start with `module.app.`,
284
+ // so a prefix test on the unkeyed address read the call as absent.
285
+ const { entities } = await render("tofu");
286
+ const { resources } = normalizeObservation(await describeResources(options(entities), stateDeps()));
287
+ expect(resources["estate/module.app"]).toMatchObject({ type: "Terraform::Module", status: "module", physicalId: "module.app" });
288
+ expect(resources["estate/module.edge"]).toMatchObject({ type: "Terraform::Module", status: "module", physicalId: "module.edge" });
289
+ });
290
+ });