#!/usr/bin/env node const { createClient } = require('../lib/client') const getConfig = require('microservice-config') const passwordPrompt = require('password-prompt') const asTable = require('as-table') const Vorpal = require('vorpal') const main = async() => { const vorpal = new Vorpal() function log(...args) { vorpal.activeCommand.log(...args) } const fillModel = async model => { if (!model.options) model.options = {} model.options.username = model.options.username || process.env.PORTAINER_USERNAME if (!model.options.username) throw new Error('Username is required.') model.options.password = model.options.password || process.env.PORTAINER_PASSWORD || await passwordPrompt('Password: ', {method: 'hide'}) if (!model.options.password) throw new Error('Password is required.') model.options.url = model.options.url || process.env.PORTAINER_URL if (!model.options.url) throw new Error('URL is required.') if (model.options.verbose) { log('options', JSON.stringify({ ...model, options: { ...model.options, password: '******' } }, null, 4)) } } const command = commandSpec => vorpal.command(commandSpec) .option('-v, --verbose', 'Enable verbose logging') .option('-u, --username ', 'Portainer username. Defaults to environment variable PORTAINER_USERNAME.') .option('-p, --password ', 'Portainer password. Defaults to environment variable PORTAINER_PASSWORD.') .option('-r, --url ', 'Portainer URL, such as http://server:9000. Defaults to environment variable PORTAINER_URL.') .option('-k, --insecure', 'Ignore certificate errors.') .option('-j, --json', 'Format JSON.') const action = handler => async function(model, cb) { try { await fillModel(model) if (model.options.insecure) { process.env['NODE_TLS_REJECT_UNAUTHORIZED'] = 0 } const client = createClient({ log, ...model.options }) const result = await handler(client, model) if (result) { if (!model.options.json) { if (Array.isArray(result)) { log(asTable(result)) } else { log(asTable([result])) } } else { log(JSON.stringify(result)) } } cb() } catch (err) { console.warn(err.message || err) process.exit(1) } } command('stack deploy ', 'Creates or updates a stack.') .option('-c, --compose-file ', 'Path to docker-compose.yml file. Defaults to ./docker-compose.yml.') .option('-P, --prune', 'Prune obsolete or orphaned containers. Defaults to true.') .option('-W, --warning', 'Add warning message to head of yaml. Defaults to true.') .action(action(async (client, model) => { if (model.options.prune === undefined) model.options.prune = true if (model.options.warning === undefined) model.options.warning = true if (model.options['compose-file'] === undefined) model.options['compose-file'] = 'docker-compose.yml' log('Upserting stack') return await client.upsertStack({ stackFile: model.options['compose-file'], ...model, ...model.options }) })) command('stack ls', 'List stacks') .action(action(async (client, model) => await client.getStacks())) command('stack rm ', 'Removes a stack') .action(action(async (client, model) => await client.deleteStack(model))) command('endpoint ls', 'List endpoints') .action(action(async (client, model) => await client.getEndpoints())) const args = process.argv.length === 2 ? [...process.argv, 'help'] : process.argv await vorpal.parse(args).catch(err => { console.error(err) process.exit(1) }) } main().catch(err => { console.error(err) process.exit(1) })