@specific.dev/spectest 0.74.0 → 0.75.0
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/daemon.js +10 -2
- package/dist/harness/image-cache.d.ts +17 -0
- package/dist/harness/image-cache.js +19 -0
- package/package.json +1 -1
- package/src/daemon.ts +10 -1
- package/src/harness/image-cache.test.ts +17 -1
- package/src/harness/image-cache.ts +21 -0
package/dist/daemon.js
CHANGED
|
@@ -50,7 +50,7 @@ import { certCovers as hostmatchCertCovers, hostWithoutPort, matchRoute, selectC
|
|
|
50
50
|
import { INGRESS_HTTPS_PORT, INGRESS_HTTP_PORT, bindRoute, clearTables, emptyTables, planBind, registryTarget, routesFor, unbindRoute, } from "./harness/ingress-table.js";
|
|
51
51
|
import { startTlsTerminator } from "./harness/tls-terminator.js";
|
|
52
52
|
import { runContainerArgs } from "./harness/container-run.js";
|
|
53
|
-
import { BUILDKIT_CACHE_DIR, BUILDKIT_CACHE_UNION, IMAGE_CACHE_MANIFEST, imageCachePathsSync, isOnImageCache, mergeCacheIndex, } from "./harness/image-cache.js";
|
|
53
|
+
import { BUILDKIT_CACHE_DIR, BUILDKIT_CACHE_UNION, buildkitCacheTag, IMAGE_CACHE_MANIFEST, imageCachePathsSync, isOnImageCache, mergeCacheIndex, } from "./harness/image-cache.js";
|
|
54
54
|
import { assertAbsolute, certificateHostnames, defaultKeyMode, expandServiceToken, isNoopChown, mountFlag, needsIdTables, numericId, resolveChownIds, } from "./harness/file-mounts.js";
|
|
55
55
|
import { conflict, notFound, requireString, } from "./harness/methods.js";
|
|
56
56
|
import { openTerminal } from "./terminal.js";
|
|
@@ -1107,7 +1107,15 @@ async function runServiceBuild(name, image, tag) {
|
|
|
1107
1107
|
// disk is what outlives the VM; `mode=max` keeps every
|
|
1108
1108
|
// intermediate layer, uncompressed so an import never inflates,
|
|
1109
1109
|
// and one tag per service so exports do not replace each other.
|
|
1110
|
-
|
|
1110
|
+
// The tag carries the `spectest/service/` prefix because the
|
|
1111
|
+
// directory is one flat tag namespace the project's own BuildKit
|
|
1112
|
+
// writes into too (the docs tell it to mount this path): tags merge
|
|
1113
|
+
// newest-wins with no warning anywhere, and a project naturally tags
|
|
1114
|
+
// a build after the thing it builds, which is also what its service
|
|
1115
|
+
// is named. Specific's `nginx-mod` service tagged its own buildctl
|
|
1116
|
+
// cache `nginx-mod` and the two overwrote each other on alternate
|
|
1117
|
+
// boots (reported 2026-09-06). The prefix is reserved in the docs.
|
|
1118
|
+
const cacheTag = buildkitCacheTag(name);
|
|
1111
1119
|
cacheExport = [
|
|
1112
1120
|
"--cache-to",
|
|
1113
1121
|
`type=local,dest=${_localCacheDir},mode=max,compression=uncompressed,force-compression=true,tag=${cacheTag}`,
|
|
@@ -56,6 +56,23 @@ export declare const BUILDKIT_CACHE_DIR = "spectest-buildkit-cache";
|
|
|
56
56
|
* path for both of its cache flags.
|
|
57
57
|
*/
|
|
58
58
|
export declare const BUILDKIT_CACHE_UNION = "/var/lib/spectest-buildkit-cache";
|
|
59
|
+
/**
|
|
60
|
+
* The prefix under which the harness tags its own exports in that
|
|
61
|
+
* directory. It is one flat tag namespace shared with the project's own
|
|
62
|
+
* BuildKit (the `/services` docs page tells it to mount the union), tags
|
|
63
|
+
* merge newest-wins with no warning, and a project naturally tags a build
|
|
64
|
+
* after the thing it builds — which is what its spectest service is named
|
|
65
|
+
* too. So the harness's tags live under a prefix no project would choose,
|
|
66
|
+
* and the docs reserve it. Reported by Specific 2026-09-06: a service
|
|
67
|
+
* `nginx-mod` running `buildctl` tagged its cache `nginx-mod`, and it and
|
|
68
|
+
* the harness's image build for that very container overwrote each other
|
|
69
|
+
* on alternate boots.
|
|
70
|
+
*/
|
|
71
|
+
export declare const BUILDKIT_CACHE_TAG_PREFIX = "spectest/service/";
|
|
72
|
+
/** The BuildKit cache tag for a service's Dockerfile build: the prefix plus
|
|
73
|
+
* the service name, lowercased with anything outside `[a-z0-9-]` folded
|
|
74
|
+
* to `-`. */
|
|
75
|
+
export declare function buildkitCacheTag(service: string): string;
|
|
59
76
|
/** The cache's paths, or `null` when this VM carries none.
|
|
60
77
|
* `SPECTEST_IMAGE_CACHE_MANIFEST` points a test at another file. */
|
|
61
78
|
export declare function imageCachePathsSync(manifest?: string): ImageCachePaths | null;
|
|
@@ -50,6 +50,25 @@ export const BUILDKIT_CACHE_DIR = "spectest-buildkit-cache";
|
|
|
50
50
|
* path for both of its cache flags.
|
|
51
51
|
*/
|
|
52
52
|
export const BUILDKIT_CACHE_UNION = "/var/lib/spectest-buildkit-cache";
|
|
53
|
+
/**
|
|
54
|
+
* The prefix under which the harness tags its own exports in that
|
|
55
|
+
* directory. It is one flat tag namespace shared with the project's own
|
|
56
|
+
* BuildKit (the `/services` docs page tells it to mount the union), tags
|
|
57
|
+
* merge newest-wins with no warning, and a project naturally tags a build
|
|
58
|
+
* after the thing it builds — which is what its spectest service is named
|
|
59
|
+
* too. So the harness's tags live under a prefix no project would choose,
|
|
60
|
+
* and the docs reserve it. Reported by Specific 2026-09-06: a service
|
|
61
|
+
* `nginx-mod` running `buildctl` tagged its cache `nginx-mod`, and it and
|
|
62
|
+
* the harness's image build for that very container overwrote each other
|
|
63
|
+
* on alternate boots.
|
|
64
|
+
*/
|
|
65
|
+
export const BUILDKIT_CACHE_TAG_PREFIX = "spectest/service/";
|
|
66
|
+
/** The BuildKit cache tag for a service's Dockerfile build: the prefix plus
|
|
67
|
+
* the service name, lowercased with anything outside `[a-z0-9-]` folded
|
|
68
|
+
* to `-`. */
|
|
69
|
+
export function buildkitCacheTag(service) {
|
|
70
|
+
return `${BUILDKIT_CACHE_TAG_PREFIX}${service.replace(/[^a-z0-9-]/gi, "-").toLowerCase()}`;
|
|
71
|
+
}
|
|
53
72
|
function parse(raw) {
|
|
54
73
|
const parsed = JSON.parse(raw);
|
|
55
74
|
const root = (parsed.disks ?? []).find((d) => d.role === "root" && d.path)?.path;
|
package/package.json
CHANGED
package/src/daemon.ts
CHANGED
|
@@ -138,6 +138,7 @@ import { runContainerArgs } from "./harness/container-run.js";
|
|
|
138
138
|
import {
|
|
139
139
|
BUILDKIT_CACHE_DIR,
|
|
140
140
|
BUILDKIT_CACHE_UNION,
|
|
141
|
+
buildkitCacheTag,
|
|
141
142
|
IMAGE_CACHE_MANIFEST,
|
|
142
143
|
imageCachePathsSync,
|
|
143
144
|
isOnImageCache,
|
|
@@ -1456,7 +1457,15 @@ async function runServiceBuild(
|
|
|
1456
1457
|
// disk is what outlives the VM; `mode=max` keeps every
|
|
1457
1458
|
// intermediate layer, uncompressed so an import never inflates,
|
|
1458
1459
|
// and one tag per service so exports do not replace each other.
|
|
1459
|
-
|
|
1460
|
+
// The tag carries the `spectest/service/` prefix because the
|
|
1461
|
+
// directory is one flat tag namespace the project's own BuildKit
|
|
1462
|
+
// writes into too (the docs tell it to mount this path): tags merge
|
|
1463
|
+
// newest-wins with no warning anywhere, and a project naturally tags
|
|
1464
|
+
// a build after the thing it builds, which is also what its service
|
|
1465
|
+
// is named. Specific's `nginx-mod` service tagged its own buildctl
|
|
1466
|
+
// cache `nginx-mod` and the two overwrote each other on alternate
|
|
1467
|
+
// boots (reported 2026-09-06). The prefix is reserved in the docs.
|
|
1468
|
+
const cacheTag = buildkitCacheTag(name);
|
|
1460
1469
|
cacheExport = [
|
|
1461
1470
|
"--cache-to",
|
|
1462
1471
|
`type=local,dest=${_localCacheDir},mode=max,compression=uncompressed,force-compression=true,tag=${cacheTag}`,
|
|
@@ -28,7 +28,7 @@ describe("isOnImageCache", () => {
|
|
|
28
28
|
});
|
|
29
29
|
});
|
|
30
30
|
|
|
31
|
-
import { mergeCacheIndex, type OciIndex } from "./image-cache";
|
|
31
|
+
import { BUILDKIT_CACHE_TAG_PREFIX, buildkitCacheTag, mergeCacheIndex, type OciIndex } from "./image-cache";
|
|
32
32
|
|
|
33
33
|
const entry = (tag: string, hex: string) => ({ mediaType: "application/vnd.oci.image.manifest.v1+json", digest: `sha256:${hex}`, size: 1, annotations: { "org.opencontainers.image.ref.name": tag } });
|
|
34
34
|
|
|
@@ -62,4 +62,20 @@ describe("mergeCacheIndex", () => {
|
|
|
62
62
|
const out = mergeCacheIndex(lower, upper, () => true);
|
|
63
63
|
expect(out.manifests.map((m) => m.annotations?.["org.opencontainers.image.ref.name"])).toEqual(["api", "cli", "worker"]);
|
|
64
64
|
});
|
|
65
|
+
|
|
66
|
+
/** The directory is one tag namespace shared with the project's own
|
|
67
|
+
* BuildKit. The harness's tags are prefixed so a project tag named after
|
|
68
|
+
* the same service sits beside it instead of replacing it. */
|
|
69
|
+
test("a project's tag and the harness's tag for the same service both survive", () => {
|
|
70
|
+
const both: OciIndex = { schemaVersion: 2, manifests: [entry("nginx-mod", "p1"), entry(buildkitCacheTag("nginx-mod"), "h1")] };
|
|
71
|
+
const out = mergeCacheIndex(both, null, () => false);
|
|
72
|
+
expect(out.manifests.map((m) => m.digest)).toEqual(["sha256:p1", "sha256:h1"]);
|
|
73
|
+
});
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
describe("buildkitCacheTag", () => {
|
|
77
|
+
test("the reserved prefix plus the normalised service name", () => {
|
|
78
|
+
expect(buildkitCacheTag("nginx-mod")).toBe(`${BUILDKIT_CACHE_TAG_PREFIX}nginx-mod`);
|
|
79
|
+
expect(buildkitCacheTag("Api_v2")).toBe("spectest/service/api-v2");
|
|
80
|
+
});
|
|
65
81
|
});
|
|
@@ -66,6 +66,27 @@ export const BUILDKIT_CACHE_DIR = "spectest-buildkit-cache";
|
|
|
66
66
|
*/
|
|
67
67
|
export const BUILDKIT_CACHE_UNION = "/var/lib/spectest-buildkit-cache";
|
|
68
68
|
|
|
69
|
+
/**
|
|
70
|
+
* The prefix under which the harness tags its own exports in that
|
|
71
|
+
* directory. It is one flat tag namespace shared with the project's own
|
|
72
|
+
* BuildKit (the `/services` docs page tells it to mount the union), tags
|
|
73
|
+
* merge newest-wins with no warning, and a project naturally tags a build
|
|
74
|
+
* after the thing it builds — which is what its spectest service is named
|
|
75
|
+
* too. So the harness's tags live under a prefix no project would choose,
|
|
76
|
+
* and the docs reserve it. Reported by Specific 2026-09-06: a service
|
|
77
|
+
* `nginx-mod` running `buildctl` tagged its cache `nginx-mod`, and it and
|
|
78
|
+
* the harness's image build for that very container overwrote each other
|
|
79
|
+
* on alternate boots.
|
|
80
|
+
*/
|
|
81
|
+
export const BUILDKIT_CACHE_TAG_PREFIX = "spectest/service/";
|
|
82
|
+
|
|
83
|
+
/** The BuildKit cache tag for a service's Dockerfile build: the prefix plus
|
|
84
|
+
* the service name, lowercased with anything outside `[a-z0-9-]` folded
|
|
85
|
+
* to `-`. */
|
|
86
|
+
export function buildkitCacheTag(service: string): string {
|
|
87
|
+
return `${BUILDKIT_CACHE_TAG_PREFIX}${service.replace(/[^a-z0-9-]/gi, "-").toLowerCase()}`;
|
|
88
|
+
}
|
|
89
|
+
|
|
69
90
|
function parse(raw: string): ImageCachePaths | null {
|
|
70
91
|
const parsed = JSON.parse(raw) as { disks?: { role?: string; path?: string }[] };
|
|
71
92
|
const root = (parsed.disks ?? []).find((d) => d.role === "root" && d.path)?.path;
|