@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.
@@ -0,0 +1,133 @@
1
+ const Router = require('express-promise-router').default;
2
+ const YAML = require('yaml');
3
+ const assetManager = require('../assetManager');
4
+
5
+
6
+ function parseBody(req) {
7
+ switch(req.headers['content-type']) {
8
+ case 'application/json':
9
+ case 'application/x-json':
10
+ case 'text/json':
11
+ return JSON.parse(req.stringBody);
12
+
13
+ case 'application/yaml':
14
+ case 'application/x-yaml':
15
+ case 'text/yaml':
16
+ case 'text/x-yaml':
17
+ default:
18
+ return YAML.parse(req.stringBody);
19
+ }
20
+ }
21
+
22
+ const router = new Router();
23
+
24
+ router.use('/', require('../middleware/cors'));
25
+ router.use('/', require('../middleware/stringBody'));
26
+
27
+ /**
28
+ * Get all local assets available
29
+ */
30
+ router.get('/', (req, res) => {
31
+ res.send(assetManager.getAssets());
32
+ });
33
+
34
+ /**
35
+ * Get single asset
36
+ */
37
+ router.get('/read', (req, res) => {
38
+ if (!req.query.ref) {
39
+ res.status(400).send({error:'Query parameter "ref" is missing'});
40
+ return;
41
+ }
42
+
43
+ try {
44
+ res.send(assetManager.getAsset(req.query.ref));
45
+ } catch(err) {
46
+ res.status(400).send({error: err.message});
47
+ }
48
+
49
+ });
50
+
51
+ /**
52
+ * Creates a new local file and registers it as an asset
53
+ */
54
+ router.post('/create', async (req, res) => {
55
+ if (!req.query.path) {
56
+ res.status(400).send({error:'Query parameter "path" is missing'});
57
+ return;
58
+ }
59
+
60
+ const content = parseBody(req);
61
+
62
+ try {
63
+ const assets = await assetManager.createAsset(req.query.path, content);
64
+
65
+ res.status(200).send(assets);
66
+ } catch(err) {
67
+ console.log('Failed while creating asset', req.query.path, err.message);
68
+ res.status(400).send({error: err.message});
69
+ }
70
+
71
+ });
72
+
73
+ /**
74
+ * Updates reference with new content
75
+ */
76
+ router.put('/update', async (req, res) => {
77
+ if (!req.query.ref) {
78
+ res.status(400).send({error:'Query parameter "ref" is missing'});
79
+ return;
80
+ }
81
+
82
+ const content = parseBody(req);
83
+
84
+ try {
85
+ await assetManager.updateAsset(req.query.ref, content);
86
+
87
+ res.sendStatus(204);
88
+ } catch(err) {
89
+ console.log('Failed while updating asset', req.query.ref, err.message);
90
+ res.status(400).send({error: err.message});
91
+ }
92
+
93
+ });
94
+
95
+
96
+ /**
97
+ * Unregisters an asset (doesn't delete the asset)
98
+ */
99
+ router.delete('/', (req, res) => {
100
+ if (!req.query.ref) {
101
+ res.status(400).send({error:'Query parameter "ref" is missing'});
102
+ return;
103
+ }
104
+
105
+ try {
106
+ assetManager.unregisterAsset(req.query.ref);
107
+
108
+ res.status(204).send();
109
+ } catch(err) {
110
+ res.status(400).send({error: err.message});
111
+ }
112
+ });
113
+
114
+
115
+ /**
116
+ * Registers an existing file as an asset
117
+ */
118
+ router.put('/import', async (req, res) => {
119
+ if (!req.query.ref) {
120
+ res.status(400).send({error:'Query parameter "ref" is missing'});
121
+ return;
122
+ }
123
+
124
+ try {
125
+ const assets = await assetManager.importFile(req.query.ref);
126
+
127
+ res.status(200).send(assets);
128
+ } catch(err) {
129
+ res.status(400).send({error: err.message});
130
+ }
131
+ });
132
+
133
+ module.exports = router;
@@ -0,0 +1,134 @@
1
+ const net = require('net');
2
+ const DEFAULT_SERVER_PORT = 35100;
3
+ const DEFAULT_START_PORT = 40000;
4
+ const DEFAULT_HOST = '127.0.0.1';
5
+
6
+ class ClusterService {
7
+
8
+ constructor() {
9
+ this._port = DEFAULT_SERVER_PORT;
10
+ this._currentPort = DEFAULT_START_PORT;
11
+ this._initialized = false;
12
+ this._reservedPorts = [];
13
+ this._host = DEFAULT_HOST;
14
+ }
15
+
16
+ reservePort(port) {
17
+ const intPort = parseInt(port);
18
+ if (this._reservedPorts.indexOf(intPort) > -1) {
19
+ throw new Error('Port already reserved: ' + intPort);
20
+ }
21
+
22
+ this._reservedPorts.push(intPort);
23
+ }
24
+
25
+ async init() {
26
+ if (this._initialized) {
27
+ return;
28
+ }
29
+
30
+ this._initialized = true;
31
+ await this._findClusterServicePort();
32
+
33
+ }
34
+
35
+ async _findClusterServicePort() {
36
+ while(true) {
37
+
38
+ const isUsed = await this._checkIfPortIsUsed(this._port);
39
+ if (!isUsed) {
40
+ break;
41
+ }
42
+
43
+ this._port++;
44
+
45
+ }
46
+ }
47
+
48
+
49
+ /**
50
+ * Gets next available port
51
+ * @return {Promise<number>}
52
+ */
53
+ async getNextAvailablePort() {
54
+ while(true) {
55
+
56
+ while (this._reservedPorts.indexOf(this._currentPort) > -1) {
57
+ this._currentPort++;
58
+ }
59
+
60
+ const nextPort = this._currentPort++;
61
+ const isUsed = await this._checkIfPortIsUsed(nextPort);
62
+ if (!isUsed) {
63
+ return nextPort;
64
+ }
65
+ }
66
+ }
67
+
68
+ _checkIfPortIsUsed(port, host=this._host) {
69
+ return new Promise((resolve, reject) => {
70
+ const server = net.createServer();
71
+
72
+ server.once('error', function(err) {
73
+ if (err.code === 'EADDRINUSE') {
74
+ server.close();
75
+ resolve(true);
76
+ return;
77
+ }
78
+
79
+ server.close();
80
+ reject(err);
81
+ });
82
+
83
+ server.once('listening', function() {
84
+ server.close();
85
+ resolve(false);
86
+ });
87
+
88
+ server.listen( port, host );
89
+ });
90
+
91
+ }
92
+
93
+
94
+ /**
95
+ * The port of this local cluster service itself
96
+ */
97
+ getClusterServicePort() {
98
+ return this._port;
99
+ }
100
+
101
+ /*
102
+ *Gets the host name ( 127.0.0.1 ) on which Express JS is listening
103
+ */
104
+ getClusterServiceHost() {
105
+ return this._host;
106
+ }
107
+
108
+ /**
109
+ * Set the port to be used for this local service
110
+ * @param port
111
+ */
112
+ setClusterServicePort(port) {
113
+ this._port = port;
114
+ }
115
+
116
+ setClusterServiceHost(host) {
117
+ this._host = host;
118
+ }
119
+
120
+ /**
121
+ * Gets that proxy path of a given request
122
+ *
123
+ * @param systemId
124
+ * @param consumerInstanceId
125
+ * @param consumerResourceName
126
+ * @param portType
127
+ * @return {string}
128
+ */
129
+ getProxyPath(systemId, consumerInstanceId, consumerResourceName, portType) {
130
+ return `/proxy/${encodeURIComponent(systemId)}/${encodeURIComponent(consumerInstanceId)}/${encodeURIComponent(consumerResourceName)}/${encodeURIComponent(portType)}/`;
131
+ }
132
+ }
133
+
134
+ module.exports = new ClusterService();
@@ -0,0 +1,40 @@
1
+ const Path = require('path');
2
+
3
+ const {registry:Targets, BlockCodeGenerator, CodeWriter} = require('@kapeta/codegen');
4
+ const ClusterConfiguration = require('@kapeta/local-cluster-config');
5
+ const TARGET_KIND = 'core/language-target';
6
+ const BLOCK_TYPE_KIND = 'core/block-type';
7
+
8
+ class CodeGeneratorManager {
9
+
10
+ reload() {
11
+ Targets.reset();
12
+ const languageTargets = ClusterConfiguration.getDefinitions(TARGET_KIND);
13
+ languageTargets.forEach((languageTarget) => {
14
+ const key = `${languageTarget.definition.metadata.name}:${languageTarget.version}`
15
+ Targets.register(key, require(languageTarget.path));
16
+ });
17
+ }
18
+
19
+ canGenerateCode(yamlContent) {
20
+ const blockTypes = ClusterConfiguration.getDefinitions(BLOCK_TYPE_KIND);
21
+ const blockTypeKinds = blockTypes.map(blockType => blockType.definition.metadata.name.toLowerCase() + ':' + blockType.version);
22
+ return yamlContent && yamlContent.kind && blockTypeKinds.indexOf(yamlContent.kind.toLowerCase()) > -1;
23
+ }
24
+
25
+ async generate(yamlFile, yamlContent) {
26
+ const baseDir = Path.dirname(yamlFile);
27
+ console.log('Generating code for path: %s', baseDir);
28
+ const codeGenerator = new BlockCodeGenerator(yamlContent);
29
+
30
+ const output = await codeGenerator.generate();
31
+ const writer = new CodeWriter(baseDir, {});
32
+ writer.write(output);
33
+
34
+ console.log('Code generated for path: %s', baseDir);
35
+ }
36
+ }
37
+
38
+ const manager = new CodeGeneratorManager();
39
+ manager.reload();
40
+ module.exports = manager;
@@ -0,0 +1,118 @@
1
+ const Router = require('express-promise-router').default;
2
+ const YAML = require('yaml');
3
+ const configManager = require('../configManager');
4
+ const serviceManager = require('../serviceManager');
5
+ const operatorManager = require('../operatorManager');
6
+
7
+ const router = new Router();
8
+
9
+ router.use('/', require('../middleware/kapeta'));
10
+ router.use('/', require('../middleware/stringBody'));
11
+
12
+ /**
13
+ * Returns the full configuration for a given service.
14
+ */
15
+ router.get('/', (req, res) => {
16
+ //Get service YAML config
17
+ const config = configManager.getConfigForService(req.kapeta.systemId, req.kapeta.instanceId);
18
+
19
+ res.send(YAML.stringify(config));
20
+ });
21
+
22
+ /**
23
+ * Updates the full configuration for a given service.
24
+ */
25
+ router.put('/', (req, res) => {
26
+
27
+ let config = YAML.parse(req.stringBody);
28
+ if (!config) {
29
+ config = {};
30
+ }
31
+ //Get service YAML config
32
+ configManager.setConfigForService(
33
+ req.kapeta.systemId,
34
+ req.kapeta.instanceId,
35
+ config
36
+ );
37
+ res.status(202).send({ok:true});
38
+ });
39
+
40
+
41
+ /**
42
+ * Resolves and checks the identify of a block instance
43
+ */
44
+ router.get('/identity', async (req, res) => {
45
+
46
+
47
+ const identity = {
48
+ systemId: req.kapeta.systemId,
49
+ instanceId: req.kapeta.instanceId
50
+ };
51
+
52
+ try {
53
+
54
+ if (!identity.systemId ||
55
+ !identity.instanceId) {
56
+ const {systemId, instanceId} = await configManager.resolveIdentity(req.kapeta.blockRef, identity.systemId);
57
+ identity.systemId = systemId;
58
+ identity.instanceId = instanceId;
59
+ } else {
60
+ await configManager.verifyIdentity(req.kapeta.blockRef, identity.systemId, identity.instanceId);
61
+ }
62
+
63
+ res.send(identity);
64
+ } catch(err) {
65
+ console.log(err);
66
+
67
+ res.send({error: err.message});
68
+ }
69
+ });
70
+
71
+ /**
72
+ * Services call this to request a free port. If a service has
73
+ * already called the endpoint the same port is returned.
74
+ */
75
+ router.get('/provides/:type', async (req, res) => {
76
+ //Get service port
77
+ res.send('' + await serviceManager.ensureServicePort(
78
+ req.kapeta.systemId,
79
+ req.kapeta.instanceId,
80
+ req.params.type
81
+ ));
82
+ });
83
+
84
+ /**
85
+ * Used by services to get info for consumed operator resource.
86
+ *
87
+ * If the operator resource is not already available this will cause it to start an instance and
88
+ * assign port numbers to it etc.
89
+ */
90
+ router.get('/consumes/resource/:resourceType/:portType/:name', async (req, res) => {
91
+ const operatorInfo = await operatorManager.getResourceInfo(
92
+ req.kapeta.systemId,
93
+ req.kapeta.instanceId,
94
+ req.params.resourceType,
95
+ req.params.portType,
96
+ req.params.name
97
+ );
98
+
99
+ res.send(operatorInfo);
100
+ });
101
+
102
+ /**
103
+ * Used by services to get address for their clients.
104
+ *
105
+ * If the remote service is not already registered with a port - we do that here
106
+ * to handle clients for services that hasn't started yet.
107
+ */
108
+ router.get('/consumes/:resourceName/:type', (req, res) => {
109
+
110
+ res.send(serviceManager.getConsumerAddress(
111
+ req.kapeta.systemId,
112
+ req.kapeta.instanceId,
113
+ req.params.resourceName,
114
+ req.params.type
115
+ ));
116
+ });
117
+
118
+ module.exports = router;
@@ -0,0 +1,128 @@
1
+ const _ = require('lodash');
2
+ const storageService = require('./storageService');
3
+ const assetManager = require('./assetManager');
4
+ const {parseKapetaUri} = require("@kapeta/nodejs-utils");
5
+
6
+ class ConfigManager {
7
+
8
+ constructor() {
9
+ this._config = storageService.section('config');
10
+ }
11
+
12
+ _forSystem(systemId) {
13
+ if (!this._config[systemId]) {
14
+ this._config[systemId] = {};
15
+ }
16
+
17
+ return this._config[systemId];
18
+ }
19
+
20
+ setConfigForService(systemId, serviceId, config) {
21
+ const systemConfig = this._forSystem(systemId);
22
+ systemConfig[serviceId] = config || {};
23
+
24
+ storageService.put('config', systemId, systemConfig);
25
+ }
26
+
27
+ getConfigForService(systemId, serviceId) {
28
+ const systemConfig = this._forSystem(systemId);
29
+
30
+ if (!systemConfig[serviceId]) {
31
+ systemConfig[serviceId] = {};
32
+ }
33
+
34
+ if (!systemConfig[serviceId].kapeta) {
35
+ systemConfig[serviceId].kapeta = {};
36
+ }
37
+
38
+ return systemConfig[serviceId];
39
+ }
40
+
41
+ /**
42
+ * Try to identify the plan and instance in a plan automatically based on the block reference
43
+ *
44
+ * It will:
45
+ * 1. Go through all plans available in the assets
46
+ * 2. Look through each plan and see if the plan is referencing the block
47
+ * 3. If only 1 plan references the block - assume that as the system id
48
+ * 4. If only 1 instance in 1 plan references the block - assume that as instance id
49
+ *
50
+ * In case multiple uses of the same block reference we will prompt to user to choose which instance they want to
51
+ * use.
52
+ *
53
+ * @param blockRef block reference
54
+ * @param [systemId] plan reference
55
+ * @returns {Promise<{systemId:string,instanceId:string}>}
56
+ */
57
+ async resolveIdentity(blockRef, systemId) {
58
+ const planAssets = assetManager.getPlans();
59
+
60
+ const blockUri = parseKapetaUri(blockRef);
61
+
62
+ let matchingIdentities = [];
63
+ planAssets.forEach((planAsset) => {
64
+ if (systemId && planAsset.ref !== systemId) {
65
+ //Skip plans that do not match systemid if provided
66
+ return;
67
+ }
68
+
69
+ if (!planAsset.data.spec.blocks) {
70
+ return;
71
+ }
72
+
73
+ planAsset.data.spec.blocks.forEach((blockInstance) => {
74
+ const refUri = parseKapetaUri(blockInstance.block.ref);
75
+ if (refUri.equals(blockUri)) {
76
+ matchingIdentities.push({
77
+ systemId: planAsset.ref,
78
+ instanceId: blockInstance.id
79
+ });
80
+ }
81
+ });
82
+ });
83
+
84
+ if (matchingIdentities.length === 0) {
85
+ if (systemId) {
86
+ throw new Error(`No uses of block "${blockRef}" was found in plan: "${systemId}"`)
87
+ }
88
+
89
+ throw new Error(`No uses of block "${blockRef}" was found any known plan`);
90
+ }
91
+
92
+ if (matchingIdentities.length > 1) {
93
+ if (systemId) {
94
+ throw new Error(`Multiple uses of block "${blockRef}" was found in plan: "${systemId}". Please specify which instance in the plan you wish to run.`)
95
+ }
96
+
97
+ throw new Error(`Multiple uses of block "${blockRef}" was found in 1 or more plan. Please specify which instance in which plan you wish to run.`);
98
+ }
99
+
100
+
101
+ return matchingIdentities[0];
102
+ }
103
+
104
+ async verifyIdentity(blockRef, systemId, instanceId) {
105
+ const planAssets = await assetManager.getPlans();
106
+
107
+ let found = false;
108
+ planAssets.forEach((planAsset) => {
109
+ if (planAsset.ref !== systemId) {
110
+ //Skip plans that do not match systemid if provided
111
+ return;
112
+ }
113
+
114
+ planAsset.data.spec.blocks.forEach((blockInstance) => {
115
+ if (blockInstance.id === instanceId &&
116
+ blockInstance.block.ref === blockRef) {
117
+ found = true;
118
+ }
119
+ });
120
+ });
121
+
122
+ if (!found) {
123
+ throw new Error(`Block "${blockRef}" was not found in plan: "${systemId}" using instance id ${instanceId}. Please verify that the provided information is accurate.`);
124
+ }
125
+ }
126
+ }
127
+
128
+ module.exports = new ConfigManager();