@kapeta/local-cluster-service 0.19.6 → 0.20.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/CHANGELOG.md +14 -0
- package/dist/cjs/src/config/routes.js +2 -3
- package/dist/cjs/src/configManager.d.ts +1 -0
- package/dist/cjs/src/configManager.js +2 -1
- package/dist/cjs/src/containerManager.d.ts +39 -32
- package/dist/cjs/src/containerManager.js +138 -108
- package/dist/cjs/src/instanceManager.js +30 -19
- package/dist/cjs/src/operatorManager.js +3 -0
- package/dist/cjs/src/taskManager.js +4 -1
- package/dist/cjs/src/utils/BlockInstanceRunner.js +9 -0
- package/dist/cjs/src/utils/utils.d.ts +3 -0
- package/dist/cjs/src/utils/utils.js +25 -1
- package/dist/esm/src/config/routes.js +2 -3
- package/dist/esm/src/configManager.d.ts +1 -0
- package/dist/esm/src/configManager.js +2 -1
- package/dist/esm/src/containerManager.d.ts +39 -32
- package/dist/esm/src/containerManager.js +138 -108
- package/dist/esm/src/instanceManager.js +30 -19
- package/dist/esm/src/operatorManager.js +3 -0
- package/dist/esm/src/taskManager.js +4 -1
- package/dist/esm/src/utils/BlockInstanceRunner.js +9 -0
- package/dist/esm/src/utils/utils.d.ts +3 -0
- package/dist/esm/src/utils/utils.js +25 -1
- package/package.json +5 -2
- package/src/config/routes.ts +2 -4
- package/src/configManager.ts +1 -0
- package/src/containerManager.ts +188 -140
- package/src/instanceManager.ts +51 -22
- package/src/operatorManager.ts +11 -1
- package/src/taskManager.ts +4 -1
- package/src/utils/BlockInstanceRunner.ts +19 -3
- package/src/utils/utils.ts +29 -0
@@ -262,6 +262,9 @@ class InstanceManager {
|
|
262
262
|
if (instance.status === types_1.InstanceStatus.STOPPED) {
|
263
263
|
return;
|
264
264
|
}
|
265
|
+
if (instance.status === types_1.InstanceStatus.STOPPING) {
|
266
|
+
return;
|
267
|
+
}
|
265
268
|
if (changeDesired && instance.desiredStatus !== types_1.DesiredInstanceStatus.EXTERNAL) {
|
266
269
|
instance.desiredStatus = types_1.DesiredInstanceStatus.STOP;
|
267
270
|
}
|
@@ -387,23 +390,24 @@ class InstanceManager {
|
|
387
390
|
}
|
388
391
|
}
|
389
392
|
const instanceConfig = await configManager_1.configManager.getConfigForSection(systemId, instanceId);
|
393
|
+
const resolvedConfig = (0, utils_1.getResolvedConfiguration)(blockSpec.configuration, instanceConfig, blockInstance.defaultConfiguration);
|
390
394
|
const task = taskManager_1.taskManager.add(`instance:start:${systemId}:${instanceId}`, async () => {
|
391
395
|
const runner = new BlockInstanceRunner_1.BlockInstanceRunner(systemId);
|
392
396
|
const startTime = Date.now();
|
393
397
|
try {
|
394
|
-
const processInfo = await runner.start(blockRef, instanceId,
|
395
|
-
instance.status = types_1.InstanceStatus.
|
398
|
+
const processInfo = await runner.start(blockRef, instanceId, resolvedConfig);
|
399
|
+
instance.status = types_1.InstanceStatus.STARTING;
|
396
400
|
return this.saveInternalInstance({
|
397
401
|
...instance,
|
398
402
|
type: processInfo.type,
|
399
403
|
pid: processInfo.pid ?? -1,
|
400
404
|
health: null,
|
401
405
|
portType: processInfo.portType,
|
402
|
-
status: types_1.InstanceStatus.
|
406
|
+
status: types_1.InstanceStatus.STARTING,
|
403
407
|
});
|
404
408
|
}
|
405
409
|
catch (e) {
|
406
|
-
console.warn('Failed to start instance: ', systemId, instanceId, blockRef, e
|
410
|
+
console.warn('Failed to start instance: ', systemId, instanceId, blockRef, e);
|
407
411
|
const logs = [
|
408
412
|
{
|
409
413
|
source: 'stdout',
|
@@ -550,7 +554,7 @@ class InstanceManager {
|
|
550
554
|
await this.start(instance.systemId, instance.instanceId);
|
551
555
|
}
|
552
556
|
catch (e) {
|
553
|
-
console.warn('Failed to start instance', instance.systemId, instance.instanceId, e);
|
557
|
+
console.warn('Failed to start previously stopped instance', instance.systemId, instance.instanceId, e);
|
554
558
|
}
|
555
559
|
return;
|
556
560
|
}
|
@@ -594,31 +598,38 @@ class InstanceManager {
|
|
594
598
|
return types_1.InstanceStatus.STOPPED;
|
595
599
|
}
|
596
600
|
const state = await container.status();
|
597
|
-
if (state
|
598
|
-
|
599
|
-
|
600
|
-
|
601
|
-
|
602
|
-
|
603
|
-
|
604
|
-
|
605
|
-
|
601
|
+
if (!state) {
|
602
|
+
return types_1.InstanceStatus.STOPPED;
|
603
|
+
}
|
604
|
+
const statusType = state.Status;
|
605
|
+
if (statusType === 'running') {
|
606
|
+
if (state.Health?.Status) {
|
607
|
+
const healthStatusType = state.Health.Status;
|
608
|
+
if (healthStatusType === 'healthy' || healthStatusType === 'none') {
|
609
|
+
return types_1.InstanceStatus.READY;
|
610
|
+
}
|
611
|
+
if (healthStatusType === 'starting') {
|
612
|
+
return types_1.InstanceStatus.STARTING;
|
613
|
+
}
|
614
|
+
if (healthStatusType === 'unhealthy') {
|
615
|
+
return types_1.InstanceStatus.UNHEALTHY;
|
616
|
+
}
|
606
617
|
}
|
607
618
|
return types_1.InstanceStatus.READY;
|
608
619
|
}
|
609
|
-
if (
|
620
|
+
if (statusType === 'created') {
|
610
621
|
return types_1.InstanceStatus.STARTING;
|
611
622
|
}
|
612
|
-
if (
|
623
|
+
if (statusType === 'exited' || statusType === 'dead') {
|
613
624
|
return types_1.InstanceStatus.STOPPED;
|
614
625
|
}
|
615
|
-
if (
|
626
|
+
if (statusType === 'removing') {
|
616
627
|
return types_1.InstanceStatus.BUSY;
|
617
628
|
}
|
618
|
-
if (
|
629
|
+
if (statusType === 'restarting') {
|
619
630
|
return types_1.InstanceStatus.BUSY;
|
620
631
|
}
|
621
|
-
if (
|
632
|
+
if (statusType === 'paused') {
|
622
633
|
return types_1.InstanceStatus.BUSY;
|
623
634
|
}
|
624
635
|
return types_1.InstanceStatus.STOPPED;
|
@@ -146,8 +146,11 @@ class OperatorManager {
|
|
146
146
|
const containerName = `kapeta-resource-${(0, md5_1.default)(nameParts.join('_'))}`;
|
147
147
|
const PortBindings = {};
|
148
148
|
const Env = [];
|
149
|
+
const systemUri = (0, nodejs_utils_1.parseKapetaUri)(systemId);
|
149
150
|
const Labels = {
|
150
151
|
kapeta: 'true',
|
152
|
+
[containerManager_1.COMPOSE_LABEL_PROJECT]: systemUri.id.replace(/[^a-z0-9]/gi, '_'),
|
153
|
+
[containerManager_1.COMPOSE_LABEL_SERVICE]: [resourceType, version].join('_').replace(/[^a-z0-9]/gi, '_'),
|
151
154
|
};
|
152
155
|
const operatorMetadata = operator.getDefinitionInfo().definition.metadata;
|
153
156
|
const bindHost = (0, utils_1.getBindHost)();
|
@@ -90,7 +90,9 @@ class TaskManager {
|
|
90
90
|
});
|
91
91
|
this._tasks.push(task);
|
92
92
|
socketManager_1.socketManager.emitGlobal(EVENT_TASK_ADDED, task.toData());
|
93
|
-
this.invokeTask(task).catch(() => {
|
93
|
+
this.invokeTask(task).catch((err) => {
|
94
|
+
console.warn(`Task ${task.id} failed`, err);
|
95
|
+
});
|
94
96
|
return task;
|
95
97
|
}
|
96
98
|
async waitFor(filter) {
|
@@ -145,6 +147,7 @@ class TaskManager {
|
|
145
147
|
task.emitUpdate();
|
146
148
|
}
|
147
149
|
catch (e) {
|
150
|
+
console.warn(`Task ${task.id} failed while waiting for it to resolve`, e);
|
148
151
|
task.errorMessage = e.message;
|
149
152
|
task.status = TaskStatus.FAILED;
|
150
153
|
task.future.reject(e);
|
@@ -146,6 +146,7 @@ class BlockInstanceRunner {
|
|
146
146
|
if (localContainer.healthcheck) {
|
147
147
|
HealthCheck = containerManager_1.containerManager.toDockerHealth({ cmd: localContainer.healthcheck });
|
148
148
|
}
|
149
|
+
const systemUri = (0, nodejs_utils_1.parseKapetaUri)(this._systemId);
|
149
150
|
return this.ensureContainer({
|
150
151
|
...dockerOpts,
|
151
152
|
Image: dockerImage,
|
@@ -154,6 +155,8 @@ class BlockInstanceRunner {
|
|
154
155
|
Labels: {
|
155
156
|
...customLabels,
|
156
157
|
instance: blockInstance.id,
|
158
|
+
[containerManager_1.COMPOSE_LABEL_PROJECT]: systemUri.id.replace(/[^a-z0-9]/gi, '_'),
|
159
|
+
[containerManager_1.COMPOSE_LABEL_SERVICE]: blockInfo.id.replace(/[^a-z0-9]/gi, '_'),
|
157
160
|
},
|
158
161
|
HealthCheck,
|
159
162
|
ExposedPorts,
|
@@ -195,12 +198,15 @@ class BlockInstanceRunner {
|
|
195
198
|
const containerName = (0, utils_1.getBlockInstanceContainerName)(this._systemId, blockInstance.id);
|
196
199
|
// For windows we need to default to root
|
197
200
|
const innerHome = process.platform === 'win32' ? '/root/.kapeta' : local_cluster_config_1.default.getKapetaBasedir();
|
201
|
+
const systemUri = (0, nodejs_utils_1.parseKapetaUri)(this._systemId);
|
198
202
|
return this.ensureContainer({
|
199
203
|
Image: dockerImage,
|
200
204
|
name: containerName,
|
201
205
|
ExposedPorts,
|
202
206
|
Labels: {
|
203
207
|
instance: blockInstance.id,
|
208
|
+
[containerManager_1.COMPOSE_LABEL_PROJECT]: systemUri.id.replace(/[^a-z0-9]/gi, '_'),
|
209
|
+
[containerManager_1.COMPOSE_LABEL_SERVICE]: blockInfo.id.replace(/[^a-z0-9]/gi, '_'),
|
204
210
|
},
|
205
211
|
Env: [
|
206
212
|
...DOCKER_ENV_VARS,
|
@@ -273,6 +279,7 @@ class BlockInstanceRunner {
|
|
273
279
|
}
|
274
280
|
// For windows we need to default to root
|
275
281
|
const innerHome = process.platform === 'win32' ? '/root/.kapeta' : local_cluster_config_1.default.getKapetaBasedir();
|
282
|
+
const systemUri = (0, nodejs_utils_1.parseKapetaUri)(this._systemId);
|
276
283
|
logs.addLog(`Creating new container for block: ${containerName}`);
|
277
284
|
const out = await this.ensureContainer({
|
278
285
|
Image: dockerImage,
|
@@ -289,6 +296,8 @@ class BlockInstanceRunner {
|
|
289
296
|
},
|
290
297
|
Labels: {
|
291
298
|
instance: blockInstance.id,
|
299
|
+
[containerManager_1.COMPOSE_LABEL_PROJECT]: systemUri.id.replace(/[^a-z0-9]/gi, '_'),
|
300
|
+
[containerManager_1.COMPOSE_LABEL_SERVICE]: blockUri.id.replace(/[^a-z0-9]/gi, '_'),
|
292
301
|
},
|
293
302
|
Env: [
|
294
303
|
`KAPETA_INSTANCE_NAME=${blockInstance.ref}`,
|
@@ -1,3 +1,5 @@
|
|
1
|
+
import { EntityList } from '@kapeta/schemas';
|
2
|
+
import { AnyMap } from '../types';
|
1
3
|
export declare function getBlockInstanceContainerName(systemId: string, instanceId: string): string;
|
2
4
|
export declare function normalizeKapetaUri(uri: string): string;
|
3
5
|
export declare function readYML(path: string): any;
|
@@ -5,3 +7,4 @@ export declare function isWindows(): boolean;
|
|
5
7
|
export declare function isMac(): boolean;
|
6
8
|
export declare function isLinux(): boolean;
|
7
9
|
export declare function getBindHost(preferredHost?: string): string;
|
10
|
+
export declare function getResolvedConfiguration(entities?: EntityList, config?: AnyMap, globalConfiguration?: AnyMap): AnyMap;
|
@@ -3,11 +3,12 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
3
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
4
4
|
};
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
6
|
-
exports.getBindHost = exports.isLinux = exports.isMac = exports.isWindows = exports.readYML = exports.normalizeKapetaUri = exports.getBlockInstanceContainerName = void 0;
|
6
|
+
exports.getResolvedConfiguration = exports.getBindHost = exports.isLinux = exports.isMac = exports.isWindows = exports.readYML = exports.normalizeKapetaUri = exports.getBlockInstanceContainerName = void 0;
|
7
7
|
const node_fs_1 = __importDefault(require("node:fs"));
|
8
8
|
const yaml_1 = __importDefault(require("yaml"));
|
9
9
|
const nodejs_utils_1 = require("@kapeta/nodejs-utils");
|
10
10
|
const md5_1 = __importDefault(require("md5"));
|
11
|
+
const lodash_1 = __importDefault(require("lodash"));
|
11
12
|
function getBlockInstanceContainerName(systemId, instanceId) {
|
12
13
|
return `kapeta-block-instance-${(0, md5_1.default)(systemId + instanceId)}`;
|
13
14
|
}
|
@@ -51,3 +52,26 @@ function getBindHost(preferredHost = '127.0.0.1') {
|
|
51
52
|
return isLinux() ? '0.0.0.0' : preferredHost;
|
52
53
|
}
|
53
54
|
exports.getBindHost = getBindHost;
|
55
|
+
function getResolvedConfiguration(entities, config, globalConfiguration) {
|
56
|
+
if (!entities || !globalConfiguration) {
|
57
|
+
return config || {};
|
58
|
+
}
|
59
|
+
const mergedConfig = config ? lodash_1.default.cloneDeep(config) : {};
|
60
|
+
entities.types?.forEach((type) => {
|
61
|
+
if (!type.properties) {
|
62
|
+
return;
|
63
|
+
}
|
64
|
+
Object.entries(type.properties).forEach(([propertyName, property]) => {
|
65
|
+
if (!property.global) {
|
66
|
+
return;
|
67
|
+
}
|
68
|
+
const configPath = type.name + '.' + propertyName;
|
69
|
+
const defaultValue = globalConfiguration ? lodash_1.default.get(globalConfiguration, configPath) : undefined;
|
70
|
+
if (!lodash_1.default.has(mergedConfig, configPath)) {
|
71
|
+
lodash_1.default.set(mergedConfig, configPath, defaultValue);
|
72
|
+
}
|
73
|
+
});
|
74
|
+
});
|
75
|
+
return mergedConfig;
|
76
|
+
}
|
77
|
+
exports.getResolvedConfiguration = getResolvedConfiguration;
|
@@ -12,7 +12,6 @@ const cors_1 = require("../middleware/cors");
|
|
12
12
|
const kapeta_1 = require("../middleware/kapeta");
|
13
13
|
const stringBody_1 = require("../middleware/stringBody");
|
14
14
|
const router = (0, express_promise_router_1.default)();
|
15
|
-
const SYSTEM_ID = '$plan';
|
16
15
|
router.use('/', cors_1.corsHandler);
|
17
16
|
router.use('/', kapeta_1.kapetaHeaders);
|
18
17
|
router.use('/', stringBody_1.stringBody);
|
@@ -54,7 +53,7 @@ router.put('/instance', async (req, res) => {
|
|
54
53
|
* Returns the full configuration for a plan
|
55
54
|
*/
|
56
55
|
router.get('/system', (req, res) => {
|
57
|
-
const config = configManager_1.configManager.getConfigForSection(req.kapeta.systemId, SYSTEM_ID);
|
56
|
+
const config = configManager_1.configManager.getConfigForSection(req.kapeta.systemId, configManager_1.SYSTEM_ID);
|
58
57
|
res.send(config);
|
59
58
|
});
|
60
59
|
/**
|
@@ -65,7 +64,7 @@ router.put('/system', (req, res) => {
|
|
65
64
|
if (!config) {
|
66
65
|
config = {};
|
67
66
|
}
|
68
|
-
configManager_1.configManager.setConfigForSection(req.kapeta.systemId, SYSTEM_ID, config);
|
67
|
+
configManager_1.configManager.setConfigForSection(req.kapeta.systemId, configManager_1.SYSTEM_ID, config);
|
69
68
|
res.status(202).send({ ok: true });
|
70
69
|
});
|
71
70
|
/**
|
@@ -1,10 +1,11 @@
|
|
1
1
|
"use strict";
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
3
|
-
exports.configManager = void 0;
|
3
|
+
exports.configManager = exports.SYSTEM_ID = void 0;
|
4
4
|
const storageService_1 = require("./storageService");
|
5
5
|
const assetManager_1 = require("./assetManager");
|
6
6
|
const nodejs_utils_1 = require("@kapeta/nodejs-utils");
|
7
7
|
const utils_1 = require("./utils/utils");
|
8
|
+
exports.SYSTEM_ID = '$plan';
|
8
9
|
class ConfigManager {
|
9
10
|
_config;
|
10
11
|
constructor() {
|
@@ -1,7 +1,6 @@
|
|
1
1
|
/// <reference types="node" />
|
2
2
|
import FSExtra from 'fs-extra';
|
3
|
-
import
|
4
|
-
import { Container } from 'node-docker-api/lib/container';
|
3
|
+
import Docker from 'dockerode';
|
5
4
|
import { InstanceInfo, LogEntry } from './types';
|
6
5
|
type StringMap = {
|
7
6
|
[key: string]: string;
|
@@ -20,24 +19,8 @@ export interface DockerMounts {
|
|
20
19
|
ReadOnly: boolean;
|
21
20
|
Consistency: string;
|
22
21
|
}
|
23
|
-
|
24
|
-
|
25
|
-
Running: boolean;
|
26
|
-
Paused: boolean;
|
27
|
-
Restarting: boolean;
|
28
|
-
OOMKilled: boolean;
|
29
|
-
Dead: boolean;
|
30
|
-
Pid: number;
|
31
|
-
ExitCode: number;
|
32
|
-
Error: string;
|
33
|
-
StartedAt: string;
|
34
|
-
FinishedAt: string;
|
35
|
-
Health?: {
|
36
|
-
Status: 'starting' | 'healthy' | 'unhealthy' | 'none';
|
37
|
-
FailingStreak: number;
|
38
|
-
Log: any[] | null;
|
39
|
-
};
|
40
|
-
}
|
22
|
+
export type DockerContainerStatus = 'created' | 'running' | 'paused' | 'restarting' | 'removing' | 'exited' | 'dead';
|
23
|
+
export type DockerContainerHealth = 'starting' | 'healthy' | 'unhealthy' | 'none';
|
41
24
|
interface Health {
|
42
25
|
cmd: string;
|
43
26
|
interval?: number;
|
@@ -45,6 +28,8 @@ interface Health {
|
|
45
28
|
retries?: number;
|
46
29
|
}
|
47
30
|
export declare const CONTAINER_LABEL_PORT_PREFIX = "kapeta_port-";
|
31
|
+
export declare const COMPOSE_LABEL_PROJECT = "com.docker.compose.project";
|
32
|
+
export declare const COMPOSE_LABEL_SERVICE = "com.docker.compose.service";
|
48
33
|
export declare const HEALTH_CHECK_TIMEOUT: number;
|
49
34
|
declare class ContainerManager {
|
50
35
|
private _docker;
|
@@ -71,12 +56,12 @@ declare class ContainerManager {
|
|
71
56
|
Retries: number;
|
72
57
|
};
|
73
58
|
private applyHash;
|
74
|
-
ensureContainer(opts: any): Promise<Container>;
|
59
|
+
ensureContainer(opts: any): Promise<Docker.Container>;
|
75
60
|
private createOrUpdateContainer;
|
76
|
-
startContainer
|
77
|
-
waitForReady(container: Container, attempt?: number): Promise<void>;
|
78
|
-
_isReady(container: Container): Promise<
|
79
|
-
remove(container: Container, opts?: {
|
61
|
+
private startContainer;
|
62
|
+
waitForReady(container: Docker.Container, attempt?: number): Promise<void>;
|
63
|
+
_isReady(container: Docker.Container): Promise<boolean>;
|
64
|
+
remove(container: Docker.Container, opts?: {
|
80
65
|
force?: boolean;
|
81
66
|
}): Promise<void>;
|
82
67
|
/**
|
@@ -84,7 +69,7 @@ declare class ContainerManager {
|
|
84
69
|
* @param name
|
85
70
|
* @return {Promise<ContainerInfo>}
|
86
71
|
*/
|
87
|
-
get(name: string): Promise<ContainerInfo |
|
72
|
+
get(name: string): Promise<ContainerInfo | undefined>;
|
88
73
|
getLogs(instance: InstanceInfo): Promise<LogEntry[]>;
|
89
74
|
stopLogListening(systemId: string, instanceId: string): Promise<void>;
|
90
75
|
ensureLogListening(systemId: string, instanceId: string, handler: (log: LogEntry) => void): Promise<void>;
|
@@ -102,11 +87,11 @@ export declare class ContainerInfo {
|
|
102
87
|
private readonly _container;
|
103
88
|
/**
|
104
89
|
*
|
105
|
-
* @param {Container} dockerContainer
|
90
|
+
* @param {Docker.Container} dockerContainer
|
106
91
|
*/
|
107
|
-
constructor(dockerContainer: Container);
|
108
|
-
get native(): Container;
|
109
|
-
isRunning(): Promise<
|
92
|
+
constructor(dockerContainer: Docker.Container);
|
93
|
+
get native(): Docker.Container;
|
94
|
+
isRunning(): Promise<boolean>;
|
110
95
|
start(): Promise<void>;
|
111
96
|
restart(): Promise<void>;
|
112
97
|
stop(): Promise<void>;
|
@@ -118,8 +103,30 @@ export declare class ContainerInfo {
|
|
118
103
|
protocol: string;
|
119
104
|
hostPort: string;
|
120
105
|
} | null>;
|
121
|
-
inspect(): Promise<
|
122
|
-
status(): Promise<
|
106
|
+
inspect(): Promise<Docker.ContainerInspectInfo | undefined>;
|
107
|
+
status(): Promise<{
|
108
|
+
Status: string;
|
109
|
+
Running: boolean;
|
110
|
+
Paused: boolean;
|
111
|
+
Restarting: boolean;
|
112
|
+
OOMKilled: boolean;
|
113
|
+
Dead: boolean;
|
114
|
+
Pid: number;
|
115
|
+
ExitCode: number;
|
116
|
+
Error: string;
|
117
|
+
StartedAt: string;
|
118
|
+
FinishedAt: string;
|
119
|
+
Health?: {
|
120
|
+
Status: string;
|
121
|
+
FailingStreak: number;
|
122
|
+
Log: {
|
123
|
+
Start: string;
|
124
|
+
End: string;
|
125
|
+
ExitCode: number;
|
126
|
+
Output: string;
|
127
|
+
}[];
|
128
|
+
} | undefined;
|
129
|
+
} | undefined>;
|
123
130
|
getPorts(): Promise<PortMap | false>;
|
124
131
|
getLogStream(): Promise<ClosableLogStream>;
|
125
132
|
getLogs(): Promise<LogEntry[]>;
|