@kapeta/local-cluster-service 0.9.1 → 0.10.1
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 +4 -8
- package/dist/cjs/src/containerManager.js +69 -64
- package/dist/cjs/src/instanceManager.js +4 -2
- package/dist/cjs/src/operatorManager.js +40 -25
- package/dist/cjs/src/utils/BlockInstanceRunner.d.ts +3 -1
- package/dist/cjs/src/utils/BlockInstanceRunner.js +159 -242
- package/dist/esm/src/containerManager.d.ts +4 -8
- package/dist/esm/src/containerManager.js +69 -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 +3 -1
- package/dist/esm/src/utils/BlockInstanceRunner.js +159 -242
- package/package.json +1 -1
- package/src/containerManager.ts +69 -73
- package/src/instanceManager.ts +8 -2
- package/src/operatorManager.ts +52 -26
- package/src/utils/BlockInstanceRunner.ts +203 -272
package/src/containerManager.ts
CHANGED
@@ -7,8 +7,8 @@ import { Docker } from 'node-docker-api';
|
|
7
7
|
import { parseKapetaUri } from '@kapeta/nodejs-utils';
|
8
8
|
import ClusterConfiguration from '@kapeta/local-cluster-config';
|
9
9
|
import { Container } from 'node-docker-api/lib/container';
|
10
|
-
import
|
11
|
-
import
|
10
|
+
import uuid from 'node-uuid';
|
11
|
+
import md5 from 'md5';
|
12
12
|
|
13
13
|
type StringMap = { [key: string]: string };
|
14
14
|
|
@@ -54,7 +54,7 @@ interface Health {
|
|
54
54
|
retries?: number;
|
55
55
|
}
|
56
56
|
|
57
|
-
const
|
57
|
+
export const CONTAINER_LABEL_PORT_PREFIX = 'kapeta_port-';
|
58
58
|
const NANO_SECOND = 1000000;
|
59
59
|
const HEALTH_CHECK_INTERVAL = 3000;
|
60
60
|
const HEALTH_CHECK_MAX = 20;
|
@@ -206,24 +206,21 @@ class ContainerManager {
|
|
206
206
|
tag = 'latest';
|
207
207
|
}
|
208
208
|
|
209
|
-
if (
|
210
|
-
|
211
|
-
|
212
|
-
|
213
|
-
return;
|
214
|
-
}
|
209
|
+
if (IMAGE_PULL_CACHE[image]) {
|
210
|
+
const timeSince = Date.now() - IMAGE_PULL_CACHE[image];
|
211
|
+
if (timeSince < cacheForMS) {
|
212
|
+
return false;
|
215
213
|
}
|
214
|
+
}
|
216
215
|
|
217
|
-
|
218
|
-
|
219
|
-
|
220
|
-
|
216
|
+
const imageTagList = (await this.docker().image.list())
|
217
|
+
.map((image) => image.data as any)
|
218
|
+
.filter((imageData) => !!imageData.RepoTags)
|
219
|
+
.map((imageData) => imageData.RepoTags as string[]);
|
221
220
|
|
222
|
-
|
223
|
-
|
224
|
-
|
225
|
-
}
|
226
|
-
console.log('Image not found: %s', image);
|
221
|
+
if (imageTagList.some((imageTags) => imageTags.indexOf(image) > -1)) {
|
222
|
+
console.log('Image found: %s', image);
|
223
|
+
return false;
|
227
224
|
}
|
228
225
|
|
229
226
|
console.log('Pulling image: %s', image);
|
@@ -240,6 +237,8 @@ class ContainerManager {
|
|
240
237
|
IMAGE_PULL_CACHE[image] = Date.now();
|
241
238
|
|
242
239
|
console.log('Image pulled: %s', image);
|
240
|
+
|
241
|
+
return true;
|
243
242
|
}
|
244
243
|
|
245
244
|
toDockerMounts(mounts: StringMap) {
|
@@ -266,66 +265,63 @@ class ContainerManager {
|
|
266
265
|
};
|
267
266
|
}
|
268
267
|
|
269
|
-
|
270
|
-
|
271
|
-
|
272
|
-
|
273
|
-
): Promise<ContainerInfo> {
|
274
|
-
const PortBindings: { [key: string]: any } = {};
|
275
|
-
const Env: string[] = [];
|
276
|
-
const Labels: StringMap = {
|
277
|
-
kapeta: 'true',
|
278
|
-
};
|
279
|
-
|
280
|
-
await this.pull(image);
|
281
|
-
|
282
|
-
const bindHost = getBindHost();
|
268
|
+
private applyHash(dockerOpts: any) {
|
269
|
+
if (dockerOpts?.Labels?.HASH) {
|
270
|
+
delete dockerOpts.Labels.HASH;
|
271
|
+
}
|
283
272
|
|
284
|
-
const
|
273
|
+
const hash = md5(JSON.stringify(dockerOpts));
|
285
274
|
|
286
|
-
|
287
|
-
|
288
|
-
|
289
|
-
|
290
|
-
|
291
|
-
HostIp: bindHost,
|
292
|
-
},
|
293
|
-
];
|
275
|
+
if (!dockerOpts.Labels) {
|
276
|
+
dockerOpts.Labels = {};
|
277
|
+
}
|
278
|
+
dockerOpts.Labels.HASH = hash;
|
279
|
+
}
|
294
280
|
|
295
|
-
|
296
|
-
|
281
|
+
async ensureContainer(opts: any) {
|
282
|
+
let imagePulled = false;
|
283
|
+
try {
|
284
|
+
imagePulled = await this.pull(opts.Image);
|
285
|
+
} catch (e) {
|
286
|
+
console.warn('Failed to pull image. Continuing...', e);
|
287
|
+
}
|
297
288
|
|
298
|
-
|
289
|
+
this.applyHash(opts);
|
290
|
+
if (!opts.name) {
|
291
|
+
console.log('Starting unnamed container: %s', opts.Image);
|
292
|
+
return this.startContainer(opts);
|
293
|
+
}
|
294
|
+
const containerInfo = await this.getContainerByName(opts.name);
|
295
|
+
if (imagePulled) {
|
296
|
+
console.log('New version of image was pulled: %s', opts.Image);
|
297
|
+
} else {
|
298
|
+
// If image was pulled always recreate
|
299
|
+
if (!containerInfo) {
|
300
|
+
console.log('Starting new container: %s', opts.name);
|
301
|
+
return this.startContainer(opts);
|
302
|
+
}
|
299
303
|
|
300
|
-
|
301
|
-
Env.push(name + '=' + value);
|
302
|
-
});
|
304
|
+
const containerData = containerInfo.native.data as any;
|
303
305
|
|
304
|
-
|
305
|
-
|
306
|
-
|
307
|
-
|
308
|
-
|
309
|
-
|
310
|
-
|
311
|
-
|
312
|
-
|
313
|
-
|
314
|
-
Cmd: opts.cmd,
|
315
|
-
ExposedPorts,
|
316
|
-
Env,
|
317
|
-
HealthCheck,
|
318
|
-
HostConfig: {
|
319
|
-
PortBindings,
|
320
|
-
Mounts,
|
321
|
-
},
|
322
|
-
});
|
306
|
+
if (containerData?.Labels?.HASH === opts.Labels.HASH) {
|
307
|
+
if (!(await containerInfo.isRunning())) {
|
308
|
+
console.log('Starting previously created container: %s', opts.name);
|
309
|
+
await containerInfo.start();
|
310
|
+
} else {
|
311
|
+
console.log('Previously created container already running: %s', opts.name);
|
312
|
+
}
|
313
|
+
return containerInfo.native;
|
314
|
+
}
|
315
|
+
}
|
323
316
|
|
324
|
-
if (
|
325
|
-
|
317
|
+
if (containerInfo) {
|
318
|
+
// Remove the container and start a new one
|
319
|
+
console.log('Replacing previously created container: %s', opts.name);
|
320
|
+
await containerInfo.remove({ force: true });
|
326
321
|
}
|
327
322
|
|
328
|
-
|
323
|
+
console.log('Starting new container: %s', opts.name);
|
324
|
+
return this.startContainer(opts);
|
329
325
|
}
|
330
326
|
|
331
327
|
async startContainer(opts: any) {
|
@@ -423,8 +419,8 @@ class ContainerManager {
|
|
423
419
|
}
|
424
420
|
}
|
425
421
|
|
426
|
-
async remove(container:Container, opts?: { force?: boolean }) {
|
427
|
-
const newName = 'deleting-' + uuid.v4()
|
422
|
+
async remove(container: Container, opts?: { force?: boolean }) {
|
423
|
+
const newName = 'deleting-' + uuid.v4();
|
428
424
|
const containerData = container.data as any;
|
429
425
|
// Rename the container first to avoid name conflicts if people start the same container
|
430
426
|
await container.rename({ name: newName });
|
@@ -537,11 +533,11 @@ export class ContainerInfo {
|
|
537
533
|
const ports: PortMap = {};
|
538
534
|
|
539
535
|
_.forEach(inspectResult.Config.Labels, (portType, name) => {
|
540
|
-
if (!name.startsWith(
|
536
|
+
if (!name.startsWith(CONTAINER_LABEL_PORT_PREFIX)) {
|
541
537
|
return;
|
542
538
|
}
|
543
539
|
|
544
|
-
const hostPort = name.substr(
|
540
|
+
const hostPort = name.substr(CONTAINER_LABEL_PORT_PREFIX.length);
|
545
541
|
|
546
542
|
portTypes[hostPort] = portType;
|
547
543
|
});
|
package/src/instanceManager.ts
CHANGED
@@ -559,7 +559,10 @@ export class InstanceManager {
|
|
559
559
|
}
|
560
560
|
}
|
561
561
|
|
562
|
-
if (
|
562
|
+
if (
|
563
|
+
instance.desiredStatus === DesiredInstanceStatus.RUN &&
|
564
|
+
[InstanceStatus.STOPPED, InstanceStatus.FAILED, InstanceStatus.STOPPING].includes(newStatus)
|
565
|
+
) {
|
563
566
|
//If the instance is stopped but we want it to run, start it
|
564
567
|
try {
|
565
568
|
await this.start(instance.systemId, instance.instanceId);
|
@@ -569,7 +572,10 @@ export class InstanceManager {
|
|
569
572
|
return;
|
570
573
|
}
|
571
574
|
|
572
|
-
if (
|
575
|
+
if (
|
576
|
+
instance.desiredStatus === DesiredInstanceStatus.STOP &&
|
577
|
+
[InstanceStatus.READY, InstanceStatus.STARTING, InstanceStatus.UNHEALTHY].includes(newStatus)
|
578
|
+
) {
|
573
579
|
//If the instance is running but we want it to stop, stop it
|
574
580
|
try {
|
575
581
|
await this.stop(instance.systemId, instance.instanceId);
|
package/src/operatorManager.ts
CHANGED
@@ -4,12 +4,14 @@ import md5 from 'md5';
|
|
4
4
|
import { parseKapetaUri } from '@kapeta/nodejs-utils';
|
5
5
|
import { serviceManager } from './serviceManager';
|
6
6
|
import { storageService } from './storageService';
|
7
|
-
import { ContainerInfo, containerManager } from './containerManager';
|
7
|
+
import { CONTAINER_LABEL_PORT_PREFIX, ContainerInfo, containerManager } from './containerManager';
|
8
8
|
import FSExtra from 'fs-extra';
|
9
|
-
import { AnyMap, EnvironmentType, OperatorInfo } from './types';
|
9
|
+
import { AnyMap, EnvironmentType, OperatorInfo, StringMap } from './types';
|
10
10
|
import { BlockInstance, Resource } from '@kapeta/schemas';
|
11
11
|
import { definitionsManager } from './definitionsManager';
|
12
|
-
import { normalizeKapetaUri } from './utils/utils';
|
12
|
+
import { getBindHost, normalizeKapetaUri } from './utils/utils';
|
13
|
+
import _ from 'lodash';
|
14
|
+
import { Container } from 'node-docker-api/lib/container';
|
13
15
|
|
14
16
|
const KIND_OPERATOR = 'core/resource-type-operator';
|
15
17
|
|
@@ -191,34 +193,58 @@ class OperatorManager {
|
|
191
193
|
const mounts = containerManager.createMounts(resourceType, operatorData.mounts);
|
192
194
|
|
193
195
|
const containerName = containerBaseName + '-' + md5(nameParts.join('_'));
|
194
|
-
let container = await containerManager.get(containerName);
|
195
196
|
|
196
|
-
const
|
197
|
-
|
198
|
-
await container.start();
|
199
|
-
}
|
197
|
+
const PortBindings: { [key: string]: any } = {};
|
198
|
+
const Env: string[] = [];
|
200
199
|
|
201
|
-
|
202
|
-
|
203
|
-
|
204
|
-
ports,
|
205
|
-
health: operatorData.health,
|
206
|
-
env: operatorData.env,
|
207
|
-
cmd: operatorData.cmd,
|
208
|
-
});
|
209
|
-
}
|
200
|
+
const Labels: StringMap = {
|
201
|
+
kapeta: 'true',
|
202
|
+
};
|
210
203
|
|
211
|
-
|
212
|
-
|
213
|
-
|
214
|
-
|
215
|
-
|
216
|
-
}
|
217
|
-
|
218
|
-
|
204
|
+
const bindHost = getBindHost();
|
205
|
+
|
206
|
+
const ExposedPorts: { [key: string]: any } = {};
|
207
|
+
|
208
|
+
_.forEach(ports, (portInfo: any, containerPort) => {
|
209
|
+
ExposedPorts['' + containerPort] = {};
|
210
|
+
PortBindings['' + containerPort] = [
|
211
|
+
{
|
212
|
+
HostPort: '' + portInfo.hostPort,
|
213
|
+
HostIp: bindHost,
|
214
|
+
},
|
215
|
+
];
|
216
|
+
|
217
|
+
Labels[CONTAINER_LABEL_PORT_PREFIX + portInfo.hostPort] = portInfo.type;
|
218
|
+
});
|
219
|
+
|
220
|
+
const Mounts = containerManager.toDockerMounts(mounts);
|
221
|
+
|
222
|
+
_.forEach(operatorData.env, (value, name) => {
|
223
|
+
Env.push(name + '=' + value);
|
224
|
+
});
|
225
|
+
|
226
|
+
let HealthCheck = undefined;
|
227
|
+
|
228
|
+
if (operatorData.health) {
|
229
|
+
HealthCheck = containerManager.toDockerHealth(operatorData.health);
|
219
230
|
}
|
220
231
|
|
221
|
-
|
232
|
+
const container = await containerManager.ensureContainer({
|
233
|
+
name: containerName,
|
234
|
+
Image: operatorData.image,
|
235
|
+
Hostname: containerName + '.kapeta',
|
236
|
+
Labels,
|
237
|
+
Cmd: operatorData.cmd,
|
238
|
+
ExposedPorts,
|
239
|
+
Env,
|
240
|
+
HealthCheck,
|
241
|
+
HostConfig: {
|
242
|
+
PortBindings,
|
243
|
+
Mounts,
|
244
|
+
},
|
245
|
+
});
|
246
|
+
|
247
|
+
return new ContainerInfo(container);
|
222
248
|
}
|
223
249
|
}
|
224
250
|
|