serverless-leo
Advanced tools
Comparing version 1.3.16 to 1.4.0
87
index.js
@@ -5,4 +5,6 @@ 'use strict' | ||
const { execSync } = require('child_process') | ||
const BbPromise = require('bluebird') | ||
const path = require('path') | ||
const fs = require('fs') | ||
const validate = require('./lib/validate') | ||
@@ -41,2 +43,26 @@ const compileLeo = require('./lib/leo') | ||
} | ||
}, | ||
'invoke-bot': { | ||
usage: 'Run a leo bot locally', | ||
lifecycleEvents: [ | ||
'leo-local' | ||
], | ||
options: { | ||
botNumber: { | ||
usage: 'Specify the bot number (default is 0)', | ||
shortcut: 'b' | ||
}, | ||
functionName: { | ||
usage: 'Specify the name of the function for the bot', | ||
shortcut: 'f' | ||
}, | ||
name: { | ||
usage: 'Specify the name of the bot', | ||
shortcut: 'n' | ||
}, | ||
stage: { | ||
usage: 'Specify the stage of the bot', | ||
shortcut: 's' | ||
} | ||
} | ||
} | ||
@@ -77,2 +103,63 @@ } | ||
.then(this.compileLeo) | ||
}, | ||
'invoke-bot:leo-local': () => { | ||
const { functionName, name, stage = 'test', botNumber = 0 } = this.serverless.pluginManager.cliOptions | ||
const lambdaName = functionName || name | ||
const regex = new RegExp(lambdaName) | ||
const functions = Object.keys(this.serverless.service.functions) | ||
const matchingFunctions = functions.filter(i => regex.test(i)) | ||
let functionKey | ||
if (matchingFunctions.length > 1) { | ||
functionKey = matchingFunctions.find(i => i === lambdaName) | ||
if (!functionKey) { | ||
throw new Error('Multiple matches found for bot name/lambda, please be more specific.') | ||
} | ||
} else if (matchingFunctions.length === 1) { | ||
functionKey = matchingFunctions[0] | ||
} else { | ||
throw new Error('Could not match bot name/lambda in serverless defined functions.') | ||
} | ||
const pathSegments = this.serverless.service.functions[functionKey].handler.split(/\//) | ||
pathSegments[pathSegments.length - 1] = 'serverless.yml' | ||
const serverlessYml = fs.readFileSync(path.join(...pathSegments)).toString() | ||
const serverlessJson = utils.ymlToJson(serverlessYml) | ||
// Build the event to invoke the lambda with | ||
let event | ||
let eventIndex = 0 | ||
if (serverlessJson[functionKey].events.length === 1) { | ||
event = serverlessJson[functionKey].events[0].leo | ||
} else { | ||
let filteredEvents = serverlessJson[functionKey].events.filter((event, index) => { | ||
if (Object.values(event.leo).some(leoKey => name === leoKey)) { | ||
eventIndex = index | ||
return true | ||
} | ||
}) | ||
if (filteredEvents.length === 1) { | ||
event = filteredEvents[0].leo | ||
} else { | ||
filteredEvents = serverlessJson[functionKey].events.filter((event, index) => { | ||
if (Object.values(event.leo).some(leoKey => new RegExp(name).test(leoKey))) { | ||
eventIndex = index | ||
return true | ||
} | ||
}) | ||
if (filteredEvents.length === 1) { | ||
event = filteredEvents[0].leo | ||
} | ||
} | ||
} | ||
if (!event) { | ||
throw new Error('Could not match the bot name with the bot configurations') | ||
} | ||
const botInfo = utils.getBotInfo(this.serverless.service.service, stage, functionKey, serverlessJson[functionKey].events, eventIndex, event, botNumber) | ||
event.botId = botInfo.id | ||
event.__cron = { | ||
id: botInfo.id, | ||
iid: '0', | ||
ts: Date.now(), | ||
force: true | ||
} | ||
this.serverless.cli.log(`Invoking local lambda ${functionKey} with data: ${JSON.stringify(event)}`) | ||
return execSync(`serverless invoke local -f ${functionKey} -d ${JSON.stringify(JSON.stringify(event))}`, { stdio: 'inherit' }) | ||
} | ||
@@ -79,0 +166,0 @@ } |
@@ -6,2 +6,3 @@ 'use strict' | ||
const BbPromise = require('bluebird') | ||
const { getBotInfo } = require('./utils') | ||
@@ -32,8 +33,7 @@ module.exports = { | ||
function addInstallProperty (botId, installProperty) { | ||
if (botIds.includes(botId)) { | ||
throw new Error(`Bot IDs must be unique. ${botId} has already been added to the cloudformation.`) | ||
function addInstallProperty (installProperty) { | ||
if (botIds.includes(installProperty.id)) { | ||
throw new Error(`Bot Ids must be unique. ${installProperty.id} has already been added to the cloudformation.`) | ||
} | ||
installProperty.id = botId | ||
botIds.push(botId) | ||
botIds.push(installProperty.id) | ||
if (registrations.length === 0) { | ||
@@ -47,3 +47,3 @@ registrations.push(cloneDeep(customInstall)) | ||
} | ||
currentRegister.Properties[botId] = installProperty | ||
currentRegister.Properties[installProperty.id] = installProperty | ||
} | ||
@@ -61,25 +61,25 @@ | ||
const config = leoEvent.leo instanceof Object ? leoEvent.leo : false | ||
const prefix = config && config.prefix ? `${config.prefix}` : undefined | ||
const suffix = config && config.suffix ? `${config.suffix}` : undefined | ||
const botPrefix = prefix ? `${prefix}-` : '' | ||
const sourceQueue = config ? config.queue : leoEvent.leo | ||
const botNumbers = times((config && config.botCount) || 1, Number) | ||
botNumbers.forEach(botNumber => { | ||
let botId | ||
let botSuffix = suffix ? `-${suffix}` : botNumber > 0 ? '-' + botNumber : '' | ||
// If there is no botPrefix, no source queue and multiple bots: add the eventIndex to the botSuffix (botId ultimately) | ||
if (!botPrefix && !sourceQueue && leoEvents.length > 1) { | ||
botSuffix = `-${eventIndex}${botSuffix}` | ||
} | ||
// Only add the queue to the bot name if there are multiple events and no prefix | ||
if (sourceQueue && !botPrefix && leoEvents.length > 1) { | ||
botId = `${this.serverless.service.service}-${stage}-${botPrefix}${sourceQueue}-${ymlFunctionName}${botSuffix}` | ||
} else { | ||
botId = `${this.serverless.service.service}-${stage}-${botPrefix}${ymlFunctionName}${botSuffix}` | ||
} | ||
const { | ||
id, | ||
cron, | ||
name, | ||
prefix, | ||
queue, | ||
register, | ||
suffix | ||
} = getBotInfo(this.serverless.service.service, stage, ymlFunctionName, leoEvents, eventIndex, config, botNumber) | ||
const installProperty = { | ||
id, | ||
name, | ||
time: cron, | ||
type: 'cron', | ||
settings: { | ||
botNumber, | ||
codeOverrides: config && config.codeOverrides, | ||
prefix, | ||
queue, | ||
source: queue, | ||
suffix | ||
@@ -91,20 +91,5 @@ }, | ||
} | ||
if (sourceQueue) { | ||
installProperty.settings.source = sourceQueue | ||
installProperty.settings.queue = sourceQueue | ||
if (queue || cron || register) { | ||
addInstallProperty(installProperty) | ||
} | ||
if (config && config.cron) { | ||
installProperty.time = config.cron | ||
} | ||
if (config && config.name) { | ||
installProperty.name = config.name | ||
} else { | ||
installProperty.name = botId.replace(`${this.serverless.service.service}-${stage}-`, '') | ||
} | ||
if (config && config.codeOverrides) { | ||
installProperty.settings.codeOverrides = config.codeOverrides | ||
} | ||
if (sourceQueue || (config && (config.cron || config.register))) { | ||
addInstallProperty(botId, installProperty) | ||
} | ||
}) | ||
@@ -111,0 +96,0 @@ }) |
@@ -50,3 +50,119 @@ const fs = require('fs') | ||
const getBotInfo = (serviceName, stage, ymlFunctionName, leoEvents, leoEventIndex, config, botNumber) => { | ||
let id | ||
let cron | ||
let name | ||
const prefix = config && config.prefix ? `${config.prefix}` : undefined | ||
const suffix = config && config.suffix ? `${config.suffix}` : undefined | ||
const botPrefix = prefix ? `${prefix}-` : '' | ||
const queue = config ? config.queue : leoEvents[leoEventIndex].leo | ||
let botSuffix = suffix ? `-${suffix}` : botNumber > 0 ? '-' + botNumber : '' | ||
// If there is no botPrefix, no source queue and multiple bots: add the eventIndex to the botSuffix (bot id ultimately) | ||
if (!botPrefix && !queue && leoEvents.length > 1) { | ||
botSuffix = `-${leoEventIndex}${botSuffix}` | ||
} | ||
// Only add the queue to the bot name if there are multiple events and no prefix | ||
if (queue && !botPrefix && leoEvents.length > 1) { | ||
id = `${serviceName}-${stage}-${botPrefix}${queue}-${ymlFunctionName}${botSuffix}` | ||
} else { | ||
id = `${serviceName}-${stage}-${botPrefix}${ymlFunctionName}${botSuffix}` | ||
} | ||
if (config && config.cron) { | ||
cron = config.cron | ||
} | ||
if (config && config.name) { | ||
name = config.name | ||
} else { | ||
name = id.replace(`${serviceName}-${stage}-`, '') | ||
} | ||
return { | ||
cron, | ||
id, | ||
name, | ||
prefix, | ||
queue, | ||
register: config && config.register, | ||
suffix | ||
} | ||
} | ||
const ymlToJson = yml => { | ||
if (/\t/.test(yml)) { | ||
throw new Error('Yml error: tabs are not expected. Use double spaced indentation.') | ||
} | ||
try { | ||
const json = {} | ||
let section = [] | ||
const spaceRegex = / {2}/g | ||
const lines = yml.replace(/\r/g, '').split('\n') | ||
lines.forEach(actualLine => { | ||
if (/^w*$/.test(actualLine)) { | ||
return | ||
} | ||
let isArrayItem = false | ||
let line = actualLine | ||
const spaceMatch = line.match(spaceRegex) | ||
const indentation = spaceMatch ? spaceMatch.length : 0 | ||
if (/^[\s]*-/.test(line)) { | ||
line = line.replace(/-/, '') | ||
isArrayItem = true | ||
} | ||
const splitLine = line.replace(spaceRegex, '').split(/:\s/).map(i => i.replace(/^[\s]*/, '').replace(/[\s]*$/, '')) | ||
const endingColonRegex = /:$/ | ||
const key = splitLine[0].replace(endingColonRegex, '') | ||
if (indentation < section.length) { | ||
section = section.slice(0, indentation) | ||
} | ||
let objToManipulate = getObjViaPath(json, section) | ||
if (!objToManipulate) { | ||
mutateViaPath(json, section, {}) | ||
objToManipulate = getObjViaPath(json, section) | ||
} | ||
if (isArrayItem) { | ||
if (!Array.isArray(objToManipulate)) { | ||
mutateViaPath(json, section, []) | ||
objToManipulate = getObjViaPath(json, section) | ||
} | ||
if (splitLine[1] === undefined && !endingColonRegex.test(splitLine[0])) { | ||
objToManipulate.push(key) | ||
} else { | ||
const newObject = {} | ||
newObject[key] = splitLine[1] === '' ? {} : splitLine[1] | ||
objToManipulate.push(newObject) | ||
section.push(objToManipulate.length - 1) | ||
} | ||
} else { | ||
objToManipulate[key] = splitLine[1] || '' | ||
} | ||
section.push(key) | ||
}) | ||
return json | ||
} catch (err) { | ||
console.log('!!!!! Error parsing yml. Note: it is expected to be in double spaced format.') | ||
throw err | ||
} | ||
} | ||
const getObjViaPath = (obj, section) => { | ||
if (!section || section.length === 0) { | ||
return obj | ||
} else if (section[0] === '') { | ||
return getObjViaPath(obj, section.slice(1, section.length)) | ||
} else { | ||
return getObjViaPath(obj[section[0]], section.slice(1, section.length)) | ||
} | ||
} | ||
const mutateViaPath = (obj, section, value) => { | ||
if (section.length === 1) { | ||
obj[section] = value | ||
} else if (section[0] === '') { | ||
mutateViaPath(obj, section.slice(1, section.length), value) | ||
} else { | ||
mutateViaPath(obj[section[0]], section.slice(1, section.length), value) | ||
} | ||
} | ||
module.exports = { | ||
getBotInfo, | ||
getDirInfo, | ||
@@ -56,3 +172,4 @@ replaceTextInFile, | ||
renameFilesInFolder, | ||
recursePathAndOperate | ||
recursePathAndOperate, | ||
ymlToJson | ||
} |
{ | ||
"name": "serverless-leo", | ||
"version": "1.3.16", | ||
"version": "1.4.0", | ||
"description": "Serverless plugin for leo microservices", | ||
@@ -5,0 +5,0 @@ "main": "index.js", |
@@ -55,4 +55,4 @@ # serverless-leo | ||
events: | ||
- leo | ||
cron: 0 0 1 * * * # Trigger Lambda from a Leo Cron (down to minute) | ||
- leo: | ||
cron: 0 0 1 * * * # Trigger Lambda from a Leo Cron (down to minute) | ||
``` | ||
@@ -97,4 +97,4 @@ | ||
events: | ||
- leo: | ||
cron: 0 0 1 * * * | ||
- leo: | ||
cron: 0 0 1 * * * | ||
``` | ||
@@ -109,5 +109,5 @@ The bot will be named the same as the lambda. | ||
events: | ||
- leo: | ||
queue: helloWorldTestQueue | ||
botCount: 4 | ||
- leo: | ||
queue: helloWorldTestQueue | ||
botCount: 4 | ||
``` | ||
@@ -122,4 +122,4 @@ This allows you to partition the queue, or change the configuration of the bot based on the value of the variable at run time. | ||
events: | ||
- leo: | ||
register: true | ||
- leo: | ||
register: true | ||
``` | ||
@@ -139,1 +139,13 @@ | ||
In this example leoStack would be used when deployed using --stage dev. leoRegister would be used when using --stage test | ||
### Invoke local bot | ||
You can invoke a bot to run on your local machine as if it were running in the cloud. It will respect the checkpoint and update it as it progresses. | ||
##### Run it locally with this command (from the main microservice directory) | ||
``` | ||
serverless invoke-bot -s test -f your_function_name | ||
``` | ||
#### Options - in order to run a specific bot there are additional options you can pass | ||
##### -f/--functionName The name of the function the bot uses. | ||
##### -s/--stage The stage of the microservice. | ||
##### -n/--name The name of the bot. A regular expression will be used to compare against the prefix, suffix, name, queue, and cron on the bot. | ||
##### -b/--botNumber The botNumber to execute. If there is no suffix/prefix/queue and there are multiple bots for the lambda, the botNumber can be used to identify the bot. |
Shell access
Supply chain riskThis module accesses the system shell. Accessing the system shell increases the risk of executing arbitrary code.
Found 1 instance in 1 package
58563
1306
147
10
1