@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.
- package/.github/workflows/check-license.yml +0 -1
- package/CHANGELOG.md +14 -0
- package/dist/cjs/src/config/routes.js +9 -0
- package/dist/cjs/src/containerManager.d.ts +2 -8
- package/dist/cjs/src/containerManager.js +4 -4
- package/dist/cjs/src/instanceManager.d.ts +2 -1
- package/dist/cjs/src/instanceManager.js +43 -1
- package/dist/cjs/src/operatorManager.d.ts +2 -2
- package/dist/cjs/src/operatorManager.js +2 -7
- package/dist/cjs/src/proxy/types/rest.js +2 -1
- package/dist/cjs/src/proxy/types/web.js +2 -1
- package/dist/cjs/src/serviceManager.d.ts +1 -0
- package/dist/cjs/src/serviceManager.js +9 -9
- package/dist/cjs/src/types.d.ts +39 -0
- package/dist/cjs/src/utils/BlockInstanceRunner.js +16 -12
- package/dist/cjs/src/utils/utils.d.ts +5 -1
- package/dist/cjs/src/utils/utils.js +11 -1
- package/dist/esm/src/config/routes.js +9 -0
- package/dist/esm/src/containerManager.d.ts +2 -8
- package/dist/esm/src/containerManager.js +4 -4
- package/dist/esm/src/instanceManager.d.ts +2 -1
- package/dist/esm/src/instanceManager.js +43 -1
- package/dist/esm/src/operatorManager.d.ts +2 -2
- package/dist/esm/src/operatorManager.js +2 -7
- package/dist/esm/src/proxy/types/rest.js +2 -1
- package/dist/esm/src/proxy/types/web.js +2 -1
- package/dist/esm/src/serviceManager.d.ts +1 -0
- package/dist/esm/src/serviceManager.js +9 -9
- package/dist/esm/src/types.d.ts +39 -0
- package/dist/esm/src/utils/BlockInstanceRunner.js +16 -12
- package/dist/esm/src/utils/utils.d.ts +5 -1
- package/dist/esm/src/utils/utils.js +11 -1
- package/package.json +6 -5
- package/src/config/routes.ts +15 -0
- package/src/containerManager.ts +5 -12
- package/src/instanceManager.ts +72 -4
- package/src/operatorManager.ts +5 -13
- package/src/proxy/types/rest.ts +2 -1
- package/src/proxy/types/web.ts +3 -2
- package/src/serviceManager.ts +11 -8
- package/src/types.ts +35 -0
- package/src/utils/BlockInstanceRunner.ts +21 -14
- 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 += '?' +
|
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 += '?' +
|
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
|
-
|
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
|
-
|
37
|
+
return 'host.docker.internal';
|
42
38
|
}
|
43
|
-
|
44
|
-
|
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://${
|
49
|
+
return `http://${hostname}:${port}/${path}`;
|
50
50
|
}
|
51
51
|
_ensureSystem(systemId) {
|
52
52
|
systemId = (0, nodejs_utils_1.normalizeKapetaUri)(systemId);
|
package/dist/esm/src/types.d.ts
CHANGED
@@ -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
|
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 =
|
302
|
+
const localPorts = local.ports ?? {};
|
303
|
+
const labels = {};
|
302
304
|
const promises = Object.entries(localPorts).map(async ([portType, value]) => {
|
303
|
-
const
|
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()}`] =
|
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 (
|
316
|
-
Object.entries(
|
319
|
+
if (local.env) {
|
320
|
+
Object.entries(local.env).forEach(([key, value]) => {
|
317
321
|
addonEnv[key] = value;
|
318
322
|
});
|
319
323
|
}
|
320
|
-
if (
|
321
|
-
|
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 (
|
325
|
-
HealthCheck = containerManager_1.containerManager.toDockerHealth(
|
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 =
|
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.
|
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/
|
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",
|
package/src/config/routes.ts
CHANGED
@@ -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;
|
package/src/containerManager.ts
CHANGED
@@ -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
|
-
|
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}_${
|
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]:
|
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.
|
993
|
+
const hostPort = name.substring(CONTAINER_LABEL_PORT_PREFIX.length);
|
1001
994
|
|
1002
995
|
portTypes[hostPort] = portType;
|
1003
996
|
});
|
package/src/instanceManager.ts
CHANGED
@@ -18,7 +18,19 @@ import {
|
|
18
18
|
HEALTH_CHECK_TIMEOUT,
|
19
19
|
} from './containerManager';
|
20
20
|
import { configManager } from './configManager';
|
21
|
-
import {
|
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);
|
package/src/operatorManager.ts
CHANGED
@@ -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
|
-
|
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,
|
package/src/proxy/types/rest.ts
CHANGED
@@ -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 += '?' +
|
109
|
+
providerPath += '?' + stringify(req.query, { arrayFormat: 'repeat' });
|
109
110
|
}
|
110
111
|
|
111
112
|
const requestHeaders = _.clone(req.headers);
|
package/src/proxy/types/web.ts
CHANGED
@@ -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 {
|
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 += '?' +
|
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);
|
package/src/serviceManager.ts
CHANGED
@@ -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
|
-
|
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://${
|
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;
|