@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
@@ -1,201 +1,8 @@
1
1
  /**
2
- * Azure Blob Storage for an azurerm backend's state, over its REST API with
3
- * `fetch` and `node:crypto`: a blob's properties and metadata, its text, a
4
- * write (under a lease when one holds it), a snapshot, a delete, and the
5
- * blob lease the backend locks a state with.
6
- *
7
- * Requests are signed with Shared Key for an account key, carry a SAS as it
8
- * is, or carry a storage token the job's OIDC identity is traded for at Entra
9
- * ID (a client assertion, the way the azurerm backend does it).
10
- *
11
- * Lifted from terragucci's `report/azure-blob.ts` (c493e0e), without SAS
12
- * links, version reads and version listing.
2
+ * The Azure Blob Storage client an azurerm backend's state uses: chant
3
+ * core's (#4554, `@intentius/chant/object-store`): a blob's properties and
4
+ * metadata, a snapshot, a block blob put, and the blob lease the backend
5
+ * locks a state with, signed with Shared Key, a SAS, or an Entra token for
6
+ * the job's OIDC identity.
13
7
  */
14
- import { createHmac } from "node:crypto";
15
- import { readFileSync } from "node:fs";
16
- import { defaultFetch, encodePath, StoreError, xmlText } from "./object-store.js";
17
- /** The REST version every request names. */
18
- export const AZURE_VERSION = "2021-08-06";
19
- /** Entra ID's public cloud. */
20
- export const AZURE_AUTHORITY = "https://login.microsoftonline.com";
21
- const STORAGE_SCOPE = "https://storage.azure.com/.default";
22
- const hmac64 = (key, s) => createHmac("sha256", Buffer.from(key, "base64")).update(s, "utf8").digest("base64");
23
- /** The headers whose values Shared Key signs by position, in order. */
24
- const SIGNED = ["content-encoding", "content-language", "content-length", "content-md5", "content-type", "date", "if-modified-since", "if-match", "if-none-match", "if-unmodified-since", "range"];
25
- /** The Shared Key Authorization header for a request. */
26
- export function sharedKey(account, accountKey, method, rawUrl, headers) {
27
- const url = new URL(rawUrl);
28
- const h = Object.fromEntries(Object.entries(headers).map(([k, v]) => [k.toLowerCase(), v]));
29
- const fixed = SIGNED.map((n) => (n === "content-length" && h[n] === "0" ? "" : (h[n] ?? "")));
30
- const ms = Object.keys(h).filter((n) => n.startsWith("x-ms-")).sort().map((n) => `${n}:${h[n].trim()}\n`).join("");
31
- const params = new Map();
32
- for (const [k, v] of url.searchParams)
33
- params.set(k.toLowerCase(), [...(params.get(k.toLowerCase()) ?? []), v]);
34
- const query = [...params.keys()].sort().map((k) => `\n${k}:${params.get(k).sort().join(",")}`).join("");
35
- const toSign = [method, ...fixed].join("\n") + "\n" + ms + `/${account}${url.pathname}` + query;
36
- return `SharedKey ${account}:${hmac64(accountKey, toSign)}`;
37
- }
38
- /** Entra ID's error, or the start of the body. */
39
- const why = (text) => {
40
- try {
41
- const j = JSON.parse(text);
42
- if (j.error)
43
- return [j.error, j.error_description?.split("\r\n")[0]].filter(Boolean).join(": ");
44
- }
45
- catch {
46
- // Not JSON: Blob Storage answers in XML.
47
- }
48
- return [xmlText(text, "Code"), xmlText(text, "Message")?.split("\n")[0]].filter(Boolean).join(": ") || text.slice(0, 300);
49
- };
50
- export class AzureBlobClient {
51
- constructor(target, fetchFn = defaultFetch, readToken = (f) => readFileSync(f, "utf-8")) {
52
- this.target = target;
53
- this.fetchFn = fetchFn;
54
- this.readToken = readToken;
55
- }
56
- where(key) {
57
- return `az://${this.target.account}/${this.target.container}/${key}`;
58
- }
59
- blobUrl(key, query = "") {
60
- const t = this.target;
61
- const q = [query, "sas" in t ? t.sas.replace(/^\?/, "") : ""].filter(Boolean).join("&");
62
- return `${t.endpoint}/${t.container}/${encodePath(key)}${q ? `?${q}` : ""}`;
63
- }
64
- /** The storage token for the OIDC identity: once per client, again a minute before it expires. */
65
- async bearer(o) {
66
- const fetchToken = async () => {
67
- let assertion;
68
- try {
69
- assertion = this.readToken(o.tokenFile).trim();
70
- }
71
- catch (e) {
72
- throw new StoreError(`cannot read the OIDC token in ${o.tokenFile}: ${e.message}`);
73
- }
74
- if (!assertion)
75
- throw new StoreError(`the OIDC token file ${o.tokenFile} is empty`);
76
- const body = new URLSearchParams({
77
- client_id: o.clientId,
78
- scope: STORAGE_SCOPE,
79
- grant_type: "client_credentials",
80
- client_assertion_type: "urn:ietf:params:oauth:client-assertion-type:jwt-bearer",
81
- client_assertion: assertion,
82
- }).toString();
83
- const res = await this.fetchFn(`${o.authority}/${o.tenantId}/oauth2/v2.0/token`, { method: "POST", headers: { "content-type": "application/x-www-form-urlencoded" }, body });
84
- const text = await res.text();
85
- if (!res.ok)
86
- throw new StoreError(`Entra ID refused the OIDC token for client ${o.clientId}: ${res.status} ${why(text)}`);
87
- let j = {};
88
- try {
89
- j = JSON.parse(text);
90
- }
91
- catch {
92
- // Reported below as no token.
93
- }
94
- if (!j.access_token)
95
- throw new StoreError(`Entra ID answered client ${o.clientId} without a token`);
96
- return { token: j.access_token, expires: Date.now() + Number(j.expires_in ?? 3600) * 1000 };
97
- };
98
- const again = () => (this.token = fetchToken().catch((e) => {
99
- this.token = undefined;
100
- throw e;
101
- }));
102
- let held = await (this.token ?? again());
103
- if (held.expires - Date.now() < 60000)
104
- held = await again();
105
- return held.token;
106
- }
107
- /** The headers for one request, signed or with the bearer token. */
108
- async headers(method, url, extra) {
109
- const h = { "x-ms-date": new Date().toUTCString(), "x-ms-version": AZURE_VERSION, ...extra };
110
- const t = this.target;
111
- if ("accountKey" in t)
112
- return { ...h, authorization: sharedKey(t.account, t.accountKey, method, url, h) };
113
- if ("sas" in t)
114
- return h;
115
- return { ...h, authorization: `Bearer ${await this.bearer(t.oidc)}` };
116
- }
117
- async get(key) {
118
- const url = this.blobUrl(key);
119
- const res = await this.fetchFn(url, { method: "GET", headers: await this.headers("GET", url, {}) });
120
- if (res.status === 404)
121
- return undefined;
122
- if (!res.ok)
123
- throw new StoreError(`GET ${this.where(key)}: ${res.status} ${why(await res.text())}`);
124
- return res.text();
125
- }
126
- /** A blob's properties and metadata, from a HEAD. */
127
- async head(key) {
128
- const url = this.blobUrl(key);
129
- const res = await this.fetchFn(url, { method: "HEAD", headers: await this.headers("HEAD", url, {}) });
130
- if (res.status === 404)
131
- return { exists: false, metadata: {} };
132
- if (!res.ok)
133
- throw new StoreError(`HEAD ${this.where(key)}: ${res.status}`);
134
- const get = (n) => res.headers?.get(n) ?? undefined;
135
- const metadata = {};
136
- const all = res.headers;
137
- all?.forEach?.((v, k) => {
138
- if (k.toLowerCase().startsWith("x-ms-meta-"))
139
- metadata[k.toLowerCase().slice("x-ms-meta-".length)] = v;
140
- });
141
- const etag = get("etag");
142
- const leaseState = get("x-ms-lease-state");
143
- return { exists: true, ...(etag ? { etag } : {}), ...(leaseState ? { leaseState } : {}), metadata };
144
- }
145
- /** Take a snapshot of a blob: its id, the time Azure gives it. */
146
- async snapshot(key) {
147
- const url = this.blobUrl(key, "comp=snapshot");
148
- const res = await this.fetchFn(url, { method: "PUT", headers: await this.headers("PUT", url, { "content-length": "0" }) });
149
- if (!res.ok)
150
- throw new StoreError(`snapshot of ${this.where(key)}: ${res.status} ${why(await res.text())}`);
151
- const id = res.headers?.get("x-ms-snapshot");
152
- if (!id)
153
- throw new StoreError(`snapshot of ${this.where(key)}: Azure answered without x-ms-snapshot`);
154
- return id;
155
- }
156
- /** Write a block blob with metadata, under a lease when one holds it; with `ifNoneMatch`, only when there is none (false when one was there). */
157
- async putBlob(key, body, contentType, options = {}) {
158
- const url = this.blobUrl(key);
159
- const meta = Object.fromEntries(Object.entries(options.metadata ?? {}).map(([k, v]) => [`x-ms-meta-${k}`, v]));
160
- const extra = {
161
- "x-ms-blob-type": "BlockBlob",
162
- "content-type": contentType,
163
- "content-length": String(Buffer.byteLength(body)),
164
- ...meta,
165
- ...(options.leaseId ? { "x-ms-lease-id": options.leaseId } : {}),
166
- ...(options.ifNoneMatch ? { "if-none-match": "*" } : {}),
167
- };
168
- const res = await this.fetchFn(url, { method: "PUT", headers: await this.headers("PUT", url, extra), body });
169
- if (options.ifNoneMatch && (res.status === 409 || res.status === 412))
170
- return false;
171
- if (!res.ok)
172
- throw new StoreError(`PUT ${this.where(key)}: ${res.status} ${why(await res.text())}`);
173
- return true;
174
- }
175
- /** Delete a blob. One that is not there is deleted already. */
176
- async remove(key) {
177
- const url = this.blobUrl(key);
178
- const res = await this.fetchFn(url, { method: "DELETE", headers: await this.headers("DELETE", url, {}) });
179
- if (!res.ok && res.status !== 404)
180
- throw new StoreError(`DELETE ${this.where(key)}: ${res.status} ${why(await res.text())}`);
181
- }
182
- /** A blob lease: acquire one that never expires with the id given (false when one holds it), or release it. */
183
- async lease(key, action, id) {
184
- const url = this.blobUrl(key, "comp=lease");
185
- const extra = { "x-ms-lease-action": action, "content-length": "0", ...(action === "acquire" ? { "x-ms-lease-duration": "-1", "x-ms-proposed-lease-id": id } : { "x-ms-lease-id": id }) };
186
- const res = await this.fetchFn(url, { method: "PUT", headers: await this.headers("PUT", url, extra) });
187
- if (action === "acquire" && res.status === 409)
188
- return false;
189
- if (!res.ok)
190
- throw new StoreError(`${action} the lease of ${this.where(key)}: ${res.status} ${why(await res.text())}`);
191
- return true;
192
- }
193
- /** Set a blob's metadata, all of it, under its lease when one holds it. */
194
- async setMetadata(key, metadata, leaseId) {
195
- const url = this.blobUrl(key, "comp=metadata");
196
- const extra = { "content-length": "0", ...Object.fromEntries(Object.entries(metadata).map(([k, v]) => [`x-ms-meta-${k}`, v])), ...(leaseId ? { "x-ms-lease-id": leaseId } : {}) };
197
- const res = await this.fetchFn(url, { method: "PUT", headers: await this.headers("PUT", url, extra) });
198
- if (!res.ok)
199
- throw new StoreError(`set the metadata of ${this.where(key)}: ${res.status} ${why(await res.text())}`);
200
- }
201
- }
8
+ export { AZURE_AUTHORITY, AZURE_VERSION, AzureBlobClient, sharedKey } from "@intentius/chant/object-store";
@@ -1,62 +1,8 @@
1
- import { type StoreFetch } from "./object-store.js";
2
- export declare const GCS_ENDPOINT = "https://storage.googleapis.com";
3
- /** IAM Credentials, where a token for an impersonated service account comes from. */
4
- export declare const IAM_CREDENTIALS = "https://iamcredentials.googleapis.com";
5
- export interface GcsTarget {
6
- bucket: string;
7
- /** The JSON API's address, no trailing slash. Default https://storage.googleapis.com. */
8
- endpoint: string;
9
- /** The credentials file, read at the first request. */
10
- credentialsFile?: string;
11
- /** The credentials as JSON: a gcs backend's `credentials` given inline. */
12
- credentialsJson?: string;
13
- /** A bearer token used as it is. */
14
- accessToken?: string;
15
- /** The service account every request acts as, through IAM's generateAccessToken. */
16
- impersonate?: string;
17
- /** IAM Credentials' address, no trailing slash. */
18
- iamEndpoint?: string;
19
- }
20
- interface ServiceAccountKey {
21
- type: "service_account";
22
- client_email: string;
23
- private_key: string;
24
- private_key_id?: string;
25
- }
26
- /** A JWT the key signs for itself, which Google's APIs take as an access token for the scope it names. */
27
- export declare function selfSignedJwt(key: ServiceAccountKey, now: Date): string;
28
- export declare class GcsClient {
29
- readonly target: GcsTarget;
30
- private readonly fetchFn;
31
- private readonly readFile;
32
- private creds?;
33
- private token?;
34
- constructor(target: GcsTarget, fetchFn?: StoreFetch, readFile?: (file: string) => string);
35
- private where;
36
- private credentials;
37
- private post;
38
- /** The job's OIDC token, from the file or URL the credentials name. */
39
- private subjectToken;
40
- private impersonateWith;
41
- /** STS's federated token, then the impersonated service account's, when the credentials name one. */
42
- private exchange;
43
- /** The identity's own token: the one the target gives, a key's self-signed JWT, or STS's for the OIDC token. */
44
- private own;
45
- /** An access token: once per client, again a minute before it expires. */
46
- private bearer;
47
- private objectUrl;
48
- /** Write an object; with `ifGenerationMatch`, only when the object still has that generation (`0`: there is none), else StoreConflict. */
49
- put(key: string, body: string, contentType: string, ifGenerationMatch?: string): Promise<void>;
50
- get(key: string): Promise<string | undefined>;
51
- /** An object's metadata and never its body: whether it is there, and its generation. */
52
- head(key: string): Promise<{
53
- exists: boolean;
54
- generation?: string;
55
- }>;
56
- /** Write an object only when there is none (ifGenerationMatch=0): true when this write made it, false when one was there. */
57
- putIfAbsent(key: string, body: string, contentType: string): Promise<boolean>;
58
- /** Delete an object's live generation. One that is not there is deleted already. */
59
- remove(key: string): Promise<void>;
60
- }
61
- export {};
1
+ /**
2
+ * The Google Cloud Storage client a gcs backend's state uses: chant core's
3
+ * (#4554, `@intentius/chant/object-store`), over the JSON API with `fetch`
4
+ * and `node:crypto`. Writes are conditional on a generation
5
+ * (`ifGenerationMatch`); a lost write is a 412, a StoreConflict.
6
+ */
7
+ export { GCS_ENDPOINT, GcsClient, IAM_CREDENTIALS, selfSignedJwt, type GcsTarget } from "@intentius/chant/object-store";
62
8
  //# sourceMappingURL=gcs.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"gcs.d.ts","sourceRoot":"","sources":["../../src/state/gcs.ts"],"names":[],"mappings":"AAoBA,OAAO,EAAyD,KAAK,UAAU,EAAE,MAAM,gBAAgB,CAAC;AAExG,eAAO,MAAM,YAAY,mCAAmC,CAAC;AAC7D,qFAAqF;AACrF,eAAO,MAAM,eAAe,0CAA0C,CAAC;AAIvE,MAAM,WAAW,SAAS;IACxB,MAAM,EAAE,MAAM,CAAC;IACf,yFAAyF;IACzF,QAAQ,EAAE,MAAM,CAAC;IACjB,uDAAuD;IACvD,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,2EAA2E;IAC3E,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,oCAAoC;IACpC,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,oFAAoF;IACpF,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,mDAAmD;IACnD,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAWD,UAAU,iBAAiB;IACzB,IAAI,EAAE,iBAAiB,CAAC;IACxB,YAAY,EAAE,MAAM,CAAC;IACrB,WAAW,EAAE,MAAM,CAAC;IACpB,cAAc,CAAC,EAAE,MAAM,CAAC;CACzB;AAMD,0GAA0G;AAC1G,wBAAgB,aAAa,CAAC,GAAG,EAAE,iBAAiB,EAAE,GAAG,EAAE,IAAI,GAAG,MAAM,CAKvE;AAgBD,qBAAa,SAAS;IAIR,QAAQ,CAAC,MAAM,EAAE,SAAS;IAAE,OAAO,CAAC,QAAQ,CAAC,OAAO;IAA6B,OAAO,CAAC,QAAQ,CAAC,QAAQ;IAHtH,OAAO,CAAC,KAAK,CAAC,CAAc;IAC5B,OAAO,CAAC,KAAK,CAAC,CAAiB;IAE/B,YAAqB,MAAM,EAAE,SAAS,EAAmB,OAAO,GAAE,UAAyB,EAAmB,QAAQ,GAAE,CAAC,IAAI,EAAE,MAAM,KAAK,MAAwC,EAAI;IAEtL,OAAO,CAAC,KAAK;IAIb,OAAO,CAAC,WAAW;YAcL,IAAI;IAWlB,uEAAuE;YACzD,YAAY;YA2BZ,eAAe;IAO7B,qGAAqG;YACvF,QAAQ;IAoBtB,gHAAgH;YAClG,GAAG;IAMjB,0EAA0E;YAC5D,MAAM;IAiBpB,OAAO,CAAC,SAAS;IAIjB,0IAA0I;IACpI,GAAG,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,EAAE,iBAAiB,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAMnG;IAEK,GAAG,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC,CAKlD;IAED,wFAAwF;IAClF,IAAI,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;QAAE,MAAM,EAAE,OAAO,CAAC;QAAC,UAAU,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC,CAYzE;IAED,6HAA6H;IACvH,WAAW,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAQlF;IAED,oFAAoF;IAC9E,MAAM,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAGvC;CACF"}
1
+ {"version":3,"file":"gcs.d.ts","sourceRoot":"","sources":["../../src/state/gcs.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,OAAO,EAAE,YAAY,EAAE,SAAS,EAAE,eAAe,EAAE,aAAa,EAAE,KAAK,SAAS,EAAE,MAAM,+BAA+B,CAAC"}
package/dist/state/gcs.js CHANGED
@@ -1,224 +1,7 @@
1
1
  /**
2
- * Google Cloud Storage for a gcs backend's state, over its JSON API with
3
- * `fetch` and `node:crypto`: an object's text and generation, a write, a
4
- * write only when there is no object (the lock), and a delete.
5
- *
6
- * The identity, first match wins: a bearer token (`access_token`, or
7
- * GOOGLE_OAUTH_ACCESS_TOKEN), else a credentials file or its JSON:
8
- *
9
- * external_account the job's OIDC token, traded at Google's STS, then the
10
- * service account it impersonates.
11
- * service_account a key: requests carry a self-signed JWT.
12
- *
13
- * With `impersonate_service_account`, every request carries that service
14
- * account's token, which the identity asks IAM for.
15
- *
16
- * Lifted from terragucci's `report/gcs.ts` (c493e0e), without signed URLs,
17
- * version listing and the re-plan's traded tokens.
2
+ * The Google Cloud Storage client a gcs backend's state uses: chant core's
3
+ * (#4554, `@intentius/chant/object-store`), over the JSON API with `fetch`
4
+ * and `node:crypto`. Writes are conditional on a generation
5
+ * (`ifGenerationMatch`); a lost write is a 412, a StoreConflict.
18
6
  */
19
- import { createSign } from "node:crypto";
20
- import { readFileSync } from "node:fs";
21
- import { defaultFetch, encodeStrict, StoreConflict, StoreError } from "./object-store.js";
22
- export const GCS_ENDPOINT = "https://storage.googleapis.com";
23
- /** IAM Credentials, where a token for an impersonated service account comes from. */
24
- export const IAM_CREDENTIALS = "https://iamcredentials.googleapis.com";
25
- const CLOUD_PLATFORM = "https://www.googleapis.com/auth/cloud-platform";
26
- const READ_WRITE = "https://www.googleapis.com/auth/devstorage.read_write";
27
- const b64url = (b) => Buffer.from(b).toString("base64url");
28
- /** A JWT the key signs for itself, which Google's APIs take as an access token for the scope it names. */
29
- export function selfSignedJwt(key, now) {
30
- const iat = Math.floor(now.getTime() / 1000);
31
- const head = b64url(JSON.stringify({ alg: "RS256", typ: "JWT", ...(key.private_key_id ? { kid: key.private_key_id } : {}) }));
32
- const body = b64url(JSON.stringify({ iss: key.client_email, sub: key.client_email, scope: READ_WRITE, iat, exp: iat + 3600 }));
33
- return `${head}.${body}.${createSign("RSA-SHA256").update(`${head}.${body}`).sign(key.private_key).toString("base64url")}`;
34
- }
35
- /** Google's error message, or the start of the body. */
36
- const why = (text) => {
37
- try {
38
- const j = JSON.parse(text);
39
- if (typeof j.error === "object" && j.error?.message)
40
- return j.error.message;
41
- if (typeof j.error === "string")
42
- return [j.error, j.error_description].filter(Boolean).join(": ");
43
- }
44
- catch {
45
- // Not JSON.
46
- }
47
- return text.slice(0, 300);
48
- };
49
- export class GcsClient {
50
- constructor(target, fetchFn = defaultFetch, readFile = (f) => readFileSync(f, "utf-8")) {
51
- this.target = target;
52
- this.fetchFn = fetchFn;
53
- this.readFile = readFile;
54
- }
55
- where(key) {
56
- return `gs://${this.target.bucket}/${key}`;
57
- }
58
- credentials() {
59
- if (this.creds)
60
- return this.creds;
61
- const source = this.target.credentialsFile ?? "the backend's credentials";
62
- let c;
63
- try {
64
- c = JSON.parse(this.target.credentialsJson ?? this.readFile(this.target.credentialsFile ?? ""));
65
- }
66
- catch (e) {
67
- throw new StoreError(`cannot read the credentials in ${source}: ${e.message}`);
68
- }
69
- if (c.type === "external_account" && c.token_url && c.audience && c.credential_source && (c.credential_source.file || c.credential_source.url))
70
- return (this.creds = c);
71
- if (c.type === "service_account" && c.client_email && c.private_key)
72
- return (this.creds = c);
73
- throw new StoreError(`${source} is not an external_account file with a credential_source file or url, nor a service_account key`);
74
- }
75
- async post(url, headers, body, what) {
76
- const res = await this.fetchFn(url, { method: "POST", headers, body });
77
- const text = await res.text();
78
- if (!res.ok)
79
- throw new StoreError(`${what}: ${res.status} ${why(text)}`);
80
- try {
81
- return JSON.parse(text);
82
- }
83
- catch {
84
- throw new StoreError(`${what}: the answer is not JSON`);
85
- }
86
- }
87
- /** The job's OIDC token, from the file or URL the credentials name. */
88
- async subjectToken(c) {
89
- const src = c.credential_source;
90
- let text;
91
- if (src.file) {
92
- try {
93
- text = this.readFile(src.file);
94
- }
95
- catch (e) {
96
- throw new StoreError(`cannot read the OIDC token in ${src.file}: ${e.message}`);
97
- }
98
- }
99
- else {
100
- const res = await this.fetchFn(src.url, { method: "GET", headers: src.headers ?? {} });
101
- text = await res.text();
102
- if (!res.ok)
103
- throw new StoreError(`the OIDC token at ${src.url.split("?")[0]}: ${res.status} ${why(text)}`);
104
- }
105
- const field = src.format?.type === "json" ? src.format.subject_token_field_name : undefined;
106
- let token = text.trim();
107
- if (field) {
108
- try {
109
- token = String(JSON.parse(text)[field] ?? "");
110
- }
111
- catch {
112
- throw new StoreError(`the job's OIDC token is not JSON with ${field}`);
113
- }
114
- }
115
- if (!token)
116
- throw new StoreError("the job's OIDC token is empty");
117
- return token;
118
- }
119
- async impersonateWith(url, bearer, sa) {
120
- const r = await this.post(url, { "content-type": "application/json", authorization: `Bearer ${bearer}` }, JSON.stringify({ scope: [CLOUD_PLATFORM], lifetime: "3600s" }), `impersonating ${sa}`);
121
- if (typeof r.accessToken !== "string")
122
- throw new StoreError(`IAM answered the impersonation of ${sa} without a token`);
123
- const at = typeof r.expireTime === "string" ? Date.parse(r.expireTime) : NaN;
124
- return { token: r.accessToken, expires: Number.isNaN(at) ? Date.now() + 3600000 : at };
125
- }
126
- /** STS's federated token, then the impersonated service account's, when the credentials name one. */
127
- async exchange(c) {
128
- const sts = await this.post(c.token_url, { "content-type": "application/x-www-form-urlencoded" }, new URLSearchParams({
129
- grant_type: "urn:ietf:params:oauth:grant-type:token-exchange",
130
- audience: c.audience,
131
- scope: CLOUD_PLATFORM,
132
- requested_token_type: "urn:ietf:params:oauth:token-type:access_token",
133
- subject_token: await this.subjectToken(c),
134
- subject_token_type: c.subject_token_type,
135
- }).toString(), `Google STS refused the OIDC token for ${c.audience}`);
136
- if (typeof sts.access_token !== "string")
137
- throw new StoreError("Google STS answered without a token");
138
- const fed = { token: sts.access_token, expires: Date.now() + Number(sts.expires_in ?? 3600) * 1000 };
139
- if (!c.service_account_impersonation_url)
140
- return fed;
141
- return this.impersonateWith(c.service_account_impersonation_url, fed.token, "the service account");
142
- }
143
- /** The identity's own token: the one the target gives, a key's self-signed JWT, or STS's for the OIDC token. */
144
- async own() {
145
- if (this.target.accessToken)
146
- return { token: this.target.accessToken, expires: Date.now() + 3600000 };
147
- const c = this.credentials();
148
- return c.type === "service_account" ? { token: selfSignedJwt(c, new Date()), expires: Date.now() + 3600000 } : this.exchange(c);
149
- }
150
- /** An access token: once per client, again a minute before it expires. */
151
- async bearer() {
152
- const sa = this.target.impersonate;
153
- const fresh = async () => {
154
- if (!sa)
155
- return this.own();
156
- const base = await this.own();
157
- return this.impersonateWith(`${(this.target.iamEndpoint ?? IAM_CREDENTIALS).replace(/\/+$/, "")}/v1/projects/-/serviceAccounts/${encodeURIComponent(sa)}:generateAccessToken`, base.token, sa);
158
- };
159
- const again = () => (this.token = fresh().catch((e) => {
160
- this.token = undefined;
161
- throw e;
162
- }));
163
- let held = await (this.token ?? again());
164
- if (held.expires - Date.now() < 60000)
165
- held = await again();
166
- return held.token;
167
- }
168
- objectUrl(key) {
169
- return `${this.target.endpoint}/storage/v1/b/${encodeURIComponent(this.target.bucket)}/o/${encodeStrict(key)}`;
170
- }
171
- /** Write an object; with `ifGenerationMatch`, only when the object still has that generation (`0`: there is none), else StoreConflict. */
172
- async put(key, body, contentType, ifGenerationMatch) {
173
- const t = this.target;
174
- const url = `${t.endpoint}/upload/storage/v1/b/${encodeURIComponent(t.bucket)}/o?uploadType=media&name=${encodeStrict(key)}${ifGenerationMatch !== undefined ? `&ifGenerationMatch=${encodeStrict(ifGenerationMatch)}` : ""}`;
175
- const res = await this.fetchFn(url, { method: "POST", headers: { "content-type": contentType, authorization: `Bearer ${await this.bearer()}` }, body });
176
- if (res.status === 412)
177
- throw new StoreConflict(`PUT ${this.where(key)}: 412, it changed since it was read`);
178
- if (!res.ok)
179
- throw new StoreError(`PUT ${this.where(key)}: ${res.status} ${why(await res.text())}`);
180
- }
181
- async get(key) {
182
- const res = await this.fetchFn(`${this.objectUrl(key)}?alt=media`, { method: "GET", headers: { authorization: `Bearer ${await this.bearer()}` } });
183
- if (res.status === 404)
184
- return undefined;
185
- if (!res.ok)
186
- throw new StoreError(`GET ${this.where(key)}: ${res.status} ${why(await res.text())}`);
187
- return res.text();
188
- }
189
- /** An object's metadata and never its body: whether it is there, and its generation. */
190
- async head(key) {
191
- const res = await this.fetchFn(this.objectUrl(key), { method: "GET", headers: { authorization: `Bearer ${await this.bearer()}` } });
192
- const text = await res.text();
193
- if (res.status === 404)
194
- return { exists: false };
195
- if (!res.ok)
196
- throw new StoreError(`GET ${this.where(key)} metadata: ${res.status} ${why(text)}`);
197
- let g;
198
- try {
199
- g = JSON.parse(text).generation;
200
- }
201
- catch {
202
- // Metadata that is not JSON: no generation.
203
- }
204
- return { exists: true, ...(g !== undefined && g !== null && g !== "" ? { generation: String(g) } : {}) };
205
- }
206
- /** Write an object only when there is none (ifGenerationMatch=0): true when this write made it, false when one was there. */
207
- async putIfAbsent(key, body, contentType) {
208
- try {
209
- await this.put(key, body, contentType, "0");
210
- return true;
211
- }
212
- catch (e) {
213
- if (e instanceof StoreConflict)
214
- return false;
215
- throw e;
216
- }
217
- }
218
- /** Delete an object's live generation. One that is not there is deleted already. */
219
- async remove(key) {
220
- const res = await this.fetchFn(this.objectUrl(key), { method: "DELETE", headers: { authorization: `Bearer ${await this.bearer()}` } });
221
- if (!res.ok && res.status !== 404)
222
- throw new StoreError(`DELETE ${this.where(key)}: ${res.status} ${why(await res.text())}`);
223
- }
224
- }
7
+ export { GCS_ENDPOINT, GcsClient, IAM_CREDENTIALS, selfSignedJwt } from "@intentius/chant/object-store";
@@ -1,37 +1,8 @@
1
1
  /**
2
2
  * What the state stores share: the fetch they call, their errors, and how a
3
- * key goes into a URL. Lifted from terragucci's `report/object-store.ts`
4
- * (c493e0e), cut to what a state's lock and write need.
3
+ * key goes into a URL. Since #4554 these are chant core's object-store
4
+ * definitions (`@intentius/chant/object-store`), so a store error thrown by
5
+ * a core client is the one a state caller catches.
5
6
  */
6
- export type StoreFetch = (url: string, init: {
7
- method: string;
8
- headers: Record<string, string>;
9
- body?: string | Uint8Array;
10
- }) => Promise<{
11
- ok: boolean;
12
- status: number;
13
- text(): Promise<string>;
14
- /** A store that sends no headers (or a test's fake) gets no conditional writes. */
15
- headers?: {
16
- get(name: string): string | null;
17
- };
18
- }>;
19
- export declare class StoreError extends Error {
20
- }
21
- /** A conditional write that lost: the object changed (412) or another write to it was in flight (409). */
22
- export declare class StoreConflict extends StoreError {
23
- }
24
- /** A condition on a write: the version the object must still have, or `*` for "there is no object yet". */
25
- export type StoreCondition = {
26
- ifMatch: string;
27
- } | {
28
- ifNoneMatch: "*";
29
- };
30
- /** A key as a URL path: each segment percent-encoded the strict way (RFC 3986), the slashes kept. */
31
- export declare const encodePath: (key: string) => string;
32
- export declare const encodeStrict: (v: string) => string;
33
- /** The first value of an XML element, unescaped. Enough for the small answers these stores send. */
34
- export declare const xmlText: (xml: string, tag: string) => string | undefined;
35
- /** The global fetch, as a StoreFetch. */
36
- export declare const defaultFetch: StoreFetch;
7
+ export { defaultFetch, encodePath, encodeStrict, StoreConflict, StoreError, xmlText, type StoreCondition, type StoreFetch } from "@intentius/chant/object-store";
37
8
  //# sourceMappingURL=object-store.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"object-store.d.ts","sourceRoot":"","sources":["../../src/state/object-store.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,MAAM,MAAM,UAAU,GAAG,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAAC,IAAI,CAAC,EAAE,MAAM,GAAG,UAAU,CAAA;CAAE,KAAK,OAAO,CAAC;IACvI,EAAE,EAAE,OAAO,CAAC;IACZ,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,IAAI,OAAO,CAAC,MAAM,CAAC,CAAC;IACxB,mFAAmF;IACnF,OAAO,CAAC,EAAE;QAAE,GAAG,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAAA;KAAE,CAAC;CAChD,CAAC,CAAC;AAEH,qBAAa,UAAW,SAAQ,KAAK;CAAG;AAExC,0GAA0G;AAC1G,qBAAa,aAAc,SAAQ,UAAU;CAAG;AAEhD,2GAA2G;AAC3G,MAAM,MAAM,cAAc,GAAG;IAAE,OAAO,EAAE,MAAM,CAAA;CAAE,GAAG;IAAE,WAAW,EAAE,GAAG,CAAA;CAAE,CAAC;AAExE,qGAAqG;AACrG,eAAO,MAAM,UAAU,QAAS,MAAM,KAAG,MAAoD,CAAC;AAE9F,eAAO,MAAM,YAAY,MAAO,MAAM,KAAG,MAA4G,CAAC;AAEtJ,oGAAoG;AACpG,eAAO,MAAM,OAAO,QAAS,MAAM,OAAO,MAAM,KAAG,MAAM,GAAG,SAG3D,CAAC;AAEF,yCAAyC;AACzC,eAAO,MAAM,YAAY,EAAE,UAA2D,CAAC"}
1
+ {"version":3,"file":"object-store.d.ts","sourceRoot":"","sources":["../../src/state/object-store.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,OAAO,EAAE,YAAY,EAAE,UAAU,EAAE,YAAY,EAAE,aAAa,EAAE,UAAU,EAAE,OAAO,EAAE,KAAK,cAAc,EAAE,KAAK,UAAU,EAAE,MAAM,+BAA+B,CAAC"}
@@ -1,20 +1,7 @@
1
1
  /**
2
2
  * What the state stores share: the fetch they call, their errors, and how a
3
- * key goes into a URL. Lifted from terragucci's `report/object-store.ts`
4
- * (c493e0e), cut to what a state's lock and write need.
3
+ * key goes into a URL. Since #4554 these are chant core's object-store
4
+ * definitions (`@intentius/chant/object-store`), so a store error thrown by
5
+ * a core client is the one a state caller catches.
5
6
  */
6
- export class StoreError extends Error {
7
- }
8
- /** A conditional write that lost: the object changed (412) or another write to it was in flight (409). */
9
- export class StoreConflict extends StoreError {
10
- }
11
- /** A key as a URL path: each segment percent-encoded the strict way (RFC 3986), the slashes kept. */
12
- export const encodePath = (key) => key.split("/").map(encodeStrict).join("/");
13
- export const encodeStrict = (v) => encodeURIComponent(v).replace(/[!'()*]/g, (c) => `%${c.charCodeAt(0).toString(16).toUpperCase()}`);
14
- /** The first value of an XML element, unescaped. Enough for the small answers these stores send. */
15
- export const xmlText = (xml, tag) => {
16
- const m = new RegExp(`<${tag}>([^<]*)</${tag}>`).exec(xml);
17
- return m?.[1].replace(/&lt;/g, "<").replace(/&gt;/g, ">").replace(/&quot;/g, '"').replace(/&apos;/g, "'").replace(/&amp;/g, "&").trim();
18
- };
19
- /** The global fetch, as a StoreFetch. */
20
- export const defaultFetch = (url, init) => fetch(url, init);
7
+ export { defaultFetch, encodePath, encodeStrict, StoreConflict, StoreError, xmlText } from "@intentius/chant/object-store";