@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,198 @@
1
+ /**
2
+ * Reading a module registry the way the CLIs do: service discovery, the
3
+ * versions list, a version's download location, and a tarball's bytes.
4
+ * Lifted from terragucci's `publish/registry.ts` (18f6431595) for chant#4565.
5
+ *
6
+ * `registryLocation` reads the download answer in one of three ways:
7
+ * `terraform` takes only the `X-Terraform-Get` header (200 or 204), `tofu`
8
+ * takes the JSON body's `location` on a 200 and the header otherwise, and the
9
+ * default takes either, header first. A test that wants to know whether
10
+ * Terraform can install from a registry asks with `reads: "terraform"`.
11
+ *
12
+ * Every request to the registry's host carries the token the CLIs would send
13
+ * it (chant#4563): `TF_TOKEN_<host>`, else the CLI config's or
14
+ * credentials.tfrc.json's `credentials` block, found by core's
15
+ * `terraform/credentials` reader. The token goes in the `Authorization`
16
+ * header and nowhere else; an error names the variable to set, never a value.
17
+ *
18
+ * A host with no service discovery of its own, such as a Nexus repository
19
+ * (chant#4605), is read through `services`: the modules.v1 URL per host, as
20
+ * a CLI's `host` block gives it, held in memory so no CLI config file is
21
+ * written. Such a host may be a bare name with no dot, as a `host` block's
22
+ * label may be.
23
+ *
24
+ * With `proxies` (chant#4606), a module whose host a proxy stands for is
25
+ * read through the proxy: an Artifactory remote repository at
26
+ * `<proxy host>/<repo>__<ns>/<name>/<system>`, a Nexus proxy or group
27
+ * repository through its host block's modules.v1 URL. A source with no host
28
+ * is read at registry.terraform.io, so through the proxy that stands for it.
29
+ */
30
+ import { gunzipSync } from "node:zlib";
31
+ import { credentialsHost, terraformToken, tokenVariable } from "@intentius/chant/terraform/credentials";
32
+ import { DISCOVERY, parseRegistrySource } from "./address.js";
33
+ import { DEFAULT_PROXY_UPSTREAM, proxyLabels, proxyRoute } from "./proxies.js";
34
+ import { DOWNLOAD_HEADER, readVersions } from "./writer.js";
35
+ const globalFetch = (url, init) => fetch(url, init);
36
+ export class RegistryError extends Error {
37
+ constructor(message) {
38
+ super(message);
39
+ this.name = "RegistryError";
40
+ }
41
+ }
42
+ /** The token a read sends to a host, by the options. */
43
+ export const readToken = (host, o) => (o.token === false ? undefined : (o.token ?? terraformToken(host, o.credentials)));
44
+ const httpGet = async (fetchFn, url, token, authorization) => {
45
+ const auth = authorization ?? (token ? `Bearer ${token}` : undefined);
46
+ try {
47
+ return await fetchFn(url, { method: "GET", redirect: "follow", ...(auth ? { headers: { Authorization: auth } } : {}) });
48
+ }
49
+ catch (e) {
50
+ const cause = e.cause?.message;
51
+ throw new RegistryError(`${url}: ${e.message}${cause ? ` (${cause})` : ""}`);
52
+ }
53
+ };
54
+ /** The error for a status that is not OK, saying where a token goes when the registry refused one. */
55
+ const answered = (url, status, host, token) => {
56
+ if (status !== 401 && status !== 403)
57
+ return new RegistryError(`${url} answered ${status}`);
58
+ const variable = tokenVariable(host);
59
+ const where = variable ? `${variable} or a credentials "${credentialsHost(host)}" block` : `a credentials "${credentialsHost(host) ?? host}" block in credentials.tfrc.json`;
60
+ return new RegistryError(token ? `${url} answered ${status}: the registry refused the token for ${host}` : `${url} answered ${status}: the registry wants a token; set ${where}`);
61
+ };
62
+ const optionsOf = (o) => (typeof o === "function" ? { fetch: o } : (o ?? {}));
63
+ /** The modules.v1 URL `services` names for a host, matched without case. */
64
+ const serviceFor = (host, o) => {
65
+ if (!o.services)
66
+ return undefined;
67
+ const h = host.toLowerCase();
68
+ for (const [k, v] of Object.entries(o.services))
69
+ if (k.toLowerCase() === h)
70
+ return v;
71
+ return undefined;
72
+ };
73
+ /** A source split by the hosts the options know as well as by the usual host shape. */
74
+ const sourceOf = (source, o) => parseRegistrySource(source, [...Object.keys(o.services ?? {}), ...proxyLabels(o.proxies)]);
75
+ /**
76
+ * Where a read of `source` goes: the source as written, or, when a proxy
77
+ * stands for its host, the proxy's host and namespace, with the options
78
+ * carrying the proxy's modules.v1 URL when it has one. A source with no host
79
+ * is taken at registry.terraform.io only when proxies are given, so a read
80
+ * without them refuses it as before.
81
+ */
82
+ function readTarget(source, o) {
83
+ let s = sourceOf(source, o);
84
+ if (!s && o.proxies?.length) {
85
+ const base = source.split("?")[0].split("//")[0];
86
+ const parts = base.split("/");
87
+ if (parts.length === 3 && parts.every((p) => /^[0-9A-Za-z][0-9A-Za-z_-]*$/.test(p)))
88
+ s = { host: DEFAULT_PROXY_UPSTREAM, namespace: parts[0], name: parts[1], system: parts[2] };
89
+ }
90
+ if (!s)
91
+ return undefined;
92
+ const r = proxyRoute(s, o.proxies);
93
+ if (!r)
94
+ return { s, o };
95
+ return { s: { ...s, host: r.host, namespace: r.namespace }, o: r.modules ? { ...o, services: { ...o.services, [r.host]: r.modules } } : o };
96
+ }
97
+ /** The modules.v1 base a host's service discovery names, ending in `/`. */
98
+ export async function modulesBase(host, options) {
99
+ const o = optionsOf(options);
100
+ const mapped = serviceFor(host, o);
101
+ if (mapped !== undefined)
102
+ return mapped.endsWith("/") ? mapped : `${mapped}/`;
103
+ const disco = `${o.scheme ?? "https"}://${host}/${DISCOVERY}`;
104
+ const token = readToken(host, o);
105
+ const res = await httpGet(o.fetch ?? globalFetch, disco, token, o.authorization);
106
+ if (!res.ok)
107
+ throw answered(disco, res.status, host, token ?? o.authorization);
108
+ let base;
109
+ try {
110
+ base = (await res.json())["modules.v1"];
111
+ }
112
+ catch {
113
+ throw new RegistryError(`${disco} is not JSON`);
114
+ }
115
+ if (typeof base !== "string")
116
+ throw new RegistryError(`${disco} names no modules.v1`);
117
+ const url = new URL(base, disco).toString();
118
+ return url.endsWith("/") ? url : `${url}/`;
119
+ }
120
+ /** Every version a registry lists for a module; none when it has no versions file. */
121
+ export async function registryVersions(source, options) {
122
+ const t = readTarget(source, optionsOf(options));
123
+ if (!t)
124
+ throw new RegistryError(`${source} is not a registry source`);
125
+ const { s, o } = t;
126
+ const token = readToken(s.host, o);
127
+ const url = `${await modulesBase(s.host, { ...o, token: token ?? false })}${s.namespace}/${s.name}/${s.system}/versions`;
128
+ const res = await httpGet(o.fetch ?? globalFetch, url, token, o.authorization);
129
+ if (res.status === 404)
130
+ return [];
131
+ if (!res.ok)
132
+ throw answered(url, res.status, s.host, token ?? o.authorization);
133
+ return readVersions(await res.text());
134
+ }
135
+ /** The location a download answer gives, read as `reads` says, or undefined when it gives none that way. */
136
+ export async function locationFromAnswer(res, reads = "either") {
137
+ const header = res.headers.get(DOWNLOAD_HEADER) || undefined;
138
+ if (reads === "terraform")
139
+ return res.status === 200 || res.status === 204 ? header : undefined;
140
+ let body;
141
+ if (res.status === 200) {
142
+ try {
143
+ const doc = (await res.json());
144
+ if (typeof doc.location === "string" && doc.location)
145
+ body = doc.location;
146
+ }
147
+ catch {
148
+ // OpenTofu fails on a 200 whose body is not JSON, before it looks at the header.
149
+ if (reads === "tofu")
150
+ return undefined;
151
+ }
152
+ }
153
+ return reads === "tofu" ? (body ?? header) : (header ?? body);
154
+ }
155
+ /** Where a registry says one version is, resolved against the download URL when it is relative. */
156
+ export async function registryLocation(source, version, options) {
157
+ const t = readTarget(source, optionsOf(options));
158
+ if (!t)
159
+ throw new RegistryError(`${source} is not a registry source`);
160
+ const s = t.s;
161
+ const o = t.o;
162
+ const token = readToken(s.host, o);
163
+ const url = `${await modulesBase(s.host, { ...o, token: token ?? false })}${s.namespace}/${s.name}/${s.system}/${version}/download`;
164
+ const res = await httpGet(o.fetch ?? globalFetch, url, token, o.authorization);
165
+ if (res.status === 404)
166
+ throw new RegistryError(`${s.host}/${s.namespace}/${s.name}/${s.system} has no version ${version}`);
167
+ if (!res.ok)
168
+ throw answered(url, res.status, s.host, token ?? o.authorization);
169
+ const reads = o.reads ?? "either";
170
+ let location = await locationFromAnswer(res, reads);
171
+ if (!location) {
172
+ throw new RegistryError(reads === "terraform" ? `${url} sends no ${DOWNLOAD_HEADER} header, and Terraform reads only the header` : `${url} names no location`);
173
+ }
174
+ if (/^(\.{0,2}\/)/.test(location))
175
+ location = new URL(location, url).toString();
176
+ return location;
177
+ }
178
+ /**
179
+ * The module archive a tarball location holds, uncompressed: the bytes a
180
+ * release's digest is of. The host's token goes along only when the location
181
+ * has no query string; a signed URL carries its own grant, and a bucket
182
+ * refuses a request that has both a signature and an `Authorization` header.
183
+ */
184
+ export async function fetchTarball(location, options) {
185
+ const o = optionsOf(options);
186
+ const u = new URL(location);
187
+ const token = u.search || (u.protocol !== "https:" && u.protocol !== "http:") ? undefined : readToken(u.host, o);
188
+ const res = await httpGet(o.fetch ?? globalFetch, location, token);
189
+ if (!res.ok)
190
+ throw answered(location, res.status, u.host, token);
191
+ const body = Buffer.from(await res.arrayBuffer());
192
+ try {
193
+ return gunzipSync(body);
194
+ }
195
+ catch {
196
+ throw new RegistryError(`${location} is not a gzip archive`);
197
+ }
198
+ }
@@ -0,0 +1,16 @@
1
+ /**
2
+ * The versions a module registry lists: plain `major.minor.patch`, with an
3
+ * optional leading `v` on input. Lifted from terragucci's `publish/semver.ts`
4
+ * (18f6431595), cut to what the registry protocol needs (chant#4565).
5
+ */
6
+ export interface Semver {
7
+ major: number;
8
+ minor: number;
9
+ patch: number;
10
+ }
11
+ export declare function parseSemver(text: string): Semver | undefined;
12
+ export declare const formatSemver: (v: Semver) => string;
13
+ export declare function compareSemver(a: Semver, b: Semver): number;
14
+ /** The newest of a list of versions; entries that are not versions are skipped. */
15
+ export declare function newest(versions: string[]): Semver | undefined;
16
+ //# sourceMappingURL=semver.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"semver.d.ts","sourceRoot":"","sources":["../../src/registry/semver.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,MAAM,WAAW,MAAM;IACrB,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;CACf;AAID,wBAAgB,WAAW,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAG5D;AAED,eAAO,MAAM,YAAY,MAAO,MAAM,KAAG,MAA4C,CAAC;AAEtF,wBAAgB,aAAa,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,GAAG,MAAM,CAE1D;AAED,mFAAmF;AACnF,wBAAgB,MAAM,CAAC,QAAQ,EAAE,MAAM,EAAE,GAAG,MAAM,GAAG,SAAS,CAO7D"}
@@ -0,0 +1,24 @@
1
+ /**
2
+ * The versions a module registry lists: plain `major.minor.patch`, with an
3
+ * optional leading `v` on input. Lifted from terragucci's `publish/semver.ts`
4
+ * (18f6431595), cut to what the registry protocol needs (chant#4565).
5
+ */
6
+ const RE = /^v?(\d+)\.(\d+)\.(\d+)$/;
7
+ export function parseSemver(text) {
8
+ const m = RE.exec(text.trim());
9
+ return m ? { major: +m[1], minor: +m[2], patch: +m[3] } : undefined;
10
+ }
11
+ export const formatSemver = (v) => `${v.major}.${v.minor}.${v.patch}`;
12
+ export function compareSemver(a, b) {
13
+ return a.major - b.major || a.minor - b.minor || a.patch - b.patch;
14
+ }
15
+ /** The newest of a list of versions; entries that are not versions are skipped. */
16
+ export function newest(versions) {
17
+ let best;
18
+ for (const v of versions) {
19
+ const p = parseSemver(v);
20
+ if (p && (!best || compareSemver(p, best) > 0))
21
+ best = p;
22
+ }
23
+ return best;
24
+ }
@@ -0,0 +1,24 @@
1
+ import type { RegistryFiles, RegistryHandler } from "./handler.js";
2
+ /** A registry written into a directory, read back as bytes. Keys that climb out of the directory read as missing. */
3
+ export declare function dirFiles(root: string): RegistryFiles;
4
+ export interface ServeOptions {
5
+ /** Default 127.0.0.1. A loopback address keeps the registry off the network. */
6
+ hostname?: string;
7
+ /** Default 0, a free port. */
8
+ port?: number;
9
+ /** PEM key and certificate: the server speaks HTTPS, as both CLIs require for discovery. */
10
+ tls?: {
11
+ key: string | Buffer;
12
+ cert: string | Buffer;
13
+ };
14
+ }
15
+ export interface ServedRegistry {
16
+ /** `<scheme>://<hostname>:<port>`, no trailing slash. */
17
+ url: string;
18
+ /** `<hostname>:<port>`, the host a registry source names. */
19
+ host: string;
20
+ close(): Promise<void>;
21
+ }
22
+ /** Serves a handler on a local socket until `close`. */
23
+ export declare function serveRegistry(handler: RegistryHandler, options?: ServeOptions): Promise<ServedRegistry>;
24
+ //# sourceMappingURL=serve.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"serve.d.ts","sourceRoot":"","sources":["../../src/registry/serve.ts"],"names":[],"mappings":"AAWA,OAAO,KAAK,EAAE,aAAa,EAAE,eAAe,EAAE,MAAM,WAAW,CAAC;AAEhE,qHAAqH;AACrH,wBAAgB,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,aAAa,CAQpD;AAED,MAAM,WAAW,YAAY;IAC3B,gFAAgF;IAChF,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,8BAA8B;IAC9B,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,4FAA4F;IAC5F,GAAG,CAAC,EAAE;QAAE,GAAG,EAAE,MAAM,GAAG,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAAA;KAAE,CAAC;CACvD;AAED,MAAM,WAAW,cAAc;IAC7B,yDAAyD;IACzD,GAAG,EAAE,MAAM,CAAC;IACZ,6DAA6D;IAC7D,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;CACxB;AAUD,wDAAwD;AACxD,wBAAsB,aAAa,CAAC,OAAO,EAAE,eAAe,EAAE,OAAO,GAAE,YAAiB,GAAG,OAAO,CAAC,cAAc,CAAC,CA+BjH"}
@@ -0,0 +1,64 @@
1
+ /**
2
+ * A registry's files on a loopback socket, for tests that run real
3
+ * `terraform init` and `tofu init` (terragucci#1287) and for a local preview.
4
+ * The answers come from `registryHandler` in ./handler; this file adds the
5
+ * directory reader and the Node server around it.
6
+ */
7
+ import { existsSync, readFileSync, statSync } from "node:fs";
8
+ import { createServer as createHttpServer } from "node:http";
9
+ import { createServer as createHttpsServer } from "node:https";
10
+ import { join, normalize, sep } from "node:path";
11
+ /** A registry written into a directory, read back as bytes. Keys that climb out of the directory read as missing. */
12
+ export function dirFiles(root) {
13
+ const base = normalize(root);
14
+ return async (key) => {
15
+ const file = normalize(join(base, key));
16
+ if (file !== base && !file.startsWith(base.endsWith(sep) ? base : `${base}${sep}`))
17
+ return {};
18
+ if (!existsSync(file) || !statSync(file).isFile())
19
+ return {};
20
+ return { body: readFileSync(file) };
21
+ };
22
+ }
23
+ const readBody = (req) => new Promise((resolve, reject) => {
24
+ const chunks = [];
25
+ req.on("data", (c) => chunks.push(c));
26
+ req.on("end", () => resolve(Buffer.concat(chunks)));
27
+ req.on("error", reject);
28
+ });
29
+ /** Serves a handler on a local socket until `close`. */
30
+ export async function serveRegistry(handler, options = {}) {
31
+ const hostname = options.hostname ?? "127.0.0.1";
32
+ const scheme = options.tls ? "https" : "http";
33
+ const listener = async (req, res) => {
34
+ try {
35
+ const method = req.method ?? "GET";
36
+ const body = method === "GET" || method === "HEAD" ? undefined : await readBody(req);
37
+ const headers = new Headers();
38
+ for (const [k, v] of Object.entries(req.headers))
39
+ if (typeof v === "string")
40
+ headers.set(k, v);
41
+ const answer = await handler(new Request(`${scheme}://${req.headers.host ?? hostname}${req.url ?? "/"}`, { method, headers, ...(body ? { body: new Uint8Array(body) } : {}) }));
42
+ const out = {};
43
+ answer.headers.forEach((v, k) => (out[k] = v));
44
+ res.writeHead(answer.status, out);
45
+ res.end(answer.body ? Buffer.from(await answer.arrayBuffer()) : undefined);
46
+ }
47
+ catch (e) {
48
+ res.writeHead(500, { "content-type": "text/plain" });
49
+ res.end(String(e.message));
50
+ }
51
+ };
52
+ const server = options.tls ? createHttpsServer({ key: options.tls.key, cert: options.tls.cert }, (q, r) => void listener(q, r)) : createHttpServer((q, r) => void listener(q, r));
53
+ await new Promise((resolve, reject) => {
54
+ server.once("error", reject);
55
+ server.listen(options.port ?? 0, hostname, () => resolve());
56
+ });
57
+ const { port } = server.address();
58
+ const host = `${hostname.includes(":") ? `[${hostname}]` : hostname}:${port}`;
59
+ return {
60
+ url: `${scheme}://${host}`,
61
+ host,
62
+ close: () => new Promise((resolve, reject) => server.close((e) => (e ? reject(e) : resolve()))),
63
+ };
64
+ }
@@ -0,0 +1,79 @@
1
+ import { type RegistryProxy } from "./proxies.js";
2
+ /** The public registry's host, which a source with no host names under Terraform. */
3
+ export declare const PUBLIC_REGISTRY_HOST = "registry.terraform.io";
4
+ /** OpenTofu's public registry's host. */
5
+ export declare const OPENTOFU_REGISTRY_HOST = "registry.opentofu.org";
6
+ /** The hosts a provider or registry module address written without one is read from, Terraform's first. */
7
+ export declare const DEFAULT_REGISTRIES: readonly string[];
8
+ /** Which binary reads an address: it decides the default registry host. */
9
+ export type SourceBinary = "terraform" | "tofu";
10
+ /** The default registry host for a binary. */
11
+ export declare const defaultRegistryHost: (binary?: SourceBinary) => string;
12
+ /** How a source is read: the binary (for default hosts) and the registry proxies in front of upstreams. */
13
+ export interface SourceFormOptions {
14
+ binary?: SourceBinary;
15
+ proxies?: readonly RegistryProxy[];
16
+ }
17
+ /** A binary alone, or the options. */
18
+ export type SourceFormArg = SourceBinary | SourceFormOptions;
19
+ /** A source without its `?` query, trimmed. */
20
+ export declare const withoutQuery: (source: string) => string;
21
+ /**
22
+ * Every form a module source matches as: as written without its query, then
23
+ * its registry forms. A hostless registry source adds itself at each default
24
+ * registry; one at a default registry adds its hostless form and its form at
25
+ * the other default registry; `tfr:///x` adds `x` at each default registry
26
+ * and `tfr://<host>/x` adds `<host>/x`. Any other source (local, git, OCI,
27
+ * an archive URL) has the one form. A source through one of `proxies` adds
28
+ * the forms of the upstream source it stands for.
29
+ */
30
+ export declare function moduleSourceForms(source: string, options?: SourceFormOptions): string[];
31
+ /**
32
+ * The module a pin-free source names, written one way: a registry source
33
+ * with no host gets the public registry's (or the binary's), a host is
34
+ * lower-cased, and Terragrunt's `tfr:///ns/name/system` and
35
+ * `tfr://host/ns/name/system` become the plain registry form. Other sources
36
+ * (git, OCI, an archive URL) are kept as they are. Moved from
37
+ * ../pin/consumers.ts (chant#4599). A source through one of `proxies` is
38
+ * the upstream source it stands for.
39
+ */
40
+ export declare function normalizeModuleSource(module: string, options?: SourceFormArg): string;
41
+ /** A provider address's parts. */
42
+ export interface ProviderAddress {
43
+ host: string;
44
+ namespace: string;
45
+ type: string;
46
+ /** `<host>/<namespace>/<type>`. */
47
+ address: string;
48
+ }
49
+ /**
50
+ * A `required_providers` source in its full form, `<host>/<namespace>/<type>`,
51
+ * lower case, with the binary's default host when it names none and the
52
+ * `hashicorp` namespace when it names only a type. Undefined for a string
53
+ * that is not a provider address. A source through one of `proxies`
54
+ * (`<host>/<repo>__<ns>/<type>` on Artifactory) gives the upstream's address.
55
+ */
56
+ export declare function parseProviderAddress(source: string, options?: SourceFormArg): ProviderAddress | undefined;
57
+ /** {@link parseProviderAddress}'s full form, or undefined. */
58
+ export declare const providerAddress: (source: string, options?: SourceFormArg) => string | undefined;
59
+ /**
60
+ * The provider `init` installs for a local name that has no
61
+ * `required_providers` entry: `hashicorp/<name>`. Undefined for the binary's
62
+ * own `terraform` provider (terraform_data, terraform_remote_state), which is
63
+ * never downloaded.
64
+ */
65
+ export declare const impliedProvider: (local: string) => string | undefined;
66
+ /**
67
+ * Every form a provider address matches as: in lower case as written, and,
68
+ * when written without a host or at a default registry, its hostless form
69
+ * and its form at each default registry. A source through one of `proxies`
70
+ * adds the forms of its upstream address.
71
+ */
72
+ export declare function providerAddressForms(source: string, options?: SourceFormOptions): string[];
73
+ /**
74
+ * The provider addresses a `.terraform.lock.hcl` names, in the order
75
+ * written, as the lock file writes them (the full form). Read by text: the
76
+ * lock file is generated and every entry is a `provider "<address>" {` line.
77
+ */
78
+ export declare function lockedProviders(lockText: string): string[];
79
+ //# sourceMappingURL=source-forms.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"source-forms.d.ts","sourceRoot":"","sources":["../../src/registry/source-forms.ts"],"names":[],"mappings":"AA8BA,OAAO,EAA4C,KAAK,aAAa,EAAE,MAAM,WAAW,CAAC;AAEzF,qFAAqF;AACrF,eAAO,MAAM,oBAAoB,0BAA0B,CAAC;AAC5D,yCAAyC;AACzC,eAAO,MAAM,sBAAsB,0BAA0B,CAAC;AAC9D,2GAA2G;AAC3G,eAAO,MAAM,kBAAkB,EAAE,SAAS,MAAM,EAAmD,CAAC;AAEpG,2EAA2E;AAC3E,MAAM,MAAM,YAAY,GAAG,WAAW,GAAG,MAAM,CAAC;AAEhD,8CAA8C;AAC9C,eAAO,MAAM,mBAAmB,YAAY,YAAY,KAAiB,MAA6E,CAAC;AAEvJ,2GAA2G;AAC3G,MAAM,WAAW,iBAAiB;IAChC,MAAM,CAAC,EAAE,YAAY,CAAC;IACtB,OAAO,CAAC,EAAE,SAAS,aAAa,EAAE,CAAC;CACpC;AAED,sCAAsC;AACtC,MAAM,MAAM,aAAa,GAAG,YAAY,GAAG,iBAAiB,CAAC;AAI7D,+CAA+C;AAC/C,eAAO,MAAM,YAAY,WAAY,MAAM,KAAG,MAAsC,CAAC;AAcrF;;;;;;;;GAQG;AACH,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,iBAAiB,GAAG,MAAM,EAAE,CAKvF;AAwBD;;;;;;;;GAQG;AACH,wBAAgB,qBAAqB,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,aAAa,GAAG,MAAM,CAcrF;AAED,kCAAkC;AAClC,MAAM,WAAW,eAAe;IAC9B,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,EAAE,MAAM,CAAC;IAClB,IAAI,EAAE,MAAM,CAAC;IACb,mCAAmC;IACnC,OAAO,EAAE,MAAM,CAAC;CACjB;AAKD;;;;;;GAMG;AACH,wBAAgB,oBAAoB,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,aAAa,GAAG,eAAe,GAAG,SAAS,CAYzG;AAED,8DAA8D;AAC9D,eAAO,MAAM,eAAe,WAAY,MAAM,YAAY,aAAa,KAAG,MAAM,GAAG,SAA2D,CAAC;AAE/I;;;;;GAKG;AACH,eAAO,MAAM,eAAe,UAAW,MAAM,KAAG,MAAM,GAAG,SAAqF,CAAC;AAE/I;;;;;GAKG;AACH,wBAAgB,oBAAoB,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,iBAAiB,GAAG,MAAM,EAAE,CAK1F;AAaD;;;;GAIG;AACH,wBAAgB,eAAe,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,EAAE,CAO1D"}
@@ -0,0 +1,202 @@
1
+ /**
2
+ * Module sources and provider addresses in a normal form (chant#3924), beside
3
+ * the registry address parsing of ./address.ts (chant#4565), so a product's
4
+ * allow list, the catalog's consumer scan and the proxy forms (chant#4606)
5
+ * compare sources one way. No Node imports, so an edge function can carry it.
6
+ *
7
+ * A module source is compared without its `?` query: a `ref`, `version` or
8
+ * `tag` pins a release and does not move where the code comes from. A
9
+ * registry source written without a host is read by Terraform from
10
+ * registry.terraform.io and by OpenTofu from registry.opentofu.org, so it
11
+ * stands for both; Terragrunt's `tfr:///` and `tfr://<host>/` are the same
12
+ * sources. Registry shapes follow `classifyModuleSource`
13
+ * (../lint/post-synth/module-source.ts), which mirrors
14
+ * hashicorp/terraform-registry-address.
15
+ *
16
+ * A provider address is `<host>/<namespace>/<type>` in lower case. A
17
+ * `required_providers` source without a host gets the binary's default
18
+ * registry, one with only a type gets the `hashicorp` namespace (Terraform's
19
+ * legacy reading), and a provider a module uses with no entry at all is the
20
+ * one `init` installs for it, `hashicorp/<name>`; the binary's own
21
+ * `terraform` provider is never downloaded and has no address.
22
+ *
23
+ * With `proxies` (chant#4606, ./proxies.ts), a source written through an
24
+ * Artifactory remote repository or a Nexus proxy host block stands for its
25
+ * upstream source: its forms include the upstream's, and its normal form
26
+ * and provider address are the upstream's, so it compares equal to the
27
+ * plain source.
28
+ */
29
+ import { classifyModuleSource } from "../lint/post-synth/module-source.js";
30
+ import { parseRegistrySource } from "./address.js";
31
+ import { moduleThroughProxy, providerThroughProxy } from "./proxies.js";
32
+ /** The public registry's host, which a source with no host names under Terraform. */
33
+ export const PUBLIC_REGISTRY_HOST = "registry.terraform.io";
34
+ /** OpenTofu's public registry's host. */
35
+ export const OPENTOFU_REGISTRY_HOST = "registry.opentofu.org";
36
+ /** The hosts a provider or registry module address written without one is read from, Terraform's first. */
37
+ export const DEFAULT_REGISTRIES = [PUBLIC_REGISTRY_HOST, OPENTOFU_REGISTRY_HOST];
38
+ /** The default registry host for a binary. */
39
+ export const defaultRegistryHost = (binary = "terraform") => (binary === "tofu" ? OPENTOFU_REGISTRY_HOST : PUBLIC_REGISTRY_HOST);
40
+ const optionsOf = (o) => (typeof o === "string" ? { binary: o } : (o ?? {}));
41
+ /** A source without its `?` query, trimmed. */
42
+ export const withoutQuery = (source) => source.trim().split("?")[0];
43
+ /** A hostless registry module source (`<ns>/<name>/<system>`, optionally `//<subdir>`), as the registry protocol shapes one. */
44
+ function isHostlessModule(source) {
45
+ const base = source.split("//")[0];
46
+ return base.split("/").length === 3 && classifyModuleSource(base).kind === "registry";
47
+ }
48
+ /** The source without a default registry's host, when it starts with one. */
49
+ function atDefaultRegistry(source) {
50
+ const host = DEFAULT_REGISTRIES.find((h) => source.toLowerCase().startsWith(`${h}/`));
51
+ return host ? source.slice(host.length + 1) : undefined;
52
+ }
53
+ /**
54
+ * Every form a module source matches as: as written without its query, then
55
+ * its registry forms. A hostless registry source adds itself at each default
56
+ * registry; one at a default registry adds its hostless form and its form at
57
+ * the other default registry; `tfr:///x` adds `x` at each default registry
58
+ * and `tfr://<host>/x` adds `<host>/x`. Any other source (local, git, OCI,
59
+ * an archive URL) has the one form. A source through one of `proxies` adds
60
+ * the forms of the upstream source it stands for.
61
+ */
62
+ export function moduleSourceForms(source, options) {
63
+ const out = plainModuleForms(source);
64
+ const via = moduleThroughProxy(source, options?.proxies);
65
+ if (via)
66
+ for (const f of plainModuleForms(via.upstream))
67
+ if (!out.includes(f))
68
+ out.push(f);
69
+ return out;
70
+ }
71
+ function plainModuleForms(source) {
72
+ const written = withoutQuery(source);
73
+ const out = [written];
74
+ const add = (...forms) => {
75
+ for (const f of forms)
76
+ if (!out.includes(f))
77
+ out.push(f);
78
+ };
79
+ const tfr = /^tfr:\/\/([^/]*)\/(.+)$/.exec(written);
80
+ if (tfr) {
81
+ if (tfr[1])
82
+ add(`${tfr[1].toLowerCase()}/${tfr[2]}`);
83
+ else
84
+ add(...DEFAULT_REGISTRIES.map((h) => `${h}/${tfr[2]}`));
85
+ if (!tfr[1])
86
+ add(tfr[2]);
87
+ return out;
88
+ }
89
+ if (isHostlessModule(written)) {
90
+ add(...DEFAULT_REGISTRIES.map((h) => `${h}/${written}`));
91
+ return out;
92
+ }
93
+ const hostless = atDefaultRegistry(written);
94
+ if (hostless && isHostlessModule(hostless))
95
+ add(hostless, ...DEFAULT_REGISTRIES.map((h) => `${h}/${hostless}`));
96
+ return out;
97
+ }
98
+ /**
99
+ * The module a pin-free source names, written one way: a registry source
100
+ * with no host gets the public registry's (or the binary's), a host is
101
+ * lower-cased, and Terragrunt's `tfr:///ns/name/system` and
102
+ * `tfr://host/ns/name/system` become the plain registry form. Other sources
103
+ * (git, OCI, an archive URL) are kept as they are. Moved from
104
+ * ../pin/consumers.ts (chant#4599). A source through one of `proxies` is
105
+ * the upstream source it stands for.
106
+ */
107
+ export function normalizeModuleSource(module, options) {
108
+ const o = optionsOf(options);
109
+ const binary = o.binary ?? "terraform";
110
+ const via = moduleThroughProxy(module, o.proxies);
111
+ let s = (via ? via.upstream : module).trim();
112
+ const tfr = /^tfr:\/\/([^/]*)\/(.*)$/.exec(s);
113
+ if (tfr)
114
+ s = `${tfr[1] || defaultRegistryHost(binary)}/${tfr[2]}`;
115
+ const [base, ...rest] = s.split("//");
116
+ const parts = base.split("/");
117
+ const registryShaped = (n) => parts.length === n && parts.every((p) => p.length > 0) && !base.includes(":");
118
+ if (registryShaped(3) && !/[.]/.test(parts[0]))
119
+ return [`${defaultRegistryHost(binary)}/${base}`, ...rest].join("//");
120
+ const reg = parts.length === 4 ? parseRegistrySource(base) : undefined;
121
+ if (reg)
122
+ return [`${reg.host}/${parts.slice(1).join("/")}`, ...rest].join("//");
123
+ return s;
124
+ }
125
+ const PROVIDER_PART = /^[0-9a-z](?:[0-9a-z_-]{0,62}[0-9a-z])?$/;
126
+ const HOST = /^[a-z0-9.-]+(?::\d+)?$/;
127
+ /**
128
+ * A `required_providers` source in its full form, `<host>/<namespace>/<type>`,
129
+ * lower case, with the binary's default host when it names none and the
130
+ * `hashicorp` namespace when it names only a type. Undefined for a string
131
+ * that is not a provider address. A source through one of `proxies`
132
+ * (`<host>/<repo>__<ns>/<type>` on Artifactory) gives the upstream's address.
133
+ */
134
+ export function parseProviderAddress(source, options) {
135
+ const o = optionsOf(options);
136
+ const binary = o.binary ?? "terraform";
137
+ const via = providerThroughProxy(source, o.proxies);
138
+ const parts = (via ? via.upstream : source).trim().toLowerCase().split("/");
139
+ let host, namespace, type;
140
+ if (parts.length === 1)
141
+ [host, namespace, type] = [defaultRegistryHost(binary), "hashicorp", parts[0]];
142
+ else if (parts.length === 2)
143
+ [host, namespace, type] = [defaultRegistryHost(binary), parts[0], parts[1]];
144
+ else if (parts.length === 3)
145
+ [host, namespace, type] = parts;
146
+ else
147
+ return undefined;
148
+ if (!HOST.test(host) || !PROVIDER_PART.test(namespace) || !PROVIDER_PART.test(type))
149
+ return undefined;
150
+ return { host, namespace, type, address: `${host}/${namespace}/${type}` };
151
+ }
152
+ /** {@link parseProviderAddress}'s full form, or undefined. */
153
+ export const providerAddress = (source, options) => parseProviderAddress(source, options)?.address;
154
+ /**
155
+ * The provider `init` installs for a local name that has no
156
+ * `required_providers` entry: `hashicorp/<name>`. Undefined for the binary's
157
+ * own `terraform` provider (terraform_data, terraform_remote_state), which is
158
+ * never downloaded.
159
+ */
160
+ export const impliedProvider = (local) => (local === "terraform" ? undefined : `hashicorp/${local.toLowerCase()}`);
161
+ /**
162
+ * Every form a provider address matches as: in lower case as written, and,
163
+ * when written without a host or at a default registry, its hostless form
164
+ * and its form at each default registry. A source through one of `proxies`
165
+ * adds the forms of its upstream address.
166
+ */
167
+ export function providerAddressForms(source, options) {
168
+ const out = plainProviderForms(source);
169
+ const via = providerThroughProxy(source, options?.proxies);
170
+ if (via)
171
+ for (const f of plainProviderForms(via.upstream))
172
+ if (!out.includes(f))
173
+ out.push(f);
174
+ return out;
175
+ }
176
+ function plainProviderForms(source) {
177
+ const a = source.trim().toLowerCase();
178
+ const out = [a];
179
+ const add = (...forms) => {
180
+ for (const f of forms)
181
+ if (!out.includes(f))
182
+ out.push(f);
183
+ };
184
+ const hostless = a.split("/").length === 2 ? a : a.split("/").length === 1 && a ? `hashicorp/${a}` : atDefaultRegistry(a);
185
+ if (hostless && hostless.split("/").length === 2)
186
+ add(hostless, ...DEFAULT_REGISTRIES.map((h) => `${h}/${hostless}`));
187
+ return out;
188
+ }
189
+ /**
190
+ * The provider addresses a `.terraform.lock.hcl` names, in the order
191
+ * written, as the lock file writes them (the full form). Read by text: the
192
+ * lock file is generated and every entry is a `provider "<address>" {` line.
193
+ */
194
+ export function lockedProviders(lockText) {
195
+ const out = [];
196
+ for (const m of lockText.matchAll(/^\s*provider\s+"([^"]+)"\s*\{/gm)) {
197
+ const a = m[1].toLowerCase();
198
+ if (!out.includes(a))
199
+ out.push(a);
200
+ }
201
+ return out;
202
+ }
@@ -0,0 +1,28 @@
1
+ import { type ModuleAddress, type ModulePublishTarget, type ModuleTargetDeps } from "./module-target.js";
2
+ /** How an Artifactory module target is declared. */
3
+ export interface ArtifactoryModuleTargetOptions {
4
+ /** The Artifactory base, such as `https://acme.jfrog.io/artifactory`. A URL with no path gets `/artifactory`. */
5
+ url: string;
6
+ /** The local Terraform repository's key, such as `terraform-local`. */
7
+ repo: string;
8
+ /** The variable the access token is read from. Default `JFROG_ACCESS_TOKEN`. */
9
+ tokenEnv?: string;
10
+ /** The variable the user name is read from. Default `JFROG_USER`. */
11
+ userEnv?: string;
12
+ /** The user name, in place of `userEnv`. */
13
+ user?: string;
14
+ /** How long `reveal` waits for Artifactory to list the version. Default 30s. */
15
+ waitMs?: number;
16
+ /** How often it asks while it waits. Default 2s. */
17
+ pollMs?: number;
18
+ }
19
+ export declare const ARTIFACTORY_TOKEN_ENV = "JFROG_ACCESS_TOKEN";
20
+ export declare const ARTIFACTORY_USER_ENV = "JFROG_USER";
21
+ /** The Artifactory base a target deploys under, without a trailing slash. */
22
+ export declare function artifactoryBase(url: string): string;
23
+ /** The source consumers write for a module in the repository: `<host>/<repo>__<namespace>/<name>/<system>`. */
24
+ export declare const artifactorySource: (host: string, repo: string, a: ModuleAddress) => string;
25
+ /** The deploy path of one version's zip. */
26
+ export declare function artifactoryDeployUrl(base: string, repo: string, a: ModuleAddress, version: string): string;
27
+ export declare function artifactoryModuleTarget(options: ArtifactoryModuleTargetOptions, deps?: ModuleTargetDeps): ModulePublishTarget<"artifactory">;
28
+ //# sourceMappingURL=artifactory.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"artifactory.d.ts","sourceRoot":"","sources":["../../../src/registry/targets/artifactory.ts"],"names":[],"mappings":"AA6CA,OAAO,EAOL,KAAK,aAAa,EAClB,KAAK,mBAAmB,EACxB,KAAK,gBAAgB,EAEtB,MAAM,iBAAiB,CAAC;AAgBzB,oDAAoD;AACpD,MAAM,WAAW,8BAA8B;IAC7C,iHAAiH;IACjH,GAAG,EAAE,MAAM,CAAC;IACZ,uEAAuE;IACvE,IAAI,EAAE,MAAM,CAAC;IACb,gFAAgF;IAChF,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,qEAAqE;IACrE,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,4CAA4C;IAC5C,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,gFAAgF;IAChF,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,oDAAoD;IACpD,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,eAAO,MAAM,qBAAqB,uBAAuB,CAAC;AAC1D,eAAO,MAAM,oBAAoB,eAAe,CAAC;AAEjD,6EAA6E;AAC7E,wBAAgB,eAAe,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CASnD;AAED,+GAA+G;AAC/G,eAAO,MAAM,iBAAiB,SAAU,MAAM,QAAQ,MAAM,KAAK,aAAa,KAAG,MAAiE,CAAC;AAEnJ,4CAA4C;AAC5C,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC,EAAE,aAAa,EAAE,OAAO,EAAE,MAAM,GAAG,MAAM,CAG1G;AAID,wBAAgB,uBAAuB,CAAC,OAAO,EAAE,8BAA8B,EAAE,IAAI,GAAE,gBAAqB,GAAG,mBAAmB,CAAC,aAAa,CAAC,CA4IhJ"}