UNPKG

@metacall/faas

Version:

Reimplementation of MetaCall FaaS platform written in TypeScript.

188 lines (187 loc) 7.18 kB
"use strict"; var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } }); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); __setModuleDefault(result, mod); return result; }; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.packageUpload = void 0; const fs = __importStar(require("fs")); const os_1 = __importDefault(require("os")); const path_1 = __importDefault(require("path")); const busboy_1 = __importDefault(require("busboy")); const unzipper_1 = require("unzipper"); const app_1 = require("../app"); const appError_1 = __importDefault(require("../utils/appError")); const config_1 = require("../utils/config"); const filesystem_1 = require("../utils/filesystem"); const getUploadError = (on, error) => { const internalError = () => ({ message: `Please upload your zip file again, Internal Server Error: ${error.toString()}`, code: 500 }); const errorUploadMessage = { file: { message: 'Error while fetching the zip file, please upload it again', code: 400 }, field: { message: 'You might be sending improperly formed multipart form data fields or jsons', code: 400 }, finish: internalError() }; const appError = errorUploadMessage[on.toString()] || internalError(); return new appError_1.default(appError.message, appError.code); }; const packageUpload = (req, res, next) => { const bb = busboy_1.default({ headers: req.headers }); const resource = { id: '', type: '', path: '', jsons: [], runners: [] }; let fileResolve; const filePromise = new Promise(resolve => { fileResolve = resolve; }); const errorHandler = (error) => { req.unpipe(bb); next(error); }; const eventHandler = (type, listener) => { bb.on(type, (...args) => { try { const fn = listener; fn(...args); } catch (e) { errorHandler(getUploadError(type, e)); } }); }; eventHandler('file', (name, file, info) => { const { mimeType, filename } = info; if (mimeType !== 'application/x-zip-compressed' && mimeType !== 'application/zip') { return errorHandler(new appError_1.default('Please upload a zip file', 404)); } const appLocation = path_1.default.join(config_1.appsDirectory, resource.id); resource.path = appLocation; // Create temporary directory for the blob fs.mkdtemp(path_1.default.join(os_1.default.tmpdir(), `metacall-faas-${resource.id}-`), (err, folder) => { if (err !== null) { return errorHandler(new appError_1.default('Failed to create temporary directory for the blob', 500)); } resource.blob = path_1.default.join(folder, filename); // Create the app folder filesystem_1.ensureFolderExists(appLocation) .then(() => { // Create the write stream for storing the blob file.pipe(fs.createWriteStream(resource.blob)).on('finish', () => { fileResolve(); }); }) .catch((error) => { errorHandler(new appError_1.default(`Failed to create folder for the resource at: ${appLocation} - ${error.toString()}`, 404)); }); }); }); eventHandler('field', (name, val) => { if (name === 'runners') { resource.runners = JSON.parse(val); } else if (name === 'jsons') { resource.jsons = JSON.parse(val); } else { resource[name] = val; } }); eventHandler('finish', () => { if (resource.blob === undefined) { throw new Error('Invalid file upload, blob path is not defined'); } const deleteBlob = () => { if (resource.blob !== undefined) { fs.unlink(resource.blob, error => { if (error !== null) { errorHandler(new appError_1.default(`Failed to delete the blob at: ${error.toString()}`, 500)); } }); } }; const deleteFolder = () => { if (resource.path !== undefined) { fs.unlink(resource.path, error => { if (error !== null) { errorHandler(new appError_1.default(`Failed to delete the path at: ${error.toString()}`, 500)); } }); } }; if (app_1.Applications[resource.id]) { deleteBlob(); return errorHandler(new appError_1.default(`There is an application with name '${resource.id}' already deployed, delete it first.`, 400)); } let resourceResolve; let resourceReject; app_1.Applications[resource.id] = new app_1.Application(); app_1.Applications[resource.id].resource = new Promise((resolve, reject) => { resourceResolve = resolve; resourceReject = reject; }); const unzipAndResolve = () => { return new Promise((resolve, reject) => { fs.createReadStream(resource.blob) .pipe(unzipper_1.Extract({ path: resource.path })) .on('close', () => { deleteBlob(); resolve(); }) .on('error', error => { deleteBlob(); deleteFolder(); reject(new appError_1.default(`Failed to unzip the resource at: ${error.toString()}`, 500)); }); }); }; void filePromise.then(() => { unzipAndResolve() .then(() => { resourceResolve(resource); res.status(201).json({ id: resource.id }); }) .catch(error => { resourceReject(error); errorHandler(error); }); }); }); eventHandler('close', () => { // Do nothing }); req.pipe(bb); }; exports.packageUpload = packageUpload;