diff --git a/README.md b/README.md index 07bf6fb..4e9b68f 100644 --- a/README.md +++ b/README.md @@ -54,9 +54,9 @@ If you're using `async/await`, you can `await` the result of Lambda Warmer and ` ```javascript const warmer = require('lambda-warmer') -exports.handler = async (event) => { +exports.handler = async (event, context) => { // if a warming event - if (await warmer(event)) return 'warmed' + if (await warmer(event, {/* lambda warmer config here */}, context)) return 'warmed' // else proceed with handler logic return 'Hello from Lambda' } @@ -69,7 +69,7 @@ const warmer = require('lambda-warmer') exports.handler = (event, context, callback) => { // Start a promise chain - warmer(event).then(isWarmer => { + warmer(event, {/* lambda warmer config here */}, context).then(isWarmer => { // if a warming event if (isWarmer) { callback(null,'warmed') @@ -81,6 +81,19 @@ exports.handler = (event, context, callback) => { } ``` +### Passing the `context` Parameter + +When your Lambda function is invoked using an **alias** (e.g., `stable`, `prod`, etc.), you need to pass the `context` object to `lambda-warmer` so it can accurately determine if the function is invoking itself. + +```javascript +exports.handler = async (event, context) => { + if (await warmer(event, {}, context)) return 'warmed' + // Your handler logic +} +``` + +By passing context, `lambda-warmer` can extract the alias from `context`.invokedFunctionArn and properly handle warming invocations. + ## Configuration Options You can send in a configuration object as the second parameter to change Lambda Warmer's default behavior. All of the settings are optional. Here is a sample configuration object. @@ -122,7 +135,7 @@ Example passing a configuration: ```javascript exports.handler = async (event, context) => { // if a warming event - if (await warmer(event, { correlationId: context.awsRequestId, delay: 50 })) return 'warmed' + if (await warmer(event, { correlationId: context.awsRequestId, delay: 50 }, context)) return 'warmed' // else proceed with handler logic return 'Hello from Lambda' } @@ -209,6 +222,21 @@ myFunction: target: myOtherFunction3 ``` +## Support for Lambda Function Aliases + +Starting from version `2.1.0`, `lambda-warmer` supports warming Lambda functions invoked via aliases. + +### How to Use Alias Support + +When invoking your Lambda function using an alias, pass the `context` object to `lambda-warmer`: + +```javascript +exports.handler = async (event, context) => { + if (await warmer(event, {/* lambda warmer config here */}, context)) return 'warmed' + // Your handler logic +} +``` + ## Logs Logs are automatically generated unless the `log` configuration option is set to `false`. Logs contain useful information beyond just invocation data. The `warm` field indicates whether or not the Lambda function was already warm when invoked. The `lastAccessed` field is the timestamp (in milliseconds) of the last time the function was accessed by a non-warming event. Similarly, the `lastAccessedSeconds` gives you a counter (in seconds) of how long it's been since it has been accessed. These can be used to determine if your concurrency can be lowered. diff --git a/index.js b/index.js index 02a2186..d1f1576 100644 --- a/index.js +++ b/index.js @@ -19,7 +19,7 @@ const funcVersion = process.env.AWS_LAMBDA_FUNCTION_VERSION const delay = ms => new Promise(res => setTimeout(res, ms)) -const handleEvent = (event, config) => { +const handleEvent = (event, context, config) => { const isWarmerPing = event && event[config.flag] if (isWarmerPing) { let concurrency = @@ -68,8 +68,11 @@ const handleEvent = (event, config) => { warm = true lastAccess = Date.now() + const currentFunctionAlias = context && context.invokedFunctionArn ? context.invokedFunctionArn.split(':').pop() : funcVersion + // Check whether this lambda is invoking a different lambda let isDifferentTarget = !(target === `${funcName}:${funcVersion}` || + target === `${funcName}:${currentFunctionAlias}` || (target === funcName && funcVersion === '$LATEST')) // Fan out if concurrency is set higher than 1 @@ -115,7 +118,7 @@ const handleEvent = (event, config) => { } } -module.exports = (event, cfg = {}) => { +module.exports = (event, cfg = {}, context) => { let config = Object.assign( {}, { @@ -134,12 +137,12 @@ module.exports = (event, cfg = {}) => { let i = 0 const handleNext = () => { if (i < event.length) { - return handleEvent(event[i++], config).then(handleNext) + return handleEvent(event[i++], context, config).then(handleNext) } return Promise.resolve(event.some((e) => e[config.flag])) } return handleNext() } else { - return handleEvent(event, config) + return handleEvent(event, context, config) } } // end module diff --git a/test/target.js b/test/target.js index 7ac88f9..ff68b47 100644 --- a/test/target.js +++ b/test/target.js @@ -49,6 +49,18 @@ describe('Target Tests', function () { }) }) + it('should do nothing if there is a target in the event with the same function name and alias', function (done) { + let warmer = rewire('../index') + stub.returns(true) + + let event = { warmer: true, concurrency: 1, target: 'test-function:stable' } + warmer(event, { log: false }, {invokedFunctionArn: 'arn:aws:lambda:us-west-2:123456789:function:test-function:stable'}).then(out => { + expect(stub.callCount).to.equal(0) + expect(out).to.equal(true) + done() + }) + }) + it( 'should invoke the same lambda if there is no target in the event and the concurrency is more than 1', function (done) { @@ -95,7 +107,7 @@ describe('Target Tests', function () { }) }) - it('if the current function is not $LATEST and the target is with no alias (i.e. $LATEST)', function (done) { + it('if the current function is not $LATEST and the target is with no version (i.e. $LATEST)', function (done) { process.env.AWS_LAMBDA_FUNCTION_VERSION = '1' let warmer = rewire('../index') stub.returns(true) @@ -109,6 +121,21 @@ describe('Target Tests', function () { done() }) }) + + it('if the current function has an alias that is not the same as the target version', function (done) { + process.env.AWS_LAMBDA_FUNCTION_VERSION = '1' + let warmer = rewire('../index') + stub.returns(true) + + let event = { warmer: true, concurrency: 1, target: 'test-function:2' } + warmer(event, { log: false }, {invokedFunctionArn: 'arn:aws:lambda:us-west-2:123456789:function:test-function:1'}).then(out => { + expect(stub.callCount).to.equal(1) + expect(stub.args[0][0].InvocationType).to.equal('RequestResponse') + expect(stub.args[0][0].FunctionName).to.equal('test-function:2') + expect(out).to.equal(true) + done() + }) + }) }) it('should return true with two lambda invocations', function (done) {