@generacy-ai/control-plane 0.2.0 → 0.3.0
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/dist/src/errors.d.ts +1 -1
- package/dist/src/errors.d.ts.map +1 -1
- package/dist/src/errors.js +1 -0
- package/dist/src/errors.js.map +1 -1
- package/dist/src/index.d.ts +3 -0
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +4 -0
- package/dist/src/index.js.map +1 -1
- package/dist/src/routes/app-config.d.ts +1 -1
- package/dist/src/routes/app-config.d.ts.map +1 -1
- package/dist/src/routes/app-config.js +5 -16
- package/dist/src/routes/app-config.js.map +1 -1
- package/dist/src/routes/lifecycle.d.ts.map +1 -1
- package/dist/src/routes/lifecycle.js +64 -1
- package/dist/src/routes/lifecycle.js.map +1 -1
- package/dist/src/schemas.d.ts +73 -4
- package/dist/src/schemas.d.ts.map +1 -1
- package/dist/src/schemas.js +23 -0
- package/dist/src/schemas.js.map +1 -1
- package/dist/src/services/docker-engine-client.d.ts +57 -0
- package/dist/src/services/docker-engine-client.d.ts.map +1 -0
- package/dist/src/services/docker-engine-client.js +326 -0
- package/dist/src/services/docker-engine-client.js.map +1 -0
- package/dist/src/services/docker-engine-types.d.ts +152 -0
- package/dist/src/services/docker-engine-types.d.ts.map +1 -0
- package/dist/src/services/docker-engine-types.js +37 -0
- package/dist/src/services/docker-engine-types.js.map +1 -0
- package/dist/src/services/worker-enumeration.d.ts +31 -0
- package/dist/src/services/worker-enumeration.d.ts.map +1 -0
- package/dist/src/services/worker-enumeration.js +71 -0
- package/dist/src/services/worker-enumeration.js.map +1 -0
- package/dist/src/services/worker-scaler.d.ts +128 -0
- package/dist/src/services/worker-scaler.d.ts.map +1 -0
- package/dist/src/services/worker-scaler.js +405 -0
- package/dist/src/services/worker-scaler.js.map +1 -0
- package/package.json +2 -1
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
import { DockerEngineClient } from './docker-engine-client.js';
|
|
2
|
+
import { type ContainerInspect, type ContainerCreateBody, DockerEngineError } from './docker-engine-types.js';
|
|
3
|
+
import { type WorkerReplica, computeProjectName, enumerateWorkers } from './worker-enumeration.js';
|
|
4
|
+
export { computeProjectName, enumerateWorkers, type WorkerReplica };
|
|
5
|
+
export interface ScaleOptions {
|
|
6
|
+
/** Target worker count. Must be >= 1 (validated upstream in lifecycle route). */
|
|
7
|
+
count: number;
|
|
8
|
+
/** Override orchestrator URL for metadata-refresh callback. */
|
|
9
|
+
orchestratorUrl?: string;
|
|
10
|
+
/** Override orchestrator internal API key. */
|
|
11
|
+
orchestratorApiKey?: string;
|
|
12
|
+
/** Override Docker socket. Default: env DOCKER_HOST or unix:///var/run/docker-host.sock. */
|
|
13
|
+
dockerHost?: string;
|
|
14
|
+
/** Override engine client (test seam — production wires the default). */
|
|
15
|
+
engineClient?: DockerEngineClient;
|
|
16
|
+
}
|
|
17
|
+
export interface ScaleResult {
|
|
18
|
+
/** Worker count observed before scaling (from Engine API enumeration, not .env). */
|
|
19
|
+
previousCount: number;
|
|
20
|
+
/** Target count from ScaleOptions.count. */
|
|
21
|
+
requestedCount: number;
|
|
22
|
+
/** Actual achieved count after the operation. Equals requestedCount on success. */
|
|
23
|
+
actualCount: number;
|
|
24
|
+
}
|
|
25
|
+
export declare class PartialScaleError extends Error {
|
|
26
|
+
readonly name = "PartialScaleError";
|
|
27
|
+
readonly requested: number;
|
|
28
|
+
readonly actual: number;
|
|
29
|
+
readonly previousCount: number;
|
|
30
|
+
readonly cause: Error;
|
|
31
|
+
constructor(requested: number, actual: number, previousCount: number, cause: Error);
|
|
32
|
+
}
|
|
33
|
+
export interface ScalePlan {
|
|
34
|
+
toCreate: number[];
|
|
35
|
+
toRemove: string[];
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Plan container-number assignment for a scale operation.
|
|
39
|
+
*
|
|
40
|
+
* Scale up: gap-fill ascending in [1..max(existing)], then append.
|
|
41
|
+
* Scale down: sort exited (highest-numbered first), then running (highest-numbered first),
|
|
42
|
+
* take the first (current - target) IDs.
|
|
43
|
+
* No-op: returns `{ toCreate: [], toRemove: [] }`.
|
|
44
|
+
*
|
|
45
|
+
* Pure function — unit-tested independently. FR-006, SC-003, SC-011.
|
|
46
|
+
*/
|
|
47
|
+
export declare function assignContainerNumbers(existing: WorkerReplica[], target: number): ScalePlan;
|
|
48
|
+
/**
|
|
49
|
+
* Clone a source replica's inspect response into a create body for a new replica.
|
|
50
|
+
*
|
|
51
|
+
* Strips orchestrator-set fields (Id, Created, State, Status, Hostname, populated
|
|
52
|
+
* NetworkSettings) and keeps Image, Cmd, Env, Entrypoint, WorkingDir, User, Labels,
|
|
53
|
+
* Healthcheck, StopSignal, StopTimeout, ExposedPorts, and all of HostConfig.
|
|
54
|
+
*
|
|
55
|
+
* Overwrites the `com.docker.compose.container-number` label with the new number —
|
|
56
|
+
* preserves all other labels (project, service, config-hash, etc.) per FR-007.
|
|
57
|
+
*
|
|
58
|
+
* Builds NetworkingConfig with **first network only** from source.NetworkSettings.Networks
|
|
59
|
+
* in insertion order. Caller is responsible for `connectNetwork` calls for remaining
|
|
60
|
+
* networks before `startContainer` (Q1=A multi-network sequencing).
|
|
61
|
+
*
|
|
62
|
+
* Throws `Error('SOURCE_REPLICA_HAS_NO_NETWORKS')` if source has zero networks.
|
|
63
|
+
*/
|
|
64
|
+
export declare function cloneInspectToCreate(inspect: ContainerInspect, newNumber: number, _newName: string): ContainerCreateBody;
|
|
65
|
+
interface ScaleUpResult {
|
|
66
|
+
created: number[];
|
|
67
|
+
failed?: {
|
|
68
|
+
number: number;
|
|
69
|
+
error: Error;
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
interface ScaleDownResult {
|
|
73
|
+
removed: string[];
|
|
74
|
+
failed?: {
|
|
75
|
+
id: string;
|
|
76
|
+
error: Error;
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* Create + connect-extra-networks + start, repeated per toCreate slot.
|
|
81
|
+
*
|
|
82
|
+
* Per-slot sequencing (Q1=A):
|
|
83
|
+
* 1. Pre-check gap-fill name collision (FR-015, Q5=A) — if any container
|
|
84
|
+
* already holds `<project>-worker-<n>`, force-remove it first. Edge
|
|
85
|
+
* case after manual `docker rm` of a running worker; normal operation
|
|
86
|
+
* doesn't trigger it because exited replicas are counted by FR-002.
|
|
87
|
+
* 2. POST /containers/create with the first network in NetworkingConfig.
|
|
88
|
+
* 3. POST /networks/<id>/connect per additional network from source.
|
|
89
|
+
* 4. POST /containers/<id>/start.
|
|
90
|
+
*
|
|
91
|
+
* On the first error, stop the loop and return `{ created, failed }`. Do NOT
|
|
92
|
+
* roll back already-created replicas (Q2=B: commit what succeeded).
|
|
93
|
+
*/
|
|
94
|
+
export declare function scaleUp(client: DockerEngineClient, project: string, source: ContainerInspect, toCreate: number[]): Promise<ScaleUpResult>;
|
|
95
|
+
/**
|
|
96
|
+
* Stop + remove per ID, in the caller-provided order (already sorted by
|
|
97
|
+
* `assignContainerNumbers`: exited first, then running, highest-numbered first).
|
|
98
|
+
* On the first error, stop and return `{ removed, failed }`. (FR-004)
|
|
99
|
+
*/
|
|
100
|
+
export declare function scaleDown(client: DockerEngineClient, toRemoveIds: string[]): Promise<ScaleDownResult>;
|
|
101
|
+
/**
|
|
102
|
+
* Scale worker replicas to the requested count via the Docker Engine API.
|
|
103
|
+
*
|
|
104
|
+
* Replaces the previous `docker compose --scale` shell-out: enumerates workers
|
|
105
|
+
* by `com.docker.compose.*` labels, clones an existing replica's config, and
|
|
106
|
+
* issues create/connect/start or stop/remove directly on the daemon. Removes
|
|
107
|
+
* the host compose-file dependency entirely.
|
|
108
|
+
*
|
|
109
|
+
* Concurrent invocations are serialized by an in-process async mutex (FR-014).
|
|
110
|
+
*
|
|
111
|
+
* Stale clone drift case (FR-013): if a user edits the host compose file and
|
|
112
|
+
* rebuilds without `docker compose up -d`, scale-up clones a stale source
|
|
113
|
+
* replica's config — same behavior as compose itself when scaling from an
|
|
114
|
+
* older replica. Documented here per the spec; not actionable in this code path.
|
|
115
|
+
*/
|
|
116
|
+
export declare function scaleWorkers(options: ScaleOptions): Promise<ScaleResult>;
|
|
117
|
+
/**
|
|
118
|
+
* Update the `workers` field in cluster.local.yaml atomically.
|
|
119
|
+
* Creates the file if absent. Preserves any other top-level fields already
|
|
120
|
+
* present.
|
|
121
|
+
*/
|
|
122
|
+
export declare function updateClusterLocalYaml(localYamlPath: string, count: number): Promise<void>;
|
|
123
|
+
/**
|
|
124
|
+
* POST to orchestrator /internal/refresh-metadata to trigger immediate metadata push.
|
|
125
|
+
*/
|
|
126
|
+
export declare function triggerMetadataRefresh(orchestratorUrl: string, apiKey: string): Promise<void>;
|
|
127
|
+
export { DockerEngineError };
|
|
128
|
+
//# sourceMappingURL=worker-scaler.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"worker-scaler.d.ts","sourceRoot":"","sources":["../../../src/services/worker-scaler.ts"],"names":[],"mappings":"AAKA,OAAO,EAAE,kBAAkB,EAAE,MAAM,2BAA2B,CAAC;AAC/D,OAAO,EACL,KAAK,gBAAgB,EACrB,KAAK,mBAAmB,EAExB,iBAAiB,EAClB,MAAM,0BAA0B,CAAC;AAClC,OAAO,EACL,KAAK,aAAa,EAClB,kBAAkB,EAClB,gBAAgB,EACjB,MAAM,yBAAyB,CAAC;AAGjC,OAAO,EAAE,kBAAkB,EAAE,gBAAgB,EAAE,KAAK,aAAa,EAAE,CAAC;AAMpE,MAAM,WAAW,YAAY;IAC3B,iFAAiF;IACjF,KAAK,EAAE,MAAM,CAAC;IACd,+DAA+D;IAC/D,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,8CAA8C;IAC9C,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,4FAA4F;IAC5F,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,yEAAyE;IACzE,YAAY,CAAC,EAAE,kBAAkB,CAAC;CACnC;AAED,MAAM,WAAW,WAAW;IAC1B,oFAAoF;IACpF,aAAa,EAAE,MAAM,CAAC;IACtB,4CAA4C;IAC5C,cAAc,EAAE,MAAM,CAAC;IACvB,mFAAmF;IACnF,WAAW,EAAE,MAAM,CAAC;CACrB;AAED,qBAAa,iBAAkB,SAAQ,KAAK;IAC1C,SAAkB,IAAI,uBAAuB;IAC7C,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAC;IAC/B,SAAkB,KAAK,EAAE,KAAK,CAAC;gBAEnB,SAAS,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,aAAa,EAAE,MAAM,EAAE,KAAK,EAAE,KAAK;CAOnF;AAMD,MAAM,WAAW,SAAS;IACxB,QAAQ,EAAE,MAAM,EAAE,CAAC;IACnB,QAAQ,EAAE,MAAM,EAAE,CAAC;CACpB;AAgBD;;;;;;;;;GASG;AACH,wBAAgB,sBAAsB,CAAC,QAAQ,EAAE,aAAa,EAAE,EAAE,MAAM,EAAE,MAAM,GAAG,SAAS,CAoC3F;AAED;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,oBAAoB,CAClC,OAAO,EAAE,gBAAgB,EACzB,SAAS,EAAE,MAAM,EACjB,QAAQ,EAAE,MAAM,GACf,mBAAmB,CAqCrB;AAMD,UAAU,aAAa;IACrB,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,MAAM,CAAC,EAAE;QAAE,MAAM,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,KAAK,CAAA;KAAE,CAAC;CAC3C;AAED,UAAU,eAAe;IACvB,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,MAAM,CAAC,EAAE;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,KAAK,CAAA;KAAE,CAAC;CACvC;AAED;;;;;;;;;;;;;;GAcG;AACH,wBAAsB,OAAO,CAC3B,MAAM,EAAE,kBAAkB,EAC1B,OAAO,EAAE,MAAM,EACf,MAAM,EAAE,gBAAgB,EACxB,QAAQ,EAAE,MAAM,EAAE,GACjB,OAAO,CAAC,aAAa,CAAC,CAuDxB;AAED;;;;GAIG;AACH,wBAAsB,SAAS,CAC7B,MAAM,EAAE,kBAAkB,EAC1B,WAAW,EAAE,MAAM,EAAE,GACpB,OAAO,CAAC,eAAe,CAAC,CAe1B;AAMD;;;;;;;;;;;;;;GAcG;AACH,wBAAsB,YAAY,CAAC,OAAO,EAAE,YAAY,GAAG,OAAO,CAAC,WAAW,CAAC,CAY9E;AAmGD;;;;GAIG;AACH,wBAAsB,sBAAsB,CAAC,aAAa,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAYhG;AAED;;GAEG;AACH,wBAAsB,sBAAsB,CAC1C,eAAe,EAAE,MAAM,EACvB,MAAM,EAAE,MAAM,GACb,OAAO,CAAC,IAAI,CAAC,CAaf;AAmCD,OAAO,EAAE,iBAAiB,EAAE,CAAC"}
|
|
@@ -0,0 +1,405 @@
|
|
|
1
|
+
import { readFile, stat, writeFile, rename } from 'node:fs/promises';
|
|
2
|
+
import { dirname, join } from 'node:path';
|
|
3
|
+
import { randomBytes } from 'node:crypto';
|
|
4
|
+
import { parse as parseYaml, stringify as stringifyYaml } from 'yaml';
|
|
5
|
+
import { resolveGeneracyDir } from './project-dir-resolver.js';
|
|
6
|
+
import { DockerEngineClient } from './docker-engine-client.js';
|
|
7
|
+
import { DockerEngineError, } from './docker-engine-types.js';
|
|
8
|
+
import { computeProjectName, enumerateWorkers, } from './worker-enumeration.js';
|
|
9
|
+
// Re-export so existing callers/tests can import these symbols from this module.
|
|
10
|
+
export { computeProjectName, enumerateWorkers };
|
|
11
|
+
export class PartialScaleError extends Error {
|
|
12
|
+
name = 'PartialScaleError';
|
|
13
|
+
requested;
|
|
14
|
+
actual;
|
|
15
|
+
previousCount;
|
|
16
|
+
cause;
|
|
17
|
+
constructor(requested, actual, previousCount, cause) {
|
|
18
|
+
super(`Partial scale: requested ${requested}, achieved ${actual} (${cause.message})`);
|
|
19
|
+
this.requested = requested;
|
|
20
|
+
this.actual = actual;
|
|
21
|
+
this.previousCount = previousCount;
|
|
22
|
+
this.cause = cause;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
// ---------------------------------------------------------------------------
|
|
26
|
+
// Module-level async mutex (FR-014, Q4=A).
|
|
27
|
+
//
|
|
28
|
+
// Promise-chain pattern from research.md §"In-process mutex" — serializes
|
|
29
|
+
// concurrent scaleWorkers() calls within this process. The second caller
|
|
30
|
+
// waits for the first to complete, then operates on the post-first state.
|
|
31
|
+
// ---------------------------------------------------------------------------
|
|
32
|
+
let inflight = Promise.resolve();
|
|
33
|
+
// ---------------------------------------------------------------------------
|
|
34
|
+
// Pure helpers
|
|
35
|
+
// ---------------------------------------------------------------------------
|
|
36
|
+
/**
|
|
37
|
+
* Plan container-number assignment for a scale operation.
|
|
38
|
+
*
|
|
39
|
+
* Scale up: gap-fill ascending in [1..max(existing)], then append.
|
|
40
|
+
* Scale down: sort exited (highest-numbered first), then running (highest-numbered first),
|
|
41
|
+
* take the first (current - target) IDs.
|
|
42
|
+
* No-op: returns `{ toCreate: [], toRemove: [] }`.
|
|
43
|
+
*
|
|
44
|
+
* Pure function — unit-tested independently. FR-006, SC-003, SC-011.
|
|
45
|
+
*/
|
|
46
|
+
export function assignContainerNumbers(existing, target) {
|
|
47
|
+
const current = existing.length;
|
|
48
|
+
if (current === target) {
|
|
49
|
+
return { toCreate: [], toRemove: [] };
|
|
50
|
+
}
|
|
51
|
+
if (target > current) {
|
|
52
|
+
// Scale up
|
|
53
|
+
const numbersInUse = new Set(existing.map((r) => r.number));
|
|
54
|
+
const max = existing.length === 0 ? 0 : Math.max(...numbersInUse);
|
|
55
|
+
const toCreate = [];
|
|
56
|
+
// Gap-fill ascending within [1..max].
|
|
57
|
+
for (let n = 1; n <= max; n++) {
|
|
58
|
+
if (toCreate.length >= target - current)
|
|
59
|
+
break;
|
|
60
|
+
if (!numbersInUse.has(n))
|
|
61
|
+
toCreate.push(n);
|
|
62
|
+
}
|
|
63
|
+
// Append above max until target reached.
|
|
64
|
+
for (let n = max + 1; toCreate.length < target - current; n++) {
|
|
65
|
+
toCreate.push(n);
|
|
66
|
+
}
|
|
67
|
+
return { toCreate, toRemove: [] };
|
|
68
|
+
}
|
|
69
|
+
// Scale down: exited first (highest-numbered first), then running (highest-numbered first).
|
|
70
|
+
const exited = existing
|
|
71
|
+
.filter((r) => r.state !== 'running')
|
|
72
|
+
.sort((a, b) => b.number - a.number);
|
|
73
|
+
const running = existing
|
|
74
|
+
.filter((r) => r.state === 'running')
|
|
75
|
+
.sort((a, b) => b.number - a.number);
|
|
76
|
+
const ordered = [...exited, ...running];
|
|
77
|
+
const toRemove = ordered.slice(0, current - target).map((r) => r.id);
|
|
78
|
+
return { toCreate: [], toRemove };
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* Clone a source replica's inspect response into a create body for a new replica.
|
|
82
|
+
*
|
|
83
|
+
* Strips orchestrator-set fields (Id, Created, State, Status, Hostname, populated
|
|
84
|
+
* NetworkSettings) and keeps Image, Cmd, Env, Entrypoint, WorkingDir, User, Labels,
|
|
85
|
+
* Healthcheck, StopSignal, StopTimeout, ExposedPorts, and all of HostConfig.
|
|
86
|
+
*
|
|
87
|
+
* Overwrites the `com.docker.compose.container-number` label with the new number —
|
|
88
|
+
* preserves all other labels (project, service, config-hash, etc.) per FR-007.
|
|
89
|
+
*
|
|
90
|
+
* Builds NetworkingConfig with **first network only** from source.NetworkSettings.Networks
|
|
91
|
+
* in insertion order. Caller is responsible for `connectNetwork` calls for remaining
|
|
92
|
+
* networks before `startContainer` (Q1=A multi-network sequencing).
|
|
93
|
+
*
|
|
94
|
+
* Throws `Error('SOURCE_REPLICA_HAS_NO_NETWORKS')` if source has zero networks.
|
|
95
|
+
*/
|
|
96
|
+
export function cloneInspectToCreate(inspect, newNumber, _newName) {
|
|
97
|
+
const networkEntries = Object.entries(inspect.NetworkSettings.Networks);
|
|
98
|
+
if (networkEntries.length === 0) {
|
|
99
|
+
throw new Error('SOURCE_REPLICA_HAS_NO_NETWORKS');
|
|
100
|
+
}
|
|
101
|
+
const [firstNetworkName, firstEndpoint] = networkEntries[0];
|
|
102
|
+
const labels = { ...(inspect.Config.Labels ?? {}) };
|
|
103
|
+
labels['com.docker.compose.container-number'] = String(newNumber);
|
|
104
|
+
const firstEndpointConfig = {};
|
|
105
|
+
if (firstEndpoint.Aliases)
|
|
106
|
+
firstEndpointConfig.Aliases = firstEndpoint.Aliases;
|
|
107
|
+
if (firstEndpoint.IPAMConfig?.IPv4Address) {
|
|
108
|
+
firstEndpointConfig.IPAMConfig = { IPv4Address: firstEndpoint.IPAMConfig.IPv4Address };
|
|
109
|
+
}
|
|
110
|
+
const body = {
|
|
111
|
+
Image: inspect.Image,
|
|
112
|
+
HostConfig: inspect.HostConfig,
|
|
113
|
+
NetworkingConfig: { EndpointsConfig: { [firstNetworkName]: firstEndpointConfig } },
|
|
114
|
+
Labels: labels,
|
|
115
|
+
};
|
|
116
|
+
// Carry through optional config fields verbatim.
|
|
117
|
+
if (inspect.Config.User !== undefined)
|
|
118
|
+
body.User = inspect.Config.User;
|
|
119
|
+
if (inspect.Config.Env !== undefined)
|
|
120
|
+
body.Env = inspect.Config.Env;
|
|
121
|
+
if (inspect.Config.Cmd !== undefined)
|
|
122
|
+
body.Cmd = inspect.Config.Cmd;
|
|
123
|
+
if (inspect.Config.Entrypoint !== undefined)
|
|
124
|
+
body.Entrypoint = inspect.Config.Entrypoint;
|
|
125
|
+
if (inspect.Config.WorkingDir !== undefined)
|
|
126
|
+
body.WorkingDir = inspect.Config.WorkingDir;
|
|
127
|
+
if (inspect.Config.Healthcheck !== undefined)
|
|
128
|
+
body.Healthcheck = inspect.Config.Healthcheck;
|
|
129
|
+
if (inspect.Config.StopSignal !== undefined)
|
|
130
|
+
body.StopSignal = inspect.Config.StopSignal;
|
|
131
|
+
if (inspect.Config.StopTimeout !== undefined)
|
|
132
|
+
body.StopTimeout = inspect.Config.StopTimeout;
|
|
133
|
+
if (inspect.Config.ExposedPorts !== undefined)
|
|
134
|
+
body.ExposedPorts = inspect.Config.ExposedPorts;
|
|
135
|
+
// Hostname is intentionally NOT carried through — Docker derives it from the
|
|
136
|
+
// container name when absent, which avoids collisions across clones.
|
|
137
|
+
return body;
|
|
138
|
+
}
|
|
139
|
+
/**
|
|
140
|
+
* Create + connect-extra-networks + start, repeated per toCreate slot.
|
|
141
|
+
*
|
|
142
|
+
* Per-slot sequencing (Q1=A):
|
|
143
|
+
* 1. Pre-check gap-fill name collision (FR-015, Q5=A) — if any container
|
|
144
|
+
* already holds `<project>-worker-<n>`, force-remove it first. Edge
|
|
145
|
+
* case after manual `docker rm` of a running worker; normal operation
|
|
146
|
+
* doesn't trigger it because exited replicas are counted by FR-002.
|
|
147
|
+
* 2. POST /containers/create with the first network in NetworkingConfig.
|
|
148
|
+
* 3. POST /networks/<id>/connect per additional network from source.
|
|
149
|
+
* 4. POST /containers/<id>/start.
|
|
150
|
+
*
|
|
151
|
+
* On the first error, stop the loop and return `{ created, failed }`. Do NOT
|
|
152
|
+
* roll back already-created replicas (Q2=B: commit what succeeded).
|
|
153
|
+
*/
|
|
154
|
+
export async function scaleUp(client, project, source, toCreate) {
|
|
155
|
+
const created = [];
|
|
156
|
+
// Source network order is daemon-authoritative (insertion order). The first
|
|
157
|
+
// network goes into NetworkingConfig at create time; the rest are attached
|
|
158
|
+
// via connectNetwork before start.
|
|
159
|
+
const sourceNetworkEntries = Object.entries(source.NetworkSettings.Networks);
|
|
160
|
+
const extraNetworks = sourceNetworkEntries.slice(1);
|
|
161
|
+
for (const number of toCreate) {
|
|
162
|
+
const name = `${project}-worker-${number}`;
|
|
163
|
+
try {
|
|
164
|
+
// Pre-check for stale container holding the target name.
|
|
165
|
+
const conflicts = await client.listContainers({
|
|
166
|
+
all: true,
|
|
167
|
+
filters: { name: [name] },
|
|
168
|
+
});
|
|
169
|
+
for (const conflict of conflicts) {
|
|
170
|
+
// Docker's name filter is substring-match — narrow to exact match.
|
|
171
|
+
const exactMatch = conflict.Names.some((n) => n === `/${name}` || n === name);
|
|
172
|
+
if (!exactMatch)
|
|
173
|
+
continue;
|
|
174
|
+
await client.removeContainer(conflict.Id, { force: true });
|
|
175
|
+
}
|
|
176
|
+
const body = cloneInspectToCreate(source, number, name);
|
|
177
|
+
const createResult = await client.createContainer(name, body);
|
|
178
|
+
// Attach remaining networks before start so workloads see full membership.
|
|
179
|
+
for (const [, endpoint] of extraNetworks) {
|
|
180
|
+
const connectBody = {
|
|
181
|
+
Container: createResult.Id,
|
|
182
|
+
};
|
|
183
|
+
const endpointConfig = {};
|
|
184
|
+
if (endpoint.Aliases)
|
|
185
|
+
endpointConfig.Aliases = endpoint.Aliases;
|
|
186
|
+
if (endpoint.IPAMConfig?.IPv4Address) {
|
|
187
|
+
endpointConfig.IPAMConfig = { IPv4Address: endpoint.IPAMConfig.IPv4Address };
|
|
188
|
+
}
|
|
189
|
+
if (Object.keys(endpointConfig).length > 0) {
|
|
190
|
+
connectBody.EndpointConfig = endpointConfig;
|
|
191
|
+
}
|
|
192
|
+
await client.connectNetwork(endpoint.NetworkID, connectBody);
|
|
193
|
+
}
|
|
194
|
+
await client.startContainer(createResult.Id);
|
|
195
|
+
created.push(number);
|
|
196
|
+
}
|
|
197
|
+
catch (err) {
|
|
198
|
+
return {
|
|
199
|
+
created,
|
|
200
|
+
failed: { number, error: err instanceof Error ? err : new Error(String(err)) },
|
|
201
|
+
};
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
return { created };
|
|
205
|
+
}
|
|
206
|
+
/**
|
|
207
|
+
* Stop + remove per ID, in the caller-provided order (already sorted by
|
|
208
|
+
* `assignContainerNumbers`: exited first, then running, highest-numbered first).
|
|
209
|
+
* On the first error, stop and return `{ removed, failed }`. (FR-004)
|
|
210
|
+
*/
|
|
211
|
+
export async function scaleDown(client, toRemoveIds) {
|
|
212
|
+
const removed = [];
|
|
213
|
+
for (const id of toRemoveIds) {
|
|
214
|
+
try {
|
|
215
|
+
await client.stopContainer(id);
|
|
216
|
+
await client.removeContainer(id);
|
|
217
|
+
removed.push(id);
|
|
218
|
+
}
|
|
219
|
+
catch (err) {
|
|
220
|
+
return {
|
|
221
|
+
removed,
|
|
222
|
+
failed: { id, error: err instanceof Error ? err : new Error(String(err)) },
|
|
223
|
+
};
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
return { removed };
|
|
227
|
+
}
|
|
228
|
+
// ---------------------------------------------------------------------------
|
|
229
|
+
// Main entry: scaleWorkers
|
|
230
|
+
// ---------------------------------------------------------------------------
|
|
231
|
+
/**
|
|
232
|
+
* Scale worker replicas to the requested count via the Docker Engine API.
|
|
233
|
+
*
|
|
234
|
+
* Replaces the previous `docker compose --scale` shell-out: enumerates workers
|
|
235
|
+
* by `com.docker.compose.*` labels, clones an existing replica's config, and
|
|
236
|
+
* issues create/connect/start or stop/remove directly on the daemon. Removes
|
|
237
|
+
* the host compose-file dependency entirely.
|
|
238
|
+
*
|
|
239
|
+
* Concurrent invocations are serialized by an in-process async mutex (FR-014).
|
|
240
|
+
*
|
|
241
|
+
* Stale clone drift case (FR-013): if a user edits the host compose file and
|
|
242
|
+
* rebuilds without `docker compose up -d`, scale-up clones a stale source
|
|
243
|
+
* replica's config — same behavior as compose itself when scaling from an
|
|
244
|
+
* older replica. Documented here per the spec; not actionable in this code path.
|
|
245
|
+
*/
|
|
246
|
+
export async function scaleWorkers(options) {
|
|
247
|
+
const previous = inflight;
|
|
248
|
+
let resolveNext;
|
|
249
|
+
inflight = new Promise((r) => {
|
|
250
|
+
resolveNext = r;
|
|
251
|
+
});
|
|
252
|
+
await previous;
|
|
253
|
+
try {
|
|
254
|
+
return await doScale(options);
|
|
255
|
+
}
|
|
256
|
+
finally {
|
|
257
|
+
resolveNext(undefined);
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
async function doScale(options) {
|
|
261
|
+
const { count } = options;
|
|
262
|
+
const orchestratorUrl = options.orchestratorUrl ?? process.env['ORCHESTRATOR_URL'] ?? 'http://127.0.0.1:3100';
|
|
263
|
+
const orchestratorApiKey = options.orchestratorApiKey ?? process.env['ORCHESTRATOR_INTERNAL_API_KEY'];
|
|
264
|
+
const dockerHostOption = options.dockerHost;
|
|
265
|
+
const client = options.engineClient ?? new DockerEngineClient(dockerHostOption !== undefined ? { dockerHost: dockerHostOption } : {});
|
|
266
|
+
const project = await computeProjectName(client);
|
|
267
|
+
const existing = await enumerateWorkers(client, project);
|
|
268
|
+
const previousCount = existing.length;
|
|
269
|
+
// No-op short-circuit: no Engine mutations, no cluster.yaml write, no metadata refresh.
|
|
270
|
+
if (previousCount === count) {
|
|
271
|
+
return { previousCount, requestedCount: count, actualCount: previousCount };
|
|
272
|
+
}
|
|
273
|
+
const plan = assignContainerNumbers(existing, count);
|
|
274
|
+
let scaleUpResult = { created: [] };
|
|
275
|
+
let scaleDownResult = { removed: [] };
|
|
276
|
+
let cause = null;
|
|
277
|
+
if (plan.toCreate.length > 0) {
|
|
278
|
+
// Clone source: first existing replica (any state — even an exited replica
|
|
279
|
+
// carries a complete inspect record).
|
|
280
|
+
const sourceReplica = existing[0];
|
|
281
|
+
const source = await client.inspectContainer(sourceReplica.id);
|
|
282
|
+
scaleUpResult = await scaleUp(client, project, source, plan.toCreate);
|
|
283
|
+
if (scaleUpResult.failed)
|
|
284
|
+
cause = scaleUpResult.failed.error;
|
|
285
|
+
}
|
|
286
|
+
else if (plan.toRemove.length > 0) {
|
|
287
|
+
scaleDownResult = await scaleDown(client, plan.toRemove);
|
|
288
|
+
if (scaleDownResult.failed)
|
|
289
|
+
cause = scaleDownResult.failed.error;
|
|
290
|
+
}
|
|
291
|
+
const actualCount = previousCount + scaleUpResult.created.length - scaleDownResult.removed.length;
|
|
292
|
+
const madeProgress = scaleUpResult.created.length > 0 || scaleDownResult.removed.length > 0;
|
|
293
|
+
if (cause && !madeProgress) {
|
|
294
|
+
// Full failure: no replicas created/removed. cluster.yaml NOT updated,
|
|
295
|
+
// metadata refresh NOT fired. Throw the underlying error directly so the
|
|
296
|
+
// route handler can map it (e.g. DockerDaemonUnavailableError → 503).
|
|
297
|
+
throw cause;
|
|
298
|
+
}
|
|
299
|
+
// Persist runtime worker count on any progress, including partial. Atomic
|
|
300
|
+
// temp+rename. Writes to cluster.local.yaml (git-ignored) to avoid mutating
|
|
301
|
+
// the template-owned, git-tracked cluster.yaml (#709).
|
|
302
|
+
const generacyDir = await resolveGeneracyDir();
|
|
303
|
+
const localYamlPath = join(generacyDir, 'cluster.local.yaml');
|
|
304
|
+
await updateClusterLocalYaml(localYamlPath, actualCount);
|
|
305
|
+
// Best-effort: keep .env's WORKER_COUNT in sync so host-side `docker compose
|
|
306
|
+
// up -d` doesn't undo the scale on the next re-up. Failures are non-blocking
|
|
307
|
+
// (cluster.local.yaml is the runtime source of truth and the CLI
|
|
308
|
+
// re-derivation step will reconcile .env on the next `npx generacy up` /
|
|
309
|
+
// `update`). See #708.
|
|
310
|
+
const envPath = join(generacyDir, '.env');
|
|
311
|
+
try {
|
|
312
|
+
await syncEnvWorkerCountInScaler(envPath, actualCount);
|
|
313
|
+
}
|
|
314
|
+
catch (err) {
|
|
315
|
+
const e = err;
|
|
316
|
+
if (e?.code === 'ENOENT') {
|
|
317
|
+
console.warn(`[worker-scaler] WORKER_COUNT sync to .env skipped: file not found at ${envPath}`);
|
|
318
|
+
}
|
|
319
|
+
else {
|
|
320
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
321
|
+
console.warn(`[worker-scaler] WORKER_COUNT sync to .env failed: ${msg}; cluster.local.yaml is the source of truth`);
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
if (orchestratorApiKey) {
|
|
325
|
+
triggerMetadataRefresh(orchestratorUrl, orchestratorApiKey).catch(() => {
|
|
326
|
+
// Non-fatal: metadata will refresh on the next periodic cycle.
|
|
327
|
+
});
|
|
328
|
+
}
|
|
329
|
+
if (cause) {
|
|
330
|
+
throw new PartialScaleError(count, actualCount, previousCount, cause);
|
|
331
|
+
}
|
|
332
|
+
return { previousCount, requestedCount: count, actualCount };
|
|
333
|
+
}
|
|
334
|
+
// ---------------------------------------------------------------------------
|
|
335
|
+
// Preserved helpers
|
|
336
|
+
// ---------------------------------------------------------------------------
|
|
337
|
+
/**
|
|
338
|
+
* Update the `workers` field in cluster.local.yaml atomically.
|
|
339
|
+
* Creates the file if absent. Preserves any other top-level fields already
|
|
340
|
+
* present.
|
|
341
|
+
*/
|
|
342
|
+
export async function updateClusterLocalYaml(localYamlPath, count) {
|
|
343
|
+
let doc;
|
|
344
|
+
try {
|
|
345
|
+
const content = await readFile(localYamlPath, 'utf-8');
|
|
346
|
+
doc = parseYaml(content) ?? {};
|
|
347
|
+
}
|
|
348
|
+
catch {
|
|
349
|
+
doc = {};
|
|
350
|
+
}
|
|
351
|
+
doc.workers = count;
|
|
352
|
+
const output = stringifyYaml(doc);
|
|
353
|
+
await atomicWrite(localYamlPath, output);
|
|
354
|
+
}
|
|
355
|
+
/**
|
|
356
|
+
* POST to orchestrator /internal/refresh-metadata to trigger immediate metadata push.
|
|
357
|
+
*/
|
|
358
|
+
export async function triggerMetadataRefresh(orchestratorUrl, apiKey) {
|
|
359
|
+
const response = await fetch(`${orchestratorUrl}/internal/refresh-metadata`, {
|
|
360
|
+
method: 'POST',
|
|
361
|
+
headers: {
|
|
362
|
+
authorization: `Bearer ${apiKey}`,
|
|
363
|
+
'content-type': 'application/json',
|
|
364
|
+
},
|
|
365
|
+
signal: AbortSignal.timeout(5000),
|
|
366
|
+
});
|
|
367
|
+
if (!response.ok) {
|
|
368
|
+
throw new Error(`refresh-metadata returned ${response.status}`);
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
/**
|
|
372
|
+
* Atomic file write: write to temp file, then rename. Temp file is created in
|
|
373
|
+
* the target directory (not os.tmpdir) so rename(2) stays on a single filesystem.
|
|
374
|
+
*/
|
|
375
|
+
async function atomicWrite(targetPath, content) {
|
|
376
|
+
const tmpPath = join(dirname(targetPath), `.${randomBytes(8).toString('hex')}.tmp`);
|
|
377
|
+
await writeFile(tmpPath, content, { mode: 0o644 });
|
|
378
|
+
await rename(tmpPath, targetPath);
|
|
379
|
+
}
|
|
380
|
+
/**
|
|
381
|
+
* Rewrite `WORKER_COUNT=<count>` in .env. Throws ENOENT if .env is missing
|
|
382
|
+
* (caller treats that as a skip-and-warn). Other errors propagate to the
|
|
383
|
+
* caller's catch and are logged as failures. The CLI re-derivation path in
|
|
384
|
+
* `worker-count-deriver.ts` is the symmetric implementation on the host side.
|
|
385
|
+
*/
|
|
386
|
+
async function syncEnvWorkerCountInScaler(envPath, count) {
|
|
387
|
+
await stat(envPath); // throws ENOENT if missing — caller logs the skip
|
|
388
|
+
const existing = await readFile(envPath, 'utf-8');
|
|
389
|
+
const line = `WORKER_COUNT=${count}`;
|
|
390
|
+
const pattern = /^WORKER_COUNT=.*$/m;
|
|
391
|
+
let next;
|
|
392
|
+
if (pattern.test(existing)) {
|
|
393
|
+
next = existing.replace(pattern, line);
|
|
394
|
+
}
|
|
395
|
+
else if (existing.length === 0) {
|
|
396
|
+
next = `${line}\n`;
|
|
397
|
+
}
|
|
398
|
+
else {
|
|
399
|
+
next = existing.endsWith('\n') ? `${existing}${line}\n` : `${existing}\n${line}\n`;
|
|
400
|
+
}
|
|
401
|
+
await atomicWrite(envPath, next);
|
|
402
|
+
}
|
|
403
|
+
// Re-export so existing tests can import from this module.
|
|
404
|
+
export { DockerEngineError };
|
|
405
|
+
//# sourceMappingURL=worker-scaler.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"worker-scaler.js","sourceRoot":"","sources":["../../../src/services/worker-scaler.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@generacy-ai/control-plane",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"description": "In-cluster control-plane HTTP service over Unix socket for cloud-hosted bootstrap UI",
|
|
5
5
|
"main": "dist/src/index.js",
|
|
6
6
|
"types": "dist/src/index.d.ts",
|
|
@@ -19,6 +19,7 @@
|
|
|
19
19
|
"dependencies": {
|
|
20
20
|
"yaml": "^2.7.0",
|
|
21
21
|
"zod": "^3.23.0",
|
|
22
|
+
"@generacy-ai/config": "0.2.0",
|
|
22
23
|
"@generacy-ai/credhelper": "0.1.1"
|
|
23
24
|
},
|
|
24
25
|
"devDependencies": {
|