UNPKG

cloudworker-proxy

Version:
385 lines (341 loc) 11.1 kB
'use strict'; /* * SERVERLESS PLATFORM SDK: INSTANCE */ const path = require('path'); const exec = require('child_process').exec; const utils = require('./utils'); const api = require('./api'); /** * Correctly formats an instanceId * @param {*} orgUid * @param {*} stageName * @param {*} appUid * @param {*} instanceName */ const generateId = (orgUid, stageName, appUid, instanceName) => { // Validate if (!orgUid || !stageName || !appUid || !instanceName) { throw new Error("'orgUid' 'stageName' 'appUid' and 'instanceName' are required"); } return `${orgUid}.${stageName}.${appUid}.${instanceName}`; }; /** * Create a new Instance * @param {*} orgName * @param {*} stageName * @param {*} appName * @param {*} instanceName */ const create = (orgName = null, stageName = null, appName = null, instanceName = null) => { // Validate if (!orgName || !stageName || !appName || !instanceName) { throw new Error("'orgName' 'stageName' 'appName' and 'instanceName' are required"); } // Instance const instance = {}; instance.orgName = orgName; instance.appName = appName; instance.stageName = stageName; instance.instanceName = instanceName; instance.componentName = null; instance.componentVersion = null; instance.inputs = {}; instance.outputs = {}; instance.state = {}; instance.description = null; // Status instance.instanceStatus = 'inactive'; instance.deploymentError = null; instance.deploymentErrorStack = null; instance.lastAction = null; // Dates instance.createdAt = Date.now(); instance.updatedAt = Date.now(); instance.lastDeployedAt = null; instance.lastActionAt = null; // Metrics instance.instanceMetrics = {}; instance.instanceMetrics.actions = 0; instance.instanceMetrics.deployments = 0; instance.instanceMetrics.removes = 0; instance.instanceMetrics.errors = 0; return instance; }; /** * Validates and (re)formats the component instance properties */ const validateAndFormat = (rawInstance) => { // Copy input object, otherwise the inputter will have unintended data modifications const instance = Object.assign({}, rawInstance); // Format Helper - If shortened properties are used, replace them with full properties if (instance.org) { instance.orgName = instance.org; delete instance.org; } if (instance.stage) { instance.stageName = instance.stage; delete instance.stage; } if (instance.app) { instance.appName = instance.app; delete instance.app; } if (instance.name) { instance.instanceName = instance.name; delete instance.name; } // Ensure all required properties exist if (!instance.orgName || !instance.stageName || !instance.appName || !instance.instanceName) { throw new Error("'orgName' 'stageName' 'appName' and 'instanceName' are required"); } // Format - If shortened component syntax is used, expand into full syntax if (instance.component) { instance.componentName = instance.component.includes('@') ? instance.component.split('@')[0] : instance.component; instance.componentVersion = instance.component.includes('@') ? instance.component.split('@')[1] : ''; delete instance.component; } // Ensure all required component properties exist if (!instance.componentName) { throw new Error("'componentName' is required"); } // Ensure an inputs object exists if (!instance.inputs) { instance.inputs = {}; } return instance; }; /** * Create or update an Instance */ const save = async (sdk, instance = {}) => { // Validate instance = validateAndFormat(instance); // Send request return api.instance.save(sdk, instance); }; /** * Get an Instance record by name */ const getByName = async ( sdk, orgName = null, stageName = null, appName = null, instanceName = null, options = null ) => { return api.instance.get(sdk, orgName, appName, stageName, instanceName, options); }; /** * List Instance records by Org */ const listByOrgName = async (sdk, orgName = null, orgUid = null) => { return api.instance.list(sdk, orgUid, orgName); }; /* * Run a "src" hook, if one is specified */ const preRunSrcHook = async (src) => { if (typeof src === 'object' && src.hook && src.dist) { // First run the build hook, if "hook" and "dist" are specified const options = { cwd: src.src }; return new Promise((resolve, reject) => { exec(src.hook, options, (err) => { if (err) { return reject( new Error(`Failed running "src.hook": "${src.hook}" due to the following error: ${err}`) ); } return resolve(path.resolve(process.cwd(), src.dist)); }); }); } else if (typeof src === 'object' && src.src) { src = path.resolve(src.src); } else if (typeof src === 'string') { src = path.resolve(src); } return src; }; /* * Uploads the component instance src input reference if available */ const preRunSrcUpload = async (sdk, orgName, inputs, options) => { const { srcOriginal } = inputs; let { src } = inputs; let exclude = []; let include = []; if (typeof srcOriginal === 'object') { exclude = srcOriginal.exclude || []; include = srcOriginal.include || []; options.targetDir = srcOriginal.targetDir; } const packageUrls = await api.instance.getUploadUrls(sdk, orgName); const uploadPromise = utils.zipAndUpload( src, null, exclude, include, packageUrls.upload, options ); // replace the src input to point to the download url so that it's download in the lambda runtime src = packageUrls.download; return { src, uploadPromise, cosInfo: packageUrls.cosInfo }; }; /** * Run a method. "inputs" override serverless.yml inputs and should only be provided when using custom methods. */ const run = async (sdk, method = {}, instanceData = {}, credentials = {}, options = {}) => { let instance = JSON.parse(JSON.stringify(instanceData)); // Validate method if (!method) { throw new Error('A "method" argument is required'); } // Validate instance instance = validateAndFormat(instance); // Run source hook and upload source, if "src" input is used... // Save original src to present neatly in Dashboard/clients, or this will be replaced by the S3 location by the functions below if (instance.inputs.src) { instance.inputs.srcOriginal = JSON.parse(JSON.stringify(instance.inputs.src)); } instance.inputs.src = await preRunSrcHook(instance.inputs.src); let size; let uploadTask; if (instance.inputs.src && typeof instance.inputs.src === 'string') { // Process source code, or skip if not provided... const { src: source, uploadPromise: task, cosInfo: sourceCosInfo } = await preRunSrcUpload( sdk, instance.orgName, instance.inputs, options ); if (options.debug) { console.debug(`the code will be uploaded to ${source}`); } instance.inputs.src = source; instance.inputs.sourceCosInfo = sourceCosInfo; uploadTask = task; } if (uploadTask) { await uploadTask; await api.instance.run(sdk, instance, method, credentials, options, size); } else { return api.instance.run(sdk, instance, method, credentials, options, size); } return null; }; /** * Finish a run */ const runFinish = async (sdk, method = null, instance = {}) => { return api.instance.runFinish(sdk, instance, method); }; /** * Run a deployment * @param {object} instance The Instance definition. * @param {object} credentials The credentials of the cloud providers required by the Instance. * @param {object} options Any options you wish to supply the Instance method. * @param {number} size The size in bytes of the source code, so that the Instance method can validate it beforehand. */ const deploy = async (sdk, instance, credentials, options) => { // Set defaults options.timeout = options.timeout || 600000; // 600000 = 10 minutes options.interval = options.interval || 500; // Set timer const startedAt = Date.now(); // Perform Run await run(sdk, 'deploy', instance, credentials, options); if (options.dev && utils.isChinaUser()) { // in tencent case, we'll setup polling in component CLI after each deployment return null; } // Set up polling // Poll function calls the instance constantly to check its status const poll = async () => { // Check if timed out if (Date.now() - options.timeout > startedAt) { throw new Error(`Request timed out after ${options.timeout / 60000} minutes`); } // Fetch instance let instanceRecord = await getByName( sdk, instance.orgName || instance.org, instance.stageName || instance.stage, instance.appName || instance.app, instance.instanceName || instance.name ); instanceRecord = instanceRecord.instance; // Sleep before calling again if (instanceRecord.instanceStatus === 'active') { return instanceRecord; } else if (instanceRecord.instanceStatus === 'error') { const error = new Error(instanceRecord.deploymentError); error.stack = instanceRecord.deploymentErrorStack; throw error; } await utils.sleep(options.interval); return poll(); }; return poll(); }; /** * Run a removal * @param {object} instance The Instance definition. * @param {object} credentials The credentials of the cloud providers required by the Instance. * @param {object} options Any options you wish to supply the Instance method. * @param {number} size The size in bytes of the source code, so that the Instance method can validate it beforehand. */ const remove = async (sdk, instance, credentials, options) => { // Set defaults options.timeout = options.timeout || 600000; // 600000 = 10 minutes options.interval = options.interval || 500; // Set timer const startedAt = Date.now(); // Perform Run await run(sdk, 'remove', instance, credentials, options); // Set up polling // Poll function calls the instance constantly to check its status const poll = async () => { // Check if timed out if (Date.now() - options.timeout > startedAt) { throw new Error(`Request timed out after ${options.timeout / 1000} seconds`); } // Fetch instance let instanceRecord = await getByName( sdk, instance.orgName || instance.org, instance.stageName || instance.stage, instance.appName || instance.app, instance.instanceName || instance.name ); instanceRecord = instanceRecord.instance; // Sleep before calling again if (instanceRecord.instanceStatus === 'inactive') { return instanceRecord; } else if (instanceRecord.instanceStatus === 'error') { const error = new Error(instanceRecord.deploymentError); error.stack = instanceRecord.deploymentErrorStack; throw error; } await utils.sleep(options.interval); return poll(); }; return poll(); }; module.exports = { generateId, validateAndFormat, create, save, getByName, listByOrgName, run, runFinish, deploy, remove, };