@intentius/chant-lexicon-terraform 0.124.54 → 0.124.56

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 (351) hide show
  1. package/README.md +7 -4
  2. package/dist/ci-setup.d.ts +11 -0
  3. package/dist/ci-setup.d.ts.map +1 -0
  4. package/dist/ci-setup.js +41 -0
  5. package/dist/components/capability-plugin.d.ts +40 -6
  6. package/dist/components/capability-plugin.d.ts.map +1 -1
  7. package/dist/components/capability-plugin.js +174 -30
  8. package/dist/components/compose-group.d.ts +1 -1
  9. package/dist/components/compose-group.d.ts.map +1 -1
  10. package/dist/components/compose-group.js +1 -1
  11. package/dist/components/release-inputs.d.ts +29 -0
  12. package/dist/components/release-inputs.d.ts.map +1 -0
  13. package/dist/components/release-inputs.js +57 -0
  14. package/dist/components/trace-facts.d.ts +16 -0
  15. package/dist/components/trace-facts.d.ts.map +1 -0
  16. package/dist/components/trace-facts.js +19 -0
  17. package/dist/cone.d.ts +53 -0
  18. package/dist/cone.d.ts.map +1 -0
  19. package/dist/cone.js +119 -0
  20. package/dist/cost-hold.d.ts +27 -0
  21. package/dist/cost-hold.d.ts.map +1 -0
  22. package/dist/cost-hold.js +21 -0
  23. package/dist/drift-rows.d.ts +61 -0
  24. package/dist/drift-rows.d.ts.map +1 -0
  25. package/dist/drift-rows.js +108 -0
  26. package/dist/exit-codes.d.ts +55 -0
  27. package/dist/exit-codes.d.ts.map +1 -0
  28. package/dist/exit-codes.js +48 -0
  29. package/dist/index.d.ts +2 -0
  30. package/dist/index.d.ts.map +1 -1
  31. package/dist/index.js +3 -0
  32. package/dist/integrity.json +7 -4
  33. package/dist/lint/audit-catalog.d.ts.map +1 -1
  34. package/dist/lint/audit-catalog.js +27 -3
  35. package/dist/lint/audit-lineage.d.ts +14 -2
  36. package/dist/lint/audit-lineage.d.ts.map +1 -1
  37. package/dist/lint/post-synth/index.d.ts.map +1 -1
  38. package/dist/lint/post-synth/index.js +4 -0
  39. package/dist/lint/post-synth/record-store.d.ts +54 -0
  40. package/dist/lint/post-synth/record-store.d.ts.map +1 -0
  41. package/dist/lint/post-synth/record-store.js +104 -0
  42. package/dist/lint/post-synth/tf001.d.ts +2 -2
  43. package/dist/lint/post-synth/tf001.js +2 -2
  44. package/dist/lint/post-synth/tf024.d.ts +35 -11
  45. package/dist/lint/post-synth/tf024.d.ts.map +1 -1
  46. package/dist/lint/post-synth/tf024.js +92 -20
  47. package/dist/lint/post-synth/tf046.d.ts +64 -0
  48. package/dist/lint/post-synth/tf046.d.ts.map +1 -0
  49. package/dist/lint/post-synth/tf046.js +175 -0
  50. package/dist/lint/post-synth/tf047.d.ts +36 -0
  51. package/dist/lint/post-synth/tf047.d.ts.map +1 -0
  52. package/dist/lint/post-synth/tf047.js +94 -0
  53. package/dist/lock-rows.d.ts +36 -0
  54. package/dist/lock-rows.d.ts.map +1 -0
  55. package/dist/lock-rows.js +60 -0
  56. package/dist/lock-timeout.d.ts +22 -0
  57. package/dist/lock-timeout.d.ts.map +1 -0
  58. package/dist/lock-timeout.js +32 -0
  59. package/dist/manifest.json +1 -1
  60. package/dist/module-catalog/index.d.ts +69 -0
  61. package/dist/module-catalog/index.d.ts.map +1 -0
  62. package/dist/module-catalog/index.js +110 -0
  63. package/dist/module-catalog/refresh-cli.d.ts +4 -0
  64. package/dist/module-catalog/refresh-cli.d.ts.map +1 -0
  65. package/dist/module-catalog/refresh-cli.js +64 -0
  66. package/dist/module-catalog/static-tier.d.ts +6 -0
  67. package/dist/module-catalog/static-tier.d.ts.map +1 -0
  68. package/dist/module-catalog/static-tier.js +1650 -0
  69. package/dist/module-catalog/types.d.ts +65 -0
  70. package/dist/module-catalog/types.d.ts.map +1 -0
  71. package/dist/module-catalog/types.js +33 -0
  72. package/dist/okf/index.md +3 -1
  73. package/dist/okf/rules/TF024.md +2 -2
  74. package/dist/okf/rules/TF046.md +11 -0
  75. package/dist/okf/rules/TF047.md +11 -0
  76. package/dist/op/activities/terraform.d.ts +29 -0
  77. package/dist/op/activities/terraform.d.ts.map +1 -1
  78. package/dist/op/activities/terraform.js +19 -4
  79. package/dist/pin/consumers.d.ts +35 -0
  80. package/dist/pin/consumers.d.ts.map +1 -0
  81. package/dist/pin/consumers.js +70 -0
  82. package/dist/plugin.d.ts.map +1 -1
  83. package/dist/plugin.js +5 -0
  84. package/dist/registry/address.d.ts +63 -0
  85. package/dist/registry/address.d.ts.map +1 -0
  86. package/dist/registry/address.js +65 -0
  87. package/dist/registry/catalog-read.d.ts +14 -0
  88. package/dist/registry/catalog-read.d.ts.map +1 -0
  89. package/dist/registry/catalog-read.js +58 -0
  90. package/dist/registry/deprecated.d.ts +37 -0
  91. package/dist/registry/deprecated.d.ts.map +1 -0
  92. package/dist/registry/deprecated.js +43 -0
  93. package/dist/registry/docs.d.ts +96 -0
  94. package/dist/registry/docs.d.ts.map +1 -0
  95. package/dist/registry/docs.js +210 -0
  96. package/dist/registry/edge/edge.d.ts +46 -0
  97. package/dist/registry/edge/edge.d.ts.map +1 -0
  98. package/dist/registry/edge/edge.js +194 -0
  99. package/dist/registry/edge/index.d.ts +11 -0
  100. package/dist/registry/edge/index.d.ts.map +1 -0
  101. package/dist/registry/edge/index.js +10 -0
  102. package/dist/registry/edge/lambda-edge.d.ts +76 -0
  103. package/dist/registry/edge/lambda-edge.d.ts.map +1 -0
  104. package/dist/registry/edge/lambda-edge.js +95 -0
  105. package/dist/registry/edge/sigv4-web.d.ts +33 -0
  106. package/dist/registry/edge/sigv4-web.d.ts.map +1 -0
  107. package/dist/registry/edge/sigv4-web.js +56 -0
  108. package/dist/registry/edge/token-source.d.ts +73 -0
  109. package/dist/registry/edge/token-source.d.ts.map +1 -0
  110. package/dist/registry/edge/token-source.js +115 -0
  111. package/dist/registry/edge/worker.d.ts +45 -0
  112. package/dist/registry/edge/worker.d.ts.map +1 -0
  113. package/dist/registry/edge/worker.js +26 -0
  114. package/dist/registry/handler.d.ts +25 -0
  115. package/dist/registry/handler.d.ts.map +1 -0
  116. package/dist/registry/handler.js +75 -0
  117. package/dist/registry/headers-file.d.ts +21 -0
  118. package/dist/registry/headers-file.d.ts.map +1 -0
  119. package/dist/registry/headers-file.js +57 -0
  120. package/dist/registry/index.d.ts +31 -0
  121. package/dist/registry/index.d.ts.map +1 -0
  122. package/dist/registry/index.js +30 -0
  123. package/dist/registry/mirror-paths.d.ts +9 -0
  124. package/dist/registry/mirror-paths.d.ts.map +1 -0
  125. package/dist/registry/mirror-paths.js +8 -0
  126. package/dist/registry/mirror.d.ts +130 -0
  127. package/dist/registry/mirror.d.ts.map +1 -0
  128. package/dist/registry/mirror.js +319 -0
  129. package/dist/registry/proxies.d.ts +73 -0
  130. package/dist/registry/proxies.d.ts.map +1 -0
  131. package/dist/registry/proxies.js +111 -0
  132. package/dist/registry/reader.d.ts +57 -0
  133. package/dist/registry/reader.d.ts.map +1 -0
  134. package/dist/registry/reader.js +198 -0
  135. package/dist/registry/semver.d.ts +16 -0
  136. package/dist/registry/semver.d.ts.map +1 -0
  137. package/dist/registry/semver.js +24 -0
  138. package/dist/registry/serve.d.ts +24 -0
  139. package/dist/registry/serve.d.ts.map +1 -0
  140. package/dist/registry/serve.js +64 -0
  141. package/dist/registry/source-forms.d.ts +79 -0
  142. package/dist/registry/source-forms.d.ts.map +1 -0
  143. package/dist/registry/source-forms.js +202 -0
  144. package/dist/registry/targets/artifactory.d.ts +28 -0
  145. package/dist/registry/targets/artifactory.d.ts.map +1 -0
  146. package/dist/registry/targets/artifactory.js +206 -0
  147. package/dist/registry/targets/fake-artifactory.d.ts +30 -0
  148. package/dist/registry/targets/fake-artifactory.d.ts.map +1 -0
  149. package/dist/registry/targets/fake-artifactory.js +142 -0
  150. package/dist/registry/targets/fake-gitlab.d.ts +39 -0
  151. package/dist/registry/targets/fake-gitlab.d.ts.map +1 -0
  152. package/dist/registry/targets/fake-gitlab.js +136 -0
  153. package/dist/registry/targets/fake-hcp.d.ts +50 -0
  154. package/dist/registry/targets/fake-hcp.d.ts.map +1 -0
  155. package/dist/registry/targets/fake-hcp.js +202 -0
  156. package/dist/registry/targets/fake-http.d.ts +31 -0
  157. package/dist/registry/targets/fake-http.d.ts.map +1 -0
  158. package/dist/registry/targets/fake-http.js +87 -0
  159. package/dist/registry/targets/fake-nexus.d.ts +27 -0
  160. package/dist/registry/targets/fake-nexus.d.ts.map +1 -0
  161. package/dist/registry/targets/fake-nexus.js +137 -0
  162. package/dist/registry/targets/gitlab.d.ts +53 -0
  163. package/dist/registry/targets/gitlab.d.ts.map +1 -0
  164. package/dist/registry/targets/gitlab.js +187 -0
  165. package/dist/registry/targets/hcp.d.ts +29 -0
  166. package/dist/registry/targets/hcp.d.ts.map +1 -0
  167. package/dist/registry/targets/hcp.js +310 -0
  168. package/dist/registry/targets/hosted.d.ts +62 -0
  169. package/dist/registry/targets/hosted.d.ts.map +1 -0
  170. package/dist/registry/targets/hosted.js +74 -0
  171. package/dist/registry/targets/index.d.ts +18 -0
  172. package/dist/registry/targets/index.d.ts.map +1 -0
  173. package/dist/registry/targets/index.js +17 -0
  174. package/dist/registry/targets/module-target.d.ts +68 -0
  175. package/dist/registry/targets/module-target.d.ts.map +1 -0
  176. package/dist/registry/targets/module-target.js +63 -0
  177. package/dist/registry/targets/nexus.d.ts +43 -0
  178. package/dist/registry/targets/nexus.d.ts.map +1 -0
  179. package/dist/registry/targets/nexus.js +250 -0
  180. package/dist/registry/targets/zip.d.ts +55 -0
  181. package/dist/registry/targets/zip.d.ts.map +1 -0
  182. package/dist/registry/targets/zip.js +278 -0
  183. package/dist/registry/writer.d.ts +101 -0
  184. package/dist/registry/writer.d.ts.map +1 -0
  185. package/dist/registry/writer.js +109 -0
  186. package/dist/rules/record-store.ts +122 -0
  187. package/dist/rules/tf001.ts +2 -2
  188. package/dist/rules/tf024.ts +105 -22
  189. package/dist/rules/tf046.ts +204 -0
  190. package/dist/rules/tf047.ts +114 -0
  191. package/dist/state/azure-blob.d.ts +8 -67
  192. package/dist/state/azure-blob.d.ts.map +1 -1
  193. package/dist/state/azure-blob.js +6 -199
  194. package/dist/state/gcs.d.ts +7 -61
  195. package/dist/state/gcs.d.ts.map +1 -1
  196. package/dist/state/gcs.js +5 -222
  197. package/dist/state/object-store.d.ts +4 -33
  198. package/dist/state/object-store.d.ts.map +1 -1
  199. package/dist/state/object-store.js +4 -17
  200. package/dist/state/s3.d.ts +11 -58
  201. package/dist/state/s3.d.ts.map +1 -1
  202. package/dist/state/s3.js +10 -161
  203. package/dist/state/store.js +2 -2
  204. package/dist/state-ops.d.ts +255 -0
  205. package/dist/state-ops.d.ts.map +1 -0
  206. package/dist/state-ops.js +305 -0
  207. package/dist/state-reads.d.ts +57 -0
  208. package/dist/state-reads.d.ts.map +1 -0
  209. package/dist/state-reads.js +104 -0
  210. package/dist/used-sources.d.ts +54 -0
  211. package/dist/used-sources.d.ts.map +1 -0
  212. package/dist/used-sources.js +157 -0
  213. package/package.json +19 -3
  214. package/src/__fixtures__/drift-record/README.md +5 -0
  215. package/src/__fixtures__/drift-rows/live-root-plan.json +70 -0
  216. package/src/__fixtures__/drift-rows/refresh-plan.json +61 -0
  217. package/src/__fixtures__/used-sources/root/extra.tofu +1 -0
  218. package/src/__fixtures__/used-sources/root/main.tf +31 -0
  219. package/src/__fixtures__/used-sources/root/modules/net/main.tf +14 -0
  220. package/src/__fixtures__/used-sources/root/modules/net/versions.tf.json +7 -0
  221. package/src/__fixtures__/used-sources/unit/terragrunt.hcl +3 -0
  222. package/src/ci-setup.ts +42 -0
  223. package/src/components/capability-plugin.test.ts +26 -14
  224. package/src/components/capability-plugin.ts +185 -31
  225. package/src/components/compose-group.ts +1 -1
  226. package/src/components/pr-loop-compose.tofu.e2e.test.ts +1 -1
  227. package/src/components/pr-loop.tofu.test.ts +1 -1
  228. package/src/components/release-inputs.ts +69 -0
  229. package/src/components/trace-facts.test.ts +21 -0
  230. package/src/components/trace-facts.ts +27 -0
  231. package/src/cone.ts +138 -0
  232. package/src/cost-hold.ts +29 -0
  233. package/src/describe-resources.engine-facts.test.ts +2 -3
  234. package/src/drift-rows.test.ts +135 -0
  235. package/src/drift-rows.ts +137 -0
  236. package/src/exit-codes.test.ts +23 -0
  237. package/src/exit-codes.ts +59 -0
  238. package/src/index.ts +3 -0
  239. package/src/lint/audit-catalog.ts +29 -3
  240. package/src/lint/audit-lineage.ts +14 -2
  241. package/src/lint/post-synth/fixtures/TF024/live-with-record-store/main.tf +18 -0
  242. package/src/lint/post-synth/fixtures/TF024/sidecar-with-backend/estate.chdf.hcl +1 -0
  243. package/src/lint/post-synth/fixtures/TF024/sidecar-with-backend/main.tf +10 -0
  244. package/src/lint/post-synth/fixtures/TF046/azurerm-no-resource-group/main.tf +15 -0
  245. package/src/lint/post-synth/fixtures/TF046/backend-config-only/main.tf +14 -0
  246. package/src/lint/post-synth/fixtures/TF046/derived-s3/backend.tf +7 -0
  247. package/src/lint/post-synth/fixtures/TF046/derived-s3/main.tf +9 -0
  248. package/src/lint/post-synth/fixtures/TF046/explicit-local/main.tf +16 -0
  249. package/src/lint/post-synth/fixtures/TF046/explicit-s3-beside-local-backend/main.tf +15 -0
  250. package/src/lint/post-synth/fixtures/TF046/implied-local/main.tf +9 -0
  251. package/src/lint/post-synth/fixtures/TF046/sidecar-implied/estate.chdf.hcl +1 -0
  252. package/src/lint/post-synth/fixtures/TF046/sidecar-implied/main.tf +5 -0
  253. package/src/lint/post-synth/fixtures/TF047/clear/main.tf +12 -0
  254. package/src/lint/post-synth/fixtures/TF047/refused/main.tf +12 -0
  255. package/src/lint/post-synth/fixtures/TF047/unknown-module/main.tf +12 -0
  256. package/src/lint/post-synth/fixtures/TF047/unknown-version/main.tf +12 -0
  257. package/src/lint/post-synth/index.ts +4 -0
  258. package/src/lint/post-synth/record-store.ts +122 -0
  259. package/src/lint/post-synth/tf001.ts +2 -2
  260. package/src/lint/post-synth/tf024.test.ts +26 -5
  261. package/src/lint/post-synth/tf024.ts +105 -22
  262. package/src/lint/post-synth/tf046.test.ts +137 -0
  263. package/src/lint/post-synth/tf046.ts +204 -0
  264. package/src/lint/post-synth/tf047.test.ts +56 -0
  265. package/src/lint/post-synth/tf047.ts +114 -0
  266. package/src/lock-rows.test.ts +70 -0
  267. package/src/lock-rows.ts +82 -0
  268. package/src/lock-timeout.ts +34 -0
  269. package/src/module-catalog/index.test.ts +93 -0
  270. package/src/module-catalog/index.ts +159 -0
  271. package/src/module-catalog/refresh-cli.ts +68 -0
  272. package/src/module-catalog/static-tier.ts +1659 -0
  273. package/src/module-catalog/types.ts +88 -0
  274. package/src/op/activities/terraform.ts +46 -5
  275. package/src/op-wave-step.test.ts +109 -0
  276. package/src/pin/__fixtures__/consumers/live/dev/main.tf +4 -0
  277. package/src/pin/__fixtures__/consumers/live/prod/main.tf +21 -0
  278. package/src/pin/__fixtures__/consumers/units/vpc/terragrunt.hcl +7 -0
  279. package/src/pin/consumers.test.ts +57 -0
  280. package/src/pin/consumers.ts +95 -0
  281. package/src/plugin.ts +6 -0
  282. package/src/registry/__fixtures__/docs/documented/.terraform-docs.yml +3 -0
  283. package/src/registry/__fixtures__/docs/documented/README.md +1 -0
  284. package/src/registry/__fixtures__/docs/documented/main.tf +8 -0
  285. package/src/registry/__fixtures__/docs/json/main.tf.json +14 -0
  286. package/src/registry/__fixtures__/docs/vpc/README.md +3 -0
  287. package/src/registry/__fixtures__/docs/vpc/examples/complete/main.tf +5 -0
  288. package/src/registry/__fixtures__/docs/vpc/examples/simple/README.md +1 -0
  289. package/src/registry/__fixtures__/docs/vpc/examples/simple/main.tf +4 -0
  290. package/src/registry/__fixtures__/docs/vpc/main.tf +14 -0
  291. package/src/registry/__fixtures__/docs/vpc/outputs.tf +9 -0
  292. package/src/registry/__fixtures__/docs/vpc/variables.tf +16 -0
  293. package/src/registry/__fixtures__/proxied-root/main.tf +12 -0
  294. package/src/registry/address.ts +100 -0
  295. package/src/registry/catalog-read.ts +56 -0
  296. package/src/registry/deprecated.test.ts +139 -0
  297. package/src/registry/deprecated.ts +76 -0
  298. package/src/registry/docs.test.ts +140 -0
  299. package/src/registry/docs.ts +299 -0
  300. package/src/registry/edge/bundle.test.ts +50 -0
  301. package/src/registry/edge/edge.test.ts +176 -0
  302. package/src/registry/edge/edge.ts +211 -0
  303. package/src/registry/edge/index.ts +10 -0
  304. package/src/registry/edge/lambda-edge.ts +127 -0
  305. package/src/registry/edge/sigv4-web.ts +79 -0
  306. package/src/registry/edge/token-source.test.ts +258 -0
  307. package/src/registry/edge/token-source.ts +169 -0
  308. package/src/registry/edge/worker.ts +61 -0
  309. package/src/registry/handler.ts +89 -0
  310. package/src/registry/headers-file.ts +61 -0
  311. package/src/registry/index.ts +31 -0
  312. package/src/registry/mirror-paths.ts +10 -0
  313. package/src/registry/mirror.test.ts +198 -0
  314. package/src/registry/mirror.ts +395 -0
  315. package/src/registry/proxies.test.ts +153 -0
  316. package/src/registry/proxies.ts +132 -0
  317. package/src/registry/reader.ts +225 -0
  318. package/src/registry/registry.test.ts +262 -0
  319. package/src/registry/semver.ts +34 -0
  320. package/src/registry/serve.ts +82 -0
  321. package/src/registry/source-forms.test.ts +87 -0
  322. package/src/registry/source-forms.ts +216 -0
  323. package/src/registry/targets/artifactory.test.ts +248 -0
  324. package/src/registry/targets/artifactory.ts +257 -0
  325. package/src/registry/targets/fake-artifactory.ts +158 -0
  326. package/src/registry/targets/fake-gitlab.ts +161 -0
  327. package/src/registry/targets/fake-hcp.ts +233 -0
  328. package/src/registry/targets/fake-http.ts +97 -0
  329. package/src/registry/targets/fake-nexus.ts +152 -0
  330. package/src/registry/targets/gitlab.test.ts +208 -0
  331. package/src/registry/targets/gitlab.ts +223 -0
  332. package/src/registry/targets/hcp.test.ts +230 -0
  333. package/src/registry/targets/hcp.ts +360 -0
  334. package/src/registry/targets/hosted.ts +117 -0
  335. package/src/registry/targets/index.ts +17 -0
  336. package/src/registry/targets/module-target.ts +131 -0
  337. package/src/registry/targets/nexus.test.ts +220 -0
  338. package/src/registry/targets/nexus.ts +314 -0
  339. package/src/registry/targets/zip.test.ts +65 -0
  340. package/src/registry/targets/zip.ts +287 -0
  341. package/src/registry/writer.ts +155 -0
  342. package/src/state/azure-blob.ts +6 -221
  343. package/src/state/gcs.ts +5 -252
  344. package/src/state/object-store.ts +4 -32
  345. package/src/state/s3.ts +10 -182
  346. package/src/state/store.ts +2 -2
  347. package/src/state-ops.test.ts +182 -0
  348. package/src/state-ops.ts +462 -0
  349. package/src/state-reads.ts +116 -0
  350. package/src/used-sources.test.ts +75 -0
  351. package/src/used-sources.ts +204 -0
@@ -0,0 +1,116 @@
1
+ /**
2
+ * Variables read from another root's state (#4545, Atmos reads moved from
3
+ * terragucci's `atmos.ts`): an Atmos stack that sets a var with
4
+ * `!terraform.state <component> [<stack>] <output>` plans on the upstream
5
+ * instance's output, which only the job that holds a credential can read.
6
+ *
7
+ * In the `terraform-apply` step a read is `{ var, root, output }`: the var it
8
+ * sets, the `terraform.roots` entry whose state holds the value (its
9
+ * directory and workspace come from that entry), and the output followed by
10
+ * the keys into its value (`.subnets.private` is `["subnets", "private"]`).
11
+ * The step reads each upstream's `output -json` once and passes the values
12
+ * as `-var`, under the step's own `vars`, which win. A read whose upstream
13
+ * has no state, or no such output yet, waits: the root is not planned on a
14
+ * stand-in, and the wave says which upstream to apply first.
15
+ */
16
+
17
+ /** One var a root reads from another root's state. */
18
+ export interface TerraformStateRead {
19
+ /** The variable it sets. */
20
+ var: string;
21
+ /** The `terraform.roots` entry whose state holds the value. */
22
+ root: string;
23
+ /** The output, then the keys into its value. */
24
+ output: string[];
25
+ }
26
+
27
+ /** What filling a root's reads came to. */
28
+ export interface FilledStateReads {
29
+ /** The values, by var. Complete only when `waiting` and `errors` are empty. */
30
+ vars: Record<string, unknown>;
31
+ /** Reads whose upstream has no such value yet: the root waits for these upstreams to apply. */
32
+ waiting: Array<{ read: TerraformStateRead; why: string }>;
33
+ /** Upstreams whose state could not be read at all. */
34
+ errors: string[];
35
+ }
36
+
37
+ /** What is wrong with a step's `reads`, empty when nothing is. */
38
+ export function stateReadsProblems(reads: unknown): string[] {
39
+ if (reads === undefined) return [];
40
+ if (!Array.isArray(reads)) return ["reads must be a list of { var, root, output }"];
41
+ const problems: string[] = [];
42
+ const vars = new Set<string>();
43
+ for (const [i, r] of reads.entries()) {
44
+ const read = r as Partial<TerraformStateRead> | null;
45
+ if (!read || typeof read !== "object") {
46
+ problems.push(`reads[${i}] must be { var, root, output }`);
47
+ continue;
48
+ }
49
+ if (typeof read.var !== "string" || read.var === "") problems.push(`reads[${i}].var must name a variable`);
50
+ else if (vars.has(read.var)) problems.push(`reads[${i}] sets ${read.var}, which an earlier read sets too`);
51
+ else vars.add(read.var);
52
+ if (typeof read.root !== "string" || read.root === "") problems.push(`reads[${i}].root must name a terraform.roots entry`);
53
+ if (!Array.isArray(read.output) || read.output.length === 0 || !read.output.every((k) => typeof k === "string" && k !== "")) problems.push(`reads[${i}].output must be the output's name, then any keys into its value`);
54
+ }
55
+ return problems;
56
+ }
57
+
58
+ /** The value a read names in its upstream's outputs, or undefined when the outputs do not have it. */
59
+ export function readOutputValue(outputs: Record<string, unknown>, output: readonly string[]): { value: unknown } | undefined {
60
+ const [name, ...keys] = output;
61
+ if (name === undefined || !(name in outputs)) return undefined;
62
+ let v: unknown = outputs[name];
63
+ for (const k of keys) {
64
+ if (!v || typeof v !== "object" || Array.isArray(v) || !(k in (v as Record<string, unknown>))) return undefined;
65
+ v = (v as Record<string, unknown>)[k];
66
+ }
67
+ return { value: v };
68
+ }
69
+
70
+ /**
71
+ * Fill `reads` from each upstream's outputs, read once per upstream through
72
+ * `outputsOf`, which throws when the state cannot be read.
73
+ */
74
+ export async function fillStateReads(reads: readonly TerraformStateRead[], outputsOf: (root: string) => Promise<Record<string, unknown>>): Promise<FilledStateReads> {
75
+ const result: FilledStateReads = { vars: {}, waiting: [], errors: [] };
76
+ const cache = new Map<string, Promise<Record<string, unknown>>>();
77
+ for (const read of reads) {
78
+ if (!cache.has(read.root)) cache.set(read.root, outputsOf(read.root));
79
+ let outputs: Record<string, unknown>;
80
+ try {
81
+ outputs = await cache.get(read.root)!;
82
+ } catch (err) {
83
+ result.errors.push(`it reads ${read.var} from ${read.root}, whose state could not be read: ${err instanceof Error ? err.message : String(err)}`);
84
+ continue;
85
+ }
86
+ const got = readOutputValue(outputs, read.output);
87
+ if (!got) {
88
+ result.waiting.push({ read, why: Object.keys(outputs).length === 0 ? "has no state yet" : `has no output ${read.output.join(".")} yet` });
89
+ continue;
90
+ }
91
+ result.vars[read.var] = got.value;
92
+ }
93
+ return result;
94
+ }
95
+
96
+ /** Why a root with waiting reads was not planned, naming what to apply first. */
97
+ export function waitingReadsReason(waiting: FilledStateReads["waiting"]): string {
98
+ const upstreams = [...new Set(waiting.map((w) => w.read.root))].sort();
99
+ return `not planned: ${waiting.map((w) => `it reads ${w.read.var} from ${w.read.root}, which ${w.why}`).join("; ")}; apply ${upstreams.join(", ")} first`;
100
+ }
101
+
102
+ /**
103
+ * The reads an Atmos instance's edges file lists (`{ reads: [{ var,
104
+ * upstream, output }] }`, as terragucci's `atmos write` leaves beside each
105
+ * instance), with each upstream mapped to its `terraform.roots` entry by
106
+ * `rootOf` (by default the upstream's own path).
107
+ */
108
+ export function atmosStateReads(edges: unknown, rootOf: (upstream: string) => string = (u) => u): TerraformStateRead[] {
109
+ const reads = (edges as { reads?: unknown } | null)?.reads;
110
+ if (!Array.isArray(reads)) return [];
111
+ return reads.flatMap((r) => {
112
+ const read = r as { var?: unknown; upstream?: unknown; output?: unknown } | null;
113
+ if (!read || typeof read.var !== "string" || typeof read.upstream !== "string" || !Array.isArray(read.output) || !read.output.every((k) => typeof k === "string")) return [];
114
+ return [{ var: read.var, root: rootOf(read.upstream), output: read.output as string[] }];
115
+ });
116
+ }
@@ -0,0 +1,75 @@
1
+ /**
2
+ * The sources a root uses (chant#3924): module calls and provider addresses
3
+ * down the local tree, implied providers, a Terragrunt unit's source, the
4
+ * normal forms, and the local calls that cannot be followed.
5
+ */
6
+ import { join } from "node:path";
7
+ import { describe, expect, test } from "vitest";
8
+ import { usedSources } from "./used-sources";
9
+
10
+ const FIX = join(import.meta.dirname, "__fixtures__", "used-sources");
11
+ const ROOT = join(FIX, "root");
12
+
13
+ describe("usedSources", () => {
14
+ test("lists the root's and its local modules' sources with their block paths", async () => {
15
+ const { used } = await usedSources(ROOT);
16
+ const rows = used.map((u) => [u.kind, u.block, u.source, u.normal]);
17
+ expect(rows).toEqual(
18
+ expect.arrayContaining([
19
+ ["provider", "required_providers.aws", "hashicorp/aws", "registry.terraform.io/hashicorp/aws"],
20
+ ["module", "module.vpc", "terraform-aws-modules/vpc/aws", "registry.terraform.io/terraform-aws-modules/vpc/aws"],
21
+ ["module", "module.net", "./modules/net", "./modules/net"],
22
+ ["provider", "provider.random", "hashicorp/random", "registry.terraform.io/hashicorp/random"],
23
+ ["provider", "provider.google", "hashicorp/google", "registry.terraform.io/hashicorp/google"],
24
+ ["module", "module.net > module.sg", "git::https://git.acme.dev/sg.git?ref=v1.0.0", "git::https://git.acme.dev/sg.git"],
25
+ ["provider", "module.net > required_providers.thing", "Registry.Acme.dev/acme/thing", "registry.acme.dev/acme/thing"],
26
+ ["provider", "module.net > provider.aws", "hashicorp/aws", "registry.terraform.io/hashicorp/aws"],
27
+ ]),
28
+ );
29
+ });
30
+
31
+ test("names the file each source is written in, relative to the project", async () => {
32
+ const { used } = await usedSources(ROOT);
33
+ expect(used.find((u) => u.block === "module.net > module.sg")!.file).toBe("modules/net/main.tf");
34
+ expect(used.find((u) => u.block === "module.net > required_providers.thing")!.file).toBe("modules/net/versions.tf.json");
35
+ expect(used.find((u) => u.block === "provider.google")!.file).toBe("extra.tofu");
36
+ });
37
+
38
+ test("says what implies a provider, and never implies the binary's own", async () => {
39
+ const { used } = await usedSources(ROOT);
40
+ expect(used.find((u) => u.block === "provider.random")!.implied).toBe('resource "random_id" "x"');
41
+ expect(used.find((u) => u.block === "module.net > provider.aws")!.implied).toBe('resource "aws_vpc" "v"');
42
+ expect(used.some((u) => u.block.endsWith("provider.terraform"))).toBe(false);
43
+ expect(used.some((u) => u.block === "provider.aws")).toBe(false);
44
+ });
45
+
46
+ test("names local calls it cannot follow and stops at a cycle", async () => {
47
+ const { used, unread } = await usedSources(ROOT);
48
+ expect(unread.map((u) => [u.block, u.source])).toEqual([
49
+ ["module.gone", "./modules/missing"],
50
+ ["module.outside", "../../outside"],
51
+ ]);
52
+ expect(unread[0]!.why).toContain("does not exist");
53
+ expect(unread[1]!.why).toContain("outside the project");
54
+ expect(used.filter((u) => u.block.startsWith("module.net > module.back > "))).toEqual([]);
55
+ });
56
+
57
+ test("reads only the root with followLocal off", async () => {
58
+ const { used, unread } = await usedSources(ROOT, { followLocal: false });
59
+ expect(used.some((u) => u.block.startsWith("module.net > "))).toBe(false);
60
+ expect(unread).toEqual([]);
61
+ });
62
+
63
+ test("leaves .tofu files out for terraform, and uses OpenTofu's registry for tofu", async () => {
64
+ const tf = await usedSources(ROOT, { binary: "terraform" });
65
+ expect(tf.used.some((u) => u.block === "provider.google")).toBe(false);
66
+ const tofu = await usedSources(ROOT, { binary: "tofu" });
67
+ expect(tofu.used.find((u) => u.block === "provider.google")!.normal).toBe("registry.opentofu.org/hashicorp/google");
68
+ expect(tofu.used.find((u) => u.block === "module.vpc")!.normal).toBe("registry.opentofu.org/terraform-aws-modules/vpc/aws");
69
+ });
70
+
71
+ test("reads a Terragrunt unit's terraform source", async () => {
72
+ const { used } = await usedSources(join(FIX, "unit"), { project: FIX });
73
+ expect(used).toEqual([{ kind: "module", file: "unit/terragrunt.hcl", block: "terraform", source: "tfr:///acme/vpc/aws?version=1.0.0", normal: "registry.terraform.io/acme/vpc/aws" }]);
74
+ });
75
+ });
@@ -0,0 +1,204 @@
1
+ /**
2
+ * Every module source and provider address a root uses (chant#3924), read
3
+ * from its files before `init` downloads anything, so a product can check
4
+ * them against an allow list (terragucci's `sources:`) or a proxy map
5
+ * (chant#4606) in a check job.
6
+ *
7
+ * For a root and, with `followLocal`, each local module it calls (`./` and
8
+ * `../`, followed down to the modules they call), it reads each `module`
9
+ * block's `source`, each `required_providers` entry, and the providers a
10
+ * module uses with no entry: a `provider` block, or a resource, data or
11
+ * ephemeral block's `provider` argument or type prefix, which `init` installs
12
+ * as `hashicorp/<name>`. In the root itself it also reads a Terragrunt unit's
13
+ * `terraform { source }`. Files are `.tf`, `.tofu` and their `.json` forms;
14
+ * `binary` narrows them to the set that binary reads, with OpenTofu's rule
15
+ * that `x.tofu` hides `x.tf`.
16
+ *
17
+ * Each source carries its file and its block path from the root
18
+ * (`module.a > module.b > required_providers.x`) and its normal form
19
+ * (./registry/source-forms.ts). A local call that cannot be followed
20
+ * (outside the project, or missing) is named in `unread`, since what it
21
+ * uses cannot be read.
22
+ */
23
+ import { existsSync, readdirSync, readFileSync, statSync } from "node:fs";
24
+ import { join, posix, relative, sep } from "node:path";
25
+ import { loadHcl2json, type Hcl2Json } from "@intentius/chant/terraform/hcl2json";
26
+ import { isJsonConfigFile, jsonConfigTree, selectConfigFiles } from "./hcl/files";
27
+ import { impliedProvider, normalizeModuleSource, providerAddress, withoutQuery, type SourceBinary } from "./registry/source-forms";
28
+ import type { RegistryProxy } from "./registry/proxies";
29
+
30
+ /** One module source or provider address a root uses. */
31
+ export interface UsedSource {
32
+ kind: "module" | "provider";
33
+ /** The file it is written in, relative to the project, `/`-separated. */
34
+ file: string;
35
+ /**
36
+ * The block that names it, after the module calls that reach it:
37
+ * `module.vpc`, `terraform` for a terragrunt.hcl's source,
38
+ * `required_providers.aws`, or `provider.aws` for an implied one.
39
+ */
40
+ block: string;
41
+ /** The source or address as written (`hashicorp/<name>` for an implied provider). */
42
+ source: string;
43
+ /**
44
+ * The normal form: for a module, the source without its query, normalised
45
+ * ({@link normalizeModuleSource}); for a provider, `<host>/<namespace>/<type>`
46
+ * ({@link providerAddress}), or the source as written when it is not an address.
47
+ */
48
+ normal: string;
49
+ /** For an implied provider: the block that implies it. */
50
+ implied?: string;
51
+ }
52
+
53
+ /** A local module call that could not be followed. */
54
+ export interface UnreadSource {
55
+ file: string;
56
+ block: string;
57
+ source: string;
58
+ why: string;
59
+ }
60
+
61
+ export interface UsedSourcesOptions {
62
+ /** Follow `./` and `../` module calls down the tree. Default true. */
63
+ followLocal?: boolean;
64
+ /** The project's top: file paths are relative to it, and a call that leaves it is not followed. Default the root's directory. */
65
+ project?: string;
66
+ /** Read only the files this binary reads, and default provider hosts to its registry. Default: every `.tf` and `.tofu` file, Terraform's registry. */
67
+ binary?: SourceBinary;
68
+ /** The HCL parser. Default: core's lazily loaded hcl2json. */
69
+ parser?: Hcl2Json;
70
+ /** Registry proxies (chant#4606): a source through one gets its upstream's normal form. */
71
+ proxies?: readonly RegistryProxy[];
72
+ }
73
+
74
+ type Tree = Record<string, unknown>;
75
+
76
+ /** A block list as hcl2json writes it (an array) or a JSON file may (one object). */
77
+ const bodies = (v: unknown): Tree[] => (Array.isArray(v) ? (v as Tree[]) : v && typeof v === "object" ? [v as Tree] : []);
78
+
79
+ const ALL_SUFFIXES = [".tf", ".tofu", ".tf.json", ".tofu.json"];
80
+
81
+ /** The configuration files of one module directory, plus terragrunt.hcl when `terragrunt`. */
82
+ function moduleFiles(dir: string, terragrunt: boolean, binary: SourceBinary | undefined): string[] {
83
+ if (!existsSync(dir) || !statSync(dir).isDirectory()) return [];
84
+ const names = readdirSync(dir).filter((n) => {
85
+ try {
86
+ return statSync(join(dir, n)).isFile();
87
+ } catch {
88
+ return false;
89
+ }
90
+ });
91
+ const config = binary ? selectConfigFiles(names, binary) : names.filter((n) => ALL_SUFFIXES.some((s) => n.endsWith(s))).sort();
92
+ return terragrunt && names.includes("terragrunt.hcl") ? [...config, "terragrunt.hcl"] : config;
93
+ }
94
+
95
+ /** The local name of the provider a resource, data or ephemeral block uses: its `provider` argument's, else its type's prefix. */
96
+ function providerOf(type: string, body: Tree): string {
97
+ const ref = typeof body.provider === "string" ? /^(?:\$\{)?([A-Za-z][A-Za-z0-9_-]*)/.exec(body.provider) : null;
98
+ return ref ? ref[1]! : type.split("_")[0]!;
99
+ }
100
+
101
+ const isLocal = (source: string): boolean => /^\.\.?\//.test(source);
102
+
103
+ interface DirRead {
104
+ providers: UsedSource[];
105
+ calls: { file: string; block: string; source: string }[];
106
+ }
107
+
108
+ async function readDir(abs: string, shown: string, prefix: string, top: boolean, o: Required<Pick<UsedSourcesOptions, "parser">> & UsedSourcesOptions): Promise<DirRead> {
109
+ const providers: UsedSource[] = [];
110
+ const calls: DirRead["calls"] = [];
111
+ const declared = new Set<string>();
112
+ const implied = new Map<string, { file: string; what: string }>();
113
+ const provider = (file: string, block: string, source: string, what?: string): UsedSource => ({
114
+ kind: "provider",
115
+ file,
116
+ block,
117
+ source,
118
+ normal: providerAddress(source, { binary: o.binary, proxies: o.proxies }) ?? source.trim().toLowerCase(),
119
+ ...(what ? { implied: what } : {}),
120
+ });
121
+ for (const name of moduleFiles(abs, top, o.binary)) {
122
+ const file = shown === "." ? name : posix.join(shown, name);
123
+ const text = readFileSync(join(abs, name), "utf-8");
124
+ let tree: Tree;
125
+ try {
126
+ tree = (isJsonConfigFile(name) ? jsonConfigTree(text) : await o.parser.parse(file, text)) as Tree;
127
+ } catch (e) {
128
+ throw new Error(`${file} could not be read (${(e as Error).message.split("\n")[0]}), so its module and provider sources are unknown`);
129
+ }
130
+ if (name === "terragrunt.hcl") {
131
+ for (const b of bodies(tree.terraform)) if (typeof b.source === "string") calls.push({ file, block: `${prefix}terraform`, source: b.source });
132
+ continue;
133
+ }
134
+ for (const [call, list] of Object.entries((tree.module as Tree | undefined) ?? {})) {
135
+ for (const b of bodies(list)) if (typeof b.source === "string") calls.push({ file, block: `${prefix}module.${call}`, source: b.source });
136
+ }
137
+ for (const t of bodies(tree.terraform)) {
138
+ for (const req of bodies(t.required_providers)) {
139
+ for (const [local, v] of Object.entries(req)) {
140
+ declared.add(local);
141
+ const source = v && typeof v === "object" && typeof (v as Tree).source === "string" ? ((v as Tree).source as string) : `hashicorp/${local}`;
142
+ providers.push(provider(file, `${prefix}required_providers.${local}`, source));
143
+ }
144
+ }
145
+ }
146
+ for (const local of Object.keys((tree.provider as Tree | undefined) ?? {})) if (!implied.has(local)) implied.set(local, { file, what: `provider "${local}"` });
147
+ for (const kind of ["resource", "data", "ephemeral"]) {
148
+ for (const [type, named] of Object.entries((tree[kind] as Tree | undefined) ?? {})) {
149
+ for (const [name, list] of Object.entries((named as Tree) ?? {})) {
150
+ for (const b of bodies(list)) {
151
+ const local = providerOf(type, b);
152
+ if (!implied.has(local)) implied.set(local, { file, what: `${kind} "${type}" "${name}"` });
153
+ }
154
+ }
155
+ }
156
+ }
157
+ }
158
+ for (const [local, at] of implied) {
159
+ const source = declared.has(local) ? undefined : impliedProvider(local);
160
+ if (source) providers.push(provider(at.file, `${prefix}provider.${local}`, source, at.what));
161
+ }
162
+ return { providers, calls };
163
+ }
164
+
165
+ /**
166
+ * Every module source and provider address the root at `dir` uses, and each
167
+ * local call that could not be followed. Throws, naming the file, when a
168
+ * file cannot be parsed, so a check fails loudly instead of checking less.
169
+ */
170
+ export async function usedSources(dir: string, options: UsedSourcesOptions = {}): Promise<{ used: UsedSource[]; unread: UnreadSource[] }> {
171
+ const project = options.project ?? dir;
172
+ const o = { ...options, parser: options.parser ?? (await loadHcl2json()) };
173
+ const follow = options.followLocal !== false;
174
+ const used: UsedSource[] = [];
175
+ const unread: UnreadSource[] = [];
176
+ const shownOf = (abs: string) => relative(project, abs).split(sep).join("/") || ".";
177
+
178
+ const walk = async (rel: string, prefix: string, chain: string[], top: boolean): Promise<void> => {
179
+ const { providers, calls } = await readDir(join(project, rel), rel, prefix, top, o);
180
+ used.push(...providers);
181
+ for (const c of calls) {
182
+ used.push({ kind: "module", file: c.file, block: c.block, source: c.source, normal: isLocal(c.source) ? withoutQuery(c.source) : normalizeModuleSource(withoutQuery(c.source), { binary: o.binary, proxies: o.proxies }) });
183
+ if (!follow || !isLocal(c.source)) continue;
184
+ // A Terragrunt source's `//` marks the directory it copies; the module is the path with it read as one `/`.
185
+ const path = withoutQuery(c.source).replace(/\/\/+/g, "/");
186
+ const next = posix.normalize(posix.join(rel, path)).replace(/\/+$/, "") || ".";
187
+ if (next === ".." || next.startsWith("../")) {
188
+ unread.push({ file: c.file, block: c.block, source: c.source, why: "it is outside the project, so the sources it uses cannot be read" });
189
+ continue;
190
+ }
191
+ if (!existsSync(join(project, next))) {
192
+ unread.push({ file: c.file, block: c.block, source: c.source, why: `${next} does not exist, so the sources it uses cannot be read` });
193
+ continue;
194
+ }
195
+ if (chain.includes(next) || moduleFiles(join(project, next), false, o.binary).length === 0) continue;
196
+ await walk(next, `${c.block} > `, [...chain, next], false);
197
+ }
198
+ };
199
+
200
+ const start = shownOf(dir);
201
+ if (start === ".." || start.startsWith("../")) throw new Error(`${dir} is outside the project ${project}`);
202
+ await walk(start, "", [start], true);
203
+ return { used, unread };
204
+ }