@norskvideo/ctl-dev-kit 0.1.7 → 0.1.9
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.
|
@@ -18,9 +18,16 @@
|
|
|
18
18
|
#
|
|
19
19
|
# Everything else is auto-detected off PRODUCT_DIR, so a product opts in just by
|
|
20
20
|
# having the directory/script — no per-product flag to keep in sync:
|
|
21
|
+
# frontend/dist the frontend workspace's build output
|
|
22
|
+
# dashboards/ the dashboard workspace's build output
|
|
23
|
+
# components/lib the components workspace's build output
|
|
21
24
|
# native/ native addon(s) (probe's signer) -> beside bundle
|
|
22
25
|
# assets/ product-template assets (funke's PNGs)
|
|
23
26
|
# scripts/runtime-images.ts -> emits the norsk-ctl.runtime-images label
|
|
27
|
+
#
|
|
28
|
+
# Only backend/dist is required. A product with no frontend/dashboard/components
|
|
29
|
+
# workspace (reuters) stages those empty and builds the same as one that has all
|
|
30
|
+
# three (funke/probe/commentary).
|
|
24
31
|
set -euo pipefail
|
|
25
32
|
|
|
26
33
|
: "${PRODUCT_DIR:?build-bundle-image.sh: PRODUCT_DIR must be set by the wrapper}"
|
|
@@ -43,11 +50,26 @@ stage="$(mktemp -d)"
|
|
|
43
50
|
trap 'rm -rf "${stage}"' EXIT
|
|
44
51
|
|
|
45
52
|
echo "==> staging artifacts"
|
|
46
|
-
mkdir -p "${stage}/backend" "${stage}/frontend" "${stage}/components
|
|
53
|
+
mkdir -p "${stage}/backend" "${stage}/frontend/dist" "${stage}/components/lib" \
|
|
54
|
+
"${stage}/dashboards" "${stage}/native" "${stage}/assets"
|
|
55
|
+
# The backend bundle is the one non-negotiable payload — every bundle product has
|
|
56
|
+
# one, so a missing dist is a broken build, not an absent workspace.
|
|
47
57
|
cp -R "${PRODUCT_DIR}/backend/dist" "${stage}/backend/dist"
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
58
|
+
|
|
59
|
+
# Frontend / dashboards / components are produced by the workspaces of the same
|
|
60
|
+
# name, so a product that has no such workspace never produces them (reuters:
|
|
61
|
+
# backend-only). Staged empty in that case, exactly as native/ and assets/ below
|
|
62
|
+
# — Dockerfile.bundle COPYs all six unconditionally, so the path must exist even
|
|
63
|
+
# when there is nothing to put in it.
|
|
64
|
+
if [ -d "${PRODUCT_DIR}/frontend/dist" ]; then
|
|
65
|
+
cp -R "${PRODUCT_DIR}/frontend/dist/." "${stage}/frontend/dist/"
|
|
66
|
+
fi
|
|
67
|
+
if [ -d "${PRODUCT_DIR}/dashboards" ]; then
|
|
68
|
+
cp -R "${PRODUCT_DIR}/dashboards/." "${stage}/dashboards/"
|
|
69
|
+
fi
|
|
70
|
+
if [ -d "${PRODUCT_DIR}/components/lib" ]; then
|
|
71
|
+
cp -R "${PRODUCT_DIR}/components/lib/." "${stage}/components/lib/"
|
|
72
|
+
fi
|
|
51
73
|
|
|
52
74
|
# Native addon(s): staged beside the bundle. Strip the build host's RUNPATH (nix
|
|
53
75
|
# store / repo-local paths) so the shipped .node carries no build-host paths; its
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
# Product integration testing on the DooD runners
|
|
2
|
+
|
|
3
|
+
Guidelines for writing (and debugging) a split product's slow-tier integration
|
|
4
|
+
suite against the org's self-hosted CI. These are hard-won: the failures below
|
|
5
|
+
_look_ like license, networking, or engine bugs but are almost always
|
|
6
|
+
**runner/harness setup** — mounts, ports, hosts, image provenance. Chasing them
|
|
7
|
+
as product bugs wastes time.
|
|
8
|
+
|
|
9
|
+
The reference implementations are the `norsk-ctl-product-playout` and
|
|
10
|
+
`norsk-ctl-product-commentary` harnesses; the shared helpers live in
|
|
11
|
+
`@norskvideo/ctl-test-harness`.
|
|
12
|
+
|
|
13
|
+
## Runner topology (docker-outside-of-docker)
|
|
14
|
+
|
|
15
|
+
The CI runner is itself a **container**. The daemon (`norsk-ctl`) and the product
|
|
16
|
+
backend are spawned **in-process inside that container**. Studio + the Norsk
|
|
17
|
+
media engine launch as **host siblings** (the runner shares the host docker
|
|
18
|
+
socket), with their ports **published on the host**.
|
|
19
|
+
|
|
20
|
+
Consequence — two address classes the harness must not confuse:
|
|
21
|
+
|
|
22
|
+
| Reaching… | Address |
|
|
23
|
+
| ------------------------------------ | ----------------------------------------- |
|
|
24
|
+
| the daemon, the product backend | `localhost:<port>` (in-process) |
|
|
25
|
+
| studio / media host-published ports | `${NORSK_TEST_HOST}:<port>` |
|
|
26
|
+
| a launched container by compose name | `<instance>-<service>-1` **on norsk-net** |
|
|
27
|
+
|
|
28
|
+
`NORSK_TEST_HOST` is `host.docker.internal` in CI and unset (→ `localhost`)
|
|
29
|
+
locally. Set it in the workflow env. The shared `studio-state` fetchers
|
|
30
|
+
(`fetchComponents`, `fetchComponentState`, …) already honour it; **any local
|
|
31
|
+
support code that hits a studio/media port must route through a `TEST_HOST`
|
|
32
|
+
helper** (`process.env.NORSK_TEST_HOST ?? "localhost"`), never a hardcoded
|
|
33
|
+
`localhost`. Daemon/backend fetches stay on `localhost`.
|
|
34
|
+
|
|
35
|
+
## Host-shared working directory (or studio EACCESes on boot)
|
|
36
|
+
|
|
37
|
+
Studio bind-mounts its instance working directory as `${workdir}:/data` and runs
|
|
38
|
+
as a **non-root** auto-detected user. If the workdir is the daemon default
|
|
39
|
+
(`~/norsk-runtime`, container-only), the sibling mount materialises **root-owned**
|
|
40
|
+
and studio dies at boot with `EACCES: mkdir '/data/moq-certs'` — the workflow
|
|
41
|
+
never starts, the SRT graph never builds, and every downstream test times out
|
|
42
|
+
looking like a networking/license fault.
|
|
43
|
+
|
|
44
|
+
Fix: point the daemon at a **host-shared** workdir under the test store dir.
|
|
45
|
+
Pre-write `config.yaml` with `defaultWorkingDirectory` under `makeStoreDir(...)`
|
|
46
|
+
(which roots in the gitignored, host-shared `test-temp/`):
|
|
47
|
+
|
|
48
|
+
```ts
|
|
49
|
+
writeFileSync(
|
|
50
|
+
join(storeDir, "config.yaml"),
|
|
51
|
+
`networkMode: docker\ndefaultWorkingDirectory: ${JSON.stringify(join(storeDir, "norsk-runtime"))}\n`,
|
|
52
|
+
);
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
Docker still leaves **root-owned** read-only bind-mount target dirs inside that
|
|
56
|
+
workdir, which the non-root runner can't `rm`. So `teardown()` must nuke the
|
|
57
|
+
store dir from a throwaway **root** container, or `afterAll` (and the runner's
|
|
58
|
+
own checkout cleanup) blocks:
|
|
59
|
+
|
|
60
|
+
```ts
|
|
61
|
+
spawnSync("docker", ["run", "--rm", "--user", "0:0", "-v", `${dirname(storeDir)}:/base`,
|
|
62
|
+
"--entrypoint", "sh", STUDIO_IMAGE, "-c", `rm -rf /base/${basename(storeDir)}`]);
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
## Reaching served media / HLS output
|
|
66
|
+
|
|
67
|
+
Studio reports playback URLs (HLS multivariant, WHIP endpoints) with the host it
|
|
68
|
+
was **configured with** — loopback locally, which the in-container test can't
|
|
69
|
+
reach. Two cases:
|
|
70
|
+
|
|
71
|
+
- **Proxied harness** — the URL is on the daemon's `publicHost` (default
|
|
72
|
+
`:443`). Route the fetch through `applyTestHost(url)` (swaps a loopback host
|
|
73
|
+
for `NORSK_TEST_HOST`). `assertMultivariantHasRenditions` does this by default.
|
|
74
|
+
- **Proxy-less harness** — the advertised `/instance/<id>/media/...` route is
|
|
75
|
+
served **only by the daemon's nginx proxy**; with no proxy, nothing answers on
|
|
76
|
+
`:443`. Reach the media container **directly on norsk-net** instead:
|
|
77
|
+
`mediaDirectUrl(url)` rewrites it to `http://<id>-media-1:8080/<native-path>`
|
|
78
|
+
(the same bypass `whip-driver` uses; the media route is unauthenticated). Pass
|
|
79
|
+
it via `assertMultivariantHasRenditions`'s `resolveFetchUrl` hook, and **join
|
|
80
|
+
the runner to norsk-net** in setup so the compose service name resolves:
|
|
81
|
+
|
|
82
|
+
```ts
|
|
83
|
+
spawnSync("docker", ["network", "create", "norsk-net"]); // idempotent
|
|
84
|
+
spawnSync("docker", ["network", "connect", "norsk-net", hostname()]);
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
## Capture servers (studio pushes → the runner)
|
|
88
|
+
|
|
89
|
+
When a test stands up an HLS/SCTE-35 capture server that **studio pushes to**,
|
|
90
|
+
studio (a host sibling on norsk-net) can't reach the runner via the host LAN IP.
|
|
91
|
+
Join norsk-net and hand studio the runner's **norsk-net IP** (`docker inspect`
|
|
92
|
+
self), falling back to the host LAN IP only when `NORSK_TEST_HOST` is unset. See
|
|
93
|
+
`manifest-capture.ts`'s `captureHost()`.
|
|
94
|
+
|
|
95
|
+
## Build the product image in the integration job
|
|
96
|
+
|
|
97
|
+
A `kind: dev` product still launches an instance whose compose references the
|
|
98
|
+
product's **image** (e.g. the runtime-screen `funke-backend` service on
|
|
99
|
+
`<product>:dev`). The `build-image` job builds it on a _different_ runner, so the
|
|
100
|
+
integration job must build it too — otherwise `docker compose up` tries to
|
|
101
|
+
**pull** the local-only tag and dies with `pull access denied` before any test
|
|
102
|
+
runs. Add `bun run build:image` inside the `nix develop .#build` block (docker is
|
|
103
|
+
reachable there); it also builds the dist the dev backend serves `/configure`
|
|
104
|
+
from.
|
|
105
|
+
|
|
106
|
+
## Stale node_modules on `clean: false` runners
|
|
107
|
+
|
|
108
|
+
The runners check out `clean: false` (to preserve `test-temp/`), so
|
|
109
|
+
`node_modules` **persists between runs**, and `bun install --frozen-lockfile`
|
|
110
|
+
does **not** reliably relink a workspace-hoisted package whose version moved. A
|
|
111
|
+
stale `@norskvideo/ctl-test-harness@0.1.2` once survived a bump to `0.1.3`, so a
|
|
112
|
+
newly-added export was `SyntaxError: export 'X' not found` and the suite crashed
|
|
113
|
+
in <1s. The bun **global cache is integrity-checked** (it can't serve a wrong
|
|
114
|
+
version); only the persisted `node_modules` dir goes stale. Guard the install:
|
|
115
|
+
|
|
116
|
+
```yaml
|
|
117
|
+
rm -rf node_modules/@norskvideo/ctl-test-harness
|
|
118
|
+
bun install --frozen-lockfile
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
When a product pins the shared harness in **both** `package.json` **and**
|
|
122
|
+
`tests/package.json`, bump **both** — a lone root bump can leave the tests
|
|
123
|
+
workspace resolving the old version.
|
|
124
|
+
|
|
125
|
+
## GPU
|
|
126
|
+
|
|
127
|
+
Instances launch with `--hardware nvidia` and encode on **nvenc** on the runners
|
|
128
|
+
(the nvenc log-grep + no-software-fallback assertions pass). Note the
|
|
129
|
+
`/usr/bin/nvidia-smi` **binary** may be absent in the media container even when
|
|
130
|
+
nvenc works — assert on the encoder logs, not on `nvidia-smi` exit status.
|
|
131
|
+
|
|
132
|
+
## License
|
|
133
|
+
|
|
134
|
+
The org secret `NORSK_LICENSE_V2` + a modern engine accept the license (a green
|
|
135
|
+
`probe` run proves it). V2 licenses need a **post-2026-07-17** engine, and SDK
|
|
136
|
+
clients that create media nodes (e.g. a WHIP driver) must answer the license
|
|
137
|
+
**handshake** via `createLicenseChallengeResponder` or the engine aborts node
|
|
138
|
+
creation with `10 ABORTED: License handshake required`. The `license is marked
|
|
139
|
+
NON-PRODUCTION` warning is expected in CI, not an error. The media engine's
|
|
140
|
+
`hugepages / rte_eal_init failed` line is **benign noise** — do not chase it.
|