@kapeta/local-cluster-service 0.9.0 → 0.10.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.
@@ -128,26 +128,6 @@ export class BlockInstanceRunner {
128
128
  throw new Error(`Missing docker image information: ${JSON.stringify(localContainer)}`);
129
129
  }
130
130
  const containerName = getBlockInstanceContainerName(blockInstance.id);
131
- const logs = new LogData();
132
- logs.addLog(`Starting block ${blockInstance.ref}`);
133
- const containerInfo = await containerManager.getContainerByName(containerName);
134
- let container = containerInfo?.native;
135
- console.log('Starting dev container', containerName);
136
- if (container) {
137
- console.log(`Dev container already exists. Deleting...`);
138
- try {
139
- await container.delete({
140
- force: true,
141
- });
142
- }
143
- catch (e) {
144
- throw new Error('Failed to delete existing container: ' + e.message);
145
- }
146
- container = undefined;
147
- }
148
- logs.addLog(`Creating new container for block: ${containerName}`);
149
- console.log('Creating new dev container', containerName, dockerImage);
150
- await containerManager.pull(dockerImage);
151
131
  const startCmd = localContainer.handlers?.onCreate ? localContainer.handlers.onCreate : '';
152
132
  const dockerOpts = localContainer.options ?? {};
153
133
  const homeDir = localContainer.userHome ? localContainer.userHome : '/root';
@@ -176,8 +156,7 @@ export class BlockInstanceRunner {
176
156
  if (localContainer.healthcheck) {
177
157
  HealthCheck = containerManager.toDockerHealth({ cmd: localContainer.healthcheck });
178
158
  }
179
- console.log('Starting dev container', containerName, dockerImage);
180
- container = await containerManager.startContainer({
159
+ return this.ensureContainer({
181
160
  Image: dockerImage,
182
161
  name: containerName,
183
162
  WorkingDir: workingDir,
@@ -204,8 +183,12 @@ export class BlockInstanceRunner {
204
183
  },
205
184
  ...dockerOpts,
206
185
  });
186
+ }
187
+ async ensureContainer(opts) {
188
+ const logs = new LogData();
189
+ const container = await containerManager.ensureContainer(opts);
207
190
  try {
208
- if (HealthCheck) {
191
+ if (opts.HealthCheck) {
209
192
  await containerManager.waitForHealthy(container);
210
193
  }
211
194
  else {
@@ -239,7 +222,7 @@ export class BlockInstanceRunner {
239
222
  const data = status.data;
240
223
  if (deleteOnExit) {
241
224
  try {
242
- await container.delete();
225
+ await containerManager.remove(container);
243
226
  }
244
227
  catch (e) { }
245
228
  }
@@ -256,7 +239,7 @@ export class BlockInstanceRunner {
256
239
  try {
257
240
  await localContainer.stop();
258
241
  if (deleteOnExit) {
259
- await localContainer.delete();
242
+ await containerManager.remove(localContainer);
260
243
  }
261
244
  }
262
245
  catch (e) { }
@@ -283,45 +266,23 @@ export class BlockInstanceRunner {
283
266
  }
284
267
  const containerName = getBlockInstanceContainerName(blockInstance.id);
285
268
  const logs = new LogData();
286
- const containerInfo = await containerManager.getContainerByName(containerName);
287
- let container = containerInfo?.native;
288
269
  // For windows we need to default to root
289
270
  const innerHome = process.platform === 'win32' ? '/root/.kapeta' : ClusterConfig.getKapetaBasedir();
290
- if (container) {
291
- const containerData = container.data;
292
- if (containerData.State === 'running') {
293
- logs.addLog(`Found existing running container for block: ${containerName}`);
294
- }
295
- else {
296
- logs.addLog(`Found existing container for block: ${containerName}. Starting now`);
297
- await container.start();
298
- }
299
- }
300
- else {
301
- logs.addLog(`Creating new container for block: ${containerName}`);
302
- container = await containerManager.startContainer({
303
- Image: dockerImage,
304
- name: containerName,
305
- Labels: {
306
- instance: blockInstance.id,
307
- },
308
- Env: [
309
- ...DOCKER_ENV_VARS,
310
- `KAPETA_LOCAL_CLUSTER_PORT=${clusterService.getClusterServicePort()}`,
311
- ...Object.entries(env).map(([key, value]) => `${key}=${value}`),
312
- ],
313
- HostConfig: {
314
- Binds: [`${toLocalBindVolume(ClusterConfig.getKapetaBasedir())}:${innerHome}`],
315
- },
316
- });
317
- try {
318
- await containerManager.waitForReady(container);
319
- }
320
- catch (e) {
321
- logs.addLog(e.message, 'ERROR');
322
- }
323
- }
324
- return this._handleContainer(container, logs);
271
+ return this.ensureContainer({
272
+ Image: dockerImage,
273
+ name: containerName,
274
+ Labels: {
275
+ instance: blockInstance.id,
276
+ },
277
+ Env: [
278
+ ...DOCKER_ENV_VARS,
279
+ `KAPETA_LOCAL_CLUSTER_PORT=${clusterService.getClusterServicePort()}`,
280
+ ...Object.entries(env).map(([key, value]) => `${key}=${value}`),
281
+ ],
282
+ HostConfig: {
283
+ Binds: [`${toLocalBindVolume(ClusterConfig.getKapetaBasedir())}:${innerHome}`],
284
+ },
285
+ });
325
286
  }
326
287
  /**
327
288
  *
@@ -344,120 +305,68 @@ export class BlockInstanceRunner {
344
305
  throw new Error(`Provider did not have local image: ${providerRef}`);
345
306
  }
346
307
  const dockerImage = spec?.local?.image;
347
- try {
348
- await containerManager.pull(dockerImage);
349
- }
350
- catch (e) {
351
- console.warn('Failed to pull image. Continuing...', e);
352
- }
353
308
  const containerName = getBlockInstanceContainerName(blockInstance.id);
354
309
  const logs = new LogData();
355
- const containerInfo = await containerManager.getContainerByName(containerName);
356
- let container = containerInfo?.native;
357
- if (container) {
358
- const containerData = container.data;
359
- if (containerData.State === 'running') {
360
- logs.addLog(`Found existing running container for block: ${containerName}`);
361
- }
362
- else {
363
- if (containerData.State?.ExitCode > 0) {
364
- logs.addLog(`Container exited with code: ${containerData.State.ExitCode}. Deleting...`);
365
- try {
366
- await container.delete();
367
- }
368
- catch (e) { }
369
- container = undefined;
370
- }
371
- else {
372
- logs.addLog(`Found existing container for block: ${containerName}. Starting now`);
373
- try {
374
- await container.start();
375
- }
376
- catch (e) {
377
- console.warn('Failed to start container. Deleting...', e);
378
- try {
379
- await container.delete();
380
- }
381
- catch (e) { }
382
- container = undefined;
383
- }
384
- }
385
- }
386
- }
387
310
  const bindHost = getBindHost();
388
- if (!container) {
389
- const ExposedPorts = {};
390
- const addonEnv = {};
391
- const PortBindings = {};
392
- let HealthCheck = undefined;
393
- let Mounts = [];
394
- const promises = Object.entries(spec.local.ports).map(async ([portType, value]) => {
395
- const dockerPort = `${value.port}/${value.type}`;
396
- ExposedPorts[dockerPort] = {};
397
- addonEnv[`KAPETA_LOCAL_SERVER_PORT_${portType.toUpperCase()}`] = value.port;
398
- const publicPort = await serviceManager.ensureServicePort(this._systemId, blockInstance.id, portType);
399
- PortBindings[dockerPort] = [
400
- {
401
- HostIp: bindHost,
402
- HostPort: `${publicPort}`,
403
- },
404
- ];
405
- });
406
- await Promise.all(promises);
407
- if (spec.local?.env) {
408
- Object.entries(spec.local.env).forEach(([key, value]) => {
409
- addonEnv[key] = value;
410
- });
411
- }
412
- if (spec.local?.mounts) {
413
- const mounts = containerManager.createMounts(blockUri.id, spec.local.mounts);
414
- Mounts = containerManager.toDockerMounts(mounts);
415
- }
416
- if (spec.local?.health) {
417
- HealthCheck = containerManager.toDockerHealth(spec.local?.health);
418
- }
419
- // For windows we need to default to root
420
- const innerHome = process.platform === 'win32' ? '/root/.kapeta' : ClusterConfig.getKapetaBasedir();
421
- logs.addLog(`Creating new container for block: ${containerName}`);
422
- container = await containerManager.startContainer({
423
- Image: dockerImage,
424
- name: containerName,
425
- ExposedPorts,
426
- HealthCheck,
427
- HostConfig: {
428
- Binds: [
429
- `${toLocalBindVolume(kapetaYmlPath)}:/kapeta.yml:ro`,
430
- `${toLocalBindVolume(ClusterConfig.getKapetaBasedir())}:${innerHome}`,
431
- ],
432
- PortBindings,
433
- Mounts,
434
- },
435
- Labels: {
436
- instance: blockInstance.id,
311
+ const ExposedPorts = {};
312
+ const addonEnv = {};
313
+ const PortBindings = {};
314
+ let HealthCheck = undefined;
315
+ let Mounts = [];
316
+ const promises = Object.entries(spec.local.ports).map(async ([portType, value]) => {
317
+ const dockerPort = `${value.port}/${value.type}`;
318
+ ExposedPorts[dockerPort] = {};
319
+ addonEnv[`KAPETA_LOCAL_SERVER_PORT_${portType.toUpperCase()}`] = value.port;
320
+ const publicPort = await serviceManager.ensureServicePort(this._systemId, blockInstance.id, portType);
321
+ PortBindings[dockerPort] = [
322
+ {
323
+ HostIp: bindHost,
324
+ HostPort: `${publicPort}`,
437
325
  },
438
- Env: [
439
- `KAPETA_INSTANCE_NAME=${blockInstance.ref}`,
440
- `KAPETA_LOCAL_CLUSTER_PORT=${clusterService.getClusterServicePort()}`,
441
- ...DOCKER_ENV_VARS,
442
- ...Object.entries({
443
- ...env,
444
- ...addonEnv,
445
- }).map(([key, value]) => `${key}=${value}`),
446
- ],
326
+ ];
327
+ });
328
+ await Promise.all(promises);
329
+ if (spec.local?.env) {
330
+ Object.entries(spec.local.env).forEach(([key, value]) => {
331
+ addonEnv[key] = value;
447
332
  });
448
- try {
449
- if (HealthCheck) {
450
- await containerManager.waitForHealthy(container);
451
- }
452
- else {
453
- await containerManager.waitForReady(container);
454
- }
455
- }
456
- catch (e) {
457
- logs.addLog(e.message, 'ERROR');
458
- }
459
333
  }
460
- const out = await this._handleContainer(container, logs, true);
334
+ if (spec.local?.mounts) {
335
+ const mounts = containerManager.createMounts(blockUri.id, spec.local.mounts);
336
+ Mounts = containerManager.toDockerMounts(mounts);
337
+ }
338
+ if (spec.local?.health) {
339
+ HealthCheck = containerManager.toDockerHealth(spec.local?.health);
340
+ }
341
+ // For windows we need to default to root
342
+ const innerHome = process.platform === 'win32' ? '/root/.kapeta' : ClusterConfig.getKapetaBasedir();
343
+ logs.addLog(`Creating new container for block: ${containerName}`);
344
+ const out = await this.ensureContainer({
345
+ Image: dockerImage,
346
+ name: containerName,
347
+ ExposedPorts,
348
+ HealthCheck,
349
+ HostConfig: {
350
+ Binds: [
351
+ `${toLocalBindVolume(kapetaYmlPath)}:/kapeta.yml:ro`,
352
+ `${toLocalBindVolume(ClusterConfig.getKapetaBasedir())}:${innerHome}`,
353
+ ],
354
+ PortBindings,
355
+ Mounts,
356
+ },
357
+ Labels: {
358
+ instance: blockInstance.id,
359
+ },
360
+ Env: [
361
+ `KAPETA_INSTANCE_NAME=${blockInstance.ref}`,
362
+ `KAPETA_LOCAL_CLUSTER_PORT=${clusterService.getClusterServicePort()}`,
363
+ ...DOCKER_ENV_VARS,
364
+ ...Object.entries({
365
+ ...env,
366
+ ...addonEnv,
367
+ }).map(([key, value]) => `${key}=${value}`),
368
+ ],
369
+ });
461
370
  const portTypes = spec.local.ports ? Object.keys(spec.local.ports) : [];
462
371
  if (portTypes.length > 0) {
463
372
  out.portType = portTypes[0];
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kapeta/local-cluster-service",
3
- "version": "0.9.0",
3
+ "version": "0.10.0",
4
4
  "description": "Manages configuration, ports and service discovery for locally running Kapeta systems",
5
5
  "type": "commonjs",
6
6
  "exports": {
@@ -7,7 +7,8 @@ import { Docker } from 'node-docker-api';
7
7
  import { parseKapetaUri } from '@kapeta/nodejs-utils';
8
8
  import ClusterConfiguration from '@kapeta/local-cluster-config';
9
9
  import { Container } from 'node-docker-api/lib/container';
10
- import { getBindHost } from './utils/utils';
10
+ import uuid from 'node-uuid';
11
+ import md5 from 'md5';
11
12
 
12
13
  type StringMap = { [key: string]: string };
13
14
 
@@ -53,7 +54,7 @@ interface Health {
53
54
  retries?: number;
54
55
  }
55
56
 
56
- const LABEL_PORT_PREFIX = 'kapeta_port-';
57
+ export const CONTAINER_LABEL_PORT_PREFIX = 'kapeta_port-';
57
58
  const NANO_SECOND = 1000000;
58
59
  const HEALTH_CHECK_INTERVAL = 3000;
59
60
  const HEALTH_CHECK_MAX = 20;
@@ -205,24 +206,21 @@ class ContainerManager {
205
206
  tag = 'latest';
206
207
  }
207
208
 
208
- if (tag !== 'latest') {
209
- if (IMAGE_PULL_CACHE[image]) {
210
- const timeSince = Date.now() - IMAGE_PULL_CACHE[image];
211
- if (timeSince < cacheForMS) {
212
- return;
213
- }
209
+ if (IMAGE_PULL_CACHE[image]) {
210
+ const timeSince = Date.now() - IMAGE_PULL_CACHE[image];
211
+ if (timeSince < cacheForMS) {
212
+ return false;
214
213
  }
214
+ }
215
215
 
216
- const imageTagList = (await this.docker().image.list())
217
- .map((image) => image.data as any)
218
- .filter((imageData) => !!imageData.RepoTags)
219
- .map((imageData) => imageData.RepoTags as string[]);
216
+ const imageTagList = (await this.docker().image.list())
217
+ .map((image) => image.data as any)
218
+ .filter((imageData) => !!imageData.RepoTags)
219
+ .map((imageData) => imageData.RepoTags as string[]);
220
220
 
221
- if (imageTagList.some((imageTags) => imageTags.indexOf(image) > -1)) {
222
- console.log('Image found: %s', image);
223
- return;
224
- }
225
- console.log('Image not found: %s', image);
221
+ if (imageTagList.some((imageTags) => imageTags.indexOf(image) > -1)) {
222
+ console.log('Image found: %s', image);
223
+ return false;
226
224
  }
227
225
 
228
226
  console.log('Pulling image: %s', image);
@@ -239,6 +237,8 @@ class ContainerManager {
239
237
  IMAGE_PULL_CACHE[image] = Date.now();
240
238
 
241
239
  console.log('Image pulled: %s', image);
240
+
241
+ return true;
242
242
  }
243
243
 
244
244
  toDockerMounts(mounts: StringMap) {
@@ -265,66 +265,63 @@ class ContainerManager {
265
265
  };
266
266
  }
267
267
 
268
- async run(
269
- image: string,
270
- name: string,
271
- opts: { ports: {}; mounts: {}; env: {}; cmd: string; health: Health }
272
- ): Promise<ContainerInfo> {
273
- const PortBindings: { [key: string]: any } = {};
274
- const Env: string[] = [];
275
- const Labels: StringMap = {
276
- kapeta: 'true',
277
- };
278
-
279
- await this.pull(image);
280
-
281
- const bindHost = getBindHost();
268
+ private applyHash(dockerOpts: any) {
269
+ if (dockerOpts?.Labels?.HASH) {
270
+ delete dockerOpts.Labels.HASH;
271
+ }
282
272
 
283
- const ExposedPorts: { [key: string]: any } = {};
273
+ const hash = md5(JSON.stringify(dockerOpts));
284
274
 
285
- _.forEach(opts.ports, (portInfo: any, containerPort) => {
286
- ExposedPorts['' + containerPort] = {};
287
- PortBindings['' + containerPort] = [
288
- {
289
- HostPort: '' + portInfo.hostPort,
290
- HostIp: bindHost,
291
- },
292
- ];
275
+ if (!dockerOpts.Labels) {
276
+ dockerOpts.Labels = {};
277
+ }
278
+ dockerOpts.Labels.HASH = hash;
279
+ }
293
280
 
294
- Labels[LABEL_PORT_PREFIX + portInfo.hostPort] = portInfo.type;
295
- });
281
+ async ensureContainer(opts: any) {
282
+ let imagePulled = false;
283
+ try {
284
+ imagePulled = await this.pull(opts.Image);
285
+ } catch (e) {
286
+ console.warn('Failed to pull image. Continuing...', e);
287
+ }
296
288
 
297
- const Mounts = this.toDockerMounts(opts.mounts);
289
+ this.applyHash(opts);
290
+ if (!opts.name) {
291
+ console.log('Starting unnamed container: %s', opts.Image);
292
+ return this.startContainer(opts);
293
+ }
294
+ const containerInfo = await this.getContainerByName(opts.name);
295
+ if (imagePulled) {
296
+ console.log('New version of image was pulled: %s', opts.Image);
297
+ } else {
298
+ // If image was pulled always recreate
299
+ if (!containerInfo) {
300
+ console.log('Starting new container: %s', opts.name);
301
+ return this.startContainer(opts);
302
+ }
298
303
 
299
- _.forEach(opts.env, (value, name) => {
300
- Env.push(name + '=' + value);
301
- });
304
+ const containerData = containerInfo.native.data as any;
302
305
 
303
- let HealthCheck = undefined;
304
-
305
- if (opts.health) {
306
- HealthCheck = this.toDockerHealth(opts.health);
307
- }
308
- const dockerContainer = await this.startContainer({
309
- name: name,
310
- Image: image,
311
- Hostname: name + '.kapeta',
312
- Labels,
313
- Cmd: opts.cmd,
314
- ExposedPorts,
315
- Env,
316
- HealthCheck,
317
- HostConfig: {
318
- PortBindings,
319
- Mounts,
320
- },
321
- });
306
+ if (containerData?.Labels?.HASH === opts.Labels.HASH) {
307
+ if (!(await containerInfo.isRunning())) {
308
+ console.log('Starting previously created container: %s', opts.name);
309
+ await containerInfo.start();
310
+ } else {
311
+ console.log('Previously created container already running: %s', opts.name);
312
+ }
313
+ return containerInfo.native;
314
+ }
315
+ }
322
316
 
323
- if (opts.health) {
324
- await this.waitForHealthy(dockerContainer);
317
+ if (containerInfo) {
318
+ // Remove the container and start a new one
319
+ console.log('Replacing previously created container: %s', opts.name);
320
+ await containerInfo.remove({ force: true });
325
321
  }
326
322
 
327
- return new ContainerInfo(dockerContainer);
323
+ console.log('Starting new container: %s', opts.name);
324
+ return this.startContainer(opts);
328
325
  }
329
326
 
330
327
  async startContainer(opts: any) {
@@ -422,6 +419,14 @@ class ContainerManager {
422
419
  }
423
420
  }
424
421
 
422
+ async remove(container: Container, opts?: { force?: boolean }) {
423
+ const newName = 'deleting-' + uuid.v4();
424
+ const containerData = container.data as any;
425
+ // Rename the container first to avoid name conflicts if people start the same container
426
+ await container.rename({ name: newName });
427
+ await container.delete({ force: !!opts?.force });
428
+ }
429
+
425
430
  /**
426
431
  *
427
432
  * @param name
@@ -488,7 +493,7 @@ export class ContainerInfo {
488
493
  }
489
494
 
490
495
  async remove(opts?: { force?: boolean }) {
491
- await this._container.delete({ force: !!opts?.force });
496
+ await containerManager.remove(this._container, opts);
492
497
  }
493
498
 
494
499
  async getPort(type: string) {
@@ -528,11 +533,11 @@ export class ContainerInfo {
528
533
  const ports: PortMap = {};
529
534
 
530
535
  _.forEach(inspectResult.Config.Labels, (portType, name) => {
531
- if (!name.startsWith(LABEL_PORT_PREFIX)) {
536
+ if (!name.startsWith(CONTAINER_LABEL_PORT_PREFIX)) {
532
537
  return;
533
538
  }
534
539
 
535
- const hostPort = name.substr(LABEL_PORT_PREFIX.length);
540
+ const hostPort = name.substr(CONTAINER_LABEL_PORT_PREFIX.length);
536
541
 
537
542
  portTypes[hostPort] = portType;
538
543
  });
@@ -559,7 +559,10 @@ export class InstanceManager {
559
559
  }
560
560
  }
561
561
 
562
- if (instance.desiredStatus === DesiredInstanceStatus.RUN && newStatus === InstanceStatus.STOPPED) {
562
+ if (
563
+ instance.desiredStatus === DesiredInstanceStatus.RUN &&
564
+ [InstanceStatus.STOPPED, InstanceStatus.FAILED, InstanceStatus.STOPPING].includes(newStatus)
565
+ ) {
563
566
  //If the instance is stopped but we want it to run, start it
564
567
  try {
565
568
  await this.start(instance.systemId, instance.instanceId);
@@ -569,7 +572,10 @@ export class InstanceManager {
569
572
  return;
570
573
  }
571
574
 
572
- if (instance.desiredStatus === DesiredInstanceStatus.STOP && newStatus === InstanceStatus.READY) {
575
+ if (
576
+ instance.desiredStatus === DesiredInstanceStatus.STOP &&
577
+ [InstanceStatus.READY, InstanceStatus.STARTING, InstanceStatus.UNHEALTHY].includes(newStatus)
578
+ ) {
573
579
  //If the instance is running but we want it to stop, stop it
574
580
  try {
575
581
  await this.stop(instance.systemId, instance.instanceId);
@@ -4,12 +4,14 @@ 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 { ContainerInfo, containerManager } from './containerManager';
7
+ import { CONTAINER_LABEL_PORT_PREFIX, ContainerInfo, containerManager } from './containerManager';
8
8
  import FSExtra from 'fs-extra';
9
- import { AnyMap, EnvironmentType, OperatorInfo } from './types';
9
+ import { AnyMap, EnvironmentType, OperatorInfo, StringMap } from './types';
10
10
  import { BlockInstance, Resource } from '@kapeta/schemas';
11
11
  import { definitionsManager } from './definitionsManager';
12
- import { normalizeKapetaUri } from './utils/utils';
12
+ import { getBindHost, normalizeKapetaUri } from './utils/utils';
13
+ import _ from 'lodash';
14
+ import { Container } from 'node-docker-api/lib/container';
13
15
 
14
16
  const KIND_OPERATOR = 'core/resource-type-operator';
15
17
 
@@ -191,34 +193,58 @@ class OperatorManager {
191
193
  const mounts = containerManager.createMounts(resourceType, operatorData.mounts);
192
194
 
193
195
  const containerName = containerBaseName + '-' + md5(nameParts.join('_'));
194
- let container = await containerManager.get(containerName);
195
196
 
196
- const isRunning = container ? await container.isRunning() : false;
197
- if (container && !isRunning) {
198
- await container.start();
199
- }
197
+ const PortBindings: { [key: string]: any } = {};
198
+ const Env: string[] = [];
200
199
 
201
- if (!container) {
202
- container = await containerManager.run(operatorData.image, containerName, {
203
- mounts,
204
- ports,
205
- health: operatorData.health,
206
- env: operatorData.env,
207
- cmd: operatorData.cmd,
208
- });
209
- }
200
+ const Labels: StringMap = {
201
+ kapeta: 'true',
202
+ };
210
203
 
211
- try {
212
- if (operatorData.health) {
213
- await containerManager.waitForHealthy(container.native);
214
- } else {
215
- await containerManager.waitForReady(container.native);
216
- }
217
- } catch (e: any) {
218
- console.error(e.message);
204
+ const bindHost = getBindHost();
205
+
206
+ const ExposedPorts: { [key: string]: any } = {};
207
+
208
+ _.forEach(ports, (portInfo: any, containerPort) => {
209
+ ExposedPorts['' + containerPort] = {};
210
+ PortBindings['' + containerPort] = [
211
+ {
212
+ HostPort: '' + portInfo.hostPort,
213
+ HostIp: bindHost,
214
+ },
215
+ ];
216
+
217
+ Labels[CONTAINER_LABEL_PORT_PREFIX + portInfo.hostPort] = portInfo.type;
218
+ });
219
+
220
+ const Mounts = containerManager.toDockerMounts(mounts);
221
+
222
+ _.forEach(operatorData.env, (value, name) => {
223
+ Env.push(name + '=' + value);
224
+ });
225
+
226
+ let HealthCheck = undefined;
227
+
228
+ if (operatorData.health) {
229
+ HealthCheck = containerManager.toDockerHealth(operatorData.health);
219
230
  }
220
231
 
221
- return container;
232
+ const container = await containerManager.ensureContainer({
233
+ name: containerName,
234
+ Image: operatorData.image,
235
+ Hostname: containerName + '.kapeta',
236
+ Labels,
237
+ Cmd: operatorData.cmd,
238
+ ExposedPorts,
239
+ Env,
240
+ HealthCheck,
241
+ HostConfig: {
242
+ PortBindings,
243
+ Mounts,
244
+ },
245
+ });
246
+
247
+ return new ContainerInfo(container);
222
248
  }
223
249
  }
224
250