@norskvideo/ctl-sdk 0.1.49 → 0.1.51
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/package.json +1 -1
- package/product-error.d.ts +6 -2
- package/product-service.d.ts +15 -0
- package/product-service.js +24 -4
package/package.json
CHANGED
package/product-error.d.ts
CHANGED
|
@@ -1,8 +1,12 @@
|
|
|
1
1
|
export declare class ProductError extends Error {
|
|
2
2
|
code: "DEV_URL_INVALID" | "DEV_URL_NOT_LOCALHOST" | "PORT_EXHAUSTED" | "DOCKER_PULL_FAILED" | "DOCKER_RUN_FAILED" | "READINESS_TIMEOUT" | "MANIFEST_FETCH_FAILED" | "MANIFEST_INVALID" | "CONFIG_SCREEN_UNREACHABLE" | "CONFIG_SCREEN_DEV_SERVER" | "LICENSE_INVALID" | "NAME_CONFLICT" | "NOT_FOUND" | "NOT_RESTARTABLE" | "RUNNER_TOO_OLD" | "PRODUCT_TEMPLATE_FETCH_FAILED"
|
|
3
3
|
/** Two products declare one bare role name with different `defaultFor`. */
|
|
4
|
-
| "ROLE_CONFLICT"
|
|
4
|
+
| "ROLE_CONFLICT"
|
|
5
|
+
/** The host's custom role names could not be read to check for a clash. */
|
|
6
|
+
| "ROLE_NAMES_UNREADABLE";
|
|
5
7
|
constructor(code: "DEV_URL_INVALID" | "DEV_URL_NOT_LOCALHOST" | "PORT_EXHAUSTED" | "DOCKER_PULL_FAILED" | "DOCKER_RUN_FAILED" | "READINESS_TIMEOUT" | "MANIFEST_FETCH_FAILED" | "MANIFEST_INVALID" | "CONFIG_SCREEN_UNREACHABLE" | "CONFIG_SCREEN_DEV_SERVER" | "LICENSE_INVALID" | "NAME_CONFLICT" | "NOT_FOUND" | "NOT_RESTARTABLE" | "RUNNER_TOO_OLD" | "PRODUCT_TEMPLATE_FETCH_FAILED"
|
|
6
8
|
/** Two products declare one bare role name with different `defaultFor`. */
|
|
7
|
-
| "ROLE_CONFLICT"
|
|
9
|
+
| "ROLE_CONFLICT"
|
|
10
|
+
/** The host's custom role names could not be read to check for a clash. */
|
|
11
|
+
| "ROLE_NAMES_UNREADABLE", message: string);
|
|
8
12
|
}
|
package/product-service.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { type Result } from "@norskvideo/ctl-foundation";
|
|
1
2
|
import { type ProductPlacement } from "./docker-runner.js";
|
|
2
3
|
import { type LicenseStager } from "./license-registration.js";
|
|
3
4
|
import type { Manifest, ProductRoleDeclaration } from "./manifest-schema.js";
|
|
@@ -159,9 +160,20 @@ export interface ProductServiceOptions {
|
|
|
159
160
|
onRolesDeclared?: (productName: string, roles: readonly ProductRoleDeclaration[], acceptance: {
|
|
160
161
|
pending: readonly RoleDefault[];
|
|
161
162
|
}) => void;
|
|
163
|
+
/**
|
|
164
|
+
* Role names a product may not declare, because the host already uses them
|
|
165
|
+
* for something else: its custom roles (ADR-0014). Names are one name space
|
|
166
|
+
* on a host, so a product role spelt like a custom role would be ambiguous in
|
|
167
|
+
* every assignment naming it. Omitted means none. An error is a host that
|
|
168
|
+
* cannot say which names it uses, and refuses any product declaring roles.
|
|
169
|
+
*/
|
|
170
|
+
reservedRoleNames?: () => Result<ReadonlySet<string>, string>;
|
|
162
171
|
/** Told once a registration is removed, so the host can drop whatever it
|
|
163
172
|
* holds against the product (role assignments on `product/<name>`). */
|
|
164
173
|
onRemoved?: (productName: string) => void;
|
|
174
|
+
/** Told after every add, remove and reload that succeeds: the host keeps a
|
|
175
|
+
* view of its products elsewhere (norsk-ctl's discovery entries). */
|
|
176
|
+
onChanged?: () => void;
|
|
165
177
|
/**
|
|
166
178
|
* What the daemon asserts when it dials a REGISTERED product's own routes —
|
|
167
179
|
* the config-screen probe and the product-template fetches (RFC 0003 D7's
|
|
@@ -219,6 +231,8 @@ export declare class ProductService {
|
|
|
219
231
|
private reachHostFor;
|
|
220
232
|
private readonly onRolesDeclared?;
|
|
221
233
|
private readonly onRemoved?;
|
|
234
|
+
private readonly onChanged?;
|
|
235
|
+
private readonly reservedRoleNames?;
|
|
222
236
|
/**
|
|
223
237
|
* Product roles travel bare, so a name means one thing on a host. Two
|
|
224
238
|
* products may share one — a common vocabulary is the point — but not with
|
|
@@ -238,6 +252,7 @@ export declare class ProductService {
|
|
|
238
252
|
* tracked containerId means running. Dev-mode: externally owned, so probe
|
|
239
253
|
* the dev URL — registered no longer implies running. */
|
|
240
254
|
isRunning(reg: ProductRegistration): Promise<boolean>;
|
|
255
|
+
private changed;
|
|
241
256
|
add(spec: ProductSpec, opts?: AddProductOpts): Promise<AddProductResult>;
|
|
242
257
|
private addSerialised;
|
|
243
258
|
remove(name: string): Promise<void>;
|
package/product-service.js
CHANGED
|
@@ -74,7 +74,9 @@ export class ProductService {
|
|
|
74
74
|
this.knownCapabilities = opts.knownCapabilities ?? [];
|
|
75
75
|
this.identityEnvFor = opts.identityEnvFor;
|
|
76
76
|
this.onRolesDeclared = opts.onRolesDeclared;
|
|
77
|
+
this.reservedRoleNames = opts.reservedRoleNames;
|
|
77
78
|
this.onRemoved = opts.onRemoved;
|
|
79
|
+
this.onChanged = opts.onChanged;
|
|
78
80
|
this.forgetIdentity = opts.forgetIdentity;
|
|
79
81
|
this.probeHeadersFor = opts.probeHeadersFor;
|
|
80
82
|
}
|
|
@@ -98,6 +100,8 @@ export class ProductService {
|
|
|
98
100
|
}
|
|
99
101
|
onRolesDeclared;
|
|
100
102
|
onRemoved;
|
|
103
|
+
onChanged;
|
|
104
|
+
reservedRoleNames;
|
|
101
105
|
/**
|
|
102
106
|
* Product roles travel bare, so a name means one thing on a host. Two
|
|
103
107
|
* products may share one — a common vocabulary is the point — but not with
|
|
@@ -106,7 +110,19 @@ export class ProductService {
|
|
|
106
110
|
* know which two products to talk to.
|
|
107
111
|
*/
|
|
108
112
|
refuseRoleConflicts(manifest, others) {
|
|
109
|
-
|
|
113
|
+
const declared = manifest.access?.roles ?? [];
|
|
114
|
+
const read = declared.length === 0 ? undefined : this.reservedRoleNames?.();
|
|
115
|
+
// Fail closed: a clash nobody could see would make one name mean two roles.
|
|
116
|
+
if (read?.status === "error") {
|
|
117
|
+
return new ProductError("ROLE_NAMES_UNREADABLE", `'${manifest.name}' declares product roles, and this host's custom roles cannot be read to check they ` +
|
|
118
|
+
`do not clash: ${read.error}`);
|
|
119
|
+
}
|
|
120
|
+
const reserved = read?.status === "ok" ? read.value : new Set();
|
|
121
|
+
for (const role of declared) {
|
|
122
|
+
if (reserved.has(role.name)) {
|
|
123
|
+
return new ProductError("ROLE_CONFLICT", `product role '${role.name}' is already a custom role on this host; role names are one name space, ` +
|
|
124
|
+
"so rename the custom role (or remove it) before registering this product");
|
|
125
|
+
}
|
|
110
126
|
for (const other of others) {
|
|
111
127
|
if (other.name === manifest.name)
|
|
112
128
|
continue;
|
|
@@ -151,8 +167,12 @@ export class ProductService {
|
|
|
151
167
|
return reg.containerId !== undefined;
|
|
152
168
|
return this.isDevUrlAlive(productBaseUrl(reg));
|
|
153
169
|
}
|
|
170
|
+
changed(result) {
|
|
171
|
+
this.onChanged?.();
|
|
172
|
+
return result;
|
|
173
|
+
}
|
|
154
174
|
async add(spec, opts = {}) {
|
|
155
|
-
return this.mutations.run(() => this.addSerialised(spec, opts));
|
|
175
|
+
return this.changed(await this.mutations.run(() => this.addSerialised(spec, opts)));
|
|
156
176
|
}
|
|
157
177
|
async addSerialised(spec, opts) {
|
|
158
178
|
const existing = this.store.read();
|
|
@@ -359,7 +379,7 @@ export class ProductService {
|
|
|
359
379
|
return { registration, warnings };
|
|
360
380
|
}
|
|
361
381
|
async remove(name) {
|
|
362
|
-
return this.mutations.run(() => this.removeSerialised(name));
|
|
382
|
+
return this.changed(await this.mutations.run(() => this.removeSerialised(name)));
|
|
363
383
|
}
|
|
364
384
|
async removeSerialised(name) {
|
|
365
385
|
const existing = this.store.read();
|
|
@@ -386,7 +406,7 @@ export class ProductService {
|
|
|
386
406
|
* stood in for. Like restart, the new container id is persisted before the
|
|
387
407
|
* readiness wait so a timed-out reload leaves a tracked container. */
|
|
388
408
|
async reload(name) {
|
|
389
|
-
return this.mutations.run(() => this.reloadSerialised(name));
|
|
409
|
+
return this.changed(await this.mutations.run(() => this.reloadSerialised(name)));
|
|
390
410
|
}
|
|
391
411
|
async reloadSerialised(name) {
|
|
392
412
|
const target = this.store.read().find((p) => p.name === name);
|