@norskvideo/ctl-test-harness 0.1.44 → 0.1.46
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-logs.d.ts +10 -0
- package/container-logs.js +64 -0
- package/package.json +1 -1
- package/smoke.js +15 -1
package/container-logs.d.ts
CHANGED
|
@@ -32,6 +32,16 @@ export interface LogHit {
|
|
|
32
32
|
container: string;
|
|
33
33
|
line: string;
|
|
34
34
|
}
|
|
35
|
+
/** Copy every engine `logs` tree under an instance-runtime root to `dest`,
|
|
36
|
+
* preserving the relative structure. Why this and not `docker logs`:
|
|
37
|
+
* norsk-ctl bind-mounts each media container's `/var/log/norsk` to
|
|
38
|
+
* `<runnerDir>/logs/media` on the host (override-generator), so the FULL
|
|
39
|
+
* structured engine log (`debug.json`, every level — not the notice+ subset
|
|
40
|
+
* `docker logs` sees, and immune to a decode-error stdout flood drowning the
|
|
41
|
+
* tail) lives on disk there until teardown nukes the store. Call this BEFORE
|
|
42
|
+
* cleanup to keep those logs for a CI artifact. Best-effort: never throws;
|
|
43
|
+
* returns the number of `logs` trees copied. */
|
|
44
|
+
export declare function preserveEngineLogs(runtimeRoot: string, dest: string): number;
|
|
35
45
|
/** A string forbids a substring; a RegExp is tested per line. */
|
|
36
46
|
export type LogPattern = string | RegExp;
|
|
37
47
|
/** Every line, in any container of the instance, that matches a forbidden
|
package/container-logs.js
CHANGED
|
@@ -8,6 +8,8 @@
|
|
|
8
8
|
// Pure argv/parse/format helpers; only the two exported probes shell out,
|
|
9
9
|
// through an injectable spawn.
|
|
10
10
|
import { spawnSync } from "node:child_process";
|
|
11
|
+
import { cpSync, existsSync, readdirSync, statSync } from "node:fs";
|
|
12
|
+
import { join, relative } from "node:path";
|
|
11
13
|
const defaultSpawn = (cmd, args, opts) => {
|
|
12
14
|
const r = spawnSync(cmd, args, opts);
|
|
13
15
|
return {
|
|
@@ -92,6 +94,68 @@ export function collectInstanceContainerLogs(instanceId, tail = DEFAULT_TAIL, sp
|
|
|
92
94
|
export function dumpInstanceContainerLogs(instanceId, tail = DEFAULT_TAIL, spawn = defaultSpawn) {
|
|
93
95
|
return formatContainerLogs(instanceId, collectInstanceContainerLogs(instanceId, tail, spawn));
|
|
94
96
|
}
|
|
97
|
+
/** Copy every engine `logs` tree under an instance-runtime root to `dest`,
|
|
98
|
+
* preserving the relative structure. Why this and not `docker logs`:
|
|
99
|
+
* norsk-ctl bind-mounts each media container's `/var/log/norsk` to
|
|
100
|
+
* `<runnerDir>/logs/media` on the host (override-generator), so the FULL
|
|
101
|
+
* structured engine log (`debug.json`, every level — not the notice+ subset
|
|
102
|
+
* `docker logs` sees, and immune to a decode-error stdout flood drowning the
|
|
103
|
+
* tail) lives on disk there until teardown nukes the store. Call this BEFORE
|
|
104
|
+
* cleanup to keep those logs for a CI artifact. Best-effort: never throws;
|
|
105
|
+
* returns the number of `logs` trees copied. */
|
|
106
|
+
export function preserveEngineLogs(runtimeRoot, dest) {
|
|
107
|
+
if (!existsSync(runtimeRoot))
|
|
108
|
+
return 0;
|
|
109
|
+
let copied = 0;
|
|
110
|
+
const walk = (dir) => {
|
|
111
|
+
let names;
|
|
112
|
+
try {
|
|
113
|
+
names = readdirSync(dir);
|
|
114
|
+
}
|
|
115
|
+
catch {
|
|
116
|
+
return;
|
|
117
|
+
}
|
|
118
|
+
for (const name of names) {
|
|
119
|
+
const p = join(dir, name);
|
|
120
|
+
try {
|
|
121
|
+
if (!statSync(p).isDirectory())
|
|
122
|
+
continue;
|
|
123
|
+
}
|
|
124
|
+
catch {
|
|
125
|
+
continue;
|
|
126
|
+
}
|
|
127
|
+
if (name === "logs") {
|
|
128
|
+
// Copy the tree, but CHILD BY CHILD: a root-owned sibling (e.g. studio
|
|
129
|
+
// logs when studio runs as root) must not abort the copy of the media
|
|
130
|
+
// logs we came for. Don't descend further past a logs/ dir.
|
|
131
|
+
let children;
|
|
132
|
+
try {
|
|
133
|
+
children = readdirSync(p);
|
|
134
|
+
}
|
|
135
|
+
catch {
|
|
136
|
+
continue;
|
|
137
|
+
}
|
|
138
|
+
let any = false;
|
|
139
|
+
for (const child of children) {
|
|
140
|
+
try {
|
|
141
|
+
cpSync(join(p, child), join(dest, relative(runtimeRoot, p), child), { recursive: true });
|
|
142
|
+
any = true;
|
|
143
|
+
}
|
|
144
|
+
catch {
|
|
145
|
+
/* best-effort per child */
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
if (any)
|
|
149
|
+
copied += 1;
|
|
150
|
+
}
|
|
151
|
+
else {
|
|
152
|
+
walk(p);
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
};
|
|
156
|
+
walk(runtimeRoot);
|
|
157
|
+
return copied;
|
|
158
|
+
}
|
|
95
159
|
const matches = (pattern, line) => typeof pattern === "string" ? line.includes(pattern) : pattern.test(line);
|
|
96
160
|
/** Every line, in any container of the instance, that matches a forbidden
|
|
97
161
|
* pattern. `clean` when there are none — including when docker cannot be
|
package/package.json
CHANGED
package/smoke.js
CHANGED
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
// rule); a product wanting typed commands bridges with toArgv in its own spec.
|
|
20
20
|
import { existsSync, readFileSync, writeFileSync } from "node:fs";
|
|
21
21
|
import { join } from "node:path";
|
|
22
|
-
import { dumpInstanceContainerLogs } from "./container-logs.js";
|
|
22
|
+
import { dumpInstanceContainerLogs, preserveEngineLogs } from "./container-logs.js";
|
|
23
23
|
import { ensureRunnerOnNetwork, netReachMode, STUDIO_INTERNAL_PORT, studioBaseFrom, } from "./container-net.js";
|
|
24
24
|
import { cleanupDaemon, requireLicenseFile, runCli, startDaemon, } from "./daemon.js";
|
|
25
25
|
import { hashSlot } from "./harness-config.js";
|
|
@@ -391,6 +391,20 @@ export async function runProductSmoke(slug, spec, deps = defaultSmokeDeps) {
|
|
|
391
391
|
catch (e) {
|
|
392
392
|
journeyError = e;
|
|
393
393
|
}
|
|
394
|
+
// Preserve the engine logs BEFORE anything tears the store down. norsk-ctl
|
|
395
|
+
// bind-mounts each media container's /var/log/norsk to <runnerDir>/logs/media,
|
|
396
|
+
// and runnerDir is <storeDir>/product-instances/<id> -- a SIBLING of the
|
|
397
|
+
// workdir (:/data, under <storeDir>/norsk-runtime), NOT under it. So walk the
|
|
398
|
+
// whole storeDir, not just norsk-runtime, or the logs/ tree is never found
|
|
399
|
+
// (this is why it preserved 0). `product remove`, `instance delete --purge`,
|
|
400
|
+
// cleanup and the root nuke below all destroy it, and we pretty much always
|
|
401
|
+
// want the Norsk logs from a smoke run; when a CI log dir is set, copy every
|
|
402
|
+
// logs/ tree out first so the workflow can upload it.
|
|
403
|
+
const ciLogDir = process.env.NORSK_CI_LOG_DIR;
|
|
404
|
+
if (ciLogDir) {
|
|
405
|
+
const n = preserveEngineLogs(storeDir, join(ciLogDir, slug));
|
|
406
|
+
deps.log(`${slug}: preserved ${n} engine log tree(s) -> ${join(ciLogDir, slug)}`);
|
|
407
|
+
}
|
|
394
408
|
if (handles.length)
|
|
395
409
|
await deps.stopSources(handles).catch(() => { });
|
|
396
410
|
// The daemon reaps its own control-plane containers on shutdown (daemon.ts,
|