serverless/lib/actions/FunctionRun.js
2016-04-18 18:42:52 +07:00

258 lines
7.6 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

'use strict';
/**
* Action: FunctionRun
* - Runs the function in the CWD for local testing
*/
module.exports = function(S) {
const path = require('path'),
SError = require(S.getServerlessPath('Error')),
SCli = require(S.getServerlessPath('utils/cli')),
SUtils = S.utils,
BbPromise = require('bluebird'),
chalk = require('chalk');
/**
* FunctionRun Class
*/
class FunctionRun extends S.classes.Plugin {
static getName() {
return 'serverless.core.' + this.name;
}
registerActions() {
S.addAction(this.functionRun.bind(this), {
handler: 'functionRun',
description: `Runs the service locally. Reads the services runtime and passes it off to a runtime-specific runner`,
context: 'function',
contextAction: 'run',
options: [
{
option: 'region',
shortcut: 'r',
description: 'region you want to run your function in'
},
{
option: 'stage',
shortcut: 's',
description: 'stage you want to run your function in'
},
{
option: 'runDeployed',
shortcut: 'd',
description: 'invoke deployed function'
},
{
option: 'invocationType',
shortcut: 'i',
description: 'Valid Values: Event | RequestResponse | DryRun . Default is RequestResponse'
},
{
option: 'log',
shortcut: 'l',
description: 'Show the log output'
}
],
parameters: [
{
parameter: 'name',
description: 'The name of the function you want to run',
position: '0'
}
]
});
return BbPromise.resolve();
}
/**
* Action
*/
functionRun(evt) {
this.evt = evt;
// Flow
return this._prompt()
.bind(this)
.then(this._validateAndPrepare)
.then(() => {
// Run local or deployed
if (this.evt.options.runDeployed) {
return this._runDeployed();
} else {
return this._runLocal();
}
})
.then(() => this.evt);
}
_prompt() {
if (!S.config.interactive || this.evt.options.stage) return BbPromise.resolve();
return this.cliPromptSelectStage('Function Run - Choose a stage: ', this.evt.options.stage, false)
.then(stage => this.evt.options.stage = stage)
.then(() => this.cliPromptSelectRegion('Select a region: ', false, true, this.evt.options.region, this.evt.options.stage) )
.then(region => this.evt.options.region = region);
}
/**
* Validate And Prepare
*/
_validateAndPrepare() {
// If CLI and path is not specified, deploy from CWD if Function
if (S.cli && !this.evt.options.name) {
// Get all functions in CWD
if (!SUtils.fileExistsSync(path.join(process.cwd(), 's-function.json'))) {
return BbPromise.reject(new SError('You must be in a function folder to run it'));
}
this.evt.options.name = SUtils.readFileSync(path.join(process.cwd(), 's-function.json')).name
}
this.function = S.getProject().getFunction(this.evt.options.name);
// Missing function
if (!this.function) return BbPromise.reject(new SError(`Function ${this.evt.options.name} does not exist in your project.`));
// load event data if not it not present already
if (this.evt.data.event) return BbPromise.resolve();
return this._getEventFromStdIn()
.then(event => event || S.utils.readFile(this.function.getRootPath('event.json')))
.then(event => this.evt.data.event = event);
}
/**
* Get event data from STDIN
* If
*/
_getEventFromStdIn() {
return new BbPromise((resolve, reject) => {
const stdin = process.stdin;
const chunks = [];
const onReadable = () => {
const chunk = stdin.read();
if (chunk !== null) chunks.push(chunk);
};
const onEnd = () => {
try {
resolve(JSON.parse(chunks.join('')));
} catch(e) {
reject(new SError("Invalid event JSON"));
}
};
stdin.setEncoding('utf8');
stdin.on('readable', onReadable);
stdin.on('end', onEnd);
setTimeout((() => {
stdin.removeListener('readable', onReadable);
stdin.removeListener('end', onEnd);
stdin.end()
resolve()
}), 5);
});
}
/**
* Run Local
*/
_runLocal() {
const name = this.evt.options.name;
const stage = this.evt.options.stage;
const region = this.evt.options.region;
const event = this.evt.data.event;
if (!name) return BbPromise.reject(new SError('Please provide a function name to run'));
SCli.log(`Running ${name}...`);
return this.function.run(stage, region, event)
.then(result => this.evt.data.result = result);
}
/**
* Run Deployed
*/
_runDeployed() {
const stage = this.evt.options.stage;
this.evt.options.invocationType = this.evt.options.invocationType || 'RequestResponse';
this.evt.options.region = this.evt.options.region || S.getProject().getAllRegions(stage)[0].name;
const region = this.evt.options.region;
if (this.evt.options.invocationType !== 'RequestResponse') {
this.evt.options.logType = 'None';
} else {
this.evt.options.logType = this.evt.options.log ? 'Tail' : 'None'
}
// validate stage: make sure stage exists
if (!S.getProject().validateStageExists(stage)) {
return BbPromise.reject(new SError(`Stage "${stage}" does not exist in your project`, SError.errorCodes.UNKNOWN));
}
// validate region: make sure region exists in stage
if (!S.getProject().validateRegionExists(stage, region)) {
return BbPromise.reject(new SError(`Region "${region}" does not exist in stage "${stage}"`));
}
// Invoke Lambda
let params = {
FunctionName: this.function.getDeployedName({ stage, region }),
// ClientContext: new Buffer(JSON.stringify({x: 1, y: [3,4]})).toString('base64'),
InvocationType: this.evt.options.invocationType,
LogType: this.evt.options.logType,
Payload: new Buffer(JSON.stringify(this.evt.data.event)),
Qualifier: stage
};
return S.getProvider('aws')
.request('Lambda', 'invoke', params, stage, region)
.then( reply => {
const color = !reply.FunctionError ? 'white' : 'red';
if (reply.Payload) {
const response = JSON.parse(reply.Payload);
if (S.config.interactive) console.log(chalk[color](JSON.stringify(response, null, 4)));
this.evt.data.result = {
response,
status: reply.FunctionError ? 'error' : 'success'
};
}
if (reply.LogResult) {
console.log(chalk.gray('--------------------------------------------------------------------'));
const logResult = new Buffer(reply.LogResult, 'base64').toString();
logResult.split('\n').forEach( line => console.log(SCli.formatLambdaLogEvent(line)) );
}
})
.catch(e => {
this.evt.data.result = {
status: 'error',
message: e.message,
stack: e.stack
};
return BbPromise.reject(e);
});
}
}
return( FunctionRun );
};