@kapeta/local-cluster-service 0.2.1 → 0.4.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/package.json +6 -2
- package/src/config/routes.js +1 -3
- package/src/configManager.js +1 -1
- package/src/containerManager.js +117 -73
- package/src/instanceManager.js +1 -0
- package/src/utils/BlockInstanceRunner.js +153 -63
package/CHANGELOG.md
CHANGED
@@ -1,3 +1,17 @@
|
|
1
|
+
# [0.4.0](https://github.com/kapetacom/local-cluster-service/compare/v0.3.0...v0.4.0) (2023-06-01)
|
2
|
+
|
3
|
+
|
4
|
+
### Features
|
5
|
+
|
6
|
+
* Change to always run local code in docker container ([#25](https://github.com/kapetacom/local-cluster-service/issues/25)) ([6e4021e](https://github.com/kapetacom/local-cluster-service/commit/6e4021e67968467555f1043f2972fc7a877aa3b7))
|
7
|
+
|
8
|
+
# [0.3.0](https://github.com/kapetacom/local-cluster-service/compare/v0.2.1...v0.3.0) (2023-05-08)
|
9
|
+
|
10
|
+
|
11
|
+
### Features
|
12
|
+
|
13
|
+
* load docker config from clusterConfig if available [KAP-609] ([1ced2c1](https://github.com/kapetacom/local-cluster-service/commit/1ced2c1ed2f72bf3331e558ee2c685385c89ab1f))
|
14
|
+
|
1
15
|
## [0.2.1](https://github.com/kapetacom/local-cluster-service/compare/v0.2.0...v0.2.1) (2023-05-07)
|
2
16
|
|
3
17
|
|
package/package.json
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
{
|
2
2
|
"name": "@kapeta/local-cluster-service",
|
3
|
-
"version": "0.
|
3
|
+
"version": "0.4.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/config/routes.js
CHANGED
package/src/configManager.js
CHANGED
@@ -92,7 +92,7 @@ class ConfigManager {
|
|
92
92
|
throw new Error(`No uses of block "${blockRef}" was found in plan: "${systemId}"`)
|
93
93
|
}
|
94
94
|
|
95
|
-
throw new Error(`No uses of block "${blockRef}" was found any known plan`);
|
95
|
+
throw new Error(`No uses of block "${blockRef}" was found in any known plan`);
|
96
96
|
}
|
97
97
|
|
98
98
|
if (matchingIdentities.length > 1) {
|
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) {
|
@@ -215,12 +227,36 @@ class ContainerManager {
|
|
215
227
|
|
216
228
|
async startContainer(opts) {
|
217
229
|
const dockerContainer = await this.docker().container.create(opts);
|
218
|
-
|
219
230
|
await dockerContainer.start();
|
220
|
-
|
221
231
|
return dockerContainer;
|
222
232
|
}
|
223
233
|
|
234
|
+
async waitForReady(container, attempt) {
|
235
|
+
if (!attempt) {
|
236
|
+
attempt = 0;
|
237
|
+
}
|
238
|
+
|
239
|
+
if (attempt >= HEALTH_CHECK_MAX) {
|
240
|
+
throw new Error(
|
241
|
+
'Container did not become ready within the timeout'
|
242
|
+
);
|
243
|
+
}
|
244
|
+
|
245
|
+
if (await this._isReady(container)) {
|
246
|
+
return;
|
247
|
+
}
|
248
|
+
|
249
|
+
return new Promise((resolve, reject) => {
|
250
|
+
setTimeout(async () => {
|
251
|
+
try {
|
252
|
+
await this.waitForReady(container, attempt + 1);
|
253
|
+
resolve();
|
254
|
+
} catch (err) {
|
255
|
+
reject(err);
|
256
|
+
}
|
257
|
+
}, HEALTH_CHECK_INTERVAL);
|
258
|
+
});
|
259
|
+
}
|
224
260
|
|
225
261
|
async waitForHealthy(container, attempt) {
|
226
262
|
if (!attempt) {
|
@@ -228,7 +264,9 @@ class ContainerManager {
|
|
228
264
|
}
|
229
265
|
|
230
266
|
if (attempt >= HEALTH_CHECK_MAX) {
|
231
|
-
throw new Error(
|
267
|
+
throw new Error(
|
268
|
+
'Container did not become healthy within the timeout'
|
269
|
+
);
|
232
270
|
}
|
233
271
|
|
234
272
|
if (await this._isHealthy(container)) {
|
@@ -247,9 +285,16 @@ class ContainerManager {
|
|
247
285
|
});
|
248
286
|
}
|
249
287
|
|
288
|
+
async _isReady(container) {
|
289
|
+
const info = await container.status();
|
290
|
+
if (info?.data?.State?.Status === 'exited') {
|
291
|
+
throw new Error('Container exited unexpectedly');
|
292
|
+
}
|
293
|
+
return info?.data?.State?.Running;
|
294
|
+
}
|
250
295
|
async _isHealthy(container) {
|
251
296
|
const info = await container.status();
|
252
|
-
return info?.data?.State?.Health?.Status ===
|
297
|
+
return info?.data?.State?.Health?.Status === 'healthy';
|
253
298
|
}
|
254
299
|
|
255
300
|
/**
|
@@ -265,7 +310,6 @@ class ContainerManager {
|
|
265
310
|
await dockerContainer.status();
|
266
311
|
} catch (err) {
|
267
312
|
//Ignore
|
268
|
-
console.log("Container not available - creating it: %s", name);
|
269
313
|
dockerContainer = null;
|
270
314
|
}
|
271
315
|
|
@@ -314,7 +358,7 @@ class ContainerInfo {
|
|
314
358
|
}
|
315
359
|
|
316
360
|
async remove(opts) {
|
317
|
-
await this._container.delete({force: !!opts.force});
|
361
|
+
await this._container.delete({ force: !!opts.force });
|
318
362
|
}
|
319
363
|
|
320
364
|
async getPort(type) {
|
package/src/instanceManager.js
CHANGED
@@ -27,6 +27,19 @@ const DOCKER_ENV_VARS = [
|
|
27
27
|
]
|
28
28
|
|
29
29
|
|
30
|
+
function getProvider(uri) {
|
31
|
+
return ClusterConfig.getProviderDefinitions().find(provider => {
|
32
|
+
const ref = `${provider.definition.metadata.name}:${provider.version}`
|
33
|
+
return parseKapetaUri(ref).id === uri.id;
|
34
|
+
});
|
35
|
+
}
|
36
|
+
|
37
|
+
function getProviderPorts(assetVersion) {
|
38
|
+
return assetVersion.definition?.spec?.providers.map(provider => {
|
39
|
+
return provider.spec?.port?.type
|
40
|
+
}).filter(t => !!t) ?? [];
|
41
|
+
}
|
42
|
+
|
30
43
|
class BlockInstanceRunner {
|
31
44
|
/**
|
32
45
|
* @param {string} [planReference]
|
@@ -66,7 +79,7 @@ class BlockInstanceRunner {
|
|
66
79
|
* @private
|
67
80
|
*/
|
68
81
|
async _execute(blockInstance) {
|
69
|
-
const env =
|
82
|
+
const env = {};
|
70
83
|
|
71
84
|
if (this._systemId) {
|
72
85
|
env[KAPETA_SYSTEM_ID] = this._systemId;
|
@@ -86,23 +99,20 @@ class BlockInstanceRunner {
|
|
86
99
|
blockUri.version = 'local';
|
87
100
|
}
|
88
101
|
|
89
|
-
const
|
102
|
+
const assetVersion = ClusterConfig.getDefinitions().find(definitions => {
|
90
103
|
const ref = `${definitions.definition.metadata.name}:${definitions.version}`
|
91
104
|
return parseKapetaUri(ref).id === blockUri.id;
|
92
105
|
});
|
93
106
|
|
94
|
-
if (!
|
107
|
+
if (!assetVersion) {
|
95
108
|
throw new Error(`Block definition not found: ${blockUri.id}`);
|
96
109
|
}
|
97
110
|
|
98
|
-
const kindUri = parseKapetaUri(
|
111
|
+
const kindUri = parseKapetaUri(assetVersion.definition.kind);
|
99
112
|
|
100
|
-
const
|
101
|
-
const ref = `${provider.definition.metadata.name}:${provider.version}`
|
102
|
-
return parseKapetaUri(ref).id === kindUri.id;
|
103
|
-
});
|
113
|
+
const providerVersion = getProvider(kindUri);
|
104
114
|
|
105
|
-
if (!
|
115
|
+
if (!providerVersion) {
|
106
116
|
throw new Error(`Kind not found: ${kindUri.id}`);
|
107
117
|
}
|
108
118
|
|
@@ -111,15 +121,14 @@ class BlockInstanceRunner {
|
|
111
121
|
*/
|
112
122
|
let processDetails;
|
113
123
|
|
114
|
-
if (
|
115
|
-
processDetails = await this._startOperatorProcess(blockInstance, blockUri,
|
124
|
+
if (providerVersion.definition.kind === KIND_BLOCK_TYPE_OPERATOR) {
|
125
|
+
processDetails = await this._startOperatorProcess(blockInstance, blockUri, providerVersion, env);
|
116
126
|
} else {
|
117
127
|
//We need a port type to know how to connect to the block consistently
|
118
|
-
const portTypes =
|
119
|
-
|
120
|
-
}).filter(t => !!t) ?? [];
|
128
|
+
const portTypes = getProviderPorts(assetVersion);
|
129
|
+
|
121
130
|
if (blockUri.version === 'local') {
|
122
|
-
processDetails = await this._startLocalProcess(blockInstance, blockUri, env);
|
131
|
+
processDetails = await this._startLocalProcess(blockInstance, blockUri, env, assetVersion);
|
123
132
|
} else {
|
124
133
|
processDetails = await this._startDockerProcess(blockInstance, blockUri, env);
|
125
134
|
}
|
@@ -141,10 +150,11 @@ class BlockInstanceRunner {
|
|
141
150
|
* @param {BlockInstanceInfo} blockInstance
|
142
151
|
* @param {BlockInfo} blockInfo
|
143
152
|
* @param {EnvironmentVariables} env
|
153
|
+
* @param assetVersion
|
144
154
|
* @return {ProcessDetails}
|
145
155
|
* @private
|
146
156
|
*/
|
147
|
-
_startLocalProcess(blockInstance, blockInfo, env) {
|
157
|
+
async _startLocalProcess(blockInstance, blockInfo, env, assetVersion) {
|
148
158
|
const baseDir = ClusterConfig.getRepositoryAssetPath(
|
149
159
|
blockInfo.handle,
|
150
160
|
blockInfo.name,
|
@@ -158,61 +168,125 @@ class BlockInstanceRunner {
|
|
158
168
|
);
|
159
169
|
}
|
160
170
|
|
161
|
-
const
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
)
|
171
|
+
const kindUri = parseKapetaUri(assetVersion.definition.spec.target.kind);
|
172
|
+
|
173
|
+
const targetVersion = getProvider(kindUri);
|
174
|
+
|
175
|
+
if (!targetVersion) {
|
176
|
+
throw new Error(`Target not found: ${kindUri.id}`);
|
167
177
|
}
|
168
178
|
|
179
|
+
const localContainer = targetVersion.definition.spec.local;
|
180
|
+
|
181
|
+
if (!localContainer) {
|
182
|
+
throw new Error(`Missing local container information from target: ${kindUri.id}`);
|
183
|
+
}
|
184
|
+
|
185
|
+
const dockerImage = localContainer.image;
|
186
|
+
if (!dockerImage) {
|
187
|
+
throw new Error(`Missing docker image information: ${JSON.stringify(localContainer)}`);
|
188
|
+
}
|
189
|
+
|
190
|
+
const containerName = `kapeta-block-instance-${blockInstance.id}`;
|
169
191
|
const logs = new LogData();
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
detached: true,
|
174
|
-
stdio: [
|
175
|
-
'pipe', 'pipe', 'pipe'
|
176
|
-
]
|
177
|
-
});
|
192
|
+
logs.addLog(`Starting block ${blockInstance.ref}`);
|
193
|
+
let container = await containerManager.getContainerByName(containerName);
|
194
|
+
console.log('Starting dev container', containerName);
|
178
195
|
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
pid: childProcess.pid,
|
188
|
-
output: outputEvents,
|
189
|
-
stderr: childProcess.stderr,
|
190
|
-
logs: () => {
|
191
|
-
return logs.getLogs();
|
192
|
-
},
|
193
|
-
stop: () => {
|
194
|
-
childProcess.kill('SIGTERM');
|
196
|
+
if (container) {
|
197
|
+
console.log(`Container already exists. Deleting...`);
|
198
|
+
try {
|
199
|
+
await container.delete({
|
200
|
+
force: true
|
201
|
+
})
|
202
|
+
} catch (e) {
|
203
|
+
throw new Error('Failed to delete existing container: ' + e.message);
|
195
204
|
}
|
196
|
-
|
205
|
+
container = null;
|
206
|
+
}
|
197
207
|
|
198
|
-
|
199
|
-
|
200
|
-
|
201
|
-
|
208
|
+
logs.addLog(`Creating new container for block: ${containerName}`);
|
209
|
+
console.log('Creating new dev container', containerName, dockerImage);
|
210
|
+
await containerManager.pull(dockerImage);
|
211
|
+
|
212
|
+
const startCmd = localContainer.handlers?.onCreate ? localContainer.handlers.onCreate : '';
|
213
|
+
const dockerOpts = localContainer.options ?? {};
|
214
|
+
const homeDir = localContainer.homeDir ? localContainer.homeDir : '/root';
|
215
|
+
const workingDir = localContainer.workingDir ? localContainer.workingDir : '/workspace';
|
216
|
+
|
217
|
+
const ExposedPorts = {};
|
218
|
+
const addonEnv = {};
|
219
|
+
const PortBindings = {};
|
220
|
+
|
221
|
+
const portTypes = getProviderPorts(assetVersion);
|
222
|
+
let port = 80;
|
223
|
+
const promises = portTypes
|
224
|
+
.map(async (portType) => {
|
225
|
+
const publicPort = await serviceManager.ensureServicePort(this._systemId, blockInstance.id, portType);
|
226
|
+
const thisPort = port++; //TODO: Not sure how we should handle multiple ports or non-HTTP ports
|
227
|
+
const dockerPort = `${thisPort}/tcp`;
|
228
|
+
ExposedPorts[dockerPort] = {};
|
229
|
+
addonEnv[`KAPETA_LOCAL_SERVER_PORT_${portType.toUpperCase()}`] = thisPort;
|
230
|
+
|
231
|
+
PortBindings[dockerPort] = [
|
232
|
+
{
|
233
|
+
HostIp: "127.0.0.1", //No public
|
234
|
+
HostPort: `${publicPort}`
|
235
|
+
}
|
236
|
+
];
|
237
|
+
});
|
202
238
|
|
203
|
-
|
204
|
-
logs.addLog(data.toString());
|
205
|
-
outputEvents.emit('data', data);
|
206
|
-
});
|
239
|
+
await Promise.all(promises);
|
207
240
|
|
208
|
-
|
209
|
-
|
210
|
-
|
241
|
+
let HealthCheck = undefined;
|
242
|
+
if (localContainer.healthcheck) {
|
243
|
+
HealthCheck = containerManager.toDockerHealth({cmd: localContainer.healthcheck});
|
244
|
+
}
|
245
|
+
|
246
|
+
container = await containerManager.startContainer({
|
247
|
+
Image: dockerImage,
|
248
|
+
name: containerName,
|
249
|
+
WorkingDir: workingDir,
|
250
|
+
Labels: {
|
251
|
+
'instance': blockInstance.id
|
252
|
+
},
|
253
|
+
HealthCheck,
|
254
|
+
ExposedPorts,
|
255
|
+
Cmd: startCmd ? startCmd.split(/\s+/g) : [],
|
256
|
+
Env: [
|
257
|
+
...DOCKER_ENV_VARS,
|
258
|
+
...Object.entries({
|
259
|
+
...env,
|
260
|
+
...addonEnv
|
261
|
+
}).map(([key, value]) => `${key}=${value}`)
|
262
|
+
],
|
263
|
+
HostConfig: {
|
264
|
+
Binds: [
|
265
|
+
`${ClusterConfig.getKapetaBasedir()}:${homeDir}/.kapeta`,
|
266
|
+
`${baseDir}:${workingDir}` //We mount
|
267
|
+
],
|
268
|
+
PortBindings
|
269
|
+
},
|
270
|
+
...dockerOpts
|
211
271
|
});
|
212
272
|
|
213
|
-
|
273
|
+
if (HealthCheck) {
|
274
|
+
await containerManager.waitForHealthy(container);
|
275
|
+
} else {
|
276
|
+
await containerManager.waitForReady(container);
|
277
|
+
}
|
278
|
+
|
279
|
+
return this._handleContainer(container, logs);
|
214
280
|
}
|
215
281
|
|
282
|
+
/**
|
283
|
+
*
|
284
|
+
* @param container
|
285
|
+
* @param logs
|
286
|
+
* @param deleteOnExit
|
287
|
+
* @return {Promise<ProcessDetails>}
|
288
|
+
* @private
|
289
|
+
*/
|
216
290
|
async _handleContainer(container, logs , deleteOnExit = false) {
|
217
291
|
const logStream = await container.logs({
|
218
292
|
follow: true,
|
@@ -315,22 +389,36 @@ class BlockInstanceRunner {
|
|
315
389
|
container = await containerManager.startContainer({
|
316
390
|
Image: dockerImage,
|
317
391
|
name: containerName,
|
318
|
-
Binds: [
|
319
|
-
`${ClusterConfig.getKapetaBasedir()}:${ClusterConfig.getKapetaBasedir()}`
|
320
|
-
],
|
321
392
|
Labels: {
|
322
393
|
'instance': blockInstance.id
|
323
394
|
},
|
324
395
|
Env: [
|
325
396
|
...DOCKER_ENV_VARS,
|
326
397
|
...Object.entries(env).map(([key, value]) => `${key}=${value}`)
|
327
|
-
]
|
398
|
+
],
|
399
|
+
HostConfig: {
|
400
|
+
Binds: [
|
401
|
+
`${ClusterConfig.getKapetaBasedir()}:${ClusterConfig.getKapetaBasedir()}`
|
402
|
+
],
|
403
|
+
|
404
|
+
}
|
328
405
|
});
|
406
|
+
|
407
|
+
await containerManager.waitForReady(container);
|
329
408
|
}
|
330
409
|
|
331
410
|
return this._handleContainer(container, logs);
|
332
411
|
}
|
333
412
|
|
413
|
+
/**
|
414
|
+
*
|
415
|
+
* @param blockInstance
|
416
|
+
* @param blockUri
|
417
|
+
* @param providerDefinition
|
418
|
+
* @param {{[key:string]:string}} env
|
419
|
+
* @return {Promise<ProcessDetails>}
|
420
|
+
* @private
|
421
|
+
*/
|
334
422
|
async _startOperatorProcess(blockInstance, blockUri, providerDefinition, env) {
|
335
423
|
const {assetFile} = ClusterConfig.getRepositoryAssetInfoPath(
|
336
424
|
blockUri.handle,
|
@@ -453,6 +541,8 @@ class BlockInstanceRunner {
|
|
453
541
|
|
454
542
|
if (HealthCheck) {
|
455
543
|
await containerManager.waitForHealthy(container);
|
544
|
+
} else {
|
545
|
+
await containerManager.waitForReady(container);
|
456
546
|
}
|
457
547
|
}
|
458
548
|
|