UNPKG

5.47 kBJavaScriptView Raw
1'use strict';
2
3const BbPromise = require('bluebird');
4const _ = require('lodash');
5const path = require('path');
6const chalk = require('chalk');
7const stdin = require('get-stdin');
8const spawn = require('child_process').spawn;
9
10class OpenWhiskInvokeLocal {
11 constructor(serverless, options) {
12 this.serverless = serverless;
13 this.options = options || {};
14 this.provider = this.serverless.getProvider('openwhisk');
15
16 this.hooks = {
17 'invoke:local:invoke': () => BbPromise.bind(this)
18 .then(this.validate)
19 .then(this.loadEnvVars)
20 .then(this.invokeLocal),
21 };
22 }
23
24 validate() {
25 if (!this.serverless.config.servicePath) {
26 throw new this.serverless.classes.Error('This command can only be run inside a service.');
27 }
28
29 this.options.functionObj = this.serverless.service.getFunction(this.options.function);
30
31 return new BbPromise(resolve => {
32 if (this.options.data) {
33 resolve();
34 } else if (this.options.path) {
35 const absolutePath = path.isAbsolute(this.options.path) ?
36 this.options.path :
37 path.join(this.serverless.config.servicePath, this.options.path);
38 if (!this.serverless.utils.fileExistsSync(absolutePath)) {
39 throw new this.serverless.classes.Error('The file you provided does not exist.');
40 }
41 this.options.data = this.serverless.utils.readFileSync(absolutePath);
42 resolve();
43 } else {
44 stdin().then(input => {
45 this.options.data = input;
46 resolve();
47 });
48 }
49 }).then(() => {
50 const params = this.options.functionObj.parameters || {};
51 let data = {};
52 try {
53 if(typeof this.options.data === 'object') {
54 data = this.options.data;
55 } else {
56 data = JSON.parse(this.options.data);
57 }
58 } catch (exception) {
59 // do nothing if it's a simple string or object already
60 }
61
62 this.options.data = Object.assign(params, data);
63 });
64 }
65
66 loadEnvVars() {
67 return this.provider.props().then(props => {
68 const envVars = {
69 __OW_API_KEY: props.auth,
70 __OW_API_HOST: props.apihost,
71 __OW_ACTION_NAME: this.calculateFunctionName(this.options.function, this.options.functionObj),
72 __OW_NAMESPACE: this.calculateFunctionNameSpace(this.options.functionObj)
73 };
74
75 _.merge(process.env, envVars);
76
77 return BbPromise.resolve();
78 })
79 }
80
81 calculateFunctionName(functionName, functionObject) {
82 const namespace = this.calculateFunctionNameSpace(functionObject);
83 const name = functionObject.name || `${this.serverless.service.service}_${functionName}`;
84 return `/${namespace}/${name}`
85 }
86
87 calculateFunctionNameSpace(functionObject) {
88 return functionObject.namespace
89 || this.serverless.service.provider.namespace
90 || '_';
91 }
92
93 invokeLocal() {
94 const runtime = this.options.functionObj.runtime
95 || this.serverless.service.provider.runtime
96 || 'nodejs:default';
97 const handler = this.options.functionObj.handler;
98 const handlerPath = handler.split('.')[0];
99 const handlerName = handler.split('.')[1];
100
101 if (runtime.startsWith('nodejs')) {
102 return this.invokeLocalNodeJs(
103 handlerPath,
104 handlerName,
105 this.options.data);
106 } else if (runtime.startsWith('python')) {
107 return this.invokeLocalPython(
108 handlerPath,
109 handlerName,
110 this.options.data);
111 }
112
113 throw new this.serverless.classes
114 .Error('You can only invoke Node.js or Python functions locally.');
115 }
116
117 invokeLocalNodeJs(handlerPath, handlerName, params) {
118 let action, result;
119
120 try {
121 /*
122 * we need require() here to load the handler from the file system
123 * which the user has to supply by passing the function name
124 */
125 action = require(path // eslint-disable-line global-require
126 .join(this.serverless.config.servicePath, handlerPath))[handlerName];
127 } catch (error) {
128 this.serverless.cli.consoleLog(error);
129 process.exit(0);
130 }
131
132 try {
133 let result = action(params)
134 return Promise.resolve(result).then(result => {
135 this.serverless.cli.consoleLog(JSON.stringify(result, null, 4));
136 }).catch(err => {
137 const errorResult = {
138 errorMessage: err.message || err,
139 errorType: err.constructor.name,
140 };
141 this.serverless.cli.consoleLog(chalk.red(JSON.stringify(errorResult, null, 4)));
142 process.exitCode = 1;
143 })
144 } catch (err) {
145 const errorResult = {
146 errorMessage: err.message,
147 errorType: err.constructor.name,
148 };
149 this.serverless.cli.consoleLog(chalk.red(JSON.stringify(errorResult, null, 4)));
150 process.exitCode = 1;
151 }
152 }
153
154 invokeLocalPython(handlerPath, handlerName, params) {
155 if (process.env.VIRTUAL_ENV) {
156 process.env.PATH = `${process.env.VIRTUAL_ENV}/bin:${process.env.PATH}`;
157 }
158 return new BbPromise(resolve => {
159 const python = spawn(
160 path.join(__dirname, 'invoke.py'), [handlerPath, handlerName], { env: process.env });
161 python.stdout.on('data', (buf) => this.serverless.cli.consoleLog(buf.toString()));
162 python.stderr.on('data', (buf) => this.serverless.cli.consoleLog(buf.toString()));
163 python.stdin.write(JSON.stringify(params || {}));
164 python.stdin.end();
165 python.on('close', () => resolve());
166 });
167 }
168}
169
170module.exports = OpenWhiskInvokeLocal;