@caelo-cms/provisioning 0.10.53 → 0.10.57
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.
- package/dist/agent-db.js +272 -128
- package/dist/audit-job-setup.d.ts +69 -0
- package/dist/audit-job-setup.d.ts.map +1 -0
- package/dist/audit-job-setup.js +317 -0
- package/dist/audit-job-setup.js.map +1 -0
- package/dist/caddy.d.ts +9 -0
- package/dist/caddy.d.ts.map +1 -1
- package/dist/caddy.js +38 -0
- package/dist/caddy.js.map +1 -1
- package/dist/cli-routing.js +1 -1
- package/dist/cli-routing.js.map +1 -1
- package/dist/cli.js +36 -9
- package/dist/cli.js.map +1 -1
- package/dist/compose.js +5 -0
- package/dist/compose.js.map +1 -1
- package/dist/converge-checkpoint.d.ts +66 -0
- package/dist/converge-checkpoint.d.ts.map +1 -0
- package/dist/converge-checkpoint.js +61 -0
- package/dist/converge-checkpoint.js.map +1 -0
- package/dist/converge-job-gcp.d.ts +46 -0
- package/dist/converge-job-gcp.d.ts.map +1 -0
- package/dist/converge-job-gcp.js +368 -0
- package/dist/converge-job-gcp.js.map +1 -0
- package/dist/converge-job-setup.d.ts +62 -0
- package/dist/converge-job-setup.d.ts.map +1 -0
- package/dist/converge-job-setup.js +228 -0
- package/dist/converge-job-setup.js.map +1 -0
- package/dist/converge-job.d.ts +141 -0
- package/dist/converge-job.d.ts.map +1 -0
- package/dist/converge-job.js +325 -0
- package/dist/converge-job.js.map +1 -0
- package/dist/gcp-names.d.ts +25 -1
- package/dist/gcp-names.d.ts.map +1 -1
- package/dist/gcp-names.js +36 -0
- package/dist/gcp-names.js.map +1 -1
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -1
- package/dist/infra-contract.d.ts +1 -1
- package/dist/infra-contract.d.ts.map +1 -1
- package/dist/infra-contract.js +28 -1
- package/dist/infra-contract.js.map +1 -1
- package/dist/install-job.d.ts +11 -3
- package/dist/install-job.d.ts.map +1 -1
- package/dist/install-job.js +22 -12
- package/dist/install-job.js.map +1 -1
- package/dist/lifecycle.d.ts +13 -1
- package/dist/lifecycle.d.ts.map +1 -1
- package/dist/lifecycle.js +115 -58
- package/dist/lifecycle.js.map +1 -1
- package/dist/operator-access.d.ts.map +1 -1
- package/dist/operator-access.js +13 -3
- package/dist/operator-access.js.map +1 -1
- package/dist/private-file.d.ts +26 -0
- package/dist/private-file.d.ts.map +1 -0
- package/dist/private-file.js +49 -0
- package/dist/private-file.js.map +1 -0
- package/dist/release-contract.d.ts +3 -2
- package/dist/release-contract.d.ts.map +1 -1
- package/dist/release-contract.js +8 -2
- package/dist/release-contract.js.map +1 -1
- package/dist/release-manifest.d.ts +2 -0
- package/dist/release-manifest.d.ts.map +1 -1
- package/dist/release-manifest.js.map +1 -1
- package/dist/remote-mcp-edge.d.ts +114 -0
- package/dist/remote-mcp-edge.d.ts.map +1 -0
- package/dist/remote-mcp-edge.js +116 -0
- package/dist/remote-mcp-edge.js.map +1 -0
- package/dist/remote-mcp.d.ts +117 -0
- package/dist/remote-mcp.d.ts.map +1 -0
- package/dist/remote-mcp.js +379 -0
- package/dist/remote-mcp.js.map +1 -0
- package/dist/runtime-secrets.d.ts +12 -0
- package/dist/runtime-secrets.d.ts.map +1 -1
- package/dist/runtime-secrets.js +32 -4
- package/dist/runtime-secrets.js.map +1 -1
- package/dist/seal-key-rotation.d.ts +38 -0
- package/dist/seal-key-rotation.d.ts.map +1 -0
- package/dist/seal-key-rotation.js +206 -0
- package/dist/seal-key-rotation.js.map +1 -0
- package/dist/stack-contract.d.ts +175 -3
- package/dist/stack-contract.d.ts.map +1 -1
- package/dist/stack-contract.js +183 -3
- package/dist/stack-contract.js.map +1 -1
- package/dist/stack-converge.d.ts +38 -1
- package/dist/stack-converge.d.ts.map +1 -1
- package/dist/stack-converge.js +74 -4
- package/dist/stack-converge.js.map +1 -1
- package/dist/state-lock-lease.d.ts +142 -0
- package/dist/state-lock-lease.d.ts.map +1 -0
- package/dist/state-lock-lease.js +347 -0
- package/dist/state-lock-lease.js.map +1 -0
- package/dist/state-store.d.ts +66 -9
- package/dist/state-store.d.ts.map +1 -1
- package/dist/state-store.js +249 -30
- package/dist/state-store.js.map +1 -1
- package/dist/tar-entries.d.ts.map +1 -1
- package/dist/tar-entries.js +84 -9
- package/dist/tar-entries.js.map +1 -1
- package/dist/upgrade/app-roll.d.ts +36 -7
- package/dist/upgrade/app-roll.d.ts.map +1 -1
- package/dist/upgrade/app-roll.js +88 -19
- package/dist/upgrade/app-roll.js.map +1 -1
- package/dist/upgrade/audit-job-roll.d.ts +38 -0
- package/dist/upgrade/audit-job-roll.d.ts.map +1 -0
- package/dist/upgrade/audit-job-roll.js +43 -0
- package/dist/upgrade/audit-job-roll.js.map +1 -0
- package/dist/upgrade/converge.d.ts.map +1 -1
- package/dist/upgrade/converge.js +141 -8
- package/dist/upgrade/converge.js.map +1 -1
- package/dist/upgrade/gcp-lookup.d.ts +27 -10
- package/dist/upgrade/gcp-lookup.d.ts.map +1 -1
- package/dist/upgrade/gcp-lookup.js +46 -7
- package/dist/upgrade/gcp-lookup.js.map +1 -1
- package/dist/upgrade/record-rolled.d.ts +40 -0
- package/dist/upgrade/record-rolled.d.ts.map +1 -0
- package/dist/upgrade/record-rolled.js +46 -0
- package/dist/upgrade/record-rolled.js.map +1 -0
- package/dist/upgrade/upgrade.d.ts +45 -1
- package/dist/upgrade/upgrade.d.ts.map +1 -1
- package/dist/upgrade/upgrade.js +60 -17
- package/dist/upgrade/upgrade.js.map +1 -1
- package/dist/upgrader-assess.d.ts +15 -1
- package/dist/upgrader-assess.d.ts.map +1 -1
- package/dist/upgrader-assess.js +36 -6
- package/dist/upgrader-assess.js.map +1 -1
- package/dist/upgrader-gcp.d.ts +15 -0
- package/dist/upgrader-gcp.d.ts.map +1 -1
- package/dist/upgrader-gcp.js +42 -7
- package/dist/upgrader-gcp.js.map +1 -1
- package/dist/upgrader-iam-preflight.d.ts +123 -0
- package/dist/upgrader-iam-preflight.d.ts.map +1 -0
- package/dist/upgrader-iam-preflight.js +414 -0
- package/dist/upgrader-iam-preflight.js.map +1 -0
- package/dist/upgrader-job.d.ts +33 -9
- package/dist/upgrader-job.d.ts.map +1 -1
- package/dist/upgrader-job.js +169 -27
- package/dist/upgrader-job.js.map +1 -1
- package/dist/upgrader-records.d.ts +58 -0
- package/dist/upgrader-records.d.ts.map +1 -0
- package/dist/upgrader-records.js +87 -0
- package/dist/upgrader-records.js.map +1 -0
- package/dist/upgrader-reporter.d.ts +3 -2
- package/dist/upgrader-reporter.d.ts.map +1 -1
- package/dist/upgrader-reporter.js +10 -2
- package/dist/upgrader-reporter.js.map +1 -1
- package/dist/upgrader-setup.d.ts +25 -3
- package/dist/upgrader-setup.d.ts.map +1 -1
- package/dist/upgrader-setup.js +132 -14
- package/dist/upgrader-setup.js.map +1 -1
- package/dist/wizards/gcp-cost.d.ts.map +1 -1
- package/dist/wizards/gcp-cost.js +14 -6
- package/dist/wizards/gcp-cost.js.map +1 -1
- package/dist/wizards/gcp-pulumi.d.ts +6 -0
- package/dist/wizards/gcp-pulumi.d.ts.map +1 -1
- package/dist/wizards/gcp-pulumi.js +7 -0
- package/dist/wizards/gcp-pulumi.js.map +1 -1
- package/dist/wizards/gcp.d.ts.map +1 -1
- package/dist/wizards/gcp.js +130 -4
- package/dist/wizards/gcp.js.map +1 -1
- package/package.json +4 -4
- package/src/agent/run.test.ts +15 -1
- package/src/audit-job-setup.test.ts +234 -0
- package/src/audit-job-setup.ts +360 -0
- package/src/caddy-redirects.docker.test.ts +19 -6
- package/src/caddy.test.ts +30 -0
- package/src/caddy.ts +43 -0
- package/src/cli-init-validation.test.ts +44 -0
- package/src/cli-routing.ts +1 -1
- package/src/cli.ts +40 -8
- package/src/compose.ts +5 -0
- package/src/converge-checkpoint.ts +75 -0
- package/src/converge-entrypoint.test.ts +213 -0
- package/src/converge-job-gcp.test.ts +222 -0
- package/src/converge-job-gcp.ts +443 -0
- package/src/converge-job-setup.test.ts +152 -0
- package/src/converge-job-setup.ts +268 -0
- package/src/converge-job.test.ts +486 -0
- package/src/converge-job.ts +542 -0
- package/src/gateway-env-isolation.test.ts +8 -1
- package/src/gcp-names.ts +43 -1
- package/src/gcp-url-map.test.ts +9 -1
- package/src/index.ts +8 -0
- package/src/infra-contract.test.ts +7 -4
- package/src/infra-contract.ts +28 -1
- package/src/install-job.test.ts +3 -1
- package/src/install-job.ts +31 -14
- package/src/lifecycle.ts +137 -71
- package/src/operator-access.test.ts +14 -0
- package/src/operator-access.ts +13 -3
- package/src/private-file.test.ts +80 -0
- package/src/private-file.ts +50 -0
- package/src/release-contract.ts +8 -2
- package/src/release-manifest.test.ts +42 -0
- package/src/release-manifest.ts +2 -0
- package/src/remote-mcp-edge.ts +137 -0
- package/src/remote-mcp.test.ts +517 -0
- package/src/remote-mcp.ts +460 -0
- package/src/runtime-secrets.test.ts +31 -6
- package/src/runtime-secrets.ts +41 -3
- package/src/seal-key-rotation.test.ts +131 -0
- package/src/seal-key-rotation.ts +258 -0
- package/src/stack-contract-parity.test.ts +20 -3
- package/src/stack-contract.test.ts +34 -2
- package/src/stack-contract.ts +305 -3
- package/src/stack-converge-secrets.test.ts +3 -0
- package/src/stack-converge.test.ts +21 -5
- package/src/stack-converge.ts +101 -7
- package/src/state-attach.test.ts +7 -1
- package/src/state-lock-lease.test.ts +302 -0
- package/src/state-lock-lease.ts +423 -0
- package/src/state-lock.test.ts +568 -0
- package/src/state-store.ts +347 -27
- package/src/tar-entries.test.ts +149 -7
- package/src/tar-entries.ts +90 -9
- package/src/upgrade/app-roll.ts +135 -21
- package/src/upgrade/audit-job-roll.ts +68 -0
- package/src/upgrade/converge.ts +170 -7
- package/src/upgrade/gcp-lookup.test.ts +80 -0
- package/src/upgrade/gcp-lookup.ts +65 -8
- package/src/upgrade/record-rolled.test.ts +105 -0
- package/src/upgrade/record-rolled.ts +91 -0
- package/src/upgrade/upgrade.test.ts +279 -5
- package/src/upgrade/upgrade.ts +120 -24
- package/src/upgrader-assess.test.ts +93 -3
- package/src/upgrader-assess.ts +58 -9
- package/src/upgrader-gcp.ts +47 -8
- package/src/upgrader-iam-preflight.test.ts +393 -0
- package/src/upgrader-iam-preflight.ts +510 -0
- package/src/upgrader-job.test.ts +587 -15
- package/src/upgrader-job.ts +237 -53
- package/src/upgrader-records.ts +151 -0
- package/src/upgrader-reporter.test.ts +6 -0
- package/src/upgrader-reporter.ts +7 -2
- package/src/upgrader-setup.test.ts +4 -3
- package/src/upgrader-setup.ts +137 -13
- package/src/wizards/gcp-cost.ts +15 -6
- package/src/wizards/gcp-pulumi.ts +7 -0
- package/src/wizards/gcp.ts +162 -4
- package/stacks/aws/index.ts +18 -0
- package/stacks/azure/index.ts +18 -0
- package/stacks/gcp/index.ts +59 -7
- package/stacks/gcp-firebase/index.ts +50 -6
|
@@ -0,0 +1,302 @@
|
|
|
1
|
+
// SPDX-License-Identifier: MPL-2.0
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* The state lock's lease (state-lock-lease.ts, #815): when a held lock may
|
|
5
|
+
* be broken, and how Cloud Run's execution status is read.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { describe, expect, it } from "bun:test";
|
|
9
|
+
import type { GcloudResult } from "./gcloud.js";
|
|
10
|
+
import {
|
|
11
|
+
type CloudRunExecutionRef,
|
|
12
|
+
type ExecutionState,
|
|
13
|
+
judgeHeldLock,
|
|
14
|
+
LEASE_SKEW_MS,
|
|
15
|
+
type LocalHost,
|
|
16
|
+
lockBody,
|
|
17
|
+
machineId,
|
|
18
|
+
PROCESS_LEASE_MS,
|
|
19
|
+
parseExecutionState,
|
|
20
|
+
parseLockBody,
|
|
21
|
+
renewedLockBody,
|
|
22
|
+
} from "./state-lock-lease.js";
|
|
23
|
+
|
|
24
|
+
const ok = (stdout = ""): GcloudResult => ({ ok: true, stdout, stderr: "", exitCode: 0 });
|
|
25
|
+
const fail = (stderr: string): GcloudResult => ({ ok: false, stdout: "", stderr, exitCode: 1 });
|
|
26
|
+
|
|
27
|
+
const EXEC: CloudRunExecutionRef = {
|
|
28
|
+
projectId: "acme",
|
|
29
|
+
region: "europe-west1",
|
|
30
|
+
name: "caelo-production-upgrader-x1",
|
|
31
|
+
};
|
|
32
|
+
const START = new Date("2026-10-16T10:00:00Z");
|
|
33
|
+
const HOUR = 60 * 60_000;
|
|
34
|
+
/** The lease of a lock taken at START by a one-hour job. */
|
|
35
|
+
const LEASE_END = START.getTime() + HOUR + LEASE_SKEW_MS;
|
|
36
|
+
|
|
37
|
+
const jobLock = lockBody("caelo upgrader job", START, { execution: EXEC, maxRuntimeMs: HOUR });
|
|
38
|
+
const at = (ms: number) => new Date(ms);
|
|
39
|
+
const state =
|
|
40
|
+
(s: ExecutionState) =>
|
|
41
|
+
(ref: CloudRunExecutionRef): ExecutionState => {
|
|
42
|
+
expect(ref).toEqual(EXEC);
|
|
43
|
+
return s;
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
describe("lockBody", () => {
|
|
47
|
+
it("an execution's lock names the execution and a lease of task timeout + skew", () => {
|
|
48
|
+
const body = parseLockBody(jobLock);
|
|
49
|
+
expect(body?.execution).toEqual(EXEC);
|
|
50
|
+
expect(body?.leaseUntil).toBe(new Date(LEASE_END).toISOString());
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
it("a machine-local lock (no holder) carries neither", () => {
|
|
54
|
+
const body = parseLockBody(lockBody("cms-provision upgrade", START));
|
|
55
|
+
expect(body?.purpose).toBe("cms-provision upgrade");
|
|
56
|
+
expect(body?.execution).toBeUndefined();
|
|
57
|
+
expect(body?.leaseUntil).toBeUndefined();
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
it("a CLI process's lock names host + pid, a unique id and a lease of leaseMs + skew", () => {
|
|
61
|
+
const a = parseLockBody(
|
|
62
|
+
lockBody("cms-provision upgrade", START, { leaseMs: PROCESS_LEASE_MS }),
|
|
63
|
+
);
|
|
64
|
+
const b = parseLockBody(
|
|
65
|
+
lockBody("cms-provision upgrade", START, { leaseMs: PROCESS_LEASE_MS }),
|
|
66
|
+
);
|
|
67
|
+
expect(a?.pid).toBe(process.pid);
|
|
68
|
+
expect(a?.host).toBeTruthy();
|
|
69
|
+
expect(a?.execution).toBeUndefined();
|
|
70
|
+
expect(a?.leaseUntil).toBe(
|
|
71
|
+
new Date(START.getTime() + PROCESS_LEASE_MS + LEASE_SKEW_MS).toISOString(),
|
|
72
|
+
);
|
|
73
|
+
expect(a?.id).toBeTruthy();
|
|
74
|
+
expect(a?.id).not.toBe(b?.id);
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
it("a renewed body keeps its id and start and moves only the lease", () => {
|
|
78
|
+
const body = parseLockBody(
|
|
79
|
+
lockBody("cms-provision upgrade", START, { leaseMs: PROCESS_LEASE_MS }, "id-1"),
|
|
80
|
+
);
|
|
81
|
+
if (!body) throw new Error("unparsable");
|
|
82
|
+
const later = at(START.getTime() + 3 * HOUR);
|
|
83
|
+
const renewed = parseLockBody(renewedLockBody(body, later, PROCESS_LEASE_MS));
|
|
84
|
+
expect(renewed).toMatchObject({ id: "id-1", startedAt: START.toISOString() });
|
|
85
|
+
expect(renewed?.leaseUntil).toBe(
|
|
86
|
+
new Date(later.getTime() + PROCESS_LEASE_MS + LEASE_SKEW_MS).toISOString(),
|
|
87
|
+
);
|
|
88
|
+
});
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
describe("judgeHeldLock — a CLI process holder (#815 review)", () => {
|
|
92
|
+
const HERE: LocalHost = { host: "laptop", alive: (pid) => pid === 4242 };
|
|
93
|
+
const cli = (host: string, pid: number) =>
|
|
94
|
+
JSON.stringify({
|
|
95
|
+
purpose: "cms-provision upgrade",
|
|
96
|
+
host,
|
|
97
|
+
pid,
|
|
98
|
+
startedAt: START.toISOString(),
|
|
99
|
+
leaseUntil: new Date(START.getTime() + PROCESS_LEASE_MS + LEASE_SKEW_MS).toISOString(),
|
|
100
|
+
});
|
|
101
|
+
const LEASE = START.getTime() + PROCESS_LEASE_MS + LEASE_SKEW_MS;
|
|
102
|
+
const never = () => {
|
|
103
|
+
throw new Error("Cloud Run must not be asked about a CLI holder");
|
|
104
|
+
};
|
|
105
|
+
|
|
106
|
+
it("its own host's lock whose pid is dead is broken at once, inside the lease", () => {
|
|
107
|
+
const v = judgeHeldLock(cli("laptop", 9999), never, at(START.getTime() + 1), HERE);
|
|
108
|
+
expect(v.kind).toBe("break");
|
|
109
|
+
expect(v.why).toContain("process is gone");
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
it("its own host's lock whose pid lives is held until the lease ends", () => {
|
|
113
|
+
expect(judgeHeldLock(cli("laptop", 4242), never, at(LEASE), HERE)).toMatchObject({
|
|
114
|
+
kind: "held",
|
|
115
|
+
manualRemoval: false,
|
|
116
|
+
});
|
|
117
|
+
expect(judgeHeldLock(cli("laptop", 4242), never, at(LEASE + 1), HERE).kind).toBe("break");
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
it("another host's lock (a Cloud Shell) is held within the lease, broken after it", () => {
|
|
121
|
+
expect(judgeHeldLock(cli("cs-1", 9999), never, at(LEASE), HERE).kind).toBe("held");
|
|
122
|
+
const v = judgeHeldLock(cli("cs-1", 9999), never, at(LEASE + 1), HERE);
|
|
123
|
+
expect(v.kind).toBe("break");
|
|
124
|
+
expect(v.why).toContain("stopped renewing");
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
it("#815 review: a dead pid is only judged on the machine that took the lock", () => {
|
|
128
|
+
const at1 = at(START.getTime() + 1);
|
|
129
|
+
const withMachine = (machine: string | null) =>
|
|
130
|
+
JSON.stringify({ ...JSON.parse(cli("localhost", 9999)), machine });
|
|
131
|
+
const here = (machine: string | null): LocalHost => ({
|
|
132
|
+
host: "localhost",
|
|
133
|
+
machine,
|
|
134
|
+
alive: () => false,
|
|
135
|
+
});
|
|
136
|
+
// Same host name, another machine (two Cloud Run containers, two
|
|
137
|
+
// laptops called alike): the pid is someone else's, the lease decides.
|
|
138
|
+
expect(judgeHeldLock(withMachine("m-a"), never, at1, here("m-b")).kind).toBe("held");
|
|
139
|
+
// A lock from Cloud Run names no machine: never judged by its pid.
|
|
140
|
+
expect(judgeHeldLock(withMachine(null), never, at1, here("m-b")).kind).toBe("held");
|
|
141
|
+
// A judge in Cloud Run has no machine id: it never trusts a pid, not
|
|
142
|
+
// even one of a lock written before machine ids.
|
|
143
|
+
expect(judgeHeldLock(cli("localhost", 9999), never, at1, here(null)).kind).toBe("held");
|
|
144
|
+
// The same machine: the dead pid ends it at once.
|
|
145
|
+
expect(judgeHeldLock(withMachine("m-a"), never, at1, here("m-a")).kind).toBe("break");
|
|
146
|
+
// Whatever the machine, the lease still ends it.
|
|
147
|
+
expect(judgeHeldLock(withMachine("m-a"), never, at(LEASE + 1), here("m-b")).kind).toBe("break");
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
it("#815 review: machineId is null inside Cloud Run and stable outside", () => {
|
|
151
|
+
expect(machineId({ CLOUD_RUN_EXECUTION: "caelo-upgrader-abc" })).toBeNull();
|
|
152
|
+
expect(machineId({ K_SERVICE: "caelo-production-admin" })).toBeNull();
|
|
153
|
+
expect(machineId({})).toBe(machineId({}));
|
|
154
|
+
const body = parseLockBody(lockBody("cms-provision upgrade", START, { leaseMs: 1 }));
|
|
155
|
+
expect(body && "machine" in body).toBe(true);
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
it("an unreadable lease is held for a person to remove", () => {
|
|
159
|
+
const body = JSON.stringify({
|
|
160
|
+
purpose: "cms-provision upgrade",
|
|
161
|
+
host: "cs-1",
|
|
162
|
+
pid: 1,
|
|
163
|
+
leaseUntil: "soon",
|
|
164
|
+
});
|
|
165
|
+
expect(judgeHeldLock(body, never, at(LEASE + HOUR), HERE)).toMatchObject({
|
|
166
|
+
kind: "held",
|
|
167
|
+
manualRemoval: true,
|
|
168
|
+
});
|
|
169
|
+
});
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
describe("judgeHeldLock", () => {
|
|
173
|
+
it("breaks the lock of an execution that has finished — succeeded, failed or cancelled", () => {
|
|
174
|
+
for (const how of ["succeeded", "failed", "cancelled"]) {
|
|
175
|
+
const v = judgeHeldLock(
|
|
176
|
+
jobLock,
|
|
177
|
+
state({ kind: "finished", how }),
|
|
178
|
+
at(START.getTime() + 60_000),
|
|
179
|
+
);
|
|
180
|
+
expect(v.kind).toBe("break");
|
|
181
|
+
expect(v.why).toContain(`has ${how}`);
|
|
182
|
+
}
|
|
183
|
+
});
|
|
184
|
+
|
|
185
|
+
it("never breaks the lock of a running execution, even long past its lease", () => {
|
|
186
|
+
for (const now of [START.getTime() + 1, LEASE_END + 1, LEASE_END + 24 * HOUR]) {
|
|
187
|
+
const v = judgeHeldLock(jobLock, state({ kind: "running" }), at(now));
|
|
188
|
+
expect(v).toMatchObject({ kind: "held", manualRemoval: false });
|
|
189
|
+
expect(v.why).toContain("still running");
|
|
190
|
+
}
|
|
191
|
+
});
|
|
192
|
+
|
|
193
|
+
it("an execution Cloud Run does not know: held within the lease, broken after it", () => {
|
|
194
|
+
const gone = state({ kind: "not-found" });
|
|
195
|
+
expect(judgeHeldLock(jobLock, gone, at(START.getTime())).kind).toBe("held");
|
|
196
|
+
// Exactly at the lease's end it still holds; one millisecond later it is dead.
|
|
197
|
+
expect(judgeHeldLock(jobLock, gone, at(LEASE_END)).kind).toBe("held");
|
|
198
|
+
expect(judgeHeldLock(jobLock, gone, at(LEASE_END + 1)).kind).toBe("break");
|
|
199
|
+
});
|
|
200
|
+
|
|
201
|
+
it("a lock whose lease is unreadable is never broken on 'not found'", () => {
|
|
202
|
+
const body = JSON.stringify({
|
|
203
|
+
purpose: "caelo upgrader job",
|
|
204
|
+
execution: EXEC,
|
|
205
|
+
leaseUntil: "soon",
|
|
206
|
+
});
|
|
207
|
+
const v = judgeHeldLock(body, state({ kind: "not-found" }), at(LEASE_END + 10 * HOUR));
|
|
208
|
+
expect(v).toMatchObject({ kind: "held", manualRemoval: true });
|
|
209
|
+
});
|
|
210
|
+
|
|
211
|
+
it("a holder Cloud Run cannot be asked about is undecided, never dead", () => {
|
|
212
|
+
const v = judgeHeldLock(
|
|
213
|
+
jobLock,
|
|
214
|
+
state({ kind: "unknown", error: "PERMISSION_DENIED" }),
|
|
215
|
+
at(LEASE_END + HOUR),
|
|
216
|
+
);
|
|
217
|
+
expect(v.kind).toBe("unknown");
|
|
218
|
+
expect(v.why).toContain("PERMISSION_DENIED");
|
|
219
|
+
});
|
|
220
|
+
|
|
221
|
+
it("a pre-#815 lock (no lease) from another host is never broken automatically, and Cloud Run is not asked", () => {
|
|
222
|
+
const describe = () => {
|
|
223
|
+
throw new Error("must not be asked");
|
|
224
|
+
};
|
|
225
|
+
const cli = judgeHeldLock(
|
|
226
|
+
lockBody("cms-provision upgrade", START),
|
|
227
|
+
describe,
|
|
228
|
+
at(START.getTime() + 100 * HOUR),
|
|
229
|
+
);
|
|
230
|
+
expect(cli).toMatchObject({ kind: "held", manualRemoval: true });
|
|
231
|
+
const legacy = JSON.stringify({
|
|
232
|
+
purpose: "caelo upgrader job",
|
|
233
|
+
host: "localhost",
|
|
234
|
+
pid: 1,
|
|
235
|
+
startedAt: START.toISOString(),
|
|
236
|
+
});
|
|
237
|
+
expect(
|
|
238
|
+
judgeHeldLock(legacy, describe, at(START.getTime() + 100 * HOUR), {
|
|
239
|
+
host: "laptop",
|
|
240
|
+
alive: () => false,
|
|
241
|
+
}).kind,
|
|
242
|
+
).toBe("held");
|
|
243
|
+
});
|
|
244
|
+
|
|
245
|
+
it("an unreadable lock body is held, with the body quoted", () => {
|
|
246
|
+
const v = judgeHeldLock("not json", state({ kind: "running" }), START);
|
|
247
|
+
expect(v).toMatchObject({ kind: "held", manualRemoval: true });
|
|
248
|
+
expect(v.why).toContain("not json");
|
|
249
|
+
});
|
|
250
|
+
});
|
|
251
|
+
|
|
252
|
+
describe("parseExecutionState", () => {
|
|
253
|
+
const exec = (status: unknown) => ok(JSON.stringify({ status }));
|
|
254
|
+
|
|
255
|
+
it("reads the Completed condition: True succeeded, False failed or cancelled", () => {
|
|
256
|
+
expect(
|
|
257
|
+
parseExecutionState(exec({ conditions: [{ type: "Completed", status: "True" }] })),
|
|
258
|
+
).toEqual({
|
|
259
|
+
kind: "finished",
|
|
260
|
+
how: "succeeded",
|
|
261
|
+
});
|
|
262
|
+
expect(
|
|
263
|
+
parseExecutionState(
|
|
264
|
+
exec({ conditions: [{ type: "Completed", status: "False", reason: "Cancelled" }] }),
|
|
265
|
+
),
|
|
266
|
+
).toEqual({ kind: "finished", how: "cancelled" });
|
|
267
|
+
expect(
|
|
268
|
+
parseExecutionState(exec({ conditions: [{ type: "Completed", status: "False" }] })),
|
|
269
|
+
).toEqual({
|
|
270
|
+
kind: "finished",
|
|
271
|
+
how: "failed",
|
|
272
|
+
});
|
|
273
|
+
});
|
|
274
|
+
|
|
275
|
+
it("a completion time without a decided condition is finished too", () => {
|
|
276
|
+
expect(parseExecutionState(exec({ completionTime: "2026-10-16T11:00:00Z" })).kind).toBe(
|
|
277
|
+
"finished",
|
|
278
|
+
);
|
|
279
|
+
});
|
|
280
|
+
|
|
281
|
+
it("an undecided or not yet started execution is running", () => {
|
|
282
|
+
expect(
|
|
283
|
+
parseExecutionState(exec({ conditions: [{ type: "Completed", status: "Unknown" }] })).kind,
|
|
284
|
+
).toBe("running");
|
|
285
|
+
expect(parseExecutionState(exec({})).kind).toBe("running");
|
|
286
|
+
expect(parseExecutionState(ok("{}")).kind).toBe("running");
|
|
287
|
+
});
|
|
288
|
+
|
|
289
|
+
it("not found vs. any other gcloud failure", () => {
|
|
290
|
+
expect(
|
|
291
|
+
parseExecutionState(fail("ERROR: (gcloud.run.jobs.executions.describe) NOT_FOUND")).kind,
|
|
292
|
+
).toBe("not-found");
|
|
293
|
+
expect(parseExecutionState(fail("PERMISSION_DENIED: run.executions.get"))).toEqual({
|
|
294
|
+
kind: "unknown",
|
|
295
|
+
error: "PERMISSION_DENIED: run.executions.get",
|
|
296
|
+
});
|
|
297
|
+
expect(parseExecutionState(ok("garbage")).kind).toBe("unknown");
|
|
298
|
+
expect(parseExecutionState(ok(JSON.stringify({ status: { conditions: "x" } }))).kind).toBe(
|
|
299
|
+
"unknown",
|
|
300
|
+
);
|
|
301
|
+
});
|
|
302
|
+
});
|