@kapeta/local-cluster-service 0.1.2 → 0.2.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/package.json +1 -1
- package/src/containerManager.js +66 -29
- package/src/instanceManager.js +7 -3
- package/src/operatorManager.js +1 -7
- package/src/utils/BlockInstanceRunner.js +40 -6
package/CHANGELOG.md
CHANGED
@@ -1,3 +1,17 @@
|
|
1
|
+
## [0.2.1](https://github.com/kapetacom/local-cluster-service/compare/v0.2.0...v0.2.1) (2023-05-07)
|
2
|
+
|
3
|
+
|
4
|
+
### Bug Fixes
|
5
|
+
|
6
|
+
* Missing semantic ([7173501](https://github.com/kapetacom/local-cluster-service/commit/7173501faf2a15caa373ef2f89c6e718deaa06f1))
|
7
|
+
|
8
|
+
# [0.2.0](https://github.com/kapetacom/local-cluster-service/compare/v0.1.2...v0.2.0) (2023-05-07)
|
9
|
+
|
10
|
+
|
11
|
+
### Features
|
12
|
+
|
13
|
+
* Add support for health checks and mounts ([cac607b](https://github.com/kapetacom/local-cluster-service/commit/cac607bc8b592e27c8b6c2ff09476f90b2e4c3f3))
|
14
|
+
|
1
15
|
## [0.1.2](https://github.com/kapetacom/local-cluster-service/compare/v0.1.1...v0.1.2) (2023-05-06)
|
2
16
|
|
3
17
|
|
package/package.json
CHANGED
package/src/containerManager.js
CHANGED
@@ -3,10 +3,14 @@ const path = require("path");
|
|
3
3
|
const _ = require('lodash');
|
4
4
|
const FS = require("node:fs");
|
5
5
|
const os = require("os");
|
6
|
+
const Path = require("path");
|
7
|
+
const storageService = require("./storageService");
|
8
|
+
const mkdirp = require("mkdirp");
|
9
|
+
const {parseKapetaUri} = require("@kapeta/nodejs-utils");
|
6
10
|
const LABEL_PORT_PREFIX = "kapeta_port-";
|
7
11
|
|
8
12
|
const NANO_SECOND = 1000000;
|
9
|
-
const HEALTH_CHECK_INTERVAL =
|
13
|
+
const HEALTH_CHECK_INTERVAL = 2000;
|
10
14
|
const HEALTH_CHECK_MAX = 20;
|
11
15
|
|
12
16
|
const promisifyStream = (stream) =>
|
@@ -20,12 +24,32 @@ class ContainerManager {
|
|
20
24
|
constructor() {
|
21
25
|
this._docker = null;
|
22
26
|
this._alive = false;
|
27
|
+
this._mountDir = Path.join(storageService.getKapetaBasedir(), 'mounts');
|
28
|
+
mkdirp.sync(this._mountDir);
|
23
29
|
}
|
30
|
+
|
31
|
+
|
24
32
|
|
25
33
|
isAlive() {
|
26
34
|
return this._alive;
|
27
35
|
}
|
28
36
|
|
37
|
+
getMountPoint(kind, mountName) {
|
38
|
+
const kindUri = parseKapetaUri(kind);
|
39
|
+
return Path.join(this._mountDir, kindUri.handle, kindUri.name, mountName);
|
40
|
+
}
|
41
|
+
|
42
|
+
createMounts(kind, mountOpts) {
|
43
|
+
const mounts = {};
|
44
|
+
|
45
|
+
_.forEach(mountOpts, (containerPath, mountName) => {
|
46
|
+
const hostPath = this.getMountPoint(kind, mountName);
|
47
|
+
mkdirp.sync(hostPath);
|
48
|
+
mounts[containerPath] = hostPath;
|
49
|
+
});
|
50
|
+
return mounts;
|
51
|
+
}
|
52
|
+
|
29
53
|
async initialize() {
|
30
54
|
// try
|
31
55
|
const connectOptions = [
|
@@ -94,6 +118,34 @@ class ContainerManager {
|
|
94
118
|
)
|
95
119
|
.then((stream) => promisifyStream(stream));
|
96
120
|
}
|
121
|
+
|
122
|
+
toDockerMounts(mounts) {
|
123
|
+
const Mounts = [];
|
124
|
+
_.forEach(mounts, (Source, Target) => {
|
125
|
+
Mounts.push({
|
126
|
+
Target,
|
127
|
+
Source,
|
128
|
+
Type: "bind",
|
129
|
+
ReadOnly: false,
|
130
|
+
Consistency: "consistent",
|
131
|
+
});
|
132
|
+
});
|
133
|
+
|
134
|
+
return Mounts;
|
135
|
+
}
|
136
|
+
|
137
|
+
toDockerHealth(health) {
|
138
|
+
return {
|
139
|
+
Test: ["CMD-SHELL", health.cmd],
|
140
|
+
Interval: health.interval
|
141
|
+
? health.interval * NANO_SECOND
|
142
|
+
: 5000 * NANO_SECOND,
|
143
|
+
Timeout: health.timeout
|
144
|
+
? health.timeout * NANO_SECOND
|
145
|
+
: 15000 * NANO_SECOND,
|
146
|
+
Retries: health.retries || 10,
|
147
|
+
};
|
148
|
+
}
|
97
149
|
|
98
150
|
/**
|
99
151
|
*
|
@@ -103,7 +155,7 @@ class ContainerManager {
|
|
103
155
|
* @return {Promise<ContainerInfo>}
|
104
156
|
*/
|
105
157
|
async run(image, name, opts) {
|
106
|
-
|
158
|
+
|
107
159
|
const PortBindings = {};
|
108
160
|
const Env = [];
|
109
161
|
const Labels = {
|
@@ -127,15 +179,7 @@ class ContainerManager {
|
|
127
179
|
Labels[LABEL_PORT_PREFIX + portInfo.hostPort] = portInfo.type;
|
128
180
|
});
|
129
181
|
|
130
|
-
|
131
|
-
Mounts.push({
|
132
|
-
Target,
|
133
|
-
Source,
|
134
|
-
Type: "bind",
|
135
|
-
ReadOnly: false,
|
136
|
-
Consistency: "consistent",
|
137
|
-
});
|
138
|
-
});
|
182
|
+
const Mounts = this.toDockerMounts(opts.mounts);
|
139
183
|
|
140
184
|
_.forEach(opts.env, (value, name) => {
|
141
185
|
Env.push(name + "=" + value);
|
@@ -144,16 +188,7 @@ class ContainerManager {
|
|
144
188
|
let HealthCheck = undefined;
|
145
189
|
|
146
190
|
if (opts.health) {
|
147
|
-
HealthCheck =
|
148
|
-
Test: ["CMD-SHELL", opts.health.cmd],
|
149
|
-
Interval: opts.health.interval
|
150
|
-
? opts.health.interval * NANO_SECOND
|
151
|
-
: 5000 * NANO_SECOND,
|
152
|
-
Timeout: opts.health.timeout
|
153
|
-
? opts.health.timeout * NANO_SECOND
|
154
|
-
: 15000 * NANO_SECOND,
|
155
|
-
Retries: opts.health.retries || 10,
|
156
|
-
};
|
191
|
+
HealthCheck = this.toDockerHealth(opts.health);
|
157
192
|
|
158
193
|
console.log("Adding health check", HealthCheck);
|
159
194
|
}
|
@@ -172,7 +207,7 @@ class ContainerManager {
|
|
172
207
|
});
|
173
208
|
|
174
209
|
if (opts.health) {
|
175
|
-
await this.
|
210
|
+
await this.waitForHealthy(dockerContainer);
|
176
211
|
}
|
177
212
|
|
178
213
|
return new ContainerInfo(dockerContainer);
|
@@ -187,31 +222,33 @@ class ContainerManager {
|
|
187
222
|
}
|
188
223
|
|
189
224
|
|
190
|
-
async
|
225
|
+
async waitForHealthy(container, attempt) {
|
191
226
|
if (!attempt) {
|
192
227
|
attempt = 0;
|
193
228
|
}
|
194
229
|
|
195
230
|
if (attempt >= HEALTH_CHECK_MAX) {
|
196
|
-
throw new Error("
|
231
|
+
throw new Error("Container did not become healthy within the timeout");
|
197
232
|
}
|
198
233
|
|
199
234
|
if (await this._isHealthy(container)) {
|
200
|
-
console.log("Container became healthy");
|
201
235
|
return;
|
202
236
|
}
|
203
237
|
|
204
|
-
return new Promise((resolve) => {
|
238
|
+
return new Promise((resolve, reject) => {
|
205
239
|
setTimeout(async () => {
|
206
|
-
|
207
|
-
|
240
|
+
try {
|
241
|
+
await this.waitForHealthy(container, attempt + 1);
|
242
|
+
resolve();
|
243
|
+
} catch (err) {
|
244
|
+
reject(err);
|
245
|
+
}
|
208
246
|
}, HEALTH_CHECK_INTERVAL);
|
209
247
|
});
|
210
248
|
}
|
211
249
|
|
212
250
|
async _isHealthy(container) {
|
213
251
|
const info = await container.status();
|
214
|
-
|
215
252
|
return info?.data?.State?.Health?.Status === "healthy";
|
216
253
|
}
|
217
254
|
|
package/src/instanceManager.js
CHANGED
@@ -193,6 +193,7 @@ class InstanceManager {
|
|
193
193
|
if (instance) {
|
194
194
|
instance.status = STATUS_STARTING;
|
195
195
|
instance.pid = info.pid;
|
196
|
+
instance.address = address;
|
196
197
|
if (info.type) {
|
197
198
|
instance.type = info.type;
|
198
199
|
}
|
@@ -207,7 +208,8 @@ class InstanceManager {
|
|
207
208
|
status: STATUS_STARTING,
|
208
209
|
pid: info.pid,
|
209
210
|
type: info.type,
|
210
|
-
health: healthUrl
|
211
|
+
health: healthUrl,
|
212
|
+
address
|
211
213
|
};
|
212
214
|
|
213
215
|
this._instances.push(instance);
|
@@ -385,7 +387,8 @@ class InstanceManager {
|
|
385
387
|
await this.registerInstance(planRef, instanceId, {
|
386
388
|
type: process.type,
|
387
389
|
pid: process.pid,
|
388
|
-
health: null
|
390
|
+
health: null,
|
391
|
+
portType: process.portType,
|
389
392
|
});
|
390
393
|
|
391
394
|
return this._processes[planRef][instanceId] = process;
|
@@ -402,7 +405,8 @@ class InstanceManager {
|
|
402
405
|
await this.registerInstance(planRef, instanceId, {
|
403
406
|
type: 'local',
|
404
407
|
pid: null,
|
405
|
-
health: null
|
408
|
+
health: null,
|
409
|
+
portType: DEFAULT_HEALTH_PORT_TYPE,
|
406
410
|
});
|
407
411
|
|
408
412
|
this._emit(instanceId, EVENT_INSTANCE_LOG, logs[0]);
|
package/src/operatorManager.js
CHANGED
@@ -144,13 +144,7 @@ class OperatorManager {
|
|
144
144
|
};
|
145
145
|
}
|
146
146
|
|
147
|
-
const mounts =
|
148
|
-
|
149
|
-
_.forEach(operatorData.mounts, (containerPath, mountName) => {
|
150
|
-
const hostPath = this._getMountPoint(resourceType, mountName);
|
151
|
-
mkdirp.sync(hostPath);
|
152
|
-
mounts[containerPath] = hostPath;
|
153
|
-
});
|
147
|
+
const mounts = containerManager.createMounts(resourceType, operatorData.mounts);
|
154
148
|
|
155
149
|
const containerName = containerBaseName + '-' + md5(nameParts.join('_'));
|
156
150
|
let container = await containerManager.get(containerName);
|
@@ -9,6 +9,7 @@ const serviceManager = require("../serviceManager");
|
|
9
9
|
const containerManager = require("../containerManager");
|
10
10
|
const LogData = require("./LogData");
|
11
11
|
const EventEmitter = require("events");
|
12
|
+
const md5 = require('md5');
|
12
13
|
const {execSync} = require("child_process");
|
13
14
|
|
14
15
|
const KIND_BLOCK_TYPE_OPERATOR = 'core/block-type-operator';
|
@@ -25,10 +26,6 @@ const DOCKER_ENV_VARS = [
|
|
25
26
|
`KAPETA_ENVIRONMENT_TYPE=docker`,
|
26
27
|
]
|
27
28
|
|
28
|
-
function md5(data) {
|
29
|
-
return require('crypto').createHash('md5').update(data).digest("hex");
|
30
|
-
}
|
31
|
-
|
32
29
|
|
33
30
|
class BlockInstanceRunner {
|
34
31
|
/**
|
@@ -117,11 +114,19 @@ class BlockInstanceRunner {
|
|
117
114
|
if (provider.definition.kind === KIND_BLOCK_TYPE_OPERATOR) {
|
118
115
|
processDetails = await this._startOperatorProcess(blockInstance, blockUri, provider, env);
|
119
116
|
} else {
|
117
|
+
//We need a port type to know how to connect to the block consistently
|
118
|
+
const portTypes = definition.definition?.spec?.providers.map(provider => {
|
119
|
+
return provider.spec?.port?.type
|
120
|
+
}).filter(t => !!t) ?? [];
|
120
121
|
if (blockUri.version === 'local') {
|
121
122
|
processDetails = await this._startLocalProcess(blockInstance, blockUri, env);
|
122
123
|
} else {
|
123
124
|
processDetails = await this._startDockerProcess(blockInstance, blockUri, env);
|
124
125
|
}
|
126
|
+
|
127
|
+
if (portTypes.length > 0) {
|
128
|
+
processDetails.portType = portTypes[0];
|
129
|
+
}
|
125
130
|
}
|
126
131
|
|
127
132
|
return {
|
@@ -386,6 +391,8 @@ class BlockInstanceRunner {
|
|
386
391
|
const ExposedPorts = {};
|
387
392
|
const addonEnv = {};
|
388
393
|
const PortBindings = {};
|
394
|
+
let HealthCheck = undefined;
|
395
|
+
let Mounts = [];
|
389
396
|
const promises = Object.entries(spec.local.ports)
|
390
397
|
.map(async ([portType, value]) => {
|
391
398
|
const dockerPort = `${value.port}/${value.type}`;
|
@@ -402,17 +409,34 @@ class BlockInstanceRunner {
|
|
402
409
|
|
403
410
|
await Promise.all(promises);
|
404
411
|
|
412
|
+
if (spec.local?.env) {
|
413
|
+
Object.entries(spec.local.env).forEach(([key, value]) => {
|
414
|
+
addonEnv[key] = value;
|
415
|
+
});
|
416
|
+
}
|
417
|
+
|
418
|
+
if (spec.local?.mounts) {
|
419
|
+
const mounts = containerManager.createMounts(blockUri.id, spec.local.mounts);
|
420
|
+
Mounts = containerManager.toDockerMounts(mounts);
|
421
|
+
}
|
422
|
+
|
423
|
+
if (spec.local?.health) {
|
424
|
+
HealthCheck = containerManager.toDockerHealth(spec.local?.health);
|
425
|
+
}
|
426
|
+
|
405
427
|
logs.addLog(`Creating new container for block: ${containerName}`);
|
406
428
|
container = await containerManager.startContainer({
|
407
429
|
Image: dockerImage,
|
408
430
|
name: containerName,
|
409
431
|
ExposedPorts,
|
432
|
+
HealthCheck,
|
410
433
|
HostConfig: {
|
411
434
|
Binds: [
|
412
435
|
`${kapetaYmlPath}:/kapeta.yml:ro`,
|
413
436
|
`${ClusterConfig.getKapetaBasedir()}:${ClusterConfig.getKapetaBasedir()}`
|
414
437
|
],
|
415
|
-
PortBindings
|
438
|
+
PortBindings,
|
439
|
+
Mounts
|
416
440
|
},
|
417
441
|
Labels: {
|
418
442
|
'instance': blockInstance.id
|
@@ -426,9 +450,19 @@ class BlockInstanceRunner {
|
|
426
450
|
}).map(([key, value]) => `${key}=${value}`)
|
427
451
|
]
|
428
452
|
});
|
453
|
+
|
454
|
+
if (HealthCheck) {
|
455
|
+
await containerManager.waitForHealthy(container);
|
456
|
+
}
|
457
|
+
}
|
458
|
+
|
459
|
+
const out = await this._handleContainer(container, logs, true);
|
460
|
+
const portTypes = spec.local.ports ? Object.keys(spec.local.ports) : [];
|
461
|
+
if (portTypes.length > 0) {
|
462
|
+
out.portType = portTypes[0];
|
429
463
|
}
|
430
464
|
|
431
|
-
return
|
465
|
+
return out;
|
432
466
|
}
|
433
467
|
}
|
434
468
|
|