@kapeta/local-cluster-service 0.36.1 → 0.38.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 (54) hide show
  1. package/.vscode/launch.json +9 -5
  2. package/CHANGELOG.md +21 -0
  3. package/dist/cjs/src/assetManager.d.ts +2 -1
  4. package/dist/cjs/src/assetManager.js +7 -4
  5. package/dist/cjs/src/containerManager.d.ts +6 -3
  6. package/dist/cjs/src/containerManager.js +96 -18
  7. package/dist/cjs/src/instanceManager.d.ts +16 -0
  8. package/dist/cjs/src/instanceManager.js +78 -14
  9. package/dist/cjs/src/operatorManager.d.ts +3 -4
  10. package/dist/cjs/src/operatorManager.js +3 -4
  11. package/dist/cjs/src/progressListener.d.ts +8 -1
  12. package/dist/cjs/src/progressListener.js +12 -1
  13. package/dist/cjs/src/repositoryManager.js +3 -2
  14. package/dist/cjs/src/taskManager.d.ts +2 -0
  15. package/dist/cjs/src/taskManager.js +9 -0
  16. package/dist/cjs/src/types.d.ts +4 -19
  17. package/dist/cjs/src/types.js +5 -1
  18. package/dist/cjs/src/utils/BlockInstanceRunner.js +16 -5
  19. package/dist/cjs/src/utils/commandLineUtils.d.ts +2 -1
  20. package/dist/cjs/src/utils/commandLineUtils.js +7 -1
  21. package/dist/cjs/src/utils/utils.d.ts +3 -3
  22. package/dist/cjs/src/utils/utils.js +4 -3
  23. package/dist/esm/src/assetManager.d.ts +2 -1
  24. package/dist/esm/src/assetManager.js +7 -4
  25. package/dist/esm/src/containerManager.d.ts +6 -3
  26. package/dist/esm/src/containerManager.js +96 -18
  27. package/dist/esm/src/instanceManager.d.ts +16 -0
  28. package/dist/esm/src/instanceManager.js +78 -14
  29. package/dist/esm/src/operatorManager.d.ts +3 -4
  30. package/dist/esm/src/operatorManager.js +3 -4
  31. package/dist/esm/src/progressListener.d.ts +8 -1
  32. package/dist/esm/src/progressListener.js +12 -1
  33. package/dist/esm/src/repositoryManager.js +3 -2
  34. package/dist/esm/src/taskManager.d.ts +2 -0
  35. package/dist/esm/src/taskManager.js +9 -0
  36. package/dist/esm/src/types.d.ts +4 -19
  37. package/dist/esm/src/types.js +5 -1
  38. package/dist/esm/src/utils/BlockInstanceRunner.js +16 -5
  39. package/dist/esm/src/utils/commandLineUtils.d.ts +2 -1
  40. package/dist/esm/src/utils/commandLineUtils.js +7 -1
  41. package/dist/esm/src/utils/utils.d.ts +3 -3
  42. package/dist/esm/src/utils/utils.js +4 -3
  43. package/package.json +17 -16
  44. package/src/assetManager.ts +9 -7
  45. package/src/containerManager.ts +110 -23
  46. package/src/instanceManager.ts +87 -16
  47. package/src/operatorManager.ts +11 -6
  48. package/src/progressListener.ts +15 -1
  49. package/src/repositoryManager.ts +5 -3
  50. package/src/taskManager.ts +11 -0
  51. package/src/types.ts +5 -19
  52. package/src/utils/BlockInstanceRunner.ts +21 -8
  53. package/src/utils/commandLineUtils.ts +10 -2
  54. package/src/utils/utils.ts +5 -6
@@ -7,21 +7,20 @@ 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.operatorManager = exports.KIND_BLOCK_OPERATOR = exports.KIND_RESOURCE_OPERATOR = void 0;
10
+ exports.operatorManager = void 0;
11
11
  const path_1 = __importDefault(require("path"));
12
12
  const md5_1 = __importDefault(require("md5"));
13
13
  const serviceManager_1 = require("./serviceManager");
14
14
  const storageService_1 = require("./storageService");
15
15
  const containerManager_1 = require("./containerManager");
16
16
  const fs_extra_1 = __importDefault(require("fs-extra"));
17
+ const types_1 = require("./types");
17
18
  const definitionsManager_1 = require("./definitionsManager");
18
19
  const utils_1 = require("./utils/utils");
19
20
  const nodejs_utils_1 = require("@kapeta/nodejs-utils");
20
21
  const lodash_1 = __importDefault(require("lodash"));
21
22
  const async_lock_1 = __importDefault(require("async-lock"));
22
23
  const taskManager_1 = require("./taskManager");
23
- exports.KIND_RESOURCE_OPERATOR = 'core/resource-type-operator';
24
- exports.KIND_BLOCK_OPERATOR = 'core/block-type-operator';
25
24
  const KIND_PLAN = 'core/plan';
26
25
  class Operator {
27
26
  _data;
@@ -52,7 +51,7 @@ class OperatorManager {
52
51
  * Get operator definition for resource type
53
52
  */
54
53
  async getOperator(fullName, version) {
55
- const operators = await definitionsManager_1.definitionsManager.getDefinitions([exports.KIND_RESOURCE_OPERATOR, exports.KIND_BLOCK_OPERATOR]);
54
+ const operators = await definitionsManager_1.definitionsManager.getDefinitions([types_1.KIND_RESOURCE_OPERATOR, types_1.KIND_BLOCK_TYPE_OPERATOR]);
56
55
  const operator = operators.find((operator) => operator.definition &&
57
56
  operator.definition.metadata &&
58
57
  operator.definition.metadata.name &&
@@ -3,11 +3,13 @@
3
3
  * SPDX-License-Identifier: BUSL-1.1
4
4
  */
5
5
  /// <reference types="node" />
6
+ import { LogEntry } from './types';
7
+ import { Task } from './taskManager';
6
8
  export declare class ProgressListener {
7
9
  private readonly systemId;
8
10
  private readonly instanceId;
9
11
  constructor(systemId?: string, instanceId?: string);
10
- private emitLog;
12
+ protected emitLog(payload: Omit<LogEntry, 'time' | 'source'>): void;
11
13
  run(command: string, directory?: string): Promise<{
12
14
  exit: number;
13
15
  signal: NodeJS.Signals | null;
@@ -22,3 +24,8 @@ export declare class ProgressListener {
22
24
  info(msg: string, ...args: any[]): void;
23
25
  debug(msg: string, ...args: any[]): void;
24
26
  }
27
+ export declare class TaskProgressListener extends ProgressListener {
28
+ private readonly task;
29
+ constructor(task: Task);
30
+ protected emitLog(payload: Omit<LogEntry, 'time' | 'source'>): void;
31
+ }
@@ -4,7 +4,7 @@
4
4
  * SPDX-License-Identifier: BUSL-1.1
5
5
  */
6
6
  Object.defineProperty(exports, "__esModule", { value: true });
7
- exports.ProgressListener = void 0;
7
+ exports.TaskProgressListener = exports.ProgressListener = void 0;
8
8
  const nodejs_process_1 = require("@kapeta/nodejs-process");
9
9
  const socketManager_1 = require("./socketManager");
10
10
  const node_util_1 = require("node:util");
@@ -120,3 +120,14 @@ class ProgressListener {
120
120
  }
121
121
  }
122
122
  exports.ProgressListener = ProgressListener;
123
+ class TaskProgressListener extends ProgressListener {
124
+ task;
125
+ constructor(task) {
126
+ super();
127
+ this.task = task;
128
+ }
129
+ emitLog(payload) {
130
+ this.task.addLog(payload.message, payload.level);
131
+ }
132
+ }
133
+ exports.TaskProgressListener = TaskProgressListener;
@@ -164,16 +164,17 @@ class RepositoryManager extends node_events_1.EventEmitter {
164
164
  async scheduleInstallation(refs) {
165
165
  //We make sure to only install one asset at a time - otherwise unexpected things might happen
166
166
  const createInstaller = (ref) => {
167
- return async () => {
167
+ return async (task) => {
168
168
  if (await definitionsManager_1.definitionsManager.exists(ref)) {
169
169
  return;
170
170
  }
171
+ const progressListener = new progressListener_1.TaskProgressListener(task);
171
172
  //console.log(`Installing asset: ${ref}`);
172
173
  //Auto-install missing asset
173
174
  try {
174
175
  //We change to a temp dir to avoid issues with the current working directory
175
176
  process.chdir(node_os_1.default.tmpdir());
176
- await nodejs_registry_utils_1.Actions.install(new progressListener_1.ProgressListener(), [ref], {});
177
+ await nodejs_registry_utils_1.Actions.install(progressListener, [ref], {});
177
178
  }
178
179
  catch (e) {
179
180
  console.error(`Failed to install asset: ${ref}`, e);
@@ -2,6 +2,7 @@
2
2
  * Copyright 2023 Kapeta Inc.
3
3
  * SPDX-License-Identifier: BUSL-1.1
4
4
  */
5
+ import { LogLevel } from './types';
5
6
  export type TaskRunner<T> = (task: Task<T>) => Promise<T>;
6
7
  export declare enum TaskStatus {
7
8
  PENDING = "PENDING",
@@ -44,6 +45,7 @@ export declare class Task<T = void> implements TaskData<T> {
44
45
  set errorMessage(errorMessage: string | undefined);
45
46
  set metadata(metadata: TaskMetadata);
46
47
  emitUpdate(): void;
48
+ addLog(log: string, level?: LogLevel): void;
47
49
  wait(): Promise<T>;
48
50
  toData(): {
49
51
  id: string;
@@ -12,6 +12,7 @@ const socketManager_1 = require("./socketManager");
12
12
  const EVENT_TASK_UPDATED = 'task-updated';
13
13
  const EVENT_TASK_ADDED = 'task-added';
14
14
  const EVENT_TASK_REMOVED = 'task-removed';
15
+ const EVENT_TASK_LOG = 'task-log';
15
16
  var TaskStatus;
16
17
  (function (TaskStatus) {
17
18
  TaskStatus["PENDING"] = "PENDING";
@@ -54,6 +55,14 @@ class Task {
54
55
  emitUpdate() {
55
56
  socketManager_1.socketManager.emitGlobal(EVENT_TASK_UPDATED, this.toData());
56
57
  }
58
+ addLog(log, level = 'INFO') {
59
+ socketManager_1.socketManager.emitGlobal(EVENT_TASK_LOG, {
60
+ id: this.id,
61
+ message: log,
62
+ level,
63
+ time: Date.now(),
64
+ });
65
+ }
57
66
  async wait() {
58
67
  return this.future.promise;
59
68
  }
@@ -6,6 +6,10 @@ import express from 'express';
6
6
  import { Connection, Resource } from '@kapeta/schemas';
7
7
  import { StringBodyRequest } from './middleware/stringBody';
8
8
  import { KapetaRequest } from './middleware/kapeta';
9
+ export declare const KIND_RESOURCE_OPERATOR = "core/resource-type-operator";
10
+ export declare const KIND_BLOCK_TYPE = "core/block-type";
11
+ export declare const KIND_BLOCK_TYPE_OPERATOR = "core/block-type-operator";
12
+ export declare const KIND_BLOCK_TYPE_EXECUTABLE = "core/block-type-executable";
9
13
  export type StringMap = {
10
14
  [key: string]: string;
11
15
  };
@@ -62,25 +66,6 @@ export interface Health {
62
66
  timeout?: number;
63
67
  retries?: number;
64
68
  }
65
- export type PortInfo = {
66
- port: number;
67
- type: 'tcp' | 'udp';
68
- } | number | string;
69
- export type LocalImageOptions<Credentials = AnyMap, Options = AnyMap> = {
70
- image: string;
71
- ports: {
72
- [key: string]: PortInfo;
73
- };
74
- credentials?: Credentials;
75
- options?: Options;
76
- cmd?: string;
77
- env?: AnyMap;
78
- health?: Health;
79
- singleton?: boolean;
80
- mounts?: {
81
- [key: string]: string;
82
- };
83
- };
84
69
  export type InstanceInfo = {
85
70
  systemId: string;
86
71
  instanceId: string;
@@ -4,7 +4,11 @@
4
4
  * SPDX-License-Identifier: BUSL-1.1
5
5
  */
6
6
  Object.defineProperty(exports, "__esModule", { value: true });
7
- exports.DesiredInstanceStatus = exports.InstanceStatus = exports.InstanceOwner = exports.InstanceType = void 0;
7
+ exports.DesiredInstanceStatus = exports.InstanceStatus = exports.InstanceOwner = exports.InstanceType = exports.KIND_BLOCK_TYPE_EXECUTABLE = exports.KIND_BLOCK_TYPE_OPERATOR = exports.KIND_BLOCK_TYPE = exports.KIND_RESOURCE_OPERATOR = void 0;
8
+ exports.KIND_RESOURCE_OPERATOR = 'core/resource-type-operator';
9
+ exports.KIND_BLOCK_TYPE = 'core/block-type';
10
+ exports.KIND_BLOCK_TYPE_OPERATOR = 'core/block-type-operator';
11
+ exports.KIND_BLOCK_TYPE_EXECUTABLE = 'core/block-type-executable';
8
12
  var InstanceType;
9
13
  (function (InstanceType) {
10
14
  InstanceType["DOCKER"] = "docker";
@@ -19,8 +19,8 @@ const clusterService_1 = require("../clusterService");
19
19
  const types_1 = require("../types");
20
20
  const definitionsManager_1 = require("../definitionsManager");
21
21
  const node_os_1 = __importDefault(require("node:os"));
22
+ const node_path_1 = __importDefault(require("node:path"));
22
23
  const taskManager_1 = require("../taskManager");
23
- const KIND_BLOCK_TYPE_OPERATOR = 'core/block-type-operator';
24
24
  const KAPETA_SYSTEM_ID = 'KAPETA_SYSTEM_ID';
25
25
  const KAPETA_BLOCK_REF = 'KAPETA_BLOCK_REF';
26
26
  const KAPETA_INSTANCE_ID = 'KAPETA_INSTANCE_ID';
@@ -115,7 +115,7 @@ class BlockInstanceRunner {
115
115
  throw new Error(`Kind not found: ${kindUri.id}`);
116
116
  }
117
117
  let processInfo;
118
- if (providerVersion.definition.kind === KIND_BLOCK_TYPE_OPERATOR) {
118
+ if (providerVersion.definition.kind === types_1.KIND_BLOCK_TYPE_OPERATOR) {
119
119
  processInfo = await this._startOperatorProcess(blockInstance, blockUri, providerVersion, env);
120
120
  }
121
121
  else {
@@ -145,6 +145,7 @@ class BlockInstanceRunner {
145
145
  if (!assetVersion.definition.spec?.target?.kind) {
146
146
  throw new Error('Missing target kind in block definition');
147
147
  }
148
+ const realLocalPath = await fs_extra_1.default.realpath(baseDir);
148
149
  const kindUri = (0, nodejs_utils_1.parseKapetaUri)(assetVersion.definition.kind);
149
150
  const providerVersion = await getProvider(kindUri);
150
151
  if (!providerVersion) {
@@ -159,10 +160,21 @@ class BlockInstanceRunner {
159
160
  if (!localContainer) {
160
161
  throw new Error(`Missing local container information from target: ${targetKindUri.id}`);
161
162
  }
162
- const dockerImage = localContainer.image;
163
- if (!dockerImage) {
163
+ let dockerImage = localContainer.image;
164
+ const isDockerImage = !localContainer.type || localContainer.type.toLowerCase() === 'docker';
165
+ const isDockerFile = Boolean(localContainer.type && localContainer.type.toLowerCase() === 'dockerfile');
166
+ if (isDockerImage && !dockerImage) {
164
167
  throw new Error(`Missing docker image information: ${JSON.stringify(localContainer)}`);
165
168
  }
169
+ if (isDockerFile) {
170
+ dockerImage = blockInfo.fullName + ':local';
171
+ const dockerFile = node_path_1.default.join(realLocalPath, localContainer.file ?? 'Dockerfile');
172
+ if (!fs_extra_1.default.existsSync(dockerFile)) {
173
+ throw new Error(`Dockerfile not found at: ${dockerFile}`);
174
+ }
175
+ const task = containerManager_1.containerManager.buildDockerImage(dockerFile, blockInfo.fullName + ':local');
176
+ await task.wait();
177
+ }
166
178
  const containerName = await (0, utils_1.getBlockInstanceContainerName)(this._systemId, blockInstance.id, targetKindUri.id);
167
179
  const startCmd = localContainer.handlers?.onCreate ? localContainer.handlers.onCreate : '';
168
180
  const dockerOpts = localContainer.options ?? {};
@@ -181,7 +193,6 @@ class BlockInstanceRunner {
181
193
  if (localContainer.healthcheck) {
182
194
  HealthCheck = containerManager_1.containerManager.toDockerHealth({ cmd: localContainer.healthcheck });
183
195
  }
184
- const realLocalPath = await fs_extra_1.default.realpath(baseDir);
185
196
  const Mounts = containerManager_1.containerManager.toDockerMounts({
186
197
  [workingDir]: (0, containerManager_1.toLocalBindVolume)(realLocalPath),
187
198
  });
@@ -2,6 +2,7 @@
2
2
  * Copyright 2023 Kapeta Inc.
3
3
  * SPDX-License-Identifier: BUSL-1.1
4
4
  */
5
+ import { Task } from '../taskManager';
5
6
  export declare function hasCLI(): Promise<boolean>;
6
- export declare function ensureCLI(): Promise<import("../taskManager").Task<void> | null>;
7
+ export declare function ensureCLI(): Promise<Task<void> | null>;
7
8
  export declare function ensureCLICommands(): Promise<void>;
@@ -15,10 +15,16 @@ async function ensureCLI() {
15
15
  if (await hasCLI()) {
16
16
  return null;
17
17
  }
18
- return taskManager_1.taskManager.add(`cli:install`, () => {
18
+ return taskManager_1.taskManager.add(`cli:install`, (task) => {
19
19
  const process = (0, nodejs_process_1.spawn)('npm', ['install', '-g', '@kapeta/kap'], {
20
20
  shell: true,
21
21
  });
22
+ process.process.stdout?.on('data', (data) => {
23
+ task.addLog(data.toString(), 'INFO');
24
+ });
25
+ process.process.stderr?.on('data', (data) => {
26
+ task.addLog(data.toString(), 'ERROR');
27
+ });
22
28
  return process.wait();
23
29
  }, {
24
30
  name: `Installing Kapeta CLI`,
@@ -2,10 +2,10 @@
2
2
  * Copyright 2023 Kapeta Inc.
3
3
  * SPDX-License-Identifier: BUSL-1.1
4
4
  */
5
- import { EntityList } from '@kapeta/schemas';
6
- import { AnyMap, PortInfo } from '../types';
5
+ import { EntityList, LocalInstancePort } from '@kapeta/schemas';
6
+ import { AnyMap } from '../types';
7
7
  export declare function getBlockInstanceContainerName(systemId: string, instanceId: string, blockType?: string): Promise<string>;
8
- export declare function toPortInfo(port: PortInfo): {
8
+ export declare function toPortInfo(port: LocalInstancePort): LocalInstancePort | {
9
9
  port: number;
10
10
  type: string;
11
11
  };
@@ -11,11 +11,12 @@ exports.getResolvedConfiguration = exports.getBindHost = exports.isLinux = expor
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"));
16
+ const types_1 = require("../types");
15
17
  const local_cluster_config_1 = __importDefault(require("@kapeta/local-cluster-config"));
16
18
  const definitionsManager_1 = require("../definitionsManager");
17
19
  const nodejs_utils_1 = require("@kapeta/nodejs-utils");
18
- const operatorManager_1 = require("../operatorManager");
19
20
  const assetManager_1 = require("../assetManager");
20
21
  async function getBlockInstanceContainerName(systemId, instanceId, blockType) {
21
22
  if (!blockType) {
@@ -33,7 +34,7 @@ async function getBlockInstanceContainerName(systemId, instanceId, blockType) {
33
34
  if (!typeDefinition) {
34
35
  throw new Error(`Block type ${blockType} not found`);
35
36
  }
36
- if ((0, nodejs_utils_1.parseKapetaUri)(typeDefinition.definition.kind).fullName === operatorManager_1.KIND_BLOCK_OPERATOR &&
37
+ if ((0, nodejs_utils_1.parseKapetaUri)(typeDefinition.definition.kind).fullName === types_1.KIND_BLOCK_TYPE_OPERATOR &&
37
38
  typeDefinition.definition.spec?.local?.singleton) {
38
39
  return `kapeta-instance-operator-${(0, md5_1.default)((0, nodejs_utils_1.normalizeKapetaUri)(systemId) + (0, nodejs_utils_1.normalizeKapetaUri)(blockType))}`;
39
40
  }
@@ -45,7 +46,7 @@ function toPortInfo(port) {
45
46
  return { port: parseInt(`${port}`), type: 'tcp' };
46
47
  }
47
48
  if (!port.type) {
48
- port.type = 'tcp';
49
+ port.type = schemas_1.LocalInstancePortType.TCP;
49
50
  }
50
51
  return port;
51
52
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kapeta/local-cluster-service",
3
- "version": "0.36.1",
3
+ "version": "0.38.0",
4
4
  "description": "Manages configuration, ports and service discovery for locally running Kapeta systems",
5
5
  "type": "commonjs",
6
6
  "exports": {
@@ -35,7 +35,8 @@
35
35
  },
36
36
  "scripts": {
37
37
  "start": "node ./dist/cjs/start.js",
38
- "dev": "nodemon -e js,ts,json ./start.ts",
38
+ "dev": "npm run start:dev",
39
+ "start:dev": "nodemon -e js,ts,json ./start.ts",
39
40
  "test": "jest",
40
41
  "clean": "rm -rf ./dist",
41
42
  "build:esm": "tsc --module nodenext --moduleResolution nodenext --outDir ./dist/esm && echo '{\"type\":\"module\"}' > ./dist/esm/package.json",
@@ -47,19 +48,19 @@
47
48
  },
48
49
  "homepage": "https://github.com/kapetacom/local-cluster-service#readme",
49
50
  "dependencies": {
50
- "@kapeta/codegen": "^1.2.1",
51
- "@kapeta/kaplang-core": "^1.9.3",
52
- "@kapeta/local-cluster-config": ">= 0.3.2 <2",
53
- "@kapeta/nodejs-api-client": ">=0.1.3 <2",
54
- "@kapeta/nodejs-process": "<2",
55
- "@kapeta/nodejs-registry-utils": ">=0.9.4 <2",
51
+ "@kapeta/codegen": "^1.3.0",
52
+ "@kapeta/kaplang-core": "^1.11.2",
53
+ "@kapeta/local-cluster-config": "^0.4.0",
54
+ "@kapeta/nodejs-api-client": ">=0.2.0 <2",
55
+ "@kapeta/nodejs-process": "^1.2.0",
56
+ "@kapeta/nodejs-registry-utils": ">=0.10.0 <2",
56
57
  "@kapeta/nodejs-utils": "<2",
57
- "@kapeta/schemas": "^2.0.0",
58
- "@kapeta/sdk-config": "^2.0.0",
59
- "@kapeta/ui-web-components": "^3.0.1",
60
- "@kapeta/ui-web-plan-editor": "^2.0.0",
61
- "@kapeta/ui-web-types": "^1.2.0",
62
- "@kapeta/web-microfrontend": "^1",
58
+ "@kapeta/schemas": "^3.4.0",
59
+ "@kapeta/sdk-config": "^2.1.0",
60
+ "@kapeta/ui-web-components": "^3.1.0",
61
+ "@kapeta/ui-web-plan-editor": "^2.3.1",
62
+ "@kapeta/ui-web-types": "^1.3.1",
63
+ "@kapeta/web-microfrontend": "^1.2.5",
63
64
  "@sentry/node": "^7.94.1",
64
65
  "@types/dockerode": "^3.3.19",
65
66
  "@types/stream-json": "^1.7.3",
@@ -85,8 +86,8 @@
85
86
  "yaml": "^1.6.0"
86
87
  },
87
88
  "devDependencies": {
88
- "@kapeta/eslint-config": "^0.6.1",
89
- "@kapeta/prettier-config": "^0.6.0",
89
+ "@kapeta/eslint-config": "^0.7.0",
90
+ "@kapeta/prettier-config": "^0.6.2",
90
91
  "@tsconfig/node18": "^18.2.0",
91
92
  "@types/async-lock": "^1.4.0",
92
93
  "@types/express": "^4.17.17",
@@ -8,14 +8,14 @@ import FS from 'fs-extra';
8
8
  import YAML from 'yaml';
9
9
  import { Definition, DefinitionInfo } from '@kapeta/local-cluster-config';
10
10
  import { codeGeneratorManager } from './codeGeneratorManager';
11
- import { ProgressListener } from './progressListener';
11
+ import { ProgressListener, TaskProgressListener } from './progressListener';
12
12
  import { normalizeKapetaUri, parseKapetaUri } from '@kapeta/nodejs-utils';
13
13
  import { repositoryManager } from './repositoryManager';
14
14
  import { BlockDefinition, BlockInstance, Plan } from '@kapeta/schemas';
15
15
  import { Actions } from '@kapeta/nodejs-registry-utils';
16
16
  import { definitionsManager } from './definitionsManager';
17
- import { taskManager } from './taskManager';
18
- import { SourceOfChange } from './types';
17
+ import { Task, taskManager } from './taskManager';
18
+ import { KIND_BLOCK_TYPE_EXECUTABLE, KIND_BLOCK_TYPE_OPERATOR, KIND_BLOCK_TYPE, SourceOfChange } from './types';
19
19
  import { cacheManager } from './cacheManager';
20
20
  import uuid from 'node-uuid';
21
21
  import os from 'node:os';
@@ -94,8 +94,9 @@ class AssetManager {
94
94
  async getAssets(assetKinds?: string[]): Promise<EnrichedAsset[]> {
95
95
  if (!assetKinds) {
96
96
  const blockTypeProviders = await definitionsManager.getDefinitions([
97
- 'core/block-type',
98
- 'core/block-type-operator',
97
+ KIND_BLOCK_TYPE,
98
+ KIND_BLOCK_TYPE_OPERATOR,
99
+ KIND_BLOCK_TYPE_EXECUTABLE,
99
100
  ]);
100
101
  assetKinds = blockTypeProviders.map((p) => {
101
102
  return `${p.definition.metadata.name}:${p.version}`;
@@ -307,11 +308,12 @@ class AssetManager {
307
308
  }
308
309
 
309
310
  console.log('Installing updates', refs);
310
- const updateAll = async () => {
311
+ const updateAll = async (task: Task) => {
312
+ const progressListener = new TaskProgressListener(task);
311
313
  try {
312
314
  //We change to a temp dir to avoid issues with the current working directory
313
315
  process.chdir(os.tmpdir());
314
- await Actions.install(new ProgressListener(), refs, {});
316
+ await Actions.install(progressListener, refs, {});
315
317
  await this.cleanupUnusedProviders();
316
318
  } catch (e) {
317
319
  console.error(`Failed to update assets: ${refs.join(',')}`, e);
@@ -14,12 +14,12 @@ import ClusterConfiguration from '@kapeta/local-cluster-config';
14
14
  import uuid from 'node-uuid';
15
15
  import md5 from 'md5';
16
16
  import { getBlockInstanceContainerName } from './utils/utils';
17
- import { Health, InstanceInfo, LogEntry, LogSource } from './types';
17
+ import { InstanceInfo, LogEntry, LogSource } from './types';
18
18
  import { KapetaAPI } from '@kapeta/nodejs-api-client';
19
19
  import { taskManager, Task } from './taskManager';
20
20
  import { EventEmitter } from 'node:events';
21
21
  import StreamValues from 'stream-json/streamers/StreamValues';
22
- import { Stream } from 'stream';
22
+ import { LocalInstanceHealth } from '@kapeta/schemas';
23
23
 
24
24
  type StringMap = { [key: string]: string };
25
25
 
@@ -100,7 +100,7 @@ enum DockerPullEventTypes {
100
100
 
101
101
  type DockerPullEventType = DockerPullEventTypes | string;
102
102
 
103
- const processJsonStream = <T>(purpose: string, stream: Stream, handler: (d: JSONMessage<T>) => void) =>
103
+ const processJsonStream = <T>(purpose: string, stream: NodeJS.ReadableStream, handler: (d: JSONMessage<T>) => void) =>
104
104
  new Promise<void>((resolve, reject) => {
105
105
  const jsonStream = StreamValues.withParser();
106
106
  jsonStream.on('data', (data: any) => {
@@ -296,15 +296,8 @@ class ContainerManager {
296
296
  }
297
297
 
298
298
  async getContainerByName(containerName: string): Promise<ContainerInfo | undefined> {
299
- const containers = await this.docker().listContainers({ all: true });
300
- const out = containers.find((container) => {
301
- return container.Names.indexOf(`/${containerName}`) > -1;
302
- });
303
-
304
- if (out) {
305
- return this.get(out.Id);
306
- }
307
- return undefined;
299
+ // The container can be fetched by name or by id using the same API call
300
+ return this.get(containerName);
308
301
  }
309
302
 
310
303
  async pull(image: string) {
@@ -398,6 +391,11 @@ class ContainerManager {
398
391
 
399
392
  const chunk = chunks[data.id];
400
393
 
394
+ if (data.stream) {
395
+ // Emit raw output to the task log
396
+ task.addLog(data.stream);
397
+ }
398
+
401
399
  switch (data.status) {
402
400
  case DockerPullEventTypes.PreparingPhase:
403
401
  case DockerPullEventTypes.WaitingPhase:
@@ -529,7 +527,7 @@ class ContainerManager {
529
527
  return Mounts;
530
528
  }
531
529
 
532
- toDockerHealth(health: Health) {
530
+ toDockerHealth(health: LocalInstanceHealth) {
533
531
  return {
534
532
  Test: ['CMD-SHELL', health.cmd],
535
533
  Interval: health.interval ? health.interval * NANO_SECOND : 5000 * NANO_SECOND,
@@ -679,7 +677,7 @@ class ContainerManager {
679
677
  let dockerContainer = null;
680
678
 
681
679
  try {
682
- dockerContainer = await this.docker().getContainer(name);
680
+ dockerContainer = this.docker().getContainer(name);
683
681
  await dockerContainer.stats();
684
682
  } catch (err) {
685
683
  //Ignore
@@ -707,7 +705,7 @@ class ContainerManager {
707
705
  ];
708
706
  }
709
707
 
710
- return containerInfo.getLogs();
708
+ return await containerInfo.getLogs();
711
709
  }
712
710
 
713
711
  async stopLogListening(systemId: string, instanceId: string) {
@@ -782,6 +780,34 @@ class ContainerManager {
782
780
  // Ignore
783
781
  }
784
782
  }
783
+
784
+ buildDockerImage(dockerFile: string, imageName: string) {
785
+ const taskName = `Building docker image: ${imageName}`;
786
+ const processor = async (task: Task) => {
787
+ const timeStarted = Date.now();
788
+ const stream = await this.docker().buildImage(
789
+ {
790
+ context: Path.dirname(dockerFile),
791
+ src: [Path.basename(dockerFile)],
792
+ },
793
+ {
794
+ t: imageName,
795
+ dockerfile: Path.basename(dockerFile),
796
+ }
797
+ );
798
+
799
+ await processJsonStream<string>(`image:build:${imageName}`, stream, (data) => {
800
+ if (data.stream) {
801
+ // Emit raw output to the task log
802
+ task.addLog(data.stream);
803
+ }
804
+ });
805
+ };
806
+
807
+ return taskManager.add(`docker:image:build:${imageName}`, processor, {
808
+ name: taskName,
809
+ });
810
+ }
785
811
  }
786
812
 
787
813
  function readLogBuffer(logBuffer: Buffer) {
@@ -1038,17 +1064,78 @@ export class ContainerInfo {
1038
1064
  });
1039
1065
 
1040
1066
  const out = readLogBuffer(logs);
1067
+ if (out.length > 0) {
1068
+ return out;
1069
+ }
1041
1070
 
1042
- if (out.length === 0) {
1043
- out.push({
1044
- time: Date.now(),
1045
- message: 'No logs found for container',
1046
- level: 'INFO',
1047
- source: 'stdout',
1048
- });
1071
+ const status = await this.status();
1072
+ const healthLogs: LogEntry[] = status?.Health?.Log
1073
+ ? status?.Health?.Log.map((log) => {
1074
+ return {
1075
+ source: 'stdout',
1076
+ level: log.ExitCode === 0 ? 'INFO' : 'ERROR',
1077
+ time: Date.now(),
1078
+ message: 'Health check: ' + log.Output,
1079
+ };
1080
+ })
1081
+ : [];
1082
+
1083
+ if (status?.Running) {
1084
+ return [
1085
+ {
1086
+ source: 'stdout',
1087
+ level: 'INFO',
1088
+ time: Date.now(),
1089
+ message: 'Container is starting...',
1090
+ },
1091
+ ...healthLogs,
1092
+ ];
1049
1093
  }
1050
1094
 
1051
- return out;
1095
+ if (status?.Restarting) {
1096
+ return [
1097
+ {
1098
+ source: 'stdout',
1099
+ level: 'INFO',
1100
+ time: Date.now(),
1101
+ message: 'Container is restarting...',
1102
+ },
1103
+ ...healthLogs,
1104
+ ];
1105
+ }
1106
+ if (status?.Paused) {
1107
+ return [
1108
+ {
1109
+ source: 'stdout',
1110
+ level: 'INFO',
1111
+ time: Date.now(),
1112
+ message: 'Container is paused...',
1113
+ },
1114
+ ...healthLogs,
1115
+ ];
1116
+ }
1117
+
1118
+ if (status?.Error) {
1119
+ return [
1120
+ {
1121
+ source: 'stderr',
1122
+ level: 'ERROR',
1123
+ time: Date.now(),
1124
+ message: 'Container failed to start:\n' + status.Error,
1125
+ },
1126
+ ...healthLogs,
1127
+ ];
1128
+ }
1129
+
1130
+ return [
1131
+ {
1132
+ source: 'stdout',
1133
+ level: 'INFO',
1134
+ time: Date.now(),
1135
+ message: 'Container not running',
1136
+ ...healthLogs,
1137
+ },
1138
+ ];
1052
1139
  }
1053
1140
  }
1054
1141