@kapeta/local-cluster-service 0.1.2 → 0.2.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 CHANGED
@@ -1,3 +1,10 @@
1
+ # [0.2.0](https://github.com/kapetacom/local-cluster-service/compare/v0.1.2...v0.2.0) (2023-05-07)
2
+
3
+
4
+ ### Features
5
+
6
+ * Add support for health checks and mounts ([cac607b](https://github.com/kapetacom/local-cluster-service/commit/cac607bc8b592e27c8b6c2ff09476f90b2e4c3f3))
7
+
1
8
  ## [0.1.2](https://github.com/kapetacom/local-cluster-service/compare/v0.1.1...v0.1.2) (2023-05-06)
2
9
 
3
10
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kapeta/local-cluster-service",
3
- "version": "0.1.2",
3
+ "version": "0.2.0",
4
4
  "description": "Manages configuration, ports and service discovery for locally running Kapeta systems",
5
5
  "main": "index.js",
6
6
  "repository": {
@@ -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 = 1000;
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
- const Mounts = [];
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
- _.forEach(opts.mounts, (Source, Target) => {
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._waitForHealthy(dockerContainer);
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 _waitForHealthy(container, attempt) {
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("Operator did not become healthy within the timeout");
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
- await this._waitForHealthy(container, attempt + 1);
207
- resolve();
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
 
@@ -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
  /**
@@ -386,6 +383,8 @@ class BlockInstanceRunner {
386
383
  const ExposedPorts = {};
387
384
  const addonEnv = {};
388
385
  const PortBindings = {};
386
+ let HealthCheck = undefined;
387
+ let Mounts = [];
389
388
  const promises = Object.entries(spec.local.ports)
390
389
  .map(async ([portType, value]) => {
391
390
  const dockerPort = `${value.port}/${value.type}`;
@@ -402,17 +401,34 @@ class BlockInstanceRunner {
402
401
 
403
402
  await Promise.all(promises);
404
403
 
404
+ if (spec.local?.env) {
405
+ Object.entries(spec.local.env).forEach(([key, value]) => {
406
+ addonEnv[key] = value;
407
+ });
408
+ }
409
+
410
+ if (spec.local?.mounts) {
411
+ const mounts = containerManager.createMounts(blockUri.id, spec.local.mounts);
412
+ Mounts = containerManager.toDockerMounts(mounts);
413
+ }
414
+
415
+ if (spec.local?.health) {
416
+ HealthCheck = containerManager.toDockerHealth(spec.local?.health);
417
+ }
418
+
405
419
  logs.addLog(`Creating new container for block: ${containerName}`);
406
420
  container = await containerManager.startContainer({
407
421
  Image: dockerImage,
408
422
  name: containerName,
409
423
  ExposedPorts,
424
+ HealthCheck,
410
425
  HostConfig: {
411
426
  Binds: [
412
427
  `${kapetaYmlPath}:/kapeta.yml:ro`,
413
428
  `${ClusterConfig.getKapetaBasedir()}:${ClusterConfig.getKapetaBasedir()}`
414
429
  ],
415
- PortBindings
430
+ PortBindings,
431
+ Mounts
416
432
  },
417
433
  Labels: {
418
434
  'instance': blockInstance.id
@@ -426,6 +442,10 @@ class BlockInstanceRunner {
426
442
  }).map(([key, value]) => `${key}=${value}`)
427
443
  ]
428
444
  });
445
+
446
+ if (HealthCheck) {
447
+ await containerManager.waitForHealthy(container);
448
+ }
429
449
  }
430
450
 
431
451
  return this._handleContainer(container, logs, true);