@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
@@ -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;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kapeta/local-cluster-service",
3
- "version": "0.34.1",
3
+ "version": "0.35.0",
4
4
  "description": "Manages configuration, ports and service discovery for locally running Kapeta systems",
5
5
  "type": "commonjs",
6
6
  "exports": {
@@ -48,18 +48,18 @@
48
48
  "homepage": "https://github.com/kapetacom/local-cluster-service#readme",
49
49
  "dependencies": {
50
50
  "@kapeta/codegen": "^1.2.1",
51
- "@kapeta/local-cluster-config": ">= 0.3.0 <2",
51
+ "@kapeta/kaplang-core": "^1.9.3",
52
+ "@kapeta/local-cluster-config": ">= 0.3.2 <2",
52
53
  "@kapeta/nodejs-api-client": ">=0.1.3 <2",
53
54
  "@kapeta/nodejs-process": "<2",
54
55
  "@kapeta/nodejs-registry-utils": ">=0.9.4 <2",
55
56
  "@kapeta/nodejs-utils": "<2",
56
- "@kapeta/sdk-config": "^2.0.0",
57
- "@kapeta/web-microfrontend": "^1",
58
- "@kapeta/kaplang-core": "^1.9",
59
57
  "@kapeta/schemas": "^2.0.0",
58
+ "@kapeta/sdk-config": "^2.0.0",
60
59
  "@kapeta/ui-web-components": "^3.0.1",
61
60
  "@kapeta/ui-web-plan-editor": "^2.0.0",
62
61
  "@kapeta/ui-web-types": "^1.2.0",
62
+ "@kapeta/web-microfrontend": "^1",
63
63
  "@sentry/node": "^7.94.1",
64
64
  "@types/dockerode": "^3.3.19",
65
65
  "@types/stream-json": "^1.7.3",
@@ -75,6 +75,7 @@
75
75
  "md5": "2.2.1",
76
76
  "node-cache": "^5.1.2",
77
77
  "node-uuid": "^1.4.8",
78
+ "qs": "^6.11.2",
78
79
  "request": "2.88.2",
79
80
  "request-promise": "4.2.6",
80
81
  "socket.io": "^4.5.2",
@@ -181,4 +181,19 @@ router.get('/consumes/:resourceName/:type', (req: KapetaRequest, res) => {
181
181
  );
182
182
  });
183
183
 
184
+ /**
185
+ * Used by services to information about a block operator
186
+ *
187
+ * If the remote service is not already running it will be started
188
+ */
189
+ router.get('/operator/:instanceId', async (req: KapetaRequest, res) => {
190
+ const operatorInfo = await instanceManager.getInstanceOperator(
191
+ req.kapeta!.systemId,
192
+ req.params.instanceId,
193
+ req.kapeta!.environment
194
+ );
195
+
196
+ res.send(operatorInfo);
197
+ });
198
+
184
199
  export default router;
@@ -14,7 +14,7 @@ import ClusterConfiguration from '@kapeta/local-cluster-config';
14
14
  import uuid from 'node-uuid';
15
15
  import md5 from 'md5';
16
16
  import { getBlockInstanceContainerName } from './utils/utils';
17
- import { InstanceInfo, LogEntry, LogSource } from './types';
17
+ import { Health, InstanceInfo, LogEntry, LogSource } from './types';
18
18
  import { KapetaAPI } from '@kapeta/nodejs-api-client';
19
19
  import { taskManager, Task } from './taskManager';
20
20
  import { EventEmitter } from 'node:events';
@@ -76,13 +76,6 @@ interface JSONMessage<T = string> {
76
76
  aux?: any;
77
77
  }
78
78
 
79
- interface Health {
80
- cmd: string;
81
- interval?: number;
82
- timeout?: number;
83
- retries?: number;
84
- }
85
-
86
79
  export const CONTAINER_LABEL_PORT_PREFIX = 'kapeta_port-';
87
80
  const NANO_SECOND = 1000000;
88
81
  const HEALTH_CHECK_INTERVAL = 3000;
@@ -255,7 +248,7 @@ class ContainerManager {
255
248
 
256
249
  async createVolumes(
257
250
  systemId: string,
258
- kind: string,
251
+ serviceId: string,
259
252
  mountOpts: StringMap | null | undefined
260
253
  ): Promise<DockerMounts[]> {
261
254
  const Mounts: DockerMounts[] = [];
@@ -263,7 +256,7 @@ class ContainerManager {
263
256
  if (mountOpts) {
264
257
  const mountOptList = Object.entries(mountOpts);
265
258
  for (const [mountName, containerPath] of mountOptList) {
266
- const volumeName = `${systemId}_${kind}_${mountName}`.replace(/[^a-z0-9]/gi, '_');
259
+ const volumeName = `${systemId}_${serviceId}_${mountName}`.replace(/[^a-z0-9]/gi, '_');
267
260
 
268
261
  Mounts.push({
269
262
  Target: containerPath,
@@ -273,7 +266,7 @@ class ContainerManager {
273
266
  Consistency: 'consistent',
274
267
  Labels: {
275
268
  [COMPOSE_LABEL_PROJECT]: systemId.replace(/[^a-z0-9]/gi, '_'),
276
- [COMPOSE_LABEL_SERVICE]: kind.replace(/[^a-z0-9]/gi, '_'),
269
+ [COMPOSE_LABEL_SERVICE]: serviceId.replace(/[^a-z0-9]/gi, '_'),
277
270
  },
278
271
  });
279
272
  }
@@ -997,7 +990,7 @@ export class ContainerInfo {
997
990
  return;
998
991
  }
999
992
 
1000
- const hostPort = name.substr(CONTAINER_LABEL_PORT_PREFIX.length);
993
+ const hostPort = name.substring(CONTAINER_LABEL_PORT_PREFIX.length);
1001
994
 
1002
995
  portTypes[hostPort] = portType;
1003
996
  });
@@ -18,7 +18,19 @@ import {
18
18
  HEALTH_CHECK_TIMEOUT,
19
19
  } from './containerManager';
20
20
  import { configManager } from './configManager';
21
- import { DesiredInstanceStatus, InstanceInfo, InstanceOwner, InstanceStatus, InstanceType, LogEntry } from './types';
21
+ import {
22
+ DesiredInstanceStatus,
23
+ EnvironmentType,
24
+ InstanceInfo,
25
+ InstanceOwner,
26
+ InstanceStatus,
27
+ InstanceType,
28
+ LocalImageOptions,
29
+ LogEntry,
30
+ OperatorInfo,
31
+ OperatorInstanceInfo,
32
+ OperatorInstancePort,
33
+ } from './types';
22
34
  import { BlockDefinitionSpec, BlockInstance, Plan } from '@kapeta/schemas';
23
35
  import { getBlockInstanceContainerName, getResolvedConfiguration } from './utils/utils';
24
36
  import { KIND_OPERATOR, operatorManager } from './operatorManager';
@@ -414,6 +426,62 @@ export class InstanceManager {
414
426
  );
415
427
  }
416
428
 
429
+ public async getInstanceOperator(
430
+ systemId: string,
431
+ instanceId: string,
432
+ environment?: EnvironmentType
433
+ ): Promise<OperatorInstanceInfo> {
434
+ const blockInstance = await assetManager.getBlockInstance(systemId, instanceId);
435
+ if (!blockInstance) {
436
+ throw new Error(`Instance not found: ${systemId}/${instanceId}`);
437
+ }
438
+ const blockRef = normalizeKapetaUri(blockInstance.block.ref);
439
+ const block = await assetManager.getAsset(blockRef, true);
440
+ if (!block) {
441
+ throw new Error(`Block not found: ${blockRef}`);
442
+ }
443
+
444
+ const operatorDefinition = await definitionsManager.getDefinition(block.kind);
445
+
446
+ if (!operatorDefinition?.definition.spec.local) {
447
+ throw new Error(`Operator block has no local definition: ${blockRef}`);
448
+ }
449
+
450
+ const localConfig = operatorDefinition.definition.spec.local as LocalImageOptions;
451
+
452
+ let instance = await this.start(systemId, instanceId);
453
+ if (instance instanceof Task) {
454
+ instance = await instance.wait();
455
+ }
456
+
457
+ const container = await containerManager.get(instance.pid as string);
458
+ if (!container) {
459
+ throw new Error(`Container not found: ${instance.pid}`);
460
+ }
461
+
462
+ const portInfo = await container.getPorts();
463
+ if (!portInfo) {
464
+ throw new Error(`No ports found for instance: ${instanceId}`);
465
+ }
466
+
467
+ const hostname = serviceManager.getLocalHost(environment);
468
+ const ports: { [key: string]: OperatorInstancePort } = {};
469
+
470
+ Object.entries(portInfo).forEach(([key, value]) => {
471
+ ports[key] = {
472
+ protocol: value.protocol,
473
+ port: parseInt(value.hostPort),
474
+ };
475
+ });
476
+
477
+ return {
478
+ hostname,
479
+ ports,
480
+ credentials: localConfig.credentials,
481
+ options: localConfig.options,
482
+ };
483
+ }
484
+
417
485
  public async start(systemId: string, instanceId: string): Promise<InstanceInfo | Task<InstanceInfo>> {
418
486
  return this.exclusive(systemId, instanceId, async () => {
419
487
  systemId = normalizeKapetaUri(systemId);
@@ -427,7 +495,7 @@ export class InstanceManager {
427
495
 
428
496
  const existingInstance = this.getInstance(systemId, instanceId);
429
497
 
430
- if (existingInstance) {
498
+ if (existingInstance && existingInstance.pid) {
431
499
  if (existingInstance.status === InstanceStatus.READY) {
432
500
  // Instance is already running
433
501
  return existingInstance;
@@ -481,8 +549,8 @@ export class InstanceManager {
481
549
  return Promise.resolve();
482
550
  }
483
551
  // Check if the operator has a local definition, if not we skip it since we can't start it
484
- if(!asset.definition.spec.local) {
485
- console.log('Skipping operator since it as no local definition: %s', consumer.kind)
552
+ if (!asset.definition.spec.local) {
553
+ console.log('Skipping operator since it as no local definition: %s', consumer.kind);
486
554
  return Promise.resolve();
487
555
  }
488
556
  console.log('Ensuring resource: %s in %s', consumerUri.id, systemId);
@@ -16,10 +16,10 @@ import {
16
16
  containerManager,
17
17
  } from './containerManager';
18
18
  import FSExtra from 'fs-extra';
19
- import { AnyMap, EnvironmentType, OperatorInfo, StringMap } from './types';
19
+ import { AnyMap, EnvironmentType, LocalImageOptions, OperatorInfo, StringMap } from './types';
20
20
  import { BlockInstance, Resource } from '@kapeta/schemas';
21
21
  import { definitionsManager } from './definitionsManager';
22
- import { getBindHost } from './utils/utils';
22
+ import { getBindHost, toPortInfo } from './utils/utils';
23
23
  import { parseKapetaUri, normalizeKapetaUri } from '@kapeta/nodejs-utils';
24
24
  import _ from 'lodash';
25
25
  import AsyncLock from 'async-lock';
@@ -34,7 +34,7 @@ class Operator {
34
34
  this._data = data;
35
35
  }
36
36
 
37
- getLocalData() {
37
+ getLocalData(): LocalImageOptions {
38
38
  return this._data.definition.spec.local;
39
39
  }
40
40
 
@@ -187,16 +187,8 @@ class OperatorManager {
187
187
  const portType = portTypes[i];
188
188
  let containerPortInfo = operatorData.ports[portType];
189
189
  const hostPort = await serviceManager.ensureServicePort(systemId, resourceType, portType);
190
-
191
- if (typeof containerPortInfo === 'number' || typeof containerPortInfo === 'string') {
192
- containerPortInfo = { port: containerPortInfo, type: 'tcp' };
193
- }
194
-
195
- if (!containerPortInfo.type) {
196
- containerPortInfo.type = 'tcp';
197
- }
198
-
199
- const portId = containerPortInfo.port + '/' + containerPortInfo.type;
190
+ const portInfo = toPortInfo(containerPortInfo);
191
+ const portId = portInfo.port + '/' + portInfo.type;
200
192
 
201
193
  ports[portId] = {
202
194
  type: portType,
@@ -14,6 +14,7 @@ import { Request, Response } from 'express';
14
14
  import { ProxyRequestInfo, SimpleRequest, StringMap } from '../../types';
15
15
  import { StringBodyRequest } from '../../middleware/stringBody';
16
16
  import { Resource } from '@kapeta/schemas';
17
+ import { stringify } from 'qs';
17
18
 
18
19
  export function getRestMethodId(restResource: Resource, httpMethod: string, httpPath: string) {
19
20
  return _.findKey(restResource.spec.methods, (method) => {
@@ -105,7 +106,7 @@ export function proxyRestRequest(req: StringBodyRequest, res: Response, opts: Pr
105
106
  }
106
107
 
107
108
  if (!_.isEmpty(req.query)) {
108
- providerPath += '?' + new URLSearchParams(req.query as any).toString();
109
+ providerPath += '?' + stringify(req.query, { arrayFormat: 'repeat' });
109
110
  }
110
111
 
111
112
  const requestHeaders = _.clone(req.headers);
@@ -7,9 +7,10 @@ import request from 'request';
7
7
  import _ from 'lodash';
8
8
  import { networkManager } from '../../networkManager';
9
9
  import { socketManager } from '../../socketManager';
10
- import { Request, Response } from 'express';
10
+ import { Response } from 'express';
11
11
  import { ProxyRequestInfo, SimpleRequest, StringMap } from '../../types';
12
12
  import { StringBodyRequest } from '../../middleware/stringBody';
13
+ import { stringify } from 'qs';
13
14
 
14
15
  export function proxyHttpRequest(req: StringBodyRequest, res: Response, opts: ProxyRequestInfo) {
15
16
  const requestHeaders = _.clone(req.headers);
@@ -28,7 +29,7 @@ export function proxyHttpRequest(req: StringBodyRequest, res: Response, opts: Pr
28
29
  }
29
30
 
30
31
  if (!_.isEmpty(req.query)) {
31
- path += '?' + new URLSearchParams(req.query as any).toString();
32
+ path += '?' + stringify(req.query, { arrayFormat: 'repeat' });
32
33
  }
33
34
 
34
35
  console.log('Proxy request to provider: %s => %s%s [http]', opts.consumerPath, opts.address, path);
@@ -34,22 +34,25 @@ class ServiceManager {
34
34
  });
35
35
  }
36
36
 
37
+ public getLocalHost(environmentType?: EnvironmentType) {
38
+ if (environmentType === 'docker') {
39
+ //We're inside a docker container, so we can use this special host name to access the host machine
40
+ return 'host.docker.internal';
41
+ }
42
+
43
+ return clusterService.getClusterServiceHost();
44
+ }
45
+
37
46
  _forLocal(port: string | number, path?: string, environmentType?: EnvironmentType) {
38
47
  if (!path) {
39
48
  path = '';
40
49
  }
41
- let host;
42
- if (environmentType === 'docker') {
43
- //We're inside a docker container, so we can use this special host name to access the host machine
44
- host = 'host.docker.internal';
45
- } else {
46
- host = clusterService.getClusterServiceHost();
47
- }
50
+ const hostname = this.getLocalHost(environmentType);
48
51
 
49
52
  if (path.startsWith('/')) {
50
53
  path = path.substring(1);
51
54
  }
52
- return `http://${host}:${port}/${path}`;
55
+ return `http://${hostname}:${port}/${path}`;
53
56
  }
54
57
 
55
58
  _ensureSystem(systemId: string) {
package/src/types.ts CHANGED
@@ -61,6 +61,26 @@ export type ProcessInfo = {
61
61
  portType?: string;
62
62
  };
63
63
 
64
+ export interface Health {
65
+ cmd: string;
66
+ interval?: number;
67
+ timeout?: number;
68
+ retries?: number;
69
+ }
70
+
71
+ export type PortInfo = { port: number; type: 'tcp' | 'udp' } | number | string;
72
+
73
+ export type LocalImageOptions<Credentials = AnyMap, Options = AnyMap> = {
74
+ image: string;
75
+ ports: { [key: string]: PortInfo };
76
+ credentials?: Credentials;
77
+ options?: Options;
78
+ cmd?: string;
79
+ env?: AnyMap;
80
+ health?: Health;
81
+ mounts?: { [key: string]: string };
82
+ };
83
+
64
84
  export type InstanceInfo = {
65
85
  systemId: string;
66
86
  instanceId: string;
@@ -86,6 +106,21 @@ interface ResourceRef {
86
106
 
87
107
  export type ProxyRequestHandler = (req: StringBodyRequest, res: express.Response, info: ProxyRequestInfo) => void;
88
108
 
109
+ export interface OperatorInstancePort {
110
+ protocol: string;
111
+ port: number;
112
+ }
113
+
114
+ export interface OperatorInstanceInfo {
115
+ hostname: string;
116
+ ports: { [portType: string]: OperatorInstancePort };
117
+ path?: string;
118
+ query?: string;
119
+ hash?: string;
120
+ options?: AnyMap;
121
+ credentials?: AnyMap;
122
+ }
123
+
89
124
  export interface OperatorInfo {
90
125
  host: string;
91
126
  port: string;