UNPKG

8.33 kBJavaScriptView Raw
1const deploy = require('./deploy');
2const create = require('./create');
3const invoke = require('./invoke');
4const list = require('./list');
5const logs = require('./logs');
6const logger = require('./logger');
7const perf = require('./perf');
8const remove = require('./remove');
9const YMLUtil = require('./binarisYML');
10const { updateAPIKey } = require('./userConf');
11const { auth } = require('../sdk');
12const { validateName } = require('./nameUtil');
13
14// core modules
15const fs = require('mz/fs');
16const fse = require('fs-extra');
17const path = require('path');
18const inquirer = require('inquirer');
19const PassThroughStream = require('stream').PassThrough;
20const columnify = require('columnify');
21
22/**
23 * Wrapper which centralizes the error handling/processing
24 * needed for the CLI. The two main purposes of this wrapper are...
25 *
26 * - formats and standardizes all errors which are thrown
27 * from the CLI/SDK
28 * - validates the incoming input(CLI input) and
29 * enforce rules(such as no sub-commands)
30 *
31 * @returns - error code of the command
32 */
33const exceptionWrapper = function tryCatchWrapper(funcToWrap) {
34 // eslint-disable-next-line consistent-return
35 return async function wrapped(options) {
36 try {
37 // git style sub commands are strictly not supported
38 // ie: bn create notacommand
39 // TODO(Ry): disabled until figure out how to do this in a cleaner way
40 // if (argData.args.length > 1) {
41 // throw new Error(`Argument "${argData.args[0]}"`,
42 // `is not a valid input to ${argData.rawArgs[2]}`);
43 // }
44 await funcToWrap(options);
45 process.exit(0);
46 } catch (err) {
47 logger.error(err.message);
48 process.exit(1);
49 }
50 };
51};
52
53/** Gather all of the relevant meta data about a function. */
54const gatherMeta = async function gatherMeta(options, forcePath = true) {
55 const metaObj = {
56 path: path.resolve(options.path || process.cwd()),
57 };
58
59 metaObj.name = options.function;
60 metaObj.printPath = '';
61 if (forcePath) {
62 const binarisConf = await YMLUtil.loadBinarisConf(metaObj.path);
63 metaObj.name = metaObj.name || YMLUtil.getFuncName(await YMLUtil.loadBinarisConf(metaObj.path));
64 validateName(metaObj.name);
65 metaObj.conf = YMLUtil.getFuncConf(binarisConf, metaObj.name);
66 await YMLUtil.checkFuncConf(metaObj.conf, metaObj.path);
67 metaObj.printPath = options.path ? ` -p ${metaObj.path}` : '';
68 }
69 metaObj.printName = options.function ? ` ${metaObj.name}` : '';
70 return metaObj;
71};
72
73/**
74 * Create a Binaris function based on the options given by
75 * the user. This boils down to creating template files with the
76 * correct information in the correct location.
77 *
78 * @param {object} options - Command line options.
79 */
80const createHandler = async function createHandler(options) {
81 // this is where the actual create function is called and immediately
82 // evaluated to determine if was successfully completed
83 const funcPath = path.resolve(options.path || process.cwd());
84 await fse.mkdirp(funcPath);
85 const funcName = await create(options.function, funcPath, options.runtime);
86 const optPath = options.path ? ` -p ${funcPath}` : '';
87 const optName = options.function ? ` ${funcName}` : '';
88 logger.info(
89`Created function ${funcName} in ${funcPath}
90 (use "bn deploy${optPath}${optName}" to deploy the function)`);
91};
92
93/**
94 * Deploys a previously created function to the Binaris cloud.
95 *
96 * @param {object} options - Command line options.
97 */
98const deployHandler = async function deployHandler(options) {
99 const meta = await gatherMeta(options);
100 const endpoint = await deploy(meta.name, meta.path, meta.conf);
101 logger.info(
102`Deployed function to ${endpoint}
103 (use "bn invoke${meta.printPath}${meta.printName}" to invoke the function)`);
104};
105
106/**
107 * Removes a previously deployed function from the Binaris cloud.
108 *
109 * @param {object} options - Command line options.
110 */
111const removeHandler = async function removeHandler(options) {
112 await remove(options.function);
113 logger.info(`Removed function ${options.function}`);
114};
115
116/**
117 * Invokes a previously deployed Binaris function.
118 *
119 * @param {object} options - Command line options.
120 */
121const invokeHandler = async function invokeHandler(options) {
122 if (options.data && options.json) {
123 throw new Error('Invoke flags --json(-j) and --data(-d) are mutually exclusive');
124 }
125
126 let funcData;
127 if (options.data) {
128 funcData = options.data;
129 } else if (options.json) {
130 funcData = await fs.readFile(options.json, 'utf8');
131 }
132
133 const response = await invoke(options.function, funcData);
134 logger.info(response.body);
135};
136
137/**
138 * Run performance test on deployed function
139 *
140 * @param {object} options - Command line options.
141 */
142const perfHandler = async function perfHandler(options) {
143 logger.info(`Running performance test on function ${options.function}.
144Executing ${options.maxRequests} invocations with ${options.concurrency} "thread${options.concurrency > 1 ? 's' : ''}".
145Stand by for results...
146`);
147 const report = await perf(options.function, options.maxRequests, options.concurrency);
148 logger.info('Perf summary');
149 logger.info('============');
150
151 logger.info(columnify({
152 'Total time': `${report.totalTimeSeconds.toFixed(1)} s`,
153 Invocations: report.totalRequests,
154 Errors: report.totalErrors,
155 Rate: `${report.rps.toFixed(1)} rps`,
156 }, { showHeaders: false }));
157
158 logger.info(`
159Latencies
160=========`);
161
162 logger.info(columnify({
163 Mean: `${report.meanLatencyMs.toFixed(1)} ms`,
164 Min: `${report.minLatencyMs.toFixed(1)} ms`,
165 Max: `${report.maxLatencyMs.toFixed(1)} ms`,
166 '50%': `${report.percentiles['50'].toFixed(1)} ms`,
167 '90%': `${report.percentiles['90'].toFixed(1)} ms`,
168 '95%': `${report.percentiles['95'].toFixed(1)} ms`,
169 '99%': `${report.percentiles['99'].toFixed(1)} ms`,
170 }, { showHeaders: false }));
171};
172
173/**
174 * List Binaris functions of given account
175 *
176 * @param {object} options - Command line options.
177 */
178const listHandler = async function listHandler(options) {
179 const listedFuncs = await list();
180 if (options.json) {
181 const rawData = Object.keys(listedFuncs).map(key => ({
182 name: key,
183 lastDeployed: `[${listedFuncs[key].tags.latest.modifiedAt}]`,
184 }));
185 logger.info(JSON.stringify(rawData));
186 } else {
187 const aggr = {};
188 Object.keys(listedFuncs).forEach((key) => {
189 aggr[key] = listedFuncs[key].tags.latest.modifiedAt;
190 });
191 if (Object.keys(aggr).length > 0) {
192 logger.info(columnify(aggr, {
193 columns: ['Function', 'Last Deployed'],
194 config: { Function: { minWidth: 25 } },
195 }));
196 }
197 }
198};
199
200/**
201 * Retrieve logs from a deployed Binaris function.
202 *
203 * @param {object} options - Command line options.
204 */
205const logsHandler = async function logsHandler(options) {
206 const logStream = new PassThroughStream({ objectMode: true });
207 let lineAcc = [];
208 logStream.on('data', (currLog) => {
209 if (currLog.msg.endsWith('\n')) {
210 logger.info(`[${currLog.time}] ${lineAcc.join('')}${currLog.msg.slice(0, -1)}`);
211 lineAcc = [];
212 } else {
213 lineAcc.push(currLog.msg);
214 }
215 });
216 await logs(options.function, options.tail, options.since, logStream);
217};
218
219
220/**
221 * Authenticate the user by saving the provided Binaris
222 * api key in a well known .binaris directory.
223 */
224const loginHandler = async function loginHandler() {
225 logger.info(
226`Please enter your Binaris API key to deploy and invoke functions.
227If you don't have a key, head over to https://binaris.com to request one`);
228 const answer = await inquirer.prompt([{
229 type: 'password',
230 name: 'apiKey',
231 message: 'API Key:',
232 }]);
233 if (!await auth.verifyAPIKey(answer.apiKey)) {
234 throw new Error('Invalid API key');
235 }
236 await updateAPIKey(answer.apiKey);
237 logger.info(
238`Authentication Succeeded
239 (use "bn create node8 hello" to create a Node.js template function in your CWD)`);
240};
241
242module.exports = {
243 deployHandler: exceptionWrapper(deployHandler),
244 createHandler: exceptionWrapper(createHandler),
245 invokeHandler: exceptionWrapper(invokeHandler),
246 listHandler: exceptionWrapper(listHandler),
247 logsHandler: exceptionWrapper(logsHandler),
248 loginHandler: exceptionWrapper(loginHandler),
249 perfHandler: exceptionWrapper(perfHandler),
250 removeHandler: exceptionWrapper(removeHandler),
251 unknownHandler: exceptionWrapper(() => {}),
252};