binaris
Version:
Binaris SDK & CLI
379 lines (349 loc) • 11.2 kB
JavaScript
'use strict';
const yargs = require('yargs');
const logger = require('./lib/logger');
const { getRealm } = require('./lib/userConf');
const { parseTimeString } = require('./lib/timeUtil');
const {
deployHandler, createHandler, feedbackHandler, invokeHandler,
listHandler, logsHandler, loginHandler, removeHandler, perfHandler,
showHandler, statsHandler,
} = require('./lib');
const { forceRealm } = require('./sdk');
/**
* Prints the provided message(along with optionally displayed help)
* and then exits the process with a code of 1.
*
* @param message - string to print before exiting
* @param displayHelp - should help be displayed before your error?
*/
const msgAndExit = function msgAndExit(message, displayHelp) {
if (displayHelp) yargs.showHelp();
logger.error(message);
process.exit(1);
};
const handleCommand = async function handleCommand(options, specificHandler) {
const realm = await getRealm();
if (realm) {
forceRealm(realm);
}
await specificHandler(options);
process.exit(0);
};
const runtimes = require('./lib/runtimes');
const executionModels = ['exclusive', 'concurrent'];
const pathOption = ['path', {
alias: 'p',
describe: 'Use directory dir.',
type: 'string',
}];
yargs
.usage(
`Binaris command line interface
Usage: $0 <command> [options]` // eslint-disable-line comma-dangle
)
.command('create <runtime> <function> [options]', 'Create a function from template', (yargs0) => {
yargs0
.usage('Usage: $0 create <runtime> <function> [options]')
.positional('runtime', {
choices: runtimes,
type: 'string',
})
.positional('function', {
describe: 'Function name',
type: 'string',
})
.option('config', {
describe: 'Function configuration',
})
.option('executionModel', {
alias: 'e',
choices: executionModels,
describe: 'Execution model for your function',
hidden: true,
})
.option('path', {
alias: 'p',
describe: 'Use directory dir. "create" will create this directory if needed.',
type: 'string',
})
.strict()
.example(
` // Create a function from python3 template
bn create python3 ninja
// Create a function from node8 template with exclusive execution model
bn create node8 pirate --config.executionModel=exclusive
// Create a function from python2 template with concurrent execution model, and FOO env
bn create python2 hello --config.executionModel=concurrent --config.env.FOO=bar
// Create a function from python3 template with deploy time BAR env
bn create python2 hello --config.env.BAR
`);
}, async (argv) => {
// validating here and not in coerce to keep output consistent
if (argv.config !== undefined) {
const configType = typeof argv.config;
if (configType !== 'object' || Array.isArray(argv.config)) {
msgAndExit(`Non object create configuration options: ${argv.config}`, true);
}
// transform boolean true for --config.env.VAR to work properly
if (argv.config.env && typeof argv.config.env === 'object') {
for (const v in argv.config.env) {
if (argv.config.env[v] === true) {
// eslint-disable-next-line no-param-reassign
argv.config.env[v] = null;
}
}
}
}
await handleCommand(argv, createHandler);
})
.command('deploy <function> [options]', 'Deploys a function to the cloud', (yargs0) => {
yargs0
.usage('Usage: $0 deploy <function> [options]')
.positional('function', {
describe: 'Function name',
type: 'string',
})
.option(...pathOption)
.strict();
}, async (argv) => {
await handleCommand(argv, deployHandler);
})
.command('remove <function> [options]', 'Remove a previously deployed function', (yargs0) => {
yargs0
.usage('Usage: $0 remove <function> [options]')
.positional('function', {
describe: 'Function name',
type: 'string',
})
.strict();
}, async (argv) => {
await handleCommand(argv, removeHandler);
})
.command('invoke <function> [options]', 'Invoke a Binaris function', (yargs0) => {
yargs0
.usage('Usage: $0 invoke <function> [options]')
.positional('function', {
describe: 'Function name',
type: 'string',
})
.option('json', {
alias: 'j',
describe: 'Path to file containing JSON data',
type: 'string',
})
.option('data', {
alias: 'd',
describe: 'Data to send with invocation',
type: 'string',
})
.strict()
.example(
` // invoke a function
bn invoke foo
// invoke using JSON file data
bn invoke foo --json ./path/to/myfile.json
// invoke foo and send JSON data in the body
bn invoke foo --data '{ "name": "helloworld" }'`);
}, async (argv) => {
await handleCommand(argv, invokeHandler);
})
.command('list [options]', 'List all deployed functions', (yargs0) => {
yargs0
.usage('Usage: $0 list [options]')
.option('json', {
describe: 'Output as JSON',
type: 'boolean',
})
.strict();
}, async (argv) => {
await handleCommand(argv, listHandler);
})
.command('perf <function> [options]', 'Measure invocation latency (experimental)', (yargs0) => {
yargs0
.usage('Usage: $0 perf <function> [options]')
.positional('function', {
describe: 'Function name',
type: 'string',
})
.option('maxRequests', {
alias: 'n',
describe: 'Number of invocations to perform',
type: 'number',
default: 5000,
})
.option('concurrency', {
alias: 'c',
describe: 'How many requests run concurrently',
type: 'number',
default: 1,
})
.option('data', {
alias: 'd',
describe: 'Data to include with performance invocations',
type: 'string',
})
.option('maxSeconds', {
alias: 't',
describe: 'Maximum time in seconds',
type: 'number',
})
.strict()
.example(
` // Run performance test on function foo (5000 invocations, serially)
bn perf foo
// Run performance test with 1,000 invocations
bn perf foo -n 1000
// Run performance test with 1,000 invocations and 4 concurrent connections
bn perf foo -n 1000 -c 4
// Run performance test with 1,000 invocations and send JSON data with each request
bn perf foo -n 1000 -d '{ "someData": "myData" }'
// Run performance test only up to 10 seconds
bn perf foo -t 10
`);
}, async (argv) => {
await handleCommand(argv, perfHandler);
})
.command('logs <function> [options]', 'Print the logs of a function', (yargs0) => {
yargs0
.usage('Usage: $0 logs <function> [options]')
.positional('function', {
describe: 'Function name',
type: 'string',
})
.option('tail', {
alias: 't',
describe: 'Outputs logs in "tail -f" fashion',
type: 'boolean',
})
.option('since', {
alias: 's',
describe: 'Outputs logs after the given ISO timestamp',
type: 'string',
})
.strict()
.example(
` // retrieve all logs
bn logs foo
// tail all logs
bn logs foo --tail
// ISO
bn logs foo --since 2018-03-09T22:12:21.861Z
// unix
bn logs foo --since 1520816105798
// offset format
bn logs foo --since 3d
bn logs foo --since 13hours
bn logs foo --since 9s`);
}, async (argv) => {
if (argv.since) {
try {
// eslint-disable-next-line no-param-reassign
argv.since = parseTimeString(argv.since);
} catch (err) {
msgAndExit(err.message);
}
}
await handleCommand(argv, logsHandler);
})
.command('stats [options]', /* hidden: 'Print usage statistics' */ false, (yargs0) => {
yargs0
.usage('Usage: $0 stats [options]')
.option('since', {
alias: 's',
describe: 'Output statistics after given ISO timestamp (inclusive)',
type: 'string',
})
.option('until', {
alias: 'u',
describe: 'Output statistics until given ISO timestamp (non-inclusive)',
type: 'string',
})
.option('json', {
describe: 'Output as JSON',
type: 'boolean',
})
.strict()
.example(
` // Retrieve all usage statistics of the account
bn stats
// Retrieve all statistics since the timestamp until now (~1 minute)
bn stats --since 2018-03-09T22:12:21.861Z
// Statistics over the last 24h
bn stats --since 1d
// Retrieve all statistics of a certain month
bn stats --since 2018-03-01T00:00:00Z --until 2018-04-01T00:00:00Z`);
}, async (argv) => {
if (argv.since) {
try {
// eslint-disable-next-line no-param-reassign
argv.since = parseTimeString(argv.since).toISOString();
} catch (err) {
msgAndExit(err.message);
}
}
if (argv.until) {
try {
// eslint-disable-next-line no-param-reassign
argv.until = parseTimeString(argv.until).toISOString();
} catch (err) {
msgAndExit(err.message);
}
}
await handleCommand(argv, statsHandler);
})
.command('show [config]', 'Show Binaris account configuration', (yargs0) => {
yargs0
.usage('Usage: $0 show --all | <config>')
.positional('config', {
describe: 'What to show',
choices: ['accountId', 'apiKey'],
})
.option('all', {
alias: 'a',
describe: 'Show it all',
type: 'boolean',
})
.strict();
}, async (argv) => {
if (!argv.all && !argv.config) {
msgAndExit('"bn show" requires positional arguments or the "--all" flag', true);
}
await handleCommand(argv, showHandler);
})
.command('login', 'Login to your Binaris account using an API key and account id', (yargs0) => {
yargs0
.usage('Usage: $0 login')
.strict();
}, async () => {
await loginHandler();
})
.command('feedback <email> <message>', 'Provide feedback on the Binaris product', (yargs0) => {
yargs0
.usage('Usage: $0 feedback <email> <message>')
.positional('email', {
describe: 'User email',
type: 'string',
})
.positional('message', {
describe: 'Feedback message',
type: 'string',
})
.example(
` // Send feedback message to us with your email address
bn feedback "you@email.com" "Great Product!"`);
}, async (argv) => {
await handleCommand(argv, feedbackHandler);
})
// .strict()
.demandCommand(1, 'Please provide at least 1 valid command')
.help('help')
.epilog(`Tip:
You can export BINARIS_LOG_LEVEL=[silly|debug|verbose] to view debug logs`)
.alias('help', 'h')
.wrap(null);
const commands = yargs.getCommandInstance().getCommands();
// first command pushed to command stack(from user input)
const currCommand = yargs.argv._[0];
if (currCommand && commands.indexOf(currCommand) === -1) {
msgAndExit(`Unknown command: '${currCommand}'`, true);
}