@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.
@@ -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, instanceConfig);
395
- instance.status = types_1.InstanceStatus.READY;
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.READY,
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.message);
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.Status === 'running') {
598
- if (state.Health?.Status === 'healthy') {
599
- return types_1.InstanceStatus.READY;
600
- }
601
- if (state.Health?.Status === 'starting') {
602
- return types_1.InstanceStatus.STARTING;
603
- }
604
- if (state.Health?.Status === 'unhealthy') {
605
- return types_1.InstanceStatus.UNHEALTHY;
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 (state.Status === 'created') {
620
+ if (statusType === 'created') {
610
621
  return types_1.InstanceStatus.STARTING;
611
622
  }
612
- if (state.Status === 'exited' || state.Status === 'dead') {
623
+ if (statusType === 'exited' || statusType === 'dead') {
613
624
  return types_1.InstanceStatus.STOPPED;
614
625
  }
615
- if (state.Status === 'removing') {
626
+ if (statusType === 'removing') {
616
627
  return types_1.InstanceStatus.BUSY;
617
628
  }
618
- if (state.Status === 'restarting') {
629
+ if (statusType === 'restarting') {
619
630
  return types_1.InstanceStatus.BUSY;
620
631
  }
621
- if (state.Status === 'paused') {
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,3 +1,4 @@
1
+ export declare const SYSTEM_ID = "$plan";
1
2
  type AnyMap = {
2
3
  [key: string]: any;
3
4
  };
@@ -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 { Docker } from 'node-docker-api';
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
- interface DockerState {
24
- Status: 'created' | 'running' | 'paused' | 'restarting' | 'removing' | 'exited' | 'dead';
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(opts: any): Promise<Container>;
77
- waitForReady(container: Container, attempt?: number): Promise<void>;
78
- _isReady(container: Container): Promise<any>;
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 | null>;
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<any>;
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<any>;
122
- status(): Promise<DockerState>;
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[]>;