UNPKG

netlify-plugin-nimbella

Version:

Nimbella plugin to extend Netlify Sites with stateful and portable serverless APIs.

185 lines (160 loc) 5.94 kB
const {existsSync} = require('fs') const {readdir, writeFile} = require('fs').promises const {join} = require('path') const {tmpdir} = require('os') /** * Deploys actions under a directory. Currently limited to lambda functions. * * @param {function} run - function provided under utils by Netlify to build event functions. * @param {string} functionsBuildDir - Path to the functions/actions directory where sources were built. * @param {string} timeout - Max allowed duration for each function activation. * @param {string} memory - Max memory allowed for each function activation. * @param {string} envsFile - Path to environment file (optional). */ async function deployActions({ run, functionsBuildDir, timeout, memory, envsFile }) { const files = await readdir(functionsBuildDir) return Promise.all( files.map(async (file) => { const [actionName, extension] = file.split('.') // This assume name.ext format const command = [ `nim action update ${actionName} ${join( functionsBuildDir, file )} --web=raw` ] if (timeout) { command.push(`--timeout=${Number(timeout)}`) } if (memory) { command.push(`--memory=${Number(memory)}`) } if (extension === 'js') { // Run node functions with lambda compatibility command.push('--kind nodejs-lambda:10 --main handler') } else { const chalk = require('chalk') // Else let the cli infer the kind based on the file extension console.warn( chalk.yellow( file + ': Lambda compatibility is not available for this function.' ), `The main handler must be called 'main', must accept a JSON object as input, and return JSON object as output.`, 'The function will run as Apache OpenWhisk action on the Nimbella Cloud.', 'See https://github.com/apache/openwhisk/blob/master/docs/actions.md#languages-and-runtimes', 'for examples of serverless function signatures that are comptatible.' ) } if (envsFile) { command.push(`--env-file=${envsFile}`) } const {stderr, exitCode, failed} = await run.command(command.join(' '), { reject: false, stdout: 'ignore' }) const success = exitCode === 0 && !failed const message = success ? 'done.' : String(stderr) console.log(`Deployment status ${file}: ${message}`) return success }) ) } async function constructEnvFileAsJson(inputs) { if (inputs.envs && inputs.envs.length > 0) { const toExport = {} const filename = join(tmpdir(), 'env.json') inputs.envs.forEach((env) => { toExport[env] = process.env[env] }) await writeFile(filename, JSON.stringify(toExport)) return filename } } // Scans the _redirects file and netlify.toml for redirects that map to // '/.netlify/functions/*' as their target. These are remapped to new endpoints. // The first set of rewrites is pre-pended to the _redirects file, then the second // set, if any, because precedence is top to bottom. async function rewriteRedirects(constants, {apihost, namespace}) { const redirects = [] const { parseRedirectsFormat, parseNetlifyConfig } = require('netlify-redirect-parser') const filterRedirects = async (description, filename, parser) => { if (filename && existsSync(filename)) { console.log( `Found ${description}. Rewriting rules that redirect (200 rewrites) to '/.netlify/functions/*'.` ) const {success} = await parser(filename) const toAdd = success.filter( (redirect) => redirect.status === 200 && redirect.to && redirect.to.startsWith('/.netlify/functions/') ) redirects.push(...toAdd) } } const redirectsFile = join(constants.PUBLISH_DIR, '_redirects') await filterRedirects('_redirects file', redirectsFile, parseRedirectsFormat) const configFile = constants.CONFIG_PATH await filterRedirects( 'redirect rules in netlify.toml', configFile, parseNetlifyConfig ) return redirects.map((redirect) => { const redirectPath = redirect.to.split('/.netlify/functions/')[1] return `${ redirect.from || redirect.path } https://${apihost}/api/v1/web/${namespace}/default/${redirectPath} 200!` }) } async function makeEnvFileAndDeploy({utils, inputs, functionsBuildDir}) { try { const envsFile = await constructEnvFileAsJson(inputs) const status = await deployActions({ run: utils.run, envsFile, functionsBuildDir, timeout: inputs.timeout, memory: inputs.memory }) const failCount = status.filter((_) => !_).length if (failCount > 0) { utils.build.failBuild(`Failed to deploy ${failCount} functions`) } } catch (error) { utils.build.failBuild('Failed to deploy the functions', {error}) } } async function buildAndDeployNetlifyFunctions({utils, inputs}) { const cpx = require('cpx') const build = require('netlify-lambda/lib/build') const functionsBuildDir = `functions-build-${Date.now()}` try { const stats = await build.run(functionsBuildDir, inputs.functions) console.log(stats.toString(stats.compilation.options.stats)) // Copy any remaining files that do not end with '.js'. // Arguably we should not do this, if the functions are Netlify // functions (Lambda) they will not run as is in the OpenWhisk // runtime used by Nimbella. Will issue warning during deploy. cpx.copySync(inputs.functions + '/**/*.!(js)', functionsBuildDir) } catch (error) { utils.build.failBuild('Failed to build the functions', {error}) return } await makeEnvFileAndDeploy({utils, inputs, functionsBuildDir}) } module.exports = { deployActions, constructEnvFileAsJson, rewriteRedirects, makeEnvFileAndDeploy, buildAndDeployNetlifyFunctions }