@caelo-cms/provisioning 0.11.9 → 0.11.11

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 (352) hide show
  1. package/dist/adapter.d.ts +6 -0
  2. package/dist/adapter.d.ts.map +1 -1
  3. package/dist/admin-domain.d.ts +10 -1
  4. package/dist/admin-domain.d.ts.map +1 -1
  5. package/dist/admin-domain.js +13 -3
  6. package/dist/admin-domain.js.map +1 -1
  7. package/dist/adopt-existing.d.ts +12 -3
  8. package/dist/adopt-existing.d.ts.map +1 -1
  9. package/dist/adopt-existing.js +143 -0
  10. package/dist/adopt-existing.js.map +1 -1
  11. package/dist/agent/command.d.ts +23 -0
  12. package/dist/agent/command.d.ts.map +1 -1
  13. package/dist/agent/command.js +32 -9
  14. package/dist/agent/command.js.map +1 -1
  15. package/dist/agent/run.d.ts +2 -0
  16. package/dist/agent/run.d.ts.map +1 -1
  17. package/dist/agent/run.js +5 -1
  18. package/dist/agent/run.js.map +1 -1
  19. package/dist/agent-db.js +69 -10
  20. package/dist/aws-task-roles.d.ts +4 -2
  21. package/dist/aws-task-roles.d.ts.map +1 -1
  22. package/dist/aws-task-roles.js +4 -1
  23. package/dist/aws-task-roles.js.map +1 -1
  24. package/dist/caddy.d.ts +21 -4
  25. package/dist/caddy.d.ts.map +1 -1
  26. package/dist/caddy.js +27 -3
  27. package/dist/caddy.js.map +1 -1
  28. package/dist/cli-routing.js +1 -1
  29. package/dist/cli-routing.js.map +1 -1
  30. package/dist/cli.js +68 -7
  31. package/dist/cli.js.map +1 -1
  32. package/dist/cloudsql-tls.d.ts +93 -0
  33. package/dist/cloudsql-tls.d.ts.map +1 -0
  34. package/dist/cloudsql-tls.js +295 -0
  35. package/dist/cloudsql-tls.js.map +1 -0
  36. package/dist/compose.d.ts +43 -6
  37. package/dist/compose.d.ts.map +1 -1
  38. package/dist/compose.js +148 -23
  39. package/dist/compose.js.map +1 -1
  40. package/dist/converge-checkpoint.d.ts +1 -0
  41. package/dist/converge-checkpoint.d.ts.map +1 -1
  42. package/dist/converge-checkpoint.js +8 -1
  43. package/dist/converge-checkpoint.js.map +1 -1
  44. package/dist/converge-db-trust.d.ts +44 -0
  45. package/dist/converge-db-trust.d.ts.map +1 -0
  46. package/dist/converge-db-trust.js +158 -0
  47. package/dist/converge-db-trust.js.map +1 -0
  48. package/dist/converge-job-gcp.d.ts +5 -2
  49. package/dist/converge-job-gcp.d.ts.map +1 -1
  50. package/dist/converge-job-gcp.js +11 -13
  51. package/dist/converge-job-gcp.js.map +1 -1
  52. package/dist/converge-job-preflight.d.ts.map +1 -1
  53. package/dist/converge-job-preflight.js +8 -0
  54. package/dist/converge-job-preflight.js.map +1 -1
  55. package/dist/converge-job.d.ts +17 -0
  56. package/dist/converge-job.d.ts.map +1 -1
  57. package/dist/converge-job.js +12 -0
  58. package/dist/converge-job.js.map +1 -1
  59. package/dist/deployed-release.d.ts +4 -1
  60. package/dist/deployed-release.d.ts.map +1 -1
  61. package/dist/deployed-release.js +18 -8
  62. package/dist/deployed-release.js.map +1 -1
  63. package/dist/gcp-names.d.ts +34 -0
  64. package/dist/gcp-names.d.ts.map +1 -1
  65. package/dist/gcp-names.js +45 -0
  66. package/dist/gcp-names.js.map +1 -1
  67. package/dist/gcp-url-map.d.ts.map +1 -1
  68. package/dist/gcp-url-map.js +6 -2
  69. package/dist/gcp-url-map.js.map +1 -1
  70. package/dist/index.d.ts +3 -2
  71. package/dist/index.d.ts.map +1 -1
  72. package/dist/index.js +2 -1
  73. package/dist/index.js.map +1 -1
  74. package/dist/infra-contract.d.ts +1 -1
  75. package/dist/infra-contract.d.ts.map +1 -1
  76. package/dist/infra-contract.js +45 -1
  77. package/dist/infra-contract.js.map +1 -1
  78. package/dist/install-job.d.ts +4 -2
  79. package/dist/install-job.d.ts.map +1 -1
  80. package/dist/install-job.js +4 -1
  81. package/dist/install-job.js.map +1 -1
  82. package/dist/install-state.d.ts +13 -0
  83. package/dist/install-state.d.ts.map +1 -1
  84. package/dist/install-state.js +17 -4
  85. package/dist/install-state.js.map +1 -1
  86. package/dist/lifecycle.d.ts.map +1 -1
  87. package/dist/lifecycle.js +20 -8
  88. package/dist/lifecycle.js.map +1 -1
  89. package/dist/mcp-auth-proxy-setup.d.ts +48 -0
  90. package/dist/mcp-auth-proxy-setup.d.ts.map +1 -0
  91. package/dist/mcp-auth-proxy-setup.js +280 -0
  92. package/dist/mcp-auth-proxy-setup.js.map +1 -0
  93. package/dist/migration-runner.d.ts.map +1 -1
  94. package/dist/migration-runner.js +8 -3
  95. package/dist/migration-runner.js.map +1 -1
  96. package/dist/migration-targets.d.ts +11 -0
  97. package/dist/migration-targets.d.ts.map +1 -0
  98. package/dist/migration-targets.js +12 -0
  99. package/dist/migration-targets.js.map +1 -0
  100. package/dist/operator-access.d.ts +10 -0
  101. package/dist/operator-access.d.ts.map +1 -1
  102. package/dist/operator-access.js +5 -0
  103. package/dist/operator-access.js.map +1 -1
  104. package/dist/pool-budget-stack.d.ts +13 -1
  105. package/dist/pool-budget-stack.d.ts.map +1 -1
  106. package/dist/pool-budget-stack.js +11 -1
  107. package/dist/pool-budget-stack.js.map +1 -1
  108. package/dist/public-role-isolation.d.ts +7 -3
  109. package/dist/public-role-isolation.d.ts.map +1 -1
  110. package/dist/public-role-isolation.js +25 -13
  111. package/dist/public-role-isolation.js.map +1 -1
  112. package/dist/release-contract.d.ts.map +1 -1
  113. package/dist/release-contract.js +3 -0
  114. package/dist/release-contract.js.map +1 -1
  115. package/dist/release-manifest.d.ts +25 -1
  116. package/dist/release-manifest.d.ts.map +1 -1
  117. package/dist/release-manifest.js +36 -3
  118. package/dist/release-manifest.js.map +1 -1
  119. package/dist/remote-mcp.d.ts +55 -18
  120. package/dist/remote-mcp.d.ts.map +1 -1
  121. package/dist/remote-mcp.js +307 -77
  122. package/dist/remote-mcp.js.map +1 -1
  123. package/dist/runtime-secrets.d.ts +11 -0
  124. package/dist/runtime-secrets.d.ts.map +1 -1
  125. package/dist/runtime-secrets.js +40 -25
  126. package/dist/runtime-secrets.js.map +1 -1
  127. package/dist/scheduler-setup.d.ts +75 -0
  128. package/dist/scheduler-setup.d.ts.map +1 -0
  129. package/dist/scheduler-setup.js +331 -0
  130. package/dist/scheduler-setup.js.map +1 -0
  131. package/dist/seal-key-rotation.d.ts +7 -7
  132. package/dist/seal-key-rotation.d.ts.map +1 -1
  133. package/dist/seal-key-rotation.js +22 -22
  134. package/dist/seal-key-rotation.js.map +1 -1
  135. package/dist/self-hosted-db-tls.d.ts +37 -0
  136. package/dist/self-hosted-db-tls.d.ts.map +1 -0
  137. package/dist/self-hosted-db-tls.js +220 -0
  138. package/dist/self-hosted-db-tls.js.map +1 -0
  139. package/dist/self-hosted-release.d.ts +3 -1
  140. package/dist/self-hosted-release.d.ts.map +1 -1
  141. package/dist/self-hosted-release.js +33 -9
  142. package/dist/self-hosted-release.js.map +1 -1
  143. package/dist/self-hosted-roles.d.ts +43 -2
  144. package/dist/self-hosted-roles.d.ts.map +1 -1
  145. package/dist/self-hosted-roles.js +87 -1
  146. package/dist/self-hosted-roles.js.map +1 -1
  147. package/dist/self-hosted-scheduler-interval.d.ts +21 -0
  148. package/dist/self-hosted-scheduler-interval.d.ts.map +1 -0
  149. package/dist/self-hosted-scheduler-interval.js +68 -0
  150. package/dist/self-hosted-scheduler-interval.js.map +1 -0
  151. package/dist/self-hosted-start.d.ts +0 -21
  152. package/dist/self-hosted-start.d.ts.map +1 -1
  153. package/dist/self-hosted-start.js +39 -1
  154. package/dist/self-hosted-start.js.map +1 -1
  155. package/dist/self-hosted-upgrade.d.ts +19 -7
  156. package/dist/self-hosted-upgrade.d.ts.map +1 -1
  157. package/dist/self-hosted-upgrade.js +78 -25
  158. package/dist/self-hosted-upgrade.js.map +1 -1
  159. package/dist/stack-contract.d.ts +234 -28
  160. package/dist/stack-contract.d.ts.map +1 -1
  161. package/dist/stack-contract.js +397 -39
  162. package/dist/stack-contract.js.map +1 -1
  163. package/dist/stack-converge.d.ts +31 -0
  164. package/dist/stack-converge.d.ts.map +1 -1
  165. package/dist/stack-converge.js +71 -30
  166. package/dist/stack-converge.js.map +1 -1
  167. package/dist/staging-database.d.ts +22 -0
  168. package/dist/staging-database.d.ts.map +1 -0
  169. package/dist/staging-database.js +117 -0
  170. package/dist/staging-database.js.map +1 -0
  171. package/dist/upgrade/app-roll.d.ts +25 -4
  172. package/dist/upgrade/app-roll.d.ts.map +1 -1
  173. package/dist/upgrade/app-roll.js +63 -17
  174. package/dist/upgrade/app-roll.js.map +1 -1
  175. package/dist/upgrade/converge.d.ts +5 -0
  176. package/dist/upgrade/converge.d.ts.map +1 -1
  177. package/dist/upgrade/converge.js +207 -20
  178. package/dist/upgrade/converge.js.map +1 -1
  179. package/dist/upgrade/health.d.ts +31 -7
  180. package/dist/upgrade/health.d.ts.map +1 -1
  181. package/dist/upgrade/health.js +41 -17
  182. package/dist/upgrade/health.js.map +1 -1
  183. package/dist/upgrade/image-forms.d.ts +1 -1
  184. package/dist/upgrade/image-forms.d.ts.map +1 -1
  185. package/dist/upgrade/image-forms.js +24 -7
  186. package/dist/upgrade/image-forms.js.map +1 -1
  187. package/dist/upgrade/release-images.d.ts +12 -3
  188. package/dist/upgrade/release-images.d.ts.map +1 -1
  189. package/dist/upgrade/release-images.js +13 -4
  190. package/dist/upgrade/release-images.js.map +1 -1
  191. package/dist/upgrade/upgrade.d.ts +19 -3
  192. package/dist/upgrade/upgrade.d.ts.map +1 -1
  193. package/dist/upgrade/upgrade.js +37 -2
  194. package/dist/upgrade/upgrade.js.map +1 -1
  195. package/dist/upgrader-gcp.d.ts +13 -0
  196. package/dist/upgrader-gcp.d.ts.map +1 -1
  197. package/dist/upgrader-gcp.js +50 -1
  198. package/dist/upgrader-gcp.js.map +1 -1
  199. package/dist/upgrader-iam-preflight.d.ts.map +1 -1
  200. package/dist/upgrader-iam-preflight.js +6 -1
  201. package/dist/upgrader-iam-preflight.js.map +1 -1
  202. package/dist/upgrader-job.d.ts +1 -1
  203. package/dist/upgrader-job.d.ts.map +1 -1
  204. package/dist/upgrader-job.js +20 -14
  205. package/dist/upgrader-job.js.map +1 -1
  206. package/dist/upgrader-records.d.ts +1 -1
  207. package/dist/upgrader-records.d.ts.map +1 -1
  208. package/dist/upgrader-records.js +26 -7
  209. package/dist/upgrader-records.js.map +1 -1
  210. package/dist/upgrader-setup.d.ts.map +1 -1
  211. package/dist/upgrader-setup.js +10 -5
  212. package/dist/upgrader-setup.js.map +1 -1
  213. package/dist/wizard.d.ts +7 -0
  214. package/dist/wizard.d.ts.map +1 -1
  215. package/dist/wizard.js +11 -0
  216. package/dist/wizard.js.map +1 -1
  217. package/dist/wizards/gcp-cost.d.ts +31 -0
  218. package/dist/wizards/gcp-cost.d.ts.map +1 -1
  219. package/dist/wizards/gcp-cost.js +103 -18
  220. package/dist/wizards/gcp-cost.js.map +1 -1
  221. package/dist/wizards/gcp.d.ts.map +1 -1
  222. package/dist/wizards/gcp.js +129 -27
  223. package/dist/wizards/gcp.js.map +1 -1
  224. package/package.json +2 -2
  225. package/src/adapter.ts +6 -0
  226. package/src/admin-domain.test.ts +9 -8
  227. package/src/admin-domain.ts +13 -3
  228. package/src/admin-memory.test.ts +2 -1
  229. package/src/adopt-existing.test.ts +85 -0
  230. package/src/adopt-existing.ts +141 -3
  231. package/src/agent/command.test.ts +49 -1
  232. package/src/agent/command.ts +39 -9
  233. package/src/agent/run.test.ts +19 -0
  234. package/src/agent/run.ts +11 -2
  235. package/src/aws-task-roles.ts +5 -2
  236. package/src/caddy-redirects.docker.test.ts +2 -0
  237. package/src/caddy-redirects.test.ts +4 -1
  238. package/src/caddy-validate.test.ts +7 -1
  239. package/src/caddy.test.ts +39 -1
  240. package/src/caddy.ts +36 -4
  241. package/src/checkpoint-secrets.test.ts +14 -6
  242. package/src/cli-routing.test.ts +1 -1
  243. package/src/cli-routing.ts +1 -1
  244. package/src/cli.ts +71 -5
  245. package/src/cloudsql-tls.test.ts +255 -0
  246. package/src/cloudsql-tls.ts +359 -0
  247. package/src/compose.ts +193 -24
  248. package/src/converge-checkpoint.ts +8 -1
  249. package/src/converge-db-trust.test.ts +237 -0
  250. package/src/converge-db-trust.ts +212 -0
  251. package/src/converge-job-gcp.ts +11 -12
  252. package/src/converge-job-preflight.test.ts +5 -0
  253. package/src/converge-job-preflight.ts +8 -0
  254. package/src/converge-job.test.ts +70 -0
  255. package/src/converge-job.ts +32 -0
  256. package/src/database-tls-builders.test.ts +56 -0
  257. package/src/deployed-release.test.ts +107 -40
  258. package/src/deployed-release.ts +30 -9
  259. package/src/gateway-env-isolation.test.ts +50 -12
  260. package/src/gcp-names.ts +56 -0
  261. package/src/gcp-url-map.test.ts +6 -8
  262. package/src/gcp-url-map.ts +6 -2
  263. package/src/index.test.ts +10 -4
  264. package/src/index.ts +7 -0
  265. package/src/infra-contract.test.ts +7 -3
  266. package/src/infra-contract.ts +45 -1
  267. package/src/install-job.ts +4 -1
  268. package/src/install-state.ts +33 -4
  269. package/src/lifecycle.ts +23 -11
  270. package/src/mcp-auth-proxy-setup.test.ts +305 -0
  271. package/src/mcp-auth-proxy-setup.ts +374 -0
  272. package/src/mcp-iap-stacks.test.ts +1 -1
  273. package/src/media-volume.test.ts +1 -1
  274. package/src/migration-runner.ts +14 -9
  275. package/src/migration-targets.ts +12 -0
  276. package/src/operator-access-stacks.test.ts +1 -1
  277. package/src/operator-access.test.ts +20 -4
  278. package/src/operator-access.ts +14 -0
  279. package/src/pool-budget-stack.ts +15 -0
  280. package/src/pool-budget-stacks.test.ts +39 -14
  281. package/src/public-role-isolation.test.ts +18 -8
  282. package/src/public-role-isolation.ts +31 -17
  283. package/src/release-contract.ts +3 -0
  284. package/src/release-manifest.test.ts +25 -0
  285. package/src/release-manifest.ts +59 -4
  286. package/src/remote-mcp-stack-config.test.ts +19 -9
  287. package/src/remote-mcp.test.ts +252 -57
  288. package/src/remote-mcp.ts +336 -77
  289. package/src/runtime-secrets.test.ts +37 -14
  290. package/src/runtime-secrets.ts +87 -27
  291. package/src/scheduler-setup.test.ts +201 -0
  292. package/src/scheduler-setup.ts +375 -0
  293. package/src/seal-key-rotation.test.ts +2 -2
  294. package/src/seal-key-rotation.ts +25 -25
  295. package/src/self-hosted-backup.test.ts +1 -0
  296. package/src/self-hosted-compose.test.ts +180 -6
  297. package/src/self-hosted-db-tls.test.ts +111 -0
  298. package/src/self-hosted-db-tls.ts +287 -0
  299. package/src/self-hosted-release-resolve.test.ts +71 -0
  300. package/src/self-hosted-release.ts +41 -11
  301. package/src/self-hosted-roles.integration.test.ts +2 -0
  302. package/src/self-hosted-roles.test.ts +87 -3
  303. package/src/self-hosted-roles.ts +101 -3
  304. package/src/self-hosted-scheduler-interval.test.ts +66 -0
  305. package/src/self-hosted-scheduler-interval.ts +79 -0
  306. package/src/self-hosted-start.test.ts +35 -2
  307. package/src/self-hosted-start.ts +20 -2
  308. package/src/self-hosted-upgrade.test.ts +104 -14
  309. package/src/self-hosted-upgrade.ts +123 -31
  310. package/src/stack-contract-parity.test.ts +79 -12
  311. package/src/stack-contract.test.ts +199 -31
  312. package/src/stack-contract.ts +472 -45
  313. package/src/stack-converge-secrets.test.ts +28 -13
  314. package/src/stack-converge.test.ts +203 -28
  315. package/src/stack-converge.ts +92 -30
  316. package/src/staging-database.test.ts +114 -0
  317. package/src/staging-database.ts +148 -0
  318. package/src/state-store.test.ts +2 -1
  319. package/src/upgrade/app-roll.ts +97 -19
  320. package/src/upgrade/converge.ts +249 -19
  321. package/src/upgrade/health.test.ts +63 -25
  322. package/src/upgrade/health.ts +54 -22
  323. package/src/upgrade/image-forms.test.ts +34 -0
  324. package/src/upgrade/image-forms.ts +23 -8
  325. package/src/upgrade/release-images.test.ts +5 -1
  326. package/src/upgrade/release-images.ts +22 -6
  327. package/src/upgrade/upgrade.test.ts +438 -46
  328. package/src/upgrade/upgrade.ts +64 -6
  329. package/src/upgrader-gcp.ts +66 -3
  330. package/src/upgrader-iam-preflight.test.ts +19 -0
  331. package/src/upgrader-iam-preflight.ts +6 -0
  332. package/src/upgrader-job.test.ts +101 -27
  333. package/src/upgrader-job.ts +27 -14
  334. package/src/upgrader-records.test.ts +97 -12
  335. package/src/upgrader-records.ts +28 -11
  336. package/src/upgrader-setup.test.ts +2 -0
  337. package/src/upgrader-setup.ts +10 -4
  338. package/src/wizard.ts +19 -0
  339. package/src/wizards/gcp-cost.test.ts +103 -0
  340. package/src/wizards/gcp-cost.ts +125 -18
  341. package/src/wizards/gcp.ts +164 -22
  342. package/stacks/aws/Pulumi.yaml +1 -1
  343. package/stacks/aws/index.ts +72 -14
  344. package/stacks/azure/index.ts +108 -21
  345. package/stacks/gcp/Pulumi.yaml +1 -1
  346. package/stacks/gcp/README.md +1 -0
  347. package/stacks/gcp/index.ts +133 -24
  348. package/stacks/gcp-firebase/Pulumi.yaml +1 -1
  349. package/stacks/gcp-firebase/index.ts +212 -34
  350. package/stacks/self-hosted/Pulumi.yaml +3 -0
  351. package/stacks/self-hosted/README.md +6 -1
  352. package/stacks/self-hosted/index.ts +40 -5
@@ -32,14 +32,20 @@ import {
32
32
  import { databaseUrls } from "./stack-contract.js";
33
33
 
34
34
  const FAKE_PW = "fakePw0rdForTests123";
35
- const legacyAdminUrl = `postgresql://admin_role:${FAKE_PW}@10.1.2.3:5432/cms_admin?sslmode=require`;
36
- const legacyPublicUrl = `postgresql://public_role:${FAKE_PW}@10.1.2.3:5432/cms_public?sslmode=require`;
35
+ const legacyAdminUrl = `postgresql://admin_role:${FAKE_PW}@db1.p-label.europe-west1.sql-psa.goog:5432/cms_admin?sslmode=verify-full&sslrootcert=/etc/caelo/db-ca/cloudsql-global.pem`;
36
+ const legacyPublicUrl = `postgresql://public_role:${FAKE_PW}@db1.p-label.europe-west1.sql-psa.goog:5432/cms_public?sslmode=verify-full&sslrootcert=/etc/caelo/db-ca/cloudsql-global.pem`;
37
37
  const setupLink = "https://admin.acme.test/setup?token=fakeSetupToken0123456789";
38
38
 
39
39
  /** What `pulumi up` returns on the current stacks, secret flags included. */
40
40
  const stackOutputs = {
41
- adminDatabaseUrlOut: { value: databaseUrls("10.1.2.3").admin, secret: false },
42
- publicDatabaseUrlOut: { value: databaseUrls("10.1.2.3").public, secret: false },
41
+ adminDatabaseUrlOut: {
42
+ value: databaseUrls("db1.p-label.europe-west1.sql-psa.goog").admin,
43
+ secret: false,
44
+ },
45
+ publicDatabaseUrlOut: {
46
+ value: databaseUrls("db1.p-label.europe-west1.sql-psa.goog").public,
47
+ secret: false,
48
+ },
43
49
  bootstrapUrlOut: { value: setupLink, secret: false },
44
50
  bootstrapTokenExpiresAtOut: { value: "2026-10-13T00:00:00.000Z", secret: false },
45
51
  providerOut: { value: "gcp", secret: false },
@@ -97,7 +103,7 @@ describe("findSecrets", () => {
97
103
  });
98
104
 
99
105
  it("does not flag the password-less database URLs the stacks export today", () => {
100
- expect(findSecrets(databaseUrls("10.1.2.3"))).toEqual([]);
106
+ expect(findSecrets(databaseUrls("db1.p-label.europe-west1.sql-psa.goog"))).toEqual([]);
101
107
  });
102
108
 
103
109
  it("flags secret-named keys holding a value, not markers or look-alikes", () => {
@@ -314,7 +320,9 @@ describe("redactCredentials", () => {
314
320
  it("masks URL passwords and leaves the rest readable", () => {
315
321
  const out = redactCredentials(`connect failed: ${legacyAdminUrl} (timeout)`);
316
322
  expect(out).not.toContain(FAKE_PW);
317
- expect(out).toContain("postgresql://admin_role:***@10.1.2.3:5432/cms_admin");
323
+ expect(out).toContain(
324
+ "postgresql://admin_role:***@db1.p-label.europe-west1.sql-psa.goog:5432/cms_admin",
325
+ );
318
326
  expect(redactCredentials("https://admin.acme.test/x")).toBe("https://admin.acme.test/x");
319
327
  });
320
328
  });
@@ -61,7 +61,7 @@ describe("initDelegatesToWizard", () => {
61
61
  describe("CLI_COMMANDS / cliUsage", () => {
62
62
  it("prints the usage line operators already know, built from the catalogue", () => {
63
63
  expect(cliUsage().split("\n")[0]).toBe(
64
- "Usage: cms-provision [wizard] [--provider <name> --domain <d> [--staging-domain <host>] --owner-email <e> --region <r>] [--local|--install-job] | <init|up|migrate|status|upgrade|backup|restore|rotate-secret|truncate|destroy|regenerate-caddy|admin-domain|staging-domain|owner-reset|state migrate|attach|pulumi-output-sync|version> [options]",
64
+ "Usage: cms-provision [wizard] [--provider <name> --domain <d> [--staging-domain <host>] --owner-email <e> --region <r> [--scheduler-interval-minutes 1-15]] [--local|--install-job] | <init|up|migrate|status|upgrade|backup|restore|rotate-secret|truncate|destroy|regenerate-caddy|admin-domain|staging-domain|owner-reset|state migrate|attach|pulumi-output-sync|version> [options]",
65
65
  );
66
66
  });
67
67
 
@@ -174,7 +174,7 @@ export const CLI_COMMANDS: readonly CliCommandDoc[] = [
174
174
  /** The text `cms-provision --help` prints, built from {@link CLI_COMMANDS}. */
175
175
  export function cliUsage(): string {
176
176
  return (
177
- `Usage: cms-provision [wizard] [--provider <name> --domain <d> [--staging-domain <host>] --owner-email <e> --region <r>] [--local|--install-job] | <${CLI_COMMANDS.map((c) => c.name).join("|")}> [options]\n` +
177
+ `Usage: cms-provision [wizard] [--provider <name> --domain <d> [--staging-domain <host>] --owner-email <e> --region <r> [--scheduler-interval-minutes 1-15]] [--local|--install-job] | <${CLI_COMMANDS.map((c) => c.name).join("|")}> [options]\n` +
178
178
  "With several installs on this machine, pick one with --install <install-id or domain> (status, upgrade and backup also take --install all); in a terminal you are asked otherwise.\n" +
179
179
  "Pass --no-wizard with no sub-command to print this usage instead of the wizard."
180
180
  );
package/src/cli.ts CHANGED
@@ -63,6 +63,7 @@ import {
63
63
  BOOTSTRAP_DIR,
64
64
  COMPOSE_ADMIN_UPSTREAM,
65
65
  COMPOSE_GATEWAY_UPSTREAM,
66
+ COMPOSE_MCP_AUTH_PROXY_UPSTREAM,
66
67
  type ComposeImages,
67
68
  composeProjectName,
68
69
  generateDockerCompose,
@@ -75,6 +76,7 @@ import {
75
76
  } from "./compose.js";
76
77
  import { tightenPrivateTree, writePrivateFile } from "./private-file.js";
77
78
  import { selfCommand } from "./self-command.js";
79
+ import { ensureSelfHostedDbTls, selfHostedDbTlsServerCertSha256 } from "./self-hosted-db-tls.js";
78
80
  import { ensureInternalSecret, writeInternalSecret } from "./self-hosted-internal-secret.js";
79
81
  import {
80
82
  bunHostRunner,
@@ -84,10 +86,12 @@ import {
84
86
  resolveVerifiedRelease,
85
87
  } from "./self-hosted-release.js";
86
88
  import {
89
+ applyPreMigrationRoles,
87
90
  convergeSelfHostedRoles,
88
91
  decideUp,
89
92
  selfHostedBootstrapScript,
90
93
  } from "./self-hosted-roles.js";
94
+ import { parseSchedulerIntervalMinutes } from "./stack-contract.js";
91
95
 
92
96
  interface CaeloConfig {
93
97
  domain: string;
@@ -150,6 +154,12 @@ interface CaeloConfig {
150
154
  * carry it).
151
155
  */
152
156
  composeProject?: string;
157
+ /**
158
+ * #1140 — the `schedulerIntervalMinutes` knob (`init
159
+ * --scheduler-interval-minutes`, 1–15): how often the scheduler container
160
+ * calls the admin's tick. Absent: the default (5).
161
+ */
162
+ schedulerIntervalMinutes?: number;
153
163
  }
154
164
 
155
165
  const REPO_ROOT = resolve(import.meta.dir, "../../..");
@@ -252,6 +262,15 @@ function loadConfig(): CaeloConfig | null {
252
262
  cfg.composeProject ??= "caelo";
253
263
  saveConfig(cfg);
254
264
  }
265
+ // #1329 — the staging gateway's login came with the staging database. An
266
+ // install from before gets its own password now; the next `up` or
267
+ // upgrade creates the role with it (self-hosted-roles.ts) before anything
268
+ // uses it, so the roles count as not converged until then.
269
+ if (cfg.rolePasswords && !cfg.rolePasswords.publicStaging) {
270
+ cfg.rolePasswords = { ...cfg.rolePasswords, publicStaging: randomSecret(32) };
271
+ cfg.rolesConverged = false;
272
+ saveConfig(cfg);
273
+ }
255
274
  return cfg;
256
275
  }
257
276
 
@@ -266,9 +285,9 @@ function arg(name: string): string | undefined {
266
285
  return process.argv[idx + 1];
267
286
  }
268
287
 
269
- /** Fresh passwords for the two application roles. */
288
+ /** Fresh passwords for the application roles. */
270
289
  function newRolePasswords(): RolePasswords {
271
- return { admin: randomSecret(32), public: randomSecret(32) };
290
+ return { admin: randomSecret(32), public: randomSecret(32), publicStaging: randomSecret(32) };
272
291
  }
273
292
 
274
293
  function emitConfig(cfg: CaeloConfig, extraDomains: CaddyDomainSpec[] = []): void {
@@ -305,6 +324,7 @@ function writeCaddyfile(cfg: CaeloConfig, extraDomains: CaddyDomainSpec[]): void
305
324
  // localhost (#707).
306
325
  adminUpstream: COMPOSE_ADMIN_UPSTREAM,
307
326
  gatewayUpstream: COMPOSE_GATEWAY_UPSTREAM,
327
+ mcpAuthProxyUpstream: COMPOSE_MCP_AUTH_PROXY_UPSTREAM,
308
328
  domains: dedupeDomains([
309
329
  ...selfHostedSeedDomains(cfg.domain, cfg.stagingDomain),
310
330
  ...extraDomains,
@@ -344,6 +364,9 @@ function renderCompose(cfg: CaeloConfig, images: ComposeImages): string {
344
364
  "config has no ports / composeProject — they are back-filled on load; this is a bug",
345
365
  );
346
366
  }
367
+ // #1338 — the install's database CA + server certificate the Compose file
368
+ // mounts (created once, the server certificate renewed when due).
369
+ for (const line of ensureSelfHostedDbTls(CAELO_DIR)) console.log(` database TLS: ${line}`);
347
370
  return generateDockerCompose({
348
371
  domain: cfg.domain,
349
372
  postgresPassword: cfg.postgresPassword,
@@ -351,6 +374,7 @@ function renderCompose(cfg: CaeloConfig, images: ComposeImages): string {
351
374
  caeloSecretKek: cfg.caeloSecretKek,
352
375
  // #770 — every render (up, upgrade, agent apply) hands the admin the
353
376
  // secret host-side commands sign their /api/internal/* tokens with.
377
+ dbTlsServerCertSha256: selfHostedDbTlsServerCertSha256(CAELO_DIR),
354
378
  internalSecret: ensureInternalSecret(CAELO_DIR, {
355
379
  randomSecret: () => randomSecret(48),
356
380
  report: (line) => console.log(line),
@@ -361,6 +385,9 @@ function renderCompose(cfg: CaeloConfig, images: ComposeImages): string {
361
385
  images,
362
386
  ports: cfg.ports,
363
387
  project: cfg.composeProject,
388
+ ...(cfg.schedulerIntervalMinutes !== undefined
389
+ ? { schedulerIntervalMinutes: cfg.schedulerIntervalMinutes }
390
+ : {}),
364
391
  ...(activeAgent(cfg) ? { agent: activeAgent(cfg) } : {}),
365
392
  });
366
393
  }
@@ -397,6 +424,20 @@ function agentContext(cfg: CaeloConfig): AgentCliContext {
397
424
  ...(activeAgent(cfg) ? { agentPassword: activeAgent(cfg)?.dbPassword } : {}),
398
425
  });
399
426
  },
427
+ applyPreMigrationRoles: () => {
428
+ if (!cfg.rolePasswords) {
429
+ return Promise.resolve({
430
+ ok: false as const,
431
+ reachable: true,
432
+ error: "config.json has no role passwords — run `cms-provision up` first",
433
+ });
434
+ }
435
+ return applyPreMigrationRoles({
436
+ probe: postgresIsUp,
437
+ psql: superuserPsql,
438
+ stagingPassword: cfg.rolePasswords.publicStaging,
439
+ });
440
+ },
400
441
  renderCompose: (images) => renderCompose(cfg, images),
401
442
  refreshCaddy: () => refreshCaddyfile(cfg),
402
443
  randomSecret: () => randomSecret(32),
@@ -552,7 +593,7 @@ async function init(): Promise<void> {
552
593
  }
553
594
  if (!domain || !ownerEmail) {
554
595
  console.error(
555
- "Usage: cms-provision init [--provider self-hosted] --domain example.com [--staging-domain staging.example.com] --owner-email me@example.com [--version x.y.z] [--skip-verify] [--dev] [--tls acme|internal] [--http-port 80] [--https-port 443]\n" +
596
+ "Usage: cms-provision init [--provider self-hosted] --domain example.com [--staging-domain staging.example.com] --owner-email me@example.com [--version x.y.z] [--skip-verify] [--dev] [--tls acme|internal] [--http-port 80] [--https-port 443] [--scheduler-interval-minutes 5]\n" +
556
597
  "Cloud providers (gcp | gcp-firebase | aws | azure) run the wizard: cms-provision --provider <name>",
557
598
  );
558
599
  process.exit(2);
@@ -567,6 +608,17 @@ async function init(): Promise<void> {
567
608
  console.error("--http-port and --https-port must differ");
568
609
  process.exit(2);
569
610
  }
611
+ const schedulerArg = arg("scheduler-interval-minutes");
612
+ let schedulerIntervalMinutes: number | undefined;
613
+ try {
614
+ schedulerIntervalMinutes =
615
+ schedulerArg === undefined
616
+ ? undefined
617
+ : parseSchedulerIntervalMinutes(schedulerArg, "--scheduler-interval-minutes");
618
+ } catch (e) {
619
+ console.error((e as Error).message);
620
+ process.exit(2);
621
+ }
570
622
  const stagingArg = arg("staging-domain");
571
623
  const stagingProblem = stagingArg
572
624
  ? stagingHostProblem(stagingArg, {
@@ -635,6 +687,7 @@ async function init(): Promise<void> {
635
687
  tls,
636
688
  ports,
637
689
  composeProject: composeProjectName(domain),
690
+ ...(schedulerIntervalMinutes !== undefined ? { schedulerIntervalMinutes } : {}),
638
691
  };
639
692
  saveConfig(cfg);
640
693
  // P15.1 — the shared HMAC secret for /api/internal/* endpoints, read by
@@ -1019,7 +1072,7 @@ async function upgradeSelfHostedCommand(opts: {
1019
1072
  console.error(
1020
1073
  "This is a DEV MODE install: the admin + gateway run your Caelo checkout, so there are\n" +
1021
1074
  "no images to upgrade. Update the checkout (git pull), then `bun packages/provisioning/src/cli.ts migrate` and\n" +
1022
- `\`docker compose -f ${COMPOSE_PATH} restart caelo-admin caelo-gateway\`.`,
1075
+ `\`docker compose -f ${COMPOSE_PATH} restart caelo-admin caelo-gateway caelo-mcp-auth-proxy\`.`,
1023
1076
  );
1024
1077
  process.exit(2);
1025
1078
  }
@@ -1047,6 +1100,7 @@ async function upgradeSelfHostedCommand(opts: {
1047
1100
  runner: bunHostRunner,
1048
1101
  renderCompose: (images) => renderCompose(cfg, images),
1049
1102
  convergeRoles: agentContext(cfg).convergeRoles,
1103
+ applyPreMigrationRoles: agentContext(cfg).applyPreMigrationRoles,
1050
1104
  refreshCaddy: () => refreshCaddyfile(cfg),
1051
1105
  log: {
1052
1106
  step: (m) => console.log(`\n==> ${m}`),
@@ -1614,13 +1668,25 @@ async function waitForDomainMappingCert(target: {
1614
1668
  * §11.C — interactive wizard. Loaded lazily so the bare-CLI startup
1615
1669
  * cost stays small (clack + kleur are pulled in only when needed).
1616
1670
  * Reads `--provider`, `--domain`, `--owner-email`, `--project-id`, `--region`,
1617
- * `--non-interactive` and the run mode (`--local` / `--install-job`, default:
1671
+ * `--scheduler-interval-minutes` (#1140), `--non-interactive` and the run mode (`--local` / `--install-job`, default:
1618
1672
  * the install job in Cloud Shell, local elsewhere — run-mode.ts) from argv.
1619
1673
  */
1620
1674
  async function wizardCommand(): Promise<void> {
1621
1675
  const { runWizard } = await import("./wizard.js");
1622
1676
  const { chooseRunMode } = await import("./run-mode.js");
1677
+ const schedulerArg = arg("scheduler-interval-minutes");
1678
+ let schedulerIntervalMinutes: number | undefined;
1679
+ try {
1680
+ schedulerIntervalMinutes =
1681
+ schedulerArg === undefined
1682
+ ? undefined
1683
+ : parseSchedulerIntervalMinutes(schedulerArg, "--scheduler-interval-minutes");
1684
+ } catch (e) {
1685
+ console.error((e as Error).message);
1686
+ process.exit(2);
1687
+ }
1623
1688
  await runWizard({
1689
+ ...(schedulerIntervalMinutes !== undefined ? { schedulerIntervalMinutes } : {}),
1624
1690
  runMode: chooseRunMode(process.argv, process.env),
1625
1691
  nonInteractive: process.argv.includes("--non-interactive"),
1626
1692
  provider: arg("provider") as Provider | undefined,
@@ -0,0 +1,255 @@
1
+ // SPDX-License-Identifier: MPL-2.0
2
+
3
+ import { describe, expect, it } from "bun:test";
4
+ import {
5
+ dialsIpAddress,
6
+ ensureCloudSqlVerifyFull,
7
+ moveServicesToDnsHost,
8
+ urlOnDnsHost,
9
+ } from "./cloudsql-tls.js";
10
+ import type { GcloudResult } from "./gcloud.js";
11
+ import type { HttpFetch } from "./runtime-secrets.js";
12
+
13
+ const DNS = "abc123.p-label.europe-west1.sql-psa.goog";
14
+ const TARGET = { projectId: "p", env: "production", sqlInstance: "caelo-production-pg-1" };
15
+
16
+ const ok = (stdout = ""): GcloudResult => ({ ok: true, stdout, stderr: "", exitCode: 0 });
17
+ const notFound = (): GcloudResult => ({
18
+ ok: false,
19
+ stdout: "",
20
+ stderr: "ERROR: (gcloud) NOT_FOUND",
21
+ exitCode: 1,
22
+ });
23
+
24
+ /** An instance as `gcloud sql instances describe --format=json` prints it. */
25
+ function instance(opts: { caMode: string; sslMode: string; dns: boolean }): string {
26
+ return JSON.stringify({
27
+ settings: {
28
+ ipConfiguration: {
29
+ serverCaMode: opts.caMode,
30
+ sslMode: opts.sslMode,
31
+ privateNetwork: "projects/p/global/networks/caelo-production-vpc-1",
32
+ },
33
+ },
34
+ ipAddresses: [{ type: "PRIVATE", ipAddress: "10.20.0.3" }],
35
+ dnsNames: opts.dns
36
+ ? [{ name: `${DNS}.`, connectionType: "PRIVATE_SERVICES_ACCESS", dnsScope: "INSTANCE" }]
37
+ : [],
38
+ });
39
+ }
40
+
41
+ /** A fake gcloud: `answer` per call, every call recorded. */
42
+ function fake(answer: (args: string[]) => GcloudResult) {
43
+ const calls: string[] = [];
44
+ return {
45
+ calls,
46
+ run: async (args: string[]) => {
47
+ calls.push(args.join(" "));
48
+ return answer(args);
49
+ },
50
+ };
51
+ }
52
+
53
+ /** The Cloud SQL Admin API listServerCertificates answer. */
54
+ function certs(body: object): HttpFetch {
55
+ return async () => ({ ok: true, status: 200, text: async () => JSON.stringify(body) });
56
+ }
57
+
58
+ describe("ensureCloudSqlVerifyFull", () => {
59
+ it("moves a per-instance-CA instance to the shared CA, rotates, and makes its name resolve", async () => {
60
+ let described = 0;
61
+ const g = fake((a) => {
62
+ const line = a.join(" ");
63
+ if (line.startsWith("sql instances describe")) {
64
+ described += 1;
65
+ return ok(
66
+ instance(
67
+ described === 1
68
+ ? {
69
+ caMode: "GOOGLE_MANAGED_INTERNAL_CA",
70
+ sslMode: "ALLOW_UNENCRYPTED_AND_ENCRYPTED",
71
+ dns: false,
72
+ }
73
+ : { caMode: "GOOGLE_MANAGED_CAS_CA", sslMode: "ENCRYPTED_ONLY", dns: true },
74
+ ),
75
+ );
76
+ }
77
+ if (line.startsWith("auth print-access-token")) return ok("token");
78
+ if (line.includes(" describe ")) return notFound();
79
+ return ok();
80
+ });
81
+ const r = await ensureCloudSqlVerifyFull(TARGET, {
82
+ run: g.run,
83
+ http: certs({ serverCerts: [] }),
84
+ sleep: async () => {},
85
+ });
86
+ expect(r).toMatchObject({ ok: true, host: DNS });
87
+ const writes = g.calls.filter((c) => !c.includes(" describe ") && !c.startsWith("auth"));
88
+ expect(writes).toEqual([
89
+ "sql instances patch caelo-production-pg-1 --project=p --server-ca-mode=GOOGLE_MANAGED_CAS_CA --ssl-mode=ENCRYPTED_ONLY --quiet",
90
+ "sql ssl server-certs create --instance=caelo-production-pg-1 --project=p",
91
+ "sql ssl server-certs rotate --instance=caelo-production-pg-1 --project=p --quiet",
92
+ `dns managed-zones create caelo-production-pg-dns --project=p --dns-name=${DNS}. --visibility=private --networks=caelo-production-vpc-1 --description=Caelo production: the Cloud SQL instance's DNS name inside the VPC (#1338)`,
93
+ `dns record-sets create ${DNS}. --zone=caelo-production-pg-dns --type=A --project=p --ttl=300 --rrdatas=10.20.0.3`,
94
+ ]);
95
+ });
96
+
97
+ it("reads the DNS name after the move when the shared CA's certificate is active at once", async () => {
98
+ // searchviu-website, v0.11.10: the patch made the shared CA's certificate
99
+ // the active one, so no rotation re-read the instance, and the
100
+ // description from before the patch named no DNS host.
101
+ let described = 0;
102
+ const g = fake((a) => {
103
+ const line = a.join(" ");
104
+ if (line.startsWith("sql instances describe")) {
105
+ described += 1;
106
+ return ok(
107
+ instance(
108
+ described === 1
109
+ ? { caMode: "GOOGLE_MANAGED_INTERNAL_CA", sslMode: "ENCRYPTED_ONLY", dns: false }
110
+ : { caMode: "GOOGLE_MANAGED_CAS_CA", sslMode: "ENCRYPTED_ONLY", dns: true },
111
+ ),
112
+ );
113
+ }
114
+ if (line.startsWith("auth print-access-token")) return ok("token");
115
+ if (line.includes(" describe ")) return notFound();
116
+ return ok();
117
+ });
118
+ const r = await ensureCloudSqlVerifyFull(TARGET, {
119
+ run: g.run,
120
+ http: certs({ activeVersion: "f1", serverCerts: [{ sha1Fingerprint: "f1" }] }),
121
+ sleep: async () => {},
122
+ });
123
+ expect(r).toMatchObject({ ok: true, host: DNS });
124
+ expect(g.calls.filter((c) => c.startsWith("sql ssl"))).toEqual([]);
125
+ });
126
+
127
+ it("changes nothing on an instance already in shape", async () => {
128
+ const g = fake((a) => {
129
+ const line = a.join(" ");
130
+ if (line.startsWith("sql instances describe")) {
131
+ return ok(
132
+ instance({ caMode: "GOOGLE_MANAGED_CAS_CA", sslMode: "ENCRYPTED_ONLY", dns: true }),
133
+ );
134
+ }
135
+ if (line.startsWith("auth")) return ok("token");
136
+ if (line.startsWith("dns managed-zones describe"))
137
+ return ok(JSON.stringify({ dnsName: `${DNS}.` }));
138
+ if (line.startsWith("dns record-sets describe"))
139
+ return ok(JSON.stringify({ rrdatas: ["10.20.0.3"] }));
140
+ return ok();
141
+ });
142
+ const r = await ensureCloudSqlVerifyFull(TARGET, {
143
+ run: g.run,
144
+ http: certs({ activeVersion: "f1", serverCerts: [{ sha1Fingerprint: "f1" }] }),
145
+ });
146
+ expect(r).toEqual({ ok: true, host: DNS, done: [] });
147
+ expect(g.calls.every((c) => c.includes(" describe ") || c.startsWith("auth"))).toBe(true);
148
+ });
149
+
150
+ it("finishes an interrupted rotation without creating a second certificate", async () => {
151
+ const g = fake((a) => {
152
+ const line = a.join(" ");
153
+ if (line.startsWith("sql instances describe")) {
154
+ return ok(
155
+ instance({ caMode: "GOOGLE_MANAGED_CAS_CA", sslMode: "ENCRYPTED_ONLY", dns: true }),
156
+ );
157
+ }
158
+ if (line.startsWith("auth")) return ok("token");
159
+ if (line.startsWith("dns managed-zones describe"))
160
+ return ok(JSON.stringify({ dnsName: `${DNS}.` }));
161
+ if (line.startsWith("dns record-sets describe"))
162
+ return ok(JSON.stringify({ rrdatas: ["10.9.9.9"] }));
163
+ return ok();
164
+ });
165
+ const r = await ensureCloudSqlVerifyFull(TARGET, {
166
+ run: g.run,
167
+ http: certs({
168
+ activeVersion: "old-per-instance",
169
+ serverCerts: [{ sha1Fingerprint: "upcoming" }],
170
+ }),
171
+ });
172
+ expect(r.ok).toBe(true);
173
+ expect(g.calls.some((c) => c.startsWith("sql ssl server-certs create"))).toBe(false);
174
+ expect(g.calls.some((c) => c.startsWith("sql ssl server-certs rotate"))).toBe(true);
175
+ expect(
176
+ g.calls.some(
177
+ (c) => c.startsWith("dns record-sets update") && c.endsWith("--rrdatas=10.20.0.3"),
178
+ ),
179
+ ).toBe(true);
180
+ });
181
+
182
+ it("refuses a zone that serves another name instead of repointing it", async () => {
183
+ const g = fake((a) => {
184
+ const line = a.join(" ");
185
+ if (line.startsWith("sql instances describe")) {
186
+ return ok(
187
+ instance({ caMode: "GOOGLE_MANAGED_CAS_CA", sslMode: "ENCRYPTED_ONLY", dns: true }),
188
+ );
189
+ }
190
+ if (line.startsWith("auth")) return ok("token");
191
+ if (line.startsWith("dns managed-zones describe")) {
192
+ return ok(JSON.stringify({ dnsName: "old.p-label.europe-west1.sql-psa.goog." }));
193
+ }
194
+ return ok();
195
+ });
196
+ const r = await ensureCloudSqlVerifyFull(TARGET, {
197
+ run: g.run,
198
+ http: certs({ activeVersion: "f1", serverCerts: [{ sha1Fingerprint: "f1" }] }),
199
+ });
200
+ expect(r).toMatchObject({ ok: false });
201
+ if (!r.ok) expect(r.error).toContain("serves old.p-label");
202
+ });
203
+
204
+ it("fails loudly when the instance names no private DNS name", async () => {
205
+ const g = fake((a) =>
206
+ a.join(" ").startsWith("sql instances describe")
207
+ ? ok(instance({ caMode: "GOOGLE_MANAGED_CAS_CA", sslMode: "ENCRYPTED_ONLY", dns: false }))
208
+ : ok("token"),
209
+ );
210
+ const r = await ensureCloudSqlVerifyFull(TARGET, {
211
+ run: g.run,
212
+ http: certs({ activeVersion: "f1", serverCerts: [{ sha1Fingerprint: "f1" }] }),
213
+ });
214
+ expect(r).toMatchObject({ ok: false });
215
+ if (!r.ok) expect(r.error).toContain("PRIVATE_SERVICES_ACCESS DNS name");
216
+ });
217
+ });
218
+
219
+ describe("moveServicesToDnsHost", () => {
220
+ it("switches only the URLs that dial an IP, keeping their sslmode", async () => {
221
+ const g = fake(() => ok());
222
+ const r = await moveServicesToDnsHost(
223
+ { projectId: "p", region: "europe-west1" },
224
+ [
225
+ {
226
+ serviceName: "caelo-production-admin-1",
227
+ liveUrls: {
228
+ ADMIN_DATABASE_URL: "postgresql://admin_role@10.20.0.3:5432/cms_admin?sslmode=require",
229
+ PUBLIC_ADMIN_DATABASE_URL:
230
+ "postgresql://admin_role@10.20.0.3:5432/cms_public?sslmode=require",
231
+ },
232
+ },
233
+ {
234
+ serviceName: "caelo-production-gateway-1",
235
+ liveUrls: { PUBLIC_DATABASE_URL: `postgresql://public_role@${DNS}:5432/cms_public` },
236
+ },
237
+ ],
238
+ DNS,
239
+ { run: g.run, sleep: async () => {} },
240
+ );
241
+ expect(r.ok).toBe(true);
242
+ expect(g.calls).toEqual([
243
+ `run services update caelo-production-admin-1 --region europe-west1 --project=p --update-env-vars=^|^ADMIN_DATABASE_URL=postgresql://admin_role@${DNS}:5432/cms_admin?sslmode=require|PUBLIC_ADMIN_DATABASE_URL=postgresql://admin_role@${DNS}:5432/cms_public?sslmode=require --quiet`,
244
+ ]);
245
+ });
246
+ });
247
+
248
+ describe("urlOnDnsHost / dialsIpAddress", () => {
249
+ it("tells IP-dialling URLs apart and moves only those", () => {
250
+ expect(dialsIpAddress("postgresql://a@10.0.0.3:5432/d")).toBe(true);
251
+ expect(dialsIpAddress(`postgresql://a@${DNS}:5432/d`)).toBe(false);
252
+ expect(dialsIpAddress("not a url")).toBe(false);
253
+ expect(urlOnDnsHost(`postgresql://a@${DNS}:5432/d`, "x")).toBeNull();
254
+ });
255
+ });