mirror of
https://github.com/serverless/serverless.git
synced 2026-01-18 14:58:43 +00:00
258 lines
7.6 KiB
JavaScript
258 lines
7.6 KiB
JavaScript
'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 service’s 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 );
|
||
}; |