@kapeta/local-cluster-service 0.9.1 → 0.10.1

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 CHANGED
@@ -1,3 +1,17 @@
1
+ ## [0.10.1](https://github.com/kapetacom/local-cluster-service/compare/v0.10.0...v0.10.1) (2023-07-27)
2
+
3
+
4
+ ### Bug Fixes
5
+
6
+ * Include port bindings for non-local containers ([#51](https://github.com/kapetacom/local-cluster-service/issues/51)) ([64fd440](https://github.com/kapetacom/local-cluster-service/commit/64fd4409ea9e2dda8e2438d0ec85a8f5a2092b1e))
7
+
8
+ # [0.10.0](https://github.com/kapetacom/local-cluster-service/compare/v0.9.1...v0.10.0) (2023-07-26)
9
+
10
+
11
+ ### Features
12
+
13
+ * Auto-reuse containers ([#50](https://github.com/kapetacom/local-cluster-service/issues/50)) ([ecb396b](https://github.com/kapetacom/local-cluster-service/commit/ecb396b541f9184302e0681f4803d2404336138e))
14
+
1
15
  ## [0.9.1](https://github.com/kapetacom/local-cluster-service/compare/v0.9.0...v0.9.1) (2023-07-26)
2
16
 
3
17
 
@@ -41,6 +41,7 @@ interface Health {
41
41
  timeout?: number;
42
42
  retries?: number;
43
43
  }
44
+ export declare const CONTAINER_LABEL_PORT_PREFIX = "kapeta_port-";
44
45
  export declare const HEALTH_CHECK_TIMEOUT: number;
45
46
  declare class ContainerManager {
46
47
  private _docker;
@@ -56,7 +57,7 @@ declare class ContainerManager {
56
57
  ping(): Promise<void>;
57
58
  docker(): Docker;
58
59
  getContainerByName(containerName: string): Promise<ContainerInfo | undefined>;
59
- pull(image: string, cacheForMS?: number): Promise<void>;
60
+ pull(image: string, cacheForMS?: number): Promise<boolean>;
60
61
  toDockerMounts(mounts: StringMap): DockerMounts[];
61
62
  toDockerHealth(health: Health): {
62
63
  Test: string[];
@@ -64,13 +65,8 @@ declare class ContainerManager {
64
65
  Timeout: number;
65
66
  Retries: number;
66
67
  };
67
- run(image: string, name: string, opts: {
68
- ports: {};
69
- mounts: {};
70
- env: {};
71
- cmd: string;
72
- health: Health;
73
- }): Promise<ContainerInfo>;
68
+ private applyHash;
69
+ ensureContainer(opts: any): Promise<Container>;
74
70
  startContainer(opts: any): Promise<Container>;
75
71
  waitForReady(container: Container, attempt?: number): Promise<void>;
76
72
  waitForHealthy(container: Container, attempt?: number): Promise<void>;
@@ -3,7 +3,7 @@ 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.containerManager = exports.toLocalBindVolume = exports.getExtraHosts = exports.ContainerInfo = exports.HEALTH_CHECK_TIMEOUT = void 0;
6
+ exports.containerManager = exports.toLocalBindVolume = exports.getExtraHosts = exports.ContainerInfo = exports.HEALTH_CHECK_TIMEOUT = exports.CONTAINER_LABEL_PORT_PREFIX = void 0;
7
7
  const path_1 = __importDefault(require("path"));
8
8
  const storageService_1 = require("./storageService");
9
9
  const os_1 = __importDefault(require("os"));
@@ -12,9 +12,9 @@ const fs_extra_1 = __importDefault(require("fs-extra"));
12
12
  const node_docker_api_1 = require("node-docker-api");
13
13
  const nodejs_utils_1 = require("@kapeta/nodejs-utils");
14
14
  const local_cluster_config_1 = __importDefault(require("@kapeta/local-cluster-config"));
15
- const utils_1 = require("./utils/utils");
16
15
  const node_uuid_1 = __importDefault(require("node-uuid"));
17
- const LABEL_PORT_PREFIX = 'kapeta_port-';
16
+ const md5_1 = __importDefault(require("md5"));
17
+ exports.CONTAINER_LABEL_PORT_PREFIX = 'kapeta_port-';
18
18
  const NANO_SECOND = 1000000;
19
19
  const HEALTH_CHECK_INTERVAL = 3000;
20
20
  const HEALTH_CHECK_MAX = 20;
@@ -147,22 +147,19 @@ class ContainerManager {
147
147
  if (!tag) {
148
148
  tag = 'latest';
149
149
  }
150
- if (tag !== 'latest') {
151
- if (IMAGE_PULL_CACHE[image]) {
152
- const timeSince = Date.now() - IMAGE_PULL_CACHE[image];
153
- if (timeSince < cacheForMS) {
154
- return;
155
- }
156
- }
157
- const imageTagList = (await this.docker().image.list())
158
- .map((image) => image.data)
159
- .filter((imageData) => !!imageData.RepoTags)
160
- .map((imageData) => imageData.RepoTags);
161
- if (imageTagList.some((imageTags) => imageTags.indexOf(image) > -1)) {
162
- console.log('Image found: %s', image);
163
- return;
150
+ if (IMAGE_PULL_CACHE[image]) {
151
+ const timeSince = Date.now() - IMAGE_PULL_CACHE[image];
152
+ if (timeSince < cacheForMS) {
153
+ return false;
164
154
  }
165
- console.log('Image not found: %s', image);
155
+ }
156
+ const imageTagList = (await this.docker().image.list())
157
+ .map((image) => image.data)
158
+ .filter((imageData) => !!imageData.RepoTags)
159
+ .map((imageData) => imageData.RepoTags);
160
+ if (imageTagList.some((imageTags) => imageTags.indexOf(image) > -1)) {
161
+ console.log('Image found: %s', image);
162
+ return false;
166
163
  }
167
164
  console.log('Pulling image: %s', image);
168
165
  await this.docker()
@@ -173,6 +170,7 @@ class ContainerManager {
173
170
  .then((stream) => promisifyStream(stream));
174
171
  IMAGE_PULL_CACHE[image] = Date.now();
175
172
  console.log('Image pulled: %s', image);
173
+ return true;
176
174
  }
177
175
  toDockerMounts(mounts) {
178
176
  const Mounts = [];
@@ -195,51 +193,58 @@ class ContainerManager {
195
193
  Retries: health.retries || 10,
196
194
  };
197
195
  }
198
- async run(image, name, opts) {
199
- const PortBindings = {};
200
- const Env = [];
201
- const Labels = {
202
- kapeta: 'true',
203
- };
204
- await this.pull(image);
205
- const bindHost = (0, utils_1.getBindHost)();
206
- const ExposedPorts = {};
207
- lodash_1.default.forEach(opts.ports, (portInfo, containerPort) => {
208
- ExposedPorts['' + containerPort] = {};
209
- PortBindings['' + containerPort] = [
210
- {
211
- HostPort: '' + portInfo.hostPort,
212
- HostIp: bindHost,
213
- },
214
- ];
215
- Labels[LABEL_PORT_PREFIX + portInfo.hostPort] = portInfo.type;
216
- });
217
- const Mounts = this.toDockerMounts(opts.mounts);
218
- lodash_1.default.forEach(opts.env, (value, name) => {
219
- Env.push(name + '=' + value);
220
- });
221
- let HealthCheck = undefined;
222
- if (opts.health) {
223
- HealthCheck = this.toDockerHealth(opts.health);
224
- }
225
- const dockerContainer = await this.startContainer({
226
- name: name,
227
- Image: image,
228
- Hostname: name + '.kapeta',
229
- Labels,
230
- Cmd: opts.cmd,
231
- ExposedPorts,
232
- Env,
233
- HealthCheck,
234
- HostConfig: {
235
- PortBindings,
236
- Mounts,
237
- },
238
- });
239
- if (opts.health) {
240
- await this.waitForHealthy(dockerContainer);
196
+ applyHash(dockerOpts) {
197
+ if (dockerOpts?.Labels?.HASH) {
198
+ delete dockerOpts.Labels.HASH;
241
199
  }
242
- return new ContainerInfo(dockerContainer);
200
+ const hash = (0, md5_1.default)(JSON.stringify(dockerOpts));
201
+ if (!dockerOpts.Labels) {
202
+ dockerOpts.Labels = {};
203
+ }
204
+ dockerOpts.Labels.HASH = hash;
205
+ }
206
+ async ensureContainer(opts) {
207
+ let imagePulled = false;
208
+ try {
209
+ imagePulled = await this.pull(opts.Image);
210
+ }
211
+ catch (e) {
212
+ console.warn('Failed to pull image. Continuing...', e);
213
+ }
214
+ this.applyHash(opts);
215
+ if (!opts.name) {
216
+ console.log('Starting unnamed container: %s', opts.Image);
217
+ return this.startContainer(opts);
218
+ }
219
+ const containerInfo = await this.getContainerByName(opts.name);
220
+ if (imagePulled) {
221
+ console.log('New version of image was pulled: %s', opts.Image);
222
+ }
223
+ else {
224
+ // If image was pulled always recreate
225
+ if (!containerInfo) {
226
+ console.log('Starting new container: %s', opts.name);
227
+ return this.startContainer(opts);
228
+ }
229
+ const containerData = containerInfo.native.data;
230
+ if (containerData?.Labels?.HASH === opts.Labels.HASH) {
231
+ if (!(await containerInfo.isRunning())) {
232
+ console.log('Starting previously created container: %s', opts.name);
233
+ await containerInfo.start();
234
+ }
235
+ else {
236
+ console.log('Previously created container already running: %s', opts.name);
237
+ }
238
+ return containerInfo.native;
239
+ }
240
+ }
241
+ if (containerInfo) {
242
+ // Remove the container and start a new one
243
+ console.log('Replacing previously created container: %s', opts.name);
244
+ await containerInfo.remove({ force: true });
245
+ }
246
+ console.log('Starting new container: %s', opts.name);
247
+ return this.startContainer(opts);
243
248
  }
244
249
  async startContainer(opts) {
245
250
  const extraHosts = getExtraHosts(this._version);
@@ -417,10 +422,10 @@ class ContainerInfo {
417
422
  const portTypes = {};
418
423
  const ports = {};
419
424
  lodash_1.default.forEach(inspectResult.Config.Labels, (portType, name) => {
420
- if (!name.startsWith(LABEL_PORT_PREFIX)) {
425
+ if (!name.startsWith(exports.CONTAINER_LABEL_PORT_PREFIX)) {
421
426
  return;
422
427
  }
423
- const hostPort = name.substr(LABEL_PORT_PREFIX.length);
428
+ const hostPort = name.substr(exports.CONTAINER_LABEL_PORT_PREFIX.length);
424
429
  portTypes[hostPort] = portType;
425
430
  });
426
431
  lodash_1.default.forEach(inspectResult.HostConfig.PortBindings, (portBindings, containerPortSpec) => {
@@ -453,7 +453,8 @@ class InstanceManager {
453
453
  changed = true;
454
454
  }
455
455
  }
456
- if (instance.desiredStatus === types_1.DesiredInstanceStatus.RUN && newStatus === types_1.InstanceStatus.STOPPED) {
456
+ if (instance.desiredStatus === types_1.DesiredInstanceStatus.RUN &&
457
+ [types_1.InstanceStatus.STOPPED, types_1.InstanceStatus.FAILED, types_1.InstanceStatus.STOPPING].includes(newStatus)) {
457
458
  //If the instance is stopped but we want it to run, start it
458
459
  try {
459
460
  await this.start(instance.systemId, instance.instanceId);
@@ -463,7 +464,8 @@ class InstanceManager {
463
464
  }
464
465
  return;
465
466
  }
466
- if (instance.desiredStatus === types_1.DesiredInstanceStatus.STOP && newStatus === types_1.InstanceStatus.READY) {
467
+ if (instance.desiredStatus === types_1.DesiredInstanceStatus.STOP &&
468
+ [types_1.InstanceStatus.READY, types_1.InstanceStatus.STARTING, types_1.InstanceStatus.UNHEALTHY].includes(newStatus)) {
467
469
  //If the instance is running but we want it to stop, stop it
468
470
  try {
469
471
  await this.stop(instance.systemId, instance.instanceId);
@@ -13,6 +13,7 @@ const containerManager_1 = require("./containerManager");
13
13
  const fs_extra_1 = __importDefault(require("fs-extra"));
14
14
  const definitionsManager_1 = require("./definitionsManager");
15
15
  const utils_1 = require("./utils/utils");
16
+ const lodash_1 = __importDefault(require("lodash"));
16
17
  const KIND_OPERATOR = 'core/resource-type-operator';
17
18
  class Operator {
18
19
  _data;
@@ -138,32 +139,46 @@ class OperatorManager {
138
139
  }
139
140
  const mounts = containerManager_1.containerManager.createMounts(resourceType, operatorData.mounts);
140
141
  const containerName = containerBaseName + '-' + (0, md5_1.default)(nameParts.join('_'));
141
- let container = await containerManager_1.containerManager.get(containerName);
142
- const isRunning = container ? await container.isRunning() : false;
143
- if (container && !isRunning) {
144
- await container.start();
145
- }
146
- if (!container) {
147
- container = await containerManager_1.containerManager.run(operatorData.image, containerName, {
148
- mounts,
149
- ports,
150
- health: operatorData.health,
151
- env: operatorData.env,
152
- cmd: operatorData.cmd,
153
- });
154
- }
155
- try {
156
- if (operatorData.health) {
157
- await containerManager_1.containerManager.waitForHealthy(container.native);
158
- }
159
- else {
160
- await containerManager_1.containerManager.waitForReady(container.native);
161
- }
162
- }
163
- catch (e) {
164
- console.error(e.message);
142
+ const PortBindings = {};
143
+ const Env = [];
144
+ const Labels = {
145
+ kapeta: 'true',
146
+ };
147
+ const bindHost = (0, utils_1.getBindHost)();
148
+ const ExposedPorts = {};
149
+ lodash_1.default.forEach(ports, (portInfo, containerPort) => {
150
+ ExposedPorts['' + containerPort] = {};
151
+ PortBindings['' + containerPort] = [
152
+ {
153
+ HostPort: '' + portInfo.hostPort,
154
+ HostIp: bindHost,
155
+ },
156
+ ];
157
+ Labels[containerManager_1.CONTAINER_LABEL_PORT_PREFIX + portInfo.hostPort] = portInfo.type;
158
+ });
159
+ const Mounts = containerManager_1.containerManager.toDockerMounts(mounts);
160
+ lodash_1.default.forEach(operatorData.env, (value, name) => {
161
+ Env.push(name + '=' + value);
162
+ });
163
+ let HealthCheck = undefined;
164
+ if (operatorData.health) {
165
+ HealthCheck = containerManager_1.containerManager.toDockerHealth(operatorData.health);
165
166
  }
166
- return container;
167
+ const container = await containerManager_1.containerManager.ensureContainer({
168
+ name: containerName,
169
+ Image: operatorData.image,
170
+ Hostname: containerName + '.kapeta',
171
+ Labels,
172
+ Cmd: operatorData.cmd,
173
+ ExposedPorts,
174
+ Env,
175
+ HealthCheck,
176
+ HostConfig: {
177
+ PortBindings,
178
+ Mounts,
179
+ },
180
+ });
181
+ return new containerManager_1.ContainerInfo(container);
167
182
  }
168
183
  }
169
184
  exports.operatorManager = new OperatorManager();
@@ -14,7 +14,6 @@ export declare class BlockInstanceRunner {
14
14
  * Starts local process
15
15
  */
16
16
  private _startLocalProcess;
17
- private _handleContainer;
18
17
  private _startDockerProcess;
19
18
  /**
20
19
  *
@@ -26,4 +25,7 @@ export declare class BlockInstanceRunner {
26
25
  * @private
27
26
  */
28
27
  _startOperatorProcess(blockInstance: BlockProcessParams, blockUri: KapetaURI, providerDefinition: DefinitionInfo, env: StringMap): Promise<ProcessInfo>;
28
+ private getDockerPortBindings;
29
+ private ensureContainer;
30
+ private _handleContainer;
29
31
  }