@kapeta/local-cluster-service 0.9.0 → 0.10.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/src/containerManager.d.ts +7 -8
- package/dist/cjs/src/containerManager.js +78 -65
- package/dist/cjs/src/instanceManager.js +4 -2
- package/dist/cjs/src/operatorManager.js +40 -25
- package/dist/cjs/src/utils/BlockInstanceRunner.d.ts +1 -0
- package/dist/cjs/src/utils/BlockInstanceRunner.js +79 -170
- package/dist/esm/src/containerManager.d.ts +7 -8
- package/dist/esm/src/containerManager.js +77 -64
- package/dist/esm/src/instanceManager.js +4 -2
- package/dist/esm/src/operatorManager.js +42 -27
- package/dist/esm/src/utils/BlockInstanceRunner.d.ts +1 -0
- package/dist/esm/src/utils/BlockInstanceRunner.js +79 -170
- package/package.json +1 -1
- package/src/containerManager.ts +76 -71
- package/src/instanceManager.ts +8 -2
- package/src/operatorManager.ts +52 -26
- package/src/utils/BlockInstanceRunner.ts +88 -177
@@ -134,26 +134,6 @@ class BlockInstanceRunner {
|
|
134
134
|
throw new Error(`Missing docker image information: ${JSON.stringify(localContainer)}`);
|
135
135
|
}
|
136
136
|
const containerName = (0, utils_1.getBlockInstanceContainerName)(blockInstance.id);
|
137
|
-
const logs = new LogData_1.LogData();
|
138
|
-
logs.addLog(`Starting block ${blockInstance.ref}`);
|
139
|
-
const containerInfo = await containerManager_1.containerManager.getContainerByName(containerName);
|
140
|
-
let container = containerInfo?.native;
|
141
|
-
console.log('Starting dev container', containerName);
|
142
|
-
if (container) {
|
143
|
-
console.log(`Dev container already exists. Deleting...`);
|
144
|
-
try {
|
145
|
-
await container.delete({
|
146
|
-
force: true,
|
147
|
-
});
|
148
|
-
}
|
149
|
-
catch (e) {
|
150
|
-
throw new Error('Failed to delete existing container: ' + e.message);
|
151
|
-
}
|
152
|
-
container = undefined;
|
153
|
-
}
|
154
|
-
logs.addLog(`Creating new container for block: ${containerName}`);
|
155
|
-
console.log('Creating new dev container', containerName, dockerImage);
|
156
|
-
await containerManager_1.containerManager.pull(dockerImage);
|
157
137
|
const startCmd = localContainer.handlers?.onCreate ? localContainer.handlers.onCreate : '';
|
158
138
|
const dockerOpts = localContainer.options ?? {};
|
159
139
|
const homeDir = localContainer.userHome ? localContainer.userHome : '/root';
|
@@ -182,8 +162,7 @@ class BlockInstanceRunner {
|
|
182
162
|
if (localContainer.healthcheck) {
|
183
163
|
HealthCheck = containerManager_1.containerManager.toDockerHealth({ cmd: localContainer.healthcheck });
|
184
164
|
}
|
185
|
-
|
186
|
-
container = await containerManager_1.containerManager.startContainer({
|
165
|
+
return this.ensureContainer({
|
187
166
|
Image: dockerImage,
|
188
167
|
name: containerName,
|
189
168
|
WorkingDir: workingDir,
|
@@ -210,8 +189,12 @@ class BlockInstanceRunner {
|
|
210
189
|
},
|
211
190
|
...dockerOpts,
|
212
191
|
});
|
192
|
+
}
|
193
|
+
async ensureContainer(opts) {
|
194
|
+
const logs = new LogData_1.LogData();
|
195
|
+
const container = await containerManager_1.containerManager.ensureContainer(opts);
|
213
196
|
try {
|
214
|
-
if (HealthCheck) {
|
197
|
+
if (opts.HealthCheck) {
|
215
198
|
await containerManager_1.containerManager.waitForHealthy(container);
|
216
199
|
}
|
217
200
|
else {
|
@@ -245,7 +228,7 @@ class BlockInstanceRunner {
|
|
245
228
|
const data = status.data;
|
246
229
|
if (deleteOnExit) {
|
247
230
|
try {
|
248
|
-
await
|
231
|
+
await containerManager_1.containerManager.remove(container);
|
249
232
|
}
|
250
233
|
catch (e) { }
|
251
234
|
}
|
@@ -262,7 +245,7 @@ class BlockInstanceRunner {
|
|
262
245
|
try {
|
263
246
|
await localContainer.stop();
|
264
247
|
if (deleteOnExit) {
|
265
|
-
await
|
248
|
+
await containerManager_1.containerManager.remove(localContainer);
|
266
249
|
}
|
267
250
|
}
|
268
251
|
catch (e) { }
|
@@ -289,45 +272,23 @@ class BlockInstanceRunner {
|
|
289
272
|
}
|
290
273
|
const containerName = (0, utils_1.getBlockInstanceContainerName)(blockInstance.id);
|
291
274
|
const logs = new LogData_1.LogData();
|
292
|
-
const containerInfo = await containerManager_1.containerManager.getContainerByName(containerName);
|
293
|
-
let container = containerInfo?.native;
|
294
275
|
// For windows we need to default to root
|
295
276
|
const innerHome = process.platform === 'win32' ? '/root/.kapeta' : local_cluster_config_1.default.getKapetaBasedir();
|
296
|
-
|
297
|
-
|
298
|
-
|
299
|
-
|
300
|
-
|
301
|
-
|
302
|
-
|
303
|
-
|
304
|
-
|
305
|
-
|
306
|
-
|
307
|
-
|
308
|
-
|
309
|
-
|
310
|
-
|
311
|
-
Labels: {
|
312
|
-
instance: blockInstance.id,
|
313
|
-
},
|
314
|
-
Env: [
|
315
|
-
...DOCKER_ENV_VARS,
|
316
|
-
`KAPETA_LOCAL_CLUSTER_PORT=${clusterService_1.clusterService.getClusterServicePort()}`,
|
317
|
-
...Object.entries(env).map(([key, value]) => `${key}=${value}`),
|
318
|
-
],
|
319
|
-
HostConfig: {
|
320
|
-
Binds: [`${(0, containerManager_1.toLocalBindVolume)(local_cluster_config_1.default.getKapetaBasedir())}:${innerHome}`],
|
321
|
-
},
|
322
|
-
});
|
323
|
-
try {
|
324
|
-
await containerManager_1.containerManager.waitForReady(container);
|
325
|
-
}
|
326
|
-
catch (e) {
|
327
|
-
logs.addLog(e.message, 'ERROR');
|
328
|
-
}
|
329
|
-
}
|
330
|
-
return this._handleContainer(container, logs);
|
277
|
+
return this.ensureContainer({
|
278
|
+
Image: dockerImage,
|
279
|
+
name: containerName,
|
280
|
+
Labels: {
|
281
|
+
instance: blockInstance.id,
|
282
|
+
},
|
283
|
+
Env: [
|
284
|
+
...DOCKER_ENV_VARS,
|
285
|
+
`KAPETA_LOCAL_CLUSTER_PORT=${clusterService_1.clusterService.getClusterServicePort()}`,
|
286
|
+
...Object.entries(env).map(([key, value]) => `${key}=${value}`),
|
287
|
+
],
|
288
|
+
HostConfig: {
|
289
|
+
Binds: [`${(0, containerManager_1.toLocalBindVolume)(local_cluster_config_1.default.getKapetaBasedir())}:${innerHome}`],
|
290
|
+
},
|
291
|
+
});
|
331
292
|
}
|
332
293
|
/**
|
333
294
|
*
|
@@ -350,120 +311,68 @@ class BlockInstanceRunner {
|
|
350
311
|
throw new Error(`Provider did not have local image: ${providerRef}`);
|
351
312
|
}
|
352
313
|
const dockerImage = spec?.local?.image;
|
353
|
-
try {
|
354
|
-
await containerManager_1.containerManager.pull(dockerImage);
|
355
|
-
}
|
356
|
-
catch (e) {
|
357
|
-
console.warn('Failed to pull image. Continuing...', e);
|
358
|
-
}
|
359
314
|
const containerName = (0, utils_1.getBlockInstanceContainerName)(blockInstance.id);
|
360
315
|
const logs = new LogData_1.LogData();
|
361
|
-
const containerInfo = await containerManager_1.containerManager.getContainerByName(containerName);
|
362
|
-
let container = containerInfo?.native;
|
363
|
-
if (container) {
|
364
|
-
const containerData = container.data;
|
365
|
-
if (containerData.State === 'running') {
|
366
|
-
logs.addLog(`Found existing running container for block: ${containerName}`);
|
367
|
-
}
|
368
|
-
else {
|
369
|
-
if (containerData.State?.ExitCode > 0) {
|
370
|
-
logs.addLog(`Container exited with code: ${containerData.State.ExitCode}. Deleting...`);
|
371
|
-
try {
|
372
|
-
await container.delete();
|
373
|
-
}
|
374
|
-
catch (e) { }
|
375
|
-
container = undefined;
|
376
|
-
}
|
377
|
-
else {
|
378
|
-
logs.addLog(`Found existing container for block: ${containerName}. Starting now`);
|
379
|
-
try {
|
380
|
-
await container.start();
|
381
|
-
}
|
382
|
-
catch (e) {
|
383
|
-
console.warn('Failed to start container. Deleting...', e);
|
384
|
-
try {
|
385
|
-
await container.delete();
|
386
|
-
}
|
387
|
-
catch (e) { }
|
388
|
-
container = undefined;
|
389
|
-
}
|
390
|
-
}
|
391
|
-
}
|
392
|
-
}
|
393
316
|
const bindHost = (0, utils_1.getBindHost)();
|
394
|
-
|
395
|
-
|
396
|
-
|
397
|
-
|
398
|
-
|
399
|
-
|
400
|
-
const
|
401
|
-
|
402
|
-
|
403
|
-
|
404
|
-
|
405
|
-
|
406
|
-
|
407
|
-
|
408
|
-
HostPort: `${publicPort}`,
|
409
|
-
},
|
410
|
-
];
|
411
|
-
});
|
412
|
-
await Promise.all(promises);
|
413
|
-
if (spec.local?.env) {
|
414
|
-
Object.entries(spec.local.env).forEach(([key, value]) => {
|
415
|
-
addonEnv[key] = value;
|
416
|
-
});
|
417
|
-
}
|
418
|
-
if (spec.local?.mounts) {
|
419
|
-
const mounts = containerManager_1.containerManager.createMounts(blockUri.id, spec.local.mounts);
|
420
|
-
Mounts = containerManager_1.containerManager.toDockerMounts(mounts);
|
421
|
-
}
|
422
|
-
if (spec.local?.health) {
|
423
|
-
HealthCheck = containerManager_1.containerManager.toDockerHealth(spec.local?.health);
|
424
|
-
}
|
425
|
-
// For windows we need to default to root
|
426
|
-
const innerHome = process.platform === 'win32' ? '/root/.kapeta' : local_cluster_config_1.default.getKapetaBasedir();
|
427
|
-
logs.addLog(`Creating new container for block: ${containerName}`);
|
428
|
-
container = await containerManager_1.containerManager.startContainer({
|
429
|
-
Image: dockerImage,
|
430
|
-
name: containerName,
|
431
|
-
ExposedPorts,
|
432
|
-
HealthCheck,
|
433
|
-
HostConfig: {
|
434
|
-
Binds: [
|
435
|
-
`${(0, containerManager_1.toLocalBindVolume)(kapetaYmlPath)}:/kapeta.yml:ro`,
|
436
|
-
`${(0, containerManager_1.toLocalBindVolume)(local_cluster_config_1.default.getKapetaBasedir())}:${innerHome}`,
|
437
|
-
],
|
438
|
-
PortBindings,
|
439
|
-
Mounts,
|
440
|
-
},
|
441
|
-
Labels: {
|
442
|
-
instance: blockInstance.id,
|
317
|
+
const ExposedPorts = {};
|
318
|
+
const addonEnv = {};
|
319
|
+
const PortBindings = {};
|
320
|
+
let HealthCheck = undefined;
|
321
|
+
let Mounts = [];
|
322
|
+
const promises = Object.entries(spec.local.ports).map(async ([portType, value]) => {
|
323
|
+
const dockerPort = `${value.port}/${value.type}`;
|
324
|
+
ExposedPorts[dockerPort] = {};
|
325
|
+
addonEnv[`KAPETA_LOCAL_SERVER_PORT_${portType.toUpperCase()}`] = value.port;
|
326
|
+
const publicPort = await serviceManager_1.serviceManager.ensureServicePort(this._systemId, blockInstance.id, portType);
|
327
|
+
PortBindings[dockerPort] = [
|
328
|
+
{
|
329
|
+
HostIp: bindHost,
|
330
|
+
HostPort: `${publicPort}`,
|
443
331
|
},
|
444
|
-
|
445
|
-
|
446
|
-
|
447
|
-
|
448
|
-
|
449
|
-
|
450
|
-
...addonEnv,
|
451
|
-
}).map(([key, value]) => `${key}=${value}`),
|
452
|
-
],
|
332
|
+
];
|
333
|
+
});
|
334
|
+
await Promise.all(promises);
|
335
|
+
if (spec.local?.env) {
|
336
|
+
Object.entries(spec.local.env).forEach(([key, value]) => {
|
337
|
+
addonEnv[key] = value;
|
453
338
|
});
|
454
|
-
try {
|
455
|
-
if (HealthCheck) {
|
456
|
-
await containerManager_1.containerManager.waitForHealthy(container);
|
457
|
-
}
|
458
|
-
else {
|
459
|
-
await containerManager_1.containerManager.waitForReady(container);
|
460
|
-
}
|
461
|
-
}
|
462
|
-
catch (e) {
|
463
|
-
logs.addLog(e.message, 'ERROR');
|
464
|
-
}
|
465
339
|
}
|
466
|
-
|
340
|
+
if (spec.local?.mounts) {
|
341
|
+
const mounts = containerManager_1.containerManager.createMounts(blockUri.id, spec.local.mounts);
|
342
|
+
Mounts = containerManager_1.containerManager.toDockerMounts(mounts);
|
343
|
+
}
|
344
|
+
if (spec.local?.health) {
|
345
|
+
HealthCheck = containerManager_1.containerManager.toDockerHealth(spec.local?.health);
|
346
|
+
}
|
347
|
+
// For windows we need to default to root
|
348
|
+
const innerHome = process.platform === 'win32' ? '/root/.kapeta' : local_cluster_config_1.default.getKapetaBasedir();
|
349
|
+
logs.addLog(`Creating new container for block: ${containerName}`);
|
350
|
+
const out = await this.ensureContainer({
|
351
|
+
Image: dockerImage,
|
352
|
+
name: containerName,
|
353
|
+
ExposedPorts,
|
354
|
+
HealthCheck,
|
355
|
+
HostConfig: {
|
356
|
+
Binds: [
|
357
|
+
`${(0, containerManager_1.toLocalBindVolume)(kapetaYmlPath)}:/kapeta.yml:ro`,
|
358
|
+
`${(0, containerManager_1.toLocalBindVolume)(local_cluster_config_1.default.getKapetaBasedir())}:${innerHome}`,
|
359
|
+
],
|
360
|
+
PortBindings,
|
361
|
+
Mounts,
|
362
|
+
},
|
363
|
+
Labels: {
|
364
|
+
instance: blockInstance.id,
|
365
|
+
},
|
366
|
+
Env: [
|
367
|
+
`KAPETA_INSTANCE_NAME=${blockInstance.ref}`,
|
368
|
+
`KAPETA_LOCAL_CLUSTER_PORT=${clusterService_1.clusterService.getClusterServicePort()}`,
|
369
|
+
...DOCKER_ENV_VARS,
|
370
|
+
...Object.entries({
|
371
|
+
...env,
|
372
|
+
...addonEnv,
|
373
|
+
}).map(([key, value]) => `${key}=${value}`),
|
374
|
+
],
|
375
|
+
});
|
467
376
|
const portTypes = spec.local.ports ? Object.keys(spec.local.ports) : [];
|
468
377
|
if (portTypes.length > 0) {
|
469
378
|
out.portType = portTypes[0];
|
@@ -41,6 +41,7 @@ interface Health {
|
|
41
41
|
timeout?: number;
|
42
42
|
retries?: number;
|
43
43
|
}
|
44
|
+
export declare const CONTAINER_LABEL_PORT_PREFIX = "kapeta_port-";
|
44
45
|
export declare const HEALTH_CHECK_TIMEOUT: number;
|
45
46
|
declare class ContainerManager {
|
46
47
|
private _docker;
|
@@ -56,7 +57,7 @@ declare class ContainerManager {
|
|
56
57
|
ping(): Promise<void>;
|
57
58
|
docker(): Docker;
|
58
59
|
getContainerByName(containerName: string): Promise<ContainerInfo | undefined>;
|
59
|
-
pull(image: string, cacheForMS?: number): Promise<
|
60
|
+
pull(image: string, cacheForMS?: number): Promise<boolean>;
|
60
61
|
toDockerMounts(mounts: StringMap): DockerMounts[];
|
61
62
|
toDockerHealth(health: Health): {
|
62
63
|
Test: string[];
|
@@ -64,18 +65,16 @@ declare class ContainerManager {
|
|
64
65
|
Timeout: number;
|
65
66
|
Retries: number;
|
66
67
|
};
|
67
|
-
|
68
|
-
|
69
|
-
mounts: {};
|
70
|
-
env: {};
|
71
|
-
cmd: string;
|
72
|
-
health: Health;
|
73
|
-
}): Promise<ContainerInfo>;
|
68
|
+
private applyHash;
|
69
|
+
ensureContainer(opts: any): Promise<Container>;
|
74
70
|
startContainer(opts: any): Promise<Container>;
|
75
71
|
waitForReady(container: Container, attempt?: number): Promise<void>;
|
76
72
|
waitForHealthy(container: Container, attempt?: number): Promise<void>;
|
77
73
|
_isReady(container: Container): Promise<any>;
|
78
74
|
_isHealthy(container: Container): Promise<boolean>;
|
75
|
+
remove(container: Container, opts?: {
|
76
|
+
force?: boolean;
|
77
|
+
}): Promise<void>;
|
79
78
|
/**
|
80
79
|
*
|
81
80
|
* @param name
|
@@ -6,8 +6,9 @@ import FSExtra from 'fs-extra';
|
|
6
6
|
import { Docker } from 'node-docker-api';
|
7
7
|
import { parseKapetaUri } from '@kapeta/nodejs-utils';
|
8
8
|
import ClusterConfiguration from '@kapeta/local-cluster-config';
|
9
|
-
import
|
10
|
-
|
9
|
+
import uuid from 'node-uuid';
|
10
|
+
import md5 from 'md5';
|
11
|
+
export const CONTAINER_LABEL_PORT_PREFIX = 'kapeta_port-';
|
11
12
|
const NANO_SECOND = 1000000;
|
12
13
|
const HEALTH_CHECK_INTERVAL = 3000;
|
13
14
|
const HEALTH_CHECK_MAX = 20;
|
@@ -140,22 +141,19 @@ class ContainerManager {
|
|
140
141
|
if (!tag) {
|
141
142
|
tag = 'latest';
|
142
143
|
}
|
143
|
-
if (
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
return;
|
148
|
-
}
|
149
|
-
}
|
150
|
-
const imageTagList = (await this.docker().image.list())
|
151
|
-
.map((image) => image.data)
|
152
|
-
.filter((imageData) => !!imageData.RepoTags)
|
153
|
-
.map((imageData) => imageData.RepoTags);
|
154
|
-
if (imageTagList.some((imageTags) => imageTags.indexOf(image) > -1)) {
|
155
|
-
console.log('Image found: %s', image);
|
156
|
-
return;
|
144
|
+
if (IMAGE_PULL_CACHE[image]) {
|
145
|
+
const timeSince = Date.now() - IMAGE_PULL_CACHE[image];
|
146
|
+
if (timeSince < cacheForMS) {
|
147
|
+
return false;
|
157
148
|
}
|
158
|
-
|
149
|
+
}
|
150
|
+
const imageTagList = (await this.docker().image.list())
|
151
|
+
.map((image) => image.data)
|
152
|
+
.filter((imageData) => !!imageData.RepoTags)
|
153
|
+
.map((imageData) => imageData.RepoTags);
|
154
|
+
if (imageTagList.some((imageTags) => imageTags.indexOf(image) > -1)) {
|
155
|
+
console.log('Image found: %s', image);
|
156
|
+
return false;
|
159
157
|
}
|
160
158
|
console.log('Pulling image: %s', image);
|
161
159
|
await this.docker()
|
@@ -166,6 +164,7 @@ class ContainerManager {
|
|
166
164
|
.then((stream) => promisifyStream(stream));
|
167
165
|
IMAGE_PULL_CACHE[image] = Date.now();
|
168
166
|
console.log('Image pulled: %s', image);
|
167
|
+
return true;
|
169
168
|
}
|
170
169
|
toDockerMounts(mounts) {
|
171
170
|
const Mounts = [];
|
@@ -188,51 +187,58 @@ class ContainerManager {
|
|
188
187
|
Retries: health.retries || 10,
|
189
188
|
};
|
190
189
|
}
|
191
|
-
|
192
|
-
|
193
|
-
|
194
|
-
const Labels = {
|
195
|
-
kapeta: 'true',
|
196
|
-
};
|
197
|
-
await this.pull(image);
|
198
|
-
const bindHost = getBindHost();
|
199
|
-
const ExposedPorts = {};
|
200
|
-
_.forEach(opts.ports, (portInfo, containerPort) => {
|
201
|
-
ExposedPorts['' + containerPort] = {};
|
202
|
-
PortBindings['' + containerPort] = [
|
203
|
-
{
|
204
|
-
HostPort: '' + portInfo.hostPort,
|
205
|
-
HostIp: bindHost,
|
206
|
-
},
|
207
|
-
];
|
208
|
-
Labels[LABEL_PORT_PREFIX + portInfo.hostPort] = portInfo.type;
|
209
|
-
});
|
210
|
-
const Mounts = this.toDockerMounts(opts.mounts);
|
211
|
-
_.forEach(opts.env, (value, name) => {
|
212
|
-
Env.push(name + '=' + value);
|
213
|
-
});
|
214
|
-
let HealthCheck = undefined;
|
215
|
-
if (opts.health) {
|
216
|
-
HealthCheck = this.toDockerHealth(opts.health);
|
217
|
-
}
|
218
|
-
const dockerContainer = await this.startContainer({
|
219
|
-
name: name,
|
220
|
-
Image: image,
|
221
|
-
Hostname: name + '.kapeta',
|
222
|
-
Labels,
|
223
|
-
Cmd: opts.cmd,
|
224
|
-
ExposedPorts,
|
225
|
-
Env,
|
226
|
-
HealthCheck,
|
227
|
-
HostConfig: {
|
228
|
-
PortBindings,
|
229
|
-
Mounts,
|
230
|
-
},
|
231
|
-
});
|
232
|
-
if (opts.health) {
|
233
|
-
await this.waitForHealthy(dockerContainer);
|
190
|
+
applyHash(dockerOpts) {
|
191
|
+
if (dockerOpts?.Labels?.HASH) {
|
192
|
+
delete dockerOpts.Labels.HASH;
|
234
193
|
}
|
235
|
-
|
194
|
+
const hash = md5(JSON.stringify(dockerOpts));
|
195
|
+
if (!dockerOpts.Labels) {
|
196
|
+
dockerOpts.Labels = {};
|
197
|
+
}
|
198
|
+
dockerOpts.Labels.HASH = hash;
|
199
|
+
}
|
200
|
+
async ensureContainer(opts) {
|
201
|
+
let imagePulled = false;
|
202
|
+
try {
|
203
|
+
imagePulled = await this.pull(opts.Image);
|
204
|
+
}
|
205
|
+
catch (e) {
|
206
|
+
console.warn('Failed to pull image. Continuing...', e);
|
207
|
+
}
|
208
|
+
this.applyHash(opts);
|
209
|
+
if (!opts.name) {
|
210
|
+
console.log('Starting unnamed container: %s', opts.Image);
|
211
|
+
return this.startContainer(opts);
|
212
|
+
}
|
213
|
+
const containerInfo = await this.getContainerByName(opts.name);
|
214
|
+
if (imagePulled) {
|
215
|
+
console.log('New version of image was pulled: %s', opts.Image);
|
216
|
+
}
|
217
|
+
else {
|
218
|
+
// If image was pulled always recreate
|
219
|
+
if (!containerInfo) {
|
220
|
+
console.log('Starting new container: %s', opts.name);
|
221
|
+
return this.startContainer(opts);
|
222
|
+
}
|
223
|
+
const containerData = containerInfo.native.data;
|
224
|
+
if (containerData?.Labels?.HASH === opts.Labels.HASH) {
|
225
|
+
if (!(await containerInfo.isRunning())) {
|
226
|
+
console.log('Starting previously created container: %s', opts.name);
|
227
|
+
await containerInfo.start();
|
228
|
+
}
|
229
|
+
else {
|
230
|
+
console.log('Previously created container already running: %s', opts.name);
|
231
|
+
}
|
232
|
+
return containerInfo.native;
|
233
|
+
}
|
234
|
+
}
|
235
|
+
if (containerInfo) {
|
236
|
+
// Remove the container and start a new one
|
237
|
+
console.log('Replacing previously created container: %s', opts.name);
|
238
|
+
await containerInfo.remove({ force: true });
|
239
|
+
}
|
240
|
+
console.log('Starting new container: %s', opts.name);
|
241
|
+
return this.startContainer(opts);
|
236
242
|
}
|
237
243
|
async startContainer(opts) {
|
238
244
|
const extraHosts = getExtraHosts(this._version);
|
@@ -318,6 +324,13 @@ class ContainerManager {
|
|
318
324
|
return false;
|
319
325
|
}
|
320
326
|
}
|
327
|
+
async remove(container, opts) {
|
328
|
+
const newName = 'deleting-' + uuid.v4();
|
329
|
+
const containerData = container.data;
|
330
|
+
// Rename the container first to avoid name conflicts if people start the same container
|
331
|
+
await container.rename({ name: newName });
|
332
|
+
await container.delete({ force: !!opts?.force });
|
333
|
+
}
|
321
334
|
/**
|
322
335
|
*
|
323
336
|
* @param name
|
@@ -373,7 +386,7 @@ export class ContainerInfo {
|
|
373
386
|
await this._container.stop();
|
374
387
|
}
|
375
388
|
async remove(opts) {
|
376
|
-
await this._container
|
389
|
+
await containerManager.remove(this._container, opts);
|
377
390
|
}
|
378
391
|
async getPort(type) {
|
379
392
|
const ports = await this.getPorts();
|
@@ -403,10 +416,10 @@ export class ContainerInfo {
|
|
403
416
|
const portTypes = {};
|
404
417
|
const ports = {};
|
405
418
|
_.forEach(inspectResult.Config.Labels, (portType, name) => {
|
406
|
-
if (!name.startsWith(
|
419
|
+
if (!name.startsWith(CONTAINER_LABEL_PORT_PREFIX)) {
|
407
420
|
return;
|
408
421
|
}
|
409
|
-
const hostPort = name.substr(
|
422
|
+
const hostPort = name.substr(CONTAINER_LABEL_PORT_PREFIX.length);
|
410
423
|
portTypes[hostPort] = portType;
|
411
424
|
});
|
412
425
|
_.forEach(inspectResult.HostConfig.PortBindings, (portBindings, containerPortSpec) => {
|
@@ -447,7 +447,8 @@ export class InstanceManager {
|
|
447
447
|
changed = true;
|
448
448
|
}
|
449
449
|
}
|
450
|
-
if (instance.desiredStatus === DesiredInstanceStatus.RUN &&
|
450
|
+
if (instance.desiredStatus === DesiredInstanceStatus.RUN &&
|
451
|
+
[InstanceStatus.STOPPED, InstanceStatus.FAILED, InstanceStatus.STOPPING].includes(newStatus)) {
|
451
452
|
//If the instance is stopped but we want it to run, start it
|
452
453
|
try {
|
453
454
|
await this.start(instance.systemId, instance.instanceId);
|
@@ -457,7 +458,8 @@ export class InstanceManager {
|
|
457
458
|
}
|
458
459
|
return;
|
459
460
|
}
|
460
|
-
if (instance.desiredStatus === DesiredInstanceStatus.STOP &&
|
461
|
+
if (instance.desiredStatus === DesiredInstanceStatus.STOP &&
|
462
|
+
[InstanceStatus.READY, InstanceStatus.STARTING, InstanceStatus.UNHEALTHY].includes(newStatus)) {
|
461
463
|
//If the instance is running but we want it to stop, stop it
|
462
464
|
try {
|
463
465
|
await this.stop(instance.systemId, instance.instanceId);
|
@@ -3,10 +3,11 @@ import md5 from 'md5';
|
|
3
3
|
import { parseKapetaUri } from '@kapeta/nodejs-utils';
|
4
4
|
import { serviceManager } from './serviceManager';
|
5
5
|
import { storageService } from './storageService';
|
6
|
-
import { containerManager } from './containerManager';
|
6
|
+
import { CONTAINER_LABEL_PORT_PREFIX, ContainerInfo, containerManager } from './containerManager';
|
7
7
|
import FSExtra from 'fs-extra';
|
8
8
|
import { definitionsManager } from './definitionsManager';
|
9
|
-
import { normalizeKapetaUri } from './utils/utils';
|
9
|
+
import { getBindHost, normalizeKapetaUri } from './utils/utils';
|
10
|
+
import _ from 'lodash';
|
10
11
|
const KIND_OPERATOR = 'core/resource-type-operator';
|
11
12
|
class Operator {
|
12
13
|
_data;
|
@@ -132,32 +133,46 @@ class OperatorManager {
|
|
132
133
|
}
|
133
134
|
const mounts = containerManager.createMounts(resourceType, operatorData.mounts);
|
134
135
|
const containerName = containerBaseName + '-' + md5(nameParts.join('_'));
|
135
|
-
|
136
|
-
const
|
137
|
-
|
138
|
-
|
139
|
-
}
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
136
|
+
const PortBindings = {};
|
137
|
+
const Env = [];
|
138
|
+
const Labels = {
|
139
|
+
kapeta: 'true',
|
140
|
+
};
|
141
|
+
const bindHost = getBindHost();
|
142
|
+
const ExposedPorts = {};
|
143
|
+
_.forEach(ports, (portInfo, containerPort) => {
|
144
|
+
ExposedPorts['' + containerPort] = {};
|
145
|
+
PortBindings['' + containerPort] = [
|
146
|
+
{
|
147
|
+
HostPort: '' + portInfo.hostPort,
|
148
|
+
HostIp: bindHost,
|
149
|
+
},
|
150
|
+
];
|
151
|
+
Labels[CONTAINER_LABEL_PORT_PREFIX + portInfo.hostPort] = portInfo.type;
|
152
|
+
});
|
153
|
+
const Mounts = containerManager.toDockerMounts(mounts);
|
154
|
+
_.forEach(operatorData.env, (value, name) => {
|
155
|
+
Env.push(name + '=' + value);
|
156
|
+
});
|
157
|
+
let HealthCheck = undefined;
|
158
|
+
if (operatorData.health) {
|
159
|
+
HealthCheck = containerManager.toDockerHealth(operatorData.health);
|
159
160
|
}
|
160
|
-
|
161
|
+
const container = await containerManager.ensureContainer({
|
162
|
+
name: containerName,
|
163
|
+
Image: operatorData.image,
|
164
|
+
Hostname: containerName + '.kapeta',
|
165
|
+
Labels,
|
166
|
+
Cmd: operatorData.cmd,
|
167
|
+
ExposedPorts,
|
168
|
+
Env,
|
169
|
+
HealthCheck,
|
170
|
+
HostConfig: {
|
171
|
+
PortBindings,
|
172
|
+
Mounts,
|
173
|
+
},
|
174
|
+
});
|
175
|
+
return new ContainerInfo(container);
|
161
176
|
}
|
162
177
|
}
|
163
178
|
export const operatorManager = new OperatorManager();
|