@kapeta/local-cluster-service 0.0.60
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/.github/workflows/pr-check.yml +18 -0
- package/.github/workflows/publish.yml +20 -0
- package/.vscode/launch.json +17 -0
- package/LICENSE +21 -0
- package/README.md +32 -0
- package/definitions.d.ts +42 -0
- package/index.js +127 -0
- package/package.json +45 -0
- package/src/assetManager.js +249 -0
- package/src/assets/routes.js +133 -0
- package/src/clusterService.js +134 -0
- package/src/codeGeneratorManager.js +40 -0
- package/src/config/routes.js +118 -0
- package/src/configManager.js +128 -0
- package/src/containerManager.js +279 -0
- package/src/filesystem/routes.js +74 -0
- package/src/filesystemManager.js +89 -0
- package/src/identities/routes.js +19 -0
- package/src/instanceManager.js +416 -0
- package/src/instances/routes.js +117 -0
- package/src/middleware/cors.js +7 -0
- package/src/middleware/kapeta.js +20 -0
- package/src/middleware/stringBody.js +11 -0
- package/src/networkManager.js +120 -0
- package/src/operatorManager.js +180 -0
- package/src/providerManager.js +84 -0
- package/src/providers/routes.js +26 -0
- package/src/proxy/routes.js +125 -0
- package/src/proxy/types/rest.js +163 -0
- package/src/proxy/types/web.js +83 -0
- package/src/serviceManager.js +117 -0
- package/src/socketManager.js +50 -0
- package/src/storageService.js +87 -0
- package/src/traffic/routes.js +18 -0
- package/src/utils/pathTemplateParser.js +116 -0
- package/start.js +7 -0
@@ -0,0 +1,18 @@
|
|
1
|
+
name: Check
|
2
|
+
|
3
|
+
on: [pull_request]
|
4
|
+
|
5
|
+
jobs:
|
6
|
+
build:
|
7
|
+
|
8
|
+
runs-on: ubuntu-latest
|
9
|
+
|
10
|
+
steps:
|
11
|
+
- uses: actions/checkout@v2
|
12
|
+
- uses: actions/setup-node@v3
|
13
|
+
with:
|
14
|
+
node-version: 18
|
15
|
+
registry-url: https://registry.npmjs.org/
|
16
|
+
- run: npm install
|
17
|
+
env:
|
18
|
+
CI: true
|
@@ -0,0 +1,20 @@
|
|
1
|
+
name: Publish to Kapeta Public NPM
|
2
|
+
on:
|
3
|
+
push:
|
4
|
+
branches:
|
5
|
+
- master
|
6
|
+
jobs:
|
7
|
+
build:
|
8
|
+
runs-on: ubuntu-latest
|
9
|
+
steps:
|
10
|
+
- uses: actions/checkout@v2
|
11
|
+
- uses: actions/setup-node@v3
|
12
|
+
with:
|
13
|
+
node-version: 18
|
14
|
+
registry-url: https://registry.npmjs.org/
|
15
|
+
scope: '@kapeta'
|
16
|
+
|
17
|
+
- run: npm install
|
18
|
+
- run: npm publish --access public
|
19
|
+
env:
|
20
|
+
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
|
@@ -0,0 +1,17 @@
|
|
1
|
+
{
|
2
|
+
// Use IntelliSense to learn about possible attributes.
|
3
|
+
// Hover to view descriptions of existing attributes.
|
4
|
+
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
|
5
|
+
"version": "0.2.0",
|
6
|
+
"configurations": [
|
7
|
+
{
|
8
|
+
"type": "node",
|
9
|
+
"request": "launch",
|
10
|
+
"name": "Launch Program",
|
11
|
+
"skipFiles": [
|
12
|
+
"<node_internals>/**"
|
13
|
+
],
|
14
|
+
"program": "${workspaceFolder}/start.js"
|
15
|
+
}
|
16
|
+
]
|
17
|
+
}
|
package/LICENSE
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
MIT License
|
2
|
+
|
3
|
+
Copyright (c) 2019 blockwarecom
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
13
|
+
copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
@@ -0,0 +1,32 @@
|
|
1
|
+
## Local cluster service for Kapeta
|
2
|
+
|
3
|
+
This service is a multi-functional service for simulating a "real" cluster - specifically during
|
4
|
+
local development.
|
5
|
+
|
6
|
+
### Features
|
7
|
+
|
8
|
+
#### Configuration Service
|
9
|
+
Provides configuration management for local services to simplify configuring local instances and
|
10
|
+
also auto-generates configuration as part of its service discovery and routing capabilities.
|
11
|
+
|
12
|
+
#### Service Discovery
|
13
|
+
Also provides simple service-discovery through its control over configuration - by simply controlling
|
14
|
+
where services find other services. This is also how it injects itself as a MITM proxy for all local
|
15
|
+
traffic and how we intend to achieve "local -> remote" and "remote -> local" tunneling in the future.
|
16
|
+
|
17
|
+
#### Local Proxy
|
18
|
+
The service also provides a local proxy server that enables fine-grained routing and traffic-inspection.
|
19
|
+
The only protocol currently supported is HTTP and REST-JSON but the intention is to add support for
|
20
|
+
several others such as MySQL, PostgreSQL, MongoDB, Redis and more.
|
21
|
+
|
22
|
+
#### Local Metrics (Not implemented)
|
23
|
+
The local cluster service should also support metrics reporting from the
|
24
|
+
local instances to make testing and checking metrics for your local environment straight-forward.
|
25
|
+
|
26
|
+
#### Remote Tunnel (Not implemented)
|
27
|
+
It's able to connect to a remote cluster and override certain endpoints conditionally in that cluster to
|
28
|
+
make them point to itself.
|
29
|
+
|
30
|
+
This is to allow these scenarios:
|
31
|
+
- Testing in production for a subset of users (Or just your own)
|
32
|
+
- Development against a team or private sandbox to avoid running every service locally
|
package/definitions.d.ts
ADDED
@@ -0,0 +1,42 @@
|
|
1
|
+
interface Request {
|
2
|
+
method: string
|
3
|
+
stringBody: string
|
4
|
+
headers: {[key:string]:string}
|
5
|
+
params: {[key:string]:string}
|
6
|
+
}
|
7
|
+
|
8
|
+
interface Response {
|
9
|
+
headers: {[key:string]:string}
|
10
|
+
status: (status:number) => void
|
11
|
+
send: (body:any) => void
|
12
|
+
end: () => void
|
13
|
+
set: (headers:{[key:string]:string}) => void
|
14
|
+
}
|
15
|
+
|
16
|
+
interface ResourceRef {
|
17
|
+
blockId:string
|
18
|
+
resourceName:string
|
19
|
+
}
|
20
|
+
|
21
|
+
declare function ProxyRequestHandler(req:Request, res:Response, info:ProxyRequestInfo);
|
22
|
+
|
23
|
+
|
24
|
+
interface Connection {
|
25
|
+
mapping: any
|
26
|
+
from: ResourceRef
|
27
|
+
to: ResourceRef
|
28
|
+
}
|
29
|
+
|
30
|
+
interface ResourceInfo {
|
31
|
+
spec:any
|
32
|
+
metadata:any
|
33
|
+
kind:string
|
34
|
+
}
|
35
|
+
|
36
|
+
interface ProxyRequestInfo {
|
37
|
+
address: string
|
38
|
+
connection:Connection
|
39
|
+
fromResource:ResourceInfo
|
40
|
+
toResource:ResourceInfo
|
41
|
+
consumerPath:string
|
42
|
+
}
|
package/index.js
ADDED
@@ -0,0 +1,127 @@
|
|
1
|
+
const clusterService = require('./src/clusterService');
|
2
|
+
const storageService = require('./src/storageService');
|
3
|
+
const serviceManager = require('./src/serviceManager');
|
4
|
+
const socketManager = require('./src/socketManager');
|
5
|
+
const containerManager = require('./src/containerManager');
|
6
|
+
const express = require('express');
|
7
|
+
const HTTP = require('http');
|
8
|
+
const {Server} = require("socket.io");
|
9
|
+
|
10
|
+
let currentServer = null;
|
11
|
+
|
12
|
+
function createServer() {
|
13
|
+
const app = express();
|
14
|
+
app.use('/traffic', require('./src/traffic/routes'));
|
15
|
+
app.use('/proxy', require('./src/proxy/routes'));
|
16
|
+
app.use('/config', require('./src/config/routes'));
|
17
|
+
app.use('/instances', require('./src/instances/routes'));
|
18
|
+
app.use('/identities', require('./src/identities/routes'));
|
19
|
+
app.use('/files', require('./src/filesystem/routes'));
|
20
|
+
app.use('/assets', require('./src/assets/routes'));
|
21
|
+
app.use('/providers', require('./src/providers/routes'));
|
22
|
+
app.use('/', (err, req, res, next) => {
|
23
|
+
console.error('Request failed: %s %s', req.method, req.originalUrl, err.stack);
|
24
|
+
res.status(500).send({
|
25
|
+
ok: false,
|
26
|
+
error: err.error ?? err.message
|
27
|
+
})
|
28
|
+
});
|
29
|
+
const server = HTTP.createServer(app);
|
30
|
+
|
31
|
+
//socket
|
32
|
+
const io = new Server(server, {
|
33
|
+
cors: {
|
34
|
+
//TODO: This should'nt be hardcoded but also shouldn't be "*"
|
35
|
+
origin: "http://localhost:8080"
|
36
|
+
}
|
37
|
+
});
|
38
|
+
socketManager.setIo(io);
|
39
|
+
return server;
|
40
|
+
}
|
41
|
+
|
42
|
+
module.exports = {
|
43
|
+
|
44
|
+
isRunning: function() {
|
45
|
+
return !!currentServer;
|
46
|
+
},
|
47
|
+
|
48
|
+
getCurrentPort: function() {
|
49
|
+
if (!currentServer) {
|
50
|
+
return -1;
|
51
|
+
}
|
52
|
+
|
53
|
+
return currentServer.port;
|
54
|
+
},
|
55
|
+
|
56
|
+
/**
|
57
|
+
* Starts the local cluster service.
|
58
|
+
* @return {Promise<Integer>} resolves when listening is done with port number. Rejects if listening failed.
|
59
|
+
*/
|
60
|
+
start: async function() {
|
61
|
+
if (currentServer) {
|
62
|
+
throw new Error('Server already started');
|
63
|
+
}
|
64
|
+
|
65
|
+
try {
|
66
|
+
await containerManager.ping()
|
67
|
+
} catch (e) {
|
68
|
+
throw new Error('Could not ping docker runtime: ' + e.toString() + '. Make sure docker is running and working.');
|
69
|
+
}
|
70
|
+
|
71
|
+
const clusterPort = storageService.get('cluster','port');
|
72
|
+
if (clusterPort) {
|
73
|
+
clusterService.setClusterServicePort(clusterPort);
|
74
|
+
}
|
75
|
+
|
76
|
+
const clusterHost = storageService.get('cluster','host');
|
77
|
+
if (clusterHost) {
|
78
|
+
clusterService.setClusterServiceHost(clusterHost);
|
79
|
+
}
|
80
|
+
|
81
|
+
await clusterService.init();
|
82
|
+
|
83
|
+
currentServer = createServer();
|
84
|
+
|
85
|
+
const port = clusterService.getClusterServicePort();
|
86
|
+
|
87
|
+
const host = clusterService.getClusterServiceHost();
|
88
|
+
|
89
|
+
if (clusterPort !== port) {
|
90
|
+
storageService.put('cluster','port', port);
|
91
|
+
}
|
92
|
+
|
93
|
+
if (clusterHost !== host) {
|
94
|
+
storageService.put('cluster','host', host);
|
95
|
+
}
|
96
|
+
|
97
|
+
return new Promise((resolve, reject) => {
|
98
|
+
|
99
|
+
currentServer.once('error', (err) => {
|
100
|
+
currentServer.close();
|
101
|
+
currentServer = null;
|
102
|
+
reject(err);
|
103
|
+
});
|
104
|
+
|
105
|
+
currentServer.listen(port, host, () => resolve({host,port}));
|
106
|
+
currentServer.host = host;
|
107
|
+
currentServer.port = port;
|
108
|
+
});
|
109
|
+
},
|
110
|
+
|
111
|
+
/**
|
112
|
+
* Stops any currently running cluster services.
|
113
|
+
* @return {Promise<boolean>} Returns true if the service was stopped - false if no service was running.
|
114
|
+
*/
|
115
|
+
stop: function() {
|
116
|
+
if (currentServer) {
|
117
|
+
return new Promise(function(resolve) {
|
118
|
+
currentServer.close(() => resolve(true));
|
119
|
+
currentServer = null;
|
120
|
+
});
|
121
|
+
}
|
122
|
+
|
123
|
+
return Promise.resolve(false);
|
124
|
+
},
|
125
|
+
getServices: () => serviceManager.getServices()
|
126
|
+
};
|
127
|
+
|
package/package.json
ADDED
@@ -0,0 +1,45 @@
|
|
1
|
+
{
|
2
|
+
"name": "@kapeta/local-cluster-service",
|
3
|
+
"version": "0.0.60",
|
4
|
+
"description": "Manages configuration, ports and service discovery for locally running Kapeta systems",
|
5
|
+
"main": "index.js",
|
6
|
+
"repository": {
|
7
|
+
"type": "git",
|
8
|
+
"url": "git+https://github.com/blockwarecom/local-cluster-service.git"
|
9
|
+
},
|
10
|
+
"keywords": [
|
11
|
+
"kapeta"
|
12
|
+
],
|
13
|
+
"author": "Henrik Hofmeister <hh@kapeta.com>",
|
14
|
+
"license": "MIT",
|
15
|
+
"bugs": {
|
16
|
+
"url": "https://github.com/blockwarecom/local-cluster-service/issues"
|
17
|
+
},
|
18
|
+
"scripts": {
|
19
|
+
"start": "nodemon start.js"
|
20
|
+
},
|
21
|
+
"homepage": "https://github.com/blockwarecom/local-cluster-service#readme",
|
22
|
+
"dependencies": {
|
23
|
+
"@kapeta/codegen": "<2",
|
24
|
+
"@kapeta/local-cluster-config": "<2",
|
25
|
+
"@kapeta/local-cluster-executor": "<2",
|
26
|
+
"@kapeta/nodejs-api-client": "<2",
|
27
|
+
"@kapeta/nodejs-utils": "<2",
|
28
|
+
"@kapeta/sdk-config": "<2",
|
29
|
+
"express": "4.17.1",
|
30
|
+
"express-promise-router": "^4.1.1",
|
31
|
+
"fs-extra": "^11.1.0",
|
32
|
+
"glob": "7.1.6",
|
33
|
+
"lodash": "^4.17.15",
|
34
|
+
"md5": "2.2.1",
|
35
|
+
"mkdirp": "0.5.1",
|
36
|
+
"node-docker-api": "1.1.22",
|
37
|
+
"node-uuid": "^1.4.8",
|
38
|
+
"request": "2.88.2",
|
39
|
+
"socket.io": "^4.5.2",
|
40
|
+
"yaml": "^1.6.0"
|
41
|
+
},
|
42
|
+
"devDependencies": {
|
43
|
+
"nodemon": "^2.0.2"
|
44
|
+
}
|
45
|
+
}
|
@@ -0,0 +1,249 @@
|
|
1
|
+
const Path = require("node:path");
|
2
|
+
const FS = require('node:fs');
|
3
|
+
const FSExtra = require('fs-extra');
|
4
|
+
const YAML = require('yaml');
|
5
|
+
const ClusterConfiguration = require('@kapeta/local-cluster-config');
|
6
|
+
const codeGeneratorManager = require('./codeGeneratorManager');
|
7
|
+
const socketManager = require('./socketManager');
|
8
|
+
|
9
|
+
function makeSymLink(directory, versionTarget) {
|
10
|
+
FSExtra.mkdirpSync(Path.dirname(versionTarget));
|
11
|
+
FSExtra.createSymlinkSync(directory, versionTarget);
|
12
|
+
}
|
13
|
+
|
14
|
+
function enrichAsset(asset) {
|
15
|
+
return {
|
16
|
+
ref: `kapeta://${asset.definition.metadata.name}:${asset.version}`,
|
17
|
+
editable: asset.version === 'local', //Only local versions are editable
|
18
|
+
exists: true,
|
19
|
+
version: asset.version,
|
20
|
+
kind: asset.definition.kind,
|
21
|
+
data: asset.definition,
|
22
|
+
path: asset.path,
|
23
|
+
ymlPath: asset.ymlPath
|
24
|
+
}
|
25
|
+
}
|
26
|
+
|
27
|
+
function compareRefs(a, b) {
|
28
|
+
const [aProtocol, aId] = parseRef(a);
|
29
|
+
const [bProtocol, bId] = parseRef(b);
|
30
|
+
|
31
|
+
return aProtocol === bProtocol && aId === bId;
|
32
|
+
}
|
33
|
+
function parseRef(ref) {
|
34
|
+
let out = ref.split(/:\/\//,2);
|
35
|
+
|
36
|
+
if (out.length === 1) {
|
37
|
+
return [
|
38
|
+
'kapeta',
|
39
|
+
ref.toLowerCase()
|
40
|
+
]
|
41
|
+
}
|
42
|
+
return [
|
43
|
+
out[0].toLowerCase(),
|
44
|
+
out[1].toLowerCase()
|
45
|
+
];
|
46
|
+
}
|
47
|
+
|
48
|
+
class AssetManager {
|
49
|
+
|
50
|
+
constructor() {
|
51
|
+
this.watcher = null;
|
52
|
+
this.listenForChanges();
|
53
|
+
}
|
54
|
+
|
55
|
+
listenForChanges() {
|
56
|
+
const baseDir = ClusterConfiguration.getRepositoryBasedir();
|
57
|
+
if (!FS.existsSync(baseDir)) {
|
58
|
+
FSExtra.mkdirpSync(baseDir);
|
59
|
+
}
|
60
|
+
|
61
|
+
let currentWebDefinitions = ClusterConfiguration
|
62
|
+
.getProviderDefinitions()
|
63
|
+
.filter(d => d.hasWeb);
|
64
|
+
|
65
|
+
console.log('Watching local repository for provider changes: %s', baseDir);
|
66
|
+
try {
|
67
|
+
this.watcher = FS.watch(baseDir, { recursive: true });
|
68
|
+
} catch (e) {
|
69
|
+
// Fallback to run without watch mode due to potential platform issues.
|
70
|
+
// https://nodejs.org/docs/latest/api/fs.html#caveats
|
71
|
+
console.log('Unable to watch for changes. Changes to assets will not update automatically.');
|
72
|
+
return;
|
73
|
+
}
|
74
|
+
this.watcher.on('change', (eventType, filename) => {
|
75
|
+
const [handle, name, version] = filename.split(/\//g);
|
76
|
+
if (!name || !version) {
|
77
|
+
return;
|
78
|
+
}
|
79
|
+
|
80
|
+
const ymlPath = Path.join(baseDir, handle, name, version, 'kapeta.yml');
|
81
|
+
const newWebDefinitions = ClusterConfiguration
|
82
|
+
.getProviderDefinitions()
|
83
|
+
.filter(d => d.hasWeb);
|
84
|
+
|
85
|
+
const newWebDefinition = newWebDefinitions.find(d => d.ymlPath === ymlPath);
|
86
|
+
let currentWebDefinition = currentWebDefinitions.find(d => d.ymlPath === ymlPath);
|
87
|
+
const ymlExists = FS.existsSync(ymlPath);
|
88
|
+
let type;
|
89
|
+
if (ymlExists) {
|
90
|
+
if (currentWebDefinition) {
|
91
|
+
type = 'updated';
|
92
|
+
} else if (newWebDefinition) {
|
93
|
+
type = 'added';
|
94
|
+
currentWebDefinition = newWebDefinition;
|
95
|
+
} else {
|
96
|
+
//Other definition was added / updated - ignore
|
97
|
+
return;
|
98
|
+
}
|
99
|
+
} else {
|
100
|
+
if (currentWebDefinition) {
|
101
|
+
//Something was removed
|
102
|
+
type = 'removed';
|
103
|
+
} else {
|
104
|
+
//Other definition was removed - ignore
|
105
|
+
return;
|
106
|
+
}
|
107
|
+
}
|
108
|
+
|
109
|
+
const payload = {type, definition: currentWebDefinition?.definition, asset: {handle, name, version} };
|
110
|
+
|
111
|
+
currentWebDefinitions = newWebDefinitions
|
112
|
+
|
113
|
+
socketManager.emit(`assets`, 'changed', payload);
|
114
|
+
});
|
115
|
+
}
|
116
|
+
|
117
|
+
stopListening() {
|
118
|
+
this.watcher.close();
|
119
|
+
this.watcher = null;
|
120
|
+
}
|
121
|
+
|
122
|
+
|
123
|
+
/**
|
124
|
+
*
|
125
|
+
* @param {string[]} [assetKinds]
|
126
|
+
* @returns {{path: *, ref: string, data: *, editable: boolean, kind: *, exists: boolean}[]}
|
127
|
+
*/
|
128
|
+
getAssets(assetKinds) {
|
129
|
+
if (!assetKinds) {
|
130
|
+
const blockTypeProviders = ClusterConfiguration.getDefinitions('core/block-type');
|
131
|
+
assetKinds = blockTypeProviders.map(p => {
|
132
|
+
return `${p.definition.metadata.name}:${p.version}`
|
133
|
+
});
|
134
|
+
assetKinds.push('core/plan');
|
135
|
+
}
|
136
|
+
|
137
|
+
const assets = ClusterConfiguration.getDefinitions(assetKinds);
|
138
|
+
|
139
|
+
return assets.map(enrichAsset);
|
140
|
+
}
|
141
|
+
|
142
|
+
getPlans() {
|
143
|
+
return this.getAssets(['core/plan']);
|
144
|
+
}
|
145
|
+
|
146
|
+
getPlan(ref) {
|
147
|
+
const asset = this.getAsset(ref);
|
148
|
+
|
149
|
+
if ('core/plan' !== asset.kind) {
|
150
|
+
throw new Error('Asset was not a plan: ' + ref);
|
151
|
+
}
|
152
|
+
|
153
|
+
return asset.data;
|
154
|
+
}
|
155
|
+
|
156
|
+
getAsset(ref) {
|
157
|
+
const asset = this.getAssets().find(a => compareRefs(a.ref,ref));
|
158
|
+
if (!asset) {
|
159
|
+
throw new Error('Asset not found: ' + ref);
|
160
|
+
}
|
161
|
+
|
162
|
+
return asset;
|
163
|
+
}
|
164
|
+
|
165
|
+
async createAsset(path, yaml) {
|
166
|
+
if (FS.existsSync(path)) {
|
167
|
+
throw new Error('File already exists: ' + path);
|
168
|
+
}
|
169
|
+
|
170
|
+
const dirName = Path.dirname(path);
|
171
|
+
if (!FS.existsSync(dirName)) {
|
172
|
+
FSExtra.mkdirpSync(dirName);
|
173
|
+
}
|
174
|
+
|
175
|
+
FS.writeFileSync(path, YAML.stringify(yaml));
|
176
|
+
|
177
|
+
const asset = await this.importFile(path);
|
178
|
+
|
179
|
+
if (codeGeneratorManager.canGenerateCode(yaml)) {
|
180
|
+
await codeGeneratorManager.generate(path, yaml);
|
181
|
+
}
|
182
|
+
|
183
|
+
return asset;
|
184
|
+
}
|
185
|
+
|
186
|
+
async updateAsset(ref, yaml) {
|
187
|
+
const asset = this.getAsset(ref);
|
188
|
+
if (!asset) {
|
189
|
+
throw new Error('Attempted to update unknown asset: ' + ref);
|
190
|
+
}
|
191
|
+
|
192
|
+
if (!asset.editable) {
|
193
|
+
throw new Error('Attempted to update read-only asset: ' + ref);
|
194
|
+
}
|
195
|
+
|
196
|
+
if (!asset.ymlPath) {
|
197
|
+
throw new Error('Attempted to update corrupted asset: ' + ref);
|
198
|
+
}
|
199
|
+
|
200
|
+
FS.writeFileSync(asset.ymlPath, YAML.stringify(yaml));
|
201
|
+
|
202
|
+
if (codeGeneratorManager.canGenerateCode(yaml)) {
|
203
|
+
await codeGeneratorManager.generate(asset.ymlPath, yaml);
|
204
|
+
} else {
|
205
|
+
console.log('Could not generate code for %s', yaml.kind ? yaml.kind : 'unknown yaml');
|
206
|
+
}
|
207
|
+
}
|
208
|
+
|
209
|
+
hasAsset(ref) {
|
210
|
+
return !!this.getAsset(ref);
|
211
|
+
}
|
212
|
+
|
213
|
+
async importFile(filePath) {
|
214
|
+
if (filePath.startsWith('file://')) {
|
215
|
+
filePath = filePath.substring('file://'.length);
|
216
|
+
}
|
217
|
+
|
218
|
+
if (!FS.existsSync(filePath)) {
|
219
|
+
throw new Error('File not found: ' + filePath);
|
220
|
+
}
|
221
|
+
|
222
|
+
const assetInfos = YAML.parseAllDocuments(FS.readFileSync(filePath).toString())
|
223
|
+
.map(doc => doc.toJSON());
|
224
|
+
|
225
|
+
const assetInfo = assetInfos[0];
|
226
|
+
const version = 'local';
|
227
|
+
const [handle, name] = assetInfo.metadata.name.split('/');
|
228
|
+
|
229
|
+
const target = ClusterConfiguration.getRepositoryAssetPath(handle, name, version);
|
230
|
+
if (!FS.existsSync(target)) {
|
231
|
+
makeSymLink(Path.dirname(filePath), target);
|
232
|
+
}
|
233
|
+
|
234
|
+
const refs = assetInfos.map(assetInfo => `kapeta://${assetInfo.metadata.name}:${version}`);
|
235
|
+
|
236
|
+
return this.getAssets().filter(a => refs.some(ref => compareRefs(ref, a.ref)));
|
237
|
+
}
|
238
|
+
|
239
|
+
unregisterAsset(ref) {
|
240
|
+
const asset = this.getAsset(ref);
|
241
|
+
if (!asset) {
|
242
|
+
throw new Error('Asset does not exists: ' + ref);
|
243
|
+
}
|
244
|
+
//Remove from repository. If its local it is just a symlink - so no unchecked code is removed.
|
245
|
+
FSExtra.removeSync(asset.path);
|
246
|
+
}
|
247
|
+
}
|
248
|
+
|
249
|
+
module.exports = new AssetManager();
|