Skip to content

Where people and agents ship software together. The open-source git platform for the whole job: issues, agents, checks and deploys to the edge.

338 linesCodeBlameRaw
1// The runner's image made in the registry (registry.mjs): its layer,
2// config and manifest, and the calls it makes, against a registry kept in
3// memory.
4import assert from "node:assert/strict";
5import { readFileSync } from "node:fs";
6import { join } from "node:path";
7import { test } from "node:test";
8import { gunzipSync } from "node:zlib";
9
10import {
11 DOCKER_MANIFEST,
12 OCI_MANIFEST,
13 RUNNER_BINARY_MODE,
14 RUNNER_BINARY_PATH,
15 RUNNER_ENTRYPOINT,
16 appendRunnerImage,
17 bearerChallenge,
18 parseRef,
19 runnerConfig,
20 runnerLayer,
21 runnerManifest,
22 sha256,
23 tarOf,
24} from "./registry.mjs";
25import { ROOT } from "./stack.mjs";
26
27/** A tar's entries: name, mode, size, owner, type and bytes, checksums checked. */
28function untar(tar) {
29 const entries = [];
30 for (let at = 0; at + 512 <= tar.length; ) {
31 const header = tar.subarray(at, at + 512);
32 if (header.every((b) => b === 0)) break;
33 const field = (start, length) =>
34 header
35 .subarray(start, start + length)
36 .toString("ascii")
37 .replace(/\0.*$/s, "")
38 .trim();
39 const summed = [...header].reduce((sum, byte, i) => sum + (i >= 148 && i < 156 ? 32 : byte), 0);
40 assert.equal(Number.parseInt(field(148, 8), 8), summed, "the header's checksum");
41 assert.equal(field(257, 6), "ustar");
42 const size = Number.parseInt(field(124, 12), 8);
43 entries.push({
44 name: field(0, 100),
45 mode: Number.parseInt(field(100, 8), 8),
46 uid: Number.parseInt(field(108, 8), 8),
47 gid: Number.parseInt(field(116, 8), 8),
48 mtime: Number.parseInt(field(136, 12), 8),
49 type: field(156, 1),
50 data: tar.subarray(at + 512, at + 512 + size),
51 });
52 at += 512 + Math.ceil(size / 512) * 512;
53 }
54 return entries;
55}
56
57test("the runner's layer is the binary alone, at its path and mode, owned by root", () => {
58 const binary = Buffer.from("\x7fELF a runner, of a length that is not a whole block");
59 const layer = runnerLayer(binary, { mtime: 1_760_000_000 });
60 const tar = gunzipSync(layer.bytes);
61 assert.equal(tar.length % 512, 0);
62 assert.equal(layer.digest, sha256(layer.bytes));
63 assert.equal(layer.size, layer.bytes.length);
64 assert.equal(layer.diffId, sha256(tar));
65 const entries = untar(tar);
66 assert.equal(entries.length, 1);
67 const [entry] = entries;
68 assert.deepEqual(
69 { name: entry.name, mode: entry.mode, uid: entry.uid, gid: entry.gid, mtime: entry.mtime, type: entry.type },
70 { name: "usr/local/bin/g1t-runner", mode: 0o755, uid: 0, gid: 0, mtime: 1_760_000_000, type: "0" },
71 );
72 assert.ok(entry.data.equals(binary));
73 // The same bytes make the same layer.
74 assert.equal(runnerLayer(binary, { mtime: 1_760_000_000 }).digest, layer.digest);
75 // An exact number of blocks, and an empty file.
76 assert.equal(untar(tarOf("a", Buffer.alloc(512, 1)))[0].data.length, 512);
77 assert.equal(untar(tarOf("a", Buffer.alloc(0)))[0].data.length, 0);
78});
79
80test("the layer and entrypoint are what services/runner/Dockerfile builds", () => {
81 const dockerfile = readFileSync(join(ROOT, "services/runner/Dockerfile"), "utf8");
82 const copy = /^COPY --chmod=(\d+) g1t-runner (\S+)$/m.exec(dockerfile);
83 assert.ok(copy, "the Dockerfile copies the binary in");
84 assert.equal(Number.parseInt(copy[1], 8), RUNNER_BINARY_MODE);
85 assert.equal(copy[2], `/${RUNNER_BINARY_PATH}`);
86 const entrypoint = /^ENTRYPOINT (.+)$/m.exec(dockerfile);
87 assert.deepEqual(JSON.parse(entrypoint[1]), RUNNER_ENTRYPOINT);
88 // Nothing else that would change the image.
89 const instructions = dockerfile
90 .split(/\r?\n/)
91 .filter((line) => line.trim() && !line.startsWith("#"))
92 .map((line) => line.split(" ")[0]);
93 assert.deepEqual(instructions, ["ARG", "FROM", "COPY", "ENTRYPOINT"]);
94});
95
96const BASE_CONFIG = {
97 architecture: "amd64",
98 os: "linux",
99 created: "2026-10-08T19:50:00Z",
100 config: { Env: ["PATH=/usr/local/bin:/usr/bin"], Cmd: ["bash"], WorkingDir: "/home/runner", User: "root" },
101 rootfs: { type: "layers", diff_ids: ["sha256:" + "1".repeat(64), "sha256:" + "2".repeat(64)] },
102 history: [{ created_by: "FROM debian" }, { created_by: "RUN apt-get install" }],
103};
104
105test("the config is the base's, with the layer, the entrypoint and no inherited command", () => {
106 const before = structuredClone(BASE_CONFIG);
107 const diffId = "sha256:" + "3".repeat(64);
108 const config = runnerConfig(BASE_CONFIG, { diffId, created: "2026-10-13T00:00:00.000Z" });
109 assert.deepEqual(BASE_CONFIG, before, "the base's config is left as it was");
110 assert.deepEqual(config.config.Entrypoint, ["g1t-runner"]);
111 assert.equal(config.config.Cmd, undefined);
112 assert.deepEqual(config.config.Env, BASE_CONFIG.config.Env);
113 assert.equal(config.config.WorkingDir, "/home/runner");
114 assert.deepEqual(config.rootfs.diff_ids, [...BASE_CONFIG.rootfs.diff_ids, diffId]);
115 assert.equal(config.created, "2026-10-13T00:00:00.000Z");
116 // History: one entry per layer, plus one that adds none.
117 assert.equal(config.history.filter((h) => !h.empty_layer).length - BASE_CONFIG.history.length, 1);
118 assert.equal(config.history.at(-1).empty_layer, true);
119 assert.throws(() => runnerConfig({ ...BASE_CONFIG, architecture: "arm64" }, { diffId, created: "x" }), /linux\/amd64/);
120});
121
122test("the manifest names the base's layers, then the binary's, in the base's format", () => {
123 const layers = [
124 { mediaType: "application/vnd.docker.image.rootfs.diff.tar.gzip", digest: "sha256:" + "a".repeat(64), size: 10 },
125 { mediaType: "application/vnd.docker.image.rootfs.diff.tar.gzip", digest: "sha256:" + "b".repeat(64), size: 20 },
126 ];
127 const config = { digest: "sha256:" + "c".repeat(64), size: 5 };
128 const layer = { digest: "sha256:" + "d".repeat(64), size: 7 };
129 const docker = runnerManifest(
130 { schemaVersion: 2, mediaType: DOCKER_MANIFEST, config: { mediaType: "application/vnd.docker.container.image.v1+json", digest: "x", size: 1 }, layers },
131 { config, layer },
132 );
133 assert.deepEqual(docker, {
134 schemaVersion: 2,
135 mediaType: DOCKER_MANIFEST,
136 config: { mediaType: "application/vnd.docker.container.image.v1+json", ...config },
137 layers: [...layers, { mediaType: "application/vnd.docker.image.rootfs.diff.tar.gzip", ...layer }],
138 });
139 // OCI, with its type left unsaid.
140 const oci = runnerManifest(
141 { schemaVersion: 2, config: { mediaType: "application/vnd.oci.image.config.v1+json", digest: "x", size: 1 }, layers: [], annotations: { a: "b" } },
142 { config, layer },
143 );
144 assert.equal(oci.mediaType, OCI_MANIFEST);
145 assert.deepEqual(oci.layers, [{ mediaType: "application/vnd.oci.image.layer.v1.tar+gzip", ...layer }]);
146 assert.equal(oci.annotations, undefined);
147});
148
149test("image references and bearer challenges are read", () => {
150 const digest = "sha256:" + "e".repeat(64);
151 assert.deepEqual(parseRef("registry.cloudflare.com/acct/g1t-runner:abc"), {
152 host: "registry.cloudflare.com",
153 name: "acct/g1t-runner",
154 tag: "abc",
155 digest: null,
156 });
157 assert.deepEqual(parseRef(`localhost:5000/g1t-runner:base-1@${digest}`), { host: "localhost:5000", name: "g1t-runner", tag: "base-1", digest });
158 assert.deepEqual(parseRef(`r.example/x@${digest}`), { host: "r.example", name: "x", tag: null, digest });
159 assert.throws(() => parseRef("r.example/x"));
160 assert.deepEqual(bearerChallenge('Bearer realm="https://auth.example/token",service="registry",scope="repository:x:pull"'), {
161 realm: "https://auth.example/token",
162 service: "registry",
163 scope: "repository:x:pull",
164 });
165 assert.equal(bearerChallenge('Basic realm="x"'), null);
166 assert.equal(bearerChallenge(null), null);
167});
168
169/** A registry in memory: one repository, Basic auth, uploads by POST, PATCH and PUT. */
170function fakeRegistry({ name, auth = "Basic " + Buffer.from("u:p").toString("base64") }) {
171 const blobs = new Map();
172 const manifests = new Map();
173 const uploads = new Map();
174 const calls = [];
175 let next = 0;
176 const reply = (status, { headers = {}, body = null } = {}) => ({
177 status,
178 ok: status >= 200 && status < 300,
179 headers: new Headers(headers),
180 arrayBuffer: async () => (body ? body.buffer.slice(body.byteOffset, body.byteOffset + body.byteLength) : new ArrayBuffer(0)),
181 text: async () => (body ? body.toString("utf8") : ""),
182 json: async () => JSON.parse(body.toString("utf8")),
183 });
184 const fetchImpl = async (url, init) => {
185 const { pathname, searchParams } = new URL(url);
186 calls.push(`${init.method} ${pathname}`);
187 if (init.headers.authorization !== auth) return reply(401);
188 const prefix = `/v2/${name}/`;
189 if (!pathname.startsWith(prefix)) return reply(404);
190 const path = pathname.slice(prefix.length);
191 let match;
192 if ((match = /^manifests\/(.+)$/.exec(path))) {
193 if (init.method === "PUT") {
194 const bytes = Buffer.from(init.body);
195 const digest = sha256(bytes);
196 for (const ref of [...JSON.parse(bytes).layers, JSON.parse(bytes).config]) {
197 if (!blobs.has(ref.digest)) return reply(400, { body: Buffer.from('{"errors":[{"code":"MANIFEST_BLOB_UNKNOWN"}]}') });
198 }
199 manifests.set(match[1], { bytes, type: init.headers["content-type"] });
200 manifests.set(digest, { bytes, type: init.headers["content-type"] });
201 return reply(201, { headers: { "docker-content-digest": digest } });
202 }
203 const found = manifests.get(match[1]);
204 return found ? reply(200, { headers: { "content-type": found.type }, body: found.bytes }) : reply(404);
205 }
206 if ((match = /^blobs\/(sha256:[0-9a-f]{64})$/.exec(path))) {
207 const found = blobs.get(match[1]);
208 if (!found) return reply(404);
209 return init.method === "HEAD" ? reply(200) : reply(200, { body: found });
210 }
211 if (path === "blobs/uploads/" && init.method === "POST") {
212 const id = `u${next++}`;
213 uploads.set(id, Buffer.alloc(0));
214 return reply(202, { headers: { location: `${prefix}blobs/uploads/${id}` } });
215 }
216 if ((match = /^blobs\/uploads\/(u\d+)$/.exec(path))) {
217 const id = match[1];
218 if (init.method === "PATCH") {
219 uploads.set(id, Buffer.concat([uploads.get(id), Buffer.from(init.body)]));
220 return reply(202, { headers: { location: `${prefix}blobs/uploads/${id}?state=1` } });
221 }
222 if (init.method === "PUT") {
223 const bytes = uploads.get(id);
224 if (sha256(bytes) !== searchParams.get("digest")) return reply(400);
225 blobs.set(searchParams.get("digest"), bytes);
226 return reply(201);
227 }
228 }
229 return reply(405);
230 };
231 return { blobs, manifests, calls, fetchImpl };
232}
233
234test("the runner's image is made in the registry from the base's manifest and one new layer, pulling none of the base", async () => {
235 const registry = fakeRegistry({ name: "acct/g1t-runner" });
236 // The base, as `docker push` left it.
237 const layerBlobs = [Buffer.from("base layer one"), Buffer.from("base layer two")];
238 for (const blob of layerBlobs) registry.blobs.set(sha256(blob), blob);
239 const configBytes = Buffer.from(JSON.stringify(BASE_CONFIG));
240 registry.blobs.set(sha256(configBytes), configBytes);
241 const baseManifest = Buffer.from(
242 JSON.stringify({
243 schemaVersion: 2,
244 mediaType: OCI_MANIFEST,
245 config: { mediaType: "application/vnd.oci.image.config.v1+json", digest: sha256(configBytes), size: configBytes.length },
246 layers: layerBlobs.map((blob) => ({ mediaType: "application/vnd.oci.image.layer.v1.tar+gzip", digest: sha256(blob), size: blob.length })),
247 }),
248 );
249 registry.manifests.set(sha256(baseManifest), { bytes: baseManifest, type: OCI_MANIFEST });
250 const base = `registry.cloudflare.com/acct/g1t-runner:base-20261008-a314499af03b@${sha256(baseManifest)}`;
251 const ref = "registry.cloudflare.com/acct/g1t-runner:0123456789abcdef";
252 const binary = Buffer.from("the runner binary");
253 const credentials = async () => ({ username: "u", password: "p" });
254
255 const digest = await appendRunnerImage({ ref, base, binary, credentials, fetchImpl: registry.fetchImpl, created: "2026-10-13T00:00:00.000Z" });
256 const made = registry.manifests.get("0123456789abcdef");
257 assert.equal(sha256(made.bytes), digest);
258 assert.equal(made.type, OCI_MANIFEST);
259 const manifest = JSON.parse(made.bytes);
260 assert.deepEqual(
261 manifest.layers.slice(0, 2).map((l) => l.digest),
262 layerBlobs.map(sha256),
263 );
264 assert.equal(manifest.layers.length, 3);
265 const config = JSON.parse(registry.blobs.get(manifest.config.digest));
266 assert.deepEqual(config.config.Entrypoint, ["g1t-runner"]);
267 assert.equal(config.rootfs.diff_ids.at(-1), sha256(gunzipSync(registry.blobs.get(manifest.layers[2].digest))));
268 assert.ok(untar(gunzipSync(registry.blobs.get(manifest.layers[2].digest)))[0].data.equals(binary));
269 // The base's layers were asked about, never downloaded.
270 for (const blob of layerBlobs) assert.ok(!registry.calls.includes(`GET /v2/acct/g1t-runner/blobs/${sha256(blob)}`));
271
272 // Made again: the layer is already there, so only the config and manifest are written.
273 const before = registry.calls.length;
274 await appendRunnerImage({ ref, base, binary, credentials, fetchImpl: registry.fetchImpl, created: "2026-10-13T00:00:00.000Z" });
275 assert.ok(!registry.calls.slice(before).some((call) => call.startsWith("PATCH")));
276});
277
278test("a base that is not where it should be, or not what base.json says, stops it", async () => {
279 const registry = fakeRegistry({ name: "acct/g1t-runner" });
280 const credentials = async () => ({ username: "u", password: "p" });
281 const digest = "sha256:" + "f".repeat(64);
282 const options = { binary: Buffer.from("x"), credentials, fetchImpl: registry.fetchImpl, attempts: 1 };
283 await assert.rejects(
284 appendRunnerImage({ ...options, ref: "registry.cloudflare.com/acct/g1t-runner:t", base: `registry.cloudflare.com/acct/other:b@${digest}` }),
285 /not in the runner's repository/,
286 );
287 await assert.rejects(
288 appendRunnerImage({ ...options, ref: "registry.cloudflare.com/acct/g1t-runner:t", base: `registry.cloudflare.com/acct/g1t-runner:b@${digest}` }),
289 /404/,
290 );
291 // Wrong credentials: the registry's answer is the error.
292 await assert.rejects(
293 appendRunnerImage({
294 ...options,
295 credentials: async () => ({ username: "u", password: "wrong" }),
296 ref: "registry.cloudflare.com/acct/g1t-runner:t",
297 base: `registry.cloudflare.com/acct/g1t-runner:b@${digest}`,
298 }),
299 /401/,
300 );
301});
302
303test("a registry that asks for a bearer token is given the one its realm trades the credentials for", async () => {
304 const registry = fakeRegistry({ name: "acct/r", auth: "Bearer tok" });
305 const inner = registry.fetchImpl;
306 const asked = [];
307 const fetchImpl = async (url, init) => {
308 if (url.startsWith("https://auth.example/")) {
309 asked.push([url, init.headers.authorization]);
310 return { ok: true, status: 200, json: async () => ({ token: "tok" }) };
311 }
312 const answer = await inner(url, init);
313 if (answer.status === 401) answer.headers.set("www-authenticate", 'Bearer realm="https://auth.example/token",service="r.example"');
314 return answer;
315 };
316 const config = Buffer.from(JSON.stringify(BASE_CONFIG));
317 registry.blobs.set(sha256(config), config);
318 const manifest = Buffer.from(
319 JSON.stringify({
320 schemaVersion: 2,
321 mediaType: DOCKER_MANIFEST,
322 config: { mediaType: "application/vnd.docker.container.image.v1+json", digest: sha256(config), size: config.length },
323 layers: [],
324 }),
325 );
326 registry.manifests.set("base", { bytes: manifest, type: DOCKER_MANIFEST });
327 await appendRunnerImage({
328 ref: "r.example/acct/r:new",
329 base: "r.example/acct/r:base",
330 binary: Buffer.from("x"),
331 credentials: async () => ({ username: "u", password: "p" }),
332 fetchImpl,
333 });
334 assert.ok(registry.manifests.has("new"));
335 assert.equal(asked.length, 1, "one token, kept");
336 assert.equal(asked[0][0], "https://auth.example/token?service=r.example&scope=repository%3Aacct%2Fr%3Apull%2Cpush");
337 assert.equal(asked[0][1], `Basic ${Buffer.from("u:p").toString("base64")}`);
338});