@kapeta/local-cluster-service 0.37.0 → 0.39.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.
Files changed (69) hide show
  1. package/CHANGELOG.md +14 -0
  2. package/dist/cjs/index.js +4 -1
  3. package/dist/cjs/src/assetManager.d.ts +2 -1
  4. package/dist/cjs/src/assetManager.js +3 -2
  5. package/dist/cjs/src/config/routes.js +2 -2
  6. package/dist/cjs/src/containerManager.d.ts +6 -3
  7. package/dist/cjs/src/containerManager.js +101 -21
  8. package/dist/cjs/src/instanceManager.d.ts +4 -2
  9. package/dist/cjs/src/instanceManager.js +71 -32
  10. package/dist/cjs/src/operatorManager.d.ts +6 -3
  11. package/dist/cjs/src/operatorManager.js +32 -23
  12. package/dist/cjs/src/progressListener.d.ts +8 -1
  13. package/dist/cjs/src/progressListener.js +12 -1
  14. package/dist/cjs/src/repositoryManager.js +3 -2
  15. package/dist/cjs/src/serviceManager.d.ts +0 -1
  16. package/dist/cjs/src/serviceManager.js +2 -8
  17. package/dist/cjs/src/taskManager.d.ts +2 -0
  18. package/dist/cjs/src/taskManager.js +9 -0
  19. package/dist/cjs/src/types.d.ts +1 -48
  20. package/dist/cjs/src/types.js +2 -1
  21. package/dist/cjs/src/utils/BlockInstanceRunner.js +45 -33
  22. package/dist/cjs/src/utils/InternalConfigProvider.d.ts +38 -0
  23. package/dist/cjs/src/utils/InternalConfigProvider.js +146 -0
  24. package/dist/cjs/src/utils/commandLineUtils.d.ts +2 -1
  25. package/dist/cjs/src/utils/commandLineUtils.js +7 -1
  26. package/dist/cjs/src/utils/utils.d.ts +26 -4
  27. package/dist/cjs/src/utils/utils.js +48 -8
  28. package/dist/esm/index.js +4 -1
  29. package/dist/esm/src/assetManager.d.ts +2 -1
  30. package/dist/esm/src/assetManager.js +3 -2
  31. package/dist/esm/src/config/routes.js +2 -2
  32. package/dist/esm/src/containerManager.d.ts +6 -3
  33. package/dist/esm/src/containerManager.js +101 -21
  34. package/dist/esm/src/instanceManager.d.ts +4 -2
  35. package/dist/esm/src/instanceManager.js +71 -32
  36. package/dist/esm/src/operatorManager.d.ts +6 -3
  37. package/dist/esm/src/operatorManager.js +32 -23
  38. package/dist/esm/src/progressListener.d.ts +8 -1
  39. package/dist/esm/src/progressListener.js +12 -1
  40. package/dist/esm/src/repositoryManager.js +3 -2
  41. package/dist/esm/src/serviceManager.d.ts +0 -1
  42. package/dist/esm/src/serviceManager.js +2 -8
  43. package/dist/esm/src/taskManager.d.ts +2 -0
  44. package/dist/esm/src/taskManager.js +9 -0
  45. package/dist/esm/src/types.d.ts +1 -48
  46. package/dist/esm/src/types.js +2 -1
  47. package/dist/esm/src/utils/BlockInstanceRunner.js +45 -33
  48. package/dist/esm/src/utils/InternalConfigProvider.d.ts +38 -0
  49. package/dist/esm/src/utils/InternalConfigProvider.js +146 -0
  50. package/dist/esm/src/utils/commandLineUtils.d.ts +2 -1
  51. package/dist/esm/src/utils/commandLineUtils.js +7 -1
  52. package/dist/esm/src/utils/utils.d.ts +26 -4
  53. package/dist/esm/src/utils/utils.js +48 -8
  54. package/index.ts +5 -2
  55. package/package.json +16 -14
  56. package/src/assetManager.ts +5 -4
  57. package/src/config/routes.ts +4 -2
  58. package/src/containerManager.ts +115 -26
  59. package/src/instanceManager.ts +86 -44
  60. package/src/operatorManager.ts +48 -40
  61. package/src/progressListener.ts +15 -1
  62. package/src/repositoryManager.ts +5 -3
  63. package/src/serviceManager.ts +3 -11
  64. package/src/taskManager.ts +11 -0
  65. package/src/types.ts +2 -50
  66. package/src/utils/BlockInstanceRunner.ts +60 -44
  67. package/src/utils/InternalConfigProvider.ts +214 -0
  68. package/src/utils/commandLineUtils.ts +10 -2
  69. package/src/utils/utils.ts +53 -10
@@ -5,7 +5,7 @@
5
5
 
6
6
  import FSExtra from 'fs-extra';
7
7
  import ClusterConfig, { DefinitionInfo } from '@kapeta/local-cluster-config';
8
- import { getBindHost, getBlockInstanceContainerName, readYML, toPortInfo } from './utils';
8
+ import { getDockerHostIp, getBlockInstanceContainerName, getOperatorInstancePorts, readYML, toPortInfo } from './utils';
9
9
  import { KapetaURI, parseKapetaUri, normalizeKapetaUri } from '@kapeta/nodejs-utils';
10
10
  import { DEFAULT_PORT_TYPE, HTTP_PORT_TYPE, HTTP_PORTS, serviceManager } from '../serviceManager';
11
11
  import {
@@ -21,16 +21,20 @@ import { clusterService } from '../clusterService';
21
21
  import {
22
22
  AnyMap,
23
23
  BlockProcessParams,
24
+ DOCKER_HOST_INTERNAL,
24
25
  InstanceType,
25
26
  KIND_BLOCK_TYPE_OPERATOR,
26
- LocalImageOptions,
27
27
  ProcessInfo,
28
28
  StringMap,
29
29
  } from '../types';
30
30
  import { definitionsManager } from '../definitionsManager';
31
31
  import Docker from 'dockerode';
32
32
  import OS from 'node:os';
33
+ import Path from 'node:path';
33
34
  import { taskManager } from '../taskManager';
35
+ import { LocalDevContainer, LocalInstance } from '@kapeta/schemas';
36
+ import { createInternalConfigProvider } from './InternalConfigProvider';
37
+ import { resolveKapetaVariables, writeConfigTemplates } from '@kapeta/config-mapper';
34
38
 
35
39
  const KAPETA_SYSTEM_ID = 'KAPETA_SYSTEM_ID';
36
40
  const KAPETA_BLOCK_REF = 'KAPETA_BLOCK_REF';
@@ -42,7 +46,7 @@ const KAPETA_INSTANCE_ID = 'KAPETA_INSTANCE_ID';
42
46
  */
43
47
  const DOCKER_ENV_VARS = [
44
48
  `KAPETA_LOCAL_SERVER=0.0.0.0`,
45
- `KAPETA_LOCAL_CLUSTER_HOST=host.docker.internal`,
49
+ `KAPETA_LOCAL_CLUSTER_HOST=${DOCKER_HOST_INTERNAL}`,
46
50
  `KAPETA_ENVIRONMENT_TYPE=docker`,
47
51
  ];
48
52
 
@@ -111,20 +115,6 @@ export class BlockInstanceRunner {
111
115
  }
112
116
 
113
117
  private async _execute(blockInstance: BlockProcessParams): Promise<ProcessInfo> {
114
- const env: StringMap = {};
115
-
116
- if (this._systemId) {
117
- env[KAPETA_SYSTEM_ID] = this._systemId;
118
- }
119
-
120
- if (blockInstance.ref) {
121
- env[KAPETA_BLOCK_REF] = blockInstance.ref;
122
- }
123
-
124
- if (blockInstance.id) {
125
- env[KAPETA_INSTANCE_ID] = blockInstance.id;
126
- }
127
-
128
118
  const blockUri = parseKapetaUri(blockInstance.ref);
129
119
 
130
120
  if (!blockUri.version) {
@@ -145,18 +135,32 @@ export class BlockInstanceRunner {
145
135
  throw new Error(`Kind not found: ${kindUri.id}`);
146
136
  }
147
137
 
138
+ const baseDir = ClusterConfig.getRepositoryAssetPath(blockUri.handle, blockUri.name, blockUri.version);
139
+ const realBaseDir = await FSExtra.realpath(baseDir);
140
+ const internalConfigProvider = await createInternalConfigProvider(
141
+ this._systemId,
142
+ blockInstance.id,
143
+ assetVersion
144
+ );
145
+
146
+ // Resolve the environment variables
147
+ const envVars = await resolveKapetaVariables(realBaseDir, internalConfigProvider);
148
+
149
+ // Write out the config templates if they exist
150
+ await writeConfigTemplates(envVars, realBaseDir);
151
+
148
152
  let processInfo: ProcessInfo;
149
153
 
150
154
  if (providerVersion.definition.kind === KIND_BLOCK_TYPE_OPERATOR) {
151
- processInfo = await this._startOperatorProcess(blockInstance, blockUri, providerVersion, env);
155
+ processInfo = await this._startOperatorProcess(blockInstance, blockUri, providerVersion, envVars);
152
156
  } else {
153
157
  //We need a port type to know how to connect to the block consistently
154
158
  const portTypes = getServiceProviderPorts(assetVersion, providerVersion);
155
159
 
156
160
  if (blockUri.version === 'local') {
157
- processInfo = await this._startLocalProcess(blockInstance, blockUri, env, assetVersion);
161
+ processInfo = await this._startLocalProcess(blockInstance, blockUri, envVars, assetVersion);
158
162
  } else {
159
- processInfo = await this._startDockerProcess(blockInstance, blockUri, env, assetVersion);
163
+ processInfo = await this._startDockerProcess(blockInstance, blockUri, envVars, assetVersion);
160
164
  }
161
165
 
162
166
  if (portTypes.length > 0) {
@@ -189,6 +193,8 @@ export class BlockInstanceRunner {
189
193
  throw new Error('Missing target kind in block definition');
190
194
  }
191
195
 
196
+ const realLocalPath = await FSExtra.realpath(baseDir);
197
+
192
198
  const kindUri = parseKapetaUri(assetVersion.definition.kind);
193
199
 
194
200
  const providerVersion = await getProvider(kindUri);
@@ -205,17 +211,29 @@ export class BlockInstanceRunner {
205
211
  throw new Error(`Target not found: ${targetKindUri.id}`);
206
212
  }
207
213
 
208
- const localContainer = targetVersion.definition.spec.local;
214
+ const localContainer = targetVersion.definition.spec.local as LocalDevContainer;
209
215
 
210
216
  if (!localContainer) {
211
217
  throw new Error(`Missing local container information from target: ${targetKindUri.id}`);
212
218
  }
213
219
 
214
- const dockerImage = localContainer.image;
215
- if (!dockerImage) {
220
+ let dockerImage = localContainer.image;
221
+ const isDockerImage = !localContainer.type || localContainer.type.toLowerCase() === 'docker';
222
+ const isDockerFile = Boolean(localContainer.type && localContainer.type.toLowerCase() === 'dockerfile');
223
+ if (isDockerImage && !dockerImage) {
216
224
  throw new Error(`Missing docker image information: ${JSON.stringify(localContainer)}`);
217
225
  }
218
226
 
227
+ if (isDockerFile) {
228
+ dockerImage = blockInfo.fullName + ':local';
229
+ const dockerFile = Path.join(realLocalPath, localContainer.file ?? 'Dockerfile');
230
+ if (!FSExtra.existsSync(dockerFile)) {
231
+ throw new Error(`Dockerfile not found at: ${dockerFile}`);
232
+ }
233
+ const task = containerManager.buildDockerImage(dockerFile, blockInfo.fullName + ':local');
234
+ await task.wait();
235
+ }
236
+
219
237
  const containerName = await getBlockInstanceContainerName(this._systemId, blockInstance.id, targetKindUri.id);
220
238
  const startCmd = localContainer.handlers?.onCreate ? localContainer.handlers.onCreate : '';
221
239
  const dockerOpts = localContainer.options ?? {};
@@ -242,11 +260,13 @@ export class BlockInstanceRunner {
242
260
  HealthCheck = containerManager.toDockerHealth({ cmd: localContainer.healthcheck });
243
261
  }
244
262
 
245
- const realLocalPath = await FSExtra.realpath(baseDir);
246
-
247
- const Mounts = containerManager.toDockerMounts({
248
- [workingDir]: toLocalBindVolume(realLocalPath),
249
- });
263
+ const Mounts = isDockerImage
264
+ ? // For docker images we mount the local directory to the working directory
265
+ containerManager.toDockerMounts({
266
+ [workingDir]: toLocalBindVolume(realLocalPath),
267
+ })
268
+ : // For dockerfiles we don't mount anything
269
+ [];
250
270
 
251
271
  const systemUri = parseKapetaUri(this._systemId);
252
272
 
@@ -391,7 +411,7 @@ export class BlockInstanceRunner {
391
411
  throw new Error(`Provider did not have local image: ${providerRef}`);
392
412
  }
393
413
 
394
- const local = spec.local as LocalImageOptions;
414
+ const local = spec.local as LocalInstance;
395
415
 
396
416
  const dockerImage = local.image;
397
417
  const operatorUri = local.singleton ? parseKapetaUri(providerRef) : blockUri;
@@ -409,34 +429,30 @@ export class BlockInstanceRunner {
409
429
  `container:start:${containerName}`,
410
430
  async () => {
411
431
  const logs = new LogData();
412
-
413
- const bindHost = getBindHost();
432
+ const hostIp = getDockerHostIp();
414
433
 
415
434
  const ExposedPorts: AnyMap = {};
416
435
  const addonEnv: StringMap = {};
417
436
  const PortBindings: AnyMap = {};
418
437
  let HealthCheck = undefined;
419
438
  let Mounts: DockerMounts[] = [];
420
- const localPorts = local.ports ?? {};
439
+ const instancePorts = await getOperatorInstancePorts(this._systemId, operatorId, local);
421
440
  const labels: { [key: string]: string } = {};
422
- const promises = Object.entries(localPorts).map(async ([portType, value]) => {
423
- const portInfo = toPortInfo(value);
424
- const dockerPort = `${portInfo.port}/${portInfo.type}`;
441
+ instancePorts.forEach((portInfo) => {
442
+ const dockerPort = `${portInfo.port}/${portInfo.protocol}`;
425
443
  ExposedPorts[dockerPort] = {};
426
- addonEnv[`KAPETA_LOCAL_SERVER_PORT_${portType.toUpperCase()}`] = `${portInfo.port}`;
427
- const publicPort = await serviceManager.ensureServicePort(this._systemId, operatorId, portType);
444
+ addonEnv[`KAPETA_LOCAL_SERVER_PORT_${portInfo.portType.toUpperCase()}`] = `${portInfo.port}`;
445
+
428
446
  PortBindings[dockerPort] = [
429
447
  {
430
- HostIp: bindHost,
431
- HostPort: `${publicPort}`,
448
+ HostIp: hostIp,
449
+ HostPort: `${portInfo.hostPort}`,
432
450
  },
433
451
  ];
434
452
 
435
- labels[CONTAINER_LABEL_PORT_PREFIX + publicPort] = portType;
453
+ labels[CONTAINER_LABEL_PORT_PREFIX + portInfo.hostPort] = portInfo.portType;
436
454
  });
437
455
 
438
- await Promise.all(promises);
439
-
440
456
  if (local.env) {
441
457
  Object.entries(local.env).forEach(([key, value]) => {
442
458
  addonEnv[key] = value as string;
@@ -519,7 +535,7 @@ export class BlockInstanceRunner {
519
535
  assetVersion: DefinitionInfo,
520
536
  providerVersion: DefinitionInfo
521
537
  ) {
522
- const bindHost = getBindHost();
538
+ const hostIp = getDockerHostIp();
523
539
  const ExposedPorts: AnyMap = {};
524
540
  const addonEnv: StringMap = {};
525
541
  const PortBindings: AnyMap = {};
@@ -535,7 +551,7 @@ export class BlockInstanceRunner {
535
551
 
536
552
  PortBindings[dockerPort] = [
537
553
  {
538
- HostIp: bindHost,
554
+ HostIp: hostIp,
539
555
  HostPort: `${publicPort}`,
540
556
  },
541
557
  ];
@@ -0,0 +1,214 @@
1
+ /**
2
+ * Copyright 2023 Kapeta Inc.
3
+ * SPDX-License-Identifier: BUSL-1.1
4
+ */
5
+ import {
6
+ BlockInstanceDetails,
7
+ ConfigProvider,
8
+ DefaultCredentials,
9
+ DefaultResourceOptions,
10
+ InstanceOperator,
11
+ ResourceInfo,
12
+ } from '@kapeta/sdk-config';
13
+ import { Definition, DefinitionInfo } from '@kapeta/local-cluster-config';
14
+ import { normalizeKapetaUri } from '@kapeta/nodejs-utils';
15
+ import { BlockDefinition, Plan } from '@kapeta/schemas';
16
+ import { configManager } from '../configManager';
17
+ import { AnyMap, EnvironmentType } from '../types';
18
+ import _ from 'lodash';
19
+ import { serviceManager } from '../serviceManager';
20
+ import { operatorManager } from '../operatorManager';
21
+ import { instanceManager } from '../instanceManager';
22
+ import { definitionsManager } from '../definitionsManager';
23
+ import { getBindAddressForEnvironment } from './utils';
24
+
25
+ /**
26
+ * A configuration provider that does the same as the LocalConfigProvider
27
+ * but without calling the API of the local cluster service (since it's running in the same process)
28
+ */
29
+ export class InternalConfigProvider implements ConfigProvider {
30
+ private readonly info: DefinitionInfo;
31
+ private readonly systemId: string;
32
+ private readonly instanceId: string;
33
+ private readonly config: AnyMap;
34
+ private readonly environment: EnvironmentType;
35
+
36
+ constructor(
37
+ systemId: string,
38
+ instanceId: string,
39
+ info: DefinitionInfo,
40
+ config: AnyMap,
41
+ environment: EnvironmentType = 'docker'
42
+ ) {
43
+ this.info = info;
44
+ this.systemId = normalizeKapetaUri(systemId);
45
+ this.instanceId = instanceId;
46
+ this.config = config;
47
+ this.environment = environment;
48
+ }
49
+
50
+ getBlockDefinition() {
51
+ return this.info.definition;
52
+ }
53
+ getBlockReference(): string {
54
+ return normalizeKapetaUri(this.info.definition.metadata.name + ':' + this.info.version);
55
+ }
56
+ getSystemId(): string {
57
+ return this.systemId;
58
+ }
59
+ getInstanceId(): string {
60
+ return this.instanceId;
61
+ }
62
+ getServerPort(portType?: string | undefined): Promise<string> {
63
+ return serviceManager.ensureServicePort(this.systemId, this.instanceId, portType);
64
+ }
65
+ async getServiceAddress(serviceName: string, portType: string): Promise<string | null> {
66
+ return serviceManager.getConsumerAddress(
67
+ this.systemId,
68
+ this.instanceId,
69
+ serviceName,
70
+ portType,
71
+ this.environment
72
+ );
73
+ }
74
+ getResourceInfo<Options = DefaultResourceOptions, Credentials = DefaultCredentials>(
75
+ resourceType: string,
76
+ portType: string,
77
+ resourceName: string
78
+ ): Promise<ResourceInfo<Options, Credentials> | null> {
79
+ return operatorManager.getConsumerResourceInfo(
80
+ this.systemId,
81
+ this.instanceId,
82
+ resourceType,
83
+ portType,
84
+ resourceName,
85
+ this.environment,
86
+ false
87
+ );
88
+ }
89
+ async getInstanceHost(instanceId: string): Promise<string | null> {
90
+ const instance = instanceManager.getInstance(this.systemId, instanceId);
91
+ return instance?.address ?? null;
92
+ }
93
+ async getServerHost(): Promise<string> {
94
+ return getBindAddressForEnvironment(this.environment);
95
+ }
96
+ getProviderId(): string {
97
+ return 'internal';
98
+ }
99
+ getOrDefault<T = any>(path: string, defaultValue: T): T {
100
+ return this.get(path) ?? defaultValue;
101
+ }
102
+ get<T = any>(path: string): T | undefined {
103
+ return _.get(this.config, path);
104
+ }
105
+
106
+ getInstanceOperator<Options = any, Credentials extends DefaultCredentials = DefaultCredentials>(
107
+ instanceId: string
108
+ ): Promise<InstanceOperator<Options, Credentials> | null> {
109
+ return instanceManager.getInstanceOperator(this.systemId, instanceId, this.environment, false);
110
+ }
111
+
112
+ public async getInstanceForConsumer<BlockType = BlockDefinition>(
113
+ resourceName: string
114
+ ): Promise<BlockInstanceDetails<BlockType> | null> {
115
+ const plan = await this.getPlan();
116
+ if (!plan) {
117
+ throw new Error('Could not find plan');
118
+ }
119
+ const instanceId = this.getInstanceId();
120
+ const connection = plan.spec.connections.find(
121
+ (connection) =>
122
+ connection.consumer.blockId === instanceId && connection.consumer.resourceName === resourceName
123
+ );
124
+
125
+ if (!connection) {
126
+ throw new Error(`Could not find connection for consumer ${resourceName}`);
127
+ }
128
+
129
+ const instance = plan.spec.blocks.find((b) => b.id === connection.provider.blockId);
130
+
131
+ if (!instance) {
132
+ throw new Error(`Could not find instance ${connection.provider.blockId} in plan`);
133
+ }
134
+
135
+ const block = await this.getBlock(instance.block.ref);
136
+
137
+ if (!block) {
138
+ throw new Error(`Could not find block ${instance.block.ref} in plan`);
139
+ }
140
+
141
+ return {
142
+ instanceId: connection.provider.blockId,
143
+ connections: [connection],
144
+ block: block as BlockType,
145
+ };
146
+ }
147
+
148
+ public async getInstancesForProvider<BlockType = BlockDefinition>(
149
+ resourceName: string
150
+ ): Promise<BlockInstanceDetails<BlockType>[]> {
151
+ const plan = await this.getPlan();
152
+ if (!plan) {
153
+ throw new Error('Could not find plan');
154
+ }
155
+ const instanceId = this.getInstanceId();
156
+
157
+ const blockDetails: { [key: string]: BlockInstanceDetails<BlockType> } = {};
158
+ const connections = plan.spec.connections.filter(
159
+ (connection) =>
160
+ connection.provider.blockId === instanceId && connection.provider.resourceName === resourceName
161
+ );
162
+
163
+ for (const connection of connections) {
164
+ const blockInstanceId = connection.consumer.blockId;
165
+ if (blockDetails[blockInstanceId]) {
166
+ blockDetails[blockInstanceId].connections.push(connection);
167
+ continue;
168
+ }
169
+
170
+ const instance = plan.spec.blocks.find((b) => b.id === blockInstanceId);
171
+ if (!instance) {
172
+ throw new Error(`Could not find instance ${blockInstanceId} in plan`);
173
+ }
174
+
175
+ const block = await this.getBlock(instance.block.ref);
176
+ if (!block) {
177
+ throw new Error(`Could not find block ${instance.block.ref} in plan`);
178
+ }
179
+
180
+ blockDetails[blockInstanceId] = {
181
+ instanceId: blockInstanceId,
182
+ connections: [connection],
183
+ block: block as BlockType,
184
+ };
185
+ }
186
+
187
+ return Object.values(blockDetails);
188
+ }
189
+
190
+ async getBlock(ref: any): Promise<Definition> {
191
+ const definition = await definitionsManager.getDefinition(ref);
192
+ if (!definition) {
193
+ throw new Error(`Could not find definition for ${ref}`);
194
+ }
195
+ return definition.definition;
196
+ }
197
+
198
+ async getPlan(): Promise<Plan> {
199
+ const definition = await definitionsManager.getDefinition(this.systemId);
200
+ if (!definition) {
201
+ throw new Error(`Could not find plan ${this.systemId}`);
202
+ }
203
+ return definition.definition as Plan;
204
+ }
205
+ }
206
+
207
+ export async function createInternalConfigProvider(
208
+ systemId: string,
209
+ instanceId: string,
210
+ info: DefinitionInfo
211
+ ): Promise<InternalConfigProvider> {
212
+ const config = await configManager.getConfigForBlockInstance(systemId, instanceId);
213
+ return new InternalConfigProvider(systemId, instanceId, info, config);
214
+ }
@@ -4,7 +4,7 @@
4
4
  */
5
5
 
6
6
  import { spawn, hasApp } from '@kapeta/nodejs-process';
7
- import { taskManager } from '../taskManager';
7
+ import { Task, taskManager } from '../taskManager';
8
8
 
9
9
  export async function hasCLI() {
10
10
  return hasApp('kap');
@@ -17,11 +17,19 @@ export async function ensureCLI() {
17
17
 
18
18
  return taskManager.add(
19
19
  `cli:install`,
20
- () => {
20
+ (task: Task) => {
21
21
  const process = spawn('npm', ['install', '-g', '@kapeta/kap'], {
22
22
  shell: true,
23
23
  });
24
24
 
25
+ process.process.stdout?.on('data', (data: any) => {
26
+ task.addLog(data.toString(), 'INFO');
27
+ });
28
+
29
+ process.process.stderr?.on('data', (data: any) => {
30
+ task.addLog(data.toString(), 'ERROR');
31
+ });
32
+
25
33
  return process.wait();
26
34
  },
27
35
  {
@@ -6,13 +6,15 @@
6
6
  import FS from 'node:fs';
7
7
  import YAML from 'yaml';
8
8
  import md5 from 'md5';
9
- import { EntityList } from '@kapeta/schemas';
9
+ import { EntityList, LocalInstance, LocalInstancePort, LocalInstancePortType } from '@kapeta/schemas';
10
10
  import _ from 'lodash';
11
- import { AnyMap, KIND_BLOCK_TYPE_OPERATOR, PortInfo } from '../types';
11
+ import { AnyMap, DOCKER_HOST_INTERNAL, EnvironmentType, KIND_BLOCK_TYPE_OPERATOR } from '../types';
12
12
  import ClusterConfiguration from '@kapeta/local-cluster-config';
13
13
  import { definitionsManager } from '../definitionsManager';
14
14
  import { normalizeKapetaUri, parseKapetaUri } from '@kapeta/nodejs-utils';
15
15
  import { assetManager } from '../assetManager';
16
+ import { serviceManager } from '../serviceManager';
17
+ import { clusterService } from '../clusterService';
16
18
 
17
19
  export async function getBlockInstanceContainerName(systemId: string, instanceId: string, blockType?: string) {
18
20
  if (!blockType) {
@@ -40,18 +42,65 @@ export async function getBlockInstanceContainerName(systemId: string, instanceId
40
42
  return `kapeta-block-instance-${md5(normalizeKapetaUri(systemId) + instanceId)}`;
41
43
  }
42
44
 
43
- export function toPortInfo(port: PortInfo) {
45
+ export function toPortInfo(port: LocalInstancePort) {
44
46
  if (typeof port === 'number' || typeof port === 'string') {
45
47
  return { port: parseInt(`${port}`), type: 'tcp' };
46
48
  }
47
49
 
48
50
  if (!port.type) {
49
- port.type = 'tcp';
51
+ port.type = LocalInstancePortType.TCP;
50
52
  }
51
53
 
52
54
  return port;
53
55
  }
54
56
 
57
+ export async function getOperatorInstancePorts(systemId: string, operatorId: string, local: LocalInstance) {
58
+ const localPorts = local.ports ?? {};
59
+
60
+ const promises = Object.entries(localPorts).map(async ([portType, value]) => {
61
+ const portInfo = toPortInfo(value);
62
+ const hostPort = await serviceManager.ensureServicePort(systemId, operatorId, portType);
63
+ return {
64
+ portType,
65
+ port: portInfo.port,
66
+ hostPort,
67
+ protocol: portInfo.type,
68
+ };
69
+ });
70
+ return await Promise.all(promises);
71
+ }
72
+
73
+ /**
74
+ * Gets the hostname where all services are available - including the cluster service.
75
+ *
76
+ * For docker this is the internal docker host - otherwise it's the local machine
77
+ * Assumed to be the same address as the cluster service outside docker.
78
+ */
79
+ export function getRemoteHostForEnvironment(environment: EnvironmentType | undefined): string {
80
+ return environment === 'docker' ? DOCKER_HOST_INTERNAL : clusterService.getClusterServiceHost();
81
+ }
82
+
83
+ /**
84
+ * Get the bind address for the given environment.
85
+ *
86
+ * Outside of docker we bind to 127.0.0.1 - inside we bind to everything (0.0.0.0)
87
+ */
88
+ export function getBindAddressForEnvironment(
89
+ environment: EnvironmentType | undefined,
90
+ preferredHost = '127.0.0.1'
91
+ ): string {
92
+ return environment === 'docker' ? '0.0.0.0' : preferredHost;
93
+ }
94
+
95
+ /**
96
+ * Get the docker host IP address for port binding.
97
+ */
98
+ export function getDockerHostIp(preferredHost = '127.0.0.1') {
99
+ // On Linux we need to bind to 0.0.0.0 to be able to connect to it from docker containers.
100
+ // TODO: This might pose a security risk - so we should authenticate all requests using a shared secret/nonce that we pass around.
101
+ return isLinux() ? '0.0.0.0' : preferredHost;
102
+ }
103
+
55
104
  export function getRemoteUrl(id: string, defautValue: string) {
56
105
  const remoteConfig = ClusterConfiguration.getClusterConfig().remote;
57
106
  return remoteConfig?.[id] ?? defautValue;
@@ -79,12 +128,6 @@ export function isLinux() {
79
128
  return !isWindows() && !isMac();
80
129
  }
81
130
 
82
- export function getBindHost(preferredHost = '127.0.0.1') {
83
- // On Linux we need to bind to 0.0.0.0 to be able to connect to it from docker containers.
84
- // TODO: This might pose a security risk - so we should authenticate all requests using a shared secret/nonce that we pass around.
85
- return isLinux() ? '0.0.0.0' : preferredHost;
86
- }
87
-
88
131
  export function getResolvedConfiguration(entities?: EntityList, config?: AnyMap, globalConfiguration?: AnyMap): AnyMap {
89
132
  if (!entities || !globalConfiguration) {
90
133
  return config || {};