@kapeta/local-cluster-service 0.6.1 → 0.7.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/.eslintrc.cjs +17 -0
- package/.github/workflows/main.yml +22 -22
- package/.prettierignore +4 -0
- package/.vscode/launch.json +2 -4
- package/CHANGELOG.md +7 -0
- package/definitions.d.ts +17 -35
- package/dist/cjs/index.d.ts +27 -0
- package/dist/cjs/index.js +126 -0
- package/dist/cjs/package.json +1 -0
- package/dist/cjs/src/assetManager.d.ts +31 -0
- package/dist/cjs/src/assetManager.js +153 -0
- package/dist/cjs/src/assets/routes.d.ts +3 -0
- package/dist/cjs/src/assets/routes.js +117 -0
- package/dist/cjs/src/clusterService.d.ts +40 -0
- package/dist/cjs/src/clusterService.js +114 -0
- package/dist/cjs/src/codeGeneratorManager.d.ts +8 -0
- package/dist/cjs/src/codeGeneratorManager.js +53 -0
- package/dist/cjs/src/config/routes.d.ts +3 -0
- package/dist/cjs/src/config/routes.js +126 -0
- package/dist/cjs/src/configManager.d.ts +36 -0
- package/dist/cjs/src/configManager.js +110 -0
- package/dist/cjs/src/containerManager.d.ts +89 -0
- package/dist/cjs/src/containerManager.js +365 -0
- package/dist/cjs/src/filesystem/routes.d.ts +3 -0
- package/dist/cjs/src/filesystem/routes.js +69 -0
- package/dist/cjs/src/filesystemManager.d.ts +15 -0
- package/dist/cjs/src/filesystemManager.js +87 -0
- package/dist/cjs/src/identities/routes.d.ts +3 -0
- package/dist/cjs/src/identities/routes.js +18 -0
- package/dist/cjs/src/instanceManager.d.ts +56 -0
- package/dist/cjs/src/instanceManager.js +424 -0
- package/dist/cjs/src/instances/routes.d.ts +3 -0
- package/dist/cjs/src/instances/routes.js +134 -0
- package/dist/cjs/src/middleware/cors.d.ts +2 -0
- package/dist/cjs/src/middleware/cors.js +10 -0
- package/dist/cjs/src/middleware/kapeta.d.ts +11 -0
- package/dist/cjs/src/middleware/kapeta.js +17 -0
- package/dist/cjs/src/middleware/stringBody.d.ts +5 -0
- package/dist/cjs/src/middleware/stringBody.js +14 -0
- package/dist/cjs/src/networkManager.d.ts +32 -0
- package/dist/cjs/src/networkManager.js +109 -0
- package/dist/cjs/src/operatorManager.d.ts +36 -0
- package/dist/cjs/src/operatorManager.js +165 -0
- package/dist/cjs/src/progressListener.d.ts +20 -0
- package/dist/cjs/src/progressListener.js +91 -0
- package/dist/cjs/src/providerManager.d.ts +9 -0
- package/dist/cjs/src/providerManager.js +51 -0
- package/dist/cjs/src/providers/routes.d.ts +3 -0
- package/dist/cjs/src/providers/routes.js +42 -0
- package/dist/cjs/src/proxy/routes.d.ts +3 -0
- package/dist/cjs/src/proxy/routes.js +111 -0
- package/dist/cjs/src/proxy/types/rest.d.ts +4 -0
- package/dist/cjs/src/proxy/types/rest.js +114 -0
- package/dist/cjs/src/proxy/types/web.d.ts +4 -0
- package/dist/cjs/src/proxy/types/web.js +53 -0
- package/dist/cjs/src/repositoryManager.d.ts +17 -0
- package/dist/cjs/src/repositoryManager.js +215 -0
- package/dist/cjs/src/serviceManager.d.ts +29 -0
- package/dist/cjs/src/serviceManager.js +99 -0
- package/dist/cjs/src/socketManager.d.ts +14 -0
- package/dist/cjs/src/socketManager.js +53 -0
- package/dist/cjs/src/storageService.d.ts +17 -0
- package/dist/cjs/src/storageService.js +74 -0
- package/dist/cjs/src/traffic/routes.d.ts +3 -0
- package/dist/cjs/src/traffic/routes.js +18 -0
- package/dist/cjs/src/types.d.ts +88 -0
- package/dist/cjs/src/types.js +2 -0
- package/dist/cjs/src/utils/BlockInstanceRunner.d.ts +29 -0
- package/dist/cjs/src/utils/BlockInstanceRunner.js +468 -0
- package/dist/cjs/src/utils/LogData.d.ts +19 -0
- package/dist/cjs/src/utils/LogData.js +43 -0
- package/dist/cjs/src/utils/pathTemplateParser.d.ts +26 -0
- package/dist/cjs/src/utils/pathTemplateParser.js +121 -0
- package/dist/cjs/src/utils/utils.d.ts +1 -0
- package/dist/cjs/src/utils/utils.js +18 -0
- package/dist/cjs/start.d.ts +1 -0
- package/dist/cjs/start.js +12 -0
- package/dist/esm/index.d.ts +27 -0
- package/dist/esm/index.js +121 -0
- package/dist/esm/package.json +1 -0
- package/dist/esm/src/assetManager.d.ts +31 -0
- package/{src → dist/esm/src}/assetManager.js +22 -60
- package/dist/esm/src/assets/routes.d.ts +3 -0
- package/{src → dist/esm/src}/assets/routes.js +21 -36
- package/dist/esm/src/clusterService.d.ts +40 -0
- package/{src → dist/esm/src}/clusterService.js +14 -37
- package/dist/esm/src/codeGeneratorManager.d.ts +8 -0
- package/{src → dist/esm/src}/codeGeneratorManager.js +15 -24
- package/dist/esm/src/config/routes.d.ts +3 -0
- package/{src → dist/esm/src}/config/routes.js +40 -89
- package/dist/esm/src/configManager.d.ts +36 -0
- package/{src → dist/esm/src}/configManager.js +11 -40
- package/dist/esm/src/containerManager.d.ts +89 -0
- package/{src → dist/esm/src}/containerManager.js +81 -182
- package/dist/esm/src/filesystem/routes.d.ts +3 -0
- package/dist/esm/src/filesystem/routes.js +64 -0
- package/dist/esm/src/filesystemManager.d.ts +15 -0
- package/{src → dist/esm/src}/filesystemManager.js +20 -28
- package/dist/esm/src/identities/routes.d.ts +3 -0
- package/dist/esm/src/identities/routes.js +13 -0
- package/dist/esm/src/instanceManager.d.ts +56 -0
- package/{src → dist/esm/src}/instanceManager.js +88 -179
- package/dist/esm/src/instances/routes.d.ts +3 -0
- package/{src → dist/esm/src}/instances/routes.js +31 -70
- package/dist/esm/src/middleware/cors.d.ts +2 -0
- package/{src → dist/esm/src}/middleware/cors.js +2 -3
- package/dist/esm/src/middleware/kapeta.d.ts +11 -0
- package/{src → dist/esm/src}/middleware/kapeta.js +3 -7
- package/dist/esm/src/middleware/stringBody.d.ts +5 -0
- package/{src → dist/esm/src}/middleware/stringBody.js +2 -3
- package/dist/esm/src/networkManager.d.ts +32 -0
- package/{src → dist/esm/src}/networkManager.js +16 -33
- package/dist/esm/src/operatorManager.d.ts +36 -0
- package/{src → dist/esm/src}/operatorManager.js +35 -91
- package/dist/esm/src/progressListener.d.ts +20 -0
- package/dist/esm/src/progressListener.js +88 -0
- package/dist/esm/src/providerManager.d.ts +9 -0
- package/dist/esm/src/providerManager.js +45 -0
- package/dist/esm/src/providers/routes.d.ts +3 -0
- package/{src → dist/esm/src}/providers/routes.js +10 -16
- package/dist/esm/src/proxy/routes.d.ts +3 -0
- package/dist/esm/src/proxy/routes.js +106 -0
- package/dist/esm/src/proxy/types/rest.d.ts +4 -0
- package/dist/esm/src/proxy/types/rest.js +107 -0
- package/dist/esm/src/proxy/types/web.d.ts +4 -0
- package/{src → dist/esm/src}/proxy/types/web.js +13 -35
- package/dist/esm/src/repositoryManager.d.ts +17 -0
- package/dist/esm/src/repositoryManager.js +209 -0
- package/dist/esm/src/serviceManager.d.ts +29 -0
- package/{src → dist/esm/src}/serviceManager.js +12 -42
- package/dist/esm/src/socketManager.d.ts +14 -0
- package/{src → dist/esm/src}/socketManager.js +19 -23
- package/dist/esm/src/storageService.d.ts +17 -0
- package/{src → dist/esm/src}/storageService.js +8 -27
- package/dist/esm/src/traffic/routes.d.ts +3 -0
- package/{src → dist/esm/src}/traffic/routes.js +4 -9
- package/dist/esm/src/types.d.ts +88 -0
- package/dist/esm/src/types.js +1 -0
- package/dist/esm/src/utils/BlockInstanceRunner.d.ts +29 -0
- package/{src → dist/esm/src}/utils/BlockInstanceRunner.js +137 -256
- package/dist/esm/src/utils/LogData.d.ts +19 -0
- package/{src → dist/esm/src}/utils/LogData.js +11 -22
- package/dist/esm/src/utils/pathTemplateParser.d.ts +26 -0
- package/{src → dist/esm/src}/utils/pathTemplateParser.js +21 -40
- package/dist/esm/src/utils/utils.d.ts +1 -0
- package/dist/esm/src/utils/utils.js +11 -0
- package/dist/esm/start.d.ts +1 -0
- package/dist/esm/start.js +7 -0
- package/index.ts +147 -0
- package/package.json +106 -74
- package/src/assetManager.ts +191 -0
- package/src/assets/routes.ts +132 -0
- package/src/clusterService.ts +134 -0
- package/src/codeGeneratorManager.ts +57 -0
- package/src/config/routes.ts +159 -0
- package/src/configManager.ts +148 -0
- package/src/containerManager.ts +466 -0
- package/src/filesystem/routes.ts +74 -0
- package/src/filesystemManager.ts +93 -0
- package/src/identities/routes.ts +20 -0
- package/src/instanceManager.ts +503 -0
- package/src/instances/routes.ts +164 -0
- package/src/middleware/cors.ts +9 -0
- package/src/middleware/kapeta.ts +27 -0
- package/src/middleware/stringBody.ts +16 -0
- package/src/networkManager.ts +137 -0
- package/src/operatorManager.ts +221 -0
- package/src/progressListener.ts +102 -0
- package/src/{providerManager.js → providerManager.ts} +15 -31
- package/src/providers/routes.ts +46 -0
- package/src/proxy/routes.ts +148 -0
- package/src/proxy/types/{rest.js → rest.ts} +30 -30
- package/src/proxy/types/web.ts +60 -0
- package/src/{repositoryManager.js → repositoryManager.ts} +45 -73
- package/src/serviceManager.ts +120 -0
- package/src/socketManager.ts +57 -0
- package/src/storageService.ts +88 -0
- package/src/traffic/routes.ts +18 -0
- package/src/types.ts +97 -0
- package/src/utils/BlockInstanceRunner.ts +555 -0
- package/src/utils/LogData.ts +47 -0
- package/src/utils/pathTemplateParser.ts +138 -0
- package/src/utils/utils.ts +12 -0
- package/start.ts +8 -0
- package/tsconfig.json +13 -0
- package/index.js +0 -127
- package/src/filesystem/routes.js +0 -74
- package/src/identities/routes.js +0 -19
- package/src/progressListener.js +0 -82
- package/src/proxy/routes.js +0 -126
- package/src/utils/utils.js +0 -13
- package/start.js +0 -7
@@ -0,0 +1,555 @@
|
|
1
|
+
import { spawn } from 'node:child_process';
|
2
|
+
import FS from 'node:fs';
|
3
|
+
import Path from 'node:path';
|
4
|
+
import { Docker } from 'node-docker-api';
|
5
|
+
import ClusterConfig, { DefinitionInfo } from '@kapeta/local-cluster-config';
|
6
|
+
import { readYML } from './utils';
|
7
|
+
import { KapetaURI, parseKapetaUri } from '@kapeta/nodejs-utils';
|
8
|
+
import { serviceManager } from '../serviceManager';
|
9
|
+
import { containerManager, DockerMounts } from '../containerManager';
|
10
|
+
import { LogData } from './LogData';
|
11
|
+
import EventEmitter from 'events';
|
12
|
+
import md5 from 'md5';
|
13
|
+
import { execSync } from 'child_process';
|
14
|
+
import { clusterService } from '../clusterService';
|
15
|
+
import { AnyMap, BlockProcessParams, ProcessDetails, ProcessInfo, StringMap } from '../types';
|
16
|
+
import { BlockDefinition, BlockInstance, BlockInstanceConfiguration, BlockResource } from '@kapeta/schemas';
|
17
|
+
import { Container } from 'node-docker-api/lib/container';
|
18
|
+
|
19
|
+
const KIND_BLOCK_TYPE_OPERATOR = 'core/block-type-operator';
|
20
|
+
const KAPETA_SYSTEM_ID = 'KAPETA_SYSTEM_ID';
|
21
|
+
const KAPETA_BLOCK_REF = 'KAPETA_BLOCK_REF';
|
22
|
+
const KAPETA_INSTANCE_ID = 'KAPETA_INSTANCE_ID';
|
23
|
+
const KAPETA_LOCAL_CLUSTER_PORT = 'KAPETA_LOCAL_CLUSTER_PORT';
|
24
|
+
/**
|
25
|
+
* Needed when running local docker containers as part of plan
|
26
|
+
* @type {string[]}
|
27
|
+
*/
|
28
|
+
const DOCKER_ENV_VARS = [
|
29
|
+
`KAPETA_LOCAL_SERVER=0.0.0.0`,
|
30
|
+
`KAPETA_LOCAL_CLUSTER_HOST=host.docker.internal`,
|
31
|
+
`KAPETA_ENVIRONMENT_TYPE=docker`,
|
32
|
+
];
|
33
|
+
|
34
|
+
function getProvider(uri: KapetaURI) {
|
35
|
+
return ClusterConfig.getProviderDefinitions().find((provider) => {
|
36
|
+
const ref = `${provider.definition.metadata.name}:${provider.version}`;
|
37
|
+
return parseKapetaUri(ref).id === uri.id;
|
38
|
+
});
|
39
|
+
}
|
40
|
+
|
41
|
+
function getProviderPorts(assetVersion: DefinitionInfo): string[] {
|
42
|
+
return (
|
43
|
+
assetVersion.definition?.spec?.providers
|
44
|
+
?.map((provider: any) => {
|
45
|
+
return provider.spec?.port?.type;
|
46
|
+
})
|
47
|
+
.filter((t: any) => !!t) ?? []
|
48
|
+
);
|
49
|
+
}
|
50
|
+
|
51
|
+
export class BlockInstanceRunner {
|
52
|
+
private readonly _systemId: string;
|
53
|
+
|
54
|
+
constructor(planReference: string) {
|
55
|
+
/**
|
56
|
+
*
|
57
|
+
* @type {string}
|
58
|
+
* @private
|
59
|
+
*/
|
60
|
+
this._systemId = planReference ?? '';
|
61
|
+
}
|
62
|
+
|
63
|
+
/**
|
64
|
+
* Start a block
|
65
|
+
*
|
66
|
+
*/
|
67
|
+
async start(blockRef: string, instanceId: string, configuration: AnyMap): Promise<ProcessInfo> {
|
68
|
+
return this._execute({
|
69
|
+
ref: blockRef,
|
70
|
+
id: instanceId,
|
71
|
+
configuration,
|
72
|
+
});
|
73
|
+
}
|
74
|
+
|
75
|
+
private async _execute(blockInstance: BlockProcessParams): Promise<ProcessInfo> {
|
76
|
+
const env: StringMap = {};
|
77
|
+
|
78
|
+
if (this._systemId) {
|
79
|
+
env[KAPETA_SYSTEM_ID] = this._systemId;
|
80
|
+
}
|
81
|
+
|
82
|
+
if (blockInstance.ref) {
|
83
|
+
env[KAPETA_BLOCK_REF] = blockInstance.ref;
|
84
|
+
}
|
85
|
+
|
86
|
+
if (blockInstance.id) {
|
87
|
+
env[KAPETA_INSTANCE_ID] = blockInstance.id;
|
88
|
+
}
|
89
|
+
|
90
|
+
const blockUri = parseKapetaUri(blockInstance.ref);
|
91
|
+
|
92
|
+
if (!blockUri.version) {
|
93
|
+
blockUri.version = 'local';
|
94
|
+
}
|
95
|
+
|
96
|
+
const assetVersion = ClusterConfig.getDefinitions().find((definitions) => {
|
97
|
+
const ref = `${definitions.definition.metadata.name}:${definitions.version}`;
|
98
|
+
return parseKapetaUri(ref).id === blockUri.id;
|
99
|
+
});
|
100
|
+
|
101
|
+
if (!assetVersion) {
|
102
|
+
throw new Error(`Block definition not found: ${blockUri.id}`);
|
103
|
+
}
|
104
|
+
|
105
|
+
const kindUri = parseKapetaUri(assetVersion.definition.kind);
|
106
|
+
|
107
|
+
const providerVersion = getProvider(kindUri);
|
108
|
+
|
109
|
+
if (!providerVersion) {
|
110
|
+
throw new Error(`Kind not found: ${kindUri.id}`);
|
111
|
+
}
|
112
|
+
|
113
|
+
let processDetails: ProcessDetails;
|
114
|
+
|
115
|
+
if (providerVersion.definition.kind === KIND_BLOCK_TYPE_OPERATOR) {
|
116
|
+
processDetails = await this._startOperatorProcess(blockInstance, blockUri, providerVersion, env);
|
117
|
+
} else {
|
118
|
+
//We need a port type to know how to connect to the block consistently
|
119
|
+
const portTypes = getProviderPorts(assetVersion);
|
120
|
+
|
121
|
+
if (blockUri.version === 'local') {
|
122
|
+
processDetails = await this._startLocalProcess(blockInstance, blockUri, env, assetVersion);
|
123
|
+
} else {
|
124
|
+
processDetails = await this._startDockerProcess(blockInstance, blockUri, env);
|
125
|
+
}
|
126
|
+
|
127
|
+
if (portTypes.length > 0) {
|
128
|
+
processDetails.portType = portTypes[0];
|
129
|
+
}
|
130
|
+
}
|
131
|
+
|
132
|
+
return {
|
133
|
+
name: blockUri.id,
|
134
|
+
...blockInstance,
|
135
|
+
...processDetails,
|
136
|
+
};
|
137
|
+
}
|
138
|
+
|
139
|
+
/**
|
140
|
+
* Starts local process
|
141
|
+
*/
|
142
|
+
private async _startLocalProcess(
|
143
|
+
blockInstance: BlockProcessParams,
|
144
|
+
blockInfo: KapetaURI,
|
145
|
+
env: StringMap,
|
146
|
+
assetVersion: DefinitionInfo
|
147
|
+
): Promise<ProcessDetails> {
|
148
|
+
const baseDir = ClusterConfig.getRepositoryAssetPath(blockInfo.handle, blockInfo.name, blockInfo.version);
|
149
|
+
|
150
|
+
if (!FS.existsSync(baseDir)) {
|
151
|
+
throw new Error(
|
152
|
+
`Local block not registered correctly - expected symlink here: ${baseDir}.\n` +
|
153
|
+
`Make sure you've run "blockctl registry link" in your local directory to connect it to Kapeta`
|
154
|
+
);
|
155
|
+
}
|
156
|
+
|
157
|
+
if (!assetVersion.definition.spec?.target?.kind) {
|
158
|
+
throw new Error('Missing target kind in block definition');
|
159
|
+
}
|
160
|
+
|
161
|
+
const kindUri = parseKapetaUri(assetVersion.definition.spec?.target?.kind);
|
162
|
+
|
163
|
+
const targetVersion = getProvider(kindUri);
|
164
|
+
|
165
|
+
if (!targetVersion) {
|
166
|
+
throw new Error(`Target not found: ${kindUri.id}`);
|
167
|
+
}
|
168
|
+
|
169
|
+
const localContainer = targetVersion.definition.spec.local;
|
170
|
+
|
171
|
+
if (!localContainer) {
|
172
|
+
throw new Error(`Missing local container information from target: ${kindUri.id}`);
|
173
|
+
}
|
174
|
+
|
175
|
+
const dockerImage = localContainer.image;
|
176
|
+
if (!dockerImage) {
|
177
|
+
throw new Error(`Missing docker image information: ${JSON.stringify(localContainer)}`);
|
178
|
+
}
|
179
|
+
|
180
|
+
const containerName = `kapeta-block-instance-${blockInstance.id}`;
|
181
|
+
const logs = new LogData();
|
182
|
+
logs.addLog(`Starting block ${blockInstance.ref}`);
|
183
|
+
let container: Container | null = (await containerManager.getContainerByName(containerName)) ?? null;
|
184
|
+
console.log('Starting dev container', containerName);
|
185
|
+
|
186
|
+
if (container) {
|
187
|
+
console.log(`Container already exists. Deleting...`);
|
188
|
+
try {
|
189
|
+
await container.delete({
|
190
|
+
force: true,
|
191
|
+
});
|
192
|
+
} catch (e: any) {
|
193
|
+
throw new Error('Failed to delete existing container: ' + e.message);
|
194
|
+
}
|
195
|
+
container = null;
|
196
|
+
}
|
197
|
+
|
198
|
+
logs.addLog(`Creating new container for block: ${containerName}`);
|
199
|
+
console.log('Creating new dev container', containerName, dockerImage);
|
200
|
+
await containerManager.pull(dockerImage);
|
201
|
+
|
202
|
+
const startCmd = localContainer.handlers?.onCreate ? localContainer.handlers.onCreate : '';
|
203
|
+
const dockerOpts = localContainer.options ?? {};
|
204
|
+
const homeDir = localContainer.userHome ? localContainer.userHome : '/root';
|
205
|
+
const workingDir = localContainer.workingDir ? localContainer.workingDir : '/workspace';
|
206
|
+
|
207
|
+
const ExposedPorts: AnyMap = {};
|
208
|
+
const addonEnv: StringMap = {};
|
209
|
+
const PortBindings: AnyMap = {};
|
210
|
+
|
211
|
+
const portTypes = getProviderPorts(assetVersion);
|
212
|
+
let port = 80;
|
213
|
+
const promises = portTypes.map(async (portType) => {
|
214
|
+
const publicPort = await serviceManager.ensureServicePort(this._systemId, blockInstance.id, portType);
|
215
|
+
const thisPort = port++; //TODO: Not sure how we should handle multiple ports or non-HTTP ports
|
216
|
+
const dockerPort = `${thisPort}/tcp`;
|
217
|
+
ExposedPorts[dockerPort] = {};
|
218
|
+
addonEnv[`KAPETA_LOCAL_SERVER_PORT_${portType.toUpperCase()}`] = '' + thisPort;
|
219
|
+
|
220
|
+
PortBindings[dockerPort] = [
|
221
|
+
{
|
222
|
+
HostIp: '127.0.0.1', //No public
|
223
|
+
HostPort: `${publicPort}`,
|
224
|
+
},
|
225
|
+
];
|
226
|
+
});
|
227
|
+
|
228
|
+
await Promise.all(promises);
|
229
|
+
|
230
|
+
let HealthCheck = undefined;
|
231
|
+
if (localContainer.healthcheck) {
|
232
|
+
HealthCheck = containerManager.toDockerHealth({ cmd: localContainer.healthcheck });
|
233
|
+
}
|
234
|
+
|
235
|
+
container = await containerManager.startContainer({
|
236
|
+
Image: dockerImage,
|
237
|
+
name: containerName,
|
238
|
+
WorkingDir: workingDir,
|
239
|
+
Labels: {
|
240
|
+
instance: blockInstance.id,
|
241
|
+
},
|
242
|
+
HealthCheck,
|
243
|
+
ExposedPorts,
|
244
|
+
Cmd: startCmd ? startCmd.split(/\s+/g) : [],
|
245
|
+
Env: [
|
246
|
+
...DOCKER_ENV_VARS,
|
247
|
+
`KAPETA_LOCAL_CLUSTER_PORT=${clusterService.getClusterServicePort()}`,
|
248
|
+
...Object.entries({
|
249
|
+
...env,
|
250
|
+
...addonEnv,
|
251
|
+
}).map(([key, value]) => `${key}=${value}`),
|
252
|
+
],
|
253
|
+
HostConfig: {
|
254
|
+
Binds: [
|
255
|
+
`${ClusterConfig.getKapetaBasedir()}:${homeDir}/.kapeta`,
|
256
|
+
`${baseDir}:${workingDir}`, //We mount
|
257
|
+
],
|
258
|
+
PortBindings,
|
259
|
+
},
|
260
|
+
...dockerOpts,
|
261
|
+
});
|
262
|
+
|
263
|
+
try {
|
264
|
+
if (HealthCheck) {
|
265
|
+
await containerManager.waitForHealthy(container);
|
266
|
+
} else {
|
267
|
+
await containerManager.waitForReady(container);
|
268
|
+
}
|
269
|
+
} catch (e: any) {
|
270
|
+
logs.addLog(e.message, 'ERROR');
|
271
|
+
}
|
272
|
+
|
273
|
+
return this._handleContainer(container, logs);
|
274
|
+
}
|
275
|
+
|
276
|
+
private async _handleContainer(
|
277
|
+
container: Container,
|
278
|
+
logs: LogData,
|
279
|
+
deleteOnExit: boolean = false
|
280
|
+
): Promise<ProcessDetails> {
|
281
|
+
let localContainer: Container | null = container;
|
282
|
+
const logStream = (await container.logs({
|
283
|
+
follow: true,
|
284
|
+
stdout: true,
|
285
|
+
stderr: true,
|
286
|
+
tail: LogData.MAX_LINES,
|
287
|
+
})) as EventEmitter;
|
288
|
+
|
289
|
+
const outputEvents = new EventEmitter();
|
290
|
+
logStream.on('data', (data) => {
|
291
|
+
logs.addLog(data.toString());
|
292
|
+
outputEvents.emit('data', data);
|
293
|
+
});
|
294
|
+
|
295
|
+
logStream.on('error', (data) => {
|
296
|
+
logs.addLog(data.toString());
|
297
|
+
outputEvents.emit('data', data);
|
298
|
+
});
|
299
|
+
|
300
|
+
logStream.on('close', async () => {
|
301
|
+
const status = await container.status();
|
302
|
+
const data = status.data as any;
|
303
|
+
if (deleteOnExit) {
|
304
|
+
try {
|
305
|
+
await container.delete();
|
306
|
+
} catch (e: any) {}
|
307
|
+
}
|
308
|
+
outputEvents.emit('exit', data?.State?.ExitCode ?? 0);
|
309
|
+
});
|
310
|
+
|
311
|
+
return {
|
312
|
+
type: 'docker',
|
313
|
+
pid: container.id,
|
314
|
+
output: outputEvents,
|
315
|
+
stop: async () => {
|
316
|
+
if (!localContainer) {
|
317
|
+
return;
|
318
|
+
}
|
319
|
+
|
320
|
+
try {
|
321
|
+
await localContainer.stop();
|
322
|
+
if (deleteOnExit) {
|
323
|
+
await localContainer.delete();
|
324
|
+
}
|
325
|
+
} catch (e) {}
|
326
|
+
localContainer = null;
|
327
|
+
},
|
328
|
+
logs: () => {
|
329
|
+
return logs.getLogs();
|
330
|
+
},
|
331
|
+
};
|
332
|
+
}
|
333
|
+
|
334
|
+
private async _startDockerProcess(blockInstance: BlockProcessParams, blockInfo: KapetaURI, env: StringMap) {
|
335
|
+
const { versionFile } = ClusterConfig.getRepositoryAssetInfoPath(
|
336
|
+
blockInfo.handle,
|
337
|
+
blockInfo.name,
|
338
|
+
blockInfo.version
|
339
|
+
);
|
340
|
+
|
341
|
+
const versionYml = versionFile;
|
342
|
+
if (!FS.existsSync(versionYml)) {
|
343
|
+
throw new Error(`Did not find version info at the expected path: ${versionYml}`);
|
344
|
+
}
|
345
|
+
|
346
|
+
const versionInfo = readYML(versionYml);
|
347
|
+
|
348
|
+
if (versionInfo?.artifact?.type !== 'docker') {
|
349
|
+
throw new Error(`Unsupported artifact type: ${versionInfo?.artifact?.type}`);
|
350
|
+
}
|
351
|
+
const dockerImage = versionInfo?.artifact?.details?.primary;
|
352
|
+
if (!dockerImage) {
|
353
|
+
throw new Error(`Missing docker image information: ${JSON.stringify(versionInfo?.artifact?.details)}`);
|
354
|
+
}
|
355
|
+
|
356
|
+
const containerName = `kapeta-block-instance-${blockInstance.id}`;
|
357
|
+
const logs = new LogData();
|
358
|
+
let container = await containerManager.getContainerByName(containerName);
|
359
|
+
|
360
|
+
if (container) {
|
361
|
+
const containerData = container.data as any;
|
362
|
+
if (containerData.State === 'running') {
|
363
|
+
logs.addLog(`Found existing running container for block: ${containerName}`);
|
364
|
+
} else {
|
365
|
+
logs.addLog(`Found existing container for block: ${containerName}. Starting now`);
|
366
|
+
await container.start();
|
367
|
+
}
|
368
|
+
} else {
|
369
|
+
logs.addLog(`Creating new container for block: ${containerName}`);
|
370
|
+
container = await containerManager.startContainer({
|
371
|
+
Image: dockerImage,
|
372
|
+
name: containerName,
|
373
|
+
Labels: {
|
374
|
+
instance: blockInstance.id,
|
375
|
+
},
|
376
|
+
Env: [
|
377
|
+
...DOCKER_ENV_VARS,
|
378
|
+
`KAPETA_LOCAL_CLUSTER_PORT=${clusterService.getClusterServicePort()}`,
|
379
|
+
...Object.entries(env).map(([key, value]) => `${key}=${value}`),
|
380
|
+
],
|
381
|
+
HostConfig: {
|
382
|
+
Binds: [`${ClusterConfig.getKapetaBasedir()}:${ClusterConfig.getKapetaBasedir()}`],
|
383
|
+
},
|
384
|
+
});
|
385
|
+
|
386
|
+
try {
|
387
|
+
await containerManager.waitForReady(container);
|
388
|
+
} catch (e: any) {
|
389
|
+
logs.addLog(e.message, 'ERROR');
|
390
|
+
}
|
391
|
+
}
|
392
|
+
|
393
|
+
return this._handleContainer(container, logs);
|
394
|
+
}
|
395
|
+
|
396
|
+
/**
|
397
|
+
*
|
398
|
+
* @param blockInstance
|
399
|
+
* @param blockUri
|
400
|
+
* @param providerDefinition
|
401
|
+
* @param {{[key:string]:string}} env
|
402
|
+
* @return {Promise<ProcessDetails>}
|
403
|
+
* @private
|
404
|
+
*/
|
405
|
+
async _startOperatorProcess(
|
406
|
+
blockInstance: BlockProcessParams,
|
407
|
+
blockUri: KapetaURI,
|
408
|
+
providerDefinition: DefinitionInfo,
|
409
|
+
env: StringMap
|
410
|
+
) {
|
411
|
+
const { assetFile } = ClusterConfig.getRepositoryAssetInfoPath(
|
412
|
+
blockUri.handle,
|
413
|
+
blockUri.name,
|
414
|
+
blockUri.version
|
415
|
+
);
|
416
|
+
|
417
|
+
const kapetaYmlPath = assetFile;
|
418
|
+
if (!FS.existsSync(kapetaYmlPath)) {
|
419
|
+
throw new Error(`Did not find kapeta.yml at the expected path: ${kapetaYmlPath}`);
|
420
|
+
}
|
421
|
+
|
422
|
+
const spec = providerDefinition.definition.spec;
|
423
|
+
const providerRef = `${providerDefinition.definition.metadata.name}:${providerDefinition.version}`;
|
424
|
+
|
425
|
+
if (!spec?.local?.image) {
|
426
|
+
throw new Error(`Provider did not have local image: ${providerRef}`);
|
427
|
+
}
|
428
|
+
|
429
|
+
const dockerImage = spec?.local?.image;
|
430
|
+
|
431
|
+
try {
|
432
|
+
await containerManager.pull(dockerImage);
|
433
|
+
} catch (e) {
|
434
|
+
console.warn('Failed to pull image. Continuing...', e);
|
435
|
+
}
|
436
|
+
|
437
|
+
const containerName = `kapeta-block-instance-${md5(blockInstance.id)}`;
|
438
|
+
const logs = new LogData();
|
439
|
+
let container: Container | null = (await containerManager.getContainerByName(containerName)) ?? null;
|
440
|
+
if (container) {
|
441
|
+
const containerData = container.data as any;
|
442
|
+
if (containerData.State === 'running') {
|
443
|
+
logs.addLog(`Found existing running container for block: ${containerName}`);
|
444
|
+
} else {
|
445
|
+
if (containerData.State?.ExitCode > 0) {
|
446
|
+
logs.addLog(`Container exited with code: ${containerData.State.ExitCode}. Deleting...`);
|
447
|
+
try {
|
448
|
+
await container.delete();
|
449
|
+
} catch (e) {}
|
450
|
+
container = null;
|
451
|
+
} else {
|
452
|
+
logs.addLog(`Found existing container for block: ${containerName}. Starting now`);
|
453
|
+
try {
|
454
|
+
await container.start();
|
455
|
+
} catch (e) {
|
456
|
+
console.warn('Failed to start container. Deleting...', e);
|
457
|
+
try {
|
458
|
+
await container.delete();
|
459
|
+
} catch (e) {}
|
460
|
+
container = null;
|
461
|
+
}
|
462
|
+
}
|
463
|
+
}
|
464
|
+
}
|
465
|
+
|
466
|
+
if (!container) {
|
467
|
+
const ExposedPorts: AnyMap = {};
|
468
|
+
const addonEnv: StringMap = {};
|
469
|
+
const PortBindings: AnyMap = {};
|
470
|
+
let HealthCheck = undefined;
|
471
|
+
let Mounts: DockerMounts[] = [];
|
472
|
+
const promises = Object.entries(spec.local.ports as { [p: string]: { port: string; type: string } }).map(
|
473
|
+
async ([portType, value]) => {
|
474
|
+
const dockerPort = `${value.port}/${value.type}`;
|
475
|
+
ExposedPorts[dockerPort] = {};
|
476
|
+
addonEnv[`KAPETA_LOCAL_SERVER_PORT_${portType.toUpperCase()}`] = value.port;
|
477
|
+
const publicPort = await serviceManager.ensureServicePort(
|
478
|
+
this._systemId,
|
479
|
+
blockInstance.id,
|
480
|
+
portType
|
481
|
+
);
|
482
|
+
PortBindings[dockerPort] = [
|
483
|
+
{
|
484
|
+
HostIp: '127.0.0.1', //No public
|
485
|
+
HostPort: `${publicPort}`,
|
486
|
+
},
|
487
|
+
];
|
488
|
+
}
|
489
|
+
);
|
490
|
+
|
491
|
+
await Promise.all(promises);
|
492
|
+
|
493
|
+
if (spec.local?.env) {
|
494
|
+
Object.entries(spec.local.env).forEach(([key, value]) => {
|
495
|
+
addonEnv[key] = value as string;
|
496
|
+
});
|
497
|
+
}
|
498
|
+
|
499
|
+
if (spec.local?.mounts) {
|
500
|
+
const mounts = containerManager.createMounts(blockUri.id, spec.local.mounts);
|
501
|
+
Mounts = containerManager.toDockerMounts(mounts);
|
502
|
+
}
|
503
|
+
|
504
|
+
if (spec.local?.health) {
|
505
|
+
HealthCheck = containerManager.toDockerHealth(spec.local?.health);
|
506
|
+
}
|
507
|
+
|
508
|
+
logs.addLog(`Creating new container for block: ${containerName}`);
|
509
|
+
container = await containerManager.startContainer({
|
510
|
+
Image: dockerImage,
|
511
|
+
name: containerName,
|
512
|
+
ExposedPorts,
|
513
|
+
HealthCheck,
|
514
|
+
HostConfig: {
|
515
|
+
Binds: [
|
516
|
+
`${kapetaYmlPath}:/kapeta.yml:ro`,
|
517
|
+
`${ClusterConfig.getKapetaBasedir()}:${ClusterConfig.getKapetaBasedir()}`,
|
518
|
+
],
|
519
|
+
PortBindings,
|
520
|
+
Mounts,
|
521
|
+
},
|
522
|
+
Labels: {
|
523
|
+
instance: blockInstance.id,
|
524
|
+
},
|
525
|
+
Env: [
|
526
|
+
`KAPETA_INSTANCE_NAME=${blockInstance.ref}`,
|
527
|
+
`KAPETA_LOCAL_CLUSTER_PORT=${clusterService.getClusterServicePort()}`,
|
528
|
+
...DOCKER_ENV_VARS,
|
529
|
+
...Object.entries({
|
530
|
+
...env,
|
531
|
+
...addonEnv,
|
532
|
+
}).map(([key, value]) => `${key}=${value}`),
|
533
|
+
],
|
534
|
+
});
|
535
|
+
|
536
|
+
try {
|
537
|
+
if (HealthCheck) {
|
538
|
+
await containerManager.waitForHealthy(container);
|
539
|
+
} else {
|
540
|
+
await containerManager.waitForReady(container);
|
541
|
+
}
|
542
|
+
} catch (e: any) {
|
543
|
+
logs.addLog(e.message, 'ERROR');
|
544
|
+
}
|
545
|
+
}
|
546
|
+
|
547
|
+
const out = await this._handleContainer(container, logs, true);
|
548
|
+
const portTypes = spec.local.ports ? Object.keys(spec.local.ports) : [];
|
549
|
+
if (portTypes.length > 0) {
|
550
|
+
out.portType = portTypes[0];
|
551
|
+
}
|
552
|
+
|
553
|
+
return out;
|
554
|
+
}
|
555
|
+
}
|
@@ -0,0 +1,47 @@
|
|
1
|
+
import { LogEntry, LogLevel, LogSource } from '../types';
|
2
|
+
|
3
|
+
const MAX_LINES = 1000;
|
4
|
+
|
5
|
+
export class LogData {
|
6
|
+
public static readonly MAX_LINES = MAX_LINES;
|
7
|
+
private readonly entries: LogEntry[] = [];
|
8
|
+
constructor() {}
|
9
|
+
|
10
|
+
/**
|
11
|
+
*
|
12
|
+
* @param {string} msg
|
13
|
+
* @param {string} [level]
|
14
|
+
* @param {string} [source]
|
15
|
+
*/
|
16
|
+
addLog(msg: string, level: LogLevel = 'INFO', source: LogSource = 'stdout') {
|
17
|
+
while (this.entries.length > MAX_LINES) {
|
18
|
+
this.entries.shift();
|
19
|
+
}
|
20
|
+
|
21
|
+
if (!msg.endsWith('\n')) {
|
22
|
+
msg += '\n';
|
23
|
+
}
|
24
|
+
this.entries.push({
|
25
|
+
time: Date.now(),
|
26
|
+
message: msg,
|
27
|
+
level,
|
28
|
+
source,
|
29
|
+
});
|
30
|
+
}
|
31
|
+
|
32
|
+
/**
|
33
|
+
*
|
34
|
+
* @return {LogEntry[]}
|
35
|
+
*/
|
36
|
+
getLogs() {
|
37
|
+
return this.entries;
|
38
|
+
}
|
39
|
+
|
40
|
+
toString() {
|
41
|
+
return this.getLogs()
|
42
|
+
.map((entry) => entry.message)
|
43
|
+
.join('\n');
|
44
|
+
}
|
45
|
+
}
|
46
|
+
|
47
|
+
module.exports = LogData;
|