@caelo-cms/provisioning 0.10.47 → 0.10.48

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 (315) hide show
  1. package/dist/agent/apply.d.ts +45 -0
  2. package/dist/agent/apply.d.ts.map +1 -0
  3. package/dist/agent/apply.js +83 -0
  4. package/dist/agent/apply.js.map +1 -0
  5. package/dist/agent/cli-provenance.d.ts +42 -0
  6. package/dist/agent/cli-provenance.d.ts.map +1 -0
  7. package/dist/agent/cli-provenance.js +138 -0
  8. package/dist/agent/cli-provenance.js.map +1 -0
  9. package/dist/agent/command.d.ts +79 -0
  10. package/dist/agent/command.d.ts.map +1 -0
  11. package/dist/agent/command.js +454 -0
  12. package/dist/agent/command.js.map +1 -0
  13. package/dist/agent/config.d.ts +30 -0
  14. package/dist/agent/config.d.ts.map +1 -0
  15. package/dist/agent/config.js +60 -0
  16. package/dist/agent/config.js.map +1 -0
  17. package/dist/agent/lock.d.ts +13 -0
  18. package/dist/agent/lock.d.ts.map +1 -0
  19. package/dist/agent/lock.js +100 -0
  20. package/dist/agent/lock.js.map +1 -0
  21. package/dist/agent/queue.d.ts +35 -0
  22. package/dist/agent/queue.d.ts.map +1 -0
  23. package/dist/agent/queue.js +50 -0
  24. package/dist/agent/queue.js.map +1 -0
  25. package/dist/agent/run.d.ts +97 -0
  26. package/dist/agent/run.d.ts.map +1 -0
  27. package/dist/agent/run.js +198 -0
  28. package/dist/agent/run.js.map +1 -0
  29. package/dist/agent/systemd.d.ts +55 -0
  30. package/dist/agent/systemd.d.ts.map +1 -0
  31. package/dist/agent/systemd.js +171 -0
  32. package/dist/agent/systemd.js.map +1 -0
  33. package/dist/agent/units.d.ts +44 -0
  34. package/dist/agent/units.d.ts.map +1 -0
  35. package/dist/agent/units.js +127 -0
  36. package/dist/agent/units.js.map +1 -0
  37. package/dist/agent-db.js +18362 -0
  38. package/dist/cli.js +119 -16
  39. package/dist/cli.js.map +1 -1
  40. package/dist/cloudsql-iam-user.d.ts +39 -0
  41. package/dist/cloudsql-iam-user.d.ts.map +1 -0
  42. package/dist/cloudsql-iam-user.js +119 -0
  43. package/dist/cloudsql-iam-user.js.map +1 -0
  44. package/dist/compose.d.ts +16 -0
  45. package/dist/compose.d.ts.map +1 -1
  46. package/dist/compose.js +26 -1
  47. package/dist/compose.js.map +1 -1
  48. package/dist/dns/index.d.ts +2 -0
  49. package/dist/dns/index.d.ts.map +1 -1
  50. package/dist/dns/index.js +3 -2
  51. package/dist/dns/index.js.map +1 -1
  52. package/dist/dns/manual.d.ts +5 -0
  53. package/dist/dns/manual.d.ts.map +1 -1
  54. package/dist/dns/manual.js +28 -12
  55. package/dist/dns/manual.js.map +1 -1
  56. package/dist/gcloud.d.ts +2 -0
  57. package/dist/gcloud.d.ts.map +1 -1
  58. package/dist/gcloud.js +1 -1
  59. package/dist/gcloud.js.map +1 -1
  60. package/dist/gcp-names.d.ts +15 -0
  61. package/dist/gcp-names.d.ts.map +1 -1
  62. package/dist/gcp-names.js +23 -0
  63. package/dist/gcp-names.js.map +1 -1
  64. package/dist/index.d.ts +6 -0
  65. package/dist/index.d.ts.map +1 -1
  66. package/dist/index.js +4 -0
  67. package/dist/index.js.map +1 -1
  68. package/dist/infra-contract.d.ts +1 -1
  69. package/dist/infra-contract.js +7 -1
  70. package/dist/infra-contract.js.map +1 -1
  71. package/dist/install-job-runner.d.ts +13 -0
  72. package/dist/install-job-runner.d.ts.map +1 -0
  73. package/dist/install-job-runner.js +155 -0
  74. package/dist/install-job-runner.js.map +1 -0
  75. package/dist/install-job.d.ts +210 -0
  76. package/dist/install-job.d.ts.map +1 -0
  77. package/dist/install-job.js +449 -0
  78. package/dist/install-job.js.map +1 -0
  79. package/dist/install-state.d.ts +10 -1
  80. package/dist/install-state.d.ts.map +1 -1
  81. package/dist/install-state.js +10 -2
  82. package/dist/install-state.js.map +1 -1
  83. package/dist/lifecycle.d.ts.map +1 -1
  84. package/dist/lifecycle.js +19 -4
  85. package/dist/lifecycle.js.map +1 -1
  86. package/dist/migration-runner.d.ts +5 -0
  87. package/dist/migration-runner.d.ts.map +1 -1
  88. package/dist/migration-runner.js +1 -1
  89. package/dist/migration-runner.js.map +1 -1
  90. package/dist/operator-access.d.ts +14 -8
  91. package/dist/operator-access.d.ts.map +1 -1
  92. package/dist/operator-access.js +16 -102
  93. package/dist/operator-access.js.map +1 -1
  94. package/dist/run-mode.d.ts +27 -0
  95. package/dist/run-mode.d.ts.map +1 -0
  96. package/dist/run-mode.js +22 -0
  97. package/dist/run-mode.js.map +1 -0
  98. package/dist/runtime-secrets.d.ts +19 -0
  99. package/dist/runtime-secrets.d.ts.map +1 -1
  100. package/dist/runtime-secrets.js +44 -0
  101. package/dist/runtime-secrets.js.map +1 -1
  102. package/dist/self-hosted-roles.d.ts +22 -1
  103. package/dist/self-hosted-roles.d.ts.map +1 -1
  104. package/dist/self-hosted-roles.js +81 -12
  105. package/dist/self-hosted-roles.js.map +1 -1
  106. package/dist/self-hosted-upgrade.d.ts +17 -0
  107. package/dist/self-hosted-upgrade.d.ts.map +1 -1
  108. package/dist/self-hosted-upgrade.js +16 -1
  109. package/dist/self-hosted-upgrade.js.map +1 -1
  110. package/dist/stack-contract.d.ts +6 -0
  111. package/dist/stack-contract.d.ts.map +1 -1
  112. package/dist/stack-contract.js +10 -1
  113. package/dist/stack-contract.js.map +1 -1
  114. package/dist/upgrade/converge.d.ts +6 -0
  115. package/dist/upgrade/converge.d.ts.map +1 -1
  116. package/dist/upgrade/converge.js +59 -2
  117. package/dist/upgrade/converge.js.map +1 -1
  118. package/dist/upgrade/gcp-lookup.d.ts +6 -0
  119. package/dist/upgrade/gcp-lookup.d.ts.map +1 -1
  120. package/dist/upgrade/gcp-lookup.js +25 -0
  121. package/dist/upgrade/gcp-lookup.js.map +1 -1
  122. package/dist/upgrade/release-images.d.ts +12 -0
  123. package/dist/upgrade/release-images.d.ts.map +1 -1
  124. package/dist/upgrade/release-images.js +31 -0
  125. package/dist/upgrade/release-images.js.map +1 -1
  126. package/dist/upgrade/upgrade.d.ts +5 -0
  127. package/dist/upgrade/upgrade.d.ts.map +1 -1
  128. package/dist/upgrade/upgrade.js +28 -1
  129. package/dist/upgrade/upgrade.js.map +1 -1
  130. package/dist/upgrader-assess.d.ts +82 -0
  131. package/dist/upgrader-assess.d.ts.map +1 -0
  132. package/dist/upgrader-assess.js +116 -0
  133. package/dist/upgrader-assess.js.map +1 -0
  134. package/dist/upgrader-gcp.d.ts +17 -0
  135. package/dist/upgrader-gcp.d.ts.map +1 -0
  136. package/dist/upgrader-gcp.js +87 -0
  137. package/dist/upgrader-gcp.js.map +1 -0
  138. package/dist/upgrader-job.d.ts +131 -0
  139. package/dist/upgrader-job.d.ts.map +1 -0
  140. package/dist/upgrader-job.js +259 -0
  141. package/dist/upgrader-job.js.map +1 -0
  142. package/dist/upgrader-reporter.d.ts +35 -0
  143. package/dist/upgrader-reporter.d.ts.map +1 -0
  144. package/dist/upgrader-reporter.js +79 -0
  145. package/dist/upgrader-reporter.js.map +1 -0
  146. package/dist/upgrader-setup.d.ts +59 -0
  147. package/dist/upgrader-setup.d.ts.map +1 -0
  148. package/dist/upgrader-setup.js +308 -0
  149. package/dist/upgrader-setup.js.map +1 -0
  150. package/dist/wizard.d.ts +6 -0
  151. package/dist/wizard.d.ts.map +1 -1
  152. package/dist/wizard.js +32 -4
  153. package/dist/wizard.js.map +1 -1
  154. package/dist/wizards/gcp-firebase.d.ts +3 -0
  155. package/dist/wizards/gcp-firebase.d.ts.map +1 -1
  156. package/dist/wizards/gcp-firebase.js +1 -0
  157. package/dist/wizards/gcp-firebase.js.map +1 -1
  158. package/dist/wizards/gcp-install-job.d.ts +74 -0
  159. package/dist/wizards/gcp-install-job.d.ts.map +1 -0
  160. package/dist/wizards/gcp-install-job.js +398 -0
  161. package/dist/wizards/gcp-install-job.js.map +1 -0
  162. package/dist/wizards/gcp-prompts.d.ts +3 -0
  163. package/dist/wizards/gcp-prompts.d.ts.map +1 -0
  164. package/dist/wizards/gcp-prompts.js +25 -0
  165. package/dist/wizards/gcp-prompts.js.map +1 -0
  166. package/dist/wizards/gcp-pulumi.d.ts +4 -2
  167. package/dist/wizards/gcp-pulumi.d.ts.map +1 -1
  168. package/dist/wizards/gcp-pulumi.js +1 -1
  169. package/dist/wizards/gcp-pulumi.js.map +1 -1
  170. package/dist/wizards/gcp.d.ts +40 -0
  171. package/dist/wizards/gcp.d.ts.map +1 -1
  172. package/dist/wizards/gcp.js +223 -59
  173. package/dist/wizards/gcp.js.map +1 -1
  174. package/package.json +6 -4
  175. package/src/adapter.ts +97 -0
  176. package/src/adc-impersonation.ts +176 -0
  177. package/src/admin-domain.test.ts +254 -0
  178. package/src/admin-domain.ts +245 -0
  179. package/src/admin-memory.test.ts +151 -0
  180. package/src/agent/apply.test.ts +185 -0
  181. package/src/agent/apply.ts +124 -0
  182. package/src/agent/cli-provenance.test.ts +159 -0
  183. package/src/agent/cli-provenance.ts +191 -0
  184. package/src/agent/command.test.ts +77 -0
  185. package/src/agent/command.ts +564 -0
  186. package/src/agent/config.ts +86 -0
  187. package/src/agent/lock-config.test.ts +97 -0
  188. package/src/agent/lock.ts +110 -0
  189. package/src/agent/queue.test.ts +10 -0
  190. package/src/agent/queue.ts +91 -0
  191. package/src/agent/run.test.ts +382 -0
  192. package/src/agent/run.ts +295 -0
  193. package/src/agent/systemd.ts +209 -0
  194. package/src/agent/units.test.ts +223 -0
  195. package/src/agent/units.ts +163 -0
  196. package/src/azure-front-door.test.ts +119 -0
  197. package/src/azure-front-door.ts +56 -0
  198. package/src/bootstrap-token.ts +20 -0
  199. package/src/caddy.ts +112 -0
  200. package/src/cdn-copy.ts +91 -0
  201. package/src/cert-wait.test.ts +194 -0
  202. package/src/cert-wait.ts +163 -0
  203. package/src/checkpoint-secrets.test.ts +380 -0
  204. package/src/checkpoint-secrets.ts +283 -0
  205. package/src/cli-routing.test.ts +45 -0
  206. package/src/cli-routing.ts +43 -0
  207. package/src/cli.ts +1491 -0
  208. package/src/cloudsql-iam-user.ts +147 -0
  209. package/src/compose.ts +334 -0
  210. package/src/default-firewall.test.ts +411 -0
  211. package/src/default-firewall.ts +342 -0
  212. package/src/deployed-release.test.ts +231 -0
  213. package/src/deployed-release.ts +220 -0
  214. package/src/dns/cloudflare.ts +182 -0
  215. package/src/dns/groups.test.ts +76 -0
  216. package/src/dns/groups.ts +86 -0
  217. package/src/dns/index.ts +56 -0
  218. package/src/dns/manual.ts +130 -0
  219. package/src/dns/types.ts +27 -0
  220. package/src/gateway-credentials.test.ts +180 -0
  221. package/src/gateway-credentials.ts +144 -0
  222. package/src/gcloud-components.test.ts +76 -0
  223. package/src/gcloud-components.ts +85 -0
  224. package/src/gcloud-retry.ts +41 -0
  225. package/src/gcloud-runner.test.ts +61 -0
  226. package/src/gcloud.test.ts +99 -0
  227. package/src/gcloud.ts +322 -0
  228. package/src/gcp-names.ts +135 -0
  229. package/src/index.test.ts +332 -0
  230. package/src/index.ts +114 -0
  231. package/src/infra-contract.ts +32 -0
  232. package/src/install-job-runner.ts +166 -0
  233. package/src/install-job.test.ts +566 -0
  234. package/src/install-job.ts +542 -0
  235. package/src/install-state.test.ts +239 -0
  236. package/src/install-state.ts +583 -0
  237. package/src/internal-token.ts +35 -0
  238. package/src/lifecycle.ts +900 -0
  239. package/src/mcp-iap-stacks.test.ts +58 -0
  240. package/src/mcp-iap.test.ts +149 -0
  241. package/src/mcp-iap.ts +131 -0
  242. package/src/media-volume.test.ts +296 -0
  243. package/src/migration-runner.ts +365 -0
  244. package/src/operator-access-stacks.test.ts +55 -0
  245. package/src/operator-access.test.ts +332 -0
  246. package/src/operator-access.ts +554 -0
  247. package/src/ops-backend.test.ts +320 -0
  248. package/src/ops-backend.ts +414 -0
  249. package/src/provisioner-identity.test.ts +482 -0
  250. package/src/provisioner-identity.ts +407 -0
  251. package/src/redirects-emit.ts +175 -0
  252. package/src/region-data.ts +428 -0
  253. package/src/regions.test.ts +277 -0
  254. package/src/regions.ts +370 -0
  255. package/src/release-contract.ts +57 -0
  256. package/src/release-manifest.test.ts +293 -0
  257. package/src/release-manifest.ts +430 -0
  258. package/src/run-mode.ts +40 -0
  259. package/src/runtime-secrets.test.ts +627 -0
  260. package/src/runtime-secrets.ts +754 -0
  261. package/src/self-command.test.ts +14 -0
  262. package/src/self-command.ts +21 -0
  263. package/src/self-hosted-compose.test.ts +177 -0
  264. package/src/self-hosted-release.ts +240 -0
  265. package/src/self-hosted-roles.integration.test.ts +161 -0
  266. package/src/self-hosted-roles.test.ts +124 -0
  267. package/src/self-hosted-roles.ts +203 -0
  268. package/src/self-hosted-upgrade.test.ts +394 -0
  269. package/src/self-hosted-upgrade.ts +548 -0
  270. package/src/stack-contract-parity.test.ts +199 -0
  271. package/src/stack-contract.test.ts +391 -0
  272. package/src/stack-contract.ts +888 -0
  273. package/src/stack-converge-secrets.test.ts +421 -0
  274. package/src/stack-converge.test.ts +833 -0
  275. package/src/stack-converge.ts +909 -0
  276. package/src/staging-admin.test.ts +246 -0
  277. package/src/staging-admin.ts +324 -0
  278. package/src/state-attach.test.ts +211 -0
  279. package/src/state-attach.ts +132 -0
  280. package/src/state-backend.test.ts +145 -0
  281. package/src/state-backend.ts +262 -0
  282. package/src/state-command.ts +293 -0
  283. package/src/state-migrate.test.ts +479 -0
  284. package/src/state-migrate.ts +434 -0
  285. package/src/state-pulumi.ts +289 -0
  286. package/src/state-store.test.ts +212 -0
  287. package/src/state-store.ts +243 -0
  288. package/src/upgrade/app-roll.ts +486 -0
  289. package/src/upgrade/backup.ts +109 -0
  290. package/src/upgrade/converge.ts +437 -0
  291. package/src/upgrade/gcp-lookup.ts +167 -0
  292. package/src/upgrade/health.test.ts +279 -0
  293. package/src/upgrade/health.ts +305 -0
  294. package/src/upgrade/release-images.test.ts +119 -0
  295. package/src/upgrade/release-images.ts +391 -0
  296. package/src/upgrade/reporter.ts +46 -0
  297. package/src/upgrade/upgrade.test.ts +626 -0
  298. package/src/upgrade/upgrade.ts +249 -0
  299. package/src/upgrader-assess.test.ts +265 -0
  300. package/src/upgrader-assess.ts +233 -0
  301. package/src/upgrader-gcp.ts +123 -0
  302. package/src/upgrader-job.test.ts +408 -0
  303. package/src/upgrader-job.ts +499 -0
  304. package/src/upgrader-reporter.test.ts +46 -0
  305. package/src/upgrader-reporter.ts +105 -0
  306. package/src/upgrader-setup.test.ts +169 -0
  307. package/src/upgrader-setup.ts +355 -0
  308. package/src/wizard.ts +425 -0
  309. package/src/wizards/gcp-cost.ts +188 -0
  310. package/src/wizards/gcp-firebase.ts +72 -0
  311. package/src/wizards/gcp-install-job.ts +506 -0
  312. package/src/wizards/gcp-prompts.ts +26 -0
  313. package/src/wizards/gcp-pulumi.test.ts +246 -0
  314. package/src/wizards/gcp-pulumi.ts +516 -0
  315. package/src/wizards/gcp.ts +1935 -0
@@ -0,0 +1,233 @@
1
+ // SPDX-License-Identifier: MPL-2.0
2
+
3
+ /**
4
+ * Whether the upgrader job may install the release an update request names
5
+ * (epic #696). Pure: the job fetches the signed manifest and reads the
6
+ * install's own state, this decides.
7
+ *
8
+ * The request row comes from the admin, so it is untrusted input: it only
9
+ * names a version and who asked. Everything that decides — the digests, the
10
+ * minimum version to upgrade from, whether the release changes the
11
+ * infrastructure or carries destructive migrations, whether it is a
12
+ * security release — comes from the manifest Caelo's release workflow
13
+ * signed, and the installed version from install.json in the install's ops
14
+ * bucket (which the admin cannot write). So a compromised admin can at most
15
+ * ask for "the newest signed release" again; it cannot pick an older one,
16
+ * skip a required intermediate release or get an infrastructure change past
17
+ * the Owner.
18
+ *
19
+ * Refusal matrix (table-tested in upgrader-assess.test.ts):
20
+ *
21
+ * | Condition | Status |
22
+ * |-----------------------------------------------------|-------------------|
23
+ * | registry unreachable | failed (retry) |
24
+ * | bad signature / tampered blob / replayed manifest | refused |
25
+ * | manifest too new for this upgrader | needs_cloud_shell |
26
+ * | installed version unknown (never upgraded via CLI) | needs_cloud_shell |
27
+ * | target not newer than installed (downgrade/same) | refused |
28
+ * | installed below the release's minFromVersion | refused |
29
+ * | release raises the infrastructure contract | needs_cloud_shell |
30
+ * | contract (destructive) migrations, automatic run | refused |
31
+ * | contract migrations, Owner's click | proceed + warning |
32
+ * | automatic run the update policy does not allow | refused |
33
+ *
34
+ * Self-hosted installs (the host agent, #696 Phase 4c) have no separately
35
+ * converged infrastructure: their Compose file, roles and bootstrap script
36
+ * are rendered by the target release's own provisioning CLI in the same run
37
+ * that rolls the images. So for them `installed.infraContractVersion` is
38
+ * null and the infra-contract row never applies; every "needs_cloud_shell"
39
+ * stop names the upgrade command on the server instead of Cloud Shell.
40
+ */
41
+
42
+ import {
43
+ autoInstallRefusal,
44
+ type PlatformUpdateTrigger,
45
+ type StoredUpdatePolicy,
46
+ updateKind,
47
+ } from "@caelo-cms/shared";
48
+ import {
49
+ assessAutoInstall,
50
+ channelForVersion,
51
+ compareSemver,
52
+ type ReleaseManifest,
53
+ type ReleaseManifestResult,
54
+ } from "./release-manifest.js";
55
+ import { needsConverge } from "./upgrade/converge.js";
56
+
57
+ /** Machine-readable reason codes (`platform_update_requests.refusal_codes`). */
58
+ export type RefusalCode =
59
+ | "registry"
60
+ | "signature"
61
+ | "manifest-unreadable"
62
+ | "install-version-unknown"
63
+ | "not-newer"
64
+ | "min-from-version"
65
+ | "infra-contract"
66
+ | "contract-migrations"
67
+ | "policy";
68
+
69
+ export type RequestDecision =
70
+ | {
71
+ readonly kind: "proceed";
72
+ readonly manifest: ReleaseManifest;
73
+ /** Notes for the timeline (e.g. destructive migrations the Owner accepted). */
74
+ readonly warnings: readonly string[];
75
+ }
76
+ | {
77
+ readonly kind: "stop";
78
+ readonly status: "refused" | "needs_cloud_shell" | "failed";
79
+ readonly codes: readonly RefusalCode[];
80
+ /** Plain words for the Owner. */
81
+ readonly reason: string;
82
+ /** The manifest, when it verified (for the facts the page explains). */
83
+ readonly manifest?: ReleaseManifest;
84
+ };
85
+
86
+ /** What the install's own state says it runs. */
87
+ export interface InstalledState {
88
+ /** install.json `releaseVersion` (self-hosted: release.json); null when never recorded. */
89
+ readonly version: string | null;
90
+ /**
91
+ * install.json `infraContractVersion` (0 when never converged); null for
92
+ * an install without separately converged infrastructure (self-hosted).
93
+ */
94
+ readonly infraContractVersion: number | null;
95
+ }
96
+
97
+ /** Where the Owner runs an upgrade by hand when the automatic path cannot. */
98
+ export type ManualUpgradePath = "cloud-shell" | "server";
99
+
100
+ /** "…once with Cloud Shell" / "…once on the server": the hand-run upgrade, in words. */
101
+ function manualRun(path: ManualUpgradePath): string {
102
+ return path === "server"
103
+ ? "with the upgrade command on the server (Settings → Updates shows it)"
104
+ : "with Cloud Shell";
105
+ }
106
+
107
+ const stop = (
108
+ status: "refused" | "needs_cloud_shell" | "failed",
109
+ code: RefusalCode,
110
+ reason: string,
111
+ manifest?: ReleaseManifest,
112
+ ): RequestDecision => ({
113
+ kind: "stop",
114
+ status,
115
+ codes: [code],
116
+ reason,
117
+ ...(manifest ? { manifest } : {}),
118
+ });
119
+
120
+ /** Decide what the job does with a request. See the file comment for the matrix. */
121
+ export function assessUpgradeRequest(input: {
122
+ readonly request: { readonly targetVersion: string; readonly trigger: PlatformUpdateTrigger };
123
+ readonly manifest: ReleaseManifestResult;
124
+ readonly installed: InstalledState;
125
+ readonly policy: StoredUpdatePolicy;
126
+ readonly now: Date;
127
+ /** Default `cloud-shell` (the GCP upgrader job). */
128
+ readonly manualPath?: ManualUpgradePath;
129
+ }): RequestDecision {
130
+ const { request, installed } = input;
131
+ const by = manualRun(input.manualPath ?? "cloud-shell");
132
+ const target = request.targetVersion;
133
+ if (!input.manifest.ok) {
134
+ const e = input.manifest.error;
135
+ switch (e.code) {
136
+ case "registry":
137
+ return stop(
138
+ "failed",
139
+ "registry",
140
+ `Could not reach the Caelo release registry to verify v${target} (${e.message}). Nothing was changed; try again later.`,
141
+ );
142
+ case "schema":
143
+ return stop(
144
+ "needs_cloud_shell",
145
+ "manifest-unreadable",
146
+ `This upgrader is too old to read the release information of v${target}. Install it once ${by}; afterwards the admin can install updates again. (${e.message})`,
147
+ );
148
+ default:
149
+ return stop(
150
+ "refused",
151
+ "signature",
152
+ `The release information of v${target} is not signed by Caelo's release workflow, so it was not installed. Nothing was changed. (${e.message})`,
153
+ );
154
+ }
155
+ }
156
+ const manifest = input.manifest.manifest;
157
+ if (installed.version === null) {
158
+ return stop(
159
+ "needs_cloud_shell",
160
+ "install-version-unknown",
161
+ `This install has not recorded which Caelo release it runs yet. Upgrade it once ${by}; that records it, and the admin can install updates from then on.`,
162
+ manifest,
163
+ );
164
+ }
165
+ if (compareSemver(target, installed.version) <= 0) {
166
+ return stop(
167
+ "refused",
168
+ "not-newer",
169
+ `v${target} is not newer than the installed v${installed.version}; Caelo never installs an older release over a newer one.`,
170
+ manifest,
171
+ );
172
+ }
173
+ if (compareSemver(installed.version, manifest.minFromVersion) < 0) {
174
+ return stop(
175
+ "refused",
176
+ "min-from-version",
177
+ `v${target} can only be installed over v${manifest.minFromVersion} or newer; this install runs v${installed.version}. Install v${manifest.minFromVersion} first.`,
178
+ manifest,
179
+ );
180
+ }
181
+ // Self-hosted: the target's own CLI renders the host setup in this run.
182
+ const infraContractVersion = installed.infraContractVersion ?? manifest.infraContractVersion;
183
+ if (
184
+ installed.infraContractVersion !== null &&
185
+ needsConverge({ infraContractVersion }, manifest.infraContractVersion)
186
+ ) {
187
+ return stop(
188
+ "needs_cloud_shell",
189
+ "infra-contract",
190
+ `v${target} changes this install's cloud setup (infrastructure contract ${infraContractVersion} → ${manifest.infraContractVersion}). The admin may not change cloud permissions, so this release is installed once with your own Google account in Cloud Shell.`,
191
+ manifest,
192
+ );
193
+ }
194
+ const assessment = assessAutoInstall(manifest, {
195
+ version: installed.version,
196
+ infraContractVersion,
197
+ });
198
+ const destructive = assessment.mode === "manual";
199
+ const warnings: string[] = [];
200
+ if (destructive) {
201
+ if (request.trigger === "auto") {
202
+ return stop(
203
+ "refused",
204
+ "contract-migrations",
205
+ `v${target} makes database changes the previous release cannot run on (${assessment.reasons.join("; ")}), so it never installs automatically. Install it from Settings → Updates.`,
206
+ manifest,
207
+ );
208
+ }
209
+ warnings.push(
210
+ `This release makes database changes the previous release cannot run on (${assessment.reasons.join("; ")}). If it has to roll back, the pre-upgrade backup must be restored.`,
211
+ );
212
+ }
213
+ if (request.trigger === "auto") {
214
+ const kind = updateKind(installed.version, target);
215
+ const refusal =
216
+ kind === null
217
+ ? "the release is not a newer version"
218
+ : autoInstallRefusal(
219
+ input.policy,
220
+ {
221
+ kind,
222
+ security: assessment.kind === "security",
223
+ contractMigrations: destructive,
224
+ channel: channelForVersion(target) ?? "unknown",
225
+ },
226
+ input.now,
227
+ );
228
+ if (refusal) {
229
+ return stop("refused", "policy", `Not installed automatically: ${refusal}.`, manifest);
230
+ }
231
+ }
232
+ return { kind: "proceed", manifest, warnings };
233
+ }
@@ -0,0 +1,123 @@
1
+ // SPDX-License-Identifier: MPL-2.0
2
+
3
+ /**
4
+ * The real side effects of the upgrader job on Google Cloud (epic #696):
5
+ * gcloud as the job's own service account, the install's ops bucket for
6
+ * install.json, the fixed release registry, the migration job and the IAP
7
+ * health probe. upgrader-job.ts takes them as {@link UpgraderJobDeps}; the
8
+ * database queue and the job log come from `@caelo-cms/platform-updater`,
9
+ * which owns the Query API connection.
10
+ *
11
+ * Nothing here reads the process env or argv: what the job may install and
12
+ * from where is compiled in (release-manifest.ts's registry + signer), and
13
+ * where it runs comes from the metadata server.
14
+ */
15
+
16
+ import { mkdtempSync } from "node:fs";
17
+ import { tmpdir } from "node:os";
18
+ import { join } from "node:path";
19
+ import { gcloud } from "./gcloud.js";
20
+ import { type GcloudRunner, realSleep } from "./gcloud-retry.js";
21
+ import { GCP_STACK_ENV } from "./gcp-names.js";
22
+ import type { InstallMetadata } from "./install-state.js";
23
+ import { readAdminConfig, runMigrationsViaCloudRunJob } from "./migration-runner.js";
24
+ import { fetchVerifiedReleaseManifest } from "./release-manifest.js";
25
+ import type { HttpFetch } from "./runtime-secrets.js";
26
+ import { opsBucketName } from "./state-backend.js";
27
+ import { acquireBucketStateLock, GcsStateStore } from "./state-store.js";
28
+ import type { AppRollPlan } from "./upgrade/app-roll.js";
29
+ import { findServiceUrl } from "./upgrade/gcp-lookup.js";
30
+ import { createIapHealthProbe } from "./upgrade/health.js";
31
+ import type { UpdateQueue, UpgraderJobDeps } from "./upgrader-job.js";
32
+ import type { JobLog } from "./upgrader-reporter.js";
33
+
34
+ /** Where the job runs, from the metadata server. */
35
+ export interface UpgraderPlacement {
36
+ readonly projectId: string;
37
+ readonly region: string;
38
+ readonly execution: string | null;
39
+ }
40
+
41
+ /**
42
+ * The Cloud SQL private IP the admin connects to — the job connects there
43
+ * too (as its own IAM database user). Read from the deployed admin service.
44
+ */
45
+ export async function upgraderDatabaseHost(
46
+ placement: Pick<UpgraderPlacement, "projectId" | "region">,
47
+ run: GcloudRunner = gcloud,
48
+ ): Promise<string> {
49
+ const cfg = await readAdminConfig(placement, run);
50
+ if (!cfg) {
51
+ throw new Error(
52
+ `could not read the admin service's database host in ${placement.projectId}/${placement.region}`,
53
+ );
54
+ }
55
+ return cfg.databaseHost;
56
+ }
57
+
58
+ /** The IAP-backed deep health probe for `plan`'s install. */
59
+ async function healthProbeFor(run: GcloudRunner, http: HttpFetch, plan: AppRollPlan) {
60
+ const { install } = plan;
61
+ const urls: Partial<Record<"admin" | "gateway", string>> = {};
62
+ for (const s of plan.services) {
63
+ const url = await findServiceUrl(run, install.projectId, install.region, s.serviceName);
64
+ if (!url) {
65
+ return { ok: false as const, error: `could not read the ${s.slug} service's URL` };
66
+ }
67
+ urls[s.slug] = url;
68
+ }
69
+ return {
70
+ ok: true as const,
71
+ probe: createIapHealthProbe(
72
+ {
73
+ provider: install.provider,
74
+ projectId: install.projectId,
75
+ domain: install.domain,
76
+ adminServiceUrl: urls.admin as string,
77
+ gatewayServiceUrl: urls.gateway as string,
78
+ },
79
+ { run, http, sleep: realSleep },
80
+ ),
81
+ };
82
+ }
83
+
84
+ /** Every dependency of {@link runUpgraderJob} except the database and the log. */
85
+ export function gcpUpgraderDeps(
86
+ placement: UpgraderPlacement,
87
+ queue: UpdateQueue,
88
+ log: JobLog,
89
+ ): UpgraderJobDeps {
90
+ const run: GcloudRunner = gcloud;
91
+ const http = fetch as unknown as HttpFetch;
92
+ // The bucket store mirrors to a local dir; in the job that is scratch.
93
+ const store = new GcsStateStore(
94
+ opsBucketName(placement.projectId),
95
+ mkdtempSync(join(tmpdir(), "caelo-upgrader-")),
96
+ );
97
+ return {
98
+ queue,
99
+ run,
100
+ sleep: realSleep,
101
+ projectId: placement.projectId,
102
+ region: placement.region,
103
+ env: GCP_STACK_ENV,
104
+ execution: placement.execution,
105
+ // The default deps: the fixed registry and signer, never overridden.
106
+ fetchManifest: (version) => fetchVerifiedReleaseManifest(version),
107
+ lockState: () =>
108
+ acquireBucketStateLock(opsBucketName(placement.projectId), "caelo upgrader job"),
109
+ readInstall: () => {
110
+ const text = store.read("install.json");
111
+ return text === null ? null : (JSON.parse(text) as InstallMetadata);
112
+ },
113
+ writeInstall: (meta) => store.write("install.json", meta),
114
+ migrate: (imageRef) =>
115
+ runMigrationsViaCloudRunJob(
116
+ { projectId: placement.projectId, region: placement.region, imageOverride: imageRef },
117
+ { run },
118
+ ),
119
+ healthProbe: (plan) => healthProbeFor(run, http, plan),
120
+ now: () => new Date(),
121
+ log,
122
+ };
123
+ }