@kapeta/local-cluster-service 0.10.0 → 0.10.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 CHANGED
@@ -1,3 +1,10 @@
1
+ ## [0.10.1](https://github.com/kapetacom/local-cluster-service/compare/v0.10.0...v0.10.1) (2023-07-27)
2
+
3
+
4
+ ### Bug Fixes
5
+
6
+ * Include port bindings for non-local containers ([#51](https://github.com/kapetacom/local-cluster-service/issues/51)) ([64fd440](https://github.com/kapetacom/local-cluster-service/commit/64fd4409ea9e2dda8e2438d0ec85a8f5a2092b1e))
7
+
1
8
  # [0.10.0](https://github.com/kapetacom/local-cluster-service/compare/v0.9.1...v0.10.0) (2023-07-26)
2
9
 
3
10
 
@@ -14,8 +14,6 @@ export declare class BlockInstanceRunner {
14
14
  * Starts local process
15
15
  */
16
16
  private _startLocalProcess;
17
- private ensureContainer;
18
- private _handleContainer;
19
17
  private _startDockerProcess;
20
18
  /**
21
19
  *
@@ -27,4 +25,7 @@ export declare class BlockInstanceRunner {
27
25
  * @private
28
26
  */
29
27
  _startOperatorProcess(blockInstance: BlockProcessParams, blockUri: KapetaURI, providerDefinition: DefinitionInfo, env: StringMap): Promise<ProcessInfo>;
28
+ private getDockerPortBindings;
29
+ private ensureContainer;
30
+ private _handleContainer;
30
31
  }
@@ -100,7 +100,7 @@ class BlockInstanceRunner {
100
100
  processInfo = await this._startLocalProcess(blockInstance, blockUri, env, assetVersion);
101
101
  }
102
102
  else {
103
- processInfo = await this._startDockerProcess(blockInstance, blockUri, env);
103
+ processInfo = await this._startDockerProcess(blockInstance, blockUri, env, assetVersion);
104
104
  }
105
105
  if (portTypes.length > 0) {
106
106
  processInfo.portType = portTypes[0];
@@ -138,26 +138,7 @@ class BlockInstanceRunner {
138
138
  const dockerOpts = localContainer.options ?? {};
139
139
  const homeDir = localContainer.userHome ? localContainer.userHome : '/root';
140
140
  const workingDir = localContainer.workingDir ? localContainer.workingDir : '/workspace';
141
- const bindHost = (0, utils_1.getBindHost)();
142
- const ExposedPorts = {};
143
- const addonEnv = {};
144
- const PortBindings = {};
145
- const portTypes = getProviderPorts(assetVersion);
146
- let port = 80;
147
- const promises = portTypes.map(async (portType) => {
148
- const publicPort = await serviceManager_1.serviceManager.ensureServicePort(this._systemId, blockInstance.id, portType);
149
- const thisPort = port++; //TODO: Not sure how we should handle multiple ports or non-HTTP ports
150
- const dockerPort = `${thisPort}/tcp`;
151
- ExposedPorts[dockerPort] = {};
152
- addonEnv[`KAPETA_LOCAL_SERVER_PORT_${portType.toUpperCase()}`] = '' + thisPort;
153
- PortBindings[dockerPort] = [
154
- {
155
- HostIp: bindHost,
156
- HostPort: `${publicPort}`,
157
- },
158
- ];
159
- });
160
- await Promise.all(promises);
141
+ const { PortBindings, ExposedPorts, addonEnv } = await this.getDockerPortBindings(blockInstance, assetVersion);
161
142
  let HealthCheck = undefined;
162
143
  if (localContainer.healthcheck) {
163
144
  HealthCheck = containerManager_1.containerManager.toDockerHealth({ cmd: localContainer.healthcheck });
@@ -190,73 +171,7 @@ class BlockInstanceRunner {
190
171
  ...dockerOpts,
191
172
  });
192
173
  }
193
- async ensureContainer(opts) {
194
- const logs = new LogData_1.LogData();
195
- const container = await containerManager_1.containerManager.ensureContainer(opts);
196
- try {
197
- if (opts.HealthCheck) {
198
- await containerManager_1.containerManager.waitForHealthy(container);
199
- }
200
- else {
201
- await containerManager_1.containerManager.waitForReady(container);
202
- }
203
- }
204
- catch (e) {
205
- logs.addLog(e.message, 'ERROR');
206
- }
207
- return this._handleContainer(container, logs);
208
- }
209
- async _handleContainer(container, logs, deleteOnExit = false) {
210
- let localContainer = container;
211
- const logStream = (await container.logs({
212
- follow: true,
213
- stdout: true,
214
- stderr: true,
215
- tail: LogData_1.LogData.MAX_LINES,
216
- }));
217
- const outputEvents = new events_1.default();
218
- logStream.on('data', (data) => {
219
- logs.addLog(data.toString());
220
- outputEvents.emit('data', data);
221
- });
222
- logStream.on('error', (data) => {
223
- logs.addLog(data.toString());
224
- outputEvents.emit('data', data);
225
- });
226
- logStream.on('close', async () => {
227
- const status = await container.status();
228
- const data = status.data;
229
- if (deleteOnExit) {
230
- try {
231
- await containerManager_1.containerManager.remove(container);
232
- }
233
- catch (e) { }
234
- }
235
- outputEvents.emit('exit', data?.State?.ExitCode ?? 0);
236
- });
237
- return {
238
- type: types_1.InstanceType.DOCKER,
239
- pid: container.id,
240
- output: outputEvents,
241
- stop: async () => {
242
- if (!localContainer) {
243
- return;
244
- }
245
- try {
246
- await localContainer.stop();
247
- if (deleteOnExit) {
248
- await containerManager_1.containerManager.remove(localContainer);
249
- }
250
- }
251
- catch (e) { }
252
- localContainer = null;
253
- },
254
- logs: () => {
255
- return logs.getLogs();
256
- },
257
- };
258
- }
259
- async _startDockerProcess(blockInstance, blockInfo, env) {
174
+ async _startDockerProcess(blockInstance, blockInfo, env, assetVersion) {
260
175
  const { versionFile } = local_cluster_config_1.default.getRepositoryAssetInfoPath(blockInfo.handle, blockInfo.name, blockInfo.version);
261
176
  const versionYml = versionFile;
262
177
  if (!node_fs_1.default.existsSync(versionYml)) {
@@ -270,23 +185,28 @@ class BlockInstanceRunner {
270
185
  if (!dockerImage) {
271
186
  throw new Error(`Missing docker image information: ${JSON.stringify(versionInfo?.artifact?.details)}`);
272
187
  }
188
+ const { PortBindings, ExposedPorts, addonEnv } = await this.getDockerPortBindings(blockInstance, assetVersion);
273
189
  const containerName = (0, utils_1.getBlockInstanceContainerName)(blockInstance.id);
274
- const logs = new LogData_1.LogData();
275
190
  // For windows we need to default to root
276
191
  const innerHome = process.platform === 'win32' ? '/root/.kapeta' : local_cluster_config_1.default.getKapetaBasedir();
277
192
  return this.ensureContainer({
278
193
  Image: dockerImage,
279
194
  name: containerName,
195
+ ExposedPorts,
280
196
  Labels: {
281
197
  instance: blockInstance.id,
282
198
  },
283
199
  Env: [
284
200
  ...DOCKER_ENV_VARS,
285
201
  `KAPETA_LOCAL_CLUSTER_PORT=${clusterService_1.clusterService.getClusterServicePort()}`,
286
- ...Object.entries(env).map(([key, value]) => `${key}=${value}`),
202
+ ...Object.entries({
203
+ ...env,
204
+ ...addonEnv
205
+ }).map(([key, value]) => `${key}=${value}`),
287
206
  ],
288
207
  HostConfig: {
289
208
  Binds: [`${(0, containerManager_1.toLocalBindVolume)(local_cluster_config_1.default.getKapetaBasedir())}:${innerHome}`],
209
+ PortBindings,
290
210
  },
291
211
  });
292
212
  }
@@ -379,5 +299,94 @@ class BlockInstanceRunner {
379
299
  }
380
300
  return out;
381
301
  }
302
+ async getDockerPortBindings(blockInstance, assetVersion) {
303
+ const bindHost = (0, utils_1.getBindHost)();
304
+ const ExposedPorts = {};
305
+ const addonEnv = {};
306
+ const PortBindings = {};
307
+ const portTypes = getProviderPorts(assetVersion);
308
+ let port = 80;
309
+ const promises = portTypes.map(async (portType) => {
310
+ const publicPort = await serviceManager_1.serviceManager.ensureServicePort(this._systemId, blockInstance.id, portType);
311
+ const thisPort = port++; //TODO: Not sure how we should handle multiple ports or non-HTTP ports
312
+ const dockerPort = `${thisPort}/tcp`;
313
+ ExposedPorts[dockerPort] = {};
314
+ addonEnv[`KAPETA_LOCAL_SERVER_PORT_${portType.toUpperCase()}`] = '' + thisPort;
315
+ PortBindings[dockerPort] = [
316
+ {
317
+ HostIp: bindHost,
318
+ HostPort: `${publicPort}`,
319
+ },
320
+ ];
321
+ });
322
+ await Promise.all(promises);
323
+ return { PortBindings, ExposedPorts, addonEnv };
324
+ }
325
+ async ensureContainer(opts) {
326
+ const logs = new LogData_1.LogData();
327
+ const container = await containerManager_1.containerManager.ensureContainer(opts);
328
+ try {
329
+ if (opts.HealthCheck) {
330
+ await containerManager_1.containerManager.waitForHealthy(container);
331
+ }
332
+ else {
333
+ await containerManager_1.containerManager.waitForReady(container);
334
+ }
335
+ }
336
+ catch (e) {
337
+ logs.addLog(e.message, 'ERROR');
338
+ }
339
+ return this._handleContainer(container, logs);
340
+ }
341
+ async _handleContainer(container, logs, deleteOnExit = false) {
342
+ let localContainer = container;
343
+ const logStream = (await container.logs({
344
+ follow: true,
345
+ stdout: true,
346
+ stderr: true,
347
+ tail: LogData_1.LogData.MAX_LINES,
348
+ }));
349
+ const outputEvents = new events_1.default();
350
+ logStream.on('data', (data) => {
351
+ logs.addLog(data.toString());
352
+ outputEvents.emit('data', data);
353
+ });
354
+ logStream.on('error', (data) => {
355
+ logs.addLog(data.toString());
356
+ outputEvents.emit('data', data);
357
+ });
358
+ logStream.on('close', async () => {
359
+ const status = await container.status();
360
+ const data = status.data;
361
+ if (deleteOnExit) {
362
+ try {
363
+ await containerManager_1.containerManager.remove(container);
364
+ }
365
+ catch (e) { }
366
+ }
367
+ outputEvents.emit('exit', data?.State?.ExitCode ?? 0);
368
+ });
369
+ return {
370
+ type: types_1.InstanceType.DOCKER,
371
+ pid: container.id,
372
+ output: outputEvents,
373
+ stop: async () => {
374
+ if (!localContainer) {
375
+ return;
376
+ }
377
+ try {
378
+ await localContainer.stop();
379
+ if (deleteOnExit) {
380
+ await containerManager_1.containerManager.remove(localContainer);
381
+ }
382
+ }
383
+ catch (e) { }
384
+ localContainer = null;
385
+ },
386
+ logs: () => {
387
+ return logs.getLogs();
388
+ },
389
+ };
390
+ }
382
391
  }
383
392
  exports.BlockInstanceRunner = BlockInstanceRunner;
@@ -14,8 +14,6 @@ export declare class BlockInstanceRunner {
14
14
  * Starts local process
15
15
  */
16
16
  private _startLocalProcess;
17
- private ensureContainer;
18
- private _handleContainer;
19
17
  private _startDockerProcess;
20
18
  /**
21
19
  *
@@ -27,4 +25,7 @@ export declare class BlockInstanceRunner {
27
25
  * @private
28
26
  */
29
27
  _startOperatorProcess(blockInstance: BlockProcessParams, blockUri: KapetaURI, providerDefinition: DefinitionInfo, env: StringMap): Promise<ProcessInfo>;
28
+ private getDockerPortBindings;
29
+ private ensureContainer;
30
+ private _handleContainer;
30
31
  }
@@ -94,7 +94,7 @@ export class BlockInstanceRunner {
94
94
  processInfo = await this._startLocalProcess(blockInstance, blockUri, env, assetVersion);
95
95
  }
96
96
  else {
97
- processInfo = await this._startDockerProcess(blockInstance, blockUri, env);
97
+ processInfo = await this._startDockerProcess(blockInstance, blockUri, env, assetVersion);
98
98
  }
99
99
  if (portTypes.length > 0) {
100
100
  processInfo.portType = portTypes[0];
@@ -132,26 +132,7 @@ export class BlockInstanceRunner {
132
132
  const dockerOpts = localContainer.options ?? {};
133
133
  const homeDir = localContainer.userHome ? localContainer.userHome : '/root';
134
134
  const workingDir = localContainer.workingDir ? localContainer.workingDir : '/workspace';
135
- const bindHost = getBindHost();
136
- const ExposedPorts = {};
137
- const addonEnv = {};
138
- const PortBindings = {};
139
- const portTypes = getProviderPorts(assetVersion);
140
- let port = 80;
141
- const promises = portTypes.map(async (portType) => {
142
- const publicPort = await serviceManager.ensureServicePort(this._systemId, blockInstance.id, portType);
143
- const thisPort = port++; //TODO: Not sure how we should handle multiple ports or non-HTTP ports
144
- const dockerPort = `${thisPort}/tcp`;
145
- ExposedPorts[dockerPort] = {};
146
- addonEnv[`KAPETA_LOCAL_SERVER_PORT_${portType.toUpperCase()}`] = '' + thisPort;
147
- PortBindings[dockerPort] = [
148
- {
149
- HostIp: bindHost,
150
- HostPort: `${publicPort}`,
151
- },
152
- ];
153
- });
154
- await Promise.all(promises);
135
+ const { PortBindings, ExposedPorts, addonEnv } = await this.getDockerPortBindings(blockInstance, assetVersion);
155
136
  let HealthCheck = undefined;
156
137
  if (localContainer.healthcheck) {
157
138
  HealthCheck = containerManager.toDockerHealth({ cmd: localContainer.healthcheck });
@@ -184,73 +165,7 @@ export class BlockInstanceRunner {
184
165
  ...dockerOpts,
185
166
  });
186
167
  }
187
- async ensureContainer(opts) {
188
- const logs = new LogData();
189
- const container = await containerManager.ensureContainer(opts);
190
- try {
191
- if (opts.HealthCheck) {
192
- await containerManager.waitForHealthy(container);
193
- }
194
- else {
195
- await containerManager.waitForReady(container);
196
- }
197
- }
198
- catch (e) {
199
- logs.addLog(e.message, 'ERROR');
200
- }
201
- return this._handleContainer(container, logs);
202
- }
203
- async _handleContainer(container, logs, deleteOnExit = false) {
204
- let localContainer = container;
205
- const logStream = (await container.logs({
206
- follow: true,
207
- stdout: true,
208
- stderr: true,
209
- tail: LogData.MAX_LINES,
210
- }));
211
- const outputEvents = new EventEmitter();
212
- logStream.on('data', (data) => {
213
- logs.addLog(data.toString());
214
- outputEvents.emit('data', data);
215
- });
216
- logStream.on('error', (data) => {
217
- logs.addLog(data.toString());
218
- outputEvents.emit('data', data);
219
- });
220
- logStream.on('close', async () => {
221
- const status = await container.status();
222
- const data = status.data;
223
- if (deleteOnExit) {
224
- try {
225
- await containerManager.remove(container);
226
- }
227
- catch (e) { }
228
- }
229
- outputEvents.emit('exit', data?.State?.ExitCode ?? 0);
230
- });
231
- return {
232
- type: InstanceType.DOCKER,
233
- pid: container.id,
234
- output: outputEvents,
235
- stop: async () => {
236
- if (!localContainer) {
237
- return;
238
- }
239
- try {
240
- await localContainer.stop();
241
- if (deleteOnExit) {
242
- await containerManager.remove(localContainer);
243
- }
244
- }
245
- catch (e) { }
246
- localContainer = null;
247
- },
248
- logs: () => {
249
- return logs.getLogs();
250
- },
251
- };
252
- }
253
- async _startDockerProcess(blockInstance, blockInfo, env) {
168
+ async _startDockerProcess(blockInstance, blockInfo, env, assetVersion) {
254
169
  const { versionFile } = ClusterConfig.getRepositoryAssetInfoPath(blockInfo.handle, blockInfo.name, blockInfo.version);
255
170
  const versionYml = versionFile;
256
171
  if (!FS.existsSync(versionYml)) {
@@ -264,23 +179,28 @@ export class BlockInstanceRunner {
264
179
  if (!dockerImage) {
265
180
  throw new Error(`Missing docker image information: ${JSON.stringify(versionInfo?.artifact?.details)}`);
266
181
  }
182
+ const { PortBindings, ExposedPorts, addonEnv } = await this.getDockerPortBindings(blockInstance, assetVersion);
267
183
  const containerName = getBlockInstanceContainerName(blockInstance.id);
268
- const logs = new LogData();
269
184
  // For windows we need to default to root
270
185
  const innerHome = process.platform === 'win32' ? '/root/.kapeta' : ClusterConfig.getKapetaBasedir();
271
186
  return this.ensureContainer({
272
187
  Image: dockerImage,
273
188
  name: containerName,
189
+ ExposedPorts,
274
190
  Labels: {
275
191
  instance: blockInstance.id,
276
192
  },
277
193
  Env: [
278
194
  ...DOCKER_ENV_VARS,
279
195
  `KAPETA_LOCAL_CLUSTER_PORT=${clusterService.getClusterServicePort()}`,
280
- ...Object.entries(env).map(([key, value]) => `${key}=${value}`),
196
+ ...Object.entries({
197
+ ...env,
198
+ ...addonEnv
199
+ }).map(([key, value]) => `${key}=${value}`),
281
200
  ],
282
201
  HostConfig: {
283
202
  Binds: [`${toLocalBindVolume(ClusterConfig.getKapetaBasedir())}:${innerHome}`],
203
+ PortBindings,
284
204
  },
285
205
  });
286
206
  }
@@ -373,4 +293,93 @@ export class BlockInstanceRunner {
373
293
  }
374
294
  return out;
375
295
  }
296
+ async getDockerPortBindings(blockInstance, assetVersion) {
297
+ const bindHost = getBindHost();
298
+ const ExposedPorts = {};
299
+ const addonEnv = {};
300
+ const PortBindings = {};
301
+ const portTypes = getProviderPorts(assetVersion);
302
+ let port = 80;
303
+ const promises = portTypes.map(async (portType) => {
304
+ const publicPort = await serviceManager.ensureServicePort(this._systemId, blockInstance.id, portType);
305
+ const thisPort = port++; //TODO: Not sure how we should handle multiple ports or non-HTTP ports
306
+ const dockerPort = `${thisPort}/tcp`;
307
+ ExposedPorts[dockerPort] = {};
308
+ addonEnv[`KAPETA_LOCAL_SERVER_PORT_${portType.toUpperCase()}`] = '' + thisPort;
309
+ PortBindings[dockerPort] = [
310
+ {
311
+ HostIp: bindHost,
312
+ HostPort: `${publicPort}`,
313
+ },
314
+ ];
315
+ });
316
+ await Promise.all(promises);
317
+ return { PortBindings, ExposedPorts, addonEnv };
318
+ }
319
+ async ensureContainer(opts) {
320
+ const logs = new LogData();
321
+ const container = await containerManager.ensureContainer(opts);
322
+ try {
323
+ if (opts.HealthCheck) {
324
+ await containerManager.waitForHealthy(container);
325
+ }
326
+ else {
327
+ await containerManager.waitForReady(container);
328
+ }
329
+ }
330
+ catch (e) {
331
+ logs.addLog(e.message, 'ERROR');
332
+ }
333
+ return this._handleContainer(container, logs);
334
+ }
335
+ async _handleContainer(container, logs, deleteOnExit = false) {
336
+ let localContainer = container;
337
+ const logStream = (await container.logs({
338
+ follow: true,
339
+ stdout: true,
340
+ stderr: true,
341
+ tail: LogData.MAX_LINES,
342
+ }));
343
+ const outputEvents = new EventEmitter();
344
+ logStream.on('data', (data) => {
345
+ logs.addLog(data.toString());
346
+ outputEvents.emit('data', data);
347
+ });
348
+ logStream.on('error', (data) => {
349
+ logs.addLog(data.toString());
350
+ outputEvents.emit('data', data);
351
+ });
352
+ logStream.on('close', async () => {
353
+ const status = await container.status();
354
+ const data = status.data;
355
+ if (deleteOnExit) {
356
+ try {
357
+ await containerManager.remove(container);
358
+ }
359
+ catch (e) { }
360
+ }
361
+ outputEvents.emit('exit', data?.State?.ExitCode ?? 0);
362
+ });
363
+ return {
364
+ type: InstanceType.DOCKER,
365
+ pid: container.id,
366
+ output: outputEvents,
367
+ stop: async () => {
368
+ if (!localContainer) {
369
+ return;
370
+ }
371
+ try {
372
+ await localContainer.stop();
373
+ if (deleteOnExit) {
374
+ await containerManager.remove(localContainer);
375
+ }
376
+ }
377
+ catch (e) { }
378
+ localContainer = null;
379
+ },
380
+ logs: () => {
381
+ return logs.getLogs();
382
+ },
383
+ };
384
+ }
376
385
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kapeta/local-cluster-service",
3
- "version": "0.10.0",
3
+ "version": "0.10.1",
4
4
  "description": "Manages configuration, ports and service discovery for locally running Kapeta systems",
5
5
  "type": "commonjs",
6
6
  "exports": {
@@ -117,7 +117,7 @@ export class BlockInstanceRunner {
117
117
  if (blockUri.version === 'local') {
118
118
  processInfo = await this._startLocalProcess(blockInstance, blockUri, env, assetVersion);
119
119
  } else {
120
- processInfo = await this._startDockerProcess(blockInstance, blockUri, env);
120
+ processInfo = await this._startDockerProcess(blockInstance, blockUri, env, assetVersion);
121
121
  }
122
122
 
123
123
  if (portTypes.length > 0) {
@@ -170,36 +170,16 @@ export class BlockInstanceRunner {
170
170
  }
171
171
 
172
172
  const containerName = getBlockInstanceContainerName(blockInstance.id);
173
-
174
173
  const startCmd = localContainer.handlers?.onCreate ? localContainer.handlers.onCreate : '';
175
174
  const dockerOpts = localContainer.options ?? {};
176
175
  const homeDir = localContainer.userHome ? localContainer.userHome : '/root';
177
176
  const workingDir = localContainer.workingDir ? localContainer.workingDir : '/workspace';
178
177
 
179
- const bindHost = getBindHost();
180
-
181
- const ExposedPorts: AnyMap = {};
182
- const addonEnv: StringMap = {};
183
- const PortBindings: AnyMap = {};
184
-
185
- const portTypes = getProviderPorts(assetVersion);
186
- let port = 80;
187
- const promises = portTypes.map(async (portType) => {
188
- const publicPort = await serviceManager.ensureServicePort(this._systemId, blockInstance.id, portType);
189
- const thisPort = port++; //TODO: Not sure how we should handle multiple ports or non-HTTP ports
190
- const dockerPort = `${thisPort}/tcp`;
191
- ExposedPorts[dockerPort] = {};
192
- addonEnv[`KAPETA_LOCAL_SERVER_PORT_${portType.toUpperCase()}`] = '' + thisPort;
193
-
194
- PortBindings[dockerPort] = [
195
- {
196
- HostIp: bindHost,
197
- HostPort: `${publicPort}`,
198
- },
199
- ];
200
- });
201
-
202
- await Promise.all(promises);
178
+ const {
179
+ PortBindings,
180
+ ExposedPorts,
181
+ addonEnv
182
+ } = await this.getDockerPortBindings(blockInstance, assetVersion);
203
183
 
204
184
  let HealthCheck = undefined;
205
185
  if (localContainer.healthcheck) {
@@ -235,83 +215,7 @@ export class BlockInstanceRunner {
235
215
  });
236
216
  }
237
217
 
238
- private async ensureContainer(opts: any) {
239
- const logs = new LogData();
240
-
241
- const container = await containerManager.ensureContainer(opts);
242
-
243
- try {
244
- if (opts.HealthCheck) {
245
- await containerManager.waitForHealthy(container);
246
- } else {
247
- await containerManager.waitForReady(container);
248
- }
249
- } catch (e: any) {
250
- logs.addLog(e.message, 'ERROR');
251
- }
252
-
253
- return this._handleContainer(container, logs);
254
- }
255
-
256
- private async _handleContainer(
257
- container: Container,
258
- logs: LogData,
259
- deleteOnExit: boolean = false
260
- ): Promise<ProcessInfo> {
261
- let localContainer: Container | null = container;
262
- const logStream = (await container.logs({
263
- follow: true,
264
- stdout: true,
265
- stderr: true,
266
- tail: LogData.MAX_LINES,
267
- })) as EventEmitter;
268
-
269
- const outputEvents = new EventEmitter();
270
- logStream.on('data', (data) => {
271
- logs.addLog(data.toString());
272
- outputEvents.emit('data', data);
273
- });
274
-
275
- logStream.on('error', (data) => {
276
- logs.addLog(data.toString());
277
- outputEvents.emit('data', data);
278
- });
279
-
280
- logStream.on('close', async () => {
281
- const status = await container.status();
282
- const data = status.data as any;
283
- if (deleteOnExit) {
284
- try {
285
- await containerManager.remove(container);
286
- } catch (e: any) {}
287
- }
288
- outputEvents.emit('exit', data?.State?.ExitCode ?? 0);
289
- });
290
-
291
- return {
292
- type: InstanceType.DOCKER,
293
- pid: container.id,
294
- output: outputEvents,
295
- stop: async () => {
296
- if (!localContainer) {
297
- return;
298
- }
299
-
300
- try {
301
- await localContainer.stop();
302
- if (deleteOnExit) {
303
- await containerManager.remove(localContainer);
304
- }
305
- } catch (e) {}
306
- localContainer = null;
307
- },
308
- logs: () => {
309
- return logs.getLogs();
310
- },
311
- };
312
- }
313
-
314
- private async _startDockerProcess(blockInstance: BlockProcessParams, blockInfo: KapetaURI, env: StringMap) {
218
+ private async _startDockerProcess(blockInstance: BlockProcessParams, blockInfo: KapetaURI, env: StringMap, assetVersion: DefinitionInfo) {
315
219
  const { versionFile } = ClusterConfig.getRepositoryAssetInfoPath(
316
220
  blockInfo.handle,
317
221
  blockInfo.name,
@@ -333,8 +237,13 @@ export class BlockInstanceRunner {
333
237
  throw new Error(`Missing docker image information: ${JSON.stringify(versionInfo?.artifact?.details)}`);
334
238
  }
335
239
 
240
+ const {
241
+ PortBindings,
242
+ ExposedPorts,
243
+ addonEnv
244
+ } = await this.getDockerPortBindings(blockInstance, assetVersion);
245
+
336
246
  const containerName = getBlockInstanceContainerName(blockInstance.id);
337
- const logs = new LogData();
338
247
 
339
248
  // For windows we need to default to root
340
249
  const innerHome = process.platform === 'win32' ? '/root/.kapeta' : ClusterConfig.getKapetaBasedir();
@@ -342,16 +251,22 @@ export class BlockInstanceRunner {
342
251
  return this.ensureContainer({
343
252
  Image: dockerImage,
344
253
  name: containerName,
254
+ ExposedPorts,
345
255
  Labels: {
346
256
  instance: blockInstance.id,
347
257
  },
348
258
  Env: [
349
259
  ...DOCKER_ENV_VARS,
350
260
  `KAPETA_LOCAL_CLUSTER_PORT=${clusterService.getClusterServicePort()}`,
351
- ...Object.entries(env).map(([key, value]) => `${key}=${value}`),
261
+ ...Object.entries({
262
+ ...env,
263
+ ...addonEnv
264
+ }).map(([key, value]) => `${key}=${value}`),
265
+
352
266
  ],
353
267
  HostConfig: {
354
268
  Binds: [`${toLocalBindVolume(ClusterConfig.getKapetaBasedir())}:${innerHome}`],
269
+ PortBindings,
355
270
  },
356
271
  });
357
272
  }
@@ -471,4 +386,110 @@ export class BlockInstanceRunner {
471
386
 
472
387
  return out;
473
388
  }
389
+
390
+
391
+ private async getDockerPortBindings(blockInstance: BlockProcessParams, assetVersion: DefinitionInfo) {
392
+ const bindHost = getBindHost();
393
+ const ExposedPorts: AnyMap = {};
394
+ const addonEnv: StringMap = {};
395
+ const PortBindings: AnyMap = {};
396
+
397
+ const portTypes = getProviderPorts(assetVersion);
398
+ let port = 80;
399
+ const promises = portTypes.map(async (portType) => {
400
+ const publicPort = await serviceManager.ensureServicePort(this._systemId, blockInstance.id, portType);
401
+ const thisPort = port++; //TODO: Not sure how we should handle multiple ports or non-HTTP ports
402
+ const dockerPort = `${thisPort}/tcp`;
403
+ ExposedPorts[dockerPort] = {};
404
+ addonEnv[`KAPETA_LOCAL_SERVER_PORT_${portType.toUpperCase()}`] = '' + thisPort;
405
+
406
+ PortBindings[dockerPort] = [
407
+ {
408
+ HostIp: bindHost,
409
+ HostPort: `${publicPort}`,
410
+ },
411
+ ];
412
+ });
413
+
414
+
415
+ await Promise.all(promises);
416
+
417
+ return {PortBindings,ExposedPorts, addonEnv};
418
+ }
419
+
420
+ private async ensureContainer(opts: any) {
421
+ const logs = new LogData();
422
+
423
+ const container = await containerManager.ensureContainer(opts);
424
+
425
+ try {
426
+ if (opts.HealthCheck) {
427
+ await containerManager.waitForHealthy(container);
428
+ } else {
429
+ await containerManager.waitForReady(container);
430
+ }
431
+ } catch (e: any) {
432
+ logs.addLog(e.message, 'ERROR');
433
+ }
434
+
435
+ return this._handleContainer(container, logs);
436
+ }
437
+
438
+ private async _handleContainer(
439
+ container: Container,
440
+ logs: LogData,
441
+ deleteOnExit: boolean = false
442
+ ): Promise<ProcessInfo> {
443
+ let localContainer: Container | null = container;
444
+ const logStream = (await container.logs({
445
+ follow: true,
446
+ stdout: true,
447
+ stderr: true,
448
+ tail: LogData.MAX_LINES,
449
+ })) as EventEmitter;
450
+
451
+ const outputEvents = new EventEmitter();
452
+ logStream.on('data', (data) => {
453
+ logs.addLog(data.toString());
454
+ outputEvents.emit('data', data);
455
+ });
456
+
457
+ logStream.on('error', (data) => {
458
+ logs.addLog(data.toString());
459
+ outputEvents.emit('data', data);
460
+ });
461
+
462
+ logStream.on('close', async () => {
463
+ const status = await container.status();
464
+ const data = status.data as any;
465
+ if (deleteOnExit) {
466
+ try {
467
+ await containerManager.remove(container);
468
+ } catch (e: any) {}
469
+ }
470
+ outputEvents.emit('exit', data?.State?.ExitCode ?? 0);
471
+ });
472
+
473
+ return {
474
+ type: InstanceType.DOCKER,
475
+ pid: container.id,
476
+ output: outputEvents,
477
+ stop: async () => {
478
+ if (!localContainer) {
479
+ return;
480
+ }
481
+
482
+ try {
483
+ await localContainer.stop();
484
+ if (deleteOnExit) {
485
+ await containerManager.remove(localContainer);
486
+ }
487
+ } catch (e) {}
488
+ localContainer = null;
489
+ },
490
+ logs: () => {
491
+ return logs.getLogs();
492
+ },
493
+ };
494
+ }
474
495
  }