@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.
- package/CHANGELOG.md +14 -0
- package/dist/cjs/index.js +4 -1
- package/dist/cjs/src/assetManager.d.ts +2 -1
- package/dist/cjs/src/assetManager.js +3 -2
- package/dist/cjs/src/config/routes.js +2 -2
- package/dist/cjs/src/containerManager.d.ts +6 -3
- package/dist/cjs/src/containerManager.js +101 -21
- package/dist/cjs/src/instanceManager.d.ts +4 -2
- package/dist/cjs/src/instanceManager.js +71 -32
- package/dist/cjs/src/operatorManager.d.ts +6 -3
- package/dist/cjs/src/operatorManager.js +32 -23
- package/dist/cjs/src/progressListener.d.ts +8 -1
- package/dist/cjs/src/progressListener.js +12 -1
- package/dist/cjs/src/repositoryManager.js +3 -2
- package/dist/cjs/src/serviceManager.d.ts +0 -1
- package/dist/cjs/src/serviceManager.js +2 -8
- package/dist/cjs/src/taskManager.d.ts +2 -0
- package/dist/cjs/src/taskManager.js +9 -0
- package/dist/cjs/src/types.d.ts +1 -48
- package/dist/cjs/src/types.js +2 -1
- package/dist/cjs/src/utils/BlockInstanceRunner.js +45 -33
- package/dist/cjs/src/utils/InternalConfigProvider.d.ts +38 -0
- package/dist/cjs/src/utils/InternalConfigProvider.js +146 -0
- package/dist/cjs/src/utils/commandLineUtils.d.ts +2 -1
- package/dist/cjs/src/utils/commandLineUtils.js +7 -1
- package/dist/cjs/src/utils/utils.d.ts +26 -4
- package/dist/cjs/src/utils/utils.js +48 -8
- package/dist/esm/index.js +4 -1
- package/dist/esm/src/assetManager.d.ts +2 -1
- package/dist/esm/src/assetManager.js +3 -2
- package/dist/esm/src/config/routes.js +2 -2
- package/dist/esm/src/containerManager.d.ts +6 -3
- package/dist/esm/src/containerManager.js +101 -21
- package/dist/esm/src/instanceManager.d.ts +4 -2
- package/dist/esm/src/instanceManager.js +71 -32
- package/dist/esm/src/operatorManager.d.ts +6 -3
- package/dist/esm/src/operatorManager.js +32 -23
- package/dist/esm/src/progressListener.d.ts +8 -1
- package/dist/esm/src/progressListener.js +12 -1
- package/dist/esm/src/repositoryManager.js +3 -2
- package/dist/esm/src/serviceManager.d.ts +0 -1
- package/dist/esm/src/serviceManager.js +2 -8
- package/dist/esm/src/taskManager.d.ts +2 -0
- package/dist/esm/src/taskManager.js +9 -0
- package/dist/esm/src/types.d.ts +1 -48
- package/dist/esm/src/types.js +2 -1
- package/dist/esm/src/utils/BlockInstanceRunner.js +45 -33
- package/dist/esm/src/utils/InternalConfigProvider.d.ts +38 -0
- package/dist/esm/src/utils/InternalConfigProvider.js +146 -0
- package/dist/esm/src/utils/commandLineUtils.d.ts +2 -1
- package/dist/esm/src/utils/commandLineUtils.js +7 -1
- package/dist/esm/src/utils/utils.d.ts +26 -4
- package/dist/esm/src/utils/utils.js +48 -8
- package/index.ts +5 -2
- package/package.json +16 -14
- package/src/assetManager.ts +5 -4
- package/src/config/routes.ts +4 -2
- package/src/containerManager.ts +115 -26
- package/src/instanceManager.ts +86 -44
- package/src/operatorManager.ts +48 -40
- package/src/progressListener.ts +15 -1
- package/src/repositoryManager.ts +5 -3
- package/src/serviceManager.ts +3 -11
- package/src/taskManager.ts +11 -0
- package/src/types.ts +2 -50
- package/src/utils/BlockInstanceRunner.ts +60 -44
- package/src/utils/InternalConfigProvider.ts +214 -0
- package/src/utils/commandLineUtils.ts +10 -2
- package/src/utils/utils.ts +53 -10
@@ -0,0 +1,146 @@
|
|
1
|
+
"use strict";
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
4
|
+
};
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
6
|
+
exports.createInternalConfigProvider = exports.InternalConfigProvider = void 0;
|
7
|
+
const nodejs_utils_1 = require("@kapeta/nodejs-utils");
|
8
|
+
const configManager_1 = require("../configManager");
|
9
|
+
const lodash_1 = __importDefault(require("lodash"));
|
10
|
+
const serviceManager_1 = require("../serviceManager");
|
11
|
+
const operatorManager_1 = require("../operatorManager");
|
12
|
+
const instanceManager_1 = require("../instanceManager");
|
13
|
+
const definitionsManager_1 = require("../definitionsManager");
|
14
|
+
const utils_1 = require("./utils");
|
15
|
+
/**
|
16
|
+
* A configuration provider that does the same as the LocalConfigProvider
|
17
|
+
* but without calling the API of the local cluster service (since it's running in the same process)
|
18
|
+
*/
|
19
|
+
class InternalConfigProvider {
|
20
|
+
info;
|
21
|
+
systemId;
|
22
|
+
instanceId;
|
23
|
+
config;
|
24
|
+
environment;
|
25
|
+
constructor(systemId, instanceId, info, config, environment = 'docker') {
|
26
|
+
this.info = info;
|
27
|
+
this.systemId = (0, nodejs_utils_1.normalizeKapetaUri)(systemId);
|
28
|
+
this.instanceId = instanceId;
|
29
|
+
this.config = config;
|
30
|
+
this.environment = environment;
|
31
|
+
}
|
32
|
+
getBlockDefinition() {
|
33
|
+
return this.info.definition;
|
34
|
+
}
|
35
|
+
getBlockReference() {
|
36
|
+
return (0, nodejs_utils_1.normalizeKapetaUri)(this.info.definition.metadata.name + ':' + this.info.version);
|
37
|
+
}
|
38
|
+
getSystemId() {
|
39
|
+
return this.systemId;
|
40
|
+
}
|
41
|
+
getInstanceId() {
|
42
|
+
return this.instanceId;
|
43
|
+
}
|
44
|
+
getServerPort(portType) {
|
45
|
+
return serviceManager_1.serviceManager.ensureServicePort(this.systemId, this.instanceId, portType);
|
46
|
+
}
|
47
|
+
async getServiceAddress(serviceName, portType) {
|
48
|
+
return serviceManager_1.serviceManager.getConsumerAddress(this.systemId, this.instanceId, serviceName, portType, this.environment);
|
49
|
+
}
|
50
|
+
getResourceInfo(resourceType, portType, resourceName) {
|
51
|
+
return operatorManager_1.operatorManager.getConsumerResourceInfo(this.systemId, this.instanceId, resourceType, portType, resourceName, this.environment, false);
|
52
|
+
}
|
53
|
+
async getInstanceHost(instanceId) {
|
54
|
+
const instance = instanceManager_1.instanceManager.getInstance(this.systemId, instanceId);
|
55
|
+
return instance?.address ?? null;
|
56
|
+
}
|
57
|
+
async getServerHost() {
|
58
|
+
return (0, utils_1.getBindAddressForEnvironment)(this.environment);
|
59
|
+
}
|
60
|
+
getProviderId() {
|
61
|
+
return 'internal';
|
62
|
+
}
|
63
|
+
getOrDefault(path, defaultValue) {
|
64
|
+
return this.get(path) ?? defaultValue;
|
65
|
+
}
|
66
|
+
get(path) {
|
67
|
+
return lodash_1.default.get(this.config, path);
|
68
|
+
}
|
69
|
+
getInstanceOperator(instanceId) {
|
70
|
+
return instanceManager_1.instanceManager.getInstanceOperator(this.systemId, instanceId, this.environment, false);
|
71
|
+
}
|
72
|
+
async getInstanceForConsumer(resourceName) {
|
73
|
+
const plan = await this.getPlan();
|
74
|
+
if (!plan) {
|
75
|
+
throw new Error('Could not find plan');
|
76
|
+
}
|
77
|
+
const instanceId = this.getInstanceId();
|
78
|
+
const connection = plan.spec.connections.find((connection) => connection.consumer.blockId === instanceId && connection.consumer.resourceName === resourceName);
|
79
|
+
if (!connection) {
|
80
|
+
throw new Error(`Could not find connection for consumer ${resourceName}`);
|
81
|
+
}
|
82
|
+
const instance = plan.spec.blocks.find((b) => b.id === connection.provider.blockId);
|
83
|
+
if (!instance) {
|
84
|
+
throw new Error(`Could not find instance ${connection.provider.blockId} in plan`);
|
85
|
+
}
|
86
|
+
const block = await this.getBlock(instance.block.ref);
|
87
|
+
if (!block) {
|
88
|
+
throw new Error(`Could not find block ${instance.block.ref} in plan`);
|
89
|
+
}
|
90
|
+
return {
|
91
|
+
instanceId: connection.provider.blockId,
|
92
|
+
connections: [connection],
|
93
|
+
block: block,
|
94
|
+
};
|
95
|
+
}
|
96
|
+
async getInstancesForProvider(resourceName) {
|
97
|
+
const plan = await this.getPlan();
|
98
|
+
if (!plan) {
|
99
|
+
throw new Error('Could not find plan');
|
100
|
+
}
|
101
|
+
const instanceId = this.getInstanceId();
|
102
|
+
const blockDetails = {};
|
103
|
+
const connections = plan.spec.connections.filter((connection) => connection.provider.blockId === instanceId && connection.provider.resourceName === resourceName);
|
104
|
+
for (const connection of connections) {
|
105
|
+
const blockInstanceId = connection.consumer.blockId;
|
106
|
+
if (blockDetails[blockInstanceId]) {
|
107
|
+
blockDetails[blockInstanceId].connections.push(connection);
|
108
|
+
continue;
|
109
|
+
}
|
110
|
+
const instance = plan.spec.blocks.find((b) => b.id === blockInstanceId);
|
111
|
+
if (!instance) {
|
112
|
+
throw new Error(`Could not find instance ${blockInstanceId} in plan`);
|
113
|
+
}
|
114
|
+
const block = await this.getBlock(instance.block.ref);
|
115
|
+
if (!block) {
|
116
|
+
throw new Error(`Could not find block ${instance.block.ref} in plan`);
|
117
|
+
}
|
118
|
+
blockDetails[blockInstanceId] = {
|
119
|
+
instanceId: blockInstanceId,
|
120
|
+
connections: [connection],
|
121
|
+
block: block,
|
122
|
+
};
|
123
|
+
}
|
124
|
+
return Object.values(blockDetails);
|
125
|
+
}
|
126
|
+
async getBlock(ref) {
|
127
|
+
const definition = await definitionsManager_1.definitionsManager.getDefinition(ref);
|
128
|
+
if (!definition) {
|
129
|
+
throw new Error(`Could not find definition for ${ref}`);
|
130
|
+
}
|
131
|
+
return definition.definition;
|
132
|
+
}
|
133
|
+
async getPlan() {
|
134
|
+
const definition = await definitionsManager_1.definitionsManager.getDefinition(this.systemId);
|
135
|
+
if (!definition) {
|
136
|
+
throw new Error(`Could not find plan ${this.systemId}`);
|
137
|
+
}
|
138
|
+
return definition.definition;
|
139
|
+
}
|
140
|
+
}
|
141
|
+
exports.InternalConfigProvider = InternalConfigProvider;
|
142
|
+
async function createInternalConfigProvider(systemId, instanceId, info) {
|
143
|
+
const config = await configManager_1.configManager.getConfigForBlockInstance(systemId, instanceId);
|
144
|
+
return new InternalConfigProvider(systemId, instanceId, info, config);
|
145
|
+
}
|
146
|
+
exports.createInternalConfigProvider = createInternalConfigProvider;
|
@@ -2,6 +2,7 @@
|
|
2
2
|
* Copyright 2023 Kapeta Inc.
|
3
3
|
* SPDX-License-Identifier: BUSL-1.1
|
4
4
|
*/
|
5
|
+
import { Task } from '../taskManager';
|
5
6
|
export declare function hasCLI(): Promise<boolean>;
|
6
|
-
export declare function ensureCLI(): Promise<
|
7
|
+
export declare function ensureCLI(): Promise<Task<void> | null>;
|
7
8
|
export declare function ensureCLICommands(): Promise<void>;
|
@@ -15,10 +15,16 @@ async function ensureCLI() {
|
|
15
15
|
if (await hasCLI()) {
|
16
16
|
return null;
|
17
17
|
}
|
18
|
-
return taskManager_1.taskManager.add(`cli:install`, () => {
|
18
|
+
return taskManager_1.taskManager.add(`cli:install`, (task) => {
|
19
19
|
const process = (0, nodejs_process_1.spawn)('npm', ['install', '-g', '@kapeta/kap'], {
|
20
20
|
shell: true,
|
21
21
|
});
|
22
|
+
process.process.stdout?.on('data', (data) => {
|
23
|
+
task.addLog(data.toString(), 'INFO');
|
24
|
+
});
|
25
|
+
process.process.stderr?.on('data', (data) => {
|
26
|
+
task.addLog(data.toString(), 'ERROR');
|
27
|
+
});
|
22
28
|
return process.wait();
|
23
29
|
}, {
|
24
30
|
name: `Installing Kapeta CLI`,
|
@@ -2,17 +2,39 @@
|
|
2
2
|
* Copyright 2023 Kapeta Inc.
|
3
3
|
* SPDX-License-Identifier: BUSL-1.1
|
4
4
|
*/
|
5
|
-
import { EntityList } from '@kapeta/schemas';
|
6
|
-
import { AnyMap,
|
5
|
+
import { EntityList, LocalInstance, LocalInstancePort } from '@kapeta/schemas';
|
6
|
+
import { AnyMap, EnvironmentType } from '../types';
|
7
7
|
export declare function getBlockInstanceContainerName(systemId: string, instanceId: string, blockType?: string): Promise<string>;
|
8
|
-
export declare function toPortInfo(port:
|
8
|
+
export declare function toPortInfo(port: LocalInstancePort): LocalInstancePort | {
|
9
9
|
port: number;
|
10
10
|
type: string;
|
11
11
|
};
|
12
|
+
export declare function getOperatorInstancePorts(systemId: string, operatorId: string, local: LocalInstance): Promise<{
|
13
|
+
portType: string;
|
14
|
+
port: number | undefined;
|
15
|
+
hostPort: any;
|
16
|
+
protocol: string | undefined;
|
17
|
+
}[]>;
|
18
|
+
/**
|
19
|
+
* Gets the hostname where all services are available - including the cluster service.
|
20
|
+
*
|
21
|
+
* For docker this is the internal docker host - otherwise it's the local machine
|
22
|
+
* Assumed to be the same address as the cluster service outside docker.
|
23
|
+
*/
|
24
|
+
export declare function getRemoteHostForEnvironment(environment: EnvironmentType | undefined): string;
|
25
|
+
/**
|
26
|
+
* Get the bind address for the given environment.
|
27
|
+
*
|
28
|
+
* Outside of docker we bind to 127.0.0.1 - inside we bind to everything (0.0.0.0)
|
29
|
+
*/
|
30
|
+
export declare function getBindAddressForEnvironment(environment: EnvironmentType | undefined, preferredHost?: string): string;
|
31
|
+
/**
|
32
|
+
* Get the docker host IP address for port binding.
|
33
|
+
*/
|
34
|
+
export declare function getDockerHostIp(preferredHost?: string): string;
|
12
35
|
export declare function getRemoteUrl(id: string, defautValue: string): any;
|
13
36
|
export declare function readYML(path: string): any;
|
14
37
|
export declare function isWindows(): boolean;
|
15
38
|
export declare function isMac(): boolean;
|
16
39
|
export declare function isLinux(): boolean;
|
17
|
-
export declare function getBindHost(preferredHost?: string): string;
|
18
40
|
export declare function getResolvedConfiguration(entities?: EntityList, config?: AnyMap, globalConfiguration?: AnyMap): AnyMap;
|
@@ -7,16 +7,19 @@ 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.
|
10
|
+
exports.getResolvedConfiguration = exports.isLinux = exports.isMac = exports.isWindows = exports.readYML = exports.getRemoteUrl = exports.getDockerHostIp = exports.getBindAddressForEnvironment = exports.getRemoteHostForEnvironment = exports.getOperatorInstancePorts = 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"));
|
14
|
+
const schemas_1 = require("@kapeta/schemas");
|
14
15
|
const lodash_1 = __importDefault(require("lodash"));
|
15
16
|
const types_1 = require("../types");
|
16
17
|
const local_cluster_config_1 = __importDefault(require("@kapeta/local-cluster-config"));
|
17
18
|
const definitionsManager_1 = require("../definitionsManager");
|
18
19
|
const nodejs_utils_1 = require("@kapeta/nodejs-utils");
|
19
20
|
const assetManager_1 = require("../assetManager");
|
21
|
+
const serviceManager_1 = require("../serviceManager");
|
22
|
+
const clusterService_1 = require("../clusterService");
|
20
23
|
async function getBlockInstanceContainerName(systemId, instanceId, blockType) {
|
21
24
|
if (!blockType) {
|
22
25
|
const instance = await assetManager_1.assetManager.getBlockInstance(systemId, instanceId);
|
@@ -45,11 +48,54 @@ function toPortInfo(port) {
|
|
45
48
|
return { port: parseInt(`${port}`), type: 'tcp' };
|
46
49
|
}
|
47
50
|
if (!port.type) {
|
48
|
-
port.type =
|
51
|
+
port.type = schemas_1.LocalInstancePortType.TCP;
|
49
52
|
}
|
50
53
|
return port;
|
51
54
|
}
|
52
55
|
exports.toPortInfo = toPortInfo;
|
56
|
+
async function getOperatorInstancePorts(systemId, operatorId, local) {
|
57
|
+
const localPorts = local.ports ?? {};
|
58
|
+
const promises = Object.entries(localPorts).map(async ([portType, value]) => {
|
59
|
+
const portInfo = toPortInfo(value);
|
60
|
+
const hostPort = await serviceManager_1.serviceManager.ensureServicePort(systemId, operatorId, portType);
|
61
|
+
return {
|
62
|
+
portType,
|
63
|
+
port: portInfo.port,
|
64
|
+
hostPort,
|
65
|
+
protocol: portInfo.type,
|
66
|
+
};
|
67
|
+
});
|
68
|
+
return await Promise.all(promises);
|
69
|
+
}
|
70
|
+
exports.getOperatorInstancePorts = getOperatorInstancePorts;
|
71
|
+
/**
|
72
|
+
* Gets the hostname where all services are available - including the cluster service.
|
73
|
+
*
|
74
|
+
* For docker this is the internal docker host - otherwise it's the local machine
|
75
|
+
* Assumed to be the same address as the cluster service outside docker.
|
76
|
+
*/
|
77
|
+
function getRemoteHostForEnvironment(environment) {
|
78
|
+
return environment === 'docker' ? types_1.DOCKER_HOST_INTERNAL : clusterService_1.clusterService.getClusterServiceHost();
|
79
|
+
}
|
80
|
+
exports.getRemoteHostForEnvironment = getRemoteHostForEnvironment;
|
81
|
+
/**
|
82
|
+
* Get the bind address for the given environment.
|
83
|
+
*
|
84
|
+
* Outside of docker we bind to 127.0.0.1 - inside we bind to everything (0.0.0.0)
|
85
|
+
*/
|
86
|
+
function getBindAddressForEnvironment(environment, preferredHost = '127.0.0.1') {
|
87
|
+
return environment === 'docker' ? '0.0.0.0' : preferredHost;
|
88
|
+
}
|
89
|
+
exports.getBindAddressForEnvironment = getBindAddressForEnvironment;
|
90
|
+
/**
|
91
|
+
* Get the docker host IP address for port binding.
|
92
|
+
*/
|
93
|
+
function getDockerHostIp(preferredHost = '127.0.0.1') {
|
94
|
+
// On Linux we need to bind to 0.0.0.0 to be able to connect to it from docker containers.
|
95
|
+
// TODO: This might pose a security risk - so we should authenticate all requests using a shared secret/nonce that we pass around.
|
96
|
+
return isLinux() ? '0.0.0.0' : preferredHost;
|
97
|
+
}
|
98
|
+
exports.getDockerHostIp = getDockerHostIp;
|
53
99
|
function getRemoteUrl(id, defautValue) {
|
54
100
|
const remoteConfig = local_cluster_config_1.default.getClusterConfig().remote;
|
55
101
|
return remoteConfig?.[id] ?? defautValue;
|
@@ -77,12 +123,6 @@ function isLinux() {
|
|
77
123
|
return !isWindows() && !isMac();
|
78
124
|
}
|
79
125
|
exports.isLinux = isLinux;
|
80
|
-
function getBindHost(preferredHost = '127.0.0.1') {
|
81
|
-
// On Linux we need to bind to 0.0.0.0 to be able to connect to it from docker containers.
|
82
|
-
// TODO: This might pose a security risk - so we should authenticate all requests using a shared secret/nonce that we pass around.
|
83
|
-
return isLinux() ? '0.0.0.0' : preferredHost;
|
84
|
-
}
|
85
|
-
exports.getBindHost = getBindHost;
|
86
126
|
function getResolvedConfiguration(entities, config, globalConfiguration) {
|
87
127
|
if (!entities || !globalConfiguration) {
|
88
128
|
return config || {};
|
package/index.ts
CHANGED
@@ -24,7 +24,7 @@ import AttachmentRoutes from './src/attachments/routes';
|
|
24
24
|
import TaskRoutes from './src/tasks/routes';
|
25
25
|
import APIRoutes from './src/api';
|
26
26
|
import AIRoutes from './src/ai/routes';
|
27
|
-
import {
|
27
|
+
import { isLinux } from './src/utils/utils';
|
28
28
|
import request from 'request';
|
29
29
|
import { repositoryManager } from './src/repositoryManager';
|
30
30
|
import { taskManager } from './src/taskManager';
|
@@ -223,7 +223,10 @@ export default {
|
|
223
223
|
reject(err);
|
224
224
|
});
|
225
225
|
|
226
|
-
|
226
|
+
// On Linux we need to bind to 0.0.0.0 to be able to connect to it from docker containers.
|
227
|
+
// TODO: This might pose a security risk - so we should authenticate all requests using a
|
228
|
+
// shared secret/nonce that we pass around.
|
229
|
+
const bindHost = isLinux() ? '0.0.0.0' : host;
|
227
230
|
|
228
231
|
currentServer.listen(port, bindHost, async () => {
|
229
232
|
try {
|
package/package.json
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
{
|
2
2
|
"name": "@kapeta/local-cluster-service",
|
3
|
-
"version": "0.
|
3
|
+
"version": "0.39.0",
|
4
4
|
"description": "Manages configuration, ports and service discovery for locally running Kapeta systems",
|
5
5
|
"type": "commonjs",
|
6
6
|
"exports": {
|
@@ -35,7 +35,8 @@
|
|
35
35
|
},
|
36
36
|
"scripts": {
|
37
37
|
"start": "node ./dist/cjs/start.js",
|
38
|
-
"dev": "
|
38
|
+
"dev": "npm run start:dev",
|
39
|
+
"start:dev": "nodemon -e js,ts,json ./start.ts",
|
39
40
|
"test": "jest",
|
40
41
|
"clean": "rm -rf ./dist",
|
41
42
|
"build:esm": "tsc --module nodenext --moduleResolution nodenext --outDir ./dist/esm && echo '{\"type\":\"module\"}' > ./dist/esm/package.json",
|
@@ -47,19 +48,19 @@
|
|
47
48
|
},
|
48
49
|
"homepage": "https://github.com/kapetacom/local-cluster-service#readme",
|
49
50
|
"dependencies": {
|
50
|
-
"@kapeta/codegen": "^1.
|
51
|
-
"@kapeta/kaplang-core": "^1.
|
51
|
+
"@kapeta/codegen": "^1.3.0",
|
52
|
+
"@kapeta/kaplang-core": "^1.11.2",
|
52
53
|
"@kapeta/local-cluster-config": "^0.4.0",
|
53
|
-
"@kapeta/nodejs-api-client": ">=0.
|
54
|
-
"@kapeta/nodejs-process": "
|
55
|
-
"@kapeta/nodejs-registry-utils": ">=0.
|
54
|
+
"@kapeta/nodejs-api-client": ">=0.2.0 <2",
|
55
|
+
"@kapeta/nodejs-process": "^1.2.0",
|
56
|
+
"@kapeta/nodejs-registry-utils": ">=0.11.1 <2",
|
56
57
|
"@kapeta/nodejs-utils": "<2",
|
57
|
-
"@kapeta/schemas": "^
|
58
|
-
"@kapeta/sdk-config": "^2.
|
59
|
-
"@kapeta/ui-web-components": "^3.0
|
60
|
-
"@kapeta/ui-web-plan-editor": "^2.
|
61
|
-
"@kapeta/ui-web-types": "^1.
|
62
|
-
"@kapeta/web-microfrontend": "^1",
|
58
|
+
"@kapeta/schemas": "^3.5.0",
|
59
|
+
"@kapeta/sdk-config": "^2.1.1",
|
60
|
+
"@kapeta/ui-web-components": "^3.1.0",
|
61
|
+
"@kapeta/ui-web-plan-editor": "^2.3.1",
|
62
|
+
"@kapeta/ui-web-types": "^1.3.1",
|
63
|
+
"@kapeta/web-microfrontend": "^1.2.5",
|
63
64
|
"@sentry/node": "^7.94.1",
|
64
65
|
"@types/dockerode": "^3.3.19",
|
65
66
|
"@types/stream-json": "^1.7.3",
|
@@ -82,7 +83,8 @@
|
|
82
83
|
"stream-json": "^1.8.0",
|
83
84
|
"tar-stream": "^3.1.6",
|
84
85
|
"typescript": "^5.1.6",
|
85
|
-
"yaml": "^1.6.0"
|
86
|
+
"yaml": "^1.6.0",
|
87
|
+
"@kapeta/config-mapper": "^1.1.1"
|
86
88
|
},
|
87
89
|
"devDependencies": {
|
88
90
|
"@kapeta/eslint-config": "^0.7.0",
|
package/src/assetManager.ts
CHANGED
@@ -8,13 +8,13 @@ import FS from 'fs-extra';
|
|
8
8
|
import YAML from 'yaml';
|
9
9
|
import { Definition, DefinitionInfo } from '@kapeta/local-cluster-config';
|
10
10
|
import { codeGeneratorManager } from './codeGeneratorManager';
|
11
|
-
import { ProgressListener } from './progressListener';
|
11
|
+
import { ProgressListener, TaskProgressListener } from './progressListener';
|
12
12
|
import { normalizeKapetaUri, parseKapetaUri } from '@kapeta/nodejs-utils';
|
13
13
|
import { repositoryManager } from './repositoryManager';
|
14
14
|
import { BlockDefinition, BlockInstance, Plan } from '@kapeta/schemas';
|
15
15
|
import { Actions } from '@kapeta/nodejs-registry-utils';
|
16
16
|
import { definitionsManager } from './definitionsManager';
|
17
|
-
import { taskManager } from './taskManager';
|
17
|
+
import { Task, taskManager } from './taskManager';
|
18
18
|
import { KIND_BLOCK_TYPE_EXECUTABLE, KIND_BLOCK_TYPE_OPERATOR, KIND_BLOCK_TYPE, SourceOfChange } from './types';
|
19
19
|
import { cacheManager } from './cacheManager';
|
20
20
|
import uuid from 'node-uuid';
|
@@ -308,11 +308,12 @@ class AssetManager {
|
|
308
308
|
}
|
309
309
|
|
310
310
|
console.log('Installing updates', refs);
|
311
|
-
const updateAll = async () => {
|
311
|
+
const updateAll = async (task: Task) => {
|
312
|
+
const progressListener = new TaskProgressListener(task);
|
312
313
|
try {
|
313
314
|
//We change to a temp dir to avoid issues with the current working directory
|
314
315
|
process.chdir(os.tmpdir());
|
315
|
-
await Actions.install(
|
316
|
+
await Actions.install(progressListener, refs, {});
|
316
317
|
await this.cleanupUnusedProviders();
|
317
318
|
} catch (e) {
|
318
319
|
console.error(`Failed to update assets: ${refs.join(',')}`, e);
|
package/src/config/routes.ts
CHANGED
@@ -157,7 +157,8 @@ router.get('/consumes/resource/:resourceType/:portType/:name', async (req: Kapet
|
|
157
157
|
req.params.resourceType,
|
158
158
|
req.params.portType,
|
159
159
|
req.params.name,
|
160
|
-
req.kapeta!.environment
|
160
|
+
req.kapeta!.environment,
|
161
|
+
req.query.ensure !== 'false'
|
161
162
|
);
|
162
163
|
|
163
164
|
res.send(operatorInfo);
|
@@ -190,7 +191,8 @@ router.get('/operator/:instanceId', async (req: KapetaRequest, res) => {
|
|
190
191
|
const operatorInfo = await instanceManager.getInstanceOperator(
|
191
192
|
req.kapeta!.systemId,
|
192
193
|
req.params.instanceId,
|
193
|
-
req.kapeta!.environment
|
194
|
+
req.kapeta!.environment,
|
195
|
+
req.query.ensure !== 'false'
|
194
196
|
);
|
195
197
|
|
196
198
|
res.send(operatorInfo);
|
package/src/containerManager.ts
CHANGED
@@ -14,12 +14,12 @@ 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 {
|
17
|
+
import { DOCKER_HOST_INTERNAL, 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';
|
21
21
|
import StreamValues from 'stream-json/streamers/StreamValues';
|
22
|
-
import {
|
22
|
+
import { LocalInstanceHealth } from '@kapeta/schemas';
|
23
23
|
|
24
24
|
type StringMap = { [key: string]: string };
|
25
25
|
|
@@ -100,7 +100,7 @@ enum DockerPullEventTypes {
|
|
100
100
|
|
101
101
|
type DockerPullEventType = DockerPullEventTypes | string;
|
102
102
|
|
103
|
-
const processJsonStream = <T>(purpose: string, stream:
|
103
|
+
const processJsonStream = <T>(purpose: string, stream: NodeJS.ReadableStream, handler: (d: JSONMessage<T>) => void) =>
|
104
104
|
new Promise<void>((resolve, reject) => {
|
105
105
|
const jsonStream = StreamValues.withParser();
|
106
106
|
jsonStream.on('data', (data: any) => {
|
@@ -296,15 +296,8 @@ class ContainerManager {
|
|
296
296
|
}
|
297
297
|
|
298
298
|
async getContainerByName(containerName: string): Promise<ContainerInfo | undefined> {
|
299
|
-
|
300
|
-
|
301
|
-
return container.Names.indexOf(`/${containerName}`) > -1;
|
302
|
-
});
|
303
|
-
|
304
|
-
if (out) {
|
305
|
-
return this.get(out.Id);
|
306
|
-
}
|
307
|
-
return undefined;
|
299
|
+
// The container can be fetched by name or by id using the same API call
|
300
|
+
return this.get(containerName);
|
308
301
|
}
|
309
302
|
|
310
303
|
async pull(image: string) {
|
@@ -398,6 +391,11 @@ class ContainerManager {
|
|
398
391
|
|
399
392
|
const chunk = chunks[data.id];
|
400
393
|
|
394
|
+
if (data.stream) {
|
395
|
+
// Emit raw output to the task log
|
396
|
+
task.addLog(data.stream);
|
397
|
+
}
|
398
|
+
|
401
399
|
switch (data.status) {
|
402
400
|
case DockerPullEventTypes.PreparingPhase:
|
403
401
|
case DockerPullEventTypes.WaitingPhase:
|
@@ -529,7 +527,7 @@ class ContainerManager {
|
|
529
527
|
return Mounts;
|
530
528
|
}
|
531
529
|
|
532
|
-
toDockerHealth(health:
|
530
|
+
toDockerHealth(health: LocalInstanceHealth) {
|
533
531
|
return {
|
534
532
|
Test: ['CMD-SHELL', health.cmd],
|
535
533
|
Interval: health.interval ? health.interval * NANO_SECOND : 5000 * NANO_SECOND,
|
@@ -667,7 +665,9 @@ class ContainerManager {
|
|
667
665
|
const newName = 'deleting-' + uuid.v4();
|
668
666
|
// Rename the container first to avoid name conflicts if people start the same container
|
669
667
|
await container.rename({ name: newName });
|
670
|
-
|
668
|
+
|
669
|
+
const newContainer = this.docker().getContainer(newName);
|
670
|
+
await newContainer.remove({ force: !!opts?.force });
|
671
671
|
}
|
672
672
|
|
673
673
|
/**
|
@@ -679,7 +679,7 @@ class ContainerManager {
|
|
679
679
|
let dockerContainer = null;
|
680
680
|
|
681
681
|
try {
|
682
|
-
dockerContainer =
|
682
|
+
dockerContainer = this.docker().getContainer(name);
|
683
683
|
await dockerContainer.stats();
|
684
684
|
} catch (err) {
|
685
685
|
//Ignore
|
@@ -707,7 +707,7 @@ class ContainerManager {
|
|
707
707
|
];
|
708
708
|
}
|
709
709
|
|
710
|
-
return containerInfo.getLogs();
|
710
|
+
return await containerInfo.getLogs();
|
711
711
|
}
|
712
712
|
|
713
713
|
async stopLogListening(systemId: string, instanceId: string) {
|
@@ -782,6 +782,34 @@ class ContainerManager {
|
|
782
782
|
// Ignore
|
783
783
|
}
|
784
784
|
}
|
785
|
+
|
786
|
+
buildDockerImage(dockerFile: string, imageName: string) {
|
787
|
+
const taskName = `Building docker image: ${imageName}`;
|
788
|
+
const processor = async (task: Task) => {
|
789
|
+
const timeStarted = Date.now();
|
790
|
+
const stream = await this.docker().buildImage(
|
791
|
+
{
|
792
|
+
context: Path.dirname(dockerFile),
|
793
|
+
src: [Path.basename(dockerFile)],
|
794
|
+
},
|
795
|
+
{
|
796
|
+
t: imageName,
|
797
|
+
dockerfile: Path.basename(dockerFile),
|
798
|
+
}
|
799
|
+
);
|
800
|
+
|
801
|
+
await processJsonStream<string>(`image:build:${imageName}`, stream, (data) => {
|
802
|
+
if (data.stream) {
|
803
|
+
// Emit raw output to the task log
|
804
|
+
task.addLog(data.stream);
|
805
|
+
}
|
806
|
+
});
|
807
|
+
};
|
808
|
+
|
809
|
+
return taskManager.add(`docker:image:build:${imageName}`, processor, {
|
810
|
+
name: taskName,
|
811
|
+
});
|
812
|
+
}
|
785
813
|
}
|
786
814
|
|
787
815
|
function readLogBuffer(logBuffer: Buffer) {
|
@@ -1038,17 +1066,78 @@ export class ContainerInfo {
|
|
1038
1066
|
});
|
1039
1067
|
|
1040
1068
|
const out = readLogBuffer(logs);
|
1069
|
+
if (out.length > 0) {
|
1070
|
+
return out;
|
1071
|
+
}
|
1041
1072
|
|
1042
|
-
|
1043
|
-
|
1044
|
-
|
1045
|
-
|
1046
|
-
|
1047
|
-
|
1048
|
-
|
1073
|
+
const status = await this.status();
|
1074
|
+
const healthLogs: LogEntry[] = status?.Health?.Log
|
1075
|
+
? status?.Health?.Log.map((log) => {
|
1076
|
+
return {
|
1077
|
+
source: 'stdout',
|
1078
|
+
level: log.ExitCode === 0 ? 'INFO' : 'ERROR',
|
1079
|
+
time: Date.now(),
|
1080
|
+
message: 'Health check: ' + log.Output,
|
1081
|
+
};
|
1082
|
+
})
|
1083
|
+
: [];
|
1084
|
+
|
1085
|
+
if (status?.Running) {
|
1086
|
+
return [
|
1087
|
+
{
|
1088
|
+
source: 'stdout',
|
1089
|
+
level: 'INFO',
|
1090
|
+
time: Date.now(),
|
1091
|
+
message: 'Container is starting...',
|
1092
|
+
},
|
1093
|
+
...healthLogs,
|
1094
|
+
];
|
1095
|
+
}
|
1096
|
+
|
1097
|
+
if (status?.Restarting) {
|
1098
|
+
return [
|
1099
|
+
{
|
1100
|
+
source: 'stdout',
|
1101
|
+
level: 'INFO',
|
1102
|
+
time: Date.now(),
|
1103
|
+
message: 'Container is restarting...',
|
1104
|
+
},
|
1105
|
+
...healthLogs,
|
1106
|
+
];
|
1107
|
+
}
|
1108
|
+
if (status?.Paused) {
|
1109
|
+
return [
|
1110
|
+
{
|
1111
|
+
source: 'stdout',
|
1112
|
+
level: 'INFO',
|
1113
|
+
time: Date.now(),
|
1114
|
+
message: 'Container is paused...',
|
1115
|
+
},
|
1116
|
+
...healthLogs,
|
1117
|
+
];
|
1049
1118
|
}
|
1050
1119
|
|
1051
|
-
|
1120
|
+
if (status?.Error) {
|
1121
|
+
return [
|
1122
|
+
{
|
1123
|
+
source: 'stderr',
|
1124
|
+
level: 'ERROR',
|
1125
|
+
time: Date.now(),
|
1126
|
+
message: 'Container failed to start:\n' + status.Error,
|
1127
|
+
},
|
1128
|
+
...healthLogs,
|
1129
|
+
];
|
1130
|
+
}
|
1131
|
+
|
1132
|
+
return [
|
1133
|
+
{
|
1134
|
+
source: 'stdout',
|
1135
|
+
level: 'INFO',
|
1136
|
+
time: Date.now(),
|
1137
|
+
message: 'Container not running',
|
1138
|
+
...healthLogs,
|
1139
|
+
},
|
1140
|
+
];
|
1052
1141
|
}
|
1053
1142
|
}
|
1054
1143
|
|
@@ -1057,11 +1146,11 @@ export function getExtraHosts(dockerVersion: string): string[] | undefined {
|
|
1057
1146
|
const [major, minor] = dockerVersion.split('.');
|
1058
1147
|
if (parseInt(major) >= 20 && parseInt(minor) >= 10) {
|
1059
1148
|
// Docker 20.10+ on Linux supports adding host.docker.internal to point to host-gateway
|
1060
|
-
return [
|
1149
|
+
return [`${DOCKER_HOST_INTERNAL}:host-gateway`];
|
1061
1150
|
}
|
1062
1151
|
// Docker versions lower than 20.10 needs an actual IP address. We use the default network bridge which
|
1063
1152
|
// is always 172.17.0.1
|
1064
|
-
return [
|
1153
|
+
return [`${DOCKER_HOST_INTERNAL}:172.17.0.1`];
|
1065
1154
|
}
|
1066
1155
|
|
1067
1156
|
return undefined;
|