UNPKG

binci

Version:

Utility for running containerized development workflows

151 lines (146 loc) 5.12 kB
'use strict' const _ = require('halcyon') const path = require('path') const dewindowize = require('./dewindowize') const command = { /** * @property {object} available args parsing instructions, matches config name * with command argument */ args: { expose: '-p', volumes: '-v', env: '-e', hosts: '--add-host' }, /** * Parses host environment variables specified with ${VAR} * @param {String} str The string to parse * @returns {String} */ parseHostEnvVars: (str) => str.toString().replace(/\$\{([^}]+)\}/g, (i, match) => { const [envVar, defaultValue = ''] = match.split(':-') return process.env.hasOwnProperty(envVar) ? process.env[envVar] : defaultValue }), /** * Parses volumes to allow relative pathing from host mounts * @param {Array} vols The volume array to parse * @returns {Array} */ parseVolumes: (vols) => vols.map((v) => v.startsWith('.') ? path.resolve(process.cwd(), v) : v), /** * Reduces args array into flagged arguments list * @param {string} type Name of the argument * @param {array} args Array of values * @returns {array} */ parseArgs: (type, args) => { args = type === 'volumes' ? command.parseVolumes(args) : args return _.chain((item) => ([command.args[type], command.parseHostEnvVars(item)]), args) }, /** * Parses config object and returns container name. Will have bc_ prefix and * InstanceID suffix if ephemeral, unaltered name for persisted containers * @param {object} cfg Config object * @returns {string} */ getName: (name, cfg) => { if (cfg.persist) return name return `bc_${name}_${global.instanceId}` }, /** * Parses config object and returns array of command arguments * @param {object} cfg Config object of instance or service * @returns {array} Command arguments */ getArgs: (cfg) => _.pipe([ _.keys, _.filter((key) => !!command.args[key]), _.chain(_.cond([ [(key) => !_.isType('Array', cfg[key]), (key) => { throw new Error(`Config error: '${key}' should be an array`) }], [_.T, (key) => command.parseArgs(key, cfg[key])] ])) ])(cfg), /** * Returns array of execution commands * @param {object} cfg Config object for instance * @returns {string} Execution script */ getExec: (cfg) => { const sh = '#!/bin/sh\nset -e;\n' const before = cfg.before ? `${cfg.before}\n` : '' const after = cfg.after ? `\n${cfg.after}` : '' // Custom exec, just run native task if (cfg.exec) return sh + before + cfg.exec + after // Ensure tasks exist if (!cfg.tasks) throw new Error('No tasks are defined') // Ensure a task is passed if (!cfg.run) throw new Error('No task has been specified') // Use predefined task(s) const run = _.pipe([ tasks => _.pick(tasks, cfg.tasks), _.toPairs, _.map(([name, command]) => { if (!command) throw new Error(`Task '${name}' does not exist.`) if (_.isType('object', command)) { if (!command.cmd) throw new Error(`Task '${name}' has no command defined.`) return command.cmd } return command }), _.join('\n') ])(cfg.run) return sh + before + run + after }, /** * Returns array of link arguments * @param {object} cfg Config object for the container * @returns {array} Link arguments */ getLinks: (cfg) => _.chain(_.pipe([_.toPairs, _.head, ([key, value]) => { return ['--link', `${command.getName(key, value)}:${key}`] }]))(cfg.services || []), /** * Returns full command arguments array * @param {object} cfg Config object for instance * @param {string} name Container name * @param {string} tmpdir Path to temp execution file * @param {boolean} primary If this is primary, i.e. not a service container * @returns {object|array} Arguments for docker command */ get: (cfg, name, tmpdir, primary = false) => { if (!cfg.from) throw new Error('Missing \'from\' property in config or argument') const cwd = dewindowize(process.cwd()) const workDir = cfg.workDir || cwd let args let cmd if (primary) { // Running the main project container args = ['run', '--rm', '-v', `${cwd}:${workDir}:cached`, '-v', `${tmpdir}:${tmpdir}`, '-w', workDir] if (cfg.privileged !== false) args.push('--privileged') /* istanbul ignore else */ if (process.stdout.isTTY) args.push('-it') cmd = ['sh', `${tmpdir}/binci.sh`] } else { // Running a service args = ['run', '-d'] if (cfg.privileged !== false) args.push('--privileged') if (!cfg.rmOnShutdown) args.push('--rm') cmd = cfg.command } // Has user config if (cfg.user) args.push(`--user=${command.parseHostEnvVars(cfg.user)}`) // All other config args = args.concat(_.flatten([ command.getArgs(cfg), command.getLinks(cfg), ['--name', command.getName(name, cfg)], cfg.from.toLowerCase(), cmd || [] ])) return primary ? { args, cmd: command.getExec(cfg) } : args } } module.exports = command