@kapeta/local-cluster-service 0.9.1 → 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.
@@ -7,9 +7,10 @@ import { containerManager, DockerMounts, toLocalBindVolume } from '../containerM
7
7
  import { LogData } from './LogData';
8
8
  import EventEmitter from 'events';
9
9
  import { clusterService } from '../clusterService';
10
- import { AnyMap, BlockProcessParams, ProcessInfo, InstanceType, StringMap } from '../types';
10
+ import { AnyMap, BlockProcessParams, InstanceType, ProcessInfo, StringMap } from '../types';
11
11
  import { Container } from 'node-docker-api/lib/container';
12
12
  import { definitionsManager } from '../definitionsManager';
13
+ import md5 from 'md5';
13
14
 
14
15
  const KIND_BLOCK_TYPE_OPERATOR = 'core/block-type-operator';
15
16
  const KAPETA_SYSTEM_ID = 'KAPETA_SYSTEM_ID';
@@ -116,7 +117,7 @@ export class BlockInstanceRunner {
116
117
  if (blockUri.version === 'local') {
117
118
  processInfo = await this._startLocalProcess(blockInstance, blockUri, env, assetVersion);
118
119
  } else {
119
- processInfo = await this._startDockerProcess(blockInstance, blockUri, env);
120
+ processInfo = await this._startDockerProcess(blockInstance, blockUri, env, assetVersion);
120
121
  }
121
122
 
122
123
  if (portTypes.length > 0) {
@@ -169,67 +170,23 @@ export class BlockInstanceRunner {
169
170
  }
170
171
 
171
172
  const containerName = getBlockInstanceContainerName(blockInstance.id);
172
- const logs = new LogData();
173
- logs.addLog(`Starting block ${blockInstance.ref}`);
174
- let containerInfo = await containerManager.getContainerByName(containerName);
175
- let container = containerInfo?.native;
176
-
177
- console.log('Starting dev container', containerName);
178
-
179
- if (containerInfo) {
180
- console.log(`Dev container already exists. Deleting...`);
181
- try {
182
- await containerInfo.remove({
183
- force: true,
184
- });
185
- } catch (e: any) {
186
- throw new Error('Failed to delete existing container: ' + e.message);
187
- }
188
- container = undefined;
189
- containerInfo = undefined;
190
- }
191
-
192
- logs.addLog(`Creating new container for block: ${containerName}`);
193
- console.log('Creating new dev container', containerName, dockerImage);
194
- await containerManager.pull(dockerImage);
195
-
196
173
  const startCmd = localContainer.handlers?.onCreate ? localContainer.handlers.onCreate : '';
197
174
  const dockerOpts = localContainer.options ?? {};
198
175
  const homeDir = localContainer.userHome ? localContainer.userHome : '/root';
199
176
  const workingDir = localContainer.workingDir ? localContainer.workingDir : '/workspace';
200
177
 
201
- const bindHost = getBindHost();
202
-
203
- const ExposedPorts: AnyMap = {};
204
- const addonEnv: StringMap = {};
205
- const PortBindings: AnyMap = {};
206
-
207
- const portTypes = getProviderPorts(assetVersion);
208
- let port = 80;
209
- const promises = portTypes.map(async (portType) => {
210
- const publicPort = await serviceManager.ensureServicePort(this._systemId, blockInstance.id, portType);
211
- const thisPort = port++; //TODO: Not sure how we should handle multiple ports or non-HTTP ports
212
- const dockerPort = `${thisPort}/tcp`;
213
- ExposedPorts[dockerPort] = {};
214
- addonEnv[`KAPETA_LOCAL_SERVER_PORT_${portType.toUpperCase()}`] = '' + thisPort;
215
-
216
- PortBindings[dockerPort] = [
217
- {
218
- HostIp: bindHost,
219
- HostPort: `${publicPort}`,
220
- },
221
- ];
222
- });
223
-
224
- await Promise.all(promises);
178
+ const {
179
+ PortBindings,
180
+ ExposedPorts,
181
+ addonEnv
182
+ } = await this.getDockerPortBindings(blockInstance, assetVersion);
225
183
 
226
184
  let HealthCheck = undefined;
227
185
  if (localContainer.healthcheck) {
228
186
  HealthCheck = containerManager.toDockerHealth({ cmd: localContainer.healthcheck });
229
187
  }
230
188
 
231
- console.log('Starting dev container', containerName, dockerImage);
232
- container = await containerManager.startContainer({
189
+ return this.ensureContainer({
233
190
  Image: dockerImage,
234
191
  name: containerName,
235
192
  WorkingDir: workingDir,
@@ -256,79 +213,9 @@ export class BlockInstanceRunner {
256
213
  },
257
214
  ...dockerOpts,
258
215
  });
259
-
260
- try {
261
- if (HealthCheck) {
262
- await containerManager.waitForHealthy(container);
263
- } else {
264
- await containerManager.waitForReady(container);
265
- }
266
- } catch (e: any) {
267
- logs.addLog(e.message, 'ERROR');
268
- }
269
-
270
- return this._handleContainer(container, logs);
271
216
  }
272
217
 
273
- private async _handleContainer(
274
- container: Container,
275
- logs: LogData,
276
- deleteOnExit: boolean = false
277
- ): Promise<ProcessInfo> {
278
- let localContainer: Container | null = container;
279
- const logStream = (await container.logs({
280
- follow: true,
281
- stdout: true,
282
- stderr: true,
283
- tail: LogData.MAX_LINES,
284
- })) as EventEmitter;
285
-
286
- const outputEvents = new EventEmitter();
287
- logStream.on('data', (data) => {
288
- logs.addLog(data.toString());
289
- outputEvents.emit('data', data);
290
- });
291
-
292
- logStream.on('error', (data) => {
293
- logs.addLog(data.toString());
294
- outputEvents.emit('data', data);
295
- });
296
-
297
- logStream.on('close', async () => {
298
- const status = await container.status();
299
- const data = status.data as any;
300
- if (deleteOnExit) {
301
- try {
302
- await containerManager.remove(container);
303
- } catch (e: any) {}
304
- }
305
- outputEvents.emit('exit', data?.State?.ExitCode ?? 0);
306
- });
307
-
308
- return {
309
- type: InstanceType.DOCKER,
310
- pid: container.id,
311
- output: outputEvents,
312
- stop: async () => {
313
- if (!localContainer) {
314
- return;
315
- }
316
-
317
- try {
318
- await localContainer.stop();
319
- if (deleteOnExit) {
320
- await containerManager.remove(localContainer);
321
- }
322
- } catch (e) {}
323
- localContainer = null;
324
- },
325
- logs: () => {
326
- return logs.getLogs();
327
- },
328
- };
329
- }
330
-
331
- private async _startDockerProcess(blockInstance: BlockProcessParams, blockInfo: KapetaURI, env: StringMap) {
218
+ private async _startDockerProcess(blockInstance: BlockProcessParams, blockInfo: KapetaURI, env: StringMap, assetVersion: DefinitionInfo) {
332
219
  const { versionFile } = ClusterConfig.getRepositoryAssetInfoPath(
333
220
  blockInfo.handle,
334
221
  blockInfo.name,
@@ -350,49 +237,38 @@ export class BlockInstanceRunner {
350
237
  throw new Error(`Missing docker image information: ${JSON.stringify(versionInfo?.artifact?.details)}`);
351
238
  }
352
239
 
240
+ const {
241
+ PortBindings,
242
+ ExposedPorts,
243
+ addonEnv
244
+ } = await this.getDockerPortBindings(blockInstance, assetVersion);
245
+
353
246
  const containerName = getBlockInstanceContainerName(blockInstance.id);
354
- const logs = new LogData();
355
- const containerInfo = await containerManager.getContainerByName(containerName);
356
- let container = containerInfo?.native;
357
247
 
358
248
  // For windows we need to default to root
359
249
  const innerHome = process.platform === 'win32' ? '/root/.kapeta' : ClusterConfig.getKapetaBasedir();
360
250
 
361
- if (container) {
362
- const containerData = container.data as any;
363
- if (containerData.State === 'running') {
364
- logs.addLog(`Found existing running container for block: ${containerName}`);
365
- } else {
366
- logs.addLog(`Found existing container for block: ${containerName}. Starting now`);
367
- await container.start();
368
- }
369
- } else {
370
- logs.addLog(`Creating new container for block: ${containerName}`);
371
-
372
- container = await containerManager.startContainer({
373
- Image: dockerImage,
374
- name: containerName,
375
- Labels: {
376
- instance: blockInstance.id,
377
- },
378
- Env: [
379
- ...DOCKER_ENV_VARS,
380
- `KAPETA_LOCAL_CLUSTER_PORT=${clusterService.getClusterServicePort()}`,
381
- ...Object.entries(env).map(([key, value]) => `${key}=${value}`),
382
- ],
383
- HostConfig: {
384
- Binds: [`${toLocalBindVolume(ClusterConfig.getKapetaBasedir())}:${innerHome}`],
385
- },
386
- });
387
-
388
- try {
389
- await containerManager.waitForReady(container);
390
- } catch (e: any) {
391
- logs.addLog(e.message, 'ERROR');
392
- }
393
- }
251
+ return this.ensureContainer({
252
+ Image: dockerImage,
253
+ name: containerName,
254
+ ExposedPorts,
255
+ Labels: {
256
+ instance: blockInstance.id,
257
+ },
258
+ Env: [
259
+ ...DOCKER_ENV_VARS,
260
+ `KAPETA_LOCAL_CLUSTER_PORT=${clusterService.getClusterServicePort()}`,
261
+ ...Object.entries({
262
+ ...env,
263
+ ...addonEnv
264
+ }).map(([key, value]) => `${key}=${value}`),
394
265
 
395
- return this._handleContainer(container, logs);
266
+ ],
267
+ HostConfig: {
268
+ Binds: [`${toLocalBindVolume(ClusterConfig.getKapetaBasedir())}:${innerHome}`],
269
+ PortBindings,
270
+ },
271
+ });
396
272
  }
397
273
 
398
274
  /**
@@ -430,130 +306,79 @@ export class BlockInstanceRunner {
430
306
 
431
307
  const dockerImage = spec?.local?.image;
432
308
 
433
- try {
434
- await containerManager.pull(dockerImage);
435
- } catch (e) {
436
- console.warn('Failed to pull image. Continuing...', e);
437
- }
438
-
439
309
  const containerName = getBlockInstanceContainerName(blockInstance.id);
440
310
  const logs = new LogData();
441
- const containerInfo = await containerManager.getContainerByName(containerName);
442
- let container = containerInfo?.native;
443
-
444
- if (container) {
445
- const containerData = container.data as any;
446
- if (containerData.State === 'running') {
447
- logs.addLog(`Found existing running container for block: ${containerName}`);
448
- } else {
449
- if (containerData.State?.ExitCode > 0) {
450
- logs.addLog(`Container exited with code: ${containerData.State.ExitCode}. Deleting...`);
451
- try {
452
- await containerManager.remove(container);
453
- } catch (e) {}
454
- container = undefined;
455
- } else {
456
- logs.addLog(`Found existing container for block: ${containerName}. Starting now`);
457
- try {
458
- await container.start();
459
- } catch (e) {
460
- console.warn('Failed to start container. Deleting...', e);
461
- try {
462
- await containerManager.remove(container);
463
- } catch (e) {}
464
- container = undefined;
465
- }
466
- }
467
- }
468
- }
469
311
 
470
312
  const bindHost = getBindHost();
471
313
 
472
- if (!container) {
473
- const ExposedPorts: AnyMap = {};
474
- const addonEnv: StringMap = {};
475
- const PortBindings: AnyMap = {};
476
- let HealthCheck = undefined;
477
- let Mounts: DockerMounts[] = [];
478
- const promises = Object.entries(spec.local.ports as { [p: string]: { port: string; type: string } }).map(
479
- async ([portType, value]) => {
480
- const dockerPort = `${value.port}/${value.type}`;
481
- ExposedPorts[dockerPort] = {};
482
- addonEnv[`KAPETA_LOCAL_SERVER_PORT_${portType.toUpperCase()}`] = value.port;
483
- const publicPort = await serviceManager.ensureServicePort(
484
- this._systemId,
485
- blockInstance.id,
486
- portType
487
- );
488
- PortBindings[dockerPort] = [
489
- {
490
- HostIp: bindHost,
491
- HostPort: `${publicPort}`,
492
- },
493
- ];
494
- }
495
- );
496
-
497
- await Promise.all(promises);
498
-
499
- if (spec.local?.env) {
500
- Object.entries(spec.local.env).forEach(([key, value]) => {
501
- addonEnv[key] = value as string;
502
- });
503
- }
504
-
505
- if (spec.local?.mounts) {
506
- const mounts = containerManager.createMounts(blockUri.id, spec.local.mounts);
507
- Mounts = containerManager.toDockerMounts(mounts);
314
+ const ExposedPorts: AnyMap = {};
315
+ const addonEnv: StringMap = {};
316
+ const PortBindings: AnyMap = {};
317
+ let HealthCheck = undefined;
318
+ let Mounts: DockerMounts[] = [];
319
+ const promises = Object.entries(spec.local.ports as { [p: string]: { port: string; type: string } }).map(
320
+ async ([portType, value]) => {
321
+ const dockerPort = `${value.port}/${value.type}`;
322
+ ExposedPorts[dockerPort] = {};
323
+ addonEnv[`KAPETA_LOCAL_SERVER_PORT_${portType.toUpperCase()}`] = value.port;
324
+ const publicPort = await serviceManager.ensureServicePort(this._systemId, blockInstance.id, portType);
325
+ PortBindings[dockerPort] = [
326
+ {
327
+ HostIp: bindHost,
328
+ HostPort: `${publicPort}`,
329
+ },
330
+ ];
508
331
  }
332
+ );
509
333
 
510
- if (spec.local?.health) {
511
- HealthCheck = containerManager.toDockerHealth(spec.local?.health);
512
- }
334
+ await Promise.all(promises);
513
335
 
514
- // For windows we need to default to root
515
- const innerHome = process.platform === 'win32' ? '/root/.kapeta' : ClusterConfig.getKapetaBasedir();
516
-
517
- logs.addLog(`Creating new container for block: ${containerName}`);
518
- container = await containerManager.startContainer({
519
- Image: dockerImage,
520
- name: containerName,
521
- ExposedPorts,
522
- HealthCheck,
523
- HostConfig: {
524
- Binds: [
525
- `${toLocalBindVolume(kapetaYmlPath)}:/kapeta.yml:ro`,
526
- `${toLocalBindVolume(ClusterConfig.getKapetaBasedir())}:${innerHome}`,
527
- ],
528
- PortBindings,
529
- Mounts,
530
- },
531
- Labels: {
532
- instance: blockInstance.id,
533
- },
534
- Env: [
535
- `KAPETA_INSTANCE_NAME=${blockInstance.ref}`,
536
- `KAPETA_LOCAL_CLUSTER_PORT=${clusterService.getClusterServicePort()}`,
537
- ...DOCKER_ENV_VARS,
538
- ...Object.entries({
539
- ...env,
540
- ...addonEnv,
541
- }).map(([key, value]) => `${key}=${value}`),
542
- ],
336
+ if (spec.local?.env) {
337
+ Object.entries(spec.local.env).forEach(([key, value]) => {
338
+ addonEnv[key] = value as string;
543
339
  });
340
+ }
544
341
 
545
- try {
546
- if (HealthCheck) {
547
- await containerManager.waitForHealthy(container);
548
- } else {
549
- await containerManager.waitForReady(container);
550
- }
551
- } catch (e: any) {
552
- logs.addLog(e.message, 'ERROR');
553
- }
342
+ if (spec.local?.mounts) {
343
+ const mounts = containerManager.createMounts(blockUri.id, spec.local.mounts);
344
+ Mounts = containerManager.toDockerMounts(mounts);
345
+ }
346
+
347
+ if (spec.local?.health) {
348
+ HealthCheck = containerManager.toDockerHealth(spec.local?.health);
554
349
  }
555
350
 
556
- const out = await this._handleContainer(container, logs, true);
351
+ // For windows we need to default to root
352
+ const innerHome = process.platform === 'win32' ? '/root/.kapeta' : ClusterConfig.getKapetaBasedir();
353
+
354
+ logs.addLog(`Creating new container for block: ${containerName}`);
355
+ const out = await this.ensureContainer({
356
+ Image: dockerImage,
357
+ name: containerName,
358
+ ExposedPorts,
359
+ HealthCheck,
360
+ HostConfig: {
361
+ Binds: [
362
+ `${toLocalBindVolume(kapetaYmlPath)}:/kapeta.yml:ro`,
363
+ `${toLocalBindVolume(ClusterConfig.getKapetaBasedir())}:${innerHome}`,
364
+ ],
365
+ PortBindings,
366
+ Mounts,
367
+ },
368
+ Labels: {
369
+ instance: blockInstance.id,
370
+ },
371
+ Env: [
372
+ `KAPETA_INSTANCE_NAME=${blockInstance.ref}`,
373
+ `KAPETA_LOCAL_CLUSTER_PORT=${clusterService.getClusterServicePort()}`,
374
+ ...DOCKER_ENV_VARS,
375
+ ...Object.entries({
376
+ ...env,
377
+ ...addonEnv,
378
+ }).map(([key, value]) => `${key}=${value}`),
379
+ ],
380
+ });
381
+
557
382
  const portTypes = spec.local.ports ? Object.keys(spec.local.ports) : [];
558
383
  if (portTypes.length > 0) {
559
384
  out.portType = portTypes[0];
@@ -561,4 +386,110 @@ export class BlockInstanceRunner {
561
386
 
562
387
  return out;
563
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
+ }
564
495
  }