@crewhaus/docker-images 0.1.8 → 0.2.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/index.d.ts +55 -0
- package/dist/index.js +67 -0
- package/package.json +2 -2
package/dist/index.d.ts
CHANGED
|
@@ -15,6 +15,13 @@
|
|
|
15
15
|
* version` succeeding); injectable `runner` for tests.
|
|
16
16
|
* - `loadDigests()` / `recordDigest()` over a `docker/digests.json`
|
|
17
17
|
* lockfile so reproducible builds work offline.
|
|
18
|
+
* - `resolveImageDigest()` + `buildImageAndRecord()` — item 47 closes the
|
|
19
|
+
* digests.json loop: PUSHED builds record their registry manifest digest
|
|
20
|
+
* (the only digest `docker pull repo@sha256:…` accepts), and the release
|
|
21
|
+
* workflow records the pushed ghcr.io digests for each v* tag. A local
|
|
22
|
+
* `--load` build records NOTHING — `docker image inspect`'s `.Id` is the
|
|
23
|
+
* image CONFIG digest, which no registry can serve, so writing it into
|
|
24
|
+
* the lockfile would hand users an unpullable pin.
|
|
18
25
|
*/
|
|
19
26
|
import { CrewhausError } from "@crewhaus/errors";
|
|
20
27
|
export declare class DockerImagesError extends CrewhausError {
|
|
@@ -70,6 +77,54 @@ export type DigestsFile = {
|
|
|
70
77
|
};
|
|
71
78
|
export declare function loadDigests(path?: string): DigestsFile;
|
|
72
79
|
export declare function recordDigest(target: TargetShape, tag: string, sha256: string, path?: string): void;
|
|
80
|
+
export type ResolveImageDigestOptions = {
|
|
81
|
+
readonly target: TargetShape;
|
|
82
|
+
readonly tag: string;
|
|
83
|
+
/** Test injection point — defaults to the same spawn runner as buildImage. */
|
|
84
|
+
readonly runner?: BuildRunner;
|
|
85
|
+
};
|
|
86
|
+
/**
|
|
87
|
+
* Resolve a PUSHED image's registry manifest digest via `docker buildx
|
|
88
|
+
* imagetools inspect`. Only the manifest digest is pullable
|
|
89
|
+
* (`docker pull repo@sha256:…`); the local image store's `.Id` is the image
|
|
90
|
+
* CONFIG digest, which registries do not serve — so there is deliberately no
|
|
91
|
+
* local-inspect fallback here (a `--load` build has no recordable digest).
|
|
92
|
+
*/
|
|
93
|
+
export declare function resolveImageDigest(opts: ResolveImageDigestOptions): Promise<string>;
|
|
94
|
+
export type BuildImageAndRecordOptions = BuildImageOptions & {
|
|
95
|
+
/** Record the built image's digest into docker/digests.json. Default true. */
|
|
96
|
+
readonly record?: boolean;
|
|
97
|
+
/** Override the digests.json location (tests / CI checkouts). */
|
|
98
|
+
readonly digestsFile?: string;
|
|
99
|
+
/** Test injection point for the recording seam. Defaults to {@link recordDigest}. */
|
|
100
|
+
readonly recordDigestFn?: typeof recordDigest;
|
|
101
|
+
};
|
|
102
|
+
export type BuildImageAndRecordResult = BuildImageResult & {
|
|
103
|
+
/** Registry manifest digest of the pushed image; undefined when recording was skipped or failed. */
|
|
104
|
+
readonly digest?: string;
|
|
105
|
+
readonly recorded: boolean;
|
|
106
|
+
/** Why the digest was not recorded (set only when recording was requested but failed). */
|
|
107
|
+
readonly recordError?: string;
|
|
108
|
+
/**
|
|
109
|
+
* Set when recording was requested but is not applicable: a `--load` build
|
|
110
|
+
* only produces a local image whose ID is a CONFIG digest, not a pullable
|
|
111
|
+
* registry manifest digest, so nothing truthful can land in the lockfile.
|
|
112
|
+
*/
|
|
113
|
+
readonly recordSkippedReason?: string;
|
|
114
|
+
};
|
|
115
|
+
/**
|
|
116
|
+
* `buildImage()` + close the digests.json loop: after a successful PUSHED
|
|
117
|
+
* build, resolve the image's registry manifest digest (`docker buildx
|
|
118
|
+
* imagetools inspect`) and record it in the lockfile — the maintenance the
|
|
119
|
+
* digests.json header always promised. `crewhaus build-image --push` calls
|
|
120
|
+
* this (recording is default-on there; `--no-record` opts out). A local
|
|
121
|
+
* `--load` build records NOTHING (see `recordSkippedReason`): its image ID is
|
|
122
|
+
* a config digest that `docker pull repo@sha256:…` cannot resolve, and the
|
|
123
|
+
* lockfile must only ever contain pullable pins. A digest-resolution/record
|
|
124
|
+
* failure does NOT fail the build — the image exists; the caller decides how
|
|
125
|
+
* loudly to surface the stale lockfile (the CLI prints a warning).
|
|
126
|
+
*/
|
|
127
|
+
export declare function buildImageAndRecord(opts: BuildImageAndRecordOptions): Promise<BuildImageAndRecordResult>;
|
|
73
128
|
export type DockerfileFingerprint = {
|
|
74
129
|
readonly stages: ReadonlyArray<{
|
|
75
130
|
readonly image: string;
|
package/dist/index.js
CHANGED
|
@@ -18,6 +18,13 @@ import { fileURLToPath } from "node:url";
|
|
|
18
18
|
* version` succeeding); injectable `runner` for tests.
|
|
19
19
|
* - `loadDigests()` / `recordDigest()` over a `docker/digests.json`
|
|
20
20
|
* lockfile so reproducible builds work offline.
|
|
21
|
+
* - `resolveImageDigest()` + `buildImageAndRecord()` — item 47 closes the
|
|
22
|
+
* digests.json loop: PUSHED builds record their registry manifest digest
|
|
23
|
+
* (the only digest `docker pull repo@sha256:…` accepts), and the release
|
|
24
|
+
* workflow records the pushed ghcr.io digests for each v* tag. A local
|
|
25
|
+
* `--load` build records NOTHING — `docker image inspect`'s `.Id` is the
|
|
26
|
+
* image CONFIG digest, which no registry can serve, so writing it into
|
|
27
|
+
* the lockfile would hand users an unpullable pin.
|
|
21
28
|
*/
|
|
22
29
|
import { CrewhausError } from "@crewhaus/errors";
|
|
23
30
|
export class DockerImagesError extends CrewhausError {
|
|
@@ -148,6 +155,66 @@ export function recordDigest(target, tag, sha256, path = DIGESTS_PATH) {
|
|
|
148
155
|
mkdirSync(dirname(path), { recursive: true });
|
|
149
156
|
writeFileSync(path, `${JSON.stringify(current, null, 2)}\n`, { mode: 0o644 });
|
|
150
157
|
}
|
|
158
|
+
const SHA256_DIGEST_RE = /sha256:[0-9a-f]{64}/;
|
|
159
|
+
/**
|
|
160
|
+
* Resolve a PUSHED image's registry manifest digest via `docker buildx
|
|
161
|
+
* imagetools inspect`. Only the manifest digest is pullable
|
|
162
|
+
* (`docker pull repo@sha256:…`); the local image store's `.Id` is the image
|
|
163
|
+
* CONFIG digest, which registries do not serve — so there is deliberately no
|
|
164
|
+
* local-inspect fallback here (a `--load` build has no recordable digest).
|
|
165
|
+
*/
|
|
166
|
+
export async function resolveImageDigest(opts) {
|
|
167
|
+
const ref = `crewhaus/${opts.target}:${opts.tag}`;
|
|
168
|
+
const argv = ["docker", "buildx", "imagetools", "inspect", ref];
|
|
169
|
+
const runner = opts.runner ?? defaultRunner;
|
|
170
|
+
const { exitCode, stdout, stderr } = await runner(argv, process.cwd());
|
|
171
|
+
if (exitCode !== 0) {
|
|
172
|
+
throw new DockerImagesError(`could not inspect ${ref} for its digest (exit ${exitCode}): ${stderr.slice(0, 512)}`);
|
|
173
|
+
}
|
|
174
|
+
const match = SHA256_DIGEST_RE.exec(stdout);
|
|
175
|
+
if (match === null) {
|
|
176
|
+
throw new DockerImagesError(`no sha256 digest in docker inspect output for ${ref}`);
|
|
177
|
+
}
|
|
178
|
+
return match[0];
|
|
179
|
+
}
|
|
180
|
+
/**
|
|
181
|
+
* `buildImage()` + close the digests.json loop: after a successful PUSHED
|
|
182
|
+
* build, resolve the image's registry manifest digest (`docker buildx
|
|
183
|
+
* imagetools inspect`) and record it in the lockfile — the maintenance the
|
|
184
|
+
* digests.json header always promised. `crewhaus build-image --push` calls
|
|
185
|
+
* this (recording is default-on there; `--no-record` opts out). A local
|
|
186
|
+
* `--load` build records NOTHING (see `recordSkippedReason`): its image ID is
|
|
187
|
+
* a config digest that `docker pull repo@sha256:…` cannot resolve, and the
|
|
188
|
+
* lockfile must only ever contain pullable pins. A digest-resolution/record
|
|
189
|
+
* failure does NOT fail the build — the image exists; the caller decides how
|
|
190
|
+
* loudly to surface the stale lockfile (the CLI prints a warning).
|
|
191
|
+
*/
|
|
192
|
+
export async function buildImageAndRecord(opts) {
|
|
193
|
+
const result = await buildImage(opts);
|
|
194
|
+
if (opts.record === false) {
|
|
195
|
+
return { ...result, recorded: false };
|
|
196
|
+
}
|
|
197
|
+
if (opts.push !== true) {
|
|
198
|
+
return {
|
|
199
|
+
...result,
|
|
200
|
+
recorded: false,
|
|
201
|
+
recordSkippedReason: "local image ID is not a registry digest — use --push to record a pullable digest",
|
|
202
|
+
};
|
|
203
|
+
}
|
|
204
|
+
const record = opts.recordDigestFn ?? recordDigest;
|
|
205
|
+
try {
|
|
206
|
+
const digest = await resolveImageDigest({
|
|
207
|
+
target: opts.target,
|
|
208
|
+
tag: opts.tag,
|
|
209
|
+
runner: opts.runner,
|
|
210
|
+
});
|
|
211
|
+
record(opts.target, opts.tag, digest, opts.digestsFile);
|
|
212
|
+
return { ...result, digest, recorded: true };
|
|
213
|
+
}
|
|
214
|
+
catch (err) {
|
|
215
|
+
return { ...result, recorded: false, recordError: err.message };
|
|
216
|
+
}
|
|
217
|
+
}
|
|
151
218
|
export function fingerprintDockerfile(text) {
|
|
152
219
|
const lines = text.split(/\r?\n/);
|
|
153
220
|
const fromStages = lines
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@crewhaus/docker-images",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Per-target-shape Docker images (multi-stage, non-root, healthchecked) + crewhaus build-image CLI wrapper (Section 32)",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
"test": "bun test src"
|
|
16
16
|
},
|
|
17
17
|
"dependencies": {
|
|
18
|
-
"@crewhaus/errors": "0.
|
|
18
|
+
"@crewhaus/errors": "0.2.0"
|
|
19
19
|
},
|
|
20
20
|
"license": "Apache-2.0",
|
|
21
21
|
"author": {
|