@kapeta/local-cluster-service 0.37.0 → 0.39.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.
Files changed (69) hide show
  1. package/CHANGELOG.md +14 -0
  2. package/dist/cjs/index.js +4 -1
  3. package/dist/cjs/src/assetManager.d.ts +2 -1
  4. package/dist/cjs/src/assetManager.js +3 -2
  5. package/dist/cjs/src/config/routes.js +2 -2
  6. package/dist/cjs/src/containerManager.d.ts +6 -3
  7. package/dist/cjs/src/containerManager.js +101 -21
  8. package/dist/cjs/src/instanceManager.d.ts +4 -2
  9. package/dist/cjs/src/instanceManager.js +71 -32
  10. package/dist/cjs/src/operatorManager.d.ts +6 -3
  11. package/dist/cjs/src/operatorManager.js +32 -23
  12. package/dist/cjs/src/progressListener.d.ts +8 -1
  13. package/dist/cjs/src/progressListener.js +12 -1
  14. package/dist/cjs/src/repositoryManager.js +3 -2
  15. package/dist/cjs/src/serviceManager.d.ts +0 -1
  16. package/dist/cjs/src/serviceManager.js +2 -8
  17. package/dist/cjs/src/taskManager.d.ts +2 -0
  18. package/dist/cjs/src/taskManager.js +9 -0
  19. package/dist/cjs/src/types.d.ts +1 -48
  20. package/dist/cjs/src/types.js +2 -1
  21. package/dist/cjs/src/utils/BlockInstanceRunner.js +45 -33
  22. package/dist/cjs/src/utils/InternalConfigProvider.d.ts +38 -0
  23. package/dist/cjs/src/utils/InternalConfigProvider.js +146 -0
  24. package/dist/cjs/src/utils/commandLineUtils.d.ts +2 -1
  25. package/dist/cjs/src/utils/commandLineUtils.js +7 -1
  26. package/dist/cjs/src/utils/utils.d.ts +26 -4
  27. package/dist/cjs/src/utils/utils.js +48 -8
  28. package/dist/esm/index.js +4 -1
  29. package/dist/esm/src/assetManager.d.ts +2 -1
  30. package/dist/esm/src/assetManager.js +3 -2
  31. package/dist/esm/src/config/routes.js +2 -2
  32. package/dist/esm/src/containerManager.d.ts +6 -3
  33. package/dist/esm/src/containerManager.js +101 -21
  34. package/dist/esm/src/instanceManager.d.ts +4 -2
  35. package/dist/esm/src/instanceManager.js +71 -32
  36. package/dist/esm/src/operatorManager.d.ts +6 -3
  37. package/dist/esm/src/operatorManager.js +32 -23
  38. package/dist/esm/src/progressListener.d.ts +8 -1
  39. package/dist/esm/src/progressListener.js +12 -1
  40. package/dist/esm/src/repositoryManager.js +3 -2
  41. package/dist/esm/src/serviceManager.d.ts +0 -1
  42. package/dist/esm/src/serviceManager.js +2 -8
  43. package/dist/esm/src/taskManager.d.ts +2 -0
  44. package/dist/esm/src/taskManager.js +9 -0
  45. package/dist/esm/src/types.d.ts +1 -48
  46. package/dist/esm/src/types.js +2 -1
  47. package/dist/esm/src/utils/BlockInstanceRunner.js +45 -33
  48. package/dist/esm/src/utils/InternalConfigProvider.d.ts +38 -0
  49. package/dist/esm/src/utils/InternalConfigProvider.js +146 -0
  50. package/dist/esm/src/utils/commandLineUtils.d.ts +2 -1
  51. package/dist/esm/src/utils/commandLineUtils.js +7 -1
  52. package/dist/esm/src/utils/utils.d.ts +26 -4
  53. package/dist/esm/src/utils/utils.js +48 -8
  54. package/index.ts +5 -2
  55. package/package.json +16 -14
  56. package/src/assetManager.ts +5 -4
  57. package/src/config/routes.ts +4 -2
  58. package/src/containerManager.ts +115 -26
  59. package/src/instanceManager.ts +86 -44
  60. package/src/operatorManager.ts +48 -40
  61. package/src/progressListener.ts +15 -1
  62. package/src/repositoryManager.ts +5 -3
  63. package/src/serviceManager.ts +3 -11
  64. package/src/taskManager.ts +11 -0
  65. package/src/types.ts +2 -50
  66. package/src/utils/BlockInstanceRunner.ts +60 -44
  67. package/src/utils/InternalConfigProvider.ts +214 -0
  68. package/src/utils/commandLineUtils.ts +10 -2
  69. package/src/utils/utils.ts +53 -10
@@ -7,16 +7,19 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
7
7
  return (mod && mod.__esModule) ? mod : { "default": mod };
8
8
  };
9
9
  Object.defineProperty(exports, "__esModule", { value: true });
10
- exports.getResolvedConfiguration = exports.getBindHost = exports.isLinux = exports.isMac = exports.isWindows = exports.readYML = exports.getRemoteUrl = exports.toPortInfo = exports.getBlockInstanceContainerName = void 0;
10
+ exports.getResolvedConfiguration = exports.isLinux = exports.isMac = exports.isWindows = exports.readYML = exports.getRemoteUrl = exports.getDockerHostIp = exports.getBindAddressForEnvironment = exports.getRemoteHostForEnvironment = exports.getOperatorInstancePorts = exports.toPortInfo = exports.getBlockInstanceContainerName = void 0;
11
11
  const node_fs_1 = __importDefault(require("node:fs"));
12
12
  const yaml_1 = __importDefault(require("yaml"));
13
13
  const md5_1 = __importDefault(require("md5"));
14
+ const schemas_1 = require("@kapeta/schemas");
14
15
  const lodash_1 = __importDefault(require("lodash"));
15
16
  const types_1 = require("../types");
16
17
  const local_cluster_config_1 = __importDefault(require("@kapeta/local-cluster-config"));
17
18
  const definitionsManager_1 = require("../definitionsManager");
18
19
  const nodejs_utils_1 = require("@kapeta/nodejs-utils");
19
20
  const assetManager_1 = require("../assetManager");
21
+ const serviceManager_1 = require("../serviceManager");
22
+ const clusterService_1 = require("../clusterService");
20
23
  async function getBlockInstanceContainerName(systemId, instanceId, blockType) {
21
24
  if (!blockType) {
22
25
  const instance = await assetManager_1.assetManager.getBlockInstance(systemId, instanceId);
@@ -45,11 +48,54 @@ function toPortInfo(port) {
45
48
  return { port: parseInt(`${port}`), type: 'tcp' };
46
49
  }
47
50
  if (!port.type) {
48
- port.type = 'tcp';
51
+ port.type = schemas_1.LocalInstancePortType.TCP;
49
52
  }
50
53
  return port;
51
54
  }
52
55
  exports.toPortInfo = toPortInfo;
56
+ async function getOperatorInstancePorts(systemId, operatorId, local) {
57
+ const localPorts = local.ports ?? {};
58
+ const promises = Object.entries(localPorts).map(async ([portType, value]) => {
59
+ const portInfo = toPortInfo(value);
60
+ const hostPort = await serviceManager_1.serviceManager.ensureServicePort(systemId, operatorId, portType);
61
+ return {
62
+ portType,
63
+ port: portInfo.port,
64
+ hostPort,
65
+ protocol: portInfo.type,
66
+ };
67
+ });
68
+ return await Promise.all(promises);
69
+ }
70
+ exports.getOperatorInstancePorts = getOperatorInstancePorts;
71
+ /**
72
+ * Gets the hostname where all services are available - including the cluster service.
73
+ *
74
+ * For docker this is the internal docker host - otherwise it's the local machine
75
+ * Assumed to be the same address as the cluster service outside docker.
76
+ */
77
+ function getRemoteHostForEnvironment(environment) {
78
+ return environment === 'docker' ? types_1.DOCKER_HOST_INTERNAL : clusterService_1.clusterService.getClusterServiceHost();
79
+ }
80
+ exports.getRemoteHostForEnvironment = getRemoteHostForEnvironment;
81
+ /**
82
+ * Get the bind address for the given environment.
83
+ *
84
+ * Outside of docker we bind to 127.0.0.1 - inside we bind to everything (0.0.0.0)
85
+ */
86
+ function getBindAddressForEnvironment(environment, preferredHost = '127.0.0.1') {
87
+ return environment === 'docker' ? '0.0.0.0' : preferredHost;
88
+ }
89
+ exports.getBindAddressForEnvironment = getBindAddressForEnvironment;
90
+ /**
91
+ * Get the docker host IP address for port binding.
92
+ */
93
+ function getDockerHostIp(preferredHost = '127.0.0.1') {
94
+ // On Linux we need to bind to 0.0.0.0 to be able to connect to it from docker containers.
95
+ // TODO: This might pose a security risk - so we should authenticate all requests using a shared secret/nonce that we pass around.
96
+ return isLinux() ? '0.0.0.0' : preferredHost;
97
+ }
98
+ exports.getDockerHostIp = getDockerHostIp;
53
99
  function getRemoteUrl(id, defautValue) {
54
100
  const remoteConfig = local_cluster_config_1.default.getClusterConfig().remote;
55
101
  return remoteConfig?.[id] ?? defautValue;
@@ -77,12 +123,6 @@ function isLinux() {
77
123
  return !isWindows() && !isMac();
78
124
  }
79
125
  exports.isLinux = isLinux;
80
- function getBindHost(preferredHost = '127.0.0.1') {
81
- // On Linux we need to bind to 0.0.0.0 to be able to connect to it from docker containers.
82
- // TODO: This might pose a security risk - so we should authenticate all requests using a shared secret/nonce that we pass around.
83
- return isLinux() ? '0.0.0.0' : preferredHost;
84
- }
85
- exports.getBindHost = getBindHost;
86
126
  function getResolvedConfiguration(entities, config, globalConfiguration) {
87
127
  if (!entities || !globalConfiguration) {
88
128
  return config || {};
package/dist/esm/index.js CHANGED
@@ -210,7 +210,10 @@ exports.default = {
210
210
  }
211
211
  reject(err);
212
212
  });
213
- const bindHost = (0, utils_1.getBindHost)(host);
213
+ // On Linux we need to bind to 0.0.0.0 to be able to connect to it from docker containers.
214
+ // TODO: This might pose a security risk - so we should authenticate all requests using a
215
+ // shared secret/nonce that we pass around.
216
+ const bindHost = (0, utils_1.isLinux)() ? '0.0.0.0' : host;
214
217
  currentServer.listen(port, bindHost, async () => {
215
218
  try {
216
219
  const ensureCLITask = await (0, commandLineUtils_1.ensureCLI)();
@@ -4,6 +4,7 @@
4
4
  */
5
5
  import { Definition } from '@kapeta/local-cluster-config';
6
6
  import { BlockDefinition, BlockInstance, Plan } from '@kapeta/schemas';
7
+ import { Task } from './taskManager';
7
8
  import { SourceOfChange } from './types';
8
9
  export interface EnrichedAsset {
9
10
  ref: string;
@@ -31,7 +32,7 @@ declare class AssetManager {
31
32
  updateAsset(ref: string, yaml: Definition, sourceOfChange?: SourceOfChange): Promise<void>;
32
33
  importFile(filePath: string): Promise<EnrichedAsset[]>;
33
34
  unregisterAsset(ref: string): Promise<void>;
34
- installAsset(ref: string, wait?: boolean): Promise<import("./taskManager").Task<void>[] | undefined>;
35
+ installAsset(ref: string, wait?: boolean): Promise<Task<void>[] | undefined>;
35
36
  private cleanupUnusedProviders;
36
37
  private upgradeAllProviders;
37
38
  private maybeGenerateCode;
@@ -233,11 +233,12 @@ class AssetManager {
233
233
  return;
234
234
  }
235
235
  console.log('Installing updates', refs);
236
- const updateAll = async () => {
236
+ const updateAll = async (task) => {
237
+ const progressListener = new progressListener_1.TaskProgressListener(task);
237
238
  try {
238
239
  //We change to a temp dir to avoid issues with the current working directory
239
240
  process.chdir(node_os_1.default.tmpdir());
240
- await nodejs_registry_utils_1.Actions.install(new progressListener_1.ProgressListener(), refs, {});
241
+ await nodejs_registry_utils_1.Actions.install(progressListener, refs, {});
241
242
  await this.cleanupUnusedProviders();
242
243
  }
243
244
  catch (e) {
@@ -136,7 +136,7 @@ router.get('/provides/:type', async (req, res) => {
136
136
  * assign port numbers to it etc.
137
137
  */
138
138
  router.get('/consumes/resource/:resourceType/:portType/:name', async (req, res) => {
139
- const operatorInfo = await operatorManager_1.operatorManager.getConsumerResourceInfo(req.kapeta.systemId, req.kapeta.instanceId, req.params.resourceType, req.params.portType, req.params.name, req.kapeta.environment);
139
+ const operatorInfo = await operatorManager_1.operatorManager.getConsumerResourceInfo(req.kapeta.systemId, req.kapeta.instanceId, req.params.resourceType, req.params.portType, req.params.name, req.kapeta.environment, req.query.ensure !== 'false');
140
140
  res.send(operatorInfo);
141
141
  });
142
142
  /**
@@ -154,7 +154,7 @@ router.get('/consumes/:resourceName/:type', (req, res) => {
154
154
  * If the remote service is not already running it will be started
155
155
  */
156
156
  router.get('/operator/:instanceId', async (req, res) => {
157
- const operatorInfo = await instanceManager_1.instanceManager.getInstanceOperator(req.kapeta.systemId, req.params.instanceId, req.kapeta.environment);
157
+ const operatorInfo = await instanceManager_1.instanceManager.getInstanceOperator(req.kapeta.systemId, req.params.instanceId, req.kapeta.environment, req.query.ensure !== 'false');
158
158
  res.send(operatorInfo);
159
159
  });
160
160
  exports.default = router;
@@ -5,7 +5,9 @@
5
5
  /// <reference types="node" />
6
6
  import FSExtra from 'fs-extra';
7
7
  import Docker from 'dockerode';
8
- import { Health, InstanceInfo, LogEntry } from './types';
8
+ import { InstanceInfo, LogEntry } from './types';
9
+ import { Task } from './taskManager';
10
+ import { LocalInstanceHealth } from '@kapeta/schemas';
9
11
  type StringMap = {
10
12
  [key: string]: string;
11
13
  };
@@ -50,11 +52,11 @@ declare class ContainerManager {
50
52
  getContainerByName(containerName: string): Promise<ContainerInfo | undefined>;
51
53
  pull(image: string): Promise<boolean>;
52
54
  toDockerMounts(mounts: StringMap): DockerMounts[];
53
- toDockerHealth(health: Health): {
55
+ toDockerHealth(health: LocalInstanceHealth): {
54
56
  Test: string[];
55
57
  Interval: number;
56
58
  Timeout: number;
57
- Retries: number;
59
+ Retries: any;
58
60
  };
59
61
  private applyHash;
60
62
  ensureContainer(opts: any): Promise<Docker.Container>;
@@ -74,6 +76,7 @@ declare class ContainerManager {
74
76
  getLogs(instance: InstanceInfo): Promise<LogEntry[]>;
75
77
  stopLogListening(systemId: string, instanceId: string): Promise<void>;
76
78
  ensureLogListening(systemId: string, instanceId: string, handler: (log: LogEntry) => void): Promise<void>;
79
+ buildDockerImage(dockerFile: string, imageName: string): Task<void>;
77
80
  }
78
81
  declare class ClosableLogStream {
79
82
  private readonly stream;
@@ -19,6 +19,7 @@ const local_cluster_config_1 = __importDefault(require("@kapeta/local-cluster-co
19
19
  const node_uuid_1 = __importDefault(require("node-uuid"));
20
20
  const md5_1 = __importDefault(require("md5"));
21
21
  const utils_1 = require("./utils/utils");
22
+ const types_1 = require("./types");
22
23
  const nodejs_api_client_1 = require("@kapeta/nodejs-api-client");
23
24
  const taskManager_1 = require("./taskManager");
24
25
  const node_events_1 = require("node:events");
@@ -207,14 +208,8 @@ class ContainerManager {
207
208
  return this._docker;
208
209
  }
209
210
  async getContainerByName(containerName) {
210
- const containers = await this.docker().listContainers({ all: true });
211
- const out = containers.find((container) => {
212
- return container.Names.indexOf(`/${containerName}`) > -1;
213
- });
214
- if (out) {
215
- return this.get(out.Id);
216
- }
217
- return undefined;
211
+ // The container can be fetched by name or by id using the same API call
212
+ return this.get(containerName);
218
213
  }
219
214
  async pull(image) {
220
215
  let [imageName, tag] = image.split(/:/);
@@ -279,6 +274,10 @@ class ContainerManager {
279
274
  };
280
275
  }
281
276
  const chunk = chunks[data.id];
277
+ if (data.stream) {
278
+ // Emit raw output to the task log
279
+ task.addLog(data.stream);
280
+ }
282
281
  switch (data.status) {
283
282
  case DockerPullEventTypes.PreparingPhase:
284
283
  case DockerPullEventTypes.WaitingPhase:
@@ -510,7 +509,8 @@ class ContainerManager {
510
509
  const newName = 'deleting-' + node_uuid_1.default.v4();
511
510
  // Rename the container first to avoid name conflicts if people start the same container
512
511
  await container.rename({ name: newName });
513
- await container.remove({ force: !!opts?.force });
512
+ const newContainer = this.docker().getContainer(newName);
513
+ await newContainer.remove({ force: !!opts?.force });
514
514
  }
515
515
  /**
516
516
  *
@@ -520,7 +520,7 @@ class ContainerManager {
520
520
  async get(name) {
521
521
  let dockerContainer = null;
522
522
  try {
523
- dockerContainer = await this.docker().getContainer(name);
523
+ dockerContainer = this.docker().getContainer(name);
524
524
  await dockerContainer.stats();
525
525
  }
526
526
  catch (err) {
@@ -545,7 +545,7 @@ class ContainerManager {
545
545
  },
546
546
  ];
547
547
  }
548
- return containerInfo.getLogs();
548
+ return await containerInfo.getLogs();
549
549
  }
550
550
  async stopLogListening(systemId, instanceId) {
551
551
  const containerName = await (0, utils_1.getBlockInstanceContainerName)(systemId, instanceId);
@@ -616,6 +616,28 @@ class ContainerManager {
616
616
  // Ignore
617
617
  }
618
618
  }
619
+ buildDockerImage(dockerFile, imageName) {
620
+ const taskName = `Building docker image: ${imageName}`;
621
+ const processor = async (task) => {
622
+ const timeStarted = Date.now();
623
+ const stream = await this.docker().buildImage({
624
+ context: path_1.default.dirname(dockerFile),
625
+ src: [path_1.default.basename(dockerFile)],
626
+ }, {
627
+ t: imageName,
628
+ dockerfile: path_1.default.basename(dockerFile),
629
+ });
630
+ await processJsonStream(`image:build:${imageName}`, stream, (data) => {
631
+ if (data.stream) {
632
+ // Emit raw output to the task log
633
+ task.addLog(data.stream);
634
+ }
635
+ });
636
+ };
637
+ return taskManager_1.taskManager.add(`docker:image:build:${imageName}`, processor, {
638
+ name: taskName,
639
+ });
640
+ }
619
641
  }
620
642
  function readLogBuffer(logBuffer) {
621
643
  const out = [];
@@ -830,15 +852,73 @@ class ContainerInfo {
830
852
  timestamps: true,
831
853
  });
832
854
  const out = readLogBuffer(logs);
833
- if (out.length === 0) {
834
- out.push({
835
- time: Date.now(),
836
- message: 'No logs found for container',
837
- level: 'INFO',
838
- source: 'stdout',
839
- });
855
+ if (out.length > 0) {
856
+ return out;
840
857
  }
841
- return out;
858
+ const status = await this.status();
859
+ const healthLogs = status?.Health?.Log
860
+ ? status?.Health?.Log.map((log) => {
861
+ return {
862
+ source: 'stdout',
863
+ level: log.ExitCode === 0 ? 'INFO' : 'ERROR',
864
+ time: Date.now(),
865
+ message: 'Health check: ' + log.Output,
866
+ };
867
+ })
868
+ : [];
869
+ if (status?.Running) {
870
+ return [
871
+ {
872
+ source: 'stdout',
873
+ level: 'INFO',
874
+ time: Date.now(),
875
+ message: 'Container is starting...',
876
+ },
877
+ ...healthLogs,
878
+ ];
879
+ }
880
+ if (status?.Restarting) {
881
+ return [
882
+ {
883
+ source: 'stdout',
884
+ level: 'INFO',
885
+ time: Date.now(),
886
+ message: 'Container is restarting...',
887
+ },
888
+ ...healthLogs,
889
+ ];
890
+ }
891
+ if (status?.Paused) {
892
+ return [
893
+ {
894
+ source: 'stdout',
895
+ level: 'INFO',
896
+ time: Date.now(),
897
+ message: 'Container is paused...',
898
+ },
899
+ ...healthLogs,
900
+ ];
901
+ }
902
+ if (status?.Error) {
903
+ return [
904
+ {
905
+ source: 'stderr',
906
+ level: 'ERROR',
907
+ time: Date.now(),
908
+ message: 'Container failed to start:\n' + status.Error,
909
+ },
910
+ ...healthLogs,
911
+ ];
912
+ }
913
+ return [
914
+ {
915
+ source: 'stdout',
916
+ level: 'INFO',
917
+ time: Date.now(),
918
+ message: 'Container not running',
919
+ ...healthLogs,
920
+ },
921
+ ];
842
922
  }
843
923
  }
844
924
  exports.ContainerInfo = ContainerInfo;
@@ -847,11 +927,11 @@ function getExtraHosts(dockerVersion) {
847
927
  const [major, minor] = dockerVersion.split('.');
848
928
  if (parseInt(major) >= 20 && parseInt(minor) >= 10) {
849
929
  // Docker 20.10+ on Linux supports adding host.docker.internal to point to host-gateway
850
- return ['host.docker.internal:host-gateway'];
930
+ return [`${types_1.DOCKER_HOST_INTERNAL}:host-gateway`];
851
931
  }
852
932
  // Docker versions lower than 20.10 needs an actual IP address. We use the default network bridge which
853
933
  // is always 172.17.0.1
854
- return ['host.docker.internal:172.17.0.1'];
934
+ return [`${types_1.DOCKER_HOST_INTERNAL}:172.17.0.1`];
855
935
  }
856
936
  return undefined;
857
937
  }
@@ -2,8 +2,9 @@
2
2
  * Copyright 2023 Kapeta Inc.
3
3
  * SPDX-License-Identifier: BUSL-1.1
4
4
  */
5
- import { EnvironmentType, InstanceInfo, LogEntry, OperatorInstanceInfo } from './types';
5
+ import { EnvironmentType, InstanceInfo, LogEntry } from './types';
6
6
  import { Task } from './taskManager';
7
+ import { InstanceOperator } from '@kapeta/sdk-config';
7
8
  export declare class InstanceManager {
8
9
  private _interval;
9
10
  private readonly _instances;
@@ -14,6 +15,7 @@ export declare class InstanceManager {
14
15
  getInstancesForPlan(systemId: string): Promise<InstanceInfo[]>;
15
16
  getInstance(systemId: string, instanceId: string): InstanceInfo | undefined;
16
17
  private exclusive;
18
+ private isLocked;
17
19
  getLogs(systemId: string, instanceId: string): Promise<LogEntry[]>;
18
20
  saveInternalInstance(instance: InstanceInfo): Promise<InstanceInfo>;
19
21
  /**
@@ -25,7 +27,7 @@ export declare class InstanceManager {
25
27
  markAsStopped(systemId: string, instanceId: string): Promise<void>;
26
28
  startAllForPlan(systemId: string): Promise<Task<InstanceInfo[]>>;
27
29
  stopAllForPlan(systemId: string): Task<void>;
28
- getInstanceOperator(systemId: string, instanceId: string, environment?: EnvironmentType): Promise<OperatorInstanceInfo>;
30
+ getInstanceOperator(systemId: string, instanceId: string, environment?: EnvironmentType, ensureContainer?: boolean): Promise<InstanceOperator<any, any>>;
29
31
  stop(systemId: string, instanceId: string): Promise<void>;
30
32
  private stopInner;
31
33
  start(systemId: string, instanceId: string, checkForSingleton?: boolean): Promise<InstanceInfo | Task<InstanceInfo>>;
@@ -79,6 +79,9 @@ class InstanceManager {
79
79
  //console.log(`Releasing lock for ${key}`, this.instanceLocks.isBusy(key));
80
80
  return result;
81
81
  }
82
+ isLocked(systemId, instanceId) {
83
+ return this.instanceLocks.isBusy(`${systemId}/${instanceId}`);
84
+ }
82
85
  async getLogs(systemId, instanceId) {
83
86
  const instance = this.getInstance(systemId, instanceId);
84
87
  if (!instance) {
@@ -212,7 +215,9 @@ class InstanceManager {
212
215
  systemId = (0, nodejs_utils_1.normalizeKapetaUri)(systemId);
213
216
  const instance = lodash_1.default.find(this._instances, { systemId, instanceId });
214
217
  if (instance && instance.owner === types_1.InstanceOwner.EXTERNAL && instance.status !== types_1.InstanceStatus.STOPPED) {
215
- instance.status = types_1.InstanceStatus.STOPPED;
218
+ if (instance.status != types_1.InstanceStatus.FAILED) {
219
+ instance.status = types_1.InstanceStatus.STOPPED;
220
+ }
216
221
  instance.pid = null;
217
222
  instance.health = null;
218
223
  socketManager_1.socketManager.emitSystemEvent(systemId, socketManager_1.EVENT_STATUS_CHANGED, instance);
@@ -266,7 +271,7 @@ class InstanceManager {
266
271
  name: `Stopping plan ${systemId}`,
267
272
  });
268
273
  }
269
- async getInstanceOperator(systemId, instanceId, environment) {
274
+ async getInstanceOperator(systemId, instanceId, environment, ensureContainer = true) {
270
275
  const blockInstance = await assetManager_1.assetManager.getBlockInstance(systemId, instanceId);
271
276
  if (!blockInstance) {
272
277
  throw new Error(`Instance not found: ${systemId}/${instanceId}`);
@@ -277,30 +282,48 @@ class InstanceManager {
277
282
  throw new Error(`Block not found: ${blockRef}`);
278
283
  }
279
284
  const operatorDefinition = await definitionsManager_1.definitionsManager.getDefinition(block.kind);
280
- if (!operatorDefinition?.definition.spec.local) {
285
+ if (!operatorDefinition) {
286
+ throw new Error(`Operator not found: ${block.kind}`);
287
+ }
288
+ if (operatorDefinition.definition.kind !== types_1.KIND_BLOCK_TYPE_OPERATOR) {
289
+ throw new Error(`Block is not an operator: ${blockRef}`);
290
+ }
291
+ if (!operatorDefinition.definition.spec.local) {
281
292
  throw new Error(`Operator block has no local definition: ${blockRef}`);
282
293
  }
283
294
  const localConfig = operatorDefinition.definition.spec.local;
284
- let instance = await this.start(systemId, instanceId);
285
- if (instance instanceof taskManager_1.Task) {
286
- instance = await instance.wait();
287
- }
288
- const container = await containerManager_1.containerManager.get(instance.pid);
289
- if (!container) {
290
- throw new Error(`Container not found: ${instance.pid}`);
295
+ const ports = {};
296
+ if (ensureContainer) {
297
+ let instance = await this.start(systemId, instanceId);
298
+ if (instance instanceof taskManager_1.Task) {
299
+ instance = await instance.wait();
300
+ }
301
+ const container = await containerManager_1.containerManager.get(instance.pid);
302
+ if (!container) {
303
+ throw new Error(`Container not found: ${instance.pid}`);
304
+ }
305
+ const portInfo = await container.getPorts();
306
+ if (!portInfo) {
307
+ throw new Error(`No ports found for instance: ${instanceId}`);
308
+ }
309
+ Object.entries(portInfo).forEach(([key, value]) => {
310
+ ports[key] = {
311
+ protocol: value.protocol,
312
+ port: parseInt(value.hostPort),
313
+ };
314
+ });
291
315
  }
292
- const portInfo = await container.getPorts();
293
- if (!portInfo) {
294
- throw new Error(`No ports found for instance: ${instanceId}`);
316
+ else {
317
+ // If we're not ensuring the container is running we just get the ports from the local config
318
+ const instancePorts = await (0, utils_1.getOperatorInstancePorts)(systemId, instanceId, localConfig);
319
+ instancePorts.forEach((port) => {
320
+ ports[port.portType] = {
321
+ protocol: port.protocol,
322
+ port: port.hostPort,
323
+ };
324
+ });
295
325
  }
296
- const hostname = serviceManager_1.serviceManager.getLocalHost(environment);
297
- const ports = {};
298
- Object.entries(portInfo).forEach(([key, value]) => {
299
- ports[key] = {
300
- protocol: value.protocol,
301
- port: parseInt(value.hostPort),
302
- };
303
- });
326
+ const hostname = (0, utils_1.getRemoteHostForEnvironment)(environment);
304
327
  return {
305
328
  hostname,
306
329
  ports,
@@ -345,6 +368,7 @@ class InstanceManager {
345
368
  if (changeDesired && instance.desiredStatus !== types_1.DesiredInstanceStatus.EXTERNAL) {
346
369
  instance.desiredStatus = types_1.DesiredInstanceStatus.STOP;
347
370
  }
371
+ const wasFailed = instance.status === types_1.InstanceStatus.FAILED;
348
372
  instance.status = types_1.InstanceStatus.STOPPING;
349
373
  socketManager_1.socketManager.emitSystemEvent(systemId, socketManager_1.EVENT_STATUS_CHANGED, instance);
350
374
  console.log('Stopping instance: %s::%s [desired: %s] [intentional: %s]', systemId, instanceId, instance.desiredStatus, changeDesired);
@@ -355,7 +379,12 @@ class InstanceManager {
355
379
  const container = await containerManager_1.containerManager.getContainerByName(containerName);
356
380
  if (container) {
357
381
  try {
358
- await container.stop();
382
+ if (wasFailed) {
383
+ await container.remove();
384
+ }
385
+ else {
386
+ await container.stop();
387
+ }
359
388
  instance.status = types_1.InstanceStatus.STOPPED;
360
389
  socketManager_1.socketManager.emitSystemEvent(systemId, socketManager_1.EVENT_STATUS_CHANGED, instance);
361
390
  this.save();
@@ -411,7 +440,7 @@ class InstanceManager {
411
440
  existingInstance = undefined;
412
441
  }
413
442
  }
414
- if (existingInstance?.pid) {
443
+ if (existingInstance && existingInstance.pid) {
415
444
  if (existingInstance.status === types_1.InstanceStatus.READY) {
416
445
  // Instance is already running
417
446
  return existingInstance;
@@ -472,8 +501,7 @@ class InstanceManager {
472
501
  return existingInstance;
473
502
  }
474
503
  }
475
- const instanceConfig = await configManager_1.configManager.getConfigForSection(systemId, instanceId);
476
- const resolvedConfig = (0, utils_1.getResolvedConfiguration)(blockSpec.configuration, instanceConfig, blockInstance.defaultConfiguration);
504
+ const resolvedConfig = await configManager_1.configManager.getConfigForBlockInstance(systemId, instanceId);
477
505
  const task = taskManager_1.taskManager.add(`instance:start:${systemId}:${instanceId}`, async () => {
478
506
  const runner = new BlockInstanceRunner_1.BlockInstanceRunner(systemId);
479
507
  const startTime = Date.now();
@@ -501,8 +529,7 @@ class InstanceManager {
501
529
  ];
502
530
  const out = await this.saveInternalInstance({
503
531
  ...instance,
504
- type: types_1.InstanceType.UNKNOWN,
505
- pid: null,
532
+ type: types_1.InstanceType.DOCKER,
506
533
  health: null,
507
534
  portType: DEFAULT_HEALTH_PORT_TYPE,
508
535
  status: types_1.InstanceStatus.FAILED,
@@ -622,9 +649,8 @@ class InstanceManager {
622
649
  }
623
650
  if (instance.status !== newStatus) {
624
651
  const oldStatus = instance.status;
625
- const skipUpdate = (newStatus === types_1.InstanceStatus.STOPPED && instance.status === types_1.InstanceStatus.FAILED) ||
626
- ([types_1.InstanceStatus.READY, types_1.InstanceStatus.UNHEALTHY].includes(newStatus) &&
627
- instance.status === types_1.InstanceStatus.STOPPING) ||
652
+ const skipUpdate = ([types_1.InstanceStatus.READY, types_1.InstanceStatus.UNHEALTHY].includes(newStatus) &&
653
+ instance.status === types_1.InstanceStatus.STOPPING) ||
628
654
  (newStatus === types_1.InstanceStatus.STOPPED &&
629
655
  instance.status === types_1.InstanceStatus.STARTING &&
630
656
  instance.desiredStatus === types_1.DesiredInstanceStatus.RUN);
@@ -637,7 +663,7 @@ class InstanceManager {
637
663
  }
638
664
  }
639
665
  if (instance.desiredStatus === types_1.DesiredInstanceStatus.RUN &&
640
- [types_1.InstanceStatus.STOPPED, types_1.InstanceStatus.FAILED, types_1.InstanceStatus.STOPPING].includes(newStatus)) {
666
+ [types_1.InstanceStatus.STOPPED, types_1.InstanceStatus.STOPPING].includes(newStatus)) {
641
667
  //If the instance is stopped but we want it to run, start it
642
668
  try {
643
669
  await this.start(instance.systemId, instance.instanceId);
@@ -708,10 +734,23 @@ class InstanceManager {
708
734
  return types_1.InstanceStatus.READY;
709
735
  }
710
736
  if (statusType === 'created') {
737
+ if (state.ExitCode !== undefined && state.ExitCode !== 0) {
738
+ // Failed during creation. Exit code is not always reliable though
739
+ if (state.Error) {
740
+ return types_1.InstanceStatus.FAILED;
741
+ }
742
+ else {
743
+ return types_1.InstanceStatus.STOPPED;
744
+ }
745
+ }
711
746
  return types_1.InstanceStatus.STARTING;
712
747
  }
713
748
  if (statusType === 'exited' || statusType === 'dead') {
714
- return types_1.InstanceStatus.STOPPED;
749
+ if (!state.Error) {
750
+ // Exit code is not always reliable - if there is no error we assume it's stopped
751
+ return types_1.InstanceStatus.STOPPED;
752
+ }
753
+ return types_1.InstanceStatus.FAILED;
715
754
  }
716
755
  if (statusType === 'removing') {
717
756
  return types_1.InstanceStatus.BUSY;
@@ -4,11 +4,13 @@
4
4
  */
5
5
  import { DefinitionInfo } from '@kapeta/local-cluster-config';
6
6
  import { ContainerInfo } from './containerManager';
7
- import { EnvironmentType, LocalImageOptions, OperatorInfo } from './types';
7
+ import { AnyMap, EnvironmentType } from './types';
8
+ import { LocalInstance } from '@kapeta/schemas';
9
+ import { ResourceInfo } from '@kapeta/sdk-config';
8
10
  declare class Operator {
9
11
  private readonly _data;
10
12
  constructor(data: DefinitionInfo);
11
- getLocalData(): LocalImageOptions;
13
+ getLocalData(): LocalInstance;
12
14
  getDefinitionInfo(): DefinitionInfo;
13
15
  getCredentials(): any;
14
16
  }
@@ -24,7 +26,8 @@ declare class OperatorManager {
24
26
  /**
25
27
  * Get information about a specific consumed resource
26
28
  */
27
- getConsumerResourceInfo(systemId: string, fromServiceId: string, resourceType: string, portType: string, name: string, environment?: EnvironmentType): Promise<OperatorInfo>;
29
+ getConsumerResourceInfo(systemId: string, fromServiceId: string, resourceType: string, portType: string, name: string, environment?: EnvironmentType, ensureContainer?: boolean): Promise<ResourceInfo<any, any>>;
30
+ getOperatorPorts(systemId: string, kind: string, version: string): Promise<AnyMap>;
28
31
  /**
29
32
  * Ensure we have a running operator of given type
30
33
  *