UNPKG

exoframe-faas

Version:

Simple function deployments for Exoframe

175 lines (150 loc) 5.2 kB
const fs = require('fs'); const path = require('path'); const rimraf = require('rimraf'); const messageBus = require('./messagebus'); const runInWorker = require('./worker'); // loaded functions storage const functions = {}; // remove function const rmDir = path => new Promise(resolve => rimraf(path, resolve)); // basic logging function generator const loggerForRoute = route => msg => functions[route].log.push(`${new Date().toISOString()} ${msg}\n`); // noop cleanup function const noopCleanup = async () => {}; // simple function to fetch all currently loaded functions exports.getLoadedFunctions = () => functions; // converts all loaded functions to list with given formatter exports.listFunctions = ({functionToContainerFormat}) => Object.keys(functions).map(route => functionToContainerFormat({config: functions[route].config, route, type: functions[route].type}) ); // gets an array of logs for given function exports.getLogsForFunction = id => { const route = Object.keys(functions).find(route => functions[route].name === id); const fn = functions[route]; if (!fn) { return; } return fn.log; }; // removes given function exports.removeFunction = async ({id, username}) => { const route = Object.keys(functions).find(route => functions[route].name === id); const fn = functions[route]; if (!fn) { return false; } // trigger cleanup await fn.cleanup(); // remove from cache delete functions[route]; // remove files await rmDir(fn.folder); return true; }; // registers new function exports.registerFunction = async ({faasFolder, folder}) => { // ignore empty current folder reference if (!folder || !folder.trim().length) { return; } // construct paths const funPath = path.join(faasFolder, folder); const funConfigPath = path.join(funPath, 'exoframe.json'); // load code and config const fun = require(funPath); const funConfig = require(funConfigPath); // expand config into default values const config = {route: `/${funConfig.name}`, type: 'http', ...funConfig.function}; // if function already exists - remove old version if (functions[config.route]) { await exports.removeFunction({id: funConfig.name}); } // store function in memory functions[config.route] = { name: funConfig.name, type: config.type, route: config.route, handler: fun, config: funConfig, folder: funPath, log: [], cleanup: noopCleanup, }; // create function context const context = {meta: functions[config.route], log: loggerForRoute(config.route)}; // we're done if it's http function if (config.type === 'http') { return; } // if function is worker type - spawn new worker thread with it if (config.type === 'worker') { const worker = runInWorker(context); functions[config.route].worker = worker; functions[config.route].cleanup = () => worker.terminate(); return; } // if function is a trigger - create new event subscription if (config.type === 'trigger') { const triggerName = functions[config.route].name; // create new function that emits trigger event for current function const emitTrigger = data => messageBus.emit(triggerName, data); // instantiate new trigger const triggerCleanup = await functions[config.route].handler(emitTrigger, context); // register new listeren for the trigger const handleTrigger = data => { Object.keys(functions) // find all function of current type .filter(key => functions[key].type === triggerName) // call them with new data .forEach(key => { const localContext = {meta: functions[key], log: loggerForRoute(functions[key].route)}; functions[key].handler({data}, localContext); }); }; messageBus.addListener(triggerName, handleTrigger); functions[config.route].cleanup = async () => { // remove event listener messageBus.removeListener(triggerName, handleTrigger); // call cleanup if (triggerCleanup) { await triggerCleanup(); } }; } // Custom or unknown function type. No need to do anything.. }; // load all functions from given folder (used on start) const loadFunctions = faasFolder => { const folders = fs.readdirSync(faasFolder); for (const folder of folders) { exports.registerFunction({faasFolder, folder}); } }; // loads all current functions and returns fastify middleware exports.setup = ({faasFolder}) => { // load current functions loadFunctions(faasFolder); // return new fastify middleware return (fastify, opts, next) => { // http handler fastify.route({ method: 'GET', path: '*', async handler(request, reply) { const route = request.params['*']; if (functions[route] && functions[route].type === 'http') { const event = request; const context = {reply, log: loggerForRoute(route)}; const res = await functions[route].handler(event, context); if (res) { reply.send(res); } return; } reply.code(404).send(`Error! Function not found!`); }, }); next(); }; };