@kapeta/local-cluster-service 0.7.5 → 0.8.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.
- package/CHANGELOG.md +14 -0
- package/definitions.d.ts +3 -6
- package/dist/cjs/index.js +10 -6
- package/dist/cjs/src/attachments/routes.d.ts +3 -0
- package/dist/cjs/src/attachments/routes.js +63 -0
- package/dist/cjs/src/containerManager.js +8 -2
- package/dist/cjs/src/identities/routes.js +12 -2
- package/dist/cjs/src/repositoryManager.js +52 -52
- package/dist/cjs/src/storageService.d.ts +1 -1
- package/dist/cjs/src/storageService.js +4 -1
- package/dist/cjs/src/utils/BlockInstanceRunner.js +4 -2
- package/dist/cjs/src/utils/utils.d.ts +4 -0
- package/dist/cjs/src/utils/utils.js +19 -1
- package/dist/esm/index.js +10 -6
- package/dist/esm/src/attachments/routes.d.ts +3 -0
- package/dist/esm/src/attachments/routes.js +58 -0
- package/dist/esm/src/containerManager.js +8 -2
- package/dist/esm/src/identities/routes.js +12 -2
- package/dist/esm/src/repositoryManager.js +52 -52
- package/dist/esm/src/storageService.d.ts +1 -1
- package/dist/esm/src/storageService.js +4 -1
- package/dist/esm/src/utils/BlockInstanceRunner.js +5 -3
- package/dist/esm/src/utils/utils.d.ts +4 -0
- package/dist/esm/src/utils/utils.js +14 -0
- package/index.ts +11 -6
- package/package.json +2 -1
- package/src/attachments/routes.ts +68 -0
- package/src/containerManager.ts +9 -2
- package/src/identities/routes.ts +11 -2
- package/src/repositoryManager.ts +58 -58
- package/src/storageService.ts +5 -1
- package/src/utils/BlockInstanceRunner.ts +7 -3
- package/src/utils/utils.ts +18 -0
package/CHANGELOG.md
CHANGED
@@ -1,3 +1,17 @@
|
|
1
|
+
# [0.8.0](https://github.com/kapetacom/local-cluster-service/compare/v0.7.6...v0.8.0) (2023-07-22)
|
2
|
+
|
3
|
+
|
4
|
+
### Features
|
5
|
+
|
6
|
+
* Added route for uploading attachments to assets ([#46](https://github.com/kapetacom/local-cluster-service/issues/46)) ([e668f33](https://github.com/kapetacom/local-cluster-service/commit/e668f33072e772077a3186fc753e8c516e15171d))
|
7
|
+
|
8
|
+
## [0.7.6](https://github.com/kapetacom/local-cluster-service/compare/v0.7.5...v0.7.6) (2023-07-17)
|
9
|
+
|
10
|
+
|
11
|
+
### Bug Fixes
|
12
|
+
|
13
|
+
* 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))
|
14
|
+
|
1
15
|
## [0.7.5](https://github.com/kapetacom/local-cluster-service/compare/v0.7.4...v0.7.5) (2023-07-17)
|
2
16
|
|
3
17
|
|
package/definitions.d.ts
CHANGED
@@ -1,8 +1,5 @@
|
|
1
|
-
declare module '
|
2
|
-
export
|
3
|
-
getCurrentIdentity(): Promise<any>;
|
4
|
-
getMemberships(identityId: string): Promise<any>;
|
5
|
-
}
|
1
|
+
declare module 'recursive-watch' {
|
2
|
+
export default function watch(path:string, callback:(filename:string) => void):() => void;
|
6
3
|
}
|
7
4
|
|
8
5
|
declare module '@kapeta/nodejs-registry-utils' {
|
@@ -16,7 +13,7 @@ declare module '@kapeta/nodejs-registry-utils' {
|
|
16
13
|
export class RegistryService {
|
17
14
|
constructor(url: string);
|
18
15
|
|
19
|
-
|
16
|
+
getVersion(fullName: string, version: string): Promise<AssetVersion>;
|
20
17
|
}
|
21
18
|
|
22
19
|
export const Config: any;
|
package/dist/cjs/index.js
CHANGED
@@ -19,6 +19,8 @@ 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 routes_9 = __importDefault(require("./src/attachments/routes"));
|
23
|
+
const utils_1 = require("./src/utils/utils");
|
22
24
|
let currentServer = null;
|
23
25
|
function createServer() {
|
24
26
|
const app = (0, express_1.default)();
|
@@ -30,11 +32,12 @@ function createServer() {
|
|
30
32
|
app.use('/files', routes_6.default);
|
31
33
|
app.use('/assets', routes_7.default);
|
32
34
|
app.use('/providers', routes_8.default);
|
33
|
-
app.use('/',
|
34
|
-
|
35
|
-
|
35
|
+
app.use('/attachments', routes_9.default);
|
36
|
+
app.use('/', (req, res) => {
|
37
|
+
console.error('Invalid request: %s %s', req.method, req.originalUrl);
|
38
|
+
res.status(400).send({
|
36
39
|
ok: false,
|
37
|
-
error:
|
40
|
+
error: 'Unknown'
|
38
41
|
});
|
39
42
|
});
|
40
43
|
const server = http_1.default.createServer(app);
|
@@ -90,7 +93,7 @@ exports.default = {
|
|
90
93
|
if (clusterHost !== host) {
|
91
94
|
storageService_1.storageService.put('cluster', 'host', host);
|
92
95
|
}
|
93
|
-
return new Promise((resolve, reject) => {
|
96
|
+
return new Promise(async (resolve, reject) => {
|
94
97
|
if (!currentServer) {
|
95
98
|
reject(new Error(`Current server wasn't set`));
|
96
99
|
return;
|
@@ -102,7 +105,8 @@ exports.default = {
|
|
102
105
|
}
|
103
106
|
reject(err);
|
104
107
|
});
|
105
|
-
|
108
|
+
const bindHost = (0, utils_1.getBindHost)(host);
|
109
|
+
currentServer.listen(port, bindHost, () => resolve({ host, port, dockerStatus: containerManager_1.containerManager.isAlive() }));
|
106
110
|
currentServer.host = host;
|
107
111
|
currentServer.port = port;
|
108
112
|
});
|
@@ -0,0 +1,63 @@
|
|
1
|
+
"use strict";
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
4
|
+
};
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
6
|
+
const express_promise_router_1 = __importDefault(require("express-promise-router"));
|
7
|
+
const nodejs_api_client_1 = require("@kapeta/nodejs-api-client");
|
8
|
+
const cors_1 = require("../middleware/cors");
|
9
|
+
const storageService_1 = require("../storageService");
|
10
|
+
const router = (0, express_promise_router_1.default)();
|
11
|
+
const api = new nodejs_api_client_1.KapetaAPI();
|
12
|
+
const DEFAULT_REGISTRY_BASE = 'https://registry.kapeta.com';
|
13
|
+
function getBaseUrl() {
|
14
|
+
const endpoint = storageService_1.storageService.get('endpoints', 'registry', DEFAULT_REGISTRY_BASE);
|
15
|
+
return `${endpoint}/v1/registry`;
|
16
|
+
}
|
17
|
+
router.use('/', cors_1.corsHandler);
|
18
|
+
router.put('/:handle/:name', async (req, res) => {
|
19
|
+
const endpoint = getBaseUrl();
|
20
|
+
if (!req.headers['content-type']) {
|
21
|
+
res.status(400).send({
|
22
|
+
status: 400,
|
23
|
+
error: 'Missing content-type header'
|
24
|
+
});
|
25
|
+
return;
|
26
|
+
}
|
27
|
+
if (!req.headers['content-length']) {
|
28
|
+
res.status(400).send({
|
29
|
+
status: 400,
|
30
|
+
error: 'Missing content-length header'
|
31
|
+
});
|
32
|
+
return;
|
33
|
+
}
|
34
|
+
if (!req.headers['content-disposition']) {
|
35
|
+
res.status(400).send({
|
36
|
+
status: 400,
|
37
|
+
error: 'Missing content-disposition header'
|
38
|
+
});
|
39
|
+
return;
|
40
|
+
}
|
41
|
+
try {
|
42
|
+
const { handle, name } = req.params;
|
43
|
+
const url = `${endpoint}/${handle}/${name}/attachments`;
|
44
|
+
console.log('Sending PUT', url);
|
45
|
+
const result = await api.send({
|
46
|
+
method: 'PUT',
|
47
|
+
url,
|
48
|
+
auth: true,
|
49
|
+
headers: {
|
50
|
+
'content-type': req.headers['content-type'],
|
51
|
+
'content-length': req.headers['content-length'],
|
52
|
+
'content-disposition': req.headers['content-disposition'],
|
53
|
+
},
|
54
|
+
body: req
|
55
|
+
});
|
56
|
+
console.log('Got result from upload', result);
|
57
|
+
res.send(result);
|
58
|
+
}
|
59
|
+
catch (e) {
|
60
|
+
res.status(e.status ?? 500).send(e);
|
61
|
+
}
|
62
|
+
});
|
63
|
+
exports.default = router;
|
@@ -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:
|
186
|
+
HostIp: bindHost,
|
181
187
|
},
|
182
188
|
];
|
183
189
|
Labels[LABEL_PORT_PREFIX + portInfo.hostPort] = portInfo.type;
|
@@ -10,9 +10,19 @@ const router = (0, express_promise_router_1.default)();
|
|
10
10
|
const api = new nodejs_api_client_1.KapetaAPI();
|
11
11
|
router.use('/', cors_1.corsHandler);
|
12
12
|
router.get('/current', async (req, res) => {
|
13
|
-
|
13
|
+
try {
|
14
|
+
res.send(await api.getCurrentIdentity());
|
15
|
+
}
|
16
|
+
catch (e) {
|
17
|
+
res.status(e.status ?? 500).send(e);
|
18
|
+
}
|
14
19
|
});
|
15
20
|
router.get('/:identityId/memberships', async (req, res) => {
|
16
|
-
|
21
|
+
try {
|
22
|
+
res.send(await api.getMemberships(req.params.identityId));
|
23
|
+
}
|
24
|
+
catch (e) {
|
25
|
+
res.status(e.status ?? 500).send(e);
|
26
|
+
}
|
17
27
|
});
|
18
28
|
exports.default = router;
|
@@ -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 =
|
43
|
-
|
44
|
-
|
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
|
-
|
72
|
-
|
73
|
-
|
47
|
+
const [handle, name, version] = filename.toString().split(/\//g);
|
48
|
+
if (!name || !version) {
|
49
|
+
return;
|
74
50
|
}
|
75
|
-
|
76
|
-
//Other definition was added / updated - ignore
|
51
|
+
if (!this.changeEventsEnabled) {
|
77
52
|
return;
|
78
53
|
}
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
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
|
-
|
89
|
-
|
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
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
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
|
105
|
+
this.watcher();
|
106
106
|
this.watcher = undefined;
|
107
107
|
}
|
108
108
|
async _install(refs) {
|
@@ -9,7 +9,7 @@ declare class StorageService {
|
|
9
9
|
_writeConfig(): void;
|
10
10
|
section<T = any>(section: string, defaultValue?: any): T;
|
11
11
|
put(section: string, property: string | any, value?: any): void;
|
12
|
-
get(section: string, property?: string):
|
12
|
+
get<T = any>(section: string, property?: string, defaultValue?: T): T | undefined;
|
13
13
|
contains(section: string, property: string): any;
|
14
14
|
ensure(section: string, property: string, value: any): any;
|
15
15
|
}
|
@@ -47,10 +47,13 @@ class StorageService {
|
|
47
47
|
this.section(section)[property] = value;
|
48
48
|
this._writeConfig();
|
49
49
|
}
|
50
|
-
get(section, property) {
|
50
|
+
get(section, property, defaultValue) {
|
51
51
|
if (!property) {
|
52
52
|
return this.section(section);
|
53
53
|
}
|
54
|
+
if (!this.contains(section, property)) {
|
55
|
+
return defaultValue;
|
56
|
+
}
|
54
57
|
return this.section(section)[property];
|
55
58
|
}
|
56
59
|
contains(section, property) {
|
@@ -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:
|
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:
|
408
|
+
HostIp: bindHost,
|
407
409
|
HostPort: `${publicPort}`,
|
408
410
|
},
|
409
411
|
];
|
@@ -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,8 @@ 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 AttachmentRoutes from './src/attachments/routes';
|
18
|
+
import { getBindHost } from './src/utils/utils';
|
17
19
|
let currentServer = null;
|
18
20
|
function createServer() {
|
19
21
|
const app = express();
|
@@ -25,11 +27,12 @@ function createServer() {
|
|
25
27
|
app.use('/files', FilesystemRoutes);
|
26
28
|
app.use('/assets', AssetsRoutes);
|
27
29
|
app.use('/providers', ProviderRoutes);
|
28
|
-
app.use('/',
|
29
|
-
|
30
|
-
|
30
|
+
app.use('/attachments', AttachmentRoutes);
|
31
|
+
app.use('/', (req, res) => {
|
32
|
+
console.error('Invalid request: %s %s', req.method, req.originalUrl);
|
33
|
+
res.status(400).send({
|
31
34
|
ok: false,
|
32
|
-
error:
|
35
|
+
error: 'Unknown'
|
33
36
|
});
|
34
37
|
});
|
35
38
|
const server = HTTP.createServer(app);
|
@@ -85,7 +88,7 @@ export default {
|
|
85
88
|
if (clusterHost !== host) {
|
86
89
|
storageService.put('cluster', 'host', host);
|
87
90
|
}
|
88
|
-
return new Promise((resolve, reject) => {
|
91
|
+
return new Promise(async (resolve, reject) => {
|
89
92
|
if (!currentServer) {
|
90
93
|
reject(new Error(`Current server wasn't set`));
|
91
94
|
return;
|
@@ -97,7 +100,8 @@ export default {
|
|
97
100
|
}
|
98
101
|
reject(err);
|
99
102
|
});
|
100
|
-
|
103
|
+
const bindHost = getBindHost(host);
|
104
|
+
currentServer.listen(port, bindHost, () => resolve({ host, port, dockerStatus: containerManager.isAlive() }));
|
101
105
|
currentServer.host = host;
|
102
106
|
currentServer.port = port;
|
103
107
|
});
|
@@ -0,0 +1,58 @@
|
|
1
|
+
import Router from 'express-promise-router';
|
2
|
+
import { KapetaAPI } from '@kapeta/nodejs-api-client';
|
3
|
+
import { corsHandler } from '../middleware/cors';
|
4
|
+
import { storageService } from "../storageService";
|
5
|
+
const router = Router();
|
6
|
+
const api = new KapetaAPI();
|
7
|
+
const DEFAULT_REGISTRY_BASE = 'https://registry.kapeta.com';
|
8
|
+
function getBaseUrl() {
|
9
|
+
const endpoint = storageService.get('endpoints', 'registry', DEFAULT_REGISTRY_BASE);
|
10
|
+
return `${endpoint}/v1/registry`;
|
11
|
+
}
|
12
|
+
router.use('/', corsHandler);
|
13
|
+
router.put('/:handle/:name', async (req, res) => {
|
14
|
+
const endpoint = getBaseUrl();
|
15
|
+
if (!req.headers['content-type']) {
|
16
|
+
res.status(400).send({
|
17
|
+
status: 400,
|
18
|
+
error: 'Missing content-type header'
|
19
|
+
});
|
20
|
+
return;
|
21
|
+
}
|
22
|
+
if (!req.headers['content-length']) {
|
23
|
+
res.status(400).send({
|
24
|
+
status: 400,
|
25
|
+
error: 'Missing content-length header'
|
26
|
+
});
|
27
|
+
return;
|
28
|
+
}
|
29
|
+
if (!req.headers['content-disposition']) {
|
30
|
+
res.status(400).send({
|
31
|
+
status: 400,
|
32
|
+
error: 'Missing content-disposition header'
|
33
|
+
});
|
34
|
+
return;
|
35
|
+
}
|
36
|
+
try {
|
37
|
+
const { handle, name } = req.params;
|
38
|
+
const url = `${endpoint}/${handle}/${name}/attachments`;
|
39
|
+
console.log('Sending PUT', url);
|
40
|
+
const result = await api.send({
|
41
|
+
method: 'PUT',
|
42
|
+
url,
|
43
|
+
auth: true,
|
44
|
+
headers: {
|
45
|
+
'content-type': req.headers['content-type'],
|
46
|
+
'content-length': req.headers['content-length'],
|
47
|
+
'content-disposition': req.headers['content-disposition'],
|
48
|
+
},
|
49
|
+
body: req
|
50
|
+
});
|
51
|
+
console.log('Got result from upload', result);
|
52
|
+
res.send(result);
|
53
|
+
}
|
54
|
+
catch (e) {
|
55
|
+
res.status(e.status ?? 500).send(e);
|
56
|
+
}
|
57
|
+
});
|
58
|
+
export default router;
|
@@ -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:
|
180
|
+
HostIp: bindHost,
|
175
181
|
},
|
176
182
|
];
|
177
183
|
Labels[LABEL_PORT_PREFIX + portInfo.hostPort] = portInfo.type;
|
@@ -5,9 +5,19 @@ const router = Router();
|
|
5
5
|
const api = new KapetaAPI();
|
6
6
|
router.use('/', corsHandler);
|
7
7
|
router.get('/current', async (req, res) => {
|
8
|
-
|
8
|
+
try {
|
9
|
+
res.send(await api.getCurrentIdentity());
|
10
|
+
}
|
11
|
+
catch (e) {
|
12
|
+
res.status(e.status ?? 500).send(e);
|
13
|
+
}
|
9
14
|
});
|
10
15
|
router.get('/:identityId/memberships', async (req, res) => {
|
11
|
-
|
16
|
+
try {
|
17
|
+
res.send(await api.getMemberships(req.params.identityId));
|
18
|
+
}
|
19
|
+
catch (e) {
|
20
|
+
res.status(e.status ?? 500).send(e);
|
21
|
+
}
|
12
22
|
});
|
13
23
|
export default router;
|
@@ -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 =
|
37
|
-
|
38
|
-
|
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
|
-
|
66
|
-
|
67
|
-
|
41
|
+
const [handle, name, version] = filename.toString().split(/\//g);
|
42
|
+
if (!name || !version) {
|
43
|
+
return;
|
68
44
|
}
|
69
|
-
|
70
|
-
//Other definition was added / updated - ignore
|
45
|
+
if (!this.changeEventsEnabled) {
|
71
46
|
return;
|
72
47
|
}
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
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
|
-
|
83
|
-
|
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
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
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
|
99
|
+
this.watcher();
|
100
100
|
this.watcher = undefined;
|
101
101
|
}
|
102
102
|
async _install(refs) {
|
@@ -9,7 +9,7 @@ declare class StorageService {
|
|
9
9
|
_writeConfig(): void;
|
10
10
|
section<T = any>(section: string, defaultValue?: any): T;
|
11
11
|
put(section: string, property: string | any, value?: any): void;
|
12
|
-
get(section: string, property?: string):
|
12
|
+
get<T = any>(section: string, property?: string, defaultValue?: T): T | undefined;
|
13
13
|
contains(section: string, property: string): any;
|
14
14
|
ensure(section: string, property: string, value: any): any;
|
15
15
|
}
|
@@ -41,10 +41,13 @@ class StorageService {
|
|
41
41
|
this.section(section)[property] = value;
|
42
42
|
this._writeConfig();
|
43
43
|
}
|
44
|
-
get(section, property) {
|
44
|
+
get(section, property, defaultValue) {
|
45
45
|
if (!property) {
|
46
46
|
return this.section(section);
|
47
47
|
}
|
48
|
+
if (!this.contains(section, property)) {
|
49
|
+
return defaultValue;
|
50
|
+
}
|
48
51
|
return this.section(section)[property];
|
49
52
|
}
|
50
53
|
contains(section, property) {
|
@@ -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:
|
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:
|
402
|
+
HostIp: bindHost,
|
401
403
|
HostPort: `${publicPort}`,
|
402
404
|
},
|
403
405
|
];
|
@@ -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,8 @@ 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 AttachmentRoutes from './src/attachments/routes';
|
19
|
+
import { getBindHost } from './src/utils/utils';
|
18
20
|
|
19
21
|
export type LocalClusterService = HTTP.Server & { host?: string; port?: number };
|
20
22
|
|
@@ -32,11 +34,12 @@ function createServer() {
|
|
32
34
|
app.use('/files', FilesystemRoutes);
|
33
35
|
app.use('/assets', AssetsRoutes);
|
34
36
|
app.use('/providers', ProviderRoutes);
|
35
|
-
app.use('/',
|
36
|
-
|
37
|
-
|
37
|
+
app.use('/attachments', AttachmentRoutes);
|
38
|
+
app.use('/', (req: express.Request, res: express.Response) => {
|
39
|
+
console.error('Invalid request: %s %s', req.method, req.originalUrl);
|
40
|
+
res.status(400).send({
|
38
41
|
ok: false,
|
39
|
-
error:
|
42
|
+
error: 'Unknown'
|
40
43
|
});
|
41
44
|
});
|
42
45
|
const server = HTTP.createServer(app);
|
@@ -108,7 +111,7 @@ export default {
|
|
108
111
|
storageService.put('cluster', 'host', host);
|
109
112
|
}
|
110
113
|
|
111
|
-
return new Promise((resolve, reject) => {
|
114
|
+
return new Promise(async (resolve, reject) => {
|
112
115
|
if (!currentServer) {
|
113
116
|
reject(new Error(`Current server wasn't set`));
|
114
117
|
return;
|
@@ -121,7 +124,9 @@ export default {
|
|
121
124
|
reject(err);
|
122
125
|
});
|
123
126
|
|
124
|
-
|
127
|
+
const bindHost = getBindHost(host);
|
128
|
+
|
129
|
+
currentServer.listen(port, bindHost, () => resolve({ host, port, dockerStatus: containerManager.isAlive() }));
|
125
130
|
currentServer.host = host;
|
126
131
|
currentServer.port = port;
|
127
132
|
});
|
package/package.json
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
{
|
2
2
|
"name": "@kapeta/local-cluster-service",
|
3
|
-
"version": "0.
|
3
|
+
"version": "0.8.0",
|
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",
|
@@ -0,0 +1,68 @@
|
|
1
|
+
import Router from 'express-promise-router';
|
2
|
+
import { KapetaAPI } from '@kapeta/nodejs-api-client';
|
3
|
+
|
4
|
+
import { corsHandler } from '../middleware/cors';
|
5
|
+
import { Request, Response } from 'express';
|
6
|
+
import {storageService} from "../storageService";
|
7
|
+
|
8
|
+
const router = Router();
|
9
|
+
const api = new KapetaAPI();
|
10
|
+
|
11
|
+
const DEFAULT_REGISTRY_BASE = 'https://registry.kapeta.com';
|
12
|
+
|
13
|
+
function getBaseUrl() {
|
14
|
+
const endpoint = storageService.get('endpoints', 'registry', DEFAULT_REGISTRY_BASE);
|
15
|
+
return `${endpoint}/v1/registry`;
|
16
|
+
}
|
17
|
+
|
18
|
+
router.use('/', corsHandler);
|
19
|
+
|
20
|
+
router.put('/:handle/:name', async (req: Request, res: Response) => {
|
21
|
+
const endpoint = getBaseUrl();
|
22
|
+
if (!req.headers['content-type']) {
|
23
|
+
res.status(400).send({
|
24
|
+
status: 400,
|
25
|
+
error: 'Missing content-type header'
|
26
|
+
});
|
27
|
+
return;
|
28
|
+
}
|
29
|
+
|
30
|
+
if (!req.headers['content-length']) {
|
31
|
+
res.status(400).send({
|
32
|
+
status: 400,
|
33
|
+
error: 'Missing content-length header'
|
34
|
+
});
|
35
|
+
return;
|
36
|
+
}
|
37
|
+
|
38
|
+
if (!req.headers['content-disposition']) {
|
39
|
+
res.status(400).send({
|
40
|
+
status: 400,
|
41
|
+
error: 'Missing content-disposition header'
|
42
|
+
});
|
43
|
+
return;
|
44
|
+
}
|
45
|
+
|
46
|
+
try {
|
47
|
+
const {handle, name} = req.params;
|
48
|
+
const url = `${endpoint}/${handle}/${name}/attachments`;
|
49
|
+
console.log('Sending PUT', url);
|
50
|
+
const result = await api.send<{url:string}>({
|
51
|
+
method: 'PUT',
|
52
|
+
url,
|
53
|
+
auth: true,
|
54
|
+
headers: {
|
55
|
+
'content-type': req.headers['content-type'],
|
56
|
+
'content-length': req.headers['content-length'],
|
57
|
+
'content-disposition': req.headers['content-disposition'],
|
58
|
+
},
|
59
|
+
body: req
|
60
|
+
});
|
61
|
+
console.log('Got result from upload', result);
|
62
|
+
res.send(result);
|
63
|
+
} catch (e:any) {
|
64
|
+
res.status(e.status ?? 500).send(e);
|
65
|
+
}
|
66
|
+
});
|
67
|
+
|
68
|
+
export default router;
|
package/src/containerManager.ts
CHANGED
@@ -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:
|
243
|
+
HostIp: bindHost,
|
237
244
|
},
|
238
245
|
];
|
239
246
|
|
package/src/identities/routes.ts
CHANGED
@@ -10,11 +10,20 @@ const api = new KapetaAPI();
|
|
10
10
|
router.use('/', corsHandler);
|
11
11
|
|
12
12
|
router.get('/current', async (req: Request, res: Response) => {
|
13
|
-
|
13
|
+
try {
|
14
|
+
res.send(await api.getCurrentIdentity());
|
15
|
+
} catch (e:any) {
|
16
|
+
res.status(e.status ?? 500).send(e);
|
17
|
+
}
|
18
|
+
|
14
19
|
});
|
15
20
|
|
16
21
|
router.get('/:identityId/memberships', async (req: Request, res: Response) => {
|
17
|
-
|
22
|
+
try {
|
23
|
+
res.send(await api.getMemberships(req.params.identityId));
|
24
|
+
} catch (e:any) {
|
25
|
+
res.status(e.status ?? 500).send(e);
|
26
|
+
}
|
18
27
|
});
|
19
28
|
|
20
29
|
export default router;
|
package/src/repositoryManager.ts
CHANGED
@@ -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?:
|
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 =
|
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
|
110
|
+
this.watcher();
|
111
111
|
this.watcher = undefined;
|
112
112
|
}
|
113
113
|
|
package/src/storageService.ts
CHANGED
@@ -53,11 +53,15 @@ class StorageService {
|
|
53
53
|
this._writeConfig();
|
54
54
|
}
|
55
55
|
|
56
|
-
get(section: string, property?: string):
|
56
|
+
get<T = any>(section: string, property?: string, defaultValue?:T): T|undefined {
|
57
57
|
if (!property) {
|
58
58
|
return this.section(section);
|
59
59
|
}
|
60
60
|
|
61
|
+
if (!this.contains(section, property)) {
|
62
|
+
return defaultValue;
|
63
|
+
}
|
64
|
+
|
61
65
|
return this.section(section)[property];
|
62
66
|
}
|
63
67
|
|
@@ -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:
|
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:
|
489
|
+
HostIp: bindHost,
|
486
490
|
HostPort: `${publicPort}`,
|
487
491
|
},
|
488
492
|
];
|
package/src/utils/utils.ts
CHANGED
@@ -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
|
+
}
|