@kapeta/local-cluster-service 0.7.5 → 0.7.6

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.7.6](https://github.com/kapetacom/local-cluster-service/compare/v0.7.5...v0.7.6) (2023-07-17)
2
+
3
+
4
+ ### Bug Fixes
5
+
6
+ * Adjustments to make docker interaction work on Linux ([#45](https://github.com/kapetacom/local-cluster-service/issues/45)) ([4c9530c](https://github.com/kapetacom/local-cluster-service/commit/4c9530c1a509c490e95cc16029202650f79e127e))
7
+
1
8
  ## [0.7.5](https://github.com/kapetacom/local-cluster-service/compare/v0.7.4...v0.7.5) (2023-07-17)
2
9
 
3
10
 
package/definitions.d.ts CHANGED
@@ -5,6 +5,10 @@ declare module '@kapeta/nodejs-api-client' {
5
5
  }
6
6
  }
7
7
 
8
+ declare module 'recursive-watch' {
9
+ export default function watch(path:string, callback:(filename:string) => void):() => void;
10
+ }
11
+
8
12
  declare module '@kapeta/nodejs-registry-utils' {
9
13
  import { Dependency, Kind } from '@kapeta/schemas';
10
14
 
@@ -16,7 +20,7 @@ declare module '@kapeta/nodejs-registry-utils' {
16
20
  export class RegistryService {
17
21
  constructor(url: string);
18
22
 
19
- async getVersion(fullName: string, version: string): Promise<AssetVersion>;
23
+ getVersion(fullName: string, version: string): Promise<AssetVersion>;
20
24
  }
21
25
 
22
26
  export const Config: any;
package/dist/cjs/index.js CHANGED
@@ -19,6 +19,7 @@ const routes_5 = __importDefault(require("./src/identities/routes"));
19
19
  const routes_6 = __importDefault(require("./src/filesystem/routes"));
20
20
  const routes_7 = __importDefault(require("./src/assets/routes"));
21
21
  const routes_8 = __importDefault(require("./src/providers/routes"));
22
+ const utils_1 = require("./src/utils/utils");
22
23
  let currentServer = null;
23
24
  function createServer() {
24
25
  const app = (0, express_1.default)();
@@ -30,11 +31,11 @@ function createServer() {
30
31
  app.use('/files', routes_6.default);
31
32
  app.use('/assets', routes_7.default);
32
33
  app.use('/providers', routes_8.default);
33
- app.use('/', (err, req, res) => {
34
- console.error('Request failed: %s %s', req.method, req.originalUrl, err);
35
- res.status(500).send({
34
+ app.use('/', (req, res) => {
35
+ console.error('Invalid request: %s %s', req.method, req.originalUrl);
36
+ res.status(400).send({
36
37
  ok: false,
37
- error: err.error ?? err.message,
38
+ error: 'Unknown'
38
39
  });
39
40
  });
40
41
  const server = http_1.default.createServer(app);
@@ -90,7 +91,7 @@ exports.default = {
90
91
  if (clusterHost !== host) {
91
92
  storageService_1.storageService.put('cluster', 'host', host);
92
93
  }
93
- return new Promise((resolve, reject) => {
94
+ return new Promise(async (resolve, reject) => {
94
95
  if (!currentServer) {
95
96
  reject(new Error(`Current server wasn't set`));
96
97
  return;
@@ -102,7 +103,8 @@ exports.default = {
102
103
  }
103
104
  reject(err);
104
105
  });
105
- currentServer.listen(port, host, () => resolve({ host, port, dockerStatus: containerManager_1.containerManager.isAlive() }));
106
+ const bindHost = (0, utils_1.getBindHost)(host);
107
+ currentServer.listen(port, bindHost, () => resolve({ host, port, dockerStatus: containerManager_1.containerManager.isAlive() }));
106
108
  currentServer.host = host;
107
109
  currentServer.port = port;
108
110
  });
@@ -12,6 +12,7 @@ const fs_extra_1 = __importDefault(require("fs-extra"));
12
12
  const node_docker_api_1 = require("node-docker-api");
13
13
  const nodejs_utils_1 = require("@kapeta/nodejs-utils");
14
14
  const local_cluster_config_1 = __importDefault(require("@kapeta/local-cluster-config"));
15
+ const utils_1 = require("./utils/utils");
15
16
  const LABEL_PORT_PREFIX = 'kapeta_port-';
16
17
  const NANO_SECOND = 1000000;
17
18
  const HEALTH_CHECK_INTERVAL = 2000;
@@ -61,7 +62,11 @@ class ContainerManager {
61
62
  await client.ping();
62
63
  this._docker = client;
63
64
  const versionInfo = await client.version();
64
- this._version = versionInfo.Server?.Version;
65
+ this._version = versionInfo.Server?.Version ?? versionInfo.Version;
66
+ if (!this._version) {
67
+ console.warn('Failed to determine version from response', versionInfo);
68
+ this._version = '0.0.0';
69
+ }
65
70
  this._alive = true;
66
71
  console.log('Connected to docker daemon with version: %s', this._version);
67
72
  return;
@@ -171,13 +176,14 @@ class ContainerManager {
171
176
  kapeta: 'true',
172
177
  };
173
178
  await this.pull(image);
179
+ const bindHost = (0, utils_1.getBindHost)();
174
180
  const ExposedPorts = {};
175
181
  lodash_1.default.forEach(opts.ports, (portInfo, containerPort) => {
176
182
  ExposedPorts['' + containerPort] = {};
177
183
  PortBindings['' + containerPort] = [
178
184
  {
179
185
  HostPort: '' + portInfo.hostPort,
180
- HostIp: '127.0.0.1',
186
+ HostIp: bindHost,
181
187
  },
182
188
  ];
183
189
  Labels[LABEL_PORT_PREFIX + portInfo.hostPort] = portInfo.type;
@@ -7,6 +7,7 @@ exports.repositoryManager = void 0;
7
7
  const node_fs_1 = __importDefault(require("node:fs"));
8
8
  const node_os_1 = __importDefault(require("node:os"));
9
9
  const node_path_1 = __importDefault(require("node:path"));
10
+ const recursive_watch_1 = __importDefault(require("recursive-watch"));
10
11
  const fs_extra_1 = __importDefault(require("fs-extra"));
11
12
  const local_cluster_config_1 = __importDefault(require("@kapeta/local-cluster-config"));
12
13
  const nodejs_utils_1 = require("@kapeta/nodejs-utils");
@@ -39,70 +40,69 @@ class RepositoryManager {
39
40
  let allDefinitions = local_cluster_config_1.default.getDefinitions();
40
41
  console.log('Watching local repository for provider changes: %s', baseDir);
41
42
  try {
42
- this.watcher = node_fs_1.default.watch(baseDir, { recursive: true });
43
- }
44
- catch (e) {
45
- // Fallback to run without watch mode due to potential platform issues.
46
- // https://nodejs.org/docs/latest/api/fs.html#caveats
47
- console.log('Unable to watch for changes. Changes to assets will not update automatically.');
48
- return;
49
- }
50
- this.watcher.on('change', (eventType, filename) => {
51
- if (!filename) {
52
- return;
53
- }
54
- const [handle, name, version] = filename.toString().split(/\//g);
55
- if (!name || !version) {
56
- return;
57
- }
58
- if (!this.changeEventsEnabled) {
59
- return;
60
- }
61
- const ymlPath = node_path_1.default.join(baseDir, handle, name, version, 'kapeta.yml');
62
- const newDefinitions = local_cluster_config_1.default.getDefinitions();
63
- const newDefinition = newDefinitions.find((d) => d.ymlPath === ymlPath);
64
- let currentDefinition = allDefinitions.find((d) => d.ymlPath === ymlPath);
65
- const ymlExists = node_fs_1.default.existsSync(ymlPath);
66
- let type;
67
- if (ymlExists) {
68
- if (currentDefinition) {
69
- type = 'updated';
43
+ this.watcher = (0, recursive_watch_1.default)(baseDir, (filename) => {
44
+ if (!filename) {
45
+ return;
70
46
  }
71
- else if (newDefinition) {
72
- type = 'added';
73
- currentDefinition = newDefinition;
47
+ const [handle, name, version] = filename.toString().split(/\//g);
48
+ if (!name || !version) {
49
+ return;
74
50
  }
75
- else {
76
- //Other definition was added / updated - ignore
51
+ if (!this.changeEventsEnabled) {
77
52
  return;
78
53
  }
79
- }
80
- else {
81
- if (currentDefinition) {
82
- const ref = (0, nodejs_utils_1.parseKapetaUri)(`${currentDefinition.definition.metadata.name}:${currentDefinition.version}`).id;
83
- delete INSTALL_ATTEMPTED[ref];
84
- //Something was removed
85
- type = 'removed';
54
+ const ymlPath = node_path_1.default.join(baseDir, handle, name, version, 'kapeta.yml');
55
+ const newDefinitions = local_cluster_config_1.default.getDefinitions();
56
+ const newDefinition = newDefinitions.find((d) => d.ymlPath === ymlPath);
57
+ let currentDefinition = allDefinitions.find((d) => d.ymlPath === ymlPath);
58
+ const ymlExists = node_fs_1.default.existsSync(ymlPath);
59
+ let type;
60
+ if (ymlExists) {
61
+ if (currentDefinition) {
62
+ type = 'updated';
63
+ }
64
+ else if (newDefinition) {
65
+ type = 'added';
66
+ currentDefinition = newDefinition;
67
+ }
68
+ else {
69
+ //Other definition was added / updated - ignore
70
+ return;
71
+ }
86
72
  }
87
73
  else {
88
- //Other definition was removed - ignore
89
- return;
74
+ if (currentDefinition) {
75
+ const ref = (0, nodejs_utils_1.parseKapetaUri)(`${currentDefinition.definition.metadata.name}:${currentDefinition.version}`).id;
76
+ delete INSTALL_ATTEMPTED[ref];
77
+ //Something was removed
78
+ type = 'removed';
79
+ }
80
+ else {
81
+ //Other definition was removed - ignore
82
+ return;
83
+ }
90
84
  }
91
- }
92
- const payload = {
93
- type,
94
- definition: currentDefinition?.definition,
95
- asset: { handle, name, version },
96
- };
97
- allDefinitions = newDefinitions;
98
- socketManager_1.socketManager.emit(`assets`, 'changed', payload);
99
- });
85
+ const payload = {
86
+ type,
87
+ definition: currentDefinition?.definition,
88
+ asset: { handle, name, version },
89
+ };
90
+ allDefinitions = newDefinitions;
91
+ socketManager_1.socketManager.emit(`assets`, 'changed', payload);
92
+ });
93
+ }
94
+ catch (e) {
95
+ // Fallback to run without watch mode due to potential platform issues.
96
+ // https://nodejs.org/docs/latest/api/fs.html#caveats
97
+ console.log('Unable to watch for changes. Changes to assets will not update automatically.', e);
98
+ return;
99
+ }
100
100
  }
101
101
  stopListening() {
102
102
  if (!this.watcher) {
103
103
  return;
104
104
  }
105
- this.watcher.close();
105
+ this.watcher();
106
106
  this.watcher = undefined;
107
107
  }
108
108
  async _install(refs) {
@@ -160,6 +160,7 @@ class BlockInstanceRunner {
160
160
  const dockerOpts = localContainer.options ?? {};
161
161
  const homeDir = localContainer.userHome ? localContainer.userHome : '/root';
162
162
  const workingDir = localContainer.workingDir ? localContainer.workingDir : '/workspace';
163
+ const bindHost = (0, utils_1.getBindHost)();
163
164
  const ExposedPorts = {};
164
165
  const addonEnv = {};
165
166
  const PortBindings = {};
@@ -173,7 +174,7 @@ class BlockInstanceRunner {
173
174
  addonEnv[`KAPETA_LOCAL_SERVER_PORT_${portType.toUpperCase()}`] = '' + thisPort;
174
175
  PortBindings[dockerPort] = [
175
176
  {
176
- HostIp: '127.0.0.1',
177
+ HostIp: bindHost,
177
178
  HostPort: `${publicPort}`,
178
179
  },
179
180
  ];
@@ -390,6 +391,7 @@ class BlockInstanceRunner {
390
391
  }
391
392
  }
392
393
  }
394
+ const bindHost = (0, utils_1.getBindHost)();
393
395
  if (!container) {
394
396
  const ExposedPorts = {};
395
397
  const addonEnv = {};
@@ -403,7 +405,7 @@ class BlockInstanceRunner {
403
405
  const publicPort = await serviceManager_1.serviceManager.ensureServicePort(this._systemId, blockInstance.id, portType);
404
406
  PortBindings[dockerPort] = [
405
407
  {
406
- HostIp: '127.0.0.1',
408
+ HostIp: bindHost,
407
409
  HostPort: `${publicPort}`,
408
410
  },
409
411
  ];
@@ -1 +1,5 @@
1
1
  export declare function readYML(path: string): any;
2
+ export declare function isWindows(): boolean;
3
+ export declare function isMac(): boolean;
4
+ export declare function isLinux(): boolean;
5
+ export declare function getBindHost(preferredHost?: string): string;
@@ -3,7 +3,7 @@ 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.readYML = void 0;
6
+ exports.getBindHost = exports.isLinux = exports.isMac = exports.isWindows = exports.readYML = void 0;
7
7
  const node_fs_1 = __importDefault(require("node:fs"));
8
8
  const yaml_1 = __importDefault(require("yaml"));
9
9
  function readYML(path) {
@@ -16,3 +16,21 @@ function readYML(path) {
16
16
  }
17
17
  }
18
18
  exports.readYML = readYML;
19
+ function isWindows() {
20
+ return 'win32' === process.platform;
21
+ }
22
+ exports.isWindows = isWindows;
23
+ function isMac() {
24
+ return 'darwin' === process.platform;
25
+ }
26
+ exports.isMac = isMac;
27
+ function isLinux() {
28
+ return !isWindows() && !isMac();
29
+ }
30
+ exports.isLinux = isLinux;
31
+ function getBindHost(preferredHost = '127.0.0.1') {
32
+ // On Linux we need to bind to 0.0.0.0 to be able to connect to it from docker containers.
33
+ // TODO: This might pose a security risk - so we should authenticate all requests using a shared secret/nonce that we pass around.
34
+ return isLinux() ? '0.0.0.0' : preferredHost;
35
+ }
36
+ exports.getBindHost = getBindHost;
package/dist/esm/index.js CHANGED
@@ -14,6 +14,7 @@ import IdentitiesRoutes from './src/identities/routes';
14
14
  import FilesystemRoutes from './src/filesystem/routes';
15
15
  import AssetsRoutes from './src/assets/routes';
16
16
  import ProviderRoutes from './src/providers/routes';
17
+ import { getBindHost } from './src/utils/utils';
17
18
  let currentServer = null;
18
19
  function createServer() {
19
20
  const app = express();
@@ -25,11 +26,11 @@ function createServer() {
25
26
  app.use('/files', FilesystemRoutes);
26
27
  app.use('/assets', AssetsRoutes);
27
28
  app.use('/providers', ProviderRoutes);
28
- app.use('/', (err, req, res) => {
29
- console.error('Request failed: %s %s', req.method, req.originalUrl, err);
30
- res.status(500).send({
29
+ app.use('/', (req, res) => {
30
+ console.error('Invalid request: %s %s', req.method, req.originalUrl);
31
+ res.status(400).send({
31
32
  ok: false,
32
- error: err.error ?? err.message,
33
+ error: 'Unknown'
33
34
  });
34
35
  });
35
36
  const server = HTTP.createServer(app);
@@ -85,7 +86,7 @@ export default {
85
86
  if (clusterHost !== host) {
86
87
  storageService.put('cluster', 'host', host);
87
88
  }
88
- return new Promise((resolve, reject) => {
89
+ return new Promise(async (resolve, reject) => {
89
90
  if (!currentServer) {
90
91
  reject(new Error(`Current server wasn't set`));
91
92
  return;
@@ -97,7 +98,8 @@ export default {
97
98
  }
98
99
  reject(err);
99
100
  });
100
- currentServer.listen(port, host, () => resolve({ host, port, dockerStatus: containerManager.isAlive() }));
101
+ const bindHost = getBindHost(host);
102
+ currentServer.listen(port, bindHost, () => resolve({ host, port, dockerStatus: containerManager.isAlive() }));
101
103
  currentServer.host = host;
102
104
  currentServer.port = port;
103
105
  });
@@ -6,6 +6,7 @@ import FSExtra from 'fs-extra';
6
6
  import { Docker } from 'node-docker-api';
7
7
  import { parseKapetaUri } from '@kapeta/nodejs-utils';
8
8
  import ClusterConfiguration from '@kapeta/local-cluster-config';
9
+ import { getBindHost } from './utils/utils';
9
10
  const LABEL_PORT_PREFIX = 'kapeta_port-';
10
11
  const NANO_SECOND = 1000000;
11
12
  const HEALTH_CHECK_INTERVAL = 2000;
@@ -55,7 +56,11 @@ class ContainerManager {
55
56
  await client.ping();
56
57
  this._docker = client;
57
58
  const versionInfo = await client.version();
58
- this._version = versionInfo.Server?.Version;
59
+ this._version = versionInfo.Server?.Version ?? versionInfo.Version;
60
+ if (!this._version) {
61
+ console.warn('Failed to determine version from response', versionInfo);
62
+ this._version = '0.0.0';
63
+ }
59
64
  this._alive = true;
60
65
  console.log('Connected to docker daemon with version: %s', this._version);
61
66
  return;
@@ -165,13 +170,14 @@ class ContainerManager {
165
170
  kapeta: 'true',
166
171
  };
167
172
  await this.pull(image);
173
+ const bindHost = getBindHost();
168
174
  const ExposedPorts = {};
169
175
  _.forEach(opts.ports, (portInfo, containerPort) => {
170
176
  ExposedPorts['' + containerPort] = {};
171
177
  PortBindings['' + containerPort] = [
172
178
  {
173
179
  HostPort: '' + portInfo.hostPort,
174
- HostIp: '127.0.0.1',
180
+ HostIp: bindHost,
175
181
  },
176
182
  ];
177
183
  Labels[LABEL_PORT_PREFIX + portInfo.hostPort] = portInfo.type;
@@ -1,6 +1,7 @@
1
1
  import FS from 'node:fs';
2
2
  import os from 'node:os';
3
3
  import Path from 'node:path';
4
+ import watch from 'recursive-watch';
4
5
  import FSExtra from 'fs-extra';
5
6
  import ClusterConfiguration from '@kapeta/local-cluster-config';
6
7
  import { parseKapetaUri } from '@kapeta/nodejs-utils';
@@ -33,70 +34,69 @@ class RepositoryManager {
33
34
  let allDefinitions = ClusterConfiguration.getDefinitions();
34
35
  console.log('Watching local repository for provider changes: %s', baseDir);
35
36
  try {
36
- this.watcher = FS.watch(baseDir, { recursive: true });
37
- }
38
- catch (e) {
39
- // Fallback to run without watch mode due to potential platform issues.
40
- // https://nodejs.org/docs/latest/api/fs.html#caveats
41
- console.log('Unable to watch for changes. Changes to assets will not update automatically.');
42
- return;
43
- }
44
- this.watcher.on('change', (eventType, filename) => {
45
- if (!filename) {
46
- return;
47
- }
48
- const [handle, name, version] = filename.toString().split(/\//g);
49
- if (!name || !version) {
50
- return;
51
- }
52
- if (!this.changeEventsEnabled) {
53
- return;
54
- }
55
- const ymlPath = Path.join(baseDir, handle, name, version, 'kapeta.yml');
56
- const newDefinitions = ClusterConfiguration.getDefinitions();
57
- const newDefinition = newDefinitions.find((d) => d.ymlPath === ymlPath);
58
- let currentDefinition = allDefinitions.find((d) => d.ymlPath === ymlPath);
59
- const ymlExists = FS.existsSync(ymlPath);
60
- let type;
61
- if (ymlExists) {
62
- if (currentDefinition) {
63
- type = 'updated';
37
+ this.watcher = watch(baseDir, (filename) => {
38
+ if (!filename) {
39
+ return;
64
40
  }
65
- else if (newDefinition) {
66
- type = 'added';
67
- currentDefinition = newDefinition;
41
+ const [handle, name, version] = filename.toString().split(/\//g);
42
+ if (!name || !version) {
43
+ return;
68
44
  }
69
- else {
70
- //Other definition was added / updated - ignore
45
+ if (!this.changeEventsEnabled) {
71
46
  return;
72
47
  }
73
- }
74
- else {
75
- if (currentDefinition) {
76
- const ref = parseKapetaUri(`${currentDefinition.definition.metadata.name}:${currentDefinition.version}`).id;
77
- delete INSTALL_ATTEMPTED[ref];
78
- //Something was removed
79
- type = 'removed';
48
+ const ymlPath = Path.join(baseDir, handle, name, version, 'kapeta.yml');
49
+ const newDefinitions = ClusterConfiguration.getDefinitions();
50
+ const newDefinition = newDefinitions.find((d) => d.ymlPath === ymlPath);
51
+ let currentDefinition = allDefinitions.find((d) => d.ymlPath === ymlPath);
52
+ const ymlExists = FS.existsSync(ymlPath);
53
+ let type;
54
+ if (ymlExists) {
55
+ if (currentDefinition) {
56
+ type = 'updated';
57
+ }
58
+ else if (newDefinition) {
59
+ type = 'added';
60
+ currentDefinition = newDefinition;
61
+ }
62
+ else {
63
+ //Other definition was added / updated - ignore
64
+ return;
65
+ }
80
66
  }
81
67
  else {
82
- //Other definition was removed - ignore
83
- return;
68
+ if (currentDefinition) {
69
+ const ref = parseKapetaUri(`${currentDefinition.definition.metadata.name}:${currentDefinition.version}`).id;
70
+ delete INSTALL_ATTEMPTED[ref];
71
+ //Something was removed
72
+ type = 'removed';
73
+ }
74
+ else {
75
+ //Other definition was removed - ignore
76
+ return;
77
+ }
84
78
  }
85
- }
86
- const payload = {
87
- type,
88
- definition: currentDefinition?.definition,
89
- asset: { handle, name, version },
90
- };
91
- allDefinitions = newDefinitions;
92
- socketManager.emit(`assets`, 'changed', payload);
93
- });
79
+ const payload = {
80
+ type,
81
+ definition: currentDefinition?.definition,
82
+ asset: { handle, name, version },
83
+ };
84
+ allDefinitions = newDefinitions;
85
+ socketManager.emit(`assets`, 'changed', payload);
86
+ });
87
+ }
88
+ catch (e) {
89
+ // Fallback to run without watch mode due to potential platform issues.
90
+ // https://nodejs.org/docs/latest/api/fs.html#caveats
91
+ console.log('Unable to watch for changes. Changes to assets will not update automatically.', e);
92
+ return;
93
+ }
94
94
  }
95
95
  stopListening() {
96
96
  if (!this.watcher) {
97
97
  return;
98
98
  }
99
- this.watcher.close();
99
+ this.watcher();
100
100
  this.watcher = undefined;
101
101
  }
102
102
  async _install(refs) {
@@ -1,6 +1,6 @@
1
1
  import FS from 'node:fs';
2
2
  import ClusterConfig from '@kapeta/local-cluster-config';
3
- import { readYML } from './utils';
3
+ import { getBindHost, readYML } from './utils';
4
4
  import { parseKapetaUri } from '@kapeta/nodejs-utils';
5
5
  import { serviceManager } from '../serviceManager';
6
6
  import { containerManager, toLocalBindVolume } from '../containerManager';
@@ -154,6 +154,7 @@ export class BlockInstanceRunner {
154
154
  const dockerOpts = localContainer.options ?? {};
155
155
  const homeDir = localContainer.userHome ? localContainer.userHome : '/root';
156
156
  const workingDir = localContainer.workingDir ? localContainer.workingDir : '/workspace';
157
+ const bindHost = getBindHost();
157
158
  const ExposedPorts = {};
158
159
  const addonEnv = {};
159
160
  const PortBindings = {};
@@ -167,7 +168,7 @@ export class BlockInstanceRunner {
167
168
  addonEnv[`KAPETA_LOCAL_SERVER_PORT_${portType.toUpperCase()}`] = '' + thisPort;
168
169
  PortBindings[dockerPort] = [
169
170
  {
170
- HostIp: '127.0.0.1',
171
+ HostIp: bindHost,
171
172
  HostPort: `${publicPort}`,
172
173
  },
173
174
  ];
@@ -384,6 +385,7 @@ export class BlockInstanceRunner {
384
385
  }
385
386
  }
386
387
  }
388
+ const bindHost = getBindHost();
387
389
  if (!container) {
388
390
  const ExposedPorts = {};
389
391
  const addonEnv = {};
@@ -397,7 +399,7 @@ export class BlockInstanceRunner {
397
399
  const publicPort = await serviceManager.ensureServicePort(this._systemId, blockInstance.id, portType);
398
400
  PortBindings[dockerPort] = [
399
401
  {
400
- HostIp: '127.0.0.1',
402
+ HostIp: bindHost,
401
403
  HostPort: `${publicPort}`,
402
404
  },
403
405
  ];
@@ -1 +1,5 @@
1
1
  export declare function readYML(path: string): any;
2
+ export declare function isWindows(): boolean;
3
+ export declare function isMac(): boolean;
4
+ export declare function isLinux(): boolean;
5
+ export declare function getBindHost(preferredHost?: string): string;
@@ -9,3 +9,17 @@ export function readYML(path) {
9
9
  throw new Error('Failed to parse plan YAML: ' + err);
10
10
  }
11
11
  }
12
+ export function isWindows() {
13
+ return 'win32' === process.platform;
14
+ }
15
+ export function isMac() {
16
+ return 'darwin' === process.platform;
17
+ }
18
+ export function isLinux() {
19
+ return !isWindows() && !isMac();
20
+ }
21
+ export function getBindHost(preferredHost = '127.0.0.1') {
22
+ // On Linux we need to bind to 0.0.0.0 to be able to connect to it from docker containers.
23
+ // TODO: This might pose a security risk - so we should authenticate all requests using a shared secret/nonce that we pass around.
24
+ return isLinux() ? '0.0.0.0' : preferredHost;
25
+ }
package/index.ts CHANGED
@@ -15,6 +15,7 @@ import IdentitiesRoutes from './src/identities/routes';
15
15
  import FilesystemRoutes from './src/filesystem/routes';
16
16
  import AssetsRoutes from './src/assets/routes';
17
17
  import ProviderRoutes from './src/providers/routes';
18
+ import { getBindHost } from './src/utils/utils';
18
19
 
19
20
  export type LocalClusterService = HTTP.Server & { host?: string; port?: number };
20
21
 
@@ -32,11 +33,11 @@ function createServer() {
32
33
  app.use('/files', FilesystemRoutes);
33
34
  app.use('/assets', AssetsRoutes);
34
35
  app.use('/providers', ProviderRoutes);
35
- app.use('/', (err: any, req: express.Request, res: express.Response) => {
36
- console.error('Request failed: %s %s', req.method, req.originalUrl, err);
37
- res.status(500).send({
36
+ app.use('/', (req: express.Request, res: express.Response) => {
37
+ console.error('Invalid request: %s %s', req.method, req.originalUrl);
38
+ res.status(400).send({
38
39
  ok: false,
39
- error: err.error ?? err.message,
40
+ error: 'Unknown'
40
41
  });
41
42
  });
42
43
  const server = HTTP.createServer(app);
@@ -108,7 +109,7 @@ export default {
108
109
  storageService.put('cluster', 'host', host);
109
110
  }
110
111
 
111
- return new Promise((resolve, reject) => {
112
+ return new Promise(async (resolve, reject) => {
112
113
  if (!currentServer) {
113
114
  reject(new Error(`Current server wasn't set`));
114
115
  return;
@@ -121,7 +122,9 @@ export default {
121
122
  reject(err);
122
123
  });
123
124
 
124
- currentServer.listen(port, host, () => resolve({ host, port, dockerStatus: containerManager.isAlive() }));
125
+ const bindHost = getBindHost(host);
126
+
127
+ currentServer.listen(port, bindHost, () => resolve({ host, port, dockerStatus: containerManager.isAlive() }));
125
128
  currentServer.host = host;
126
129
  currentServer.port = port;
127
130
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kapeta/local-cluster-service",
3
- "version": "0.7.5",
3
+ "version": "0.7.6",
4
4
  "description": "Manages configuration, ports and service discovery for locally running Kapeta systems",
5
5
  "type": "commonjs",
6
6
  "exports": {
@@ -59,6 +59,7 @@
59
59
  "node-cache": "^5.1.2",
60
60
  "node-docker-api": "1.1.22",
61
61
  "node-uuid": "^1.4.8",
62
+ "recursive-watch": "^1.1.4",
62
63
  "request": "2.88.2",
63
64
  "request-promise": "4.2.6",
64
65
  "socket.io": "^4.5.2",
@@ -7,6 +7,7 @@ import { Docker } from 'node-docker-api';
7
7
  import { parseKapetaUri } from '@kapeta/nodejs-utils';
8
8
  import ClusterConfiguration from '@kapeta/local-cluster-config';
9
9
  import { Container } from 'node-docker-api/lib/container';
10
+ import { getBindHost } from './utils/utils';
10
11
 
11
12
  type StringMap = { [key: string]: string };
12
13
 
@@ -88,7 +89,11 @@ class ContainerManager {
88
89
  await client.ping();
89
90
  this._docker = client;
90
91
  const versionInfo: any = await client.version();
91
- this._version = versionInfo.Server?.Version;
92
+ this._version = versionInfo.Server?.Version ?? versionInfo.Version;
93
+ if (!this._version) {
94
+ console.warn('Failed to determine version from response', versionInfo);
95
+ this._version = '0.0.0';
96
+ }
92
97
  this._alive = true;
93
98
  console.log('Connected to docker daemon with version: %s', this._version);
94
99
  return;
@@ -226,6 +231,8 @@ class ContainerManager {
226
231
 
227
232
  await this.pull(image);
228
233
 
234
+ const bindHost = getBindHost();
235
+
229
236
  const ExposedPorts: { [key: string]: any } = {};
230
237
 
231
238
  _.forEach(opts.ports, (portInfo: any, containerPort) => {
@@ -233,7 +240,7 @@ class ContainerManager {
233
240
  PortBindings['' + containerPort] = [
234
241
  {
235
242
  HostPort: '' + portInfo.hostPort,
236
- HostIp: '127.0.0.1',
243
+ HostIp: bindHost,
237
244
  },
238
245
  ];
239
246
 
@@ -1,6 +1,7 @@
1
1
  import FS from 'node:fs';
2
2
  import os from 'node:os';
3
3
  import Path from 'node:path';
4
+ import watch from 'recursive-watch';
4
5
  import FSExtra, { FSWatcher } from 'fs-extra';
5
6
  import ClusterConfiguration from '@kapeta/local-cluster-config';
6
7
  import { parseKapetaUri } from '@kapeta/nodejs-utils';
@@ -15,7 +16,7 @@ class RepositoryManager {
15
16
  private changeEventsEnabled: boolean;
16
17
  private _registryService: RegistryService;
17
18
  private _cache: { [key: string]: boolean };
18
- private watcher?: FSWatcher;
19
+ private watcher?: () => void;
19
20
  private _installQueue: (() => Promise<void>)[];
20
21
  private _processing: boolean = false;
21
22
  constructor() {
@@ -40,74 +41,73 @@ class RepositoryManager {
40
41
 
41
42
  console.log('Watching local repository for provider changes: %s', baseDir);
42
43
  try {
43
- this.watcher = FS.watch(baseDir, { recursive: true });
44
+ this.watcher = watch(baseDir, (filename:string) => {
45
+ if (!filename) {
46
+ return;
47
+ }
48
+
49
+ const [handle, name, version] = filename.toString().split(/\//g);
50
+ if (!name || !version) {
51
+ return;
52
+ }
53
+
54
+ if (!this.changeEventsEnabled) {
55
+ return;
56
+ }
57
+
58
+ const ymlPath = Path.join(baseDir, handle, name, version, 'kapeta.yml');
59
+ const newDefinitions = ClusterConfiguration.getDefinitions();
60
+
61
+ const newDefinition = newDefinitions.find((d) => d.ymlPath === ymlPath);
62
+ let currentDefinition = allDefinitions.find((d) => d.ymlPath === ymlPath);
63
+ const ymlExists = FS.existsSync(ymlPath);
64
+ let type;
65
+ if (ymlExists) {
66
+ if (currentDefinition) {
67
+ type = 'updated';
68
+ } else if (newDefinition) {
69
+ type = 'added';
70
+ currentDefinition = newDefinition;
71
+ } else {
72
+ //Other definition was added / updated - ignore
73
+ return;
74
+ }
75
+ } else {
76
+ if (currentDefinition) {
77
+ const ref = parseKapetaUri(
78
+ `${currentDefinition.definition.metadata.name}:${currentDefinition.version}`
79
+ ).id;
80
+ delete INSTALL_ATTEMPTED[ref];
81
+ //Something was removed
82
+ type = 'removed';
83
+ } else {
84
+ //Other definition was removed - ignore
85
+ return;
86
+ }
87
+ }
88
+
89
+ const payload = {
90
+ type,
91
+ definition: currentDefinition?.definition,
92
+ asset: { handle, name, version },
93
+ };
94
+
95
+ allDefinitions = newDefinitions;
96
+ socketManager.emit(`assets`, 'changed', payload);
97
+ });
44
98
  } catch (e) {
45
99
  // Fallback to run without watch mode due to potential platform issues.
46
100
  // https://nodejs.org/docs/latest/api/fs.html#caveats
47
- console.log('Unable to watch for changes. Changes to assets will not update automatically.');
101
+ console.log('Unable to watch for changes. Changes to assets will not update automatically.', e);
48
102
  return;
49
103
  }
50
- this.watcher.on('change', (eventType, filename) => {
51
- if (!filename) {
52
- return;
53
- }
54
-
55
- const [handle, name, version] = filename.toString().split(/\//g);
56
- if (!name || !version) {
57
- return;
58
- }
59
-
60
- if (!this.changeEventsEnabled) {
61
- return;
62
- }
63
-
64
- const ymlPath = Path.join(baseDir, handle, name, version, 'kapeta.yml');
65
- const newDefinitions = ClusterConfiguration.getDefinitions();
66
-
67
- const newDefinition = newDefinitions.find((d) => d.ymlPath === ymlPath);
68
- let currentDefinition = allDefinitions.find((d) => d.ymlPath === ymlPath);
69
- const ymlExists = FS.existsSync(ymlPath);
70
- let type;
71
- if (ymlExists) {
72
- if (currentDefinition) {
73
- type = 'updated';
74
- } else if (newDefinition) {
75
- type = 'added';
76
- currentDefinition = newDefinition;
77
- } else {
78
- //Other definition was added / updated - ignore
79
- return;
80
- }
81
- } else {
82
- if (currentDefinition) {
83
- const ref = parseKapetaUri(
84
- `${currentDefinition.definition.metadata.name}:${currentDefinition.version}`
85
- ).id;
86
- delete INSTALL_ATTEMPTED[ref];
87
- //Something was removed
88
- type = 'removed';
89
- } else {
90
- //Other definition was removed - ignore
91
- return;
92
- }
93
- }
94
-
95
- const payload = {
96
- type,
97
- definition: currentDefinition?.definition,
98
- asset: { handle, name, version },
99
- };
100
-
101
- allDefinitions = newDefinitions;
102
- socketManager.emit(`assets`, 'changed', payload);
103
- });
104
104
  }
105
105
 
106
106
  stopListening() {
107
107
  if (!this.watcher) {
108
108
  return;
109
109
  }
110
- this.watcher.close();
110
+ this.watcher();
111
111
  this.watcher = undefined;
112
112
  }
113
113
 
@@ -1,6 +1,6 @@
1
1
  import FS from 'node:fs';
2
2
  import ClusterConfig, { DefinitionInfo } from '@kapeta/local-cluster-config';
3
- import { readYML } from './utils';
3
+ import { getBindHost, readYML } from './utils';
4
4
  import { KapetaURI, parseKapetaUri } from '@kapeta/nodejs-utils';
5
5
  import { serviceManager } from '../serviceManager';
6
6
  import { containerManager, DockerMounts, toLocalBindVolume } from '../containerManager';
@@ -199,6 +199,8 @@ export class BlockInstanceRunner {
199
199
  const homeDir = localContainer.userHome ? localContainer.userHome : '/root';
200
200
  const workingDir = localContainer.workingDir ? localContainer.workingDir : '/workspace';
201
201
 
202
+ const bindHost = getBindHost();
203
+
202
204
  const ExposedPorts: AnyMap = {};
203
205
  const addonEnv: StringMap = {};
204
206
  const PortBindings: AnyMap = {};
@@ -214,7 +216,7 @@ export class BlockInstanceRunner {
214
216
 
215
217
  PortBindings[dockerPort] = [
216
218
  {
217
- HostIp: '127.0.0.1', //No public
219
+ HostIp: bindHost,
218
220
  HostPort: `${publicPort}`,
219
221
  },
220
222
  ];
@@ -464,6 +466,8 @@ export class BlockInstanceRunner {
464
466
  }
465
467
  }
466
468
 
469
+ const bindHost = getBindHost();
470
+
467
471
  if (!container) {
468
472
  const ExposedPorts: AnyMap = {};
469
473
  const addonEnv: StringMap = {};
@@ -482,7 +486,7 @@ export class BlockInstanceRunner {
482
486
  );
483
487
  PortBindings[dockerPort] = [
484
488
  {
485
- HostIp: '127.0.0.1', //No public
489
+ HostIp: bindHost,
486
490
  HostPort: `${publicPort}`,
487
491
  },
488
492
  ];
@@ -10,3 +10,21 @@ export function readYML(path: string) {
10
10
  throw new Error('Failed to parse plan YAML: ' + err);
11
11
  }
12
12
  }
13
+
14
+ export function isWindows() {
15
+ return 'win32' === process.platform;
16
+ }
17
+
18
+ export function isMac() {
19
+ return 'darwin' === process.platform;
20
+ }
21
+
22
+ export function isLinux() {
23
+ return !isWindows() && !isMac();
24
+ }
25
+
26
+ export function getBindHost(preferredHost = '127.0.0.1') {
27
+ // On Linux we need to bind to 0.0.0.0 to be able to connect to it from docker containers.
28
+ // TODO: This might pose a security risk - so we should authenticate all requests using a shared secret/nonce that we pass around.
29
+ return isLinux() ? '0.0.0.0' : preferredHost;
30
+ }