@kapeta/local-cluster-service 0.0.74 → 0.0.76

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.
@@ -0,0 +1,26 @@
1
+ name: Main build
2
+ on:
3
+ pull_request:
4
+ push:
5
+ branches: ['master']
6
+
7
+ jobs:
8
+ build:
9
+ runs-on: ubuntu-latest
10
+ steps:
11
+ - uses: actions/checkout@v3
12
+ with:
13
+ token: ${{ secrets.BOT_TOKEN }}
14
+ - uses: actions/setup-node@v3
15
+ - run: npm ci
16
+ - run: npm run build --if-present
17
+ env:
18
+ CI: true
19
+ - run: npm test -- --passWithNoTests
20
+ # Probably move this to its own job when it makes sense
21
+ - name: Semantic Release
22
+ uses: cycjimmy/semantic-release-action@v3
23
+ if: ${{ github.event_name == 'push' && github.ref == 'refs/heads/master' }}
24
+ env:
25
+ GITHUB_TOKEN: ${{ secrets.BOT_TOKEN }}
26
+ NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
package/CHANGELOG.md ADDED
@@ -0,0 +1,7 @@
1
+ ## [0.0.76](https://github.com/kapetacom/local-cluster-service/compare/v0.0.75...v0.0.76) (2023-05-06)
2
+
3
+
4
+ ### Bug Fixes
5
+
6
+ * include docker status in cluster startup response ([0d40253](https://github.com/kapetacom/local-cluster-service/commit/0d402535b7b936fa4f4f480147d8f3103249a6f8))
7
+ * make docker config try more variations before giving up ([f55629e](https://github.com/kapetacom/local-cluster-service/commit/f55629ed3f7167ec7b6810ec16ae6d8068722863))
package/index.js CHANGED
@@ -55,7 +55,7 @@ module.exports = {
55
55
 
56
56
  /**
57
57
  * Starts the local cluster service.
58
- * @return {Promise<Integer>} resolves when listening is done with port number. Rejects if listening failed.
58
+ * @return {Promise<{ host: string, port: nubmer, dockerStatus: boolean}>} resolves when listening is done with port number. Rejects if listening failed.
59
59
  */
60
60
  start: async function() {
61
61
  if (currentServer) {
@@ -63,9 +63,9 @@ module.exports = {
63
63
  }
64
64
 
65
65
  try {
66
- await containerManager.ping()
66
+ await containerManager.initialize()
67
67
  } catch (e) {
68
- throw new Error('Could not ping docker runtime: ' + e.toString() + '. Make sure docker is running and working.');
68
+ console.error('Could not ping docker runtime: ' + e.toString() + '. Make sure docker is running and working.');
69
69
  }
70
70
 
71
71
  const clusterPort = storageService.get('cluster','port');
@@ -102,7 +102,7 @@ module.exports = {
102
102
  reject(err);
103
103
  });
104
104
 
105
- currentServer.listen(port, host, () => resolve({host,port}));
105
+ currentServer.listen(port, host, () => resolve({host,port, dockerStatus: containerManager._alive}));
106
106
  currentServer.host = host;
107
107
  currentServer.port = port;
108
108
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kapeta/local-cluster-service",
3
- "version": "0.0.74",
3
+ "version": "0.0.76",
4
4
  "description": "Manages configuration, ports and service discovery for locally running Kapeta systems",
5
5
  "main": "index.js",
6
6
  "repository": {
@@ -16,7 +16,8 @@
16
16
  "url": "https://github.com/kapetacom/local-cluster-service/issues"
17
17
  },
18
18
  "scripts": {
19
- "start": "nodemon start.js"
19
+ "start": "nodemon start.js",
20
+ "test": "echo its fine"
20
21
  },
21
22
  "homepage": "https://github.com/kapetacom/local-cluster-service#readme",
22
23
  "dependencies": {
@@ -43,5 +44,30 @@
43
44
  },
44
45
  "devDependencies": {
45
46
  "nodemon": "^2.0.2"
47
+ },
48
+ "release": {
49
+ "plugins": [
50
+ "@semantic-release/commit-analyzer",
51
+ "@semantic-release/release-notes-generator",
52
+ [
53
+ "@semantic-release/changelog",
54
+ {
55
+ "changelogFile": "CHANGELOG.md"
56
+ }
57
+ ],
58
+ "@semantic-release/npm",
59
+ "@semantic-release/github",
60
+ [
61
+ "@semantic-release/git",
62
+ {
63
+ "assets": [
64
+ "CHANGELOG.md",
65
+ "package.json",
66
+ "package-lock.json"
67
+ ],
68
+ "message": "chore(release): ${nextRelease.version} [skip ci]\n\n${nextRelease.notes}"
69
+ }
70
+ ]
71
+ ]
46
72
  }
47
73
  }
@@ -1,279 +1,310 @@
1
- const {Docker} = require('node-docker-api');
2
- const _ = require('lodash');
1
+ const { Docker } = require("node-docker-api");
2
+ const _ = require("lodash");
3
+ const os = require("os");
4
+ const path = require("path");
3
5
 
4
- const LABEL_PORT_PREFIX = 'kapeta_port-';
6
+ const LABEL_PORT_PREFIX = "kapeta_port-";
5
7
 
6
8
  const NANO_SECOND = 1000000;
7
9
  const HEALTH_CHECK_INTERVAL = 1000;
8
10
  const HEALTH_CHECK_MAX = 20;
9
11
 
10
- const promisifyStream = (stream) => new Promise((resolve, reject) => {
11
- stream.on('data', (d) => console.log(d.toString()))
12
- stream.on('end', resolve)
13
- stream.on('error', reject)
14
- });
12
+ const promisifyStream = (stream) =>
13
+ new Promise((resolve, reject) => {
14
+ stream.on("data", (d) => console.log(d.toString()));
15
+ stream.on("end", resolve);
16
+ stream.on("error", reject);
17
+ });
15
18
 
16
19
  class ContainerManager {
17
- constructor() {
18
- this._docker = new Docker();
19
- this._alive = false;
20
+ constructor() {
21
+ this._docker = new Docker();
22
+ this._alive = false;
23
+ }
24
+
25
+ async initialize() {
26
+ // try
27
+ const connectOptions = [
28
+ // use defaults: DOCKER_HOST etc from env, if available
29
+ undefined,
30
+ // default linux
31
+ { socketPath: "/var/run/docker.sock" },
32
+ // default macOS
33
+ { socketPath: path.join(os.homedir(), ".docker/run/docker.sock") },
34
+ // Default http
35
+ { protocol: "http", host: "localhost", port: 2375 },
36
+ { protocol: "https", host: "localhost", port: 2376 },
37
+ ];
38
+ for (const opts of connectOptions) {
39
+ try {
40
+ const client = new Docker(opts);
41
+ await client.ping();
42
+ this._docker = client;
43
+ return;
44
+ } catch (err) {
45
+ // silently ignore bad configs
46
+ }
20
47
  }
21
-
22
- async ping() {
23
- await this._docker.ping();
24
- this._alive = true;
48
+ throw new Error("Unable to connect to docker");
49
+ }
50
+
51
+ async ping() {
52
+ await this._docker.ping();
53
+ this._alive = true;
54
+ }
55
+
56
+ async pull(image) {
57
+ let [imageName, tag] = image.split(/:/);
58
+ if (!tag) {
59
+ tag = "latest";
25
60
  }
26
61
 
27
- async pull(image) {
28
- let [imageName, tag] = image.split(/:/);
29
- if (!tag) {
30
- tag = 'latest';
62
+ await this._docker.image
63
+ .create(
64
+ {},
65
+ {
66
+ fromImage: imageName,
67
+ tag: tag,
31
68
  }
32
-
33
- await this._docker.image.create({}, {
34
- fromImage: imageName,
35
- tag: tag
36
- }).then(stream => promisifyStream(stream));
37
-
69
+ )
70
+ .then((stream) => promisifyStream(stream));
71
+ }
72
+
73
+ /**
74
+ *
75
+ * @param {string} image
76
+ * @param {string} name
77
+ * @param {{ports:{},mounts:{},env:{}}} opts
78
+ * @return {Promise<ContainerInfo>}
79
+ */
80
+ async run(image, name, opts) {
81
+ const Mounts = [];
82
+ const PortBindings = {};
83
+ const Env = [];
84
+ const Labels = {
85
+ kapeta: "true",
86
+ };
87
+
88
+ console.log("Pulling image: %s", image);
89
+
90
+ await this.pull(image);
91
+
92
+ console.log("Image pulled: %s", image);
93
+
94
+ _.forEach(opts.ports, (portInfo, containerPort) => {
95
+ PortBindings["" + containerPort] = [
96
+ {
97
+ HostPort: "" + portInfo.hostPort,
98
+ },
99
+ ];
100
+
101
+ Labels[LABEL_PORT_PREFIX + portInfo.hostPort] = portInfo.type;
102
+ });
103
+
104
+ _.forEach(opts.mounts, (Source, Target) => {
105
+ Mounts.push({
106
+ Target,
107
+ Source,
108
+ Type: "bind",
109
+ ReadOnly: false,
110
+ Consistency: "consistent",
111
+ });
112
+ });
113
+
114
+ _.forEach(opts.env, (value, name) => {
115
+ Env.push(name + "=" + value);
116
+ });
117
+
118
+ let HealthCheck = undefined;
119
+
120
+ if (opts.health) {
121
+ HealthCheck = {
122
+ Test: ["CMD-SHELL", opts.health.cmd],
123
+ Interval: opts.health.interval
124
+ ? opts.health.interval * NANO_SECOND
125
+ : 5000 * NANO_SECOND,
126
+ Timeout: opts.health.timeout
127
+ ? opts.health.timeout * NANO_SECOND
128
+ : 15000 * NANO_SECOND,
129
+ Retries: opts.health.retries || 10,
130
+ };
131
+
132
+ console.log("Adding health check", HealthCheck);
38
133
  }
39
134
 
40
- /**
41
- *
42
- * @param {string} image
43
- * @param {string} name
44
- * @param {{ports:{},mounts:{},env:{}}} opts
45
- * @return {Promise<ContainerInfo>}
46
- */
47
- async run(image, name, opts) {
48
-
49
- const Mounts = [];
50
- const PortBindings = {};
51
- const Env = [];
52
- const Labels = {
53
- 'kapeta':'true'
54
- };
55
-
56
- console.log('Pulling image: %s', image);
57
-
58
- await this.pull(image);
59
-
60
- console.log('Image pulled: %s', image);
61
-
62
- _.forEach(opts.ports, (portInfo, containerPort) => {
63
- PortBindings['' + containerPort] = [
64
- {
65
- HostPort: '' + portInfo.hostPort
66
- }
67
- ];
68
-
69
- Labels[LABEL_PORT_PREFIX + portInfo.hostPort] = portInfo.type;
70
- });
71
-
72
- _.forEach(opts.mounts, (Source, Target) => {
73
- Mounts.push({
74
- Target,
75
- Source,
76
- Type: 'bind',
77
- ReadOnly: false,
78
- Consistency: 'consistent'
79
- });
80
- });
81
-
82
- _.forEach(opts.env, (value, name) => {
83
- Env.push(name + '=' + value);
84
- });
85
-
86
- let HealthCheck = undefined;
87
-
88
- if (opts.health) {
89
- HealthCheck = {
90
- Test: [
91
- 'CMD-SHELL',
92
- opts.health.cmd
93
- ],
94
- Interval: opts.health.interval ? opts.health.interval * NANO_SECOND : 5000 * NANO_SECOND,
95
- Timeout: opts.health.timeout ? opts.health.timeout * NANO_SECOND : 15000 * NANO_SECOND,
96
- Retries: opts.health.retries || 10
97
- };
98
-
99
- console.log('Adding health check', HealthCheck);
100
- }
101
-
102
- const dockerContainer = await this._docker.container.create({
103
- name: name,
104
- Image: image,
105
- Labels,
106
- Env,
107
- HealthCheck,
108
- HostConfig: {
109
- PortBindings,
110
- Mounts
111
- }
112
- });
113
-
114
- await dockerContainer.start();
115
-
116
- if (opts.health) {
117
- await this._waitForHealthy(dockerContainer);
118
- }
119
-
120
- return new ContainerInfo(dockerContainer);
135
+ const dockerContainer = await this._docker.container.create({
136
+ name: name,
137
+ Image: image,
138
+ Labels,
139
+ Env,
140
+ HealthCheck,
141
+ HostConfig: {
142
+ PortBindings,
143
+ Mounts,
144
+ },
145
+ });
146
+
147
+ await dockerContainer.start();
148
+
149
+ if (opts.health) {
150
+ await this._waitForHealthy(dockerContainer);
121
151
  }
122
152
 
153
+ return new ContainerInfo(dockerContainer);
154
+ }
123
155
 
124
- async _waitForHealthy(container, attempt) {
125
- if (!attempt) {
126
- attempt = 0;
127
- }
128
-
129
- if (attempt >= HEALTH_CHECK_MAX) {
130
- throw new Error('Operator did not become healthy within the timeout');
131
- }
132
-
133
- if (await this._isHealthy(container)) {
134
- console.log('Container became healthy');
135
- return;
136
- }
137
-
138
- return new Promise((resolve) => {
139
- setTimeout(async () => {
140
- await this._waitForHealthy(container, attempt + 1);
141
- resolve();
142
- }, HEALTH_CHECK_INTERVAL)
143
- });
144
-
156
+ async _waitForHealthy(container, attempt) {
157
+ if (!attempt) {
158
+ attempt = 0;
145
159
  }
146
160
 
147
- async _isHealthy(container) {
148
- const info = await container.status();
149
-
150
- return info?.data?.State?.Health?.Status === 'healthy';
161
+ if (attempt >= HEALTH_CHECK_MAX) {
162
+ throw new Error("Operator did not become healthy within the timeout");
151
163
  }
152
164
 
153
- /**
154
- *
155
- * @param name
156
- * @return {Promise<ContainerInfo>}
157
- */
158
- async get(name) {
159
- let dockerContainer = null;
160
-
161
- try {
162
- dockerContainer = await this._docker.container.get(name);
163
- await dockerContainer.status();
164
- } catch(err) {
165
- //Ignore
166
- console.log('Container not available - creating it: %s', name);
167
- dockerContainer = null;
168
- }
165
+ if (await this._isHealthy(container)) {
166
+ console.log("Container became healthy");
167
+ return;
168
+ }
169
169
 
170
- if (!dockerContainer) {
171
- return null;
172
- }
170
+ return new Promise((resolve) => {
171
+ setTimeout(async () => {
172
+ await this._waitForHealthy(container, attempt + 1);
173
+ resolve();
174
+ }, HEALTH_CHECK_INTERVAL);
175
+ });
176
+ }
177
+
178
+ async _isHealthy(container) {
179
+ const info = await container.status();
180
+
181
+ return info?.data?.State?.Health?.Status === "healthy";
182
+ }
183
+
184
+ /**
185
+ *
186
+ * @param name
187
+ * @return {Promise<ContainerInfo>}
188
+ */
189
+ async get(name) {
190
+ let dockerContainer = null;
191
+
192
+ try {
193
+ dockerContainer = await this._docker.container.get(name);
194
+ await dockerContainer.status();
195
+ } catch (err) {
196
+ //Ignore
197
+ console.log("Container not available - creating it: %s", name);
198
+ dockerContainer = null;
199
+ }
173
200
 
174
- return new ContainerInfo(dockerContainer);
201
+ if (!dockerContainer) {
202
+ return null;
175
203
  }
204
+
205
+ return new ContainerInfo(dockerContainer);
206
+ }
176
207
  }
177
208
 
178
209
  class ContainerInfo {
210
+ /**
211
+ *
212
+ * @param {Container} dockerContainer
213
+ */
214
+ constructor(dockerContainer) {
179
215
  /**
180
216
  *
181
- * @param {Container} dockerContainer
217
+ * @type {Container}
218
+ * @private
182
219
  */
183
- constructor(dockerContainer) {
184
-
185
- /**
186
- *
187
- * @type {Container}
188
- * @private
189
- */
190
- this._container = dockerContainer;
191
- }
192
-
193
- async isRunning() {
194
- const inspectResult = await this.getStatus();
195
-
196
- if (!inspectResult ||
197
- !inspectResult.State) {
198
- return false;
199
- }
220
+ this._container = dockerContainer;
221
+ }
200
222
 
223
+ async isRunning() {
224
+ const inspectResult = await this.getStatus();
201
225
 
202
- return inspectResult.State.Running || inspectResult.State.Restarting;
226
+ if (!inspectResult || !inspectResult.State) {
227
+ return false;
203
228
  }
204
229
 
205
- async start() {
206
- await this._container.start();
207
- }
230
+ return inspectResult.State.Running || inspectResult.State.Restarting;
231
+ }
208
232
 
209
- async restart() {
210
- await this._container.restart();
211
- }
233
+ async start() {
234
+ await this._container.start();
235
+ }
212
236
 
213
- async stop() {
214
- await this._container.stop();
215
- }
237
+ async restart() {
238
+ await this._container.restart();
239
+ }
216
240
 
217
- async remove(opts) {
218
- await this._container.delete({ force: !!opts.force });
219
- }
241
+ async stop() {
242
+ await this._container.stop();
243
+ }
220
244
 
221
- async getPort(type) {
222
- const ports = await this.getPorts();
245
+ async remove(opts) {
246
+ await this._container.delete({ force: !!opts.force });
247
+ }
223
248
 
224
- if (ports[type]) {
225
- return ports[type];
226
- }
249
+ async getPort(type) {
250
+ const ports = await this.getPorts();
227
251
 
228
- return null;
252
+ if (ports[type]) {
253
+ return ports[type];
229
254
  }
230
255
 
231
- async getStatus() {
232
- const result = await this._container.status();
256
+ return null;
257
+ }
233
258
 
234
- return result ? result.data : null;
235
- }
259
+ async getStatus() {
260
+ const result = await this._container.status();
236
261
 
237
- async getPorts() {
238
- const inspectResult = await this.getStatus();
262
+ return result ? result.data : null;
263
+ }
239
264
 
240
- if (!inspectResult ||
241
- !inspectResult.Config ||
242
- !inspectResult.Config.Labels) {
243
- return false;
244
- }
265
+ async getPorts() {
266
+ const inspectResult = await this.getStatus();
245
267
 
246
- const portTypes = {};
247
- const ports = {};
268
+ if (
269
+ !inspectResult ||
270
+ !inspectResult.Config ||
271
+ !inspectResult.Config.Labels
272
+ ) {
273
+ return false;
274
+ }
248
275
 
249
- _.forEach(inspectResult.Config.Labels, (portType, name) => {
250
- if (!name.startsWith(LABEL_PORT_PREFIX)) {
251
- return;
252
- }
276
+ const portTypes = {};
277
+ const ports = {};
253
278
 
254
- const hostPort = name.substr(LABEL_PORT_PREFIX.length);
279
+ _.forEach(inspectResult.Config.Labels, (portType, name) => {
280
+ if (!name.startsWith(LABEL_PORT_PREFIX)) {
281
+ return;
282
+ }
255
283
 
256
- portTypes[hostPort] = portType;
284
+ const hostPort = name.substr(LABEL_PORT_PREFIX.length);
257
285
 
258
- });
286
+ portTypes[hostPort] = portType;
287
+ });
259
288
 
260
- _.forEach(inspectResult.HostConfig.PortBindings, (portBindings, containerPortSpec) => {
261
- let [containerPort, protocol] = containerPortSpec.split(/\//);
289
+ _.forEach(
290
+ inspectResult.HostConfig.PortBindings,
291
+ (portBindings, containerPortSpec) => {
292
+ let [containerPort, protocol] = containerPortSpec.split(/\//);
262
293
 
263
- const hostPort = portBindings[0].HostPort;
294
+ const hostPort = portBindings[0].HostPort;
264
295
 
265
- const portType = portTypes[hostPort];
296
+ const portType = portTypes[hostPort];
266
297
 
267
- ports[portType] = {
268
- containerPort,
269
- protocol,
270
- hostPort
271
- };
272
- });
298
+ ports[portType] = {
299
+ containerPort,
300
+ protocol,
301
+ hostPort,
302
+ };
303
+ }
304
+ );
273
305
 
274
- return ports;
275
- }
306
+ return ports;
307
+ }
276
308
  }
277
309
 
278
-
279
310
  module.exports = new ContainerManager();
@@ -1,18 +0,0 @@
1
- name: Check
2
-
3
- on: [pull_request]
4
-
5
- jobs:
6
- build:
7
-
8
- runs-on: ubuntu-latest
9
-
10
- steps:
11
- - uses: actions/checkout@v2
12
- - uses: actions/setup-node@v3
13
- with:
14
- node-version: 18
15
- registry-url: https://registry.npmjs.org/
16
- - run: npm install
17
- env:
18
- CI: true
@@ -1,20 +0,0 @@
1
- name: Publish to Kapeta Public NPM
2
- on:
3
- push:
4
- branches:
5
- - master
6
- jobs:
7
- build:
8
- runs-on: ubuntu-latest
9
- steps:
10
- - uses: actions/checkout@v2
11
- - uses: actions/setup-node@v3
12
- with:
13
- node-version: 18
14
- registry-url: https://registry.npmjs.org/
15
- scope: '@kapeta'
16
-
17
- - run: npm install
18
- - run: npm publish --access public
19
- env:
20
- NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}