@caelo-cms/provisioning 0.10.65 → 0.10.67
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 +37 -6
- package/dist/converge-job-gcp.d.ts.map +1 -1
- package/dist/converge-job-gcp.js +1 -1
- package/dist/converge-job-gcp.js.map +1 -1
- package/dist/install-state.d.ts +17 -2
- package/dist/install-state.d.ts.map +1 -1
- package/dist/install-state.js +11 -3
- package/dist/install-state.js.map +1 -1
- package/dist/lifecycle.d.ts +4 -1
- package/dist/lifecycle.d.ts.map +1 -1
- package/dist/lifecycle.js +12 -2
- package/dist/lifecycle.js.map +1 -1
- package/dist/release-manifest.d.ts +10 -4
- package/dist/release-manifest.d.ts.map +1 -1
- package/dist/release-manifest.js +17 -8
- package/dist/release-manifest.js.map +1 -1
- package/dist/upgrade/__tests__/live-indexes.d.ts +23 -0
- package/dist/upgrade/__tests__/live-indexes.d.ts.map +1 -0
- package/dist/upgrade/__tests__/live-indexes.js +41 -0
- package/dist/upgrade/__tests__/live-indexes.js.map +1 -0
- package/dist/upgrade/gcp-lookup.d.ts +22 -13
- package/dist/upgrade/gcp-lookup.d.ts.map +1 -1
- package/dist/upgrade/gcp-lookup.js +42 -3
- package/dist/upgrade/gcp-lookup.js.map +1 -1
- package/dist/upgrade/image-forms.d.ts +55 -0
- package/dist/upgrade/image-forms.d.ts.map +1 -0
- package/dist/upgrade/image-forms.js +164 -0
- package/dist/upgrade/image-forms.js.map +1 -0
- package/dist/upgrade/release-images.d.ts +24 -0
- package/dist/upgrade/release-images.d.ts.map +1 -1
- package/dist/upgrade/release-images.js +30 -0
- package/dist/upgrade/release-images.js.map +1 -1
- package/dist/upgrader-gcp.d.ts.map +1 -1
- package/dist/upgrader-gcp.js +3 -0
- package/dist/upgrader-gcp.js.map +1 -1
- package/dist/upgrader-job.d.ts +8 -0
- package/dist/upgrader-job.d.ts.map +1 -1
- package/dist/upgrader-job.js +51 -10
- package/dist/upgrader-job.js.map +1 -1
- package/dist/upgrader-records.d.ts +27 -2
- package/dist/upgrader-records.d.ts.map +1 -1
- package/dist/upgrader-records.js +77 -19
- package/dist/upgrader-records.js.map +1 -1
- package/dist/wizards/gcp.d.ts.map +1 -1
- package/dist/wizards/gcp.js +8 -1
- package/dist/wizards/gcp.js.map +1 -1
- package/package.json +2 -2
- package/src/converge-job-gcp.ts +1 -0
- package/src/install-state.test.ts +32 -0
- package/src/install-state.ts +21 -2
- package/src/lifecycle.ts +19 -2
- package/src/release-manifest.test.ts +27 -2
- package/src/release-manifest.ts +20 -7
- package/src/upgrade/__tests__/fixtures/admin-0.10.64-index.json +28 -0
- package/src/upgrade/__tests__/fixtures/gateway-0.10.64-index.json +28 -0
- package/src/upgrade/__tests__/live-indexes.ts +61 -0
- package/src/upgrade/gcp-lookup.test.ts +53 -9
- package/src/upgrade/gcp-lookup.ts +60 -15
- package/src/upgrade/image-forms.test.ts +192 -0
- package/src/upgrade/image-forms.ts +226 -0
- package/src/upgrade/release-images.ts +49 -0
- package/src/upgrader-gcp.ts +3 -0
- package/src/upgrader-job.test.ts +153 -0
- package/src/upgrader-job.ts +73 -12
- package/src/upgrader-records.test.ts +202 -0
- package/src/upgrader-records.ts +115 -19
- package/src/wizards/gcp.ts +17 -1
package/src/upgrader-records.ts
CHANGED
|
@@ -15,6 +15,12 @@
|
|
|
15
15
|
* Images that belong to no signed release are a deployment nobody can
|
|
16
16
|
* vouch for, so the run stops and says so.
|
|
17
17
|
*
|
|
18
|
+
* A serving image is a release's image when its digest is one of that
|
|
19
|
+
* image's forms (#1025, upgrade/image-forms.ts): the index the manifest
|
|
20
|
+
* signs, or a platform manifest inside it. Cloud Run records the
|
|
21
|
+
* linux/amd64 platform manifest it resolved, so comparing with the index
|
|
22
|
+
* alone never matched on a live install.
|
|
23
|
+
*
|
|
18
24
|
* Which releases to try comes from untrusted hints — the version
|
|
19
25
|
* install.json names, and the release tags pointing at the serving digests
|
|
20
26
|
* in the fixed mirror (releaseVersionForDigest). A wrong hint costs one
|
|
@@ -25,6 +31,7 @@ import type { GcloudRunner } from "./gcloud-retry.js";
|
|
|
25
31
|
import type { ImageDigests, InstallMetadata } from "./install-state.js";
|
|
26
32
|
import type { ReleaseManifest, ReleaseManifestResult } from "./release-manifest.js";
|
|
27
33
|
import type { RollSlug } from "./upgrade/app-roll.js";
|
|
34
|
+
import type { ReleaseImageForms } from "./upgrade/image-forms.js";
|
|
28
35
|
import { releaseVersionForDigest } from "./upgrade/release-images.js";
|
|
29
36
|
|
|
30
37
|
/** Every digest each serving image is known by (gcp-lookup.ts servingImageDigests). */
|
|
@@ -32,18 +39,35 @@ export type ServingDigests = Readonly<Record<RollSlug, readonly string[]>>;
|
|
|
32
39
|
|
|
33
40
|
const SLUGS: readonly RollSlug[] = ["admin", "gateway"];
|
|
34
41
|
|
|
35
|
-
/**
|
|
42
|
+
/**
|
|
43
|
+
* Whether install.json names the images that serve: per service, the
|
|
44
|
+
* recorded index digest or its recorded linux/amd64 form (#1025) is one of
|
|
45
|
+
* the digests the serving revision is known by.
|
|
46
|
+
*/
|
|
36
47
|
export function recordMatchesServing(
|
|
37
|
-
recorded:
|
|
48
|
+
recorded: Pick<InstallMetadata, "imageDigests" | "platformDigests">,
|
|
38
49
|
serving: ServingDigests,
|
|
39
50
|
): boolean {
|
|
40
|
-
|
|
41
|
-
|
|
51
|
+
const { imageDigests, platformDigests } = recorded;
|
|
52
|
+
if (!imageDigests) return false;
|
|
53
|
+
return SLUGS.every(
|
|
54
|
+
(slug) =>
|
|
55
|
+
serving[slug].includes(imageDigests[slug]) ||
|
|
56
|
+
(platformDigests !== undefined && serving[slug].includes(platformDigests[slug])),
|
|
57
|
+
);
|
|
42
58
|
}
|
|
43
59
|
|
|
44
60
|
/** What {@link findServingRelease} learned. */
|
|
45
61
|
export type ServingRelease =
|
|
46
|
-
| {
|
|
62
|
+
| {
|
|
63
|
+
readonly ok: true;
|
|
64
|
+
readonly manifest: ReleaseManifest;
|
|
65
|
+
/**
|
|
66
|
+
* The linux/amd64 form of each of its images (#1025), to record next
|
|
67
|
+
* to the index digests; null when it is not known for both.
|
|
68
|
+
*/
|
|
69
|
+
readonly platformDigests: ImageDigests | null;
|
|
70
|
+
}
|
|
47
71
|
| {
|
|
48
72
|
readonly ok: false;
|
|
49
73
|
/**
|
|
@@ -62,6 +86,15 @@ export interface ServingReleaseDeps {
|
|
|
62
86
|
readonly run: GcloudRunner;
|
|
63
87
|
/** fetchVerifiedReleaseManifest against the fixed registry. */
|
|
64
88
|
readonly fetchManifest: (version: string) => Promise<ReleaseManifestResult>;
|
|
89
|
+
/** Every digest a release image is known by (#1025; createReleaseImageForms). */
|
|
90
|
+
readonly imageForms: ReleaseImageForms;
|
|
91
|
+
/**
|
|
92
|
+
* The image digests the services' templates name (the index a deploy
|
|
93
|
+
* pinned; after a rollback, possibly another release's). Untrusted hints
|
|
94
|
+
* for which release tags to look up, asked only when nothing else found
|
|
95
|
+
* the serving release.
|
|
96
|
+
*/
|
|
97
|
+
readonly hintDigests?: () => Promise<ServingDigests>;
|
|
65
98
|
}
|
|
66
99
|
|
|
67
100
|
/**
|
|
@@ -79,7 +112,7 @@ export async function findServingRelease(
|
|
|
79
112
|
const unverified: string[] = [];
|
|
80
113
|
const unreachable: string[] = [];
|
|
81
114
|
const tried: string[] = [];
|
|
82
|
-
for await (const version of candidateVersions(meta, serving, deps
|
|
115
|
+
for await (const version of candidateVersions(meta, serving, deps)) {
|
|
83
116
|
if (tried.includes(version)) continue;
|
|
84
117
|
tried.push(version);
|
|
85
118
|
const fetched = await deps.fetchManifest(version);
|
|
@@ -92,12 +125,21 @@ export async function findServingRelease(
|
|
|
92
125
|
continue;
|
|
93
126
|
}
|
|
94
127
|
const m = fetched.manifest;
|
|
95
|
-
const
|
|
96
|
-
if (
|
|
97
|
-
|
|
98
|
-
|
|
128
|
+
const judged = await judgeRelease(m, serving, deps.imageForms);
|
|
129
|
+
if (judged.kind === "unreadable") {
|
|
130
|
+
// The index could not be read, so this release is neither confirmed
|
|
131
|
+
// nor ruled out: an outage is retried, anything else is a release
|
|
132
|
+
// record that could not be verified.
|
|
133
|
+
(judged.transient ? unreachable : unverified).push(`v${version} (${judged.message})`);
|
|
134
|
+
continue;
|
|
135
|
+
}
|
|
136
|
+
if (judged.matching.length === SLUGS.length) {
|
|
137
|
+
return { ok: true, manifest: m, platformDigests: judged.platformDigests };
|
|
138
|
+
}
|
|
139
|
+
if (judged.matching.length > 0) {
|
|
140
|
+
const other = SLUGS.filter((s) => !judged.matching.includes(s));
|
|
99
141
|
partial.push(
|
|
100
|
-
`the ${matching.join(", ")} serves v${m.version}'s image, the ${other.join(", ")} does not`,
|
|
142
|
+
`the ${judged.matching.join(", ")} serves v${m.version}'s image, the ${other.join(", ")} does not`,
|
|
101
143
|
);
|
|
102
144
|
}
|
|
103
145
|
}
|
|
@@ -125,6 +167,51 @@ export async function findServingRelease(
|
|
|
125
167
|
};
|
|
126
168
|
}
|
|
127
169
|
|
|
170
|
+
/** Which of release `m`'s images serve, by {@link judgeRelease}. */
|
|
171
|
+
type ReleaseJudgement =
|
|
172
|
+
| {
|
|
173
|
+
readonly kind: "judged";
|
|
174
|
+
readonly matching: readonly RollSlug[];
|
|
175
|
+
readonly platformDigests: ImageDigests | null;
|
|
176
|
+
}
|
|
177
|
+
| { readonly kind: "unreadable"; readonly transient: boolean; readonly message: string };
|
|
178
|
+
|
|
179
|
+
/**
|
|
180
|
+
* Which of `m`'s images serve. A serving digest equal to the signed index
|
|
181
|
+
* matches without a lookup; any other is compared with the image's
|
|
182
|
+
* platform forms (signed, or read from the content-checked index).
|
|
183
|
+
*/
|
|
184
|
+
async function judgeRelease(
|
|
185
|
+
m: ReleaseManifest,
|
|
186
|
+
serving: ServingDigests,
|
|
187
|
+
imageForms: ReleaseImageForms,
|
|
188
|
+
): Promise<ReleaseJudgement> {
|
|
189
|
+
const matching: RollSlug[] = [];
|
|
190
|
+
const platform: Partial<Record<RollSlug, string>> = {};
|
|
191
|
+
for (const slug of SLUGS) {
|
|
192
|
+
if (serving[slug].includes(m.digests[slug])) {
|
|
193
|
+
matching.push(slug);
|
|
194
|
+
const signed = m.platformDigests?.[slug];
|
|
195
|
+
if (signed) platform[slug] = signed;
|
|
196
|
+
continue;
|
|
197
|
+
}
|
|
198
|
+
const forms = await imageForms.of(m, slug);
|
|
199
|
+
if (!forms.ok)
|
|
200
|
+
return { kind: "unreadable", transient: forms.transient, message: forms.message };
|
|
201
|
+
const match = serving[slug].find((d) => forms.digests.includes(d));
|
|
202
|
+
if (match) {
|
|
203
|
+
matching.push(slug);
|
|
204
|
+
if (forms.amd64) platform[slug] = forms.amd64;
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
const { admin, gateway } = platform;
|
|
208
|
+
return {
|
|
209
|
+
kind: "judged",
|
|
210
|
+
matching,
|
|
211
|
+
platformDigests: admin && gateway ? { admin, gateway } : null,
|
|
212
|
+
};
|
|
213
|
+
}
|
|
214
|
+
|
|
128
215
|
/**
|
|
129
216
|
* Release versions worth checking, most likely first. Lazy: the version
|
|
130
217
|
* install.json names usually is the serving one, and then no tag listing
|
|
@@ -133,17 +220,26 @@ export async function findServingRelease(
|
|
|
133
220
|
async function* candidateVersions(
|
|
134
221
|
meta: InstallMetadata,
|
|
135
222
|
serving: ServingDigests,
|
|
136
|
-
|
|
223
|
+
deps: ServingReleaseDeps,
|
|
137
224
|
): AsyncGenerator<string> {
|
|
138
225
|
if (meta.releaseVersion) yield meta.releaseVersion;
|
|
139
|
-
// Release tags point at the index digest a deploy pins
|
|
140
|
-
//
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
226
|
+
// Release tags point at the index digest a deploy pins. The platform
|
|
227
|
+
// digest Cloud Run records for a revision (#1025) has no tag, so the
|
|
228
|
+
// indexes the service templates name are asked too: only hints, the
|
|
229
|
+
// signed manifest still decides.
|
|
230
|
+
const asked: string[] = [];
|
|
231
|
+
const tagged = async function* (digests: ServingDigests): AsyncGenerator<string> {
|
|
232
|
+
for (const slug of SLUGS) {
|
|
233
|
+
for (const digest of digests[slug]) {
|
|
234
|
+
if (asked.includes(`${slug}@${digest}`)) continue;
|
|
235
|
+
asked.push(`${slug}@${digest}`);
|
|
236
|
+
const version = await releaseVersionForDigest(deps.run, slug, digest);
|
|
237
|
+
if (version) yield version;
|
|
238
|
+
}
|
|
145
239
|
}
|
|
146
|
-
}
|
|
240
|
+
};
|
|
241
|
+
yield* tagged(serving);
|
|
242
|
+
if (deps.hintDigests) yield* tagged(await deps.hintDigests());
|
|
147
243
|
}
|
|
148
244
|
|
|
149
245
|
function capitalize(s: string): string {
|
package/src/wizards/gcp.ts
CHANGED
|
@@ -120,6 +120,7 @@ import {
|
|
|
120
120
|
} from "../state-backend.js";
|
|
121
121
|
import { acquireStateLock, GcsStateStore } from "../state-store.js";
|
|
122
122
|
import { lookupFailure, resolveGcpResourceName } from "../upgrade/gcp-lookup.js";
|
|
123
|
+
import { createReleaseImageForms, platformDigestsOf } from "../upgrade/image-forms.js";
|
|
123
124
|
import {
|
|
124
125
|
releaseVersionForDigest,
|
|
125
126
|
resolveOpsImageRef,
|
|
@@ -500,7 +501,22 @@ export async function runGcpConverge(opts: GcpConvergeOpts): Promise<void> {
|
|
|
500
501
|
// the admin can install the next update itself.
|
|
501
502
|
if (upgraderReady && convergeReady && remoteMcpReady) {
|
|
502
503
|
const version = releaseVersion;
|
|
503
|
-
|
|
504
|
+
// #1025 — the linux/amd64 forms Cloud Run reports, next to the indexes,
|
|
505
|
+
// so the upgrader recognises what serves without a registry lookup.
|
|
506
|
+
const platform = await platformDigestsOf(imageDigests, createReleaseImageForms());
|
|
507
|
+
if (!platform.ok) {
|
|
508
|
+
log.warn(
|
|
509
|
+
yellow(
|
|
510
|
+
`install.json records the release by its image indexes only; their linux/amd64 forms could not be read (${platform.message}). The first in-product update reads them from the release registry.`,
|
|
511
|
+
),
|
|
512
|
+
);
|
|
513
|
+
}
|
|
514
|
+
recordImageDigests(
|
|
515
|
+
installId,
|
|
516
|
+
imageDigests,
|
|
517
|
+
version,
|
|
518
|
+
platform.ok ? platform.platformDigests : null,
|
|
519
|
+
);
|
|
504
520
|
recordInfraContractVersion(installId, INFRA_CONTRACT_VERSION);
|
|
505
521
|
if (!version) {
|
|
506
522
|
log.warn(
|