@kapeta/local-cluster-service 0.2.1 → 0.3.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 +7 -0
- package/package.json +6 -2
- package/src/containerManager.js +85 -72
package/CHANGELOG.md
CHANGED
@@ -1,3 +1,10 @@
|
|
1
|
+
# [0.3.0](https://github.com/kapetacom/local-cluster-service/compare/v0.2.1...v0.3.0) (2023-05-08)
|
2
|
+
|
3
|
+
|
4
|
+
### Features
|
5
|
+
|
6
|
+
* load docker config from clusterConfig if available [KAP-609] ([1ced2c1](https://github.com/kapetacom/local-cluster-service/commit/1ced2c1ed2f72bf3331e558ee2c685385c89ab1f))
|
7
|
+
|
1
8
|
## [0.2.1](https://github.com/kapetacom/local-cluster-service/compare/v0.2.0...v0.2.1) (2023-05-07)
|
2
9
|
|
3
10
|
|
package/package.json
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
{
|
2
2
|
"name": "@kapeta/local-cluster-service",
|
3
|
-
"version": "0.
|
3
|
+
"version": "0.3.0",
|
4
4
|
"description": "Manages configuration, ports and service discovery for locally running Kapeta systems",
|
5
5
|
"main": "index.js",
|
6
6
|
"repository": {
|
@@ -22,7 +22,7 @@
|
|
22
22
|
"homepage": "https://github.com/kapetacom/local-cluster-service#readme",
|
23
23
|
"dependencies": {
|
24
24
|
"@kapeta/codegen": "<2",
|
25
|
-
"@kapeta/local-cluster-config": "<2",
|
25
|
+
"@kapeta/local-cluster-config": ">= 0.1.0 <2",
|
26
26
|
"@kapeta/nodejs-api-client": "<2",
|
27
27
|
"@kapeta/nodejs-registry-utils": "<2",
|
28
28
|
"@kapeta/nodejs-utils": "<2",
|
@@ -44,6 +44,10 @@
|
|
44
44
|
"devDependencies": {
|
45
45
|
"nodemon": "^2.0.2"
|
46
46
|
},
|
47
|
+
"prettier": {
|
48
|
+
"tabWidth": 4,
|
49
|
+
"singleQuote": true
|
50
|
+
},
|
47
51
|
"release": {
|
48
52
|
"plugins": [
|
49
53
|
"@semantic-release/commit-analyzer",
|
package/src/containerManager.js
CHANGED
@@ -1,13 +1,15 @@
|
|
1
|
-
const {Docker} = require(
|
2
|
-
const path = require(
|
1
|
+
const { Docker } = require('node-docker-api');
|
2
|
+
const path = require('path');
|
3
3
|
const _ = require('lodash');
|
4
|
-
const
|
5
|
-
const
|
6
|
-
const
|
7
|
-
const
|
8
|
-
const
|
9
|
-
|
10
|
-
const
|
4
|
+
const os = require('os');
|
5
|
+
const Path = require('path');
|
6
|
+
const storageService = require('./storageService');
|
7
|
+
const mkdirp = require('mkdirp');
|
8
|
+
const { parseKapetaUri } = require('@kapeta/nodejs-utils');
|
9
|
+
|
10
|
+
const ClusterConfiguration = require('@kapeta/local-cluster-config');
|
11
|
+
|
12
|
+
const LABEL_PORT_PREFIX = 'kapeta_port-';
|
11
13
|
|
12
14
|
const NANO_SECOND = 1000000;
|
13
15
|
const HEALTH_CHECK_INTERVAL = 2000;
|
@@ -15,9 +17,9 @@ const HEALTH_CHECK_MAX = 20;
|
|
15
17
|
|
16
18
|
const promisifyStream = (stream) =>
|
17
19
|
new Promise((resolve, reject) => {
|
18
|
-
stream.on(
|
19
|
-
stream.on(
|
20
|
-
stream.on(
|
20
|
+
stream.on('data', (d) => console.log(d.toString()));
|
21
|
+
stream.on('end', resolve);
|
22
|
+
stream.on('error', reject);
|
21
23
|
});
|
22
24
|
|
23
25
|
class ContainerManager {
|
@@ -27,8 +29,41 @@ class ContainerManager {
|
|
27
29
|
this._mountDir = Path.join(storageService.getKapetaBasedir(), 'mounts');
|
28
30
|
mkdirp.sync(this._mountDir);
|
29
31
|
}
|
30
|
-
|
31
|
-
|
32
|
+
|
33
|
+
async initialize() {
|
34
|
+
// Use the value from cluster-service.yml if configured
|
35
|
+
const dockerConfig = ClusterConfiguration.getDockerConfig();
|
36
|
+
const connectOptions =
|
37
|
+
Object.keys(dockerConfig).length > 0
|
38
|
+
? [dockerConfig]
|
39
|
+
: [
|
40
|
+
// use defaults: DOCKER_HOST etc from env, if available
|
41
|
+
undefined,
|
42
|
+
// default linux
|
43
|
+
{ socketPath: '/var/run/docker.sock' },
|
44
|
+
// default macOS
|
45
|
+
{
|
46
|
+
socketPath: path.join(
|
47
|
+
os.homedir(),
|
48
|
+
'.docker/run/docker.sock'
|
49
|
+
),
|
50
|
+
},
|
51
|
+
// Default http
|
52
|
+
{ protocol: 'http', host: 'localhost', port: 2375 },
|
53
|
+
{ protocol: 'https', host: 'localhost', port: 2376 },
|
54
|
+
];
|
55
|
+
for (const opts of connectOptions) {
|
56
|
+
try {
|
57
|
+
const client = new Docker(opts);
|
58
|
+
await client.ping();
|
59
|
+
this._docker = client;
|
60
|
+
this._alive = true;
|
61
|
+
return;
|
62
|
+
} catch (err) {
|
63
|
+
// silently ignore bad configs
|
64
|
+
}
|
65
|
+
}
|
66
|
+
}
|
32
67
|
|
33
68
|
isAlive() {
|
34
69
|
return this._alive;
|
@@ -36,9 +71,14 @@ class ContainerManager {
|
|
36
71
|
|
37
72
|
getMountPoint(kind, mountName) {
|
38
73
|
const kindUri = parseKapetaUri(kind);
|
39
|
-
return Path.join(
|
74
|
+
return Path.join(
|
75
|
+
this._mountDir,
|
76
|
+
kindUri.handle,
|
77
|
+
kindUri.name,
|
78
|
+
mountName
|
79
|
+
);
|
40
80
|
}
|
41
|
-
|
81
|
+
|
42
82
|
createMounts(kind, mountOpts) {
|
43
83
|
const mounts = {};
|
44
84
|
|
@@ -50,42 +90,16 @@ class ContainerManager {
|
|
50
90
|
return mounts;
|
51
91
|
}
|
52
92
|
|
53
|
-
async initialize() {
|
54
|
-
// try
|
55
|
-
const connectOptions = [
|
56
|
-
// use defaults: DOCKER_HOST etc from env, if available
|
57
|
-
undefined,
|
58
|
-
// default linux
|
59
|
-
{socketPath: "/var/run/docker.sock"},
|
60
|
-
// default macOS
|
61
|
-
{socketPath: path.join(os.homedir(), ".docker/run/docker.sock")},
|
62
|
-
// Default http
|
63
|
-
{protocol: "http", host: "localhost", port: 2375},
|
64
|
-
{protocol: "https", host: "localhost", port: 2376},
|
65
|
-
];
|
66
|
-
for (const opts of connectOptions) {
|
67
|
-
try {
|
68
|
-
const client = new Docker(opts);
|
69
|
-
await client.ping();
|
70
|
-
this._docker = client;
|
71
|
-
this._alive = true;
|
72
|
-
return;
|
73
|
-
} catch (err) {
|
74
|
-
// silently ignore bad configs
|
75
|
-
}
|
76
|
-
}
|
77
|
-
throw new Error("Unable to connect to docker");
|
78
|
-
}
|
79
|
-
|
80
93
|
async ping() {
|
81
|
-
|
82
94
|
try {
|
83
95
|
const pingResult = await this.docker().ping();
|
84
96
|
if (pingResult !== 'OK') {
|
85
97
|
throw new Error(`Ping failed: ${pingResult}`);
|
86
98
|
}
|
87
99
|
} catch (e) {
|
88
|
-
throw new Error(
|
100
|
+
throw new Error(
|
101
|
+
`Docker not running. Please start the docker daemon before running this command. Error: ${e.message}`
|
102
|
+
);
|
89
103
|
}
|
90
104
|
}
|
91
105
|
docker() {
|
@@ -96,8 +110,8 @@ class ContainerManager {
|
|
96
110
|
}
|
97
111
|
|
98
112
|
async getContainerByName(containerName) {
|
99
|
-
const containers = await this.docker().container.list({all: true});
|
100
|
-
return containers.find(container => {
|
113
|
+
const containers = await this.docker().container.list({ all: true });
|
114
|
+
return containers.find((container) => {
|
101
115
|
return container.data.Names.indexOf(`/${containerName}`) > -1;
|
102
116
|
});
|
103
117
|
}
|
@@ -108,8 +122,8 @@ class ContainerManager {
|
|
108
122
|
tag = 'latest';
|
109
123
|
}
|
110
124
|
|
111
|
-
await this.docker()
|
112
|
-
.create(
|
125
|
+
await this.docker()
|
126
|
+
.image.create(
|
113
127
|
{},
|
114
128
|
{
|
115
129
|
fromImage: imageName,
|
@@ -118,25 +132,25 @@ class ContainerManager {
|
|
118
132
|
)
|
119
133
|
.then((stream) => promisifyStream(stream));
|
120
134
|
}
|
121
|
-
|
135
|
+
|
122
136
|
toDockerMounts(mounts) {
|
123
|
-
const Mounts = [];
|
137
|
+
const Mounts = [];
|
124
138
|
_.forEach(mounts, (Source, Target) => {
|
125
139
|
Mounts.push({
|
126
140
|
Target,
|
127
141
|
Source,
|
128
|
-
Type:
|
142
|
+
Type: 'bind',
|
129
143
|
ReadOnly: false,
|
130
|
-
Consistency:
|
144
|
+
Consistency: 'consistent',
|
131
145
|
});
|
132
146
|
});
|
133
|
-
|
147
|
+
|
134
148
|
return Mounts;
|
135
149
|
}
|
136
|
-
|
150
|
+
|
137
151
|
toDockerHealth(health) {
|
138
152
|
return {
|
139
|
-
Test: [
|
153
|
+
Test: ['CMD-SHELL', health.cmd],
|
140
154
|
Interval: health.interval
|
141
155
|
? health.interval * NANO_SECOND
|
142
156
|
: 5000 * NANO_SECOND,
|
@@ -155,25 +169,24 @@ class ContainerManager {
|
|
155
169
|
* @return {Promise<ContainerInfo>}
|
156
170
|
*/
|
157
171
|
async run(image, name, opts) {
|
158
|
-
|
159
172
|
const PortBindings = {};
|
160
173
|
const Env = [];
|
161
174
|
const Labels = {
|
162
|
-
kapeta:
|
175
|
+
kapeta: 'true',
|
163
176
|
};
|
164
177
|
|
165
|
-
console.log(
|
178
|
+
console.log('Pulling image: %s', image);
|
166
179
|
|
167
180
|
await this.pull(image);
|
168
181
|
|
169
|
-
console.log(
|
182
|
+
console.log('Image pulled: %s', image);
|
170
183
|
|
171
184
|
_.forEach(opts.ports, (portInfo, containerPort) => {
|
172
185
|
PortBindings['' + containerPort] = [
|
173
186
|
{
|
174
187
|
HostPort: '' + portInfo.hostPort,
|
175
|
-
HostIp: '127.0.0.1'
|
176
|
-
}
|
188
|
+
HostIp: '127.0.0.1',
|
189
|
+
},
|
177
190
|
];
|
178
191
|
|
179
192
|
Labels[LABEL_PORT_PREFIX + portInfo.hostPort] = portInfo.type;
|
@@ -182,7 +195,7 @@ class ContainerManager {
|
|
182
195
|
const Mounts = this.toDockerMounts(opts.mounts);
|
183
196
|
|
184
197
|
_.forEach(opts.env, (value, name) => {
|
185
|
-
Env.push(name +
|
198
|
+
Env.push(name + '=' + value);
|
186
199
|
});
|
187
200
|
|
188
201
|
let HealthCheck = undefined;
|
@@ -190,10 +203,9 @@ class ContainerManager {
|
|
190
203
|
if (opts.health) {
|
191
204
|
HealthCheck = this.toDockerHealth(opts.health);
|
192
205
|
|
193
|
-
console.log(
|
206
|
+
console.log('Adding health check', HealthCheck);
|
194
207
|
}
|
195
208
|
|
196
|
-
|
197
209
|
const dockerContainer = await this.startContainer({
|
198
210
|
name: name,
|
199
211
|
Image: image,
|
@@ -202,8 +214,8 @@ class ContainerManager {
|
|
202
214
|
HealthCheck,
|
203
215
|
HostConfig: {
|
204
216
|
PortBindings,
|
205
|
-
Mounts
|
206
|
-
}
|
217
|
+
Mounts,
|
218
|
+
},
|
207
219
|
});
|
208
220
|
|
209
221
|
if (opts.health) {
|
@@ -221,14 +233,15 @@ class ContainerManager {
|
|
221
233
|
return dockerContainer;
|
222
234
|
}
|
223
235
|
|
224
|
-
|
225
236
|
async waitForHealthy(container, attempt) {
|
226
237
|
if (!attempt) {
|
227
238
|
attempt = 0;
|
228
239
|
}
|
229
240
|
|
230
241
|
if (attempt >= HEALTH_CHECK_MAX) {
|
231
|
-
throw new Error(
|
242
|
+
throw new Error(
|
243
|
+
'Container did not become healthy within the timeout'
|
244
|
+
);
|
232
245
|
}
|
233
246
|
|
234
247
|
if (await this._isHealthy(container)) {
|
@@ -249,7 +262,7 @@ class ContainerManager {
|
|
249
262
|
|
250
263
|
async _isHealthy(container) {
|
251
264
|
const info = await container.status();
|
252
|
-
return info?.data?.State?.Health?.Status ===
|
265
|
+
return info?.data?.State?.Health?.Status === 'healthy';
|
253
266
|
}
|
254
267
|
|
255
268
|
/**
|
@@ -265,7 +278,7 @@ class ContainerManager {
|
|
265
278
|
await dockerContainer.status();
|
266
279
|
} catch (err) {
|
267
280
|
//Ignore
|
268
|
-
console.log(
|
281
|
+
console.log('Container not available - creating it: %s', name);
|
269
282
|
dockerContainer = null;
|
270
283
|
}
|
271
284
|
|
@@ -314,7 +327,7 @@ class ContainerInfo {
|
|
314
327
|
}
|
315
328
|
|
316
329
|
async remove(opts) {
|
317
|
-
await this._container.delete({force: !!opts.force});
|
330
|
+
await this._container.delete({ force: !!opts.force });
|
318
331
|
}
|
319
332
|
|
320
333
|
async getPort(type) {
|