1 | const deploy = require('./deploy');
|
2 | const create = require('./create');
|
3 | const invoke = require('./invoke');
|
4 | const list = require('./list');
|
5 | const logs = require('./logs');
|
6 | const logger = require('./logger');
|
7 | const perf = require('./perf');
|
8 | const remove = require('./remove');
|
9 | const YMLUtil = require('./binarisYML');
|
10 | const { updateAPIKey } = require('./userConf');
|
11 | const { auth } = require('../sdk');
|
12 | const { validateName } = require('./nameUtil');
|
13 |
|
14 |
|
15 | const fs = require('mz/fs');
|
16 | const fse = require('fs-extra');
|
17 | const path = require('path');
|
18 | const inquirer = require('inquirer');
|
19 | const PassThroughStream = require('stream').PassThrough;
|
20 | const columnify = require('columnify');
|
21 |
|
22 |
|
23 |
|
24 |
|
25 |
|
26 |
|
27 |
|
28 |
|
29 |
|
30 |
|
31 |
|
32 |
|
33 | const exceptionWrapper = function tryCatchWrapper(funcToWrap) {
|
34 |
|
35 | return async function wrapped(options) {
|
36 | try {
|
37 |
|
38 |
|
39 |
|
40 |
|
41 |
|
42 |
|
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 |
|
54 | const 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 |
|
75 |
|
76 |
|
77 |
|
78 |
|
79 |
|
80 | const createHandler = async function createHandler(options) {
|
81 |
|
82 |
|
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 |
|
95 |
|
96 |
|
97 |
|
98 | const 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 |
|
108 |
|
109 |
|
110 |
|
111 | const removeHandler = async function removeHandler(options) {
|
112 | await remove(options.function);
|
113 | logger.info(`Removed function ${options.function}`);
|
114 | };
|
115 |
|
116 |
|
117 |
|
118 |
|
119 |
|
120 |
|
121 | const 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 |
|
139 |
|
140 |
|
141 |
|
142 | const perfHandler = async function perfHandler(options) {
|
143 | logger.info(`Running performance test on function ${options.function}.
|
144 | Executing ${options.maxRequests} invocations with ${options.concurrency} "thread${options.concurrency > 1 ? 's' : ''}".
|
145 | Stand 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(`
|
159 | Latencies
|
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 |
|
175 |
|
176 |
|
177 |
|
178 | const 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 |
|
202 |
|
203 |
|
204 |
|
205 | const 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 |
|
222 |
|
223 |
|
224 | const loginHandler = async function loginHandler() {
|
225 | logger.info(
|
226 | `Please enter your Binaris API key to deploy and invoke functions.
|
227 | If 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 |
|
242 | module.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 | };
|