@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,279 @@
1
+ const {Docker} = require('node-docker-api');
2
+ const _ = require('lodash');
3
+
4
+ const LABEL_PORT_PREFIX = 'kapeta_port-';
5
+
6
+ const NANO_SECOND = 1000000;
7
+ const HEALTH_CHECK_INTERVAL = 1000;
8
+ const HEALTH_CHECK_MAX = 20;
9
+
10
+ const promisifyStream = (stream) => new Promise((resolve, reject) => {
11
+ stream.on('data', (d) => console.log(d.toString()))
12
+ stream.on('end', resolve)
13
+ stream.on('error', reject)
14
+ });
15
+
16
+ class ContainerManager {
17
+ constructor() {
18
+ this._docker = new Docker();
19
+ this._alive = false;
20
+ }
21
+
22
+ async ping() {
23
+ await this._docker.ping();
24
+ this._alive = true;
25
+ }
26
+
27
+ async pull(image) {
28
+ let [imageName, tag] = image.split(/:/);
29
+ if (!tag) {
30
+ tag = 'latest';
31
+ }
32
+
33
+ await this._docker.image.create({}, {
34
+ fromImage: imageName,
35
+ tag: tag
36
+ }).then(stream => promisifyStream(stream));
37
+
38
+ }
39
+
40
+ /**
41
+ *
42
+ * @param {string} image
43
+ * @param {string} name
44
+ * @param {{ports:{},mounts:{},env:{}}} opts
45
+ * @return {Promise<ContainerInfo>}
46
+ */
47
+ async run(image, name, opts) {
48
+
49
+ const Mounts = [];
50
+ const PortBindings = {};
51
+ const Env = [];
52
+ const Labels = {
53
+ 'kapeta':'true'
54
+ };
55
+
56
+ console.log('Pulling image: %s', image);
57
+
58
+ await this.pull(image);
59
+
60
+ console.log('Image pulled: %s', image);
61
+
62
+ _.forEach(opts.ports, (portInfo, containerPort) => {
63
+ PortBindings['' + containerPort] = [
64
+ {
65
+ HostPort: '' + portInfo.hostPort
66
+ }
67
+ ];
68
+
69
+ Labels[LABEL_PORT_PREFIX + portInfo.hostPort] = portInfo.type;
70
+ });
71
+
72
+ _.forEach(opts.mounts, (Source, Target) => {
73
+ Mounts.push({
74
+ Target,
75
+ Source,
76
+ Type: 'bind',
77
+ ReadOnly: false,
78
+ Consistency: 'consistent'
79
+ });
80
+ });
81
+
82
+ _.forEach(opts.env, (value, name) => {
83
+ Env.push(name + '=' + value);
84
+ });
85
+
86
+ let HealthCheck = undefined;
87
+
88
+ if (opts.health) {
89
+ HealthCheck = {
90
+ Test: [
91
+ 'CMD-SHELL',
92
+ opts.health.cmd
93
+ ],
94
+ Interval: opts.health.interval ? opts.health.interval * NANO_SECOND : 5000 * NANO_SECOND,
95
+ Timeout: opts.health.timeout ? opts.health.timeout * NANO_SECOND : 15000 * NANO_SECOND,
96
+ Retries: opts.health.retries || 10
97
+ };
98
+
99
+ console.log('Adding health check', HealthCheck);
100
+ }
101
+
102
+ const dockerContainer = await this._docker.container.create({
103
+ name: name,
104
+ Image: image,
105
+ Labels,
106
+ Env,
107
+ HealthCheck,
108
+ HostConfig: {
109
+ PortBindings,
110
+ Mounts
111
+ }
112
+ });
113
+
114
+ await dockerContainer.start();
115
+
116
+ if (opts.health) {
117
+ await this._waitForHealthy(dockerContainer);
118
+ }
119
+
120
+ return new ContainerInfo(dockerContainer);
121
+ }
122
+
123
+
124
+ async _waitForHealthy(container, attempt) {
125
+ if (!attempt) {
126
+ attempt = 0;
127
+ }
128
+
129
+ if (attempt >= HEALTH_CHECK_MAX) {
130
+ throw new Error('Operator did not become healthy within the timeout');
131
+ }
132
+
133
+ if (await this._isHealthy(container)) {
134
+ console.log('Container became healthy');
135
+ return;
136
+ }
137
+
138
+ return new Promise((resolve) => {
139
+ setTimeout(async () => {
140
+ await this._waitForHealthy(container, attempt + 1);
141
+ resolve();
142
+ }, HEALTH_CHECK_INTERVAL)
143
+ });
144
+
145
+ }
146
+
147
+ async _isHealthy(container) {
148
+ const info = await container.status();
149
+
150
+ return info?.data?.State?.Health?.Status === 'healthy';
151
+ }
152
+
153
+ /**
154
+ *
155
+ * @param name
156
+ * @return {Promise<ContainerInfo>}
157
+ */
158
+ async get(name) {
159
+ let dockerContainer = null;
160
+
161
+ try {
162
+ dockerContainer = await this._docker.container.get(name);
163
+ await dockerContainer.status();
164
+ } catch(err) {
165
+ //Ignore
166
+ console.log('Container not available - creating it: %s', name);
167
+ dockerContainer = null;
168
+ }
169
+
170
+ if (!dockerContainer) {
171
+ return null;
172
+ }
173
+
174
+ return new ContainerInfo(dockerContainer);
175
+ }
176
+ }
177
+
178
+ class ContainerInfo {
179
+ /**
180
+ *
181
+ * @param {Container} dockerContainer
182
+ */
183
+ constructor(dockerContainer) {
184
+
185
+ /**
186
+ *
187
+ * @type {Container}
188
+ * @private
189
+ */
190
+ this._container = dockerContainer;
191
+ }
192
+
193
+ async isRunning() {
194
+ const inspectResult = await this.getStatus();
195
+
196
+ if (!inspectResult ||
197
+ !inspectResult.State) {
198
+ return false;
199
+ }
200
+
201
+
202
+ return inspectResult.State.Running || inspectResult.State.Restarting;
203
+ }
204
+
205
+ async start() {
206
+ await this._container.start();
207
+ }
208
+
209
+ async restart() {
210
+ await this._container.restart();
211
+ }
212
+
213
+ async stop() {
214
+ await this._container.stop();
215
+ }
216
+
217
+ async remove(opts) {
218
+ await this._container.delete({ force: !!opts.force });
219
+ }
220
+
221
+ async getPort(type) {
222
+ const ports = await this.getPorts();
223
+
224
+ if (ports[type]) {
225
+ return ports[type];
226
+ }
227
+
228
+ return null;
229
+ }
230
+
231
+ async getStatus() {
232
+ const result = await this._container.status();
233
+
234
+ return result ? result.data : null;
235
+ }
236
+
237
+ async getPorts() {
238
+ const inspectResult = await this.getStatus();
239
+
240
+ if (!inspectResult ||
241
+ !inspectResult.Config ||
242
+ !inspectResult.Config.Labels) {
243
+ return false;
244
+ }
245
+
246
+ const portTypes = {};
247
+ const ports = {};
248
+
249
+ _.forEach(inspectResult.Config.Labels, (portType, name) => {
250
+ if (!name.startsWith(LABEL_PORT_PREFIX)) {
251
+ return;
252
+ }
253
+
254
+ const hostPort = name.substr(LABEL_PORT_PREFIX.length);
255
+
256
+ portTypes[hostPort] = portType;
257
+
258
+ });
259
+
260
+ _.forEach(inspectResult.HostConfig.PortBindings, (portBindings, containerPortSpec) => {
261
+ let [containerPort, protocol] = containerPortSpec.split(/\//);
262
+
263
+ const hostPort = portBindings[0].HostPort;
264
+
265
+ const portType = portTypes[hostPort];
266
+
267
+ ports[portType] = {
268
+ containerPort,
269
+ protocol,
270
+ hostPort
271
+ };
272
+ });
273
+
274
+ return ports;
275
+ }
276
+ }
277
+
278
+
279
+ module.exports = new ContainerManager();
@@ -0,0 +1,74 @@
1
+ const Router = require('express-promise-router').default;
2
+ const stringBodyMiddleware = require('../middleware/stringBody');
3
+ const fileManager = require('../filesystemManager');
4
+
5
+ let router = new Router();
6
+
7
+ router.use('/', require('../middleware/cors'));
8
+
9
+ router.get('/root', (req, res) => {
10
+ res.send(fileManager.getRootFolder());
11
+ });
12
+
13
+ router.get('/project/root', (req, res) => {
14
+ res.send(fileManager.getProjectRootFolder());
15
+ });
16
+
17
+ router.use('/project/root', stringBodyMiddleware);
18
+ router.post('/project/root', (req, res) => {
19
+ fileManager.setProjectRootFolder(req.stringBody);
20
+ res.sendStatus(204);
21
+ });
22
+
23
+
24
+ router.use("/",(req,res,next)=>{
25
+ if (!req.query.path) {
26
+ res.status(400).send({ error: 'Missing required query parameter "path"' });
27
+ return;
28
+ }
29
+ next();
30
+ });
31
+
32
+
33
+ router.get('/list', async (req, res) => {
34
+ let pathArg = req.query.path;
35
+
36
+ try {
37
+ res.send(await fileManager.readDirectory(pathArg))
38
+ } catch (err) {
39
+ res.status(400).send({error:''+err});
40
+ }
41
+
42
+ });
43
+
44
+ router.get('/readfile', async (req, res) => {
45
+ let pathArg = req.query.path;
46
+ try {
47
+ res.send(await fileManager.readFile(pathArg));
48
+ } catch (err) {
49
+ res.status(400).send({error:''+err});
50
+ }
51
+ });
52
+
53
+ router.put('/mkdir', async (req, res) => {
54
+ let pathArg = req.query.path;
55
+ try {
56
+ await fileManager.createFolder(pathArg);
57
+ res.sendStatus(204);
58
+ } catch (err) {
59
+ res.status(400).send({error:''+err});
60
+ }
61
+ });
62
+
63
+ router.use('/writefile', stringBodyMiddleware);
64
+ router.post('/writefile', async (req, res) => {
65
+ let pathArg = req.query.path;
66
+ try {
67
+ await fileManager.writeFile(pathArg, req.stringBody);
68
+ res.sendStatus(204);
69
+ } catch (err) {
70
+ res.status(400).send({error:''+err});
71
+ }
72
+ });
73
+
74
+ module.exports = router;
@@ -0,0 +1,89 @@
1
+ const Path = require("path");
2
+ const FS = require("fs");
3
+ const FSExtra = require('fs-extra');
4
+ const storageService = require('./storageService');
5
+ const SECTION_ID = 'filesystem';
6
+ const PROJECT_ROOT = 'project_root';
7
+
8
+ function isFile(path) {
9
+ try {
10
+ return FS.statSync(path).isFile()
11
+ } catch (error) {
12
+ return false;
13
+ }
14
+ }
15
+
16
+ class FilesystemManager {
17
+
18
+ async writeFile(path, data) {
19
+ const dirName = Path.dirname(path);
20
+ console.log('Dir name', dirName, path);
21
+ if (!FS.existsSync(dirName)) {
22
+ console.log('Making folder', dirName);
23
+ FSExtra.mkdirpSync(dirName, {});
24
+ }
25
+ FS.writeFileSync(path, data);
26
+ }
27
+
28
+ async createFolder(path) {
29
+ return new Promise((resolve, reject) => {
30
+ FS.mkdir(path, (err) => {
31
+ if (err) {
32
+ err.message += ". You can only create one single folder at a time.";
33
+ reject(err.message);
34
+ return;
35
+ }
36
+ resolve();
37
+ })
38
+ })
39
+ }
40
+
41
+ async readDirectory(path) {
42
+ return new Promise((resolve, reject) => {
43
+ let response = [];
44
+ FS.readdir(path, (err, files) => {
45
+ if (err) {
46
+ reject(new Error(err));
47
+ return;
48
+ }
49
+ files.forEach((file) => {
50
+ response.push({
51
+ path: Path.join(path, file),
52
+ folder: FS.lstatSync(Path.join(path, file)).isDirectory()
53
+ })
54
+ });
55
+ resolve(response)
56
+ });
57
+ })
58
+ }
59
+
60
+ async readFile(path) {
61
+ return new Promise((resolve, reject) => {
62
+ if (!isFile(path)) {
63
+ reject(new Error("The path provided is invalid.Please check that the path and file name that were provided are spelled correctly. "));
64
+ } else {
65
+ FS.readFile(path, (err, data) => {
66
+ if (err) {
67
+ reject(new Error(err.message));
68
+ return;
69
+ }
70
+ resolve(data)
71
+ });
72
+ }
73
+ })
74
+ }
75
+
76
+ getRootFolder() {
77
+ return require('os').homedir();
78
+ }
79
+
80
+ getProjectRootFolder() {
81
+ return storageService.get(SECTION_ID, PROJECT_ROOT);
82
+ }
83
+
84
+ setProjectRootFolder(folder) {
85
+ storageService.put(SECTION_ID, PROJECT_ROOT, folder);
86
+ }
87
+ }
88
+
89
+ module.exports = new FilesystemManager();
@@ -0,0 +1,19 @@
1
+ const Router = require('express-promise-router').default;
2
+ const {KapetaAPI} = require('@kapeta/nodejs-api-client');
3
+
4
+ const instanceManager = require('../instanceManager');
5
+
6
+ const router = new Router();
7
+ const api = new KapetaAPI();
8
+
9
+ router.use('/', require('../middleware/cors'));
10
+
11
+ router.get('/current', async (req, res) => {
12
+ res.send(await api.getCurrentIdentity());
13
+ });
14
+
15
+ router.get('/:identityId/memberships', async (req, res) => {
16
+ res.send(await api.getMemberships(req.params.identityId));
17
+ });
18
+
19
+ module.exports = router;