@intentius/chant-lexicon-terraform 0.124.52 → 0.124.55

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 (641) hide show
  1. package/README.md +14 -10
  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/ci-setup.d.ts +11 -0
  27. package/dist/ci-setup.d.ts.map +1 -0
  28. package/dist/ci-setup.js +41 -0
  29. package/dist/commands.d.ts.map +1 -1
  30. package/dist/commands.js +4 -3
  31. package/dist/components/capability-plugin.d.ts +105 -9
  32. package/dist/components/capability-plugin.d.ts.map +1 -1
  33. package/dist/components/capability-plugin.js +357 -36
  34. package/dist/components/compose-group.d.ts +145 -0
  35. package/dist/components/compose-group.d.ts.map +1 -0
  36. package/dist/components/compose-group.js +407 -0
  37. package/dist/components/placeholder-preview.d.ts +78 -0
  38. package/dist/components/placeholder-preview.d.ts.map +1 -0
  39. package/dist/components/placeholder-preview.js +254 -0
  40. package/dist/components/release-inputs.d.ts +29 -0
  41. package/dist/components/release-inputs.d.ts.map +1 -0
  42. package/dist/components/release-inputs.js +57 -0
  43. package/dist/components/trace-facts.d.ts +16 -0
  44. package/dist/components/trace-facts.d.ts.map +1 -0
  45. package/dist/components/trace-facts.js +19 -0
  46. package/dist/compose/apply-events.d.ts +46 -0
  47. package/dist/compose/apply-events.d.ts.map +1 -0
  48. package/dist/compose/apply-events.js +77 -0
  49. package/dist/compose/estate.d.ts +194 -0
  50. package/dist/compose/estate.d.ts.map +1 -0
  51. package/dist/compose/estate.js +363 -0
  52. package/dist/compose/hcl.d.ts +99 -0
  53. package/dist/compose/hcl.d.ts.map +1 -0
  54. package/dist/compose/hcl.js +302 -0
  55. package/dist/compose/module-address.d.ts +24 -0
  56. package/dist/compose/module-address.d.ts.map +1 -0
  57. package/dist/compose/module-address.js +43 -0
  58. package/dist/compose/root.d.ts +127 -0
  59. package/dist/compose/root.d.ts.map +1 -0
  60. package/dist/compose/root.js +586 -0
  61. package/dist/compose/run.d.ts +183 -0
  62. package/dist/compose/run.d.ts.map +1 -0
  63. package/dist/compose/run.js +447 -0
  64. package/dist/compose/slice.d.ts +102 -0
  65. package/dist/compose/slice.d.ts.map +1 -0
  66. package/dist/compose/slice.js +249 -0
  67. package/dist/compose/state-address.d.ts +28 -0
  68. package/dist/compose/state-address.d.ts.map +1 -0
  69. package/dist/compose/state-address.js +154 -0
  70. package/dist/compose/state-merge.d.ts +52 -0
  71. package/dist/compose/state-merge.d.ts.map +1 -0
  72. package/dist/compose/state-merge.js +135 -0
  73. package/dist/compose.d.ts +179 -0
  74. package/dist/compose.d.ts.map +1 -0
  75. package/dist/compose.js +482 -0
  76. package/dist/composites/terraform-apply-op.d.ts.map +1 -1
  77. package/dist/composites/terraform-apply-op.js +7 -3
  78. package/dist/cone.d.ts +53 -0
  79. package/dist/cone.d.ts.map +1 -0
  80. package/dist/cone.js +119 -0
  81. package/dist/config.d.ts +59 -13
  82. package/dist/config.d.ts.map +1 -1
  83. package/dist/config.js +43 -9
  84. package/dist/cost-hold.d.ts +27 -0
  85. package/dist/cost-hold.d.ts.map +1 -0
  86. package/dist/cost-hold.js +21 -0
  87. package/dist/describe-resources.d.ts +74 -12
  88. package/dist/describe-resources.d.ts.map +1 -1
  89. package/dist/describe-resources.js +374 -66
  90. package/dist/drift-read.d.ts +33 -0
  91. package/dist/drift-read.d.ts.map +1 -0
  92. package/dist/drift-read.js +243 -0
  93. package/dist/drift-record.d.ts +147 -0
  94. package/dist/drift-record.d.ts.map +1 -0
  95. package/dist/drift-record.js +383 -0
  96. package/dist/drift-rows.d.ts +61 -0
  97. package/dist/drift-rows.d.ts.map +1 -0
  98. package/dist/drift-rows.js +108 -0
  99. package/dist/exit-codes.d.ts +55 -0
  100. package/dist/exit-codes.d.ts.map +1 -0
  101. package/dist/exit-codes.js +48 -0
  102. package/dist/hcl/edges.d.ts.map +1 -1
  103. package/dist/hcl/edges.js +24 -1
  104. package/dist/hcl/files.d.ts +61 -0
  105. package/dist/hcl/files.d.ts.map +1 -0
  106. package/dist/hcl/files.js +221 -0
  107. package/dist/hcl/instances.d.ts +82 -0
  108. package/dist/hcl/instances.d.ts.map +1 -0
  109. package/dist/hcl/instances.js +390 -0
  110. package/dist/hcl/parse.d.ts +81 -9
  111. package/dist/hcl/parse.d.ts.map +1 -1
  112. package/dist/hcl/parse.js +114 -16
  113. package/dist/hcl/roots.d.ts.map +1 -1
  114. package/dist/hcl/roots.js +5 -0
  115. package/dist/hcl/suppressions.d.ts.map +1 -1
  116. package/dist/hcl/suppressions.js +8 -2
  117. package/dist/index.d.ts +13 -2
  118. package/dist/index.d.ts.map +1 -1
  119. package/dist/index.js +24 -2
  120. package/dist/integrity.json +8 -5
  121. package/dist/lint/audit-catalog.d.ts.map +1 -1
  122. package/dist/lint/audit-catalog.js +27 -3
  123. package/dist/lint/audit-lineage.d.ts +14 -2
  124. package/dist/lint/audit-lineage.d.ts.map +1 -1
  125. package/dist/lint/post-synth/index.d.ts.map +1 -1
  126. package/dist/lint/post-synth/index.js +4 -0
  127. package/dist/lint/post-synth/record-store.d.ts +54 -0
  128. package/dist/lint/post-synth/record-store.d.ts.map +1 -0
  129. package/dist/lint/post-synth/record-store.js +104 -0
  130. package/dist/lint/post-synth/tf001.d.ts +2 -2
  131. package/dist/lint/post-synth/tf001.js +2 -2
  132. package/dist/lint/post-synth/tf024.d.ts +35 -11
  133. package/dist/lint/post-synth/tf024.d.ts.map +1 -1
  134. package/dist/lint/post-synth/tf024.js +92 -20
  135. package/dist/lint/post-synth/tf046.d.ts +64 -0
  136. package/dist/lint/post-synth/tf046.d.ts.map +1 -0
  137. package/dist/lint/post-synth/tf046.js +175 -0
  138. package/dist/lint/post-synth/tf047.d.ts +36 -0
  139. package/dist/lint/post-synth/tf047.d.ts.map +1 -0
  140. package/dist/lint/post-synth/tf047.js +94 -0
  141. package/dist/live-plan-reasons.d.ts +21 -0
  142. package/dist/live-plan-reasons.d.ts.map +1 -0
  143. package/dist/live-plan-reasons.js +47 -0
  144. package/dist/lock-rows.d.ts +36 -0
  145. package/dist/lock-rows.d.ts.map +1 -0
  146. package/dist/lock-rows.js +60 -0
  147. package/dist/lock-timeout.d.ts +22 -0
  148. package/dist/lock-timeout.d.ts.map +1 -0
  149. package/dist/lock-timeout.js +32 -0
  150. package/dist/lsp/option-keys.d.ts.map +1 -1
  151. package/dist/lsp/option-keys.js +2 -0
  152. package/dist/manifest.json +1 -1
  153. package/dist/module-catalog/index.d.ts +69 -0
  154. package/dist/module-catalog/index.d.ts.map +1 -0
  155. package/dist/module-catalog/index.js +110 -0
  156. package/dist/module-catalog/refresh-cli.d.ts +4 -0
  157. package/dist/module-catalog/refresh-cli.d.ts.map +1 -0
  158. package/dist/module-catalog/refresh-cli.js +64 -0
  159. package/dist/module-catalog/static-tier.d.ts +6 -0
  160. package/dist/module-catalog/static-tier.d.ts.map +1 -0
  161. package/dist/module-catalog/static-tier.js +1650 -0
  162. package/dist/module-catalog/types.d.ts +65 -0
  163. package/dist/module-catalog/types.d.ts.map +1 -0
  164. package/dist/module-catalog/types.js +33 -0
  165. package/dist/okf/index.md +3 -1
  166. package/dist/okf/rules/TF024.md +2 -2
  167. package/dist/okf/rules/TF046.md +11 -0
  168. package/dist/okf/rules/TF047.md +11 -0
  169. package/dist/op/activities/index.d.ts +6 -2
  170. package/dist/op/activities/index.d.ts.map +1 -1
  171. package/dist/op/activities/index.js +5 -1
  172. package/dist/op/activities/live-detect.d.ts +4 -3
  173. package/dist/op/activities/live-detect.d.ts.map +1 -1
  174. package/dist/op/activities/live-detect.js +57 -32
  175. package/dist/op/activities/pin-rollout.d.ts +2 -0
  176. package/dist/op/activities/pin-rollout.d.ts.map +1 -1
  177. package/dist/op/activities/pin-rollout.js +1 -0
  178. package/dist/op/activities/terraform.d.ts +182 -5
  179. package/dist/op/activities/terraform.d.ts.map +1 -1
  180. package/dist/op/activities/terraform.js +224 -24
  181. package/dist/op/activity-contracts.d.ts +27 -0
  182. package/dist/op/activity-contracts.d.ts.map +1 -1
  183. package/dist/op/activity-contracts.js +19 -1
  184. package/dist/op/builders.d.ts +7 -1
  185. package/dist/op/builders.d.ts.map +1 -1
  186. package/dist/op/builders.js +9 -0
  187. package/dist/pin/consumers.d.ts +35 -0
  188. package/dist/pin/consumers.d.ts.map +1 -0
  189. package/dist/pin/consumers.js +70 -0
  190. package/dist/pin/edit.d.ts.map +1 -1
  191. package/dist/pin/edit.js +35 -0
  192. package/dist/pin/rollout.d.ts +2 -0
  193. package/dist/pin/rollout.d.ts.map +1 -1
  194. package/dist/pin/rollout.js +14 -11
  195. package/dist/plan-shape.d.ts +30 -0
  196. package/dist/plan-shape.d.ts.map +1 -0
  197. package/dist/plan-shape.js +125 -0
  198. package/dist/plugin.d.ts.map +1 -1
  199. package/dist/plugin.js +37 -18
  200. package/dist/registry/address.d.ts +63 -0
  201. package/dist/registry/address.d.ts.map +1 -0
  202. package/dist/registry/address.js +65 -0
  203. package/dist/registry/catalog-read.d.ts +14 -0
  204. package/dist/registry/catalog-read.d.ts.map +1 -0
  205. package/dist/registry/catalog-read.js +58 -0
  206. package/dist/registry/deprecated.d.ts +37 -0
  207. package/dist/registry/deprecated.d.ts.map +1 -0
  208. package/dist/registry/deprecated.js +43 -0
  209. package/dist/registry/docs.d.ts +96 -0
  210. package/dist/registry/docs.d.ts.map +1 -0
  211. package/dist/registry/docs.js +210 -0
  212. package/dist/registry/edge/edge.d.ts +46 -0
  213. package/dist/registry/edge/edge.d.ts.map +1 -0
  214. package/dist/registry/edge/edge.js +194 -0
  215. package/dist/registry/edge/index.d.ts +11 -0
  216. package/dist/registry/edge/index.d.ts.map +1 -0
  217. package/dist/registry/edge/index.js +10 -0
  218. package/dist/registry/edge/lambda-edge.d.ts +76 -0
  219. package/dist/registry/edge/lambda-edge.d.ts.map +1 -0
  220. package/dist/registry/edge/lambda-edge.js +95 -0
  221. package/dist/registry/edge/sigv4-web.d.ts +33 -0
  222. package/dist/registry/edge/sigv4-web.d.ts.map +1 -0
  223. package/dist/registry/edge/sigv4-web.js +56 -0
  224. package/dist/registry/edge/token-source.d.ts +73 -0
  225. package/dist/registry/edge/token-source.d.ts.map +1 -0
  226. package/dist/registry/edge/token-source.js +115 -0
  227. package/dist/registry/edge/worker.d.ts +45 -0
  228. package/dist/registry/edge/worker.d.ts.map +1 -0
  229. package/dist/registry/edge/worker.js +26 -0
  230. package/dist/registry/handler.d.ts +25 -0
  231. package/dist/registry/handler.d.ts.map +1 -0
  232. package/dist/registry/handler.js +75 -0
  233. package/dist/registry/headers-file.d.ts +21 -0
  234. package/dist/registry/headers-file.d.ts.map +1 -0
  235. package/dist/registry/headers-file.js +57 -0
  236. package/dist/registry/index.d.ts +31 -0
  237. package/dist/registry/index.d.ts.map +1 -0
  238. package/dist/registry/index.js +30 -0
  239. package/dist/registry/mirror-paths.d.ts +9 -0
  240. package/dist/registry/mirror-paths.d.ts.map +1 -0
  241. package/dist/registry/mirror-paths.js +8 -0
  242. package/dist/registry/mirror.d.ts +130 -0
  243. package/dist/registry/mirror.d.ts.map +1 -0
  244. package/dist/registry/mirror.js +319 -0
  245. package/dist/registry/proxies.d.ts +73 -0
  246. package/dist/registry/proxies.d.ts.map +1 -0
  247. package/dist/registry/proxies.js +111 -0
  248. package/dist/registry/reader.d.ts +57 -0
  249. package/dist/registry/reader.d.ts.map +1 -0
  250. package/dist/registry/reader.js +198 -0
  251. package/dist/registry/semver.d.ts +16 -0
  252. package/dist/registry/semver.d.ts.map +1 -0
  253. package/dist/registry/semver.js +24 -0
  254. package/dist/registry/serve.d.ts +24 -0
  255. package/dist/registry/serve.d.ts.map +1 -0
  256. package/dist/registry/serve.js +64 -0
  257. package/dist/registry/source-forms.d.ts +79 -0
  258. package/dist/registry/source-forms.d.ts.map +1 -0
  259. package/dist/registry/source-forms.js +202 -0
  260. package/dist/registry/targets/artifactory.d.ts +28 -0
  261. package/dist/registry/targets/artifactory.d.ts.map +1 -0
  262. package/dist/registry/targets/artifactory.js +206 -0
  263. package/dist/registry/targets/fake-artifactory.d.ts +30 -0
  264. package/dist/registry/targets/fake-artifactory.d.ts.map +1 -0
  265. package/dist/registry/targets/fake-artifactory.js +142 -0
  266. package/dist/registry/targets/fake-gitlab.d.ts +39 -0
  267. package/dist/registry/targets/fake-gitlab.d.ts.map +1 -0
  268. package/dist/registry/targets/fake-gitlab.js +136 -0
  269. package/dist/registry/targets/fake-hcp.d.ts +50 -0
  270. package/dist/registry/targets/fake-hcp.d.ts.map +1 -0
  271. package/dist/registry/targets/fake-hcp.js +202 -0
  272. package/dist/registry/targets/fake-http.d.ts +31 -0
  273. package/dist/registry/targets/fake-http.d.ts.map +1 -0
  274. package/dist/registry/targets/fake-http.js +87 -0
  275. package/dist/registry/targets/fake-nexus.d.ts +27 -0
  276. package/dist/registry/targets/fake-nexus.d.ts.map +1 -0
  277. package/dist/registry/targets/fake-nexus.js +137 -0
  278. package/dist/registry/targets/gitlab.d.ts +53 -0
  279. package/dist/registry/targets/gitlab.d.ts.map +1 -0
  280. package/dist/registry/targets/gitlab.js +187 -0
  281. package/dist/registry/targets/hcp.d.ts +29 -0
  282. package/dist/registry/targets/hcp.d.ts.map +1 -0
  283. package/dist/registry/targets/hcp.js +310 -0
  284. package/dist/registry/targets/hosted.d.ts +62 -0
  285. package/dist/registry/targets/hosted.d.ts.map +1 -0
  286. package/dist/registry/targets/hosted.js +74 -0
  287. package/dist/registry/targets/index.d.ts +18 -0
  288. package/dist/registry/targets/index.d.ts.map +1 -0
  289. package/dist/registry/targets/index.js +17 -0
  290. package/dist/registry/targets/module-target.d.ts +68 -0
  291. package/dist/registry/targets/module-target.d.ts.map +1 -0
  292. package/dist/registry/targets/module-target.js +63 -0
  293. package/dist/registry/targets/nexus.d.ts +43 -0
  294. package/dist/registry/targets/nexus.d.ts.map +1 -0
  295. package/dist/registry/targets/nexus.js +250 -0
  296. package/dist/registry/targets/zip.d.ts +55 -0
  297. package/dist/registry/targets/zip.d.ts.map +1 -0
  298. package/dist/registry/targets/zip.js +278 -0
  299. package/dist/registry/writer.d.ts +101 -0
  300. package/dist/registry/writer.d.ts.map +1 -0
  301. package/dist/registry/writer.js +109 -0
  302. package/dist/report.d.ts +15 -6
  303. package/dist/report.d.ts.map +1 -1
  304. package/dist/report.js +28 -55
  305. package/dist/rules/record-store.ts +122 -0
  306. package/dist/rules/tf001.ts +2 -2
  307. package/dist/rules/tf024.ts +105 -22
  308. package/dist/rules/tf046.ts +204 -0
  309. package/dist/rules/tf047.ts +114 -0
  310. package/dist/skills/chant-terraform.md +1 -1
  311. package/dist/state/azure-blob.d.ts +9 -0
  312. package/dist/state/azure-blob.d.ts.map +1 -0
  313. package/dist/state/azure-blob.js +8 -0
  314. package/dist/state/backend.d.ts +158 -0
  315. package/dist/state/backend.d.ts.map +1 -0
  316. package/dist/state/backend.js +406 -0
  317. package/dist/state/exec.d.ts +39 -0
  318. package/dist/state/exec.d.ts.map +1 -0
  319. package/dist/state/exec.js +80 -0
  320. package/dist/state/gcs.d.ts +8 -0
  321. package/dist/state/gcs.d.ts.map +1 -0
  322. package/dist/state/gcs.js +7 -0
  323. package/dist/state/index.d.ts +15 -0
  324. package/dist/state/index.d.ts.map +1 -0
  325. package/dist/state/index.js +14 -0
  326. package/dist/state/locks.d.ts +105 -0
  327. package/dist/state/locks.d.ts.map +1 -0
  328. package/dist/state/locks.js +130 -0
  329. package/dist/state/object-store.d.ts +8 -0
  330. package/dist/state/object-store.d.ts.map +1 -0
  331. package/dist/state/object-store.js +7 -0
  332. package/dist/state/root-states.d.ts +131 -0
  333. package/dist/state/root-states.d.ts.map +1 -0
  334. package/dist/state/root-states.js +241 -0
  335. package/dist/state/s3.d.ts +21 -0
  336. package/dist/state/s3.d.ts.map +1 -0
  337. package/dist/state/s3.js +19 -0
  338. package/dist/state/store.d.ts +51 -0
  339. package/dist/state/store.d.ts.map +1 -0
  340. package/dist/state/store.js +185 -0
  341. package/dist/state-ops.d.ts +255 -0
  342. package/dist/state-ops.d.ts.map +1 -0
  343. package/dist/state-ops.js +305 -0
  344. package/dist/state-reads.d.ts +57 -0
  345. package/dist/state-reads.d.ts.map +1 -0
  346. package/dist/state-reads.js +104 -0
  347. package/dist/terragrunt/affected.d.ts.map +1 -1
  348. package/dist/terragrunt/affected.js +2 -1
  349. package/dist/used-sources.d.ts +54 -0
  350. package/dist/used-sources.d.ts.map +1 -0
  351. package/dist/used-sources.js +157 -0
  352. package/package.json +24 -4
  353. package/src/__fixtures__/ACCEPTANCE.md +69 -7
  354. package/src/__fixtures__/compose/apply-failed-in-app.jsonl +12 -0
  355. package/src/__fixtures__/compose/state-app.json +66 -0
  356. package/src/__fixtures__/compose/state-edge.json +40 -0
  357. package/src/__fixtures__/compose/state-net.json +58 -0
  358. package/src/__fixtures__/compose/three-app.plan.json +184 -0
  359. package/src/__fixtures__/compose/three-composed.plan.json +509 -0
  360. package/src/__fixtures__/compose/three-edge.plan.json +144 -0
  361. package/src/__fixtures__/compose/three-net.plan.json +174 -0
  362. package/src/__fixtures__/compose/unknown-composed.plan.json +228 -0
  363. package/src/__fixtures__/compose/working-after-apply.json +145 -0
  364. package/src/__fixtures__/config-blocks/app/main.tf +61 -0
  365. package/src/__fixtures__/config-blocks/app/modules/network/main.tf +4 -0
  366. package/src/__fixtures__/config-blocks/app-json/main.tf.json +39 -0
  367. package/src/__fixtures__/config-files/shadow/main.tf +3 -0
  368. package/src/__fixtures__/config-files/shadow/main.tofu +3 -0
  369. package/src/__fixtures__/config-files/shadow/other.tf +3 -0
  370. package/src/__fixtures__/config-files/tf/main.tf +11 -0
  371. package/src/__fixtures__/config-files/tf-json/main.tf.json +18 -0
  372. package/src/__fixtures__/config-files/tofu/main.tofu +11 -0
  373. package/src/__fixtures__/config-files/tofu-json/main.tofu.json +18 -0
  374. package/src/__fixtures__/config-files/tofu-only/main.tofu +3 -0
  375. package/src/__fixtures__/drift-record/README.md +16 -0
  376. package/src/__fixtures__/drift-record/live-plan-sections.json +27 -0
  377. package/src/__fixtures__/drift-rows/live-root-plan.json +70 -0
  378. package/src/__fixtures__/drift-rows/refresh-plan.json +61 -0
  379. package/src/__fixtures__/instance-forms/app/main.tf +110 -0
  380. package/src/__fixtures__/instance-forms/app/modules/svc/main.tf +3 -0
  381. package/src/__fixtures__/instance-vars/app/main.tf +52 -0
  382. package/src/__fixtures__/instance-vars/app/modules/app/main.tf +12 -0
  383. package/src/__fixtures__/instance-vars/app/modules/edge/main.tf +3 -0
  384. package/src/__fixtures__/instance-vars/app/terraform.tfvars +1 -0
  385. package/src/__fixtures__/live-module-keyed/live-graph.json +259 -0
  386. package/src/__fixtures__/live-module-keyed/live-plan.json +87 -0
  387. package/src/__fixtures__/live-module-keyed/root/estate.chdf.hcl +1 -0
  388. package/src/__fixtures__/live-module-keyed/root/main.tf +21 -0
  389. package/src/__fixtures__/live-module-keyed/root/modules/app/main.tf +17 -0
  390. package/src/__fixtures__/live-module-keyed/root/modules/edge/main.tf +7 -0
  391. package/src/__fixtures__/live-module-keyed/root/versions.tf +30 -0
  392. package/src/__fixtures__/live-module-keyed/show-state.json +146 -0
  393. package/src/__fixtures__/pr-loop/README.md +50 -0
  394. package/src/__fixtures__/pr-loop/chant.config.json +13 -0
  395. package/src/__fixtures__/pr-loop/estate.component.ts +10 -0
  396. package/src/__fixtures__/pr-loop/roots/a/main.tf +11 -0
  397. package/src/__fixtures__/pr-loop/roots/app/main.tf +19 -0
  398. package/src/__fixtures__/pr-loop/roots/b/main.tf +7 -0
  399. package/src/__fixtures__/pr-loop/roots/dns/main.tf +3 -0
  400. package/src/__fixtures__/pr-loop/roots/net/main.tf +7 -0
  401. package/src/__fixtures__/report/applied.show.json +51 -0
  402. package/src/__fixtures__/report/generate.ts +63 -0
  403. package/src/__fixtures__/report/plan.show.json +200 -0
  404. package/src/__fixtures__/report/tf-apply/change-set.json +170 -0
  405. package/src/__fixtures__/report/tf-apply/report.json +338 -0
  406. package/src/__fixtures__/report/tf-plan/change-set.json +170 -0
  407. package/src/__fixtures__/report/tf-plan/report.json +320 -0
  408. package/src/__fixtures__/three-cloud-azure.graph.json +290 -0
  409. package/src/__fixtures__/used-sources/root/extra.tofu +1 -0
  410. package/src/__fixtures__/used-sources/root/main.tf +31 -0
  411. package/src/__fixtures__/used-sources/root/modules/net/main.tf +14 -0
  412. package/src/__fixtures__/used-sources/root/modules/net/versions.tf.json +7 -0
  413. package/src/__fixtures__/used-sources/unit/terragrunt.hcl +3 -0
  414. package/src/applied-state.ts +52 -0
  415. package/src/backend-config.test.ts +105 -0
  416. package/src/backend-config.ts +133 -0
  417. package/src/behaviour/kinds.ts +4 -0
  418. package/src/behaviour/predict.ts +5 -2
  419. package/src/behaviour/reference-catalog.plugin.test.ts +65 -0
  420. package/src/behaviour/reference-catalog.ts +16 -5
  421. package/src/behaviour/request.ts +3 -2
  422. package/src/change-set-instances.test.ts +98 -0
  423. package/src/changed-roots.ts +14 -16
  424. package/src/ci-setup.ts +42 -0
  425. package/src/commands.ts +4 -3
  426. package/src/components/capability-plugin.test.ts +198 -19
  427. package/src/components/capability-plugin.ts +383 -35
  428. package/src/components/change-set-graph.e2e.test.ts +11 -5
  429. package/src/components/compose-group.test.ts +486 -0
  430. package/src/components/compose-group.ts +489 -0
  431. package/src/components/demo-composed-roots.e2e.test.ts +440 -0
  432. package/src/components/member-outputs.test.ts +76 -0
  433. package/src/components/placeholder-preview.test.ts +147 -0
  434. package/src/components/placeholder-preview.ts +257 -0
  435. package/src/components/pr-loop-compose.tofu.e2e.test.ts +363 -0
  436. package/src/components/pr-loop.tofu.test.ts +113 -57
  437. package/src/components/release-inputs.ts +69 -0
  438. package/src/components/trace-facts.test.ts +21 -0
  439. package/src/components/trace-facts.ts +27 -0
  440. package/src/compose/apply-events.test.ts +62 -0
  441. package/src/compose/apply-events.ts +100 -0
  442. package/src/compose/estate.choudoufu.e2e.test.ts +112 -0
  443. package/src/compose/estate.test.ts +421 -0
  444. package/src/compose/estate.ts +443 -0
  445. package/src/compose/hcl.ts +311 -0
  446. package/src/compose/module-address.ts +49 -0
  447. package/src/compose/root.test.ts +416 -0
  448. package/src/compose/root.ts +592 -0
  449. package/src/compose/run.test.ts +440 -0
  450. package/src/compose/run.tofu.test.ts +176 -0
  451. package/src/compose/run.ts +527 -0
  452. package/src/compose/slice.test.ts +184 -0
  453. package/src/compose/slice.ts +290 -0
  454. package/src/compose/state-address.ts +157 -0
  455. package/src/compose/state-merge.test.ts +131 -0
  456. package/src/compose/state-merge.ts +148 -0
  457. package/src/compose.test.ts +248 -0
  458. package/src/compose.ts +627 -0
  459. package/src/composites/terraform-apply-op.test.ts +9 -0
  460. package/src/composites/terraform-apply-op.ts +7 -3
  461. package/src/cone.ts +138 -0
  462. package/src/config-file-listers.test.ts +265 -0
  463. package/src/config.ts +44 -9
  464. package/src/cost-hold.ts +29 -0
  465. package/src/describe-resources.engine-facts.test.ts +411 -0
  466. package/src/describe-resources.live-module-keyed.test.ts +290 -0
  467. package/src/describe-resources.ts +416 -70
  468. package/src/drift-read.ts +284 -0
  469. package/src/drift-record.test.ts +223 -0
  470. package/src/drift-record.ts +455 -0
  471. package/src/drift-rows.test.ts +135 -0
  472. package/src/drift-rows.ts +137 -0
  473. package/src/exit-codes.test.ts +23 -0
  474. package/src/exit-codes.ts +59 -0
  475. package/src/hcl/config-blocks.test.ts +174 -0
  476. package/src/hcl/edges.ts +21 -1
  477. package/src/hcl/files.ts +234 -0
  478. package/src/hcl/instance-forms.test.ts +94 -0
  479. package/src/hcl/instances.test.ts +137 -0
  480. package/src/hcl/instances.ts +422 -0
  481. package/src/hcl/parse.test.ts +139 -1
  482. package/src/hcl/parse.ts +149 -16
  483. package/src/hcl/roots.ts +6 -0
  484. package/src/hcl/suppressions.ts +8 -2
  485. package/src/index.ts +79 -0
  486. package/src/lint/audit-catalog.ts +29 -3
  487. package/src/lint/audit-lineage.ts +14 -2
  488. package/src/lint/post-synth/fixtures/TF024/live-with-record-store/main.tf +18 -0
  489. package/src/lint/post-synth/fixtures/TF024/sidecar-with-backend/estate.chdf.hcl +1 -0
  490. package/src/lint/post-synth/fixtures/TF024/sidecar-with-backend/main.tf +10 -0
  491. package/src/lint/post-synth/fixtures/TF046/azurerm-no-resource-group/main.tf +15 -0
  492. package/src/lint/post-synth/fixtures/TF046/backend-config-only/main.tf +14 -0
  493. package/src/lint/post-synth/fixtures/TF046/derived-s3/backend.tf +7 -0
  494. package/src/lint/post-synth/fixtures/TF046/derived-s3/main.tf +9 -0
  495. package/src/lint/post-synth/fixtures/TF046/explicit-local/main.tf +16 -0
  496. package/src/lint/post-synth/fixtures/TF046/explicit-s3-beside-local-backend/main.tf +15 -0
  497. package/src/lint/post-synth/fixtures/TF046/implied-local/main.tf +9 -0
  498. package/src/lint/post-synth/fixtures/TF046/sidecar-implied/estate.chdf.hcl +1 -0
  499. package/src/lint/post-synth/fixtures/TF046/sidecar-implied/main.tf +5 -0
  500. package/src/lint/post-synth/fixtures/TF047/clear/main.tf +12 -0
  501. package/src/lint/post-synth/fixtures/TF047/refused/main.tf +12 -0
  502. package/src/lint/post-synth/fixtures/TF047/unknown-module/main.tf +12 -0
  503. package/src/lint/post-synth/fixtures/TF047/unknown-version/main.tf +12 -0
  504. package/src/lint/post-synth/index.ts +4 -0
  505. package/src/lint/post-synth/record-store.ts +122 -0
  506. package/src/lint/post-synth/tf001.ts +2 -2
  507. package/src/lint/post-synth/tf024.test.ts +26 -5
  508. package/src/lint/post-synth/tf024.ts +105 -22
  509. package/src/lint/post-synth/tf046.test.ts +137 -0
  510. package/src/lint/post-synth/tf046.ts +204 -0
  511. package/src/lint/post-synth/tf047.test.ts +56 -0
  512. package/src/lint/post-synth/tf047.ts +114 -0
  513. package/src/live-plan-reasons.ts +54 -0
  514. package/src/lock-rows.test.ts +70 -0
  515. package/src/lock-rows.ts +82 -0
  516. package/src/lock-timeout.ts +34 -0
  517. package/src/lsp/completions.test.ts +1 -1
  518. package/src/lsp/option-keys.ts +2 -0
  519. package/src/module-catalog/index.test.ts +93 -0
  520. package/src/module-catalog/index.ts +159 -0
  521. package/src/module-catalog/refresh-cli.ts +68 -0
  522. package/src/module-catalog/static-tier.ts +1659 -0
  523. package/src/module-catalog/types.ts +88 -0
  524. package/src/op/activities/choudoufu-plan-set-op.test.ts +111 -0
  525. package/src/op/activities/index.ts +9 -0
  526. package/src/op/activities/live-detect.ts +55 -26
  527. package/src/op/activities/pin-rollout.ts +3 -0
  528. package/src/op/activities/registry.test.ts +2 -1
  529. package/src/op/activities/terraform.test.ts +55 -0
  530. package/src/op/activities/terraform.ts +312 -27
  531. package/src/op/activity-contracts.test.ts +1 -0
  532. package/src/op/activity-contracts.ts +24 -1
  533. package/src/op/builders.ts +14 -0
  534. package/src/op-wave-step.test.ts +109 -0
  535. package/src/pin/__fixtures__/consumers/live/dev/main.tf +4 -0
  536. package/src/pin/__fixtures__/consumers/live/prod/main.tf +21 -0
  537. package/src/pin/__fixtures__/consumers/units/vpc/terragrunt.hcl +7 -0
  538. package/src/pin/bundle.test.ts +2 -0
  539. package/src/pin/consumers.test.ts +57 -0
  540. package/src/pin/consumers.ts +95 -0
  541. package/src/pin/edit.ts +30 -0
  542. package/src/pin/rollout.ts +16 -11
  543. package/src/plan-shape.test.ts +148 -0
  544. package/src/plan-shape.ts +128 -0
  545. package/src/plugin.ts +39 -19
  546. package/src/registry/__fixtures__/docs/documented/.terraform-docs.yml +3 -0
  547. package/src/registry/__fixtures__/docs/documented/README.md +1 -0
  548. package/src/registry/__fixtures__/docs/documented/main.tf +8 -0
  549. package/src/registry/__fixtures__/docs/json/main.tf.json +14 -0
  550. package/src/registry/__fixtures__/docs/vpc/README.md +3 -0
  551. package/src/registry/__fixtures__/docs/vpc/examples/complete/main.tf +5 -0
  552. package/src/registry/__fixtures__/docs/vpc/examples/simple/README.md +1 -0
  553. package/src/registry/__fixtures__/docs/vpc/examples/simple/main.tf +4 -0
  554. package/src/registry/__fixtures__/docs/vpc/main.tf +14 -0
  555. package/src/registry/__fixtures__/docs/vpc/outputs.tf +9 -0
  556. package/src/registry/__fixtures__/docs/vpc/variables.tf +16 -0
  557. package/src/registry/__fixtures__/proxied-root/main.tf +12 -0
  558. package/src/registry/address.ts +100 -0
  559. package/src/registry/catalog-read.ts +56 -0
  560. package/src/registry/deprecated.test.ts +139 -0
  561. package/src/registry/deprecated.ts +76 -0
  562. package/src/registry/docs.test.ts +140 -0
  563. package/src/registry/docs.ts +299 -0
  564. package/src/registry/edge/bundle.test.ts +50 -0
  565. package/src/registry/edge/edge.test.ts +176 -0
  566. package/src/registry/edge/edge.ts +211 -0
  567. package/src/registry/edge/index.ts +10 -0
  568. package/src/registry/edge/lambda-edge.ts +127 -0
  569. package/src/registry/edge/sigv4-web.ts +79 -0
  570. package/src/registry/edge/token-source.test.ts +258 -0
  571. package/src/registry/edge/token-source.ts +169 -0
  572. package/src/registry/edge/worker.ts +61 -0
  573. package/src/registry/handler.ts +89 -0
  574. package/src/registry/headers-file.ts +61 -0
  575. package/src/registry/index.ts +31 -0
  576. package/src/registry/mirror-paths.ts +10 -0
  577. package/src/registry/mirror.test.ts +198 -0
  578. package/src/registry/mirror.ts +395 -0
  579. package/src/registry/proxies.test.ts +153 -0
  580. package/src/registry/proxies.ts +132 -0
  581. package/src/registry/reader.ts +225 -0
  582. package/src/registry/registry.test.ts +262 -0
  583. package/src/registry/semver.ts +34 -0
  584. package/src/registry/serve.ts +82 -0
  585. package/src/registry/source-forms.test.ts +87 -0
  586. package/src/registry/source-forms.ts +216 -0
  587. package/src/registry/targets/artifactory.test.ts +248 -0
  588. package/src/registry/targets/artifactory.ts +257 -0
  589. package/src/registry/targets/fake-artifactory.ts +158 -0
  590. package/src/registry/targets/fake-gitlab.ts +161 -0
  591. package/src/registry/targets/fake-hcp.ts +233 -0
  592. package/src/registry/targets/fake-http.ts +97 -0
  593. package/src/registry/targets/fake-nexus.ts +152 -0
  594. package/src/registry/targets/gitlab.test.ts +208 -0
  595. package/src/registry/targets/gitlab.ts +223 -0
  596. package/src/registry/targets/hcp.test.ts +230 -0
  597. package/src/registry/targets/hcp.ts +360 -0
  598. package/src/registry/targets/hosted.ts +117 -0
  599. package/src/registry/targets/index.ts +17 -0
  600. package/src/registry/targets/module-target.ts +131 -0
  601. package/src/registry/targets/nexus.test.ts +220 -0
  602. package/src/registry/targets/nexus.ts +314 -0
  603. package/src/registry/targets/zip.test.ts +65 -0
  604. package/src/registry/targets/zip.ts +287 -0
  605. package/src/registry/writer.ts +155 -0
  606. package/src/report.test.ts +82 -1
  607. package/src/report.ts +31 -54
  608. package/src/signal-only-root.test.ts +72 -0
  609. package/src/skills/chant-terraform.md +1 -1
  610. package/src/state/azure-blob.ts +8 -0
  611. package/src/state/backend-verdict.test.ts +187 -0
  612. package/src/state/backend.test.ts +127 -0
  613. package/src/state/backend.ts +488 -0
  614. package/src/state/exec.ts +111 -0
  615. package/src/state/gcs.ts +7 -0
  616. package/src/state/index.ts +15 -0
  617. package/src/state/locks.test.ts +84 -0
  618. package/src/state/locks.ts +201 -0
  619. package/src/state/object-store.ts +7 -0
  620. package/src/state/root-states-floci.e2e.test.ts +66 -0
  621. package/src/state/root-states.test.ts +462 -0
  622. package/src/state/root-states.ts +309 -0
  623. package/src/state/s3.ts +21 -0
  624. package/src/state/store.ts +225 -0
  625. package/src/state-ops.test.ts +182 -0
  626. package/src/state-ops.ts +462 -0
  627. package/src/state-reads.ts +116 -0
  628. package/src/terragrunt/affected.ts +2 -1
  629. package/src/used-sources.test.ts +75 -0
  630. package/src/used-sources.ts +204 -0
  631. package/src/workspace-kinds.test.ts +4 -17
  632. package/workspace-kinds.json +1 -24
  633. package/dist/workspace-member.d.ts +0 -30
  634. package/dist/workspace-member.d.ts.map +0 -1
  635. package/dist/workspace-member.js +0 -91
  636. package/schemas/pulumi.schema.json +0 -34
  637. package/src/testdata/pulumi-member/Pulumi.yaml +0 -2
  638. package/src/testdata/pulumi-member/stack-export.json +0 -53
  639. package/src/testdata/pulumi-unexported/Pulumi.yaml +0 -2
  640. package/src/workspace-member.test.ts +0 -89
  641. package/src/workspace-member.ts +0 -126
@@ -0,0 +1,440 @@
1
+ /**
2
+ * `examples/demo-composed-roots` through the pull-request loop (#4448): both
3
+ * scenarios, each from a fresh base, through `chant components pr-plan`, one
4
+ * approval and `chant components pr-apply`, with `terraform.compose` on.
5
+ *
6
+ * - The edit act (a port changes, an admin port appears): one group of three,
7
+ * every value known at plan, one approval, pr-apply exits 0.
8
+ * - The create act (a subnet, a worker in it, edge recording the worker's
9
+ * subnet): the group's plan shows the subnet id as known after apply in app
10
+ * and edge, one approval, pr-apply exits 0 with no round, and the worker
11
+ * holds the real subnet id after the apply.
12
+ *
13
+ * Twice: on `tofu` with each root's state on local disk, against a floci at
14
+ * CHANT_FLOCI_ENDPOINT (for example `docker run -d -p 4611:4566 floci/floci`
15
+ * and `CHANT_FLOCI_ENDPOINT=http://localhost:4611`), and on `choudoufu`
16
+ * estates against CHOUDOUFU_EMULATOR_ENDPOINT, as
17
+ * `../compose/estate.choudoufu.e2e.test.ts` is gated. For estates each root
18
+ * gets a `live` block, each variable becomes a read of the same output
19
+ * through `terraform_estate_outputs`, and the components lose their `vars`:
20
+ * an estate's module call takes no inputs (`writeEstateRoot`). Core joins
21
+ * the three into a group by the estate reads `terraform-apply` reports
22
+ * (`groupReads`, #4447).
23
+ *
24
+ * Both need the `aws` CLI to read the emulator after the apply, and the
25
+ * hashicorp/aws provider from the registry (CHANT_OFFLINE skips). Each suite
26
+ * skips with the reason named when something is missing.
27
+ *
28
+ * The choudoufu suite reads:
29
+ *
30
+ * - CHOUDOUFU_BIN: the choudoufu binary to run, a path or a name on PATH.
31
+ * Unset, it is the `choudoufu` on PATH. When set, the test puts a
32
+ * `choudoufu` link to it first on its own PATH, so the roots (whose config
33
+ * says `binary: "choudoufu"`) and the version and commit checks below all
34
+ * run that binary.
35
+ * - CHOUDOUFU_EMULATOR_ENDPOINT: the AWS emulator the estates apply to and the
36
+ * `aws` CLI reads back, for example the floci that choudoufu's `just smoke`
37
+ * brings up (`http://localhost:4566`). Unset, the suite skips.
38
+ * - CHOUDOUFU_REPO: a choudoufu checkout holding both the binary's commit and
39
+ * choudoufu#2261's fix, so `git merge-base` can tell whether the build
40
+ * carries it (see CHOUDOUFU_SSM_UPDATE_FIX). Default `../choudoufu` beside
41
+ * this repo. When it cannot answer, the suite skips and says so.
42
+ *
43
+ * For example, from the chant repo:
44
+ *
45
+ * CHOUDOUFU_BIN=$HOME/src/choudoufu/bin/choudoufu \
46
+ * CHOUDOUFU_EMULATOR_ENDPOINT=http://localhost:4566 \
47
+ * CHOUDOUFU_REPO=$HOME/src/choudoufu \
48
+ * npx vitest run --project e2e lexicons/terraform/src/components/demo-composed-roots.e2e.test.ts
49
+ */
50
+
51
+ import { execFileSync } from "node:child_process";
52
+ import { lookup } from "node:dns/promises";
53
+ import { accessSync, constants, existsSync, mkdtempSync, readFileSync, readdirSync, rmSync, symlinkSync, writeFileSync } from "node:fs";
54
+ import { tmpdir } from "node:os";
55
+ import { delimiter, isAbsolute, join, resolve } from "node:path";
56
+ import { afterAll, beforeAll, describe, expect, test } from "vitest";
57
+ import type { PrMember, PrReport } from "@intentius/chant/pr-loop";
58
+ import { openScenarioLoop, type ScenarioLoop } from "@intentius/chant-test-utils/pr-loop-harness";
59
+ import { terraformPlugin } from "../plugin";
60
+ import { isOlderVersion, MIN_CHOUDOUFU_ESTATES_VERSION, parseChoudoufuVersion } from "../op/activities/terraform";
61
+
62
+ const REPO = join(import.meta.dirname, "..", "..", "..", "..");
63
+ const EXAMPLE = join(REPO, "examples", "demo-composed-roots");
64
+ const TIMEOUT = 900_000;
65
+
66
+ function runs(bin: string, args: string[]): string | undefined {
67
+ try {
68
+ return execFileSync(bin, args, { encoding: "utf8", stdio: ["ignore", "pipe", "ignore"] });
69
+ } catch {
70
+ return undefined;
71
+ }
72
+ }
73
+
74
+ /** The registry has to answer for the provider download; a DNS answer is enough of a probe. */
75
+ async function resolvable(host: string): Promise<boolean> {
76
+ try {
77
+ await Promise.race([lookup(host), new Promise((_r, reject) => setTimeout(() => reject(new Error("dns timeout")), 3000))]);
78
+ return true;
79
+ } catch {
80
+ return false;
81
+ }
82
+ }
83
+
84
+ const offline = process.env.CHANT_OFFLINE ? "CHANT_OFFLINE is set, and the roots download hashicorp/aws" : undefined;
85
+ const awsCli = runs("aws", ["--version"]) === undefined ? "the aws CLI is not on PATH" : undefined;
86
+ const floci = process.env.CHANT_FLOCI_ENDPOINT?.replace(/\/+$/, "");
87
+ const emulator = process.env.CHOUDOUFU_EMULATOR_ENDPOINT?.replace(/\/+$/, "");
88
+
89
+ /**
90
+ * The choudoufu binary the suite runs: CHOUDOUFU_BIN (a path, or a name on
91
+ * PATH) when set, else `choudoufu` on PATH. A set CHOUDOUFU_BIN is linked as
92
+ * `choudoufu` in a scratch directory put first on this process's PATH, which
93
+ * the harness's commands inherit, so every `choudoufu` the roots run is it.
94
+ * `error` says why a set CHOUDOUFU_BIN cannot be used.
95
+ */
96
+ function choudoufuBinary(): { bin?: string; error?: string } {
97
+ const named = process.env.CHOUDOUFU_BIN?.trim();
98
+ if (!named) return { bin: runs("which", ["choudoufu"])?.trim() || undefined };
99
+ const bin = named.includes("/") ? (isAbsolute(named) ? named : resolve(named)) : runs("which", [named])?.trim();
100
+ try {
101
+ if (!bin) throw new Error("not found");
102
+ accessSync(bin, constants.X_OK);
103
+ } catch {
104
+ return { error: `CHOUDOUFU_BIN=${named} is not an executable file` };
105
+ }
106
+ const linkDir = mkdtempSync(join(tmpdir(), "chant-choudoufu-bin-"));
107
+ symlinkSync(bin, join(linkDir, "choudoufu"));
108
+ process.env.PATH = `${linkDir}${delimiter}${process.env.PATH ?? ""}`;
109
+ process.on("exit", () => rmSync(linkDir, { recursive: true, force: true }));
110
+ return { bin };
111
+ }
112
+
113
+ const choudoufu = choudoufuBinary();
114
+
115
+ const tofuMissing =
116
+ runs("tofu", ["version"]) === undefined
117
+ ? "tofu is not on PATH"
118
+ : !floci
119
+ ? "CHANT_FLOCI_ENDPOINT is not set (for example `docker run -d -p 4611:4566 floci/floci` and CHANT_FLOCI_ENDPOINT=http://localhost:4611)"
120
+ : (awsCli ?? offline ?? ((await resolvable("registry.opentofu.org")) ? undefined : "registry.opentofu.org does not resolve"));
121
+
122
+ /**
123
+ * The choudoufu commit that fixes an in-place `aws_ssm_parameter` update
124
+ * under a `live` block (choudoufu#2261, chant#4470): before it, the plan
125
+ * carried `insecure_value` as known after apply and the apply sent no
126
+ * `PutParameter`. It is on choudoufu's integration/engine-ship (merge
127
+ * de9bdaa0bb) and in no release yet, so a version floor would refuse every
128
+ * build that has it. The check is the build's commit instead: the
129
+ * `vcs.revision` Go stamps into the binary, which has to descend from this
130
+ * one in a choudoufu checkout (CHOUDOUFU_REPO, or `../choudoufu` beside this
131
+ * repo). When a release carries the fix, a release build at or above it can
132
+ * pass on its version alone.
133
+ */
134
+ const CHOUDOUFU_SSM_UPDATE_FIX = "9edd589376223f07642ee6bdf20d31cc56ba76cf";
135
+
136
+ /** The commit a Go binary was built at, from its embedded build info: `go version -m` when go is on PATH, else the bytes themselves. */
137
+ function goBuildRevision(bin: string): string | undefined {
138
+ const rev = /^\s*build\s+vcs\.revision=([0-9a-f]{40})\s*$/m;
139
+ const fromGo = runs("go", ["version", "-m", bin]);
140
+ if (fromGo !== undefined) return rev.exec(fromGo)?.[1];
141
+ try {
142
+ return /\tvcs\.revision=([0-9a-f]{40})\n/.exec(readFileSync(bin).toString("latin1"))?.[1];
143
+ } catch {
144
+ return undefined;
145
+ }
146
+ }
147
+
148
+ /** Why this choudoufu lacks {@link CHOUDOUFU_SSM_UPDATE_FIX}, or cannot be shown to have it. `undefined` when it has it. */
149
+ function choudoufuLacksSsmUpdateFix(): string | undefined {
150
+ const bin = choudoufu.bin;
151
+ const rev = bin ? goBuildRevision(bin) : undefined;
152
+ const fix = `choudoufu#2261's fix (${CHOUDOUFU_SSM_UPDATE_FIX.slice(0, 10)})`;
153
+ if (!rev) return `the choudoufu binary names no vcs.revision, so it cannot be shown to carry ${fix}; build it from a git checkout`;
154
+ const repo = process.env.CHOUDOUFU_REPO ?? join(REPO, "..", "choudoufu");
155
+ try {
156
+ execFileSync("git", ["-C", repo, "merge-base", "--is-ancestor", CHOUDOUFU_SSM_UPDATE_FIX, rev], { stdio: "ignore" });
157
+ return undefined;
158
+ } catch (e) {
159
+ if ((e as { status?: number }).status === 1) return `choudoufu at ${rev.slice(0, 10)} does not carry ${fix}`;
160
+ return `cannot tell whether choudoufu at ${rev.slice(0, 10)} carries ${fix}: ${repo} is not a choudoufu checkout holding both commits (set CHOUDOUFU_REPO, and fetch)`;
161
+ }
162
+ }
163
+
164
+ const choudoufuOut = choudoufu.bin ? runs(choudoufu.bin, ["version"]) : undefined;
165
+ const choudoufuVersion = choudoufuOut === undefined ? undefined : (parseChoudoufuVersion(choudoufuOut) ?? "dev");
166
+ const choudoufuMissing =
167
+ choudoufu.error ??
168
+ (choudoufuVersion === undefined
169
+ ? process.env.CHOUDOUFU_BIN
170
+ ? `CHOUDOUFU_BIN=${process.env.CHOUDOUFU_BIN} does not answer \`version\``
171
+ : "choudoufu is not on PATH (or set CHOUDOUFU_BIN)"
172
+ : choudoufuVersion !== "dev" && isOlderVersion(choudoufuVersion, MIN_CHOUDOUFU_ESTATES_VERSION)
173
+ ? `choudoufu ${choudoufuVersion} is older than v${MIN_CHOUDOUFU_ESTATES_VERSION}`
174
+ : (choudoufuLacksSsmUpdateFix() ?? (!emulator
175
+ ? "CHOUDOUFU_EMULATOR_ENDPOINT is not set (bring up choudoufu's `just smoke` emulator stack and export it)"
176
+ : (awsCli ?? offline ?? ((await resolvable("registry.terraform.io")) ? undefined : "registry.terraform.io does not resolve")))));
177
+
178
+ interface Mode {
179
+ name: "tofu" | "choudoufu";
180
+ missing: string | undefined;
181
+ endpoint: string | undefined;
182
+ estate: boolean;
183
+ }
184
+
185
+ const MODES: Mode[] = [
186
+ { name: "tofu", missing: tofuMissing, endpoint: floci, estate: false },
187
+ { name: "choudoufu", missing: choudoufuMissing, endpoint: emulator, estate: true },
188
+ ];
189
+
190
+ /** The root each root's variables read, by root: every variable is that root's output of the same name. */
191
+ const UPSTREAM: Record<string, string> = { app: "network", edge: "app" };
192
+ const ROOTS = ["network", "app", "edge"];
193
+
194
+ /** A root's variables of the form the example writes them. */
195
+ const VARIABLE = /variable "(\w+)" \{\n type = string\n\}/g;
196
+
197
+ /**
198
+ * The root as an estate's: each variable read through `terraform_estate_outputs`
199
+ * of its upstream's estate instead, and no longer declared.
200
+ */
201
+ function estateRoot(id: string, root: string, text: string): string {
202
+ const up = UPSTREAM[root];
203
+ if (!up) return text;
204
+ const names = [...text.matchAll(VARIABLE)].map((m) => m[1]!);
205
+ let out = text.replace(new RegExp(`${VARIABLE.source}\\n*`, "g"), "");
206
+ for (const n of names) out = out.replaceAll(`var.${n}`, `data.terraform_estate_outputs.${up}.values.${n}`);
207
+ const list = names.map((n) => `"${n}"`).join(", ");
208
+ return `${out}\ndata "terraform_estate_outputs" "${up}" {\n estate = "${id}-${up}"\n names = [${list}]\n}\n`;
209
+ }
210
+
211
+ /** A component's step `vars`, which an estate's root no longer declares. */
212
+ const VARS = /^[ \t]*vars: \{\n[\s\S]*?^[ \t]*\},\n/m;
213
+
214
+ /** Every copied file with the example's names made this run's own, and for estates the roots, the components and the config rewritten. */
215
+ function rewrite(mode: Mode, id: string) {
216
+ return (rel: string, text: string): string => {
217
+ let out = text.replaceAll("chant-compose", id);
218
+ if (mode.estate && rel === "chant.config.ts") out = out.replace('binary: "tofu"', 'binary: "choudoufu"');
219
+ const root = rel.match(/^roots\/(\w+)\/main\.tf$/)?.[1];
220
+ if (mode.estate && root) out = estateRoot(id, root, out);
221
+ if (mode.estate && rel.endsWith(".component.ts")) out = out.replace(VARS, "");
222
+ return out;
223
+ };
224
+ }
225
+
226
+ /**
227
+ * For estates, each root's `live` block, in a file of its own, with one record
228
+ * store for the three. The base applies root by root, so app reads network's
229
+ * outputs from the records network's apply wrote, and an estate reads another's
230
+ * outputs from its own store. The default `local` store is `.tofu-records`
231
+ * beside each root, so the three share an `s3` store instead: a bucket on the
232
+ * emulator, with the versioning and lifecycle rule choudoufu asks of one.
233
+ */
234
+ function prepare(mode: Mode, id: string) {
235
+ const bucket = `${id}-records`;
236
+ let made = false;
237
+ return (dir: string): void => {
238
+ if (!mode.estate) return;
239
+ if (!made) {
240
+ // Before the harness sets the run's environment: the credentials are given here.
241
+ const env = { ...process.env, AWS_ACCESS_KEY_ID: process.env.AWS_ACCESS_KEY_ID ?? "test", AWS_SECRET_ACCESS_KEY: process.env.AWS_SECRET_ACCESS_KEY ?? "test", AWS_DEFAULT_REGION: "us-east-1" };
242
+ const s3api = (...a: string[]) => execFileSync("aws", ["--endpoint-url", mode.endpoint!, "s3api", ...a, "--bucket", bucket], { env, stdio: "ignore" });
243
+ s3api("create-bucket");
244
+ s3api("put-bucket-versioning", "--versioning-configuration", "Status=Enabled");
245
+ const rule = { Rules: [{ ID: "records", Status: "Enabled", Filter: {}, NoncurrentVersionExpiration: { NoncurrentDays: 30 } }] };
246
+ s3api("put-bucket-lifecycle-configuration", "--lifecycle-configuration", JSON.stringify(rule));
247
+ made = true;
248
+ }
249
+ const store = ` record_store "s3" {\n bucket = "${bucket}"\n region = "us-east-1"\n }\n`;
250
+ for (const r of ROOTS) writeFileSync(join(dir, "roots", r, "live.tf"), `terraform {\n live {\n estate = "${id}-${r}"\n${store} }\n}\n`);
251
+ };
252
+ }
253
+
254
+ function awsJson(endpoint: string, ...args: string[]): unknown {
255
+ return JSON.parse(execFileSync("aws", ["--endpoint-url", endpoint, "--output", "json", ...args], { encoding: "utf8" }));
256
+ }
257
+
258
+ const ssm = (endpoint: string, name: string): string =>
259
+ (awsJson(endpoint, "ssm", "get-parameter", "--name", name) as { Parameter: { Value: string } }).Parameter.Value;
260
+
261
+ const byComponent = (r: PrReport): Record<string, PrMember> => Object.fromEntries(r.members.map((m) => [m.component, m]));
262
+ const counts = (create: number, update: number) => ({ create, update, replace: 0, delete: 0 });
263
+
264
+ /** What the plan does to a parameter's `insecure_value`: the action, and the attribute's before and after (or that it is unknown). */
265
+ function insecureValue(r: PrReport, resource: string): { action: string; before?: unknown; after?: unknown; unknown?: true } | undefined {
266
+ const e = r.changeSet.entries.find((x) => x.address === resource || x.address.endsWith(`.${resource}`));
267
+ if (!e) return undefined;
268
+ const a = e.attributes.find((x) => x.path === "insecure_value");
269
+ return { action: e.action, before: a?.before, after: a?.after, ...(a?.unknown ? { unknown: a.unknown } : {}) };
270
+ }
271
+
272
+ /** Paths a composed run must not leave in the checkout. */
273
+ function leftovers(dir: string): string[] {
274
+ const out: string[] = [];
275
+ if (existsSync(join(dir, ".chant-compose"))) out.push(".chant-compose");
276
+ for (const r of ROOTS) for (const f of readdirSync(join(dir, "roots", r))) if (f.endsWith(".tflock") || f.endsWith(".chant-unwritten")) out.push(`roots/${r}/${f}`);
277
+ return out;
278
+ }
279
+
280
+ function actSuite(mode: Mode, scenario: string, tests: (ctx: { loop: () => ScenarioLoop; id: string; endpoint: string }) => void): void {
281
+ describe.skipIf(mode.missing !== undefined)(`demo-composed-roots, ${scenario}, on ${mode.name}${mode.missing ? `: skipped, ${mode.missing}` : ""}`, () => {
282
+ const id = `cc-${mode.name}-${scenario.replace("chant-compose-", "")}-${Date.now().toString(36)}`;
283
+ const endpoint = mode.endpoint!;
284
+ let loop: ScenarioLoop | undefined;
285
+
286
+ beforeAll(async () => {
287
+ loop = await openScenarioLoop({
288
+ example: EXAMPLE,
289
+ repo: REPO,
290
+ plugins: [terraformPlugin],
291
+ env: "demo",
292
+ rewrite: rewrite(mode, id),
293
+ prepare: prepare(mode, id),
294
+ processEnv: {
295
+ AWS_ENDPOINT_URL: endpoint,
296
+ AWS_ACCESS_KEY_ID: process.env.AWS_ACCESS_KEY_ID ?? "test",
297
+ AWS_SECRET_ACCESS_KEY: process.env.AWS_SECRET_ACCESS_KEY ?? "test",
298
+ AWS_REGION: "us-east-1",
299
+ AWS_DEFAULT_REGION: "us-east-1",
300
+ },
301
+ });
302
+ // The base, applied root by root as the demo host applies it (a fan-out never composes).
303
+ expect(await loop.applyBase()).toBe(0);
304
+ }, TIMEOUT);
305
+
306
+ afterAll(async () => {
307
+ await loop?.close();
308
+ });
309
+
310
+ tests({ loop: () => loop!, id, endpoint });
311
+ });
312
+ }
313
+
314
+ for (const mode of MODES) {
315
+ actSuite(mode, "chant-compose-edit", ({ loop, id, endpoint }) => {
316
+ test("one pr-plan digest over a group of three, every value known at plan", async () => {
317
+ const { code, report: plan } = await loop().open(1, "chant-compose-edit");
318
+ expect(code).toBe(0);
319
+ expect(plan.status).toBe("planned");
320
+ expect([...plan.selection.changed].sort()).toEqual(["app", "edge", "network"]);
321
+ expect(plan.groups).toEqual([expect.objectContaining({ capability: "terraform-apply", components: ["network", "app", "edge"] })]);
322
+ expect(plan.groups![0]!.digest).toEqual(expect.any(String));
323
+ expect(plan.groups![0]!.reads ?? []).toEqual([]);
324
+ expect(plan.fallbacks ?? []).toEqual([]);
325
+ const m = byComponent(plan);
326
+ expect(Object.fromEntries(Object.entries(m).map(([c, x]) => [c, [x.status, x.counts]]))).toEqual({
327
+ network: ["planned", counts(1, 1)],
328
+ app: ["planned", counts(1, 1)],
329
+ edge: ["planned", counts(1, 1)],
330
+ });
331
+ for (const x of Object.values(m)) {
332
+ expect(x.unknownInputs).toBeUndefined();
333
+ for (const r of x.reads ?? []) expect(r.unknown).toBeUndefined();
334
+ }
335
+ // Each port's parameter plans its new value, on estates too (choudoufu#2261, #4470).
336
+ expect(insecureValue(plan, "aws_ssm_parameter.http_proxy_port")).toEqual({ action: "update", before: "8080", after: "8090" });
337
+ expect(insecureValue(plan, "aws_ssm_parameter.listener_port")).toEqual({ action: "update", before: "8080", after: "8090" });
338
+ expect(insecureValue(plan, "aws_ssm_parameter.upstream")).toEqual({ action: "update", before: "http://app:8080", after: "http://app:8090" });
339
+ expect(plan.changeSet.digest).toBe(plan.digest);
340
+ expect(plan.approval).toEqual({ status: "pending" });
341
+ expect(leftovers(loop().dir)).toEqual([]);
342
+ }, TIMEOUT);
343
+
344
+ test("one approval, and pr-apply exits 0 with no round: both ports reach all three roots", async () => {
345
+ const plan = loop().report("plan");
346
+ await loop().approve(1, "pr-apply", plan.digest);
347
+ expect(await loop().merge(1)).toBe(0);
348
+ const applied = loop().report("apply");
349
+ expect(applied.digest).toBe(plan.digest);
350
+ expect(applied.status).toBe("applied");
351
+ expect(applied.approval).toEqual({ status: "approved", approvedBy: ["github:alice"] });
352
+ expect(applied.rounds ?? []).toEqual([]);
353
+ expect(applied.groups?.map((g) => g.components)).toEqual([["network", "app", "edge"]]);
354
+ // The report lists members in no set order; the group names the apply order.
355
+ expect(Object.fromEntries(applied.members.map((x) => [x.component, [x.status, x.groupOutcome]]))).toEqual({
356
+ network: ["applied", "applied"],
357
+ app: ["applied", "applied"],
358
+ edge: ["applied", "applied"],
359
+ });
360
+ // The in-place updates reached the emulator: on estates the apply sends PutParameter (choudoufu#2261, #4470).
361
+ expect(ssm(endpoint, `/${id}/http-proxy-port`)).toBe("8090");
362
+ expect(ssm(endpoint, `/${id}/listener-port`)).toBe("8090");
363
+ expect(ssm(endpoint, `/${id}/upstream`)).toBe("http://app:8090");
364
+ expect(ssm(endpoint, `/${id}/admin-upstream`)).toBe("http://app:9000");
365
+ expect(leftovers(loop().dir)).toEqual([]);
366
+ }, TIMEOUT);
367
+ });
368
+
369
+ actSuite(mode, "chant-compose-create", ({ loop, id, endpoint }) => {
370
+ test("one pr-plan digest over a group of three, the new subnet id known after apply in app and edge", async () => {
371
+ const { code, report: plan } = await loop().open(1, "chant-compose-create");
372
+ expect(code).toBe(0);
373
+ expect(plan.status).toBe("planned");
374
+ expect(plan.groups).toEqual([expect.objectContaining({ capability: "terraform-apply", components: ["network", "app", "edge"] })]);
375
+ expect(plan.fallbacks ?? []).toEqual([]);
376
+ // What the group's apply computes: the subnet's id, read by app, and the worker's subnet, read by edge.
377
+ const reads = plan.groups![0]!.reads ?? [];
378
+ expect(reads).toHaveLength(2);
379
+ expect(reads).toEqual(
380
+ expect.arrayContaining([
381
+ { component: "app", ref: "network.subnet_b_id" },
382
+ { component: "edge", ref: "app.worker_subnet_id" },
383
+ ]),
384
+ );
385
+ const m = byComponent(plan);
386
+ // A member's reads are its `vars`; an estate's components have none.
387
+ if (!mode.estate) {
388
+ expect(m.app!.reads?.find((r) => r.upstream === "network")?.unknown).toEqual(["subnet_b_id"]);
389
+ expect(m.edge!.reads?.find((r) => r.upstream === "app")?.unknown).toEqual(["worker_subnet_id"]);
390
+ }
391
+ // Inside the group they are no holes, and no member waits on a later round.
392
+ for (const x of Object.values(m)) expect(x.unknownInputs).toBeUndefined();
393
+ for (const member of plan.changeSet.members) expect(member.holes ?? []).toEqual([]);
394
+ // edge's allowlist plans its new value, on estates too (choudoufu#2261, #4470).
395
+ expect(insecureValue(plan, "aws_ssm_parameter.allowlist")).toEqual({ action: "update", before: "10.0.1.0/24", after: "10.0.1.0/24,10.0.2.0/24" });
396
+ expect(Object.fromEntries(Object.entries(m).map(([c, x]) => [c, x.counts]))).toEqual({
397
+ network: counts(1, 0),
398
+ app: counts(1, 0),
399
+ edge: counts(1, 1),
400
+ });
401
+ expect(leftovers(loop().dir)).toEqual([]);
402
+ }, TIMEOUT);
403
+
404
+ test("one approval, and pr-apply exits 0 with no round: app's worker and edge hold the real subnet id", async () => {
405
+ const plan = loop().report("plan");
406
+ await loop().approve(1, "pr-apply", plan.digest);
407
+ expect(await loop().merge(1)).toBe(0);
408
+ const applied = loop().report("apply");
409
+ expect(applied.digest).toBe(plan.digest);
410
+ expect(applied.status).toBe("applied");
411
+ expect(applied.rounds ?? []).toEqual([]);
412
+ // The report lists members in no set order; the group names the apply order.
413
+ expect(Object.fromEntries(applied.members.map((x) => [x.component, [x.status, x.groupOutcome]]))).toEqual({
414
+ network: ["applied", "applied"],
415
+ app: ["applied", "applied"],
416
+ edge: ["applied", "applied"],
417
+ });
418
+
419
+ const subnets = (awsJson(endpoint, "ec2", "describe-subnets", "--filters", "Name=cidr-block,Values=10.0.2.0/24", `Name=tag:chant:estate,Values=${id}`) as { Subnets: { SubnetId: string }[] }).Subnets;
420
+ expect(subnets).toHaveLength(1);
421
+ const subnetB = subnets[0]!.SubnetId;
422
+ expect(subnetB).toMatch(/^subnet-/);
423
+ const workers = (awsJson(endpoint, "ec2", "describe-network-interfaces", "--filters", `Name=subnet-id,Values=${subnetB}`, `Name=tag:chant:estate,Values=${id}`) as {
424
+ NetworkInterfaces: { SubnetId: string }[];
425
+ }).NetworkInterfaces;
426
+ expect(workers.map((w) => w.SubnetId)).toEqual([subnetB]);
427
+ expect(ssm(endpoint, `/${id}/worker-subnet`)).toBe(subnetB);
428
+ expect(ssm(endpoint, `/${id}/allowlist`)).toBe("10.0.1.0/24,10.0.2.0/24");
429
+
430
+ if (!mode.estate) {
431
+ // app's own state holds the id network's state does.
432
+ const state = (r: string) => JSON.parse(readFileSync(join(loop().dir, "roots", r, "terraform.tfstate"), "utf8")) as { resources: { type: string; name: string; instances: { attributes: Record<string, unknown> }[] }[] };
433
+ const attr = (r: string, type: string, name: string, key: string) => state(r).resources.find((x) => x.type === type && x.name === name)!.instances[0]!.attributes[key];
434
+ expect(attr("network", "aws_subnet", "b", "id")).toBe(subnetB);
435
+ expect(attr("app", "aws_network_interface", "worker", "subnet_id")).toBe(subnetB);
436
+ }
437
+ expect(leftovers(loop().dir)).toEqual([]);
438
+ }, TIMEOUT);
439
+ });
440
+ }
@@ -0,0 +1,76 @@
1
+ /**
2
+ * The choudoufu engine's member outputs (#4455): what a choudoufu member's
3
+ * set plan says its outputs will be, for core's `member-outputs`, and the
4
+ * plugin contributing the source. The commands themselves need a choudoufu
5
+ * binary and an estate, which the maintainer runs.
6
+ */
7
+
8
+ import { describe, expect, test } from "vitest";
9
+ import { choudoufuMemberPlannedOutputs, choudoufuOutputSource, terraformCapabilityPlugin } from "./capability-plugin";
10
+ import { parseChoudoufuPlanSet } from "../op/activities/terraform";
11
+
12
+ /** The azure site's plan, as `live-plan-set` embeds it: the Container App new, so its FQDN is unknown. */
13
+ const newApp = {
14
+ format_version: "1.2",
15
+ planned_values: { outputs: { azureEndpoint: { sensitive: false }, resourceGroup: { sensitive: false, value: "local" } } },
16
+ output_changes: {
17
+ azureEndpoint: { actions: ["create"], before: null, after_unknown: true },
18
+ resourceGroup: { actions: ["no-op"], before: "local", after: "local", after_unknown: false },
19
+ },
20
+ resource_changes: [
21
+ {
22
+ address: "azurerm_container_app.web",
23
+ mode: "managed",
24
+ type: "azurerm_container_app",
25
+ name: "web",
26
+ change: { actions: ["create"], before: null, after: { name: "web" }, after_unknown: { ingress: [{ fqdn: true }] } },
27
+ },
28
+ ],
29
+ };
30
+
31
+ /** The same site with nothing to change: the FQDN is the one applied. */
32
+ const settled = {
33
+ format_version: "1.2",
34
+ planned_values: { outputs: { azureEndpoint: { sensitive: false, value: "web.local.azurecontainerapps.io" } } },
35
+ output_changes: { azureEndpoint: { actions: ["no-op"], before: "web.local.azurecontainerapps.io", after: "web.local.azurecontainerapps.io", after_unknown: false } },
36
+ resource_changes: [],
37
+ };
38
+
39
+ function setDocument(plan: unknown, changes: boolean): string {
40
+ return JSON.stringify({
41
+ format_version: "1",
42
+ roots: [{ root: "azure", estate: "three-cloud-azure", status: "planned", error: "", changes, plan, digest: "sha256:root" }],
43
+ summary: { roots: 1, planned: 1, changed: changes ? 1 : 0, failed: 0 },
44
+ digest: "sha256:set",
45
+ exit_code: changes ? 2 : 0,
46
+ });
47
+ }
48
+
49
+ describe("choudoufuMemberPlannedOutputs", () => {
50
+ test("an output unknown after apply is named unknown; the rest are known, with chant's digest of the plan and its changes", () => {
51
+ const set = parseChoudoufuPlanSet(setDocument(newApp, true), { member: "azure", root: "azure" });
52
+ expect(choudoufuMemberPlannedOutputs(set)).toEqual({
53
+ known: { resourceGroup: "local" },
54
+ unknown: ["azureEndpoint"],
55
+ planDigest: set.planDigest,
56
+ changes: true,
57
+ });
58
+ });
59
+
60
+ test("a settled member's output is known, as applied, and it has nothing to apply", () => {
61
+ const set = parseChoudoufuPlanSet(setDocument(settled, false), { member: "azure", root: "azure" });
62
+ expect(choudoufuMemberPlannedOutputs(set)).toEqual({
63
+ known: { azureEndpoint: "web.local.azurecontainerapps.io" },
64
+ unknown: [],
65
+ planDigest: set.planDigest,
66
+ changes: false,
67
+ });
68
+ });
69
+ });
70
+
71
+ describe("the plugin's engine output sources", () => {
72
+ test("contributes the choudoufu source, for members of kind choudoufu", () => {
73
+ expect(terraformCapabilityPlugin.engineOutputSources?.()).toEqual([choudoufuOutputSource]);
74
+ expect(choudoufuOutputSource.kinds).toEqual(["choudoufu"]);
75
+ });
76
+ });
@@ -0,0 +1,147 @@
1
+ /**
2
+ * Placeholder previews of a root whose `vars` read outputs not known yet
3
+ * (#4463): a stand-in per type, the cases with no preview, and what the
4
+ * stand-ins drive in the plan JSON.
5
+ */
6
+
7
+ import { describe, expect, test } from "vitest";
8
+ import { markPlaceholderDriven, placeholderVars, TERRAFORM_UNKNOWN_MARK, terraformPlaceholder, waitingHole } from "./placeholder-preview";
9
+
10
+ describe("terraformPlaceholder: a stand-in typed from the variable block (#4463)", () => {
11
+ test("string", () => {
12
+ expect(terraformPlaceholder("string")).toBe(TERRAFORM_UNKNOWN_MARK);
13
+ });
14
+
15
+ test("number", () => {
16
+ expect(terraformPlaceholder("number")).toBe(0);
17
+ });
18
+
19
+ test("bool", () => {
20
+ expect(terraformPlaceholder("bool")).toBe(false);
21
+ });
22
+
23
+ test("list", () => {
24
+ expect(terraformPlaceholder("list(string)")).toEqual([]);
25
+ expect(terraformPlaceholder("list(object({ id = string }))")).toEqual([]);
26
+ expect(terraformPlaceholder("set(string)")).toEqual([]);
27
+ });
28
+
29
+ test("map", () => {
30
+ expect(terraformPlaceholder("map(string)")).toEqual({});
31
+ expect(terraformPlaceholder("map(number)")).toEqual({});
32
+ });
33
+
34
+ test("an object gets a stand-in per attribute, optional ones by their inner type", () => {
35
+ expect(terraformPlaceholder("object({ id = string, port = number, public = optional(bool, true) })")).toEqual({
36
+ id: TERRAFORM_UNKNOWN_MARK,
37
+ port: 0,
38
+ public: false,
39
+ });
40
+ expect(terraformPlaceholder("object({\n id = string\n tags = map(string)\n})")).toEqual({ id: TERRAFORM_UNKNOWN_MARK, tags: {} });
41
+ });
42
+
43
+ test("a tuple gets a stand-in per element", () => {
44
+ expect(terraformPlaceholder("tuple([string, number])")).toEqual([TERRAFORM_UNKNOWN_MARK, 0]);
45
+ });
46
+
47
+ test("no type, any, or a type in the old interpolated form reads as its type", () => {
48
+ expect(terraformPlaceholder(undefined)).toBe(TERRAFORM_UNKNOWN_MARK);
49
+ expect(terraformPlaceholder("any")).toBe(TERRAFORM_UNKNOWN_MARK);
50
+ expect(terraformPlaceholder("${number}")).toBe(0);
51
+ });
52
+ });
53
+
54
+ describe("placeholderVars (#4463)", () => {
55
+ const declared = new Map([
56
+ ["vpc_id", { type: "string" }],
57
+ ["size", { type: "number" }],
58
+ ["name", { type: "string" }],
59
+ ]);
60
+
61
+ test("fills each variable that holds nothing with its typed stand-in, and keeps the rest", () => {
62
+ expect(placeholderVars({ vpc_id: undefined, size: undefined, name: "app" }, declared)).toEqual({
63
+ placeholders: ["size", "vpc_id"],
64
+ vars: { vpc_id: TERRAFORM_UNKNOWN_MARK, size: 0, name: "app" },
65
+ });
66
+ });
67
+
68
+ test("says why when no preview can be made", () => {
69
+ expect(placeholderVars({ name: "app" }, declared)).toBe("no variable of the step holds the unknown read");
70
+ expect(placeholderVars({ subnet: undefined }, declared)).toMatch(/declares no variable "subnet"/);
71
+ expect(placeholderVars({ name: { id: undefined } }, declared)).toMatch(/inside var\.name/);
72
+ });
73
+ });
74
+
75
+ describe("markPlaceholderDriven (#4463)", () => {
76
+ const plan = {
77
+ configuration: {
78
+ root_module: {
79
+ resources: [
80
+ {
81
+ address: "aws_subnet.a",
82
+ mode: "managed",
83
+ type: "aws_subnet",
84
+ name: "a",
85
+ expressions: { vpc_id: { references: ["var.vpc_id"] }, cidr_block: { constant_value: "10.0.1.0/24" } },
86
+ },
87
+ ],
88
+ module_calls: {
89
+ net: {
90
+ expressions: { parent: { references: ["var.vpc_id"] } },
91
+ module: {
92
+ resources: [{ address: "aws_route_table.rt", mode: "managed", type: "aws_route_table", name: "rt", expressions: { vpc_id: { references: ["var.parent"] } } }],
93
+ },
94
+ },
95
+ },
96
+ outputs: { subnet_vpc: { expression: { references: ["var.vpc_id"] } }, cidr: { expression: { constant_value: "10.0.1.0/24" } } },
97
+ },
98
+ },
99
+ resource_changes: [
100
+ {
101
+ address: "aws_subnet.a",
102
+ mode: "managed",
103
+ type: "aws_subnet",
104
+ name: "a",
105
+ change: { actions: ["create"], before: null, after: { vpc_id: TERRAFORM_UNKNOWN_MARK, cidr_block: "10.0.1.0/24" }, after_unknown: { id: true } },
106
+ },
107
+ {
108
+ address: "module.net.aws_route_table.rt",
109
+ module_address: "module.net",
110
+ mode: "managed",
111
+ type: "aws_route_table",
112
+ name: "rt",
113
+ change: { actions: ["create"], before: null, after: { vpc_id: TERRAFORM_UNKNOWN_MARK }, after_unknown: {} },
114
+ },
115
+ ],
116
+ planned_values: { outputs: { subnet_vpc: { value: TERRAFORM_UNKNOWN_MARK }, cidr: { value: "10.0.1.0/24" } } },
117
+ output_changes: { subnet_vpc: { actions: ["create"], after: TERRAFORM_UNKNOWN_MARK, after_unknown: false }, cidr: { actions: ["create"], after: "10.0.1.0/24", after_unknown: false } },
118
+ };
119
+
120
+ test("marks what the stand-ins drive unknown, through module inputs and outputs, and leaves the rest", () => {
121
+ const marked = markPlaceholderDriven(plan, ["vpc_id"]);
122
+ if (!("plan" in marked)) throw new Error(marked.refused);
123
+ const out = marked.plan as typeof plan;
124
+ expect(out.resource_changes[0]!.change).toEqual({ actions: ["create"], before: null, after: { cidr_block: "10.0.1.0/24" }, after_unknown: { id: true, vpc_id: true } });
125
+ expect(out.resource_changes[1]!.change.after_unknown).toEqual({ vpc_id: true });
126
+ expect(out.output_changes.subnet_vpc).toEqual({ actions: ["create"], after_unknown: true });
127
+ expect(out.output_changes.cidr.after).toBe("10.0.1.0/24");
128
+ expect(out.planned_values.outputs).toEqual({ cidr: { value: "10.0.1.0/24" } });
129
+ // The input is left as it was.
130
+ expect(plan.resource_changes[0]!.change.after.vpc_id).toBe(TERRAFORM_UNKNOWN_MARK);
131
+ });
132
+
133
+ test("refuses a count or for_each that reads a stand-in", () => {
134
+ const counted = structuredClone(plan) as Record<string, any>;
135
+ counted.configuration.root_module.resources[0].count_expression = { references: ["var.vpc_id"] };
136
+ expect(markPlaceholderDriven(counted, ["vpc_id"])).toEqual({
137
+ refused: "the count or for_each of aws_subnet.a reads a stand-in, so the preview cannot say how many instances there are",
138
+ });
139
+ });
140
+ });
141
+
142
+ test("the hole a root keeps says why there is no preview (#4463)", () => {
143
+ expect(waitingHole("app", ["net"])).toEqual({ address: "app", type: "terraform_root", reason: "inputs from net; planned again once they apply" });
144
+ expect(waitingHole("app", ["net"], "the plan with stand-in values failed: x").reason).toBe(
145
+ "inputs from net; planned again once they apply; no preview: the plan with stand-in values failed: x",
146
+ );
147
+ });