@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
package/CHANGELOG.md
CHANGED
@@ -1,3 +1,17 @@
|
|
1
|
+
# [0.10.0](https://github.com/kapetacom/local-cluster-service/compare/v0.9.1...v0.10.0) (2023-07-26)
|
2
|
+
|
3
|
+
|
4
|
+
### Features
|
5
|
+
|
6
|
+
* Auto-reuse containers ([#50](https://github.com/kapetacom/local-cluster-service/issues/50)) ([ecb396b](https://github.com/kapetacom/local-cluster-service/commit/ecb396b541f9184302e0681f4803d2404336138e))
|
7
|
+
|
8
|
+
## [0.9.1](https://github.com/kapetacom/local-cluster-service/compare/v0.9.0...v0.9.1) (2023-07-26)
|
9
|
+
|
10
|
+
|
11
|
+
### Bug Fixes
|
12
|
+
|
13
|
+
* Rename containers before deleting to avoid name conflicts ([#49](https://github.com/kapetacom/local-cluster-service/issues/49)) ([ac977f0](https://github.com/kapetacom/local-cluster-service/commit/ac977f0a9f18f57a517342d51e4c1e1fee68e4ff))
|
14
|
+
|
1
15
|
# [0.9.0](https://github.com/kapetacom/local-cluster-service/compare/v0.8.3...v0.9.0) (2023-07-24)
|
2
16
|
|
3
17
|
|
@@ -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
|
@@ -3,7 +3,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
3
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
4
4
|
};
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
6
|
-
exports.containerManager = exports.toLocalBindVolume = exports.getExtraHosts = exports.ContainerInfo = exports.HEALTH_CHECK_TIMEOUT = void 0;
|
6
|
+
exports.containerManager = exports.toLocalBindVolume = exports.getExtraHosts = exports.ContainerInfo = exports.HEALTH_CHECK_TIMEOUT = exports.CONTAINER_LABEL_PORT_PREFIX = void 0;
|
7
7
|
const path_1 = __importDefault(require("path"));
|
8
8
|
const storageService_1 = require("./storageService");
|
9
9
|
const os_1 = __importDefault(require("os"));
|
@@ -12,8 +12,9 @@ const fs_extra_1 = __importDefault(require("fs-extra"));
|
|
12
12
|
const node_docker_api_1 = require("node-docker-api");
|
13
13
|
const nodejs_utils_1 = require("@kapeta/nodejs-utils");
|
14
14
|
const local_cluster_config_1 = __importDefault(require("@kapeta/local-cluster-config"));
|
15
|
-
const
|
16
|
-
const
|
15
|
+
const node_uuid_1 = __importDefault(require("node-uuid"));
|
16
|
+
const md5_1 = __importDefault(require("md5"));
|
17
|
+
exports.CONTAINER_LABEL_PORT_PREFIX = 'kapeta_port-';
|
17
18
|
const NANO_SECOND = 1000000;
|
18
19
|
const HEALTH_CHECK_INTERVAL = 3000;
|
19
20
|
const HEALTH_CHECK_MAX = 20;
|
@@ -146,22 +147,19 @@ class ContainerManager {
|
|
146
147
|
if (!tag) {
|
147
148
|
tag = 'latest';
|
148
149
|
}
|
149
|
-
if (
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
return;
|
154
|
-
}
|
155
|
-
}
|
156
|
-
const imageTagList = (await this.docker().image.list())
|
157
|
-
.map((image) => image.data)
|
158
|
-
.filter((imageData) => !!imageData.RepoTags)
|
159
|
-
.map((imageData) => imageData.RepoTags);
|
160
|
-
if (imageTagList.some((imageTags) => imageTags.indexOf(image) > -1)) {
|
161
|
-
console.log('Image found: %s', image);
|
162
|
-
return;
|
150
|
+
if (IMAGE_PULL_CACHE[image]) {
|
151
|
+
const timeSince = Date.now() - IMAGE_PULL_CACHE[image];
|
152
|
+
if (timeSince < cacheForMS) {
|
153
|
+
return false;
|
163
154
|
}
|
164
|
-
|
155
|
+
}
|
156
|
+
const imageTagList = (await this.docker().image.list())
|
157
|
+
.map((image) => image.data)
|
158
|
+
.filter((imageData) => !!imageData.RepoTags)
|
159
|
+
.map((imageData) => imageData.RepoTags);
|
160
|
+
if (imageTagList.some((imageTags) => imageTags.indexOf(image) > -1)) {
|
161
|
+
console.log('Image found: %s', image);
|
162
|
+
return false;
|
165
163
|
}
|
166
164
|
console.log('Pulling image: %s', image);
|
167
165
|
await this.docker()
|
@@ -172,6 +170,7 @@ class ContainerManager {
|
|
172
170
|
.then((stream) => promisifyStream(stream));
|
173
171
|
IMAGE_PULL_CACHE[image] = Date.now();
|
174
172
|
console.log('Image pulled: %s', image);
|
173
|
+
return true;
|
175
174
|
}
|
176
175
|
toDockerMounts(mounts) {
|
177
176
|
const Mounts = [];
|
@@ -194,51 +193,58 @@ class ContainerManager {
|
|
194
193
|
Retries: health.retries || 10,
|
195
194
|
};
|
196
195
|
}
|
197
|
-
|
198
|
-
|
199
|
-
|
200
|
-
const Labels = {
|
201
|
-
kapeta: 'true',
|
202
|
-
};
|
203
|
-
await this.pull(image);
|
204
|
-
const bindHost = (0, utils_1.getBindHost)();
|
205
|
-
const ExposedPorts = {};
|
206
|
-
lodash_1.default.forEach(opts.ports, (portInfo, containerPort) => {
|
207
|
-
ExposedPorts['' + containerPort] = {};
|
208
|
-
PortBindings['' + containerPort] = [
|
209
|
-
{
|
210
|
-
HostPort: '' + portInfo.hostPort,
|
211
|
-
HostIp: bindHost,
|
212
|
-
},
|
213
|
-
];
|
214
|
-
Labels[LABEL_PORT_PREFIX + portInfo.hostPort] = portInfo.type;
|
215
|
-
});
|
216
|
-
const Mounts = this.toDockerMounts(opts.mounts);
|
217
|
-
lodash_1.default.forEach(opts.env, (value, name) => {
|
218
|
-
Env.push(name + '=' + value);
|
219
|
-
});
|
220
|
-
let HealthCheck = undefined;
|
221
|
-
if (opts.health) {
|
222
|
-
HealthCheck = this.toDockerHealth(opts.health);
|
223
|
-
}
|
224
|
-
const dockerContainer = await this.startContainer({
|
225
|
-
name: name,
|
226
|
-
Image: image,
|
227
|
-
Hostname: name + '.kapeta',
|
228
|
-
Labels,
|
229
|
-
Cmd: opts.cmd,
|
230
|
-
ExposedPorts,
|
231
|
-
Env,
|
232
|
-
HealthCheck,
|
233
|
-
HostConfig: {
|
234
|
-
PortBindings,
|
235
|
-
Mounts,
|
236
|
-
},
|
237
|
-
});
|
238
|
-
if (opts.health) {
|
239
|
-
await this.waitForHealthy(dockerContainer);
|
196
|
+
applyHash(dockerOpts) {
|
197
|
+
if (dockerOpts?.Labels?.HASH) {
|
198
|
+
delete dockerOpts.Labels.HASH;
|
240
199
|
}
|
241
|
-
|
200
|
+
const hash = (0, md5_1.default)(JSON.stringify(dockerOpts));
|
201
|
+
if (!dockerOpts.Labels) {
|
202
|
+
dockerOpts.Labels = {};
|
203
|
+
}
|
204
|
+
dockerOpts.Labels.HASH = hash;
|
205
|
+
}
|
206
|
+
async ensureContainer(opts) {
|
207
|
+
let imagePulled = false;
|
208
|
+
try {
|
209
|
+
imagePulled = await this.pull(opts.Image);
|
210
|
+
}
|
211
|
+
catch (e) {
|
212
|
+
console.warn('Failed to pull image. Continuing...', e);
|
213
|
+
}
|
214
|
+
this.applyHash(opts);
|
215
|
+
if (!opts.name) {
|
216
|
+
console.log('Starting unnamed container: %s', opts.Image);
|
217
|
+
return this.startContainer(opts);
|
218
|
+
}
|
219
|
+
const containerInfo = await this.getContainerByName(opts.name);
|
220
|
+
if (imagePulled) {
|
221
|
+
console.log('New version of image was pulled: %s', opts.Image);
|
222
|
+
}
|
223
|
+
else {
|
224
|
+
// If image was pulled always recreate
|
225
|
+
if (!containerInfo) {
|
226
|
+
console.log('Starting new container: %s', opts.name);
|
227
|
+
return this.startContainer(opts);
|
228
|
+
}
|
229
|
+
const containerData = containerInfo.native.data;
|
230
|
+
if (containerData?.Labels?.HASH === opts.Labels.HASH) {
|
231
|
+
if (!(await containerInfo.isRunning())) {
|
232
|
+
console.log('Starting previously created container: %s', opts.name);
|
233
|
+
await containerInfo.start();
|
234
|
+
}
|
235
|
+
else {
|
236
|
+
console.log('Previously created container already running: %s', opts.name);
|
237
|
+
}
|
238
|
+
return containerInfo.native;
|
239
|
+
}
|
240
|
+
}
|
241
|
+
if (containerInfo) {
|
242
|
+
// Remove the container and start a new one
|
243
|
+
console.log('Replacing previously created container: %s', opts.name);
|
244
|
+
await containerInfo.remove({ force: true });
|
245
|
+
}
|
246
|
+
console.log('Starting new container: %s', opts.name);
|
247
|
+
return this.startContainer(opts);
|
242
248
|
}
|
243
249
|
async startContainer(opts) {
|
244
250
|
const extraHosts = getExtraHosts(this._version);
|
@@ -324,6 +330,13 @@ class ContainerManager {
|
|
324
330
|
return false;
|
325
331
|
}
|
326
332
|
}
|
333
|
+
async remove(container, opts) {
|
334
|
+
const newName = 'deleting-' + node_uuid_1.default.v4();
|
335
|
+
const containerData = container.data;
|
336
|
+
// Rename the container first to avoid name conflicts if people start the same container
|
337
|
+
await container.rename({ name: newName });
|
338
|
+
await container.delete({ force: !!opts?.force });
|
339
|
+
}
|
327
340
|
/**
|
328
341
|
*
|
329
342
|
* @param name
|
@@ -379,7 +392,7 @@ class ContainerInfo {
|
|
379
392
|
await this._container.stop();
|
380
393
|
}
|
381
394
|
async remove(opts) {
|
382
|
-
await this._container
|
395
|
+
await exports.containerManager.remove(this._container, opts);
|
383
396
|
}
|
384
397
|
async getPort(type) {
|
385
398
|
const ports = await this.getPorts();
|
@@ -409,10 +422,10 @@ class ContainerInfo {
|
|
409
422
|
const portTypes = {};
|
410
423
|
const ports = {};
|
411
424
|
lodash_1.default.forEach(inspectResult.Config.Labels, (portType, name) => {
|
412
|
-
if (!name.startsWith(
|
425
|
+
if (!name.startsWith(exports.CONTAINER_LABEL_PORT_PREFIX)) {
|
413
426
|
return;
|
414
427
|
}
|
415
|
-
const hostPort = name.substr(
|
428
|
+
const hostPort = name.substr(exports.CONTAINER_LABEL_PORT_PREFIX.length);
|
416
429
|
portTypes[hostPort] = portType;
|
417
430
|
});
|
418
431
|
lodash_1.default.forEach(inspectResult.HostConfig.PortBindings, (portBindings, containerPortSpec) => {
|
@@ -453,7 +453,8 @@ class InstanceManager {
|
|
453
453
|
changed = true;
|
454
454
|
}
|
455
455
|
}
|
456
|
-
if (instance.desiredStatus === types_1.DesiredInstanceStatus.RUN &&
|
456
|
+
if (instance.desiredStatus === types_1.DesiredInstanceStatus.RUN &&
|
457
|
+
[types_1.InstanceStatus.STOPPED, types_1.InstanceStatus.FAILED, types_1.InstanceStatus.STOPPING].includes(newStatus)) {
|
457
458
|
//If the instance is stopped but we want it to run, start it
|
458
459
|
try {
|
459
460
|
await this.start(instance.systemId, instance.instanceId);
|
@@ -463,7 +464,8 @@ class InstanceManager {
|
|
463
464
|
}
|
464
465
|
return;
|
465
466
|
}
|
466
|
-
if (instance.desiredStatus === types_1.DesiredInstanceStatus.STOP &&
|
467
|
+
if (instance.desiredStatus === types_1.DesiredInstanceStatus.STOP &&
|
468
|
+
[types_1.InstanceStatus.READY, types_1.InstanceStatus.STARTING, types_1.InstanceStatus.UNHEALTHY].includes(newStatus)) {
|
467
469
|
//If the instance is running but we want it to stop, stop it
|
468
470
|
try {
|
469
471
|
await this.stop(instance.systemId, instance.instanceId);
|
@@ -13,6 +13,7 @@ const containerManager_1 = require("./containerManager");
|
|
13
13
|
const fs_extra_1 = __importDefault(require("fs-extra"));
|
14
14
|
const definitionsManager_1 = require("./definitionsManager");
|
15
15
|
const utils_1 = require("./utils/utils");
|
16
|
+
const lodash_1 = __importDefault(require("lodash"));
|
16
17
|
const KIND_OPERATOR = 'core/resource-type-operator';
|
17
18
|
class Operator {
|
18
19
|
_data;
|
@@ -138,32 +139,46 @@ class OperatorManager {
|
|
138
139
|
}
|
139
140
|
const mounts = containerManager_1.containerManager.createMounts(resourceType, operatorData.mounts);
|
140
141
|
const containerName = containerBaseName + '-' + (0, md5_1.default)(nameParts.join('_'));
|
141
|
-
|
142
|
-
const
|
143
|
-
|
144
|
-
|
145
|
-
}
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
142
|
+
const PortBindings = {};
|
143
|
+
const Env = [];
|
144
|
+
const Labels = {
|
145
|
+
kapeta: 'true',
|
146
|
+
};
|
147
|
+
const bindHost = (0, utils_1.getBindHost)();
|
148
|
+
const ExposedPorts = {};
|
149
|
+
lodash_1.default.forEach(ports, (portInfo, containerPort) => {
|
150
|
+
ExposedPorts['' + containerPort] = {};
|
151
|
+
PortBindings['' + containerPort] = [
|
152
|
+
{
|
153
|
+
HostPort: '' + portInfo.hostPort,
|
154
|
+
HostIp: bindHost,
|
155
|
+
},
|
156
|
+
];
|
157
|
+
Labels[containerManager_1.CONTAINER_LABEL_PORT_PREFIX + portInfo.hostPort] = portInfo.type;
|
158
|
+
});
|
159
|
+
const Mounts = containerManager_1.containerManager.toDockerMounts(mounts);
|
160
|
+
lodash_1.default.forEach(operatorData.env, (value, name) => {
|
161
|
+
Env.push(name + '=' + value);
|
162
|
+
});
|
163
|
+
let HealthCheck = undefined;
|
164
|
+
if (operatorData.health) {
|
165
|
+
HealthCheck = containerManager_1.containerManager.toDockerHealth(operatorData.health);
|
165
166
|
}
|
166
|
-
|
167
|
+
const container = await containerManager_1.containerManager.ensureContainer({
|
168
|
+
name: containerName,
|
169
|
+
Image: operatorData.image,
|
170
|
+
Hostname: containerName + '.kapeta',
|
171
|
+
Labels,
|
172
|
+
Cmd: operatorData.cmd,
|
173
|
+
ExposedPorts,
|
174
|
+
Env,
|
175
|
+
HealthCheck,
|
176
|
+
HostConfig: {
|
177
|
+
PortBindings,
|
178
|
+
Mounts,
|
179
|
+
},
|
180
|
+
});
|
181
|
+
return new containerManager_1.ContainerInfo(container);
|
167
182
|
}
|
168
183
|
}
|
169
184
|
exports.operatorManager = new OperatorManager();
|