@kapeta/local-cluster-service 0.34.1 → 0.35.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 (43) hide show
  1. package/.github/workflows/check-license.yml +0 -1
  2. package/CHANGELOG.md +14 -0
  3. package/dist/cjs/src/config/routes.js +9 -0
  4. package/dist/cjs/src/containerManager.d.ts +2 -8
  5. package/dist/cjs/src/containerManager.js +4 -4
  6. package/dist/cjs/src/instanceManager.d.ts +2 -1
  7. package/dist/cjs/src/instanceManager.js +43 -1
  8. package/dist/cjs/src/operatorManager.d.ts +2 -2
  9. package/dist/cjs/src/operatorManager.js +2 -7
  10. package/dist/cjs/src/proxy/types/rest.js +2 -1
  11. package/dist/cjs/src/proxy/types/web.js +2 -1
  12. package/dist/cjs/src/serviceManager.d.ts +1 -0
  13. package/dist/cjs/src/serviceManager.js +9 -9
  14. package/dist/cjs/src/types.d.ts +39 -0
  15. package/dist/cjs/src/utils/BlockInstanceRunner.js +16 -12
  16. package/dist/cjs/src/utils/utils.d.ts +5 -1
  17. package/dist/cjs/src/utils/utils.js +11 -1
  18. package/dist/esm/src/config/routes.js +9 -0
  19. package/dist/esm/src/containerManager.d.ts +2 -8
  20. package/dist/esm/src/containerManager.js +4 -4
  21. package/dist/esm/src/instanceManager.d.ts +2 -1
  22. package/dist/esm/src/instanceManager.js +43 -1
  23. package/dist/esm/src/operatorManager.d.ts +2 -2
  24. package/dist/esm/src/operatorManager.js +2 -7
  25. package/dist/esm/src/proxy/types/rest.js +2 -1
  26. package/dist/esm/src/proxy/types/web.js +2 -1
  27. package/dist/esm/src/serviceManager.d.ts +1 -0
  28. package/dist/esm/src/serviceManager.js +9 -9
  29. package/dist/esm/src/types.d.ts +39 -0
  30. package/dist/esm/src/utils/BlockInstanceRunner.js +16 -12
  31. package/dist/esm/src/utils/utils.d.ts +5 -1
  32. package/dist/esm/src/utils/utils.js +11 -1
  33. package/package.json +6 -5
  34. package/src/config/routes.ts +15 -0
  35. package/src/containerManager.ts +5 -12
  36. package/src/instanceManager.ts +72 -4
  37. package/src/operatorManager.ts +5 -13
  38. package/src/proxy/types/rest.ts +2 -1
  39. package/src/proxy/types/web.ts +3 -2
  40. package/src/serviceManager.ts +11 -8
  41. package/src/types.ts +35 -0
  42. package/src/utils/BlockInstanceRunner.ts +21 -14
  43. package/src/utils/utils.ts +13 -2
@@ -15,4 +15,3 @@ jobs:
15
15
  holder: 'Kapeta Inc.'
16
16
  license: 'BUSL-1.1'
17
17
  sources: 'src/**/* test/*'
18
-
package/CHANGELOG.md CHANGED
@@ -1,3 +1,17 @@
1
+ # [0.35.0](https://github.com/kapetacom/local-cluster-service/compare/v0.34.2...v0.35.0) (2024-01-31)
2
+
3
+
4
+ ### Features
5
+
6
+ * Adds support for getting instance operator info ([#122](https://github.com/kapetacom/local-cluster-service/issues/122)) ([8fa18ac](https://github.com/kapetacom/local-cluster-service/commit/8fa18ac58226ce9776f79a64d3a99cf56456e834))
7
+
8
+ ## [0.34.2](https://github.com/kapetacom/local-cluster-service/compare/v0.34.1...v0.34.2) (2024-01-22)
9
+
10
+
11
+ ### Bug Fixes
12
+
13
+ * Forward query params correctly ([#121](https://github.com/kapetacom/local-cluster-service/issues/121)) ([1bbf6f0](https://github.com/kapetacom/local-cluster-service/commit/1bbf6f0cf0fee218c441f3b556683873e208c951))
14
+
1
15
  ## [0.34.1](https://github.com/kapetacom/local-cluster-service/compare/v0.34.0...v0.34.1) (2024-01-22)
2
16
 
3
17
 
@@ -148,4 +148,13 @@ router.get('/consumes/resource/:resourceType/:portType/:name', async (req, res)
148
148
  router.get('/consumes/:resourceName/:type', (req, res) => {
149
149
  res.send(serviceManager_1.serviceManager.getConsumerAddress(req.kapeta.systemId, req.kapeta.instanceId, req.params.resourceName, req.params.type, req.kapeta.environment));
150
150
  });
151
+ /**
152
+ * Used by services to information about a block operator
153
+ *
154
+ * If the remote service is not already running it will be started
155
+ */
156
+ router.get('/operator/:instanceId', async (req, res) => {
157
+ const operatorInfo = await instanceManager_1.instanceManager.getInstanceOperator(req.kapeta.systemId, req.params.instanceId, req.kapeta.environment);
158
+ res.send(operatorInfo);
159
+ });
151
160
  exports.default = router;
@@ -5,7 +5,7 @@
5
5
  /// <reference types="node" />
6
6
  import FSExtra from 'fs-extra';
7
7
  import Docker from 'dockerode';
8
- import { InstanceInfo, LogEntry } from './types';
8
+ import { Health, InstanceInfo, LogEntry } from './types';
9
9
  type StringMap = {
10
10
  [key: string]: string;
11
11
  };
@@ -26,12 +26,6 @@ export interface DockerMounts {
26
26
  }
27
27
  export type DockerContainerStatus = 'created' | 'running' | 'paused' | 'restarting' | 'removing' | 'exited' | 'dead';
28
28
  export type DockerContainerHealth = 'starting' | 'healthy' | 'unhealthy' | 'none';
29
- interface Health {
30
- cmd: string;
31
- interval?: number;
32
- timeout?: number;
33
- retries?: number;
34
- }
35
29
  export declare const CONTAINER_LABEL_PORT_PREFIX = "kapeta_port-";
36
30
  export declare const COMPOSE_LABEL_PROJECT = "com.docker.compose.project";
37
31
  export declare const COMPOSE_LABEL_SERVICE = "com.docker.compose.service";
@@ -50,7 +44,7 @@ declare class ContainerManager {
50
44
  isAlive(): boolean;
51
45
  getMountPoint(systemId: string, ref: string, mountName: string): string;
52
46
  createMounts(systemId: string, kind: string, mountOpts: StringMap | null | undefined): Promise<StringMap>;
53
- createVolumes(systemId: string, kind: string, mountOpts: StringMap | null | undefined): Promise<DockerMounts[]>;
47
+ createVolumes(systemId: string, serviceId: string, mountOpts: StringMap | null | undefined): Promise<DockerMounts[]>;
54
48
  ping(): Promise<void>;
55
49
  docker(): Docker;
56
50
  getContainerByName(containerName: string): Promise<ContainerInfo | undefined>;
@@ -168,12 +168,12 @@ class ContainerManager {
168
168
  }
169
169
  return mounts;
170
170
  }
171
- async createVolumes(systemId, kind, mountOpts) {
171
+ async createVolumes(systemId, serviceId, mountOpts) {
172
172
  const Mounts = [];
173
173
  if (mountOpts) {
174
174
  const mountOptList = Object.entries(mountOpts);
175
175
  for (const [mountName, containerPath] of mountOptList) {
176
- const volumeName = `${systemId}_${kind}_${mountName}`.replace(/[^a-z0-9]/gi, '_');
176
+ const volumeName = `${systemId}_${serviceId}_${mountName}`.replace(/[^a-z0-9]/gi, '_');
177
177
  Mounts.push({
178
178
  Target: containerPath,
179
179
  Source: volumeName,
@@ -182,7 +182,7 @@ class ContainerManager {
182
182
  Consistency: 'consistent',
183
183
  Labels: {
184
184
  [exports.COMPOSE_LABEL_PROJECT]: systemId.replace(/[^a-z0-9]/gi, '_'),
185
- [exports.COMPOSE_LABEL_SERVICE]: kind.replace(/[^a-z0-9]/gi, '_'),
185
+ [exports.COMPOSE_LABEL_SERVICE]: serviceId.replace(/[^a-z0-9]/gi, '_'),
186
186
  },
187
187
  });
188
188
  }
@@ -791,7 +791,7 @@ class ContainerInfo {
791
791
  if (!name.startsWith(exports.CONTAINER_LABEL_PORT_PREFIX)) {
792
792
  return;
793
793
  }
794
- const hostPort = name.substr(exports.CONTAINER_LABEL_PORT_PREFIX.length);
794
+ const hostPort = name.substring(exports.CONTAINER_LABEL_PORT_PREFIX.length);
795
795
  portTypes[hostPort] = portType;
796
796
  });
797
797
  lodash_1.default.forEach(inspectResult.HostConfig.PortBindings, (portBindings, containerPortSpec) => {
@@ -2,7 +2,7 @@
2
2
  * Copyright 2023 Kapeta Inc.
3
3
  * SPDX-License-Identifier: BUSL-1.1
4
4
  */
5
- import { InstanceInfo, LogEntry } from './types';
5
+ import { EnvironmentType, InstanceInfo, LogEntry, OperatorInstanceInfo } from './types';
6
6
  import { Task } from './taskManager';
7
7
  export declare class InstanceManager {
8
8
  private _interval;
@@ -27,6 +27,7 @@ export declare class InstanceManager {
27
27
  stop(systemId: string, instanceId: string): Promise<void>;
28
28
  private stopInner;
29
29
  stopAllForPlan(systemId: string): Task<void>;
30
+ getInstanceOperator(systemId: string, instanceId: string, environment?: EnvironmentType): Promise<OperatorInstanceInfo>;
30
31
  start(systemId: string, instanceId: string): Promise<InstanceInfo | Task<InstanceInfo>>;
31
32
  /**
32
33
  * Stops an instance but does not remove it from the list of active instances
@@ -323,6 +323,48 @@ class InstanceManager {
323
323
  name: `Stopping plan ${systemId}`,
324
324
  });
325
325
  }
326
+ async getInstanceOperator(systemId, instanceId, environment) {
327
+ const blockInstance = await assetManager_1.assetManager.getBlockInstance(systemId, instanceId);
328
+ if (!blockInstance) {
329
+ throw new Error(`Instance not found: ${systemId}/${instanceId}`);
330
+ }
331
+ const blockRef = (0, nodejs_utils_1.normalizeKapetaUri)(blockInstance.block.ref);
332
+ const block = await assetManager_1.assetManager.getAsset(blockRef, true);
333
+ if (!block) {
334
+ throw new Error(`Block not found: ${blockRef}`);
335
+ }
336
+ const operatorDefinition = await definitionsManager_1.definitionsManager.getDefinition(block.kind);
337
+ if (!operatorDefinition?.definition.spec.local) {
338
+ throw new Error(`Operator block has no local definition: ${blockRef}`);
339
+ }
340
+ const localConfig = operatorDefinition.definition.spec.local;
341
+ let instance = await this.start(systemId, instanceId);
342
+ if (instance instanceof taskManager_1.Task) {
343
+ instance = await instance.wait();
344
+ }
345
+ const container = await containerManager_1.containerManager.get(instance.pid);
346
+ if (!container) {
347
+ throw new Error(`Container not found: ${instance.pid}`);
348
+ }
349
+ const portInfo = await container.getPorts();
350
+ if (!portInfo) {
351
+ throw new Error(`No ports found for instance: ${instanceId}`);
352
+ }
353
+ const hostname = serviceManager_1.serviceManager.getLocalHost(environment);
354
+ const ports = {};
355
+ Object.entries(portInfo).forEach(([key, value]) => {
356
+ ports[key] = {
357
+ protocol: value.protocol,
358
+ port: parseInt(value.hostPort),
359
+ };
360
+ });
361
+ return {
362
+ hostname,
363
+ ports,
364
+ credentials: localConfig.credentials,
365
+ options: localConfig.options,
366
+ };
367
+ }
326
368
  async start(systemId, instanceId) {
327
369
  return this.exclusive(systemId, instanceId, async () => {
328
370
  systemId = (0, nodejs_utils_1.normalizeKapetaUri)(systemId);
@@ -333,7 +375,7 @@ class InstanceManager {
333
375
  throw new Error('Block not found: ' + blockRef);
334
376
  }
335
377
  const existingInstance = this.getInstance(systemId, instanceId);
336
- if (existingInstance) {
378
+ if (existingInstance && existingInstance.pid) {
337
379
  if (existingInstance.status === types_1.InstanceStatus.READY) {
338
380
  // Instance is already running
339
381
  return existingInstance;
@@ -4,12 +4,12 @@
4
4
  */
5
5
  import { DefinitionInfo } from '@kapeta/local-cluster-config';
6
6
  import { ContainerInfo } from './containerManager';
7
- import { EnvironmentType, OperatorInfo } from './types';
7
+ import { EnvironmentType, LocalImageOptions, OperatorInfo } from './types';
8
8
  export declare const KIND_OPERATOR = "core/resource-type-operator";
9
9
  declare class Operator {
10
10
  private readonly _data;
11
11
  constructor(data: DefinitionInfo);
12
- getLocalData(): any;
12
+ getLocalData(): LocalImageOptions;
13
13
  getDefinitionInfo(): DefinitionInfo;
14
14
  getCredentials(): any;
15
15
  }
@@ -137,13 +137,8 @@ class OperatorManager {
137
137
  const portType = portTypes[i];
138
138
  let containerPortInfo = operatorData.ports[portType];
139
139
  const hostPort = await serviceManager_1.serviceManager.ensureServicePort(systemId, resourceType, portType);
140
- if (typeof containerPortInfo === 'number' || typeof containerPortInfo === 'string') {
141
- containerPortInfo = { port: containerPortInfo, type: 'tcp' };
142
- }
143
- if (!containerPortInfo.type) {
144
- containerPortInfo.type = 'tcp';
145
- }
146
- const portId = containerPortInfo.port + '/' + containerPortInfo.type;
140
+ const portInfo = (0, utils_1.toPortInfo)(containerPortInfo);
141
+ const portId = portInfo.port + '/' + portInfo.type;
147
142
  ports[portId] = {
148
143
  type: portType,
149
144
  hostPort,
@@ -14,6 +14,7 @@ const path_1 = __importDefault(require("path"));
14
14
  const pathTemplateParser_1 = require("../../utils/pathTemplateParser");
15
15
  const networkManager_1 = require("../../networkManager");
16
16
  const socketManager_1 = require("../../socketManager");
17
+ const qs_1 = require("qs");
17
18
  function getRestMethodId(restResource, httpMethod, httpPath) {
18
19
  return lodash_1.default.findKey(restResource.spec.methods, (method) => {
19
20
  let methodType = method.method ? method.method.toUpperCase() : 'GET';
@@ -77,7 +78,7 @@ function proxyRestRequest(req, res, opts) {
77
78
  providerPath = '/' + providerPath;
78
79
  }
79
80
  if (!lodash_1.default.isEmpty(req.query)) {
80
- providerPath += '?' + new URLSearchParams(req.query).toString();
81
+ providerPath += '?' + (0, qs_1.stringify)(req.query, { arrayFormat: 'repeat' });
81
82
  }
82
83
  const requestHeaders = lodash_1.default.clone(req.headers);
83
84
  delete requestHeaders['content-length'];
@@ -12,6 +12,7 @@ const request_1 = __importDefault(require("request"));
12
12
  const lodash_1 = __importDefault(require("lodash"));
13
13
  const networkManager_1 = require("../../networkManager");
14
14
  const socketManager_1 = require("../../socketManager");
15
+ const qs_1 = require("qs");
15
16
  function proxyHttpRequest(req, res, opts) {
16
17
  const requestHeaders = lodash_1.default.clone(req.headers);
17
18
  delete requestHeaders['content-length'];
@@ -26,7 +27,7 @@ function proxyHttpRequest(req, res, opts) {
26
27
  path = path.replace(sourceBasePath, targetBasePath);
27
28
  }
28
29
  if (!lodash_1.default.isEmpty(req.query)) {
29
- path += '?' + new URLSearchParams(req.query).toString();
30
+ path += '?' + (0, qs_1.stringify)(req.query, { arrayFormat: 'repeat' });
30
31
  }
31
32
  console.log('Proxy request to provider: %s => %s%s [http]', opts.consumerPath, opts.address, path);
32
33
  const reqOpts = {
@@ -9,6 +9,7 @@ export declare const HTTP_PORTS: string[];
9
9
  declare class ServiceManager {
10
10
  private _systems;
11
11
  constructor();
12
+ getLocalHost(environmentType?: EnvironmentType): string;
12
13
  _forLocal(port: string | number, path?: string, environmentType?: EnvironmentType): string;
13
14
  _ensureSystem(systemId: string): any;
14
15
  _ensureService(systemId: string, serviceId: string): any;
@@ -31,22 +31,22 @@ class ServiceManager {
31
31
  });
32
32
  });
33
33
  }
34
- _forLocal(port, path, environmentType) {
35
- if (!path) {
36
- path = '';
37
- }
38
- let host;
34
+ getLocalHost(environmentType) {
39
35
  if (environmentType === 'docker') {
40
36
  //We're inside a docker container, so we can use this special host name to access the host machine
41
- host = 'host.docker.internal';
37
+ return 'host.docker.internal';
42
38
  }
43
- else {
44
- host = clusterService_1.clusterService.getClusterServiceHost();
39
+ return clusterService_1.clusterService.getClusterServiceHost();
40
+ }
41
+ _forLocal(port, path, environmentType) {
42
+ if (!path) {
43
+ path = '';
45
44
  }
45
+ const hostname = this.getLocalHost(environmentType);
46
46
  if (path.startsWith('/')) {
47
47
  path = path.substring(1);
48
48
  }
49
- return `http://${host}:${port}/${path}`;
49
+ return `http://${hostname}:${port}/${path}`;
50
50
  }
51
51
  _ensureSystem(systemId) {
52
52
  systemId = (0, nodejs_utils_1.normalizeKapetaUri)(systemId);
@@ -56,6 +56,30 @@ export type ProcessInfo = {
56
56
  pid?: number | string | null;
57
57
  portType?: string;
58
58
  };
59
+ export interface Health {
60
+ cmd: string;
61
+ interval?: number;
62
+ timeout?: number;
63
+ retries?: number;
64
+ }
65
+ export type PortInfo = {
66
+ port: number;
67
+ type: 'tcp' | 'udp';
68
+ } | number | string;
69
+ export type LocalImageOptions<Credentials = AnyMap, Options = AnyMap> = {
70
+ image: string;
71
+ ports: {
72
+ [key: string]: PortInfo;
73
+ };
74
+ credentials?: Credentials;
75
+ options?: Options;
76
+ cmd?: string;
77
+ env?: AnyMap;
78
+ health?: Health;
79
+ mounts?: {
80
+ [key: string]: string;
81
+ };
82
+ };
59
83
  export type InstanceInfo = {
60
84
  systemId: string;
61
85
  instanceId: string;
@@ -73,6 +97,21 @@ export type InstanceInfo = {
73
97
  portType?: string;
74
98
  };
75
99
  export type ProxyRequestHandler = (req: StringBodyRequest, res: express.Response, info: ProxyRequestInfo) => void;
100
+ export interface OperatorInstancePort {
101
+ protocol: string;
102
+ port: number;
103
+ }
104
+ export interface OperatorInstanceInfo {
105
+ hostname: string;
106
+ ports: {
107
+ [portType: string]: OperatorInstancePort;
108
+ };
109
+ path?: string;
110
+ query?: string;
111
+ hash?: string;
112
+ options?: AnyMap;
113
+ credentials?: AnyMap;
114
+ }
76
115
  export interface OperatorInfo {
77
116
  host: string;
78
117
  port: string;
@@ -288,7 +288,8 @@ class BlockInstanceRunner {
288
288
  if (!spec?.local?.image) {
289
289
  throw new Error(`Provider did not have local image: ${providerRef}`);
290
290
  }
291
- const dockerImage = spec?.local?.image;
291
+ const local = spec.local;
292
+ const dockerImage = local.image;
292
293
  //We only want 1 operator per operator type - across all local systems
293
294
  const containerName = (0, utils_1.getBlockInstanceContainerName)(this._systemId, blockInstance.id);
294
295
  const logs = new LogData_1.LogData();
@@ -298,11 +299,13 @@ class BlockInstanceRunner {
298
299
  const PortBindings = {};
299
300
  let HealthCheck = undefined;
300
301
  let Mounts = [];
301
- const localPorts = spec.local.ports;
302
+ const localPorts = local.ports ?? {};
303
+ const labels = {};
302
304
  const promises = Object.entries(localPorts).map(async ([portType, value]) => {
303
- const dockerPort = `${value.port}/${value.type}`;
305
+ const portInfo = (0, utils_1.toPortInfo)(value);
306
+ const dockerPort = `${portInfo.port}/${portInfo.type}`;
304
307
  ExposedPorts[dockerPort] = {};
305
- addonEnv[`KAPETA_LOCAL_SERVER_PORT_${portType.toUpperCase()}`] = value.port;
308
+ addonEnv[`KAPETA_LOCAL_SERVER_PORT_${portType.toUpperCase()}`] = `${portInfo.port}`;
306
309
  const publicPort = await serviceManager_1.serviceManager.ensureServicePort(this._systemId, blockInstance.id, portType);
307
310
  PortBindings[dockerPort] = [
308
311
  {
@@ -310,19 +313,19 @@ class BlockInstanceRunner {
310
313
  HostPort: `${publicPort}`,
311
314
  },
312
315
  ];
316
+ labels[containerManager_1.CONTAINER_LABEL_PORT_PREFIX + publicPort] = portType;
313
317
  });
314
318
  await Promise.all(promises);
315
- if (spec.local?.env) {
316
- Object.entries(spec.local.env).forEach(([key, value]) => {
319
+ if (local.env) {
320
+ Object.entries(local.env).forEach(([key, value]) => {
317
321
  addonEnv[key] = value;
318
322
  });
319
323
  }
320
- if (spec.local?.mounts) {
321
- const mounts = await containerManager_1.containerManager.createMounts(this._systemId, blockUri.id, spec.local.mounts);
322
- Mounts = containerManager_1.containerManager.toDockerMounts(mounts);
324
+ if (local.mounts) {
325
+ Mounts = await containerManager_1.containerManager.createVolumes(this._systemId, blockUri.id, local.mounts);
323
326
  }
324
- if (spec.local?.health) {
325
- HealthCheck = containerManager_1.containerManager.toDockerHealth(spec.local?.health);
327
+ if (local.health) {
328
+ HealthCheck = containerManager_1.containerManager.toDockerHealth(local.health);
326
329
  }
327
330
  // For windows we need to default to root
328
331
  const innerHome = process.platform === 'win32' ? '/root/.kapeta' : local_cluster_config_1.default.getKapetaBasedir();
@@ -342,6 +345,7 @@ class BlockInstanceRunner {
342
345
  Mounts,
343
346
  },
344
347
  Labels: {
348
+ ...labels,
345
349
  instance: blockInstance.id,
346
350
  [containerManager_1.COMPOSE_LABEL_PROJECT]: systemUri.id.replace(/[^a-z0-9]/gi, '_'),
347
351
  [containerManager_1.COMPOSE_LABEL_SERVICE]: blockUri.id.replace(/[^a-z0-9]/gi, '_'),
@@ -356,7 +360,7 @@ class BlockInstanceRunner {
356
360
  }).map(([key, value]) => `${key}=${value}`),
357
361
  ],
358
362
  });
359
- const portTypes = spec.local.ports ? Object.keys(spec.local.ports) : [];
363
+ const portTypes = local.ports ? Object.keys(local.ports) : [];
360
364
  if (portTypes.length > 0) {
361
365
  out.portType = portTypes[0];
362
366
  }
@@ -3,8 +3,12 @@
3
3
  * SPDX-License-Identifier: BUSL-1.1
4
4
  */
5
5
  import { EntityList } from '@kapeta/schemas';
6
- import { AnyMap } from '../types';
6
+ import { AnyMap, PortInfo } from '../types';
7
7
  export declare function getBlockInstanceContainerName(systemId: string, instanceId: string): string;
8
+ export declare function toPortInfo(port: PortInfo): {
9
+ port: number;
10
+ type: string;
11
+ };
8
12
  export declare function getRemoteUrl(id: string, defautValue: string): any;
9
13
  export declare function readYML(path: string): any;
10
14
  export declare function isWindows(): boolean;
@@ -7,7 +7,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
7
7
  return (mod && mod.__esModule) ? mod : { "default": mod };
8
8
  };
9
9
  Object.defineProperty(exports, "__esModule", { value: true });
10
- exports.getResolvedConfiguration = exports.getBindHost = exports.isLinux = exports.isMac = exports.isWindows = exports.readYML = exports.getRemoteUrl = exports.getBlockInstanceContainerName = void 0;
10
+ exports.getResolvedConfiguration = exports.getBindHost = exports.isLinux = exports.isMac = exports.isWindows = exports.readYML = exports.getRemoteUrl = exports.toPortInfo = exports.getBlockInstanceContainerName = void 0;
11
11
  const node_fs_1 = __importDefault(require("node:fs"));
12
12
  const yaml_1 = __importDefault(require("yaml"));
13
13
  const md5_1 = __importDefault(require("md5"));
@@ -17,6 +17,16 @@ function getBlockInstanceContainerName(systemId, instanceId) {
17
17
  return `kapeta-block-instance-${(0, md5_1.default)(systemId + instanceId)}`;
18
18
  }
19
19
  exports.getBlockInstanceContainerName = getBlockInstanceContainerName;
20
+ function toPortInfo(port) {
21
+ if (typeof port === 'number' || typeof port === 'string') {
22
+ return { port: parseInt(`${port}`), type: 'tcp' };
23
+ }
24
+ if (!port.type) {
25
+ port.type = 'tcp';
26
+ }
27
+ return port;
28
+ }
29
+ exports.toPortInfo = toPortInfo;
20
30
  function getRemoteUrl(id, defautValue) {
21
31
  const remoteConfig = local_cluster_config_1.default.getClusterConfig().remote;
22
32
  return remoteConfig?.[id] ?? defautValue;
@@ -148,4 +148,13 @@ router.get('/consumes/resource/:resourceType/:portType/:name', async (req, res)
148
148
  router.get('/consumes/:resourceName/:type', (req, res) => {
149
149
  res.send(serviceManager_1.serviceManager.getConsumerAddress(req.kapeta.systemId, req.kapeta.instanceId, req.params.resourceName, req.params.type, req.kapeta.environment));
150
150
  });
151
+ /**
152
+ * Used by services to information about a block operator
153
+ *
154
+ * If the remote service is not already running it will be started
155
+ */
156
+ router.get('/operator/:instanceId', async (req, res) => {
157
+ const operatorInfo = await instanceManager_1.instanceManager.getInstanceOperator(req.kapeta.systemId, req.params.instanceId, req.kapeta.environment);
158
+ res.send(operatorInfo);
159
+ });
151
160
  exports.default = router;
@@ -5,7 +5,7 @@
5
5
  /// <reference types="node" />
6
6
  import FSExtra from 'fs-extra';
7
7
  import Docker from 'dockerode';
8
- import { InstanceInfo, LogEntry } from './types';
8
+ import { Health, InstanceInfo, LogEntry } from './types';
9
9
  type StringMap = {
10
10
  [key: string]: string;
11
11
  };
@@ -26,12 +26,6 @@ export interface DockerMounts {
26
26
  }
27
27
  export type DockerContainerStatus = 'created' | 'running' | 'paused' | 'restarting' | 'removing' | 'exited' | 'dead';
28
28
  export type DockerContainerHealth = 'starting' | 'healthy' | 'unhealthy' | 'none';
29
- interface Health {
30
- cmd: string;
31
- interval?: number;
32
- timeout?: number;
33
- retries?: number;
34
- }
35
29
  export declare const CONTAINER_LABEL_PORT_PREFIX = "kapeta_port-";
36
30
  export declare const COMPOSE_LABEL_PROJECT = "com.docker.compose.project";
37
31
  export declare const COMPOSE_LABEL_SERVICE = "com.docker.compose.service";
@@ -50,7 +44,7 @@ declare class ContainerManager {
50
44
  isAlive(): boolean;
51
45
  getMountPoint(systemId: string, ref: string, mountName: string): string;
52
46
  createMounts(systemId: string, kind: string, mountOpts: StringMap | null | undefined): Promise<StringMap>;
53
- createVolumes(systemId: string, kind: string, mountOpts: StringMap | null | undefined): Promise<DockerMounts[]>;
47
+ createVolumes(systemId: string, serviceId: string, mountOpts: StringMap | null | undefined): Promise<DockerMounts[]>;
54
48
  ping(): Promise<void>;
55
49
  docker(): Docker;
56
50
  getContainerByName(containerName: string): Promise<ContainerInfo | undefined>;
@@ -168,12 +168,12 @@ class ContainerManager {
168
168
  }
169
169
  return mounts;
170
170
  }
171
- async createVolumes(systemId, kind, mountOpts) {
171
+ async createVolumes(systemId, serviceId, mountOpts) {
172
172
  const Mounts = [];
173
173
  if (mountOpts) {
174
174
  const mountOptList = Object.entries(mountOpts);
175
175
  for (const [mountName, containerPath] of mountOptList) {
176
- const volumeName = `${systemId}_${kind}_${mountName}`.replace(/[^a-z0-9]/gi, '_');
176
+ const volumeName = `${systemId}_${serviceId}_${mountName}`.replace(/[^a-z0-9]/gi, '_');
177
177
  Mounts.push({
178
178
  Target: containerPath,
179
179
  Source: volumeName,
@@ -182,7 +182,7 @@ class ContainerManager {
182
182
  Consistency: 'consistent',
183
183
  Labels: {
184
184
  [exports.COMPOSE_LABEL_PROJECT]: systemId.replace(/[^a-z0-9]/gi, '_'),
185
- [exports.COMPOSE_LABEL_SERVICE]: kind.replace(/[^a-z0-9]/gi, '_'),
185
+ [exports.COMPOSE_LABEL_SERVICE]: serviceId.replace(/[^a-z0-9]/gi, '_'),
186
186
  },
187
187
  });
188
188
  }
@@ -791,7 +791,7 @@ class ContainerInfo {
791
791
  if (!name.startsWith(exports.CONTAINER_LABEL_PORT_PREFIX)) {
792
792
  return;
793
793
  }
794
- const hostPort = name.substr(exports.CONTAINER_LABEL_PORT_PREFIX.length);
794
+ const hostPort = name.substring(exports.CONTAINER_LABEL_PORT_PREFIX.length);
795
795
  portTypes[hostPort] = portType;
796
796
  });
797
797
  lodash_1.default.forEach(inspectResult.HostConfig.PortBindings, (portBindings, containerPortSpec) => {
@@ -2,7 +2,7 @@
2
2
  * Copyright 2023 Kapeta Inc.
3
3
  * SPDX-License-Identifier: BUSL-1.1
4
4
  */
5
- import { InstanceInfo, LogEntry } from './types';
5
+ import { EnvironmentType, InstanceInfo, LogEntry, OperatorInstanceInfo } from './types';
6
6
  import { Task } from './taskManager';
7
7
  export declare class InstanceManager {
8
8
  private _interval;
@@ -27,6 +27,7 @@ export declare class InstanceManager {
27
27
  stop(systemId: string, instanceId: string): Promise<void>;
28
28
  private stopInner;
29
29
  stopAllForPlan(systemId: string): Task<void>;
30
+ getInstanceOperator(systemId: string, instanceId: string, environment?: EnvironmentType): Promise<OperatorInstanceInfo>;
30
31
  start(systemId: string, instanceId: string): Promise<InstanceInfo | Task<InstanceInfo>>;
31
32
  /**
32
33
  * Stops an instance but does not remove it from the list of active instances
@@ -323,6 +323,48 @@ class InstanceManager {
323
323
  name: `Stopping plan ${systemId}`,
324
324
  });
325
325
  }
326
+ async getInstanceOperator(systemId, instanceId, environment) {
327
+ const blockInstance = await assetManager_1.assetManager.getBlockInstance(systemId, instanceId);
328
+ if (!blockInstance) {
329
+ throw new Error(`Instance not found: ${systemId}/${instanceId}`);
330
+ }
331
+ const blockRef = (0, nodejs_utils_1.normalizeKapetaUri)(blockInstance.block.ref);
332
+ const block = await assetManager_1.assetManager.getAsset(blockRef, true);
333
+ if (!block) {
334
+ throw new Error(`Block not found: ${blockRef}`);
335
+ }
336
+ const operatorDefinition = await definitionsManager_1.definitionsManager.getDefinition(block.kind);
337
+ if (!operatorDefinition?.definition.spec.local) {
338
+ throw new Error(`Operator block has no local definition: ${blockRef}`);
339
+ }
340
+ const localConfig = operatorDefinition.definition.spec.local;
341
+ let instance = await this.start(systemId, instanceId);
342
+ if (instance instanceof taskManager_1.Task) {
343
+ instance = await instance.wait();
344
+ }
345
+ const container = await containerManager_1.containerManager.get(instance.pid);
346
+ if (!container) {
347
+ throw new Error(`Container not found: ${instance.pid}`);
348
+ }
349
+ const portInfo = await container.getPorts();
350
+ if (!portInfo) {
351
+ throw new Error(`No ports found for instance: ${instanceId}`);
352
+ }
353
+ const hostname = serviceManager_1.serviceManager.getLocalHost(environment);
354
+ const ports = {};
355
+ Object.entries(portInfo).forEach(([key, value]) => {
356
+ ports[key] = {
357
+ protocol: value.protocol,
358
+ port: parseInt(value.hostPort),
359
+ };
360
+ });
361
+ return {
362
+ hostname,
363
+ ports,
364
+ credentials: localConfig.credentials,
365
+ options: localConfig.options,
366
+ };
367
+ }
326
368
  async start(systemId, instanceId) {
327
369
  return this.exclusive(systemId, instanceId, async () => {
328
370
  systemId = (0, nodejs_utils_1.normalizeKapetaUri)(systemId);
@@ -333,7 +375,7 @@ class InstanceManager {
333
375
  throw new Error('Block not found: ' + blockRef);
334
376
  }
335
377
  const existingInstance = this.getInstance(systemId, instanceId);
336
- if (existingInstance) {
378
+ if (existingInstance && existingInstance.pid) {
337
379
  if (existingInstance.status === types_1.InstanceStatus.READY) {
338
380
  // Instance is already running
339
381
  return existingInstance;
@@ -4,12 +4,12 @@
4
4
  */
5
5
  import { DefinitionInfo } from '@kapeta/local-cluster-config';
6
6
  import { ContainerInfo } from './containerManager';
7
- import { EnvironmentType, OperatorInfo } from './types';
7
+ import { EnvironmentType, LocalImageOptions, OperatorInfo } from './types';
8
8
  export declare const KIND_OPERATOR = "core/resource-type-operator";
9
9
  declare class Operator {
10
10
  private readonly _data;
11
11
  constructor(data: DefinitionInfo);
12
- getLocalData(): any;
12
+ getLocalData(): LocalImageOptions;
13
13
  getDefinitionInfo(): DefinitionInfo;
14
14
  getCredentials(): any;
15
15
  }
@@ -137,13 +137,8 @@ class OperatorManager {
137
137
  const portType = portTypes[i];
138
138
  let containerPortInfo = operatorData.ports[portType];
139
139
  const hostPort = await serviceManager_1.serviceManager.ensureServicePort(systemId, resourceType, portType);
140
- if (typeof containerPortInfo === 'number' || typeof containerPortInfo === 'string') {
141
- containerPortInfo = { port: containerPortInfo, type: 'tcp' };
142
- }
143
- if (!containerPortInfo.type) {
144
- containerPortInfo.type = 'tcp';
145
- }
146
- const portId = containerPortInfo.port + '/' + containerPortInfo.type;
140
+ const portInfo = (0, utils_1.toPortInfo)(containerPortInfo);
141
+ const portId = portInfo.port + '/' + portInfo.type;
147
142
  ports[portId] = {
148
143
  type: portType,
149
144
  hostPort,