@norskvideo/ctl-test-harness 0.1.59 → 0.1.61
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 +11 -0
- package/container-logs.js +35 -1
- package/demo/run.d.ts +4 -0
- package/demo/run.js +21 -0
- package/package.json +4 -4
- package/smoke.js +6 -1
package/container-logs.d.ts
CHANGED
|
@@ -28,6 +28,17 @@ export declare function formatContainerLogs(instanceId: string, entries: Contain
|
|
|
28
28
|
export declare function collectInstanceContainerLogs(instanceId: string, tail?: number, spawn?: SpawnLike): ContainerLog[];
|
|
29
29
|
/** The ready-to-print diagnostic block (empty-marker when nothing is found). */
|
|
30
30
|
export declare function dumpInstanceContainerLogs(instanceId: string, tail?: number, spawn?: SpawnLike): string;
|
|
31
|
+
/** Write every container of one instance to `<dest>/<container>.log`, for a CI
|
|
32
|
+
* artifact. The companion to {@link preserveEngineLogs}, which keeps the media
|
|
33
|
+
* container's bind-mounted engine tree: this keeps what ONLY Docker has, and
|
|
34
|
+
* the studio container's stdout is where "Refusing to run workflow" is printed
|
|
35
|
+
* — the line the gate failures point at and that teardown then destroys.
|
|
36
|
+
*
|
|
37
|
+
* Call BEFORE cleanup. Best-effort throughout and never throws: a diagnostic
|
|
38
|
+
* that fails the run it was meant to explain is worse than no diagnostic.
|
|
39
|
+
* Returns the number of logs written, and creates nothing when there are none.
|
|
40
|
+
*/
|
|
41
|
+
export declare function preserveInstanceContainerLogs(instanceId: string, dest: string, tail?: number, spawn?: SpawnLike): number;
|
|
31
42
|
export interface LogHit {
|
|
32
43
|
container: string;
|
|
33
44
|
line: string;
|
package/container-logs.js
CHANGED
|
@@ -8,7 +8,7 @@
|
|
|
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";
|
|
11
|
+
import { cpSync, existsSync, mkdirSync, readdirSync, statSync, writeFileSync } from "node:fs";
|
|
12
12
|
import { join, relative } from "node:path";
|
|
13
13
|
const defaultSpawn = (cmd, args, opts) => {
|
|
14
14
|
const r = spawnSync(cmd, args, opts);
|
|
@@ -94,6 +94,40 @@ export function collectInstanceContainerLogs(instanceId, tail = DEFAULT_TAIL, sp
|
|
|
94
94
|
export function dumpInstanceContainerLogs(instanceId, tail = DEFAULT_TAIL, spawn = defaultSpawn) {
|
|
95
95
|
return formatContainerLogs(instanceId, collectInstanceContainerLogs(instanceId, tail, spawn));
|
|
96
96
|
}
|
|
97
|
+
/** Write every container of one instance to `<dest>/<container>.log`, for a CI
|
|
98
|
+
* artifact. The companion to {@link preserveEngineLogs}, which keeps the media
|
|
99
|
+
* container's bind-mounted engine tree: this keeps what ONLY Docker has, and
|
|
100
|
+
* the studio container's stdout is where "Refusing to run workflow" is printed
|
|
101
|
+
* — the line the gate failures point at and that teardown then destroys.
|
|
102
|
+
*
|
|
103
|
+
* Call BEFORE cleanup. Best-effort throughout and never throws: a diagnostic
|
|
104
|
+
* that fails the run it was meant to explain is worse than no diagnostic.
|
|
105
|
+
* Returns the number of logs written, and creates nothing when there are none.
|
|
106
|
+
*/
|
|
107
|
+
export function preserveInstanceContainerLogs(instanceId, dest, tail = DEFAULT_TAIL, spawn = defaultSpawn) {
|
|
108
|
+
const entries = collectInstanceContainerLogs(instanceId, tail, spawn);
|
|
109
|
+
if (entries.length === 0)
|
|
110
|
+
return 0;
|
|
111
|
+
let written = 0;
|
|
112
|
+
try {
|
|
113
|
+
mkdirSync(dest, { recursive: true });
|
|
114
|
+
}
|
|
115
|
+
catch {
|
|
116
|
+
return 0;
|
|
117
|
+
}
|
|
118
|
+
for (const e of entries) {
|
|
119
|
+
try {
|
|
120
|
+
// The state goes in the file, not just the name: an `exited` container
|
|
121
|
+
// with an empty log says something a missing file does not.
|
|
122
|
+
writeFileSync(join(dest, `${e.name}.log`), `# ${e.name} (${e.state})\n${e.log}`);
|
|
123
|
+
written++;
|
|
124
|
+
}
|
|
125
|
+
catch {
|
|
126
|
+
// Keep going: one unwritable file must not cost the others.
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
return written;
|
|
130
|
+
}
|
|
97
131
|
/** Copy every engine `logs` tree under an instance-runtime root to `dest`,
|
|
98
132
|
* preserving the relative structure. Why this and not `docker logs`:
|
|
99
133
|
* norsk-ctl bind-mounts each media container's `/var/log/norsk` to
|
package/demo/run.d.ts
CHANGED
|
@@ -315,6 +315,10 @@ export declare class DemoSession {
|
|
|
315
315
|
/** Tear down in reverse: sources, instance, then — private only — the
|
|
316
316
|
* daemon (with its proxy), the dev backend, the store. Reuse leaves the
|
|
317
317
|
* developer's daemon, store and registration as they are. */
|
|
318
|
+
/** Keep each instance's container logs under NORSK_CI_LOG_DIR for the
|
|
319
|
+
* workflow's artifact upload. No env var (local dev) means no-op; every
|
|
320
|
+
* failure inside is swallowed by the helper. */
|
|
321
|
+
private preserveContainerLogs;
|
|
318
322
|
teardown(opts: {
|
|
319
323
|
instances: string[];
|
|
320
324
|
handles: SourceHandle[];
|
package/demo/run.js
CHANGED
|
@@ -29,6 +29,7 @@ import { existsSync, lstatSync, mkdirSync, readdirSync, readFileSync, readlinkSy
|
|
|
29
29
|
import { homedir } from "node:os";
|
|
30
30
|
import { dirname, isAbsolute, join, resolve } from "node:path";
|
|
31
31
|
import { parseManifestSeed, repoOf } from "@norskvideo/ctl-sdk/manifest-seed";
|
|
32
|
+
import { preserveInstanceContainerLogs } from "../container-logs.js";
|
|
32
33
|
import { DOCKER_NETWORK_NAME, ensureRunnerOnNetwork, netReachMode } from "../container-net.js";
|
|
33
34
|
import { cleanupDaemon, requireLicenseFile, runCli, startDaemon, } from "../daemon.js";
|
|
34
35
|
import { hashSlot } from "../harness-config.js";
|
|
@@ -653,7 +654,27 @@ export class DemoSession {
|
|
|
653
654
|
/** Tear down in reverse: sources, instance, then — private only — the
|
|
654
655
|
* daemon (with its proxy), the dev backend, the store. Reuse leaves the
|
|
655
656
|
* developer's daemon, store and registration as they are. */
|
|
657
|
+
/** Keep each instance's container logs under NORSK_CI_LOG_DIR for the
|
|
658
|
+
* workflow's artifact upload. No env var (local dev) means no-op; every
|
|
659
|
+
* failure inside is swallowed by the helper. */
|
|
660
|
+
preserveContainerLogs(instances) {
|
|
661
|
+
const ciLogDir = process.env.NORSK_CI_LOG_DIR;
|
|
662
|
+
if (!ciLogDir)
|
|
663
|
+
return;
|
|
664
|
+
for (const id of instances) {
|
|
665
|
+
const n = preserveInstanceContainerLogs(id, join(ciLogDir, "containers", id));
|
|
666
|
+
if (n > 0)
|
|
667
|
+
this.deps.log(`preserved ${n} container log(s) for ${id} -> ${join(ciLogDir, "containers", id)}`);
|
|
668
|
+
}
|
|
669
|
+
}
|
|
656
670
|
async teardown(opts) {
|
|
671
|
+
// FIRST, while the containers are still standing. Everything below removes
|
|
672
|
+
// them, and `docker logs` is the only copy of the studio container's stdout
|
|
673
|
+
// — the "Refusing to run workflow" line that the workflow-never-started
|
|
674
|
+
// gates tell you to go and read. Unconditional rather than on-failure,
|
|
675
|
+
// because teardown is not told the verdict, and a tail per container is
|
|
676
|
+
// cheap next to a re-run.
|
|
677
|
+
this.preserveContainerLogs(opts.instances);
|
|
657
678
|
if (opts.handles.length)
|
|
658
679
|
await this.deps.stopSources(opts.handles).catch(() => { });
|
|
659
680
|
if (this.policy === "reuse") {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@norskvideo/ctl-test-harness",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.61",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"exports": {
|
|
6
6
|
".": {
|
|
@@ -85,9 +85,9 @@
|
|
|
85
85
|
"devDependencies": {
|
|
86
86
|
"@types/bun": "latest",
|
|
87
87
|
"@types/node": "^22.10.0",
|
|
88
|
-
"@norskvideo/norsk-studio": "1.30.0-2026-09-
|
|
89
|
-
"@norskvideo/norsk-studio-built-ins": "1.30.0-2026-09-
|
|
90
|
-
"@norskvideo/norsk-sdk": "1.3.1-2026-09-
|
|
88
|
+
"@norskvideo/norsk-studio": "1.30.0-2026-09-28-b92e8139",
|
|
89
|
+
"@norskvideo/norsk-studio-built-ins": "1.30.0-2026-09-28-b92e8139",
|
|
90
|
+
"@norskvideo/norsk-sdk": "1.3.1-2026-09-30-acca6a9d"
|
|
91
91
|
},
|
|
92
92
|
"peerDependenciesMeta": {
|
|
93
93
|
"@norskvideo/norsk-studio": {
|
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, writeFileSync } from "node:fs";
|
|
21
21
|
import { join } from "node:path";
|
|
22
|
-
import { dumpInstanceContainerLogs, preserveEngineLogs } from "./container-logs.js";
|
|
22
|
+
import { dumpInstanceContainerLogs, preserveEngineLogs, preserveInstanceContainerLogs } 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";
|
|
@@ -434,6 +434,11 @@ export async function runProductSmoke(slug, spec, deps = defaultSmokeDeps) {
|
|
|
434
434
|
if (ciLogDir) {
|
|
435
435
|
const n = preserveEngineLogs(storeDir, join(ciLogDir, slug));
|
|
436
436
|
deps.log(`${slug}: preserved ${n} engine log tree(s) -> ${join(ciLogDir, slug)}`);
|
|
437
|
+
// And the half the bind-mounted tree does not hold: the STUDIO container's
|
|
438
|
+
// stdout, where "Refusing to run workflow" is printed. Still standing at
|
|
439
|
+
// this point; everything below removes it.
|
|
440
|
+
const c = preserveInstanceContainerLogs(instanceId, join(ciLogDir, slug, "containers", instanceId));
|
|
441
|
+
deps.log(`${slug}: preserved ${c} container log(s) -> ${join(ciLogDir, slug, "containers", instanceId)}`);
|
|
437
442
|
}
|
|
438
443
|
if (handles.length)
|
|
439
444
|
await deps.stopSources(handles).catch(() => { });
|