@kapeta/local-cluster-service 0.19.6 → 0.19.7

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.19.7](https://github.com/kapetacom/local-cluster-service/compare/v0.19.6...v0.19.7) (2023-09-10)
2
+
3
+
4
+ ### Bug Fixes
5
+
6
+ * Replaced node-docker-api with dockerode and types ([#72](https://github.com/kapetacom/local-cluster-service/issues/72)) ([3d4894c](https://github.com/kapetacom/local-cluster-service/commit/3d4894c6f78dc6efe5e75587d6463f33a86ded62))
7
+
1
8
  ## [0.19.6](https://github.com/kapetacom/local-cluster-service/compare/v0.19.5...v0.19.6) (2023-09-08)
2
9
 
3
10
 
@@ -1,7 +1,6 @@
1
1
  /// <reference types="node" />
2
2
  import FSExtra from 'fs-extra';
3
- import { Docker } from 'node-docker-api';
4
- import { Container } from 'node-docker-api/lib/container';
3
+ import Docker from 'dockerode';
5
4
  import { InstanceInfo, LogEntry } from './types';
6
5
  type StringMap = {
7
6
  [key: string]: string;
@@ -20,24 +19,8 @@ export interface DockerMounts {
20
19
  ReadOnly: boolean;
21
20
  Consistency: string;
22
21
  }
23
- interface DockerState {
24
- Status: 'created' | 'running' | 'paused' | 'restarting' | 'removing' | 'exited' | 'dead';
25
- Running: boolean;
26
- Paused: boolean;
27
- Restarting: boolean;
28
- OOMKilled: boolean;
29
- Dead: boolean;
30
- Pid: number;
31
- ExitCode: number;
32
- Error: string;
33
- StartedAt: string;
34
- FinishedAt: string;
35
- Health?: {
36
- Status: 'starting' | 'healthy' | 'unhealthy' | 'none';
37
- FailingStreak: number;
38
- Log: any[] | null;
39
- };
40
- }
22
+ export type DockerContainerStatus = 'created' | 'running' | 'paused' | 'restarting' | 'removing' | 'exited' | 'dead';
23
+ export type DockerContainerHealth = 'starting' | 'healthy' | 'unhealthy' | 'none';
41
24
  interface Health {
42
25
  cmd: string;
43
26
  interval?: number;
@@ -45,6 +28,8 @@ interface Health {
45
28
  retries?: number;
46
29
  }
47
30
  export declare const CONTAINER_LABEL_PORT_PREFIX = "kapeta_port-";
31
+ export declare const COMPOSE_LABEL_PROJECT = "com.docker.compose.project";
32
+ export declare const COMPOSE_LABEL_SERVICE = "com.docker.compose.service";
48
33
  export declare const HEALTH_CHECK_TIMEOUT: number;
49
34
  declare class ContainerManager {
50
35
  private _docker;
@@ -71,12 +56,12 @@ declare class ContainerManager {
71
56
  Retries: number;
72
57
  };
73
58
  private applyHash;
74
- ensureContainer(opts: any): Promise<Container>;
59
+ ensureContainer(opts: any): Promise<Docker.Container>;
75
60
  private createOrUpdateContainer;
76
- startContainer(opts: any): Promise<Container>;
77
- waitForReady(container: Container, attempt?: number): Promise<void>;
78
- _isReady(container: Container): Promise<any>;
79
- remove(container: Container, opts?: {
61
+ private startContainer;
62
+ waitForReady(container: Docker.Container, attempt?: number): Promise<void>;
63
+ _isReady(container: Docker.Container): Promise<boolean>;
64
+ remove(container: Docker.Container, opts?: {
80
65
  force?: boolean;
81
66
  }): Promise<void>;
82
67
  /**
@@ -84,7 +69,7 @@ declare class ContainerManager {
84
69
  * @param name
85
70
  * @return {Promise<ContainerInfo>}
86
71
  */
87
- get(name: string): Promise<ContainerInfo | null>;
72
+ get(name: string): Promise<ContainerInfo | undefined>;
88
73
  getLogs(instance: InstanceInfo): Promise<LogEntry[]>;
89
74
  stopLogListening(systemId: string, instanceId: string): Promise<void>;
90
75
  ensureLogListening(systemId: string, instanceId: string, handler: (log: LogEntry) => void): Promise<void>;
@@ -102,11 +87,11 @@ export declare class ContainerInfo {
102
87
  private readonly _container;
103
88
  /**
104
89
  *
105
- * @param {Container} dockerContainer
90
+ * @param {Docker.Container} dockerContainer
106
91
  */
107
- constructor(dockerContainer: Container);
108
- get native(): Container;
109
- isRunning(): Promise<any>;
92
+ constructor(dockerContainer: Docker.Container);
93
+ get native(): Docker.Container;
94
+ isRunning(): Promise<boolean>;
110
95
  start(): Promise<void>;
111
96
  restart(): Promise<void>;
112
97
  stop(): Promise<void>;
@@ -118,8 +103,30 @@ export declare class ContainerInfo {
118
103
  protocol: string;
119
104
  hostPort: string;
120
105
  } | null>;
121
- inspect(): Promise<any>;
122
- status(): Promise<DockerState>;
106
+ inspect(): Promise<Docker.ContainerInspectInfo | undefined>;
107
+ status(): Promise<{
108
+ Status: string;
109
+ Running: boolean;
110
+ Paused: boolean;
111
+ Restarting: boolean;
112
+ OOMKilled: boolean;
113
+ Dead: boolean;
114
+ Pid: number;
115
+ ExitCode: number;
116
+ Error: string;
117
+ StartedAt: string;
118
+ FinishedAt: string;
119
+ Health?: {
120
+ Status: string;
121
+ FailingStreak: number;
122
+ Log: {
123
+ Start: string;
124
+ End: string;
125
+ ExitCode: number;
126
+ Output: string;
127
+ }[];
128
+ } | undefined;
129
+ } | undefined>;
123
130
  getPorts(): Promise<PortMap | false>;
124
131
  getLogStream(): Promise<ClosableLogStream>;
125
132
  getLogs(): Promise<LogEntry[]>;
@@ -3,13 +3,13 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
3
3
  return (mod && mod.__esModule) ? mod : { "default": mod };
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
- exports.containerManager = exports.toLocalBindVolume = exports.getExtraHosts = exports.ContainerInfo = exports.HEALTH_CHECK_TIMEOUT = exports.CONTAINER_LABEL_PORT_PREFIX = void 0;
6
+ exports.containerManager = exports.toLocalBindVolume = exports.getExtraHosts = exports.ContainerInfo = exports.HEALTH_CHECK_TIMEOUT = exports.COMPOSE_LABEL_SERVICE = exports.COMPOSE_LABEL_PROJECT = exports.CONTAINER_LABEL_PORT_PREFIX = void 0;
7
7
  const path_1 = __importDefault(require("path"));
8
8
  const storageService_1 = require("./storageService");
9
9
  const os_1 = __importDefault(require("os"));
10
10
  const lodash_1 = __importDefault(require("lodash"));
11
11
  const fs_extra_1 = __importDefault(require("fs-extra"));
12
- const node_docker_api_1 = require("node-docker-api");
12
+ const dockerode_1 = __importDefault(require("dockerode"));
13
13
  const nodejs_utils_1 = require("@kapeta/nodejs-utils");
14
14
  const local_cluster_config_1 = __importDefault(require("@kapeta/local-cluster-config"));
15
15
  const node_uuid_1 = __importDefault(require("node-uuid"));
@@ -18,15 +18,45 @@ const utils_1 = require("./utils/utils");
18
18
  const nodejs_api_client_1 = require("@kapeta/nodejs-api-client");
19
19
  const taskManager_1 = require("./taskManager");
20
20
  const node_events_1 = require("node:events");
21
+ const StreamValues_1 = __importDefault(require("stream-json/streamers/StreamValues"));
21
22
  exports.CONTAINER_LABEL_PORT_PREFIX = 'kapeta_port-';
22
23
  const NANO_SECOND = 1000000;
23
24
  const HEALTH_CHECK_INTERVAL = 3000;
24
25
  const HEALTH_CHECK_MAX = 20;
26
+ exports.COMPOSE_LABEL_PROJECT = 'com.docker.compose.project';
27
+ exports.COMPOSE_LABEL_SERVICE = 'com.docker.compose.service';
25
28
  exports.HEALTH_CHECK_TIMEOUT = HEALTH_CHECK_INTERVAL * HEALTH_CHECK_MAX * 2;
26
- const promisifyStream = (stream, handler) => new Promise((resolve, reject) => {
27
- stream.on('data', handler);
28
- stream.on('end', resolve);
29
- stream.on('error', reject);
29
+ var DockerPullEventTypes;
30
+ (function (DockerPullEventTypes) {
31
+ DockerPullEventTypes["PreparingPhase"] = "Preparing";
32
+ DockerPullEventTypes["WaitingPhase"] = "Waiting";
33
+ DockerPullEventTypes["PullingFsPhase"] = "Pulling fs layer";
34
+ DockerPullEventTypes["DownloadingPhase"] = "Downloading";
35
+ DockerPullEventTypes["DownloadCompletePhase"] = "Download complete";
36
+ DockerPullEventTypes["ExtractingPhase"] = "Extracting";
37
+ DockerPullEventTypes["VerifyingChecksumPhase"] = "Verifying Checksum";
38
+ DockerPullEventTypes["AlreadyExistsPhase"] = "Already exists";
39
+ DockerPullEventTypes["PullCompletePhase"] = "Pull complete";
40
+ })(DockerPullEventTypes || (DockerPullEventTypes = {}));
41
+ const processJsonStream = (purpose, stream, handler) => new Promise((resolve, reject) => {
42
+ const jsonStream = StreamValues_1.default.withParser();
43
+ jsonStream.on('data', (data) => {
44
+ try {
45
+ handler(data.value);
46
+ }
47
+ catch (e) {
48
+ console.error('Failed while processing data for stream: %s', purpose, e);
49
+ }
50
+ });
51
+ jsonStream.on('end', () => {
52
+ console.log('Docker stream ended: %s', purpose);
53
+ resolve();
54
+ });
55
+ jsonStream.on('error', (err) => {
56
+ console.error('Docker stream failed: %s', purpose, err);
57
+ reject(err);
58
+ });
59
+ stream.pipe(jsonStream);
30
60
  });
31
61
  class ContainerManager {
32
62
  _docker;
@@ -64,9 +94,9 @@ class ContainerManager {
64
94
  ];
65
95
  for (const opts of connectOptions) {
66
96
  try {
67
- const client = new node_docker_api_1.Docker({
97
+ const client = new dockerode_1.default({
68
98
  ...opts,
69
- timeout: 10000,
99
+ timeout: 15 * 60 * 1000, //15 minutes should be enough for any operation
70
100
  });
71
101
  await client.ping();
72
102
  this._docker = client;
@@ -143,13 +173,12 @@ class ContainerManager {
143
173
  return this._docker;
144
174
  }
145
175
  async getContainerByName(containerName) {
146
- const containers = await this.docker().container.list({ all: true });
176
+ const containers = await this.docker().listContainers({ all: true });
147
177
  const out = containers.find((container) => {
148
- const containerData = container.data;
149
- return containerData.Names.indexOf(`/${containerName}`) > -1;
178
+ return container.Names.indexOf(`/${containerName}`) > -1;
150
179
  });
151
180
  if (out) {
152
- return new ContainerInfo(out);
181
+ return this.get(out.Id);
153
182
  }
154
183
  return undefined;
155
184
  }
@@ -158,8 +187,7 @@ class ContainerManager {
158
187
  if (!tag) {
159
188
  tag = 'latest';
160
189
  }
161
- const imageTagList = (await this.docker().image.list())
162
- .map((image) => image.data)
190
+ const imageTagList = (await this.docker().listImages({}))
163
191
  .filter((imageData) => !!imageData.RepoTags)
164
192
  .map((imageData) => imageData.RepoTags);
165
193
  if (imageTagList.some((imageTags) => imageTags.indexOf(image) > -1)) {
@@ -184,66 +212,61 @@ class ContainerManager {
184
212
  serveraddress: 'docker.kapeta.com',
185
213
  }
186
214
  : {};
187
- const stream = (await this.docker().image.create(auth, {
188
- fromImage: imageName,
189
- tag: tag,
190
- }));
215
+ const stream = await this.docker().pull(image, {
216
+ authconfig: auth,
217
+ });
191
218
  const chunks = {};
192
219
  let lastEmitted = Date.now();
193
- await promisifyStream(stream, (rawData) => {
194
- const lines = rawData.toString().trim().split('\n');
195
- lines.forEach((line) => {
196
- const data = JSON.parse(line);
197
- if (![
198
- 'Waiting',
199
- 'Downloading',
200
- 'Extracting',
201
- 'Download complete',
202
- 'Pull complete',
203
- 'Already exists',
204
- ].includes(data.status)) {
205
- return;
206
- }
207
- if (!chunks[data.id]) {
208
- chunks[data.id] = {
209
- downloading: {
210
- total: 0,
211
- current: 0,
212
- },
213
- extracting: {
214
- total: 0,
215
- current: 0,
216
- },
217
- done: false,
220
+ await processJsonStream(`image:pull:${image}`, stream, (data) => {
221
+ if (!chunks[data.id]) {
222
+ chunks[data.id] = {
223
+ downloading: {
224
+ total: 0,
225
+ current: 0,
226
+ },
227
+ extracting: {
228
+ total: 0,
229
+ current: 0,
230
+ },
231
+ done: false,
232
+ };
233
+ }
234
+ const chunk = chunks[data.id];
235
+ switch (data.status) {
236
+ case DockerPullEventTypes.PreparingPhase:
237
+ case DockerPullEventTypes.WaitingPhase:
238
+ case DockerPullEventTypes.PullingFsPhase:
239
+ //Do nothing
240
+ break;
241
+ case DockerPullEventTypes.DownloadingPhase:
242
+ case DockerPullEventTypes.VerifyingChecksumPhase:
243
+ chunk.downloading = {
244
+ total: data.progressDetail?.total ?? 0,
245
+ current: data.progressDetail?.current ?? 0,
218
246
  };
219
- }
220
- const chunk = chunks[data.id];
221
- switch (data.status) {
222
- case 'Downloading':
223
- chunk.downloading = data.progressDetail;
224
- break;
225
- case 'Extracting':
226
- chunk.extracting = data.progressDetail;
227
- break;
228
- case 'Download complete':
229
- chunk.downloading.current = chunks[data.id].downloading.total;
230
- break;
231
- case 'Pull complete':
232
- chunk.extracting.current = chunks[data.id].extracting.total;
233
- chunk.done = true;
234
- break;
235
- case 'Already exists':
236
- // Force layer to be done
237
- chunk.downloading.current = 1;
238
- chunk.downloading.total = 1;
239
- chunk.extracting.current = 1;
240
- chunk.extracting.total = 1;
241
- chunk.done = true;
242
- break;
243
- }
244
- });
245
- if (Date.now() - lastEmitted < 1000) {
246
- return;
247
+ break;
248
+ case DockerPullEventTypes.ExtractingPhase:
249
+ chunk.extracting = {
250
+ total: data.progressDetail?.total ?? 0,
251
+ current: data.progressDetail?.current ?? 0,
252
+ };
253
+ break;
254
+ case DockerPullEventTypes.DownloadCompletePhase:
255
+ chunk.downloading.current = chunks[data.id].downloading.total;
256
+ break;
257
+ case DockerPullEventTypes.PullCompletePhase:
258
+ chunk.extracting.current = chunks[data.id].extracting.total;
259
+ chunk.done = true;
260
+ break;
261
+ }
262
+ if (data.status === DockerPullEventTypes.AlreadyExistsPhase ||
263
+ data.status.includes('Image is up to date') ||
264
+ data.status.includes('Downloaded newer image')) {
265
+ chunk.downloading.current = 1;
266
+ chunk.downloading.total = 1;
267
+ chunk.extracting.current = 1;
268
+ chunk.extracting.total = 1;
269
+ chunk.done = true;
247
270
  }
248
271
  const chunkList = Object.values(chunks);
249
272
  let totals = {
@@ -255,6 +278,7 @@ class ContainerManager {
255
278
  total: 0,
256
279
  current: 0,
257
280
  },
281
+ percent: 0,
258
282
  total: chunkList.length,
259
283
  done: 0,
260
284
  };
@@ -275,14 +299,17 @@ class ContainerManager {
275
299
  totals.done++;
276
300
  }
277
301
  });
278
- const progress = totals.total > 0 ? (totals.done / totals.total) * 100 : 0;
302
+ totals.percent = totals.total > 0 ? (totals.done / totals.total) * 100 : 0;
279
303
  task.metadata = {
280
304
  ...task.metadata,
281
305
  image,
282
- progress,
306
+ progress: totals.percent,
283
307
  status: totals,
284
308
  timeTaken: Date.now() - timeStarted,
285
309
  };
310
+ if (Date.now() - lastEmitted < 1000) {
311
+ return;
312
+ }
286
313
  task.emitUpdate();
287
314
  lastEmitted = Date.now();
288
315
  //console.log('Pulling image %s: %s % [done: %s, total: %s]', image, Math.round(percent), totals.done, totals.total);
@@ -299,6 +326,7 @@ class ContainerManager {
299
326
  name: taskName,
300
327
  image,
301
328
  progress: -1,
329
+ group: 'docker:pull', //It's faster to pull images one at a time
302
330
  });
303
331
  await task.wait();
304
332
  return true;
@@ -344,32 +372,32 @@ class ContainerManager {
344
372
  console.log('Starting unnamed container: %s', opts.Image);
345
373
  return this.startContainer(opts);
346
374
  }
347
- const containerInfo = await this.getContainerByName(opts.name);
375
+ const container = await this.getContainerByName(opts.name);
348
376
  if (imagePulled) {
377
+ // If image was pulled always recreate
349
378
  console.log('New version of image was pulled: %s', opts.Image);
350
379
  }
351
380
  else {
352
- // If image was pulled always recreate
353
- if (!containerInfo) {
381
+ if (!container) {
354
382
  console.log('Starting new container: %s', opts.name);
355
383
  return this.startContainer(opts);
356
384
  }
357
- const containerData = containerInfo.native.data;
358
- if (containerData?.Labels?.HASH === opts.Labels.HASH) {
359
- if (!(await containerInfo.isRunning())) {
385
+ const containerData = await container.inspect();
386
+ if (containerData?.Config.Labels?.HASH === opts.Labels.HASH) {
387
+ if (!(await container.isRunning())) {
360
388
  console.log('Starting previously created container: %s', opts.name);
361
- await containerInfo.start();
389
+ await container.start();
362
390
  }
363
391
  else {
364
392
  console.log('Previously created container already running: %s', opts.name);
365
393
  }
366
- return containerInfo.native;
394
+ return container.native;
367
395
  }
368
396
  }
369
- if (containerInfo) {
397
+ if (container) {
370
398
  // Remove the container and start a new one
371
399
  console.log('Replacing previously created container: %s', opts.name);
372
- await containerInfo.remove({ force: true });
400
+ await container.remove({ force: true });
373
401
  }
374
402
  console.log('Starting new container: %s', opts.name);
375
403
  return this.startContainer(opts);
@@ -385,7 +413,7 @@ class ContainerManager {
385
413
  }
386
414
  opts.HostConfig.ExtraHosts = opts.HostConfig.ExtraHosts.concat(extraHosts);
387
415
  }
388
- const dockerContainer = await this.docker().container.create(opts);
416
+ const dockerContainer = await this.docker().createContainer(opts);
389
417
  await dockerContainer.start();
390
418
  return dockerContainer;
391
419
  }
@@ -414,30 +442,28 @@ class ContainerManager {
414
442
  async _isReady(container) {
415
443
  let info;
416
444
  try {
417
- info = await container.status();
445
+ info = await container.inspect();
418
446
  }
419
447
  catch (err) {
420
448
  return false;
421
449
  }
422
- const infoData = info?.data;
423
- const state = infoData?.State;
424
- if (state?.Status === 'exited' || state?.Status === 'removing' || state?.Status === 'dead') {
450
+ const state = info.State;
451
+ if (state.Status === 'exited' || state?.Status === 'removing' || state?.Status === 'dead') {
425
452
  throw new Error('Container exited unexpectedly');
426
453
  }
427
- if (infoData?.State?.Health) {
454
+ if (state.Health) {
428
455
  // If container has health info - wait for it to become healthy
429
- return infoData.State.Health.Status === 'healthy';
456
+ return state.Health.Status === 'healthy';
430
457
  }
431
458
  else {
432
- return infoData?.State?.Running ?? false;
459
+ return state.Running ?? false;
433
460
  }
434
461
  }
435
462
  async remove(container, opts) {
436
463
  const newName = 'deleting-' + node_uuid_1.default.v4();
437
- const containerData = container.data;
438
464
  // Rename the container first to avoid name conflicts if people start the same container
439
465
  await container.rename({ name: newName });
440
- await container.delete({ force: !!opts?.force });
466
+ await container.remove({ force: !!opts?.force });
441
467
  }
442
468
  /**
443
469
  *
@@ -447,15 +473,15 @@ class ContainerManager {
447
473
  async get(name) {
448
474
  let dockerContainer = null;
449
475
  try {
450
- dockerContainer = await this.docker().container.get(name);
451
- await dockerContainer.status();
476
+ dockerContainer = await this.docker().getContainer(name);
477
+ await dockerContainer.stats();
452
478
  }
453
479
  catch (err) {
454
480
  //Ignore
455
481
  dockerContainer = null;
456
482
  }
457
483
  if (!dockerContainer) {
458
- return null;
484
+ return undefined;
459
485
  }
460
486
  return new ContainerInfo(dockerContainer);
461
487
  }
@@ -647,12 +673,12 @@ class ContainerInfo {
647
673
  _container;
648
674
  /**
649
675
  *
650
- * @param {Container} dockerContainer
676
+ * @param {Docker.Container} dockerContainer
651
677
  */
652
678
  constructor(dockerContainer) {
653
679
  /**
654
680
  *
655
- * @type {Container}
681
+ * @type {Docker.Container}
656
682
  * @private
657
683
  */
658
684
  this._container = dockerContainer;
@@ -668,12 +694,21 @@ class ContainerInfo {
668
694
  return inspectResult.State.Running || inspectResult.State.Restarting;
669
695
  }
670
696
  async start() {
697
+ if (await this.isRunning()) {
698
+ return;
699
+ }
671
700
  await this._container.start();
672
701
  }
673
702
  async restart() {
703
+ if (!(await this.isRunning())) {
704
+ return this.start();
705
+ }
674
706
  await this._container.restart();
675
707
  }
676
708
  async stop() {
709
+ if (!(await this.isRunning())) {
710
+ return;
711
+ }
677
712
  await this._container.stop();
678
713
  }
679
714
  async remove(opts) {
@@ -688,16 +723,15 @@ class ContainerInfo {
688
723
  }
689
724
  async inspect() {
690
725
  try {
691
- const result = await this._container.status();
692
- return result ? result.data : null;
726
+ return await this._container.inspect();
693
727
  }
694
728
  catch (err) {
695
- return null;
729
+ return undefined;
696
730
  }
697
731
  }
698
732
  async status() {
699
733
  const result = await this.inspect();
700
- return result.State;
734
+ return result?.State;
701
735
  }
702
736
  async getPorts() {
703
737
  const inspectResult = await this.inspect();
@@ -742,17 +776,13 @@ class ContainerInfo {
742
776
  }
743
777
  }
744
778
  async getLogs() {
745
- const logStream = (await this.native.logs({
779
+ const logs = await this.native.logs({
746
780
  stdout: true,
747
781
  stderr: true,
748
782
  follow: false,
749
783
  timestamps: true,
750
- }));
751
- const chunks = [];
752
- await promisifyStream(logStream, (data) => {
753
- chunks.push(data);
754
784
  });
755
- const out = readLogBuffer(Buffer.concat(chunks));
785
+ const out = readLogBuffer(logs);
756
786
  if (out.length === 0) {
757
787
  out.push({
758
788
  time: Date.now(),
@@ -262,6 +262,9 @@ class InstanceManager {
262
262
  if (instance.status === types_1.InstanceStatus.STOPPED) {
263
263
  return;
264
264
  }
265
+ if (instance.status === types_1.InstanceStatus.STOPPING) {
266
+ return;
267
+ }
265
268
  if (changeDesired && instance.desiredStatus !== types_1.DesiredInstanceStatus.EXTERNAL) {
266
269
  instance.desiredStatus = types_1.DesiredInstanceStatus.STOP;
267
270
  }
@@ -392,18 +395,18 @@ class InstanceManager {
392
395
  const startTime = Date.now();
393
396
  try {
394
397
  const processInfo = await runner.start(blockRef, instanceId, instanceConfig);
395
- instance.status = types_1.InstanceStatus.READY;
398
+ instance.status = types_1.InstanceStatus.STARTING;
396
399
  return this.saveInternalInstance({
397
400
  ...instance,
398
401
  type: processInfo.type,
399
402
  pid: processInfo.pid ?? -1,
400
403
  health: null,
401
404
  portType: processInfo.portType,
402
- status: types_1.InstanceStatus.READY,
405
+ status: types_1.InstanceStatus.STARTING,
403
406
  });
404
407
  }
405
408
  catch (e) {
406
- console.warn('Failed to start instance: ', systemId, instanceId, blockRef, e.message);
409
+ console.warn('Failed to start instance: ', systemId, instanceId, blockRef, e);
407
410
  const logs = [
408
411
  {
409
412
  source: 'stdout',
@@ -550,7 +553,7 @@ class InstanceManager {
550
553
  await this.start(instance.systemId, instance.instanceId);
551
554
  }
552
555
  catch (e) {
553
- console.warn('Failed to start instance', instance.systemId, instance.instanceId, e);
556
+ console.warn('Failed to start previously stopped instance', instance.systemId, instance.instanceId, e);
554
557
  }
555
558
  return;
556
559
  }
@@ -594,31 +597,38 @@ class InstanceManager {
594
597
  return types_1.InstanceStatus.STOPPED;
595
598
  }
596
599
  const state = await container.status();
597
- if (state.Status === 'running') {
598
- if (state.Health?.Status === 'healthy') {
599
- return types_1.InstanceStatus.READY;
600
- }
601
- if (state.Health?.Status === 'starting') {
602
- return types_1.InstanceStatus.STARTING;
603
- }
604
- if (state.Health?.Status === 'unhealthy') {
605
- return types_1.InstanceStatus.UNHEALTHY;
600
+ if (!state) {
601
+ return types_1.InstanceStatus.STOPPED;
602
+ }
603
+ const statusType = state.Status;
604
+ if (statusType === 'running') {
605
+ if (state.Health?.Status) {
606
+ const healthStatusType = state.Health.Status;
607
+ if (healthStatusType === 'healthy' || healthStatusType === 'none') {
608
+ return types_1.InstanceStatus.READY;
609
+ }
610
+ if (healthStatusType === 'starting') {
611
+ return types_1.InstanceStatus.STARTING;
612
+ }
613
+ if (healthStatusType === 'unhealthy') {
614
+ return types_1.InstanceStatus.UNHEALTHY;
615
+ }
606
616
  }
607
617
  return types_1.InstanceStatus.READY;
608
618
  }
609
- if (state.Status === 'created') {
619
+ if (statusType === 'created') {
610
620
  return types_1.InstanceStatus.STARTING;
611
621
  }
612
- if (state.Status === 'exited' || state.Status === 'dead') {
622
+ if (statusType === 'exited' || statusType === 'dead') {
613
623
  return types_1.InstanceStatus.STOPPED;
614
624
  }
615
- if (state.Status === 'removing') {
625
+ if (statusType === 'removing') {
616
626
  return types_1.InstanceStatus.BUSY;
617
627
  }
618
- if (state.Status === 'restarting') {
628
+ if (statusType === 'restarting') {
619
629
  return types_1.InstanceStatus.BUSY;
620
630
  }
621
- if (state.Status === 'paused') {
631
+ if (statusType === 'paused') {
622
632
  return types_1.InstanceStatus.BUSY;
623
633
  }
624
634
  return types_1.InstanceStatus.STOPPED;