@metacall/faas
Version:
Reimplementation of MetaCall FaaS platform written in TypeScript.
176 lines (175 loc) • 6.77 kB
JavaScript
;
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.uploadPackage = 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 uploadPackage = (req, res, next) => {
const bb = busboy_1.default({ headers: req.headers });
const resource = {
id: '',
type: '',
path: '',
jsons: []
};
let handled = false; // TODO: Promisify the whole controller
const errorHandler = (error) => {
if (handled == false) {
req.unpipe(bb);
next(error);
}
handled = true;
};
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));
})
.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 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 options = { path: resource.path };
fs.createReadStream(resource.blob)
.pipe(unzipper_1.Extract(options))
.on('close', () => {
deleteBlob();
resourceResolve(resource);
})
.on('error', error => {
deleteBlob();
deleteFolder();
const appError = new appError_1.default(`Failed to unzip the resource at: ${error.toString()}`, 500);
errorHandler(appError);
resourceReject(appError);
});
});
eventHandler('close', () => {
if (handled == false) {
res.status(201).json({
id: resource.id
});
}
handled = true;
});
req.pipe(bb);
};
exports.uploadPackage = uploadPackage;