@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
package/src/instanceManager.ts
CHANGED
@@ -6,11 +6,16 @@ import { storageService } from './storageService';
|
|
6
6
|
import { EVENT_INSTANCE_CREATED, EVENT_INSTANCE_EXITED, EVENT_STATUS_CHANGED, socketManager } from './socketManager';
|
7
7
|
import { serviceManager } from './serviceManager';
|
8
8
|
import { assetManager } from './assetManager';
|
9
|
-
import {
|
9
|
+
import {
|
10
|
+
containerManager,
|
11
|
+
DockerContainerHealth,
|
12
|
+
DockerContainerStatus,
|
13
|
+
HEALTH_CHECK_TIMEOUT,
|
14
|
+
} from './containerManager';
|
10
15
|
import { configManager } from './configManager';
|
11
16
|
import { DesiredInstanceStatus, InstanceInfo, InstanceOwner, InstanceStatus, InstanceType, LogEntry } from './types';
|
12
17
|
import { BlockDefinitionSpec, BlockInstance, Plan } from '@kapeta/schemas';
|
13
|
-
import { getBlockInstanceContainerName, normalizeKapetaUri } from './utils/utils';
|
18
|
+
import { getBlockInstanceContainerName, getResolvedConfiguration, normalizeKapetaUri } from './utils/utils';
|
14
19
|
import { KIND_OPERATOR, operatorManager } from './operatorManager';
|
15
20
|
import { parseKapetaUri } from '@kapeta/nodejs-utils';
|
16
21
|
import { definitionsManager } from './definitionsManager';
|
@@ -336,6 +341,10 @@ export class InstanceManager {
|
|
336
341
|
return;
|
337
342
|
}
|
338
343
|
|
344
|
+
if (instance.status === InstanceStatus.STOPPING) {
|
345
|
+
return;
|
346
|
+
}
|
347
|
+
|
339
348
|
if (changeDesired && instance.desiredStatus !== DesiredInstanceStatus.EXTERNAL) {
|
340
349
|
instance.desiredStatus = DesiredInstanceStatus.STOP;
|
341
350
|
}
|
@@ -488,15 +497,20 @@ export class InstanceManager {
|
|
488
497
|
}
|
489
498
|
|
490
499
|
const instanceConfig = await configManager.getConfigForSection(systemId, instanceId);
|
500
|
+
const resolvedConfig = getResolvedConfiguration(
|
501
|
+
blockSpec.configuration,
|
502
|
+
instanceConfig,
|
503
|
+
blockInstance.defaultConfiguration
|
504
|
+
);
|
491
505
|
const task = taskManager.add(
|
492
506
|
`instance:start:${systemId}:${instanceId}`,
|
493
507
|
async () => {
|
494
508
|
const runner = new BlockInstanceRunner(systemId);
|
495
509
|
const startTime = Date.now();
|
496
510
|
try {
|
497
|
-
const processInfo = await runner.start(blockRef, instanceId,
|
511
|
+
const processInfo = await runner.start(blockRef, instanceId, resolvedConfig);
|
498
512
|
|
499
|
-
instance.status = InstanceStatus.
|
513
|
+
instance.status = InstanceStatus.STARTING;
|
500
514
|
|
501
515
|
return this.saveInternalInstance({
|
502
516
|
...instance,
|
@@ -504,10 +518,10 @@ export class InstanceManager {
|
|
504
518
|
pid: processInfo.pid ?? -1,
|
505
519
|
health: null,
|
506
520
|
portType: processInfo.portType,
|
507
|
-
status: InstanceStatus.
|
521
|
+
status: InstanceStatus.STARTING,
|
508
522
|
});
|
509
523
|
} catch (e: any) {
|
510
|
-
console.warn('Failed to start instance: ', systemId, instanceId, blockRef, e
|
524
|
+
console.warn('Failed to start instance: ', systemId, instanceId, blockRef, e);
|
511
525
|
const logs: LogEntry[] = [
|
512
526
|
{
|
513
527
|
source: 'stdout',
|
@@ -686,7 +700,12 @@ export class InstanceManager {
|
|
686
700
|
try {
|
687
701
|
await this.start(instance.systemId, instance.instanceId);
|
688
702
|
} catch (e: any) {
|
689
|
-
console.warn(
|
703
|
+
console.warn(
|
704
|
+
'Failed to start previously stopped instance',
|
705
|
+
instance.systemId,
|
706
|
+
instance.instanceId,
|
707
|
+
e
|
708
|
+
);
|
690
709
|
}
|
691
710
|
return;
|
692
711
|
}
|
@@ -738,37 +757,47 @@ export class InstanceManager {
|
|
738
757
|
return InstanceStatus.STOPPED;
|
739
758
|
}
|
740
759
|
const state = await container.status();
|
760
|
+
if (!state) {
|
761
|
+
return InstanceStatus.STOPPED;
|
762
|
+
}
|
741
763
|
|
742
|
-
|
743
|
-
if (state.Health?.Status === 'healthy') {
|
744
|
-
return InstanceStatus.READY;
|
745
|
-
}
|
746
|
-
if (state.Health?.Status === 'starting') {
|
747
|
-
return InstanceStatus.STARTING;
|
748
|
-
}
|
749
|
-
if (state.Health?.Status === 'unhealthy') {
|
750
|
-
return InstanceStatus.UNHEALTHY;
|
751
|
-
}
|
764
|
+
const statusType = state.Status as DockerContainerStatus;
|
752
765
|
|
766
|
+
if (statusType === 'running') {
|
767
|
+
if (state.Health?.Status) {
|
768
|
+
const healthStatusType = state.Health.Status as DockerContainerHealth;
|
769
|
+
if (healthStatusType === 'healthy' || healthStatusType === 'none') {
|
770
|
+
return InstanceStatus.READY;
|
771
|
+
}
|
772
|
+
|
773
|
+
if (healthStatusType === 'starting') {
|
774
|
+
return InstanceStatus.STARTING;
|
775
|
+
}
|
776
|
+
|
777
|
+
if (healthStatusType === 'unhealthy') {
|
778
|
+
return InstanceStatus.UNHEALTHY;
|
779
|
+
}
|
780
|
+
}
|
753
781
|
return InstanceStatus.READY;
|
754
782
|
}
|
755
|
-
|
783
|
+
|
784
|
+
if (statusType === 'created') {
|
756
785
|
return InstanceStatus.STARTING;
|
757
786
|
}
|
758
787
|
|
759
|
-
if (
|
788
|
+
if (statusType === 'exited' || statusType === 'dead') {
|
760
789
|
return InstanceStatus.STOPPED;
|
761
790
|
}
|
762
791
|
|
763
|
-
if (
|
792
|
+
if (statusType === 'removing') {
|
764
793
|
return InstanceStatus.BUSY;
|
765
794
|
}
|
766
795
|
|
767
|
-
if (
|
796
|
+
if (statusType === 'restarting') {
|
768
797
|
return InstanceStatus.BUSY;
|
769
798
|
}
|
770
799
|
|
771
|
-
if (
|
800
|
+
if (statusType === 'paused') {
|
772
801
|
return InstanceStatus.BUSY;
|
773
802
|
}
|
774
803
|
|
package/src/operatorManager.ts
CHANGED
@@ -4,7 +4,13 @@ import md5 from 'md5';
|
|
4
4
|
import { parseKapetaUri } from '@kapeta/nodejs-utils';
|
5
5
|
import { serviceManager } from './serviceManager';
|
6
6
|
import { storageService } from './storageService';
|
7
|
-
import {
|
7
|
+
import {
|
8
|
+
COMPOSE_LABEL_PROJECT,
|
9
|
+
COMPOSE_LABEL_SERVICE,
|
10
|
+
CONTAINER_LABEL_PORT_PREFIX,
|
11
|
+
ContainerInfo,
|
12
|
+
containerManager,
|
13
|
+
} from './containerManager';
|
8
14
|
import FSExtra from 'fs-extra';
|
9
15
|
import { AnyMap, EnvironmentType, OperatorInfo, StringMap } from './types';
|
10
16
|
import { BlockInstance, Resource } from '@kapeta/schemas';
|
@@ -199,8 +205,12 @@ class OperatorManager {
|
|
199
205
|
const PortBindings: { [key: string]: any } = {};
|
200
206
|
const Env: string[] = [];
|
201
207
|
|
208
|
+
const systemUri = parseKapetaUri(systemId);
|
209
|
+
|
202
210
|
const Labels: StringMap = {
|
203
211
|
kapeta: 'true',
|
212
|
+
[COMPOSE_LABEL_PROJECT]: systemUri.id.replace(/[^a-z0-9]/gi, '_'),
|
213
|
+
[COMPOSE_LABEL_SERVICE]: [resourceType, version].join('_').replace(/[^a-z0-9]/gi, '_'),
|
204
214
|
};
|
205
215
|
|
206
216
|
const operatorMetadata = operator.getDefinitionInfo().definition.metadata;
|
package/src/taskManager.ts
CHANGED
@@ -139,7 +139,9 @@ class TaskManager {
|
|
139
139
|
|
140
140
|
socketManager.emitGlobal(EVENT_TASK_ADDED, task.toData());
|
141
141
|
|
142
|
-
this.invokeTask(task).catch(() => {
|
142
|
+
this.invokeTask(task).catch((err) => {
|
143
|
+
console.warn(`Task ${task.id} failed`, err);
|
144
|
+
});
|
143
145
|
|
144
146
|
return task;
|
145
147
|
}
|
@@ -205,6 +207,7 @@ class TaskManager {
|
|
205
207
|
task.future.resolve(result);
|
206
208
|
task.emitUpdate();
|
207
209
|
} catch (e: any) {
|
210
|
+
console.warn(`Task ${task.id} failed while waiting for it to resolve`, e);
|
208
211
|
task.errorMessage = e.message;
|
209
212
|
task.status = TaskStatus.FAILED;
|
210
213
|
task.future.reject(e);
|
@@ -3,12 +3,18 @@ import ClusterConfig, { DefinitionInfo } from '@kapeta/local-cluster-config';
|
|
3
3
|
import { getBindHost, getBlockInstanceContainerName, normalizeKapetaUri, readYML } from './utils';
|
4
4
|
import { KapetaURI, parseKapetaUri } from '@kapeta/nodejs-utils';
|
5
5
|
import { serviceManager } from '../serviceManager';
|
6
|
-
import {
|
6
|
+
import {
|
7
|
+
COMPOSE_LABEL_PROJECT,
|
8
|
+
COMPOSE_LABEL_SERVICE,
|
9
|
+
containerManager,
|
10
|
+
DockerMounts,
|
11
|
+
toLocalBindVolume,
|
12
|
+
} from '../containerManager';
|
7
13
|
import { LogData } from './LogData';
|
8
14
|
import { clusterService } from '../clusterService';
|
9
15
|
import { AnyMap, BlockProcessParams, InstanceType, ProcessInfo, StringMap } from '../types';
|
10
|
-
import { Container } from 'node-docker-api/lib/container';
|
11
16
|
import { definitionsManager } from '../definitionsManager';
|
17
|
+
import Docker from 'dockerode';
|
12
18
|
|
13
19
|
const KIND_BLOCK_TYPE_OPERATOR = 'core/block-type-operator';
|
14
20
|
const KAPETA_SYSTEM_ID = 'KAPETA_SYSTEM_ID';
|
@@ -185,6 +191,8 @@ export class BlockInstanceRunner {
|
|
185
191
|
HealthCheck = containerManager.toDockerHealth({ cmd: localContainer.healthcheck });
|
186
192
|
}
|
187
193
|
|
194
|
+
const systemUri = parseKapetaUri(this._systemId);
|
195
|
+
|
188
196
|
return this.ensureContainer({
|
189
197
|
...dockerOpts,
|
190
198
|
Image: dockerImage,
|
@@ -193,6 +201,8 @@ export class BlockInstanceRunner {
|
|
193
201
|
Labels: {
|
194
202
|
...customLabels,
|
195
203
|
instance: blockInstance.id,
|
204
|
+
[COMPOSE_LABEL_PROJECT]: systemUri.id.replace(/[^a-z0-9]/gi, '_'),
|
205
|
+
[COMPOSE_LABEL_SERVICE]: blockInfo.id.replace(/[^a-z0-9]/gi, '_'),
|
196
206
|
},
|
197
207
|
HealthCheck,
|
198
208
|
ExposedPorts,
|
@@ -250,6 +260,7 @@ export class BlockInstanceRunner {
|
|
250
260
|
|
251
261
|
// For windows we need to default to root
|
252
262
|
const innerHome = process.platform === 'win32' ? '/root/.kapeta' : ClusterConfig.getKapetaBasedir();
|
263
|
+
const systemUri = parseKapetaUri(this._systemId);
|
253
264
|
|
254
265
|
return this.ensureContainer({
|
255
266
|
Image: dockerImage,
|
@@ -257,6 +268,8 @@ export class BlockInstanceRunner {
|
|
257
268
|
ExposedPorts,
|
258
269
|
Labels: {
|
259
270
|
instance: blockInstance.id,
|
271
|
+
[COMPOSE_LABEL_PROJECT]: systemUri.id.replace(/[^a-z0-9]/gi, '_'),
|
272
|
+
[COMPOSE_LABEL_SERVICE]: blockInfo.id.replace(/[^a-z0-9]/gi, '_'),
|
260
273
|
},
|
261
274
|
Env: [
|
262
275
|
...DOCKER_ENV_VARS,
|
@@ -354,6 +367,7 @@ export class BlockInstanceRunner {
|
|
354
367
|
// For windows we need to default to root
|
355
368
|
const innerHome = process.platform === 'win32' ? '/root/.kapeta' : ClusterConfig.getKapetaBasedir();
|
356
369
|
|
370
|
+
const systemUri = parseKapetaUri(this._systemId);
|
357
371
|
logs.addLog(`Creating new container for block: ${containerName}`);
|
358
372
|
const out = await this.ensureContainer({
|
359
373
|
Image: dockerImage,
|
@@ -370,6 +384,8 @@ export class BlockInstanceRunner {
|
|
370
384
|
},
|
371
385
|
Labels: {
|
372
386
|
instance: blockInstance.id,
|
387
|
+
[COMPOSE_LABEL_PROJECT]: systemUri.id.replace(/[^a-z0-9]/gi, '_'),
|
388
|
+
[COMPOSE_LABEL_SERVICE]: blockUri.id.replace(/[^a-z0-9]/gi, '_'),
|
373
389
|
},
|
374
390
|
Env: [
|
375
391
|
`KAPETA_INSTANCE_NAME=${blockInstance.ref}`,
|
@@ -424,7 +440,7 @@ export class BlockInstanceRunner {
|
|
424
440
|
return this._handleContainer(container);
|
425
441
|
}
|
426
442
|
|
427
|
-
private async _handleContainer(container: Container): Promise<ProcessInfo> {
|
443
|
+
private async _handleContainer(container: Docker.Container): Promise<ProcessInfo> {
|
428
444
|
return {
|
429
445
|
type: InstanceType.DOCKER,
|
430
446
|
pid: container.id,
|
package/src/utils/utils.ts
CHANGED
@@ -2,6 +2,9 @@ import FS from 'node:fs';
|
|
2
2
|
import YAML from 'yaml';
|
3
3
|
import { parseKapetaUri } from '@kapeta/nodejs-utils';
|
4
4
|
import md5 from 'md5';
|
5
|
+
import { EntityList } from '@kapeta/schemas';
|
6
|
+
import _ from 'lodash';
|
7
|
+
import { AnyMap } from '../types';
|
5
8
|
|
6
9
|
export function getBlockInstanceContainerName(systemId: string, instanceId: string) {
|
7
10
|
return `kapeta-block-instance-${md5(systemId + instanceId)}`;
|
@@ -47,3 +50,29 @@ export function getBindHost(preferredHost = '127.0.0.1') {
|
|
47
50
|
// TODO: This might pose a security risk - so we should authenticate all requests using a shared secret/nonce that we pass around.
|
48
51
|
return isLinux() ? '0.0.0.0' : preferredHost;
|
49
52
|
}
|
53
|
+
|
54
|
+
export function getResolvedConfiguration(entities?: EntityList, config?: AnyMap, globalConfiguration?: AnyMap): AnyMap {
|
55
|
+
if (!entities || !globalConfiguration) {
|
56
|
+
return config || {};
|
57
|
+
}
|
58
|
+
|
59
|
+
const mergedConfig = config ? _.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
|
+
|
69
|
+
const configPath = type.name + '.' + propertyName;
|
70
|
+
const defaultValue = globalConfiguration ? _.get(globalConfiguration, configPath) : undefined;
|
71
|
+
if (!_.has(mergedConfig, configPath)) {
|
72
|
+
_.set(mergedConfig, configPath, defaultValue);
|
73
|
+
}
|
74
|
+
});
|
75
|
+
});
|
76
|
+
|
77
|
+
return mergedConfig;
|
78
|
+
}
|