Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add provisionedConcurrency support #47

Open
wants to merge 7 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ testProjects/*/package-lock.json
testProjects/*/yarn.lock
.serverlessUnzipped
node_modules
node_modules.nosync
.vscode/
.eslintcache
dist
Expand Down
9 changes: 6 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,9 @@ inputs:
- xxx
- xxx
region: us-east-2 # (optional) aws region to deploy to. default is us-east-1.
provisionedConcurrency: 1 # (optional) provisioned concurrency, default is 0.
alias:
name: provisioned # (optional) alias for provisioned concurrency config, default is "provisioned".
```

**Note:** Unlike the `src` input, the `handler` input is relative to the `src` input, not to the current working directory.
Expand All @@ -118,22 +121,22 @@ $ serverless dev

### 6. Monitor

Anytime you need to know more about your running `aws-lambda` instance, you can run the following command to view the most critical info.
Anytime you need to know more about your running `aws-lambda` instance, you can run the following command to view the most critical info.

```
$ serverless info
```

This is especially helpful when you want to know the outputs of your instances so that you can reference them in another instance. It also shows you the status of your instance, when it was last deployed, and how many times it was deployed. You will also see a url where you'll be able to view more info about your instance on the Serverless Dashboard.

To digg even deeper, you can pass the `--debug` flag to view the state of your component instance in case the deployment failed for any reason.
To digg even deeper, you can pass the `--debug` flag to view the state of your component instance in case the deployment failed for any reason.

```
$ serverless info --debug
```
### 7. Remove

If you want to tear down your entire `aws-lambda` infrastructure that was created during deployment, just run the following command in the directory containing the `serverless.yml` file.
If you want to tear down your entire `aws-lambda` infrastructure that was created during deployment, just run the following command in the directory containing the `serverless.yml` file.
```
$ serverless remove
```
Expand Down
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,10 @@
"license": "Apache",
"dependencies": {},
"devDependencies": {
"@serverless/platform-client": "^0.24.0",
"@serverless/platform-client": "^4.2.2",
"aws-sdk": "^2.640.0",
"dotenv": "^8.2.0",
"babel-eslint": "9.0.0",
"dotenv": "^8.2.0",
"eslint": "5.6.0",
"eslint-config-prettier": "^3.6.0",
"eslint-plugin-import": "^2.18.0",
Expand Down
4 changes: 1 addition & 3 deletions src/_src/handler.js
Original file line number Diff line number Diff line change
@@ -1,3 +1 @@
module.exports.handler = async () => {
return 'Hello Serverless'
}
module.exports.handler = () => 'Hello Serverless'
35 changes: 34 additions & 1 deletion src/serverless.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,11 @@ const {
updateLambdaFunctionCode,
updateLambdaFunctionConfig,
getLambdaFunction,
getLambdaAlias,
createLambdaAlias,
updateLambdaAlias,
deleteLambdaAlias,
updateProvisionedConcurrencyConfig,
createOrUpdateFunctionRole,
createOrUpdateMetaRole,
deleteLambdaFunction,
Expand Down Expand Up @@ -80,16 +85,42 @@ class AwsLambda extends Component {
const createResult = await createLambdaFunction(this, clients.lambda, inputs)
inputs.arn = createResult.arn
inputs.hash = createResult.hash
inputs.version = createResult.version
console.log(`Successfully created an AWS Lambda function`)
} else {
// Update a Lambda function
inputs.arn = prevLambda.arn
console.log(`Updating ${inputs.name} AWS lambda function.`)
await updateLambdaFunctionCode(clients.lambda, inputs)
const updateResult = await updateLambdaFunctionCode(clients.lambda, inputs)
inputs.version = updateResult.version
await updateLambdaFunctionConfig(this, clients.lambda, inputs)
console.log(`Successfully updated AWS Lambda function`)
}

const prevAlias = await getLambdaAlias(clients.lambda, inputs)

// Maintain Alias and its provisionedConcurrency
if (inputs.provisionedConcurrency) {
if (!prevAlias) {
// Create alias and provisioned concurrency settings
console.log(`Creating alias "${inputs.aliasName}" for version ${inputs.version}`)
await createLambdaAlias(clients.lambda, inputs)
await updateProvisionedConcurrencyConfig(clients.lambda, inputs)
console.log(`Successfully created alias`)
} else {
// Update alias to point to the correct version
console.log(`Updating alias "${inputs.aliasName}" for version ${inputs.version}`)
// Must go before alias, otherwise AWS will block until existing instances are shutdown
await updateProvisionedConcurrencyConfig(clients.lambda, inputs)
await updateLambdaAlias(clients.lambda, inputs)
console.log(`Successfully updated alias`)
}
} else if (prevAlias) {
console.log(`Deleting provisioned concurrency configuration`)
await deleteLambdaAlias(clients.lambda, inputs)
console.log(`Successfully deleted provisioned concurrency configuration`)
}

// Update state
this.state.name = inputs.name
this.state.arn = inputs.arn
Expand All @@ -98,6 +129,8 @@ class AwsLambda extends Component {
return {
name: inputs.name,
arn: inputs.arn,
version: inputs.version,
provisionedConcurrency: inputs.provisionedConcurrency,
securityGroupIds: inputs.securityGroupIds,
subnetIds: inputs.subnetIds
}
Expand Down
110 changes: 106 additions & 4 deletions src/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,9 @@ const prepareInputs = (inputs, instance) => {
layers: inputs.layers || [],
securityGroupIds: inputs.vpcConfig ? inputs.vpcConfig.securityGroupIds : false,
subnetIds: inputs.vpcConfig ? inputs.vpcConfig.subnetIds : false,
retry: inputs.retry || 0
retry: inputs.retry || 0,
provisionedConcurrency: inputs.provisionedConcurrency || 0,
aliasName: (inputs.alias && inputs.alias.name) || 'provisioned'
}
}

Expand Down Expand Up @@ -226,7 +228,7 @@ const createLambdaFunction = async (instance, lambda, inputs) => {

try {
const res = await lambda.createFunction(params).promise()
return { arn: res.FunctionArn, hash: res.CodeSha256 }
return { arn: res.FunctionArn, hash: res.CodeSha256, version: res.Version }
} catch (e) {
if (e.message.includes(`The role defined for the function cannot be assumed by Lambda`)) {
// we need to wait after the role is created before it can be assumed
Expand Down Expand Up @@ -297,7 +299,101 @@ const updateLambdaFunctionCode = async (lambda, inputs) => {
functionCodeParams.ZipFile = await readFile(inputs.src)
const res = await lambda.updateFunctionCode(functionCodeParams).promise()

return res.FunctionArn
return { arn: res.FunctionArn, hash: res.CodeSha256, version: res.Version }
}

/**
* Get Lambda Alias
* @param {*} lambda
* @param {*} inputs
*/
const getLambdaAlias = async (lambda, inputs) => {
try {
const res = await lambda
.getAlias({
FunctionName: inputs.name,
Name: inputs.aliasName
})
.promise()

return {
name: res.Name,
description: res.Description,
arn: res.AliasArn,
resourceId: res.ResourceId,
routingConfig: res.RoutingConfig
}
} catch (e) {
if (e.code === 'ResourceNotFoundException') {
return null
}
throw e
}
}

/**
* Create a Lambda Alias
* @param {*} lambda
* @param {*} inputs
*/
const createLambdaAlias = async (lambda, inputs) => {
const params = {
FunctionName: inputs.name,
FunctionVersion: inputs.version,
Name: inputs.aliasName
}

const res = await lambda.createAlias(params).promise()
return { name: res.Name, arn: res.AliasArn }
}

/**
* Update a Lambda Alias
* @param {*} lambda
* @param {*} inputs
*/
const updateLambdaAlias = async (lambda, inputs) => {
const params = {
FunctionName: inputs.name,
FunctionVersion: inputs.version,
Name: inputs.aliasName
}

const res = await lambda.updateAlias(params).promise()
return { name: res.Name, arn: res.AliasArn }
}

/**
* Delete Lambda Alias, provisioned concurrency settings will be deleted together
* @param {*} lambda
* @param {*} inputs
*/
const deleteLambdaAlias = async (lambda, inputs) => {
const params = {
FunctionName: inputs.name,
Name: inputs.aliasName
}

const res = await lambda.deleteAlias(params).promise()
}

/**
* Update provisioned concurrency configurations
* @param {*} lambda
* @param {*} inputs
*/
const updateProvisionedConcurrencyConfig = async (lambda, inputs) => {
const params = {
FunctionName: inputs.name,
ProvisionedConcurrentExecutions: inputs.provisionedConcurrency,
Qualifier: inputs.aliasName
}

const res = await lambda.putProvisionedConcurrencyConfig(params).promise()
return {
allocated: res.AllocatedProvisionedConcurrentExecutions,
requested: res.RequestedProvisionedConcurrentExecutions
}
}

/**
Expand Down Expand Up @@ -391,7 +487,8 @@ const inputsChanged = (prevLambda, lambda) => {
'env',
'hash',
'securityGroupIds',
'subnetIds'
'subnetIds',
'provisionedConcurrency'
]
const inputs = pick(keys, lambda)
const prevInputs = pick(keys, prevLambda)
Expand Down Expand Up @@ -482,6 +579,11 @@ module.exports = {
updateLambdaFunctionCode,
updateLambdaFunctionConfig,
getLambdaFunction,
getLambdaAlias,
createLambdaAlias,
updateLambdaAlias,
deleteLambdaAlias,
updateProvisionedConcurrencyConfig,
inputsChanged,
deleteLambdaFunction,
removeAllRoles,
Expand Down