UNPKG

serverless-python-requirements

Version:
206 lines (188 loc) 5.76 kB
const spawn = require('child-process-ext/spawn'); const isWsl = require('is-wsl'); const fse = require('fs-extra'); const path = require('path'); const os = require('os'); /** * Helper function to run a docker command * @param {string[]} options * @return {Object} */ async function dockerCommand(options, pluginInstance) { const cmd = 'docker'; try { return await spawn(cmd, options, { encoding: 'utf-8' }); } catch (e) { if ( e.stderrBuffer && e.stderrBuffer.toString().includes('command not found') ) { throw new pluginInstance.serverless.classes.Error( 'docker not found! Please install it.', 'PYTHON_REQUIREMENTS_DOCKER_NOT_FOUND' ); } throw e; } } /** * Build the custom Docker image * @param {string} dockerFile * @param {string[]} extraArgs * @return {string} The name of the built docker image. */ async function buildImage(dockerFile, extraArgs, pluginInstance) { const imageName = 'sls-py-reqs-custom'; const options = ['build', '-f', dockerFile, '-t', imageName]; if (Array.isArray(extraArgs)) { options.push(...extraArgs); } else { throw new pluginInstance.serverless.classes.Error( 'dockerRunCmdExtraArgs option must be an array', 'PYTHON_REQUIREMENTS_INVALID_DOCKER_EXTRA_ARGS' ); } options.push('.'); await dockerCommand(options, pluginInstance); return imageName; } /** * Find a file that exists on all projects so we can test if Docker can see it too * @param {string} servicePath * @return {string} file name */ function findTestFile(servicePath, pluginInstance) { if (fse.pathExistsSync(path.join(servicePath, 'serverless.yml'))) { return 'serverless.yml'; } if (fse.pathExistsSync(path.join(servicePath, 'serverless.yaml'))) { return 'serverless.yaml'; } if (fse.pathExistsSync(path.join(servicePath, 'serverless.json'))) { return 'serverless.json'; } if (fse.pathExistsSync(path.join(servicePath, 'requirements.txt'))) { return 'requirements.txt'; } throw new pluginInstance.serverless.classes.Error( 'Unable to find serverless.{yml|yaml|json} or requirements.txt for getBindPath()', 'PYTHON_REQUIREMENTS_MISSING_GET_BIND_PATH_FILE' ); } /** * Test bind path to make sure it's working * @param {string} bindPath * @return {boolean} */ async function tryBindPath(bindPath, testFile, pluginInstance) { const { serverless, log } = pluginInstance; const debug = process.env.SLS_DEBUG; const options = [ 'run', '--rm', '-v', `${bindPath}:/test`, 'alpine', 'ls', `/test/${testFile}`, ]; try { if (debug) { if (log) { log.debug(`Trying bindPath ${bindPath} (${options})`); } else { serverless.cli.log(`Trying bindPath ${bindPath} (${options})`); } } const ps = await dockerCommand(options, pluginInstance); if (debug) { if (log) { log.debug(ps.stdoutBuffer.toString().trim()); } else { serverless.cli.log(ps.stdoutBuffer.toString().trim()); } } return ps.stdoutBuffer.toString().trim() === `/test/${testFile}`; } catch (err) { if (debug) { if (log) { log.debug(`Finding bindPath failed with ${err}`); } else { serverless.cli.log(`Finding bindPath failed with ${err}`); } } return false; } } /** * Get bind path depending on os platform * @param {object} serverless * @param {string} servicePath * @return {string} The bind path. */ async function getBindPath(servicePath, pluginInstance) { // Determine bind path let isWsl1 = isWsl && !os.release().includes('microsoft-standard'); if (process.platform !== 'win32' && !isWsl1) { return servicePath; } // test docker is available await dockerCommand(['version'], pluginInstance); // find good bind path for Windows let bindPaths = []; let baseBindPath = servicePath.replace(/\\([^\s])/g, '/$1'); let drive; let path; bindPaths.push(baseBindPath); if (baseBindPath.startsWith('/mnt/')) { // cygwin "/mnt/C/users/..." baseBindPath = baseBindPath.replace(/^\/mnt\//, '/'); } if (baseBindPath[1] == ':') { // normal windows "c:/users/..." drive = baseBindPath[0]; path = baseBindPath.substring(3); } else if (baseBindPath[0] == '/' && baseBindPath[2] == '/') { // gitbash "/c/users/..." drive = baseBindPath[1]; path = baseBindPath.substring(3); } else { throw new Error(`Unknown path format ${baseBindPath.substr(10)}...`); } bindPaths.push(`/${drive.toLowerCase()}/${path}`); // Docker Toolbox (seems like Docker for Windows can support this too) bindPaths.push(`${drive.toLowerCase()}:/${path}`); // Docker for Windows // other options just in case bindPaths.push(`/${drive.toUpperCase()}/${path}`); bindPaths.push(`/mnt/${drive.toLowerCase()}/${path}`); bindPaths.push(`/mnt/${drive.toUpperCase()}/${path}`); bindPaths.push(`${drive.toUpperCase()}:/${path}`); const testFile = findTestFile(servicePath, pluginInstance); for (let i = 0; i < bindPaths.length; i++) { const bindPath = bindPaths[i]; if (await tryBindPath(bindPath, testFile, pluginInstance)) { return bindPath; } } throw new Error('Unable to find good bind path format'); } /** * Find out what uid the docker machine is using * @param {string} bindPath * @return {boolean} */ async function getDockerUid(bindPath, pluginInstance) { const options = [ 'run', '--rm', '-v', `${bindPath}:/test`, 'alpine', 'stat', '-c', '%u', '/bin/sh', ]; const ps = await dockerCommand(options, pluginInstance); return ps.stdoutBuffer.toString().trim(); } module.exports = { buildImage, getBindPath, getDockerUid };