1 | 'use strict';
|
2 |
|
3 | const BbPromise = require('bluebird');
|
4 | const _ = require('lodash');
|
5 | const path = require('path');
|
6 | const chalk = require('chalk');
|
7 | const stdin = require('get-stdin');
|
8 | const spawn = require('child_process').spawn;
|
9 |
|
10 | class 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 |
|
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 |
|
123 |
|
124 |
|
125 | action = require(path
|
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 |
|
170 | module.exports = OpenWhiskInvokeLocal;
|