@kapeta/local-cluster-service 0.35.0 → 0.36.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.
@@ -22,6 +22,7 @@ import { AnyMap, BlockProcessParams, InstanceType, LocalImageOptions, ProcessInf
22
22
  import { definitionsManager } from '../definitionsManager';
23
23
  import Docker from 'dockerode';
24
24
  import OS from 'node:os';
25
+ import { taskManager } from '../taskManager';
25
26
 
26
27
  const KIND_BLOCK_TYPE_OPERATOR = 'core/block-type-operator';
27
28
  const KAPETA_SYSTEM_ID = 'KAPETA_SYSTEM_ID';
@@ -201,7 +202,7 @@ export class BlockInstanceRunner {
201
202
  throw new Error(`Missing docker image information: ${JSON.stringify(localContainer)}`);
202
203
  }
203
204
 
204
- const containerName = getBlockInstanceContainerName(this._systemId, blockInstance.id);
205
+ const containerName = await getBlockInstanceContainerName(this._systemId, blockInstance.id, targetKindUri.id);
205
206
  const startCmd = localContainer.handlers?.onCreate ? localContainer.handlers.onCreate : '';
206
207
  const dockerOpts = localContainer.options ?? {};
207
208
  const homeDir = localContainer.userHome ? localContainer.userHome : '/root';
@@ -322,7 +323,7 @@ export class BlockInstanceRunner {
322
323
  providerVersion
323
324
  );
324
325
 
325
- const containerName = getBlockInstanceContainerName(this._systemId, blockInstance.id);
326
+ const containerName = await getBlockInstanceContainerName(this._systemId, blockInstance.id, kindUri.id);
326
327
 
327
328
  // For windows we need to default to root
328
329
  const innerHome = process.platform === 'win32' ? '/root/.kapeta' : ClusterConfig.getKapetaBasedir();
@@ -352,16 +353,7 @@ export class BlockInstanceRunner {
352
353
  });
353
354
  }
354
355
 
355
- /**
356
- *
357
- * @param blockInstance
358
- * @param blockUri
359
- * @param providerDefinition
360
- * @param {{[key:string]:string}} env
361
- * @return {Promise<ProcessDetails>}
362
- * @private
363
- */
364
- async _startOperatorProcess(
356
+ private async _startOperatorProcess(
365
357
  blockInstance: BlockProcessParams,
366
358
  blockUri: KapetaURI,
367
359
  providerDefinition: DefinitionInfo,
@@ -388,93 +380,121 @@ export class BlockInstanceRunner {
388
380
  const local = spec.local as LocalImageOptions;
389
381
 
390
382
  const dockerImage = local.image;
383
+ const operatorUri = local.singleton ? parseKapetaUri(providerRef) : blockUri;
384
+ const operatorId = local.singleton ? providerRef : blockInstance.id;
385
+ const operatorRef = local.singleton ? providerRef : blockInstance.ref;
391
386
 
392
- //We only want 1 operator per operator type - across all local systems
393
- const containerName = getBlockInstanceContainerName(this._systemId, blockInstance.id);
394
- const logs = new LogData();
395
-
396
- const bindHost = getBindHost();
397
-
398
- const ExposedPorts: AnyMap = {};
399
- const addonEnv: StringMap = {};
400
- const PortBindings: AnyMap = {};
401
- let HealthCheck = undefined;
402
- let Mounts: DockerMounts[] = [];
403
- const localPorts = local.ports ?? {};
404
- const labels: { [key: string]: string } = {};
405
- const promises = Object.entries(localPorts).map(async ([portType, value]) => {
406
- const portInfo = toPortInfo(value);
407
- const dockerPort = `${portInfo.port}/${portInfo.type}`;
408
- ExposedPorts[dockerPort] = {};
409
- addonEnv[`KAPETA_LOCAL_SERVER_PORT_${portType.toUpperCase()}`] = `${portInfo.port}`;
410
- const publicPort = await serviceManager.ensureServicePort(this._systemId, blockInstance.id, portType);
411
- PortBindings[dockerPort] = [
412
- {
413
- HostIp: bindHost,
414
- HostPort: `${publicPort}`,
415
- },
416
- ];
417
-
418
- labels[CONTAINER_LABEL_PORT_PREFIX + publicPort] = portType;
419
- });
420
-
421
- await Promise.all(promises);
422
-
423
- if (local.env) {
424
- Object.entries(local.env).forEach(([key, value]) => {
425
- addonEnv[key] = value as string;
426
- });
427
- }
428
-
429
- if (local.mounts) {
430
- Mounts = await containerManager.createVolumes(this._systemId, blockUri.id, local.mounts);
387
+ if (local.singleton && env) {
388
+ env[KAPETA_BLOCK_REF] = operatorRef;
389
+ env[KAPETA_INSTANCE_ID] = operatorId;
431
390
  }
432
391
 
433
- if (local.health) {
434
- HealthCheck = containerManager.toDockerHealth(local.health);
435
- }
436
-
437
- // For windows we need to default to root
438
- const innerHome = process.platform === 'win32' ? '/root/.kapeta' : ClusterConfig.getKapetaBasedir();
439
-
440
- const systemUri = parseKapetaUri(this._systemId);
441
- logs.addLog(`Creating new container for block: ${containerName}`);
442
- const out = await this.ensureContainer({
443
- Image: dockerImage,
444
- name: containerName,
445
- ExposedPorts,
446
- HealthCheck,
447
- HostConfig: {
448
- Binds: [
449
- `${toLocalBindVolume(kapetaYmlPath)}:/kapeta.yml:ro`,
450
- `${toLocalBindVolume(ClusterConfig.getKapetaBasedir())}:${innerHome}`,
451
- ],
452
- PortBindings,
453
- Mounts,
454
- },
455
- Labels: {
456
- ...labels,
457
- instance: blockInstance.id,
458
- [COMPOSE_LABEL_PROJECT]: systemUri.id.replace(/[^a-z0-9]/gi, '_'),
459
- [COMPOSE_LABEL_SERVICE]: blockUri.id.replace(/[^a-z0-9]/gi, '_'),
392
+ const containerName = await getBlockInstanceContainerName(this._systemId, blockInstance.id, providerRef);
393
+
394
+ const task = taskManager.add(
395
+ `container:start:${containerName}`,
396
+ async () => {
397
+ const logs = new LogData();
398
+
399
+ const bindHost = getBindHost();
400
+
401
+ const ExposedPorts: AnyMap = {};
402
+ const addonEnv: StringMap = {};
403
+ const PortBindings: AnyMap = {};
404
+ let HealthCheck = undefined;
405
+ let Mounts: DockerMounts[] = [];
406
+ const localPorts = local.ports ?? {};
407
+ const labels: { [key: string]: string } = {};
408
+ const promises = Object.entries(localPorts).map(async ([portType, value]) => {
409
+ const portInfo = toPortInfo(value);
410
+ const dockerPort = `${portInfo.port}/${portInfo.type}`;
411
+ ExposedPorts[dockerPort] = {};
412
+ addonEnv[`KAPETA_LOCAL_SERVER_PORT_${portType.toUpperCase()}`] = `${portInfo.port}`;
413
+ const publicPort = await serviceManager.ensureServicePort(this._systemId, operatorId, portType);
414
+ PortBindings[dockerPort] = [
415
+ {
416
+ HostIp: bindHost,
417
+ HostPort: `${publicPort}`,
418
+ },
419
+ ];
420
+
421
+ labels[CONTAINER_LABEL_PORT_PREFIX + publicPort] = portType;
422
+ });
423
+
424
+ await Promise.all(promises);
425
+
426
+ if (local.env) {
427
+ Object.entries(local.env).forEach(([key, value]) => {
428
+ addonEnv[key] = value as string;
429
+ });
430
+ }
431
+
432
+ if (local.mounts) {
433
+ Mounts = await containerManager.createVolumes(this._systemId, operatorUri.id, local.mounts);
434
+ }
435
+
436
+ if (local.health) {
437
+ HealthCheck = containerManager.toDockerHealth(local.health);
438
+ }
439
+
440
+ // For windows we need to default to root
441
+ const innerHome = process.platform === 'win32' ? '/root/.kapeta' : ClusterConfig.getKapetaBasedir();
442
+
443
+ const Binds = local.singleton
444
+ ? [`${toLocalBindVolume(ClusterConfig.getKapetaBasedir())}:${innerHome}`]
445
+ : [
446
+ `${toLocalBindVolume(kapetaYmlPath)}:/kapeta.yml:ro`,
447
+ `${toLocalBindVolume(ClusterConfig.getKapetaBasedir())}:${innerHome}`,
448
+ ];
449
+
450
+ const systemUri = parseKapetaUri(this._systemId);
451
+
452
+ console.log(
453
+ `Ensuring container for operator block: ${containerName} [singleton: ${!!local.singleton}]`
454
+ );
455
+
456
+ logs.addLog(`Ensuring container for operator block: ${containerName}`);
457
+ const out = await this.ensureContainer({
458
+ Image: dockerImage,
459
+ name: containerName,
460
+ ExposedPorts,
461
+ HealthCheck,
462
+ HostConfig: {
463
+ Binds,
464
+ PortBindings,
465
+ Mounts,
466
+ },
467
+ Labels: {
468
+ ...labels,
469
+ instance: operatorId,
470
+ [COMPOSE_LABEL_PROJECT]: systemUri.id.replace(/[^a-z0-9]/gi, '_'),
471
+ [COMPOSE_LABEL_SERVICE]: operatorUri.id.replace(/[^a-z0-9]/gi, '_'),
472
+ },
473
+ Env: [
474
+ `KAPETA_INSTANCE_NAME=${operatorRef}`,
475
+ `KAPETA_LOCAL_CLUSTER_PORT=${clusterService.getClusterServicePort()}`,
476
+ ...DOCKER_ENV_VARS,
477
+ ...Object.entries({
478
+ ...env,
479
+ ...addonEnv,
480
+ }).map(([key, value]) => `${key}=${value}`),
481
+ ],
482
+ });
483
+
484
+ const portTypes = local.ports ? Object.keys(local.ports) : [];
485
+ if (portTypes.length > 0) {
486
+ out.portType = portTypes[0];
487
+ }
488
+
489
+ return out;
460
490
  },
461
- Env: [
462
- `KAPETA_INSTANCE_NAME=${blockInstance.ref}`,
463
- `KAPETA_LOCAL_CLUSTER_PORT=${clusterService.getClusterServicePort()}`,
464
- ...DOCKER_ENV_VARS,
465
- ...Object.entries({
466
- ...env,
467
- ...addonEnv,
468
- }).map(([key, value]) => `${key}=${value}`),
469
- ],
470
- });
471
-
472
- const portTypes = local.ports ? Object.keys(local.ports) : [];
473
- if (portTypes.length > 0) {
474
- out.portType = portTypes[0];
475
- }
491
+ {
492
+ name: `Starting container for ${providerRef}`,
493
+ systemId: this._systemId,
494
+ }
495
+ );
476
496
 
477
- return out;
497
+ return task.wait();
478
498
  }
479
499
 
480
500
  private async getDockerPortBindings(
@@ -10,8 +10,34 @@ import { EntityList } from '@kapeta/schemas';
10
10
  import _ from 'lodash';
11
11
  import { AnyMap, PortInfo } from '../types';
12
12
  import ClusterConfiguration from '@kapeta/local-cluster-config';
13
+ import { definitionsManager } from '../definitionsManager';
14
+ import { parseKapetaUri } from '@kapeta/nodejs-utils';
15
+ import { KIND_BLOCK_OPERATOR } from '../operatorManager';
16
+ import { assetManager } from '../assetManager';
17
+
18
+ export async function getBlockInstanceContainerName(systemId: string, instanceId: string, blockType?: string) {
19
+ if (!blockType) {
20
+ const instance = await assetManager.getBlockInstance(systemId, instanceId);
21
+ if (!instance) {
22
+ throw new Error(`Instance ${instanceId} not found in plan ${systemId}`);
23
+ }
24
+ const block = await assetManager.getAsset(instance.block.ref);
25
+ if (!block) {
26
+ throw new Error(`Block ${instance.block.ref} not found`);
27
+ }
28
+ blockType = block.data.kind;
29
+ }
30
+ const typeDefinition = await definitionsManager.getDefinition(blockType);
31
+ if (!typeDefinition) {
32
+ throw new Error(`Block type ${blockType} not found`);
33
+ }
34
+ if (
35
+ parseKapetaUri(typeDefinition.definition.kind).fullName === KIND_BLOCK_OPERATOR &&
36
+ typeDefinition.definition.spec?.local?.singleton
37
+ ) {
38
+ return `kapeta-instance-operator-${md5(systemId + blockType)}`;
39
+ }
13
40
 
14
- export function getBlockInstanceContainerName(systemId: string, instanceId: string) {
15
41
  return `kapeta-block-instance-${md5(systemId + instanceId)}`;
16
42
  }
17
43