Skip to content
This repository was archived by the owner on Nov 23, 2022. It is now read-only.

Commit 4d9a18d

Browse files
committed
Basic version extracted from exoframe-server
0 parents  commit 4d9a18d

File tree

9 files changed

+4057
-0
lines changed

9 files changed

+4057
-0
lines changed

.gitignore

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
node_modules

README.md

+17
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
# Exoframe-FaaS
2+
3+
> Simple function deployments for Exoframe
4+
5+
[![Build Status](https://travis-ci.org/exoframejs/exoframe-server.svg?branch=master)](https://travis-ci.org/exoframejs/exoframe-server)
6+
[![Coverage Status](https://coveralls.io/repos/github/exoframejs/exoframe-server/badge.svg?branch=master)](https://coveralls.io/github/exoframejs/exoframe-server?branch=master)
7+
[![license](https://img.shields.io/github/license/mashape/apistatus.svg)](https://opensource.org/licenses/MIT)
8+
9+
Exoframe is a self-hosted tool that allows simple one-command deployments using Docker.
10+
11+
## Installation, usage and docs
12+
13+
For more details on how to get it up and running please follow the following link [how to setup exoframe-server](https://github.com/exoframejs/exoframe/tree/master/docs).
14+
15+
## License
16+
17+
Licensed under MIT.

index.js

+17
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
// export faas methods
2+
const {getLogsForFunction, listFunctions, registerFunction, removeFunction, setup} = require('./src/index');
3+
const template = require('./src/template');
4+
5+
module.exports = {
6+
// logs
7+
getLogsForFunction,
8+
// listing
9+
listFunctions,
10+
// function registration and removal
11+
registerFunction,
12+
removeFunction,
13+
// init & middleware setup
14+
setup,
15+
// exoframe template
16+
template,
17+
};

package.json

+20
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
{
2+
"name": "exoframe-faas",
3+
"version": "0.1.0",
4+
"description": "Simple function deployments for Exoframe",
5+
"main": "index.js",
6+
"scripts": {
7+
"test": "echo \"Error: no test specified\" && exit 1"
8+
},
9+
"keywords": [],
10+
"author": "Tim Ermilov <[email protected]> (http://codezen.net)",
11+
"license": "MIT",
12+
"dependencies": {
13+
"fs-extra": "^8.0.1",
14+
"rimraf": "^2.6.3"
15+
},
16+
"devDependencies": {
17+
"fastify": "^2.4.1",
18+
"jest": "^24.8.0"
19+
}
20+
}

src/index.js

+128
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,128 @@
1+
const fs = require('fs');
2+
const path = require('path');
3+
const rimraf = require('rimraf');
4+
5+
const runInWorker = require('./worker');
6+
7+
// loaded functions storage
8+
const functions = {};
9+
10+
// remove function
11+
const rmDir = path => new Promise(resolve => rimraf(path, resolve));
12+
13+
// basic logging function generator
14+
const loggerForRoute = route => msg => functions[route].log.push(`${new Date().toISOString()} ${msg}\n`);
15+
16+
exports.listFunctions = ({functionToContainerFormat}) =>
17+
Object.keys(functions).map(route =>
18+
functionToContainerFormat({config: functions[route].config, route, type: functions[route].type})
19+
);
20+
21+
exports.getLogsForFunction = id => {
22+
const route = Object.keys(functions).find(route => functions[route].config.name === id);
23+
const fn = functions[route];
24+
if (!fn) {
25+
return;
26+
}
27+
return fn.log;
28+
};
29+
30+
exports.removeFunction = async ({id, username}) => {
31+
const route = Object.keys(functions).find(route => functions[route].config.name === id);
32+
const fn = functions[route];
33+
if (!fn) {
34+
return false;
35+
}
36+
37+
// if running in worker - trigger cleanup
38+
if (fn.type === 'worker') {
39+
fn.worker.terminate();
40+
}
41+
42+
// remove from cache
43+
delete functions[route];
44+
// remove files
45+
await rmDir(fn.folder);
46+
47+
return true;
48+
};
49+
50+
exports.registerFunction = async ({faasFolder, folder}) => {
51+
// ignore empty current folder reference
52+
if (!folder || !folder.trim().length) {
53+
return;
54+
}
55+
// construct paths
56+
const funPath = path.join(faasFolder, folder);
57+
const funConfigPath = path.join(funPath, 'exoframe.json');
58+
// load code and config
59+
const fun = require(funPath);
60+
const funConfig = require(funConfigPath);
61+
// expand config into default values
62+
const config = {route: `/${funConfig.name}`, type: 'http', ...funConfig.function};
63+
64+
// if function already exists - remove old version
65+
if (functions[config.route]) {
66+
await exports.removeFunction({id: funConfig.name});
67+
}
68+
69+
// store function in memory
70+
functions[config.route] = {
71+
type: config.type,
72+
route: config.route,
73+
handler: fun,
74+
config: funConfig,
75+
folder: funPath,
76+
log: [],
77+
};
78+
79+
// we're done if it's http function
80+
if (config.type === 'http') {
81+
return;
82+
}
83+
84+
// otherwise - execute work based on function
85+
if (config.type === 'worker') {
86+
const worker = runInWorker({meta: functions[config.route], log: loggerForRoute(config.route)});
87+
functions[config.route].worker = worker;
88+
return;
89+
}
90+
91+
throw new Error(`Unknown function type! Couldn't register ${functions[config.route]}!`);
92+
};
93+
94+
const loadFunctions = faasFolder => {
95+
const folders = fs.readdirSync(faasFolder);
96+
for (const folder of folders) {
97+
exports.registerFunction(folder);
98+
}
99+
};
100+
101+
exports.setup = config => {
102+
// load current functions
103+
loadFunctions(config.faasFolder);
104+
// return new fastify middleware
105+
return (fastify, opts, next) => {
106+
// http handler
107+
fastify.route({
108+
method: 'GET',
109+
path: '*',
110+
async handler(request, reply) {
111+
const route = request.params['*'];
112+
if (functions[route] && functions[route].type === 'http') {
113+
const event = request;
114+
const context = {reply, log: loggerForRoute(route)};
115+
const res = await functions[route].handler(event, context);
116+
if (res) {
117+
reply.send(res);
118+
}
119+
return;
120+
}
121+
122+
reply.code(404).send(`Error! Function not found!`);
123+
},
124+
});
125+
126+
next();
127+
};
128+
};

src/runner.js

+17
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
const {parentPort, workerData} = require('worker_threads');
2+
3+
const {file} = workerData;
4+
5+
const execute = require(file);
6+
7+
const log = msg => {
8+
parentPort.postMessage(msg);
9+
};
10+
11+
const main = async () => {
12+
parentPort.postMessage('Worker starting with:', file);
13+
const cleanup = await execute(null, {log});
14+
parentPort.postMessage('Worker started, cleanup:', cleanup);
15+
};
16+
17+
main();

src/template.js

+54
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
// npm packages
2+
const fs = require('fs');
3+
const fse = require('fs-extra');
4+
const path = require('path');
5+
6+
// template name
7+
exports.name = 'faas';
8+
9+
// function to check if the template fits this recipe
10+
exports.checkTemplate = async ({config}) => {
11+
// if project has function field defined in config
12+
try {
13+
return config.function;
14+
} catch (e) {
15+
return false;
16+
}
17+
};
18+
19+
// function to execute current template
20+
exports.executeTemplate = async ({config, serverConfig, username, tempDockerDir, folder, resultStream, util, faas}) => {
21+
try {
22+
// generate dockerfile
23+
const faasFolder = path.join(tempDockerDir, folder);
24+
util.writeStatus(resultStream, {message: 'Deploying function project..', level: 'info'});
25+
26+
// execute yarn to install deps
27+
const hasPackageJson = fs.existsSync(path.join(faasFolder, 'package.json'));
28+
util.logger.debug('Function has package.json:', hasPackageJson);
29+
if (hasPackageJson) {
30+
const log = await util.runYarn({args: ['install'], cwd: faasFolder});
31+
util.logger.debug('Installed function deps:', log);
32+
}
33+
34+
// copy folder to current folder for functions
35+
const destFolder = path.join(serverConfig.faasFolder, folder);
36+
util.logger.debug('Copying function from-to:', {faasFolder, destFolder});
37+
await fse.move(faasFolder, destFolder);
38+
util.logger.debug('Copied function to server..');
39+
40+
// register new function
41+
await faas.registerFunction({faasFolder: serverConfig.faasFolder, folder});
42+
util.logger.debug('Registered function on server..');
43+
44+
// return new deployment
45+
const {route, type} = {route: `/${config.name}`, type: 'http', ...config.function};
46+
const deployment = util.functionToContainerFormat({config, route, type});
47+
util.writeStatus(resultStream, {message: 'Deployment success!', deployments: [deployment], level: 'info'});
48+
resultStream.end('');
49+
} catch (e) {
50+
util.logger.debug('Function deployment failed!', e);
51+
util.writeStatus(resultStream, {message: e.error, error: e.error, log: e.log, level: 'error'});
52+
resultStream.end('');
53+
}
54+
};

src/worker.js

+20
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
const path = require('path');
2+
const {Worker} = require('worker_threads');
3+
4+
module.exports = ({meta, log}) => {
5+
const file = path.join(meta.folder, 'index.js');
6+
const runner = path.join(__dirname, 'runner.js');
7+
const worker = new Worker(runner, {
8+
workerData: {file},
9+
});
10+
worker.on('message', msg => {
11+
log(msg);
12+
});
13+
worker.on('error', err => {
14+
log(err);
15+
});
16+
worker.on('exit', code => {
17+
log(`Worker stopped with exit code ${code}`);
18+
});
19+
return worker;
20+
};

0 commit comments

Comments
 (0)