@norskvideo/ctl-test-harness 0.1.38 → 0.1.39
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/container-net.d.ts +32 -2
- package/container-net.js +40 -5
- package/package.json +1 -1
- package/smoke.js +10 -2
package/container-net.d.ts
CHANGED
|
@@ -88,7 +88,33 @@ export declare function studioBaseFrom(studio: StudioReach, publishHost: string,
|
|
|
88
88
|
* In a container the id also appears as the hostname, but cgroup is present in
|
|
89
89
|
* both cgroup v1 (`/docker/<id>`) and v2 (`docker-<id>.scope`) layouts. */
|
|
90
90
|
export declare function parseOwnContainerId(cgroupText: string): string | null;
|
|
91
|
-
|
|
91
|
+
/** This process's own container id, or null on a bare host.
|
|
92
|
+
*
|
|
93
|
+
* The cgroup id is preferred and costs nothing, but it is not reliable: under
|
|
94
|
+
* cgroup NAMESPACES — docker's default for years — a container sees `0::/` and
|
|
95
|
+
* no id at all, so cgroup alone reports "not in a container" from inside one.
|
|
96
|
+
* That false negative is why `ensureRunnerOnNetwork` has been attaching nothing
|
|
97
|
+
* on every docker-outside-of-docker runner while its caller read the verdict as
|
|
98
|
+
* success.
|
|
99
|
+
*
|
|
100
|
+
* The fallback does not guess at the hostname's shape. `/.dockerenv` answers
|
|
101
|
+
* "is THIS process containerised", which is the question, and `docker inspect`
|
|
102
|
+
* arbitrates what the hostname names — docker's default hostname is the short
|
|
103
|
+
* id, and compose sets it to the service name, which inspect resolves as well.
|
|
104
|
+
* Both must agree, so a bare host that merely shares a name with a container
|
|
105
|
+
* still resolves to null. */
|
|
106
|
+
export declare function resolveContainerId(opts: {
|
|
107
|
+
cgroupText: string | null;
|
|
108
|
+
inContainer: boolean;
|
|
109
|
+
hostname: string;
|
|
110
|
+
isDockerObject: (id: string) => boolean;
|
|
111
|
+
}): string | null;
|
|
112
|
+
export declare function ownContainerId(opts?: {
|
|
113
|
+
read?: () => string;
|
|
114
|
+
inContainer?: boolean;
|
|
115
|
+
hostname?: string;
|
|
116
|
+
isDockerObject?: (id: string) => boolean;
|
|
117
|
+
}): string | null;
|
|
92
118
|
/** `docker network connect` is not idempotent — a second attach errors. Treat
|
|
93
119
|
* the "already attached" variants as success so setup can call this blindly. */
|
|
94
120
|
export declare function isAlreadyOnNetworkError(stderr: string): boolean;
|
|
@@ -103,8 +129,12 @@ type Runner = (cmd: string, args: string[]) => RunResult;
|
|
|
103
129
|
* `failed` (a real error the caller should surface). */
|
|
104
130
|
export declare function ensureRunnerOnNetwork(opts?: {
|
|
105
131
|
network?: string;
|
|
106
|
-
/** Explicit id (tests). Omitted ->
|
|
132
|
+
/** Explicit id (tests). Omitted -> resolved by `ownContainerId`. `null` forces the not-in-container branch. */
|
|
107
133
|
containerId?: string | null;
|
|
134
|
+
/** Leaf inputs for that resolution (tests), so the whole chain can be
|
|
135
|
+
* exercised without docker. Omitted -> the real cgroup, /.dockerenv,
|
|
136
|
+
* hostname and `docker inspect`. */
|
|
137
|
+
detect?: Parameters<typeof ownContainerId>[0];
|
|
108
138
|
run?: Runner;
|
|
109
139
|
}): "attached" | "already" | "not-in-container" | "failed";
|
|
110
140
|
export {};
|
package/container-net.js
CHANGED
|
@@ -30,7 +30,8 @@
|
|
|
30
30
|
// switch: `direct` on the Linux DooD CI box, `publish` (the default) everywhere
|
|
31
31
|
// else. Nothing here changes behaviour until a caller opts a suite into `direct`.
|
|
32
32
|
import { spawnSync } from "node:child_process";
|
|
33
|
-
import { readFileSync } from "node:fs";
|
|
33
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
34
|
+
import { hostname } from "node:os";
|
|
34
35
|
// Mirrors of norsk-ctl's @norsk-ctl/shared constants — re-declared (not imported)
|
|
35
36
|
// because this package doesn't depend on the ctl backend, exactly as
|
|
36
37
|
// studio-state.ts re-declares MEDIA_HTTP_PORT. Source of truth:
|
|
@@ -136,13 +137,47 @@ export function parseOwnContainerId(cgroupText) {
|
|
|
136
137
|
const m = cgroupText.match(/[0-9a-f]{64}/);
|
|
137
138
|
return m ? m[0] : null;
|
|
138
139
|
}
|
|
139
|
-
|
|
140
|
+
/** This process's own container id, or null on a bare host.
|
|
141
|
+
*
|
|
142
|
+
* The cgroup id is preferred and costs nothing, but it is not reliable: under
|
|
143
|
+
* cgroup NAMESPACES — docker's default for years — a container sees `0::/` and
|
|
144
|
+
* no id at all, so cgroup alone reports "not in a container" from inside one.
|
|
145
|
+
* That false negative is why `ensureRunnerOnNetwork` has been attaching nothing
|
|
146
|
+
* on every docker-outside-of-docker runner while its caller read the verdict as
|
|
147
|
+
* success.
|
|
148
|
+
*
|
|
149
|
+
* The fallback does not guess at the hostname's shape. `/.dockerenv` answers
|
|
150
|
+
* "is THIS process containerised", which is the question, and `docker inspect`
|
|
151
|
+
* arbitrates what the hostname names — docker's default hostname is the short
|
|
152
|
+
* id, and compose sets it to the service name, which inspect resolves as well.
|
|
153
|
+
* Both must agree, so a bare host that merely shares a name with a container
|
|
154
|
+
* still resolves to null. */
|
|
155
|
+
export function resolveContainerId(opts) {
|
|
156
|
+
const fromCgroup = opts.cgroupText === null ? null : parseOwnContainerId(opts.cgroupText);
|
|
157
|
+
if (fromCgroup)
|
|
158
|
+
return fromCgroup;
|
|
159
|
+
if (!opts.inContainer || !opts.hostname)
|
|
160
|
+
return null;
|
|
161
|
+
return opts.isDockerObject(opts.hostname) ? opts.hostname : null;
|
|
162
|
+
}
|
|
163
|
+
function dockerKnows(id) {
|
|
164
|
+
return spawnSync("docker", ["inspect", "-f", "{{.Id}}", id], { encoding: "utf-8" }).status === 0;
|
|
165
|
+
}
|
|
166
|
+
export function ownContainerId(opts = {}) {
|
|
167
|
+
const read = opts.read ?? (() => readFileSync("/proc/self/cgroup", "utf-8"));
|
|
168
|
+
let cgroupText;
|
|
140
169
|
try {
|
|
141
|
-
|
|
170
|
+
cgroupText = read();
|
|
142
171
|
}
|
|
143
172
|
catch {
|
|
144
|
-
|
|
173
|
+
cgroupText = null;
|
|
145
174
|
}
|
|
175
|
+
return resolveContainerId({
|
|
176
|
+
cgroupText,
|
|
177
|
+
inContainer: opts.inContainer ?? existsSync("/.dockerenv"),
|
|
178
|
+
hostname: opts.hostname ?? hostname(),
|
|
179
|
+
isDockerObject: opts.isDockerObject ?? dockerKnows,
|
|
180
|
+
});
|
|
146
181
|
}
|
|
147
182
|
/** `docker network connect` is not idempotent — a second attach errors. Treat
|
|
148
183
|
* the "already attached" variants as success so setup can call this blindly. */
|
|
@@ -159,7 +194,7 @@ const defaultRunner = (cmd, args) => {
|
|
|
159
194
|
* `failed` (a real error the caller should surface). */
|
|
160
195
|
export function ensureRunnerOnNetwork(opts = {}) {
|
|
161
196
|
const network = opts.network ?? DOCKER_NETWORK_NAME;
|
|
162
|
-
const id = opts.containerId === undefined ? ownContainerId() : opts.containerId;
|
|
197
|
+
const id = opts.containerId === undefined ? ownContainerId(opts.detect) : opts.containerId;
|
|
163
198
|
if (!id)
|
|
164
199
|
return "not-in-container";
|
|
165
200
|
const run = opts.run ?? defaultRunner;
|
package/package.json
CHANGED
package/smoke.js
CHANGED
|
@@ -235,8 +235,16 @@ export async function runProductSmoke(slug, spec, deps = defaultSmokeDeps) {
|
|
|
235
235
|
const started = deps.startDaemon(storeDir, { port: ports.daemonPort, seedConfig: false });
|
|
236
236
|
daemon = started.daemon;
|
|
237
237
|
await started.ready;
|
|
238
|
-
if (mode === "direct"
|
|
239
|
-
|
|
238
|
+
if (mode === "direct") {
|
|
239
|
+
// Say which verdict it was. "not-in-container" used to be the SILENT
|
|
240
|
+
// outcome on every docker-outside-of-docker runner, because the cgroup-only
|
|
241
|
+
// detection could not see through a cgroup namespace; direct reach then
|
|
242
|
+
// worked or not by luck of the runner already being on norsk-net, and
|
|
243
|
+
// nothing in the log distinguished the two.
|
|
244
|
+
const verdict = deps.ensureNetwork();
|
|
245
|
+
deps.log(`runner network for direct reach: ${verdict}`);
|
|
246
|
+
if (verdict === "failed")
|
|
247
|
+
throw new Error("could not join the runner to norsk-net for direct reach");
|
|
240
248
|
}
|
|
241
249
|
// Register.
|
|
242
250
|
const licenseFile = spec.product.licenseFile ?? deps.licenseFile();
|