Skip to content

Commit

Permalink
feat: support aliases
Browse files Browse the repository at this point in the history
  • Loading branch information
naorpeled committed Oct 12, 2024
1 parent 41011a3 commit 6f4eaf5
Show file tree
Hide file tree
Showing 3 changed files with 67 additions and 9 deletions.
36 changes: 32 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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'
}
Expand All @@ -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')
Expand All @@ -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.
Expand Down Expand Up @@ -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'
}
Expand Down Expand Up @@ -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.
Expand Down
11 changes: 7 additions & 4 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 =
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -115,7 +118,7 @@ const handleEvent = (event, config) => {
}
}

module.exports = (event, cfg = {}) => {
module.exports = (event, cfg = {}, context) => {
let config = Object.assign(
{},
{
Expand All @@ -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
29 changes: 28 additions & 1 deletion test/target.js
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down Expand Up @@ -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)
Expand All @@ -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) {
Expand Down

0 comments on commit 6f4eaf5

Please sign in to comment.