From 9ea1ac7f4911290f9fb98f37b267a322731b2ec5 Mon Sep 17 00:00:00 2001 From: Vicary A Date: Wed, 5 May 2021 22:50:48 +0800 Subject: [PATCH 1/7] add provisionedConcurrency support --- README.md | 9 ++-- src/serverless.js | 35 ++++++++++++++- src/utils.js | 106 ++++++++++++++++++++++++++++++++++++++++++++-- 3 files changed, 143 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index c2a78db..adf86f2 100644 --- a/README.md +++ b/README.md @@ -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. @@ -118,7 +121,7 @@ $ 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 @@ -126,14 +129,14 @@ $ 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 ``` diff --git a/src/serverless.js b/src/serverless.js index 769f211..c09c678 100644 --- a/src/serverless.js +++ b/src/serverless.js @@ -8,6 +8,11 @@ const { updateLambdaFunctionCode, updateLambdaFunctionConfig, getLambdaFunction, + getLambdaAlias, + createLambdaAlias, + updateLambdaAlias, + deleteLambdaAlias, + updateProvisionedConcurrencyConfig, createOrUpdateFunctionRole, createOrUpdateMetaRole, deleteLambdaFunction, @@ -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(inputs.name, inputs.aliasName) + + // Maintain Alias and the underlying provisionedConcurrency configuration + 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) + console.log(`Successfully created alias`) + } else { + // Update alias to point to the correct version + console.log(`Updating alias "${inputs.aliasName}" for version ${inputs.version}`) + await updateLambdaAlias(clients.lambda, inputs) + console.log(`Successfully updated alias`) + } + + await updateProvisionedConcurrencyConfig(clients.lambda, inputs) + console.log(`Successfully updated provisioned concurrency configuration`) + } 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 @@ -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 } diff --git a/src/utils.js b/src/utils.js index 4f34f67..3f7e9d4 100644 --- a/src/utils.js +++ b/src/utils.js @@ -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, + aliasName: inputs.alias && inputs.alias.name || "provisioned" } } @@ -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 @@ -300,6 +302,98 @@ const updateLambdaFunctionCode = async (lambda, inputs) => { return res.FunctionArn } +/** + * Get Lambda Alias + * @param {String} functionName + * @param {String} aliasName + */ +const getLambdaAlias = async (functionName, aliasName) => { + try { + const res = await lambda.getAlias({ + FunctionName: functionName, + Name: 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, assuming provisioned concurrency settings will be deleted along the way + * @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, + ProvisionedConcurrencyExecutions: inputs.provisionedConcurrency, + Qualifier: inputs.aliasName + } + + const res = await lambda.putProvisionedConcurrencyConfig(params).promise() + return { + allocated: res.AllocatedProvisionedConcurrentExecutions, + requested: res.RequestedProvisionedConcurrentExecutions + } +} + /** * Get Lambda Function * @param {*} lambda @@ -391,7 +485,8 @@ const inputsChanged = (prevLambda, lambda) => { 'env', 'hash', 'securityGroupIds', - 'subnetIds' + 'subnetIds', + 'provisionedConcurrency' ] const inputs = pick(keys, lambda) const prevInputs = pick(keys, prevLambda) @@ -482,6 +577,11 @@ module.exports = { updateLambdaFunctionCode, updateLambdaFunctionConfig, getLambdaFunction, + getLambdaAlias, + createLambdaAlias, + updateLambdaAlias, + deleteLambdaAlias, + updateProvisionedConcurrencyConfig, inputsChanged, deleteLambdaFunction, removeAllRoles, From ccaeb3b4cd49a6d57e8fc451d7a64e9b83c20da8 Mon Sep 17 00:00:00 2001 From: Vicary A Date: Thu, 6 May 2021 22:33:27 +0800 Subject: [PATCH 2/7] audit fix --- package.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index 0568fcc..a7777ea 100644 --- a/package.json +++ b/package.json @@ -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", From ec00994c6e38f84ecfa844dd0a2702e04ba13b13 Mon Sep 17 00:00:00 2001 From: Vicary A Date: Thu, 6 May 2021 22:35:24 +0800 Subject: [PATCH 3/7] fix typo and deployment order --- src/_src/handler.js | 3 --- src/serverless.js | 14 +++++++------- src/utils.js | 18 ++++++++++-------- 3 files changed, 17 insertions(+), 18 deletions(-) delete mode 100644 src/_src/handler.js diff --git a/src/_src/handler.js b/src/_src/handler.js deleted file mode 100644 index f7f884f..0000000 --- a/src/_src/handler.js +++ /dev/null @@ -1,3 +0,0 @@ -module.exports.handler = async () => { - return 'Hello Serverless' -} diff --git a/src/serverless.js b/src/serverless.js index c09c678..63d31f1 100644 --- a/src/serverless.js +++ b/src/serverless.js @@ -85,36 +85,36 @@ class AwsLambda extends Component { const createResult = await createLambdaFunction(this, clients.lambda, inputs) inputs.arn = createResult.arn inputs.hash = createResult.hash - inputs.version = createResult.version; + 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.`) const updateResult = await updateLambdaFunctionCode(clients.lambda, inputs) - inputs.version = updateResult.version; + inputs.version = updateResult.version await updateLambdaFunctionConfig(this, clients.lambda, inputs) console.log(`Successfully updated AWS Lambda function`) } - const prevAlias = await getLambdaAlias(inputs.name, inputs.aliasName) + const prevAlias = await getLambdaAlias(clients.lambda, inputs) - // Maintain Alias and the underlying provisionedConcurrency configuration + // 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`) } - - await updateProvisionedConcurrencyConfig(clients.lambda, inputs) - console.log(`Successfully updated provisioned concurrency configuration`) } else if (prevAlias) { console.log(`Deleting provisioned concurrency configuration`) await deleteLambdaAlias(clients.lambda, inputs) diff --git a/src/utils.js b/src/utils.js index 3f7e9d4..87c1d04 100644 --- a/src/utils.js +++ b/src/utils.js @@ -63,7 +63,7 @@ const prepareInputs = (inputs, instance) => { subnetIds: inputs.vpcConfig ? inputs.vpcConfig.subnetIds : false, retry: inputs.retry || 0, provisionedConcurrency: inputs.provisionedConcurrency, - aliasName: inputs.alias && inputs.alias.name || "provisioned" + aliasName: (inputs.alias && inputs.alias.name) || 'provisioned' } } @@ -299,7 +299,7 @@ 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 } } /** @@ -307,12 +307,14 @@ const updateLambdaFunctionCode = async (lambda, inputs) => { * @param {String} functionName * @param {String} aliasName */ -const getLambdaAlias = async (functionName, aliasName) => { +const getLambdaAlias = async (lambda, inputs) => { try { - const res = await lambda.getAlias({ - FunctionName: functionName, - Name: aliasName - }).promise(); + const res = await lambda + .getAlias({ + FunctionName: inputs.name, + Name: inputs.aliasName + }) + .promise() return { name: res.Name, @@ -383,7 +385,7 @@ const deleteLambdaAlias = async (lambda, inputs) => { const updateProvisionedConcurrencyConfig = async (lambda, inputs) => { const params = { FunctionName: inputs.name, - ProvisionedConcurrencyExecutions: inputs.provisionedConcurrency, + ProvisionedConcurrentExecutions: inputs.provisionedConcurrency, Qualifier: inputs.aliasName } From 1d259b3b405f5147d69120ee71cf1d5ced7f8a4d Mon Sep 17 00:00:00 2001 From: Vicary A Date: Thu, 6 May 2021 22:40:52 +0800 Subject: [PATCH 4/7] fix jsdoc --- src/utils.js | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/utils.js b/src/utils.js index 87c1d04..8375495 100644 --- a/src/utils.js +++ b/src/utils.js @@ -304,8 +304,8 @@ const updateLambdaFunctionCode = async (lambda, inputs) => { /** * Get Lambda Alias - * @param {String} functionName - * @param {String} aliasName + * @param {*} lambda + * @param {*} inputs */ const getLambdaAlias = async (lambda, inputs) => { try { @@ -332,7 +332,7 @@ const getLambdaAlias = async (lambda, inputs) => { } /** - * Create a lambda alias + * Create a Lambda Alias * @param {*} lambda * @param {*} inputs */ @@ -348,7 +348,7 @@ const createLambdaAlias = async (lambda, inputs) => { } /** - * Update a lambda alias + * Update a Lambda Alias * @param {*} lambda * @param {*} inputs */ @@ -364,7 +364,7 @@ const updateLambdaAlias = async (lambda, inputs) => { } /** - * Delete lambda alias, assuming provisioned concurrency settings will be deleted along the way + * Delete Lambda Alias, provisioned concurrency settings will be deleted together * @param {*} lambda * @param {*} inputs */ From e9a288340b374225032ac52a1f669cf61ccb302b Mon Sep 17 00:00:00 2001 From: Vicary A Date: Sat, 8 May 2021 15:11:52 +0800 Subject: [PATCH 5/7] support nosync for iCloud Drive --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 5a05d27..69d5431 100644 --- a/.gitignore +++ b/.gitignore @@ -11,6 +11,7 @@ testProjects/*/package-lock.json testProjects/*/yarn.lock .serverlessUnzipped node_modules +node_modules.nosync .vscode/ .eslintcache dist From 61f53e80ecd12df761078c4af82a31b470a8b831 Mon Sep 17 00:00:00 2001 From: Vicary A Date: Sun, 9 May 2021 22:03:26 +0800 Subject: [PATCH 6/7] add placeholder handler back --- src/_src/handler.js | 1 + 1 file changed, 1 insertion(+) create mode 100644 src/_src/handler.js diff --git a/src/_src/handler.js b/src/_src/handler.js new file mode 100644 index 0000000..68c2f82 --- /dev/null +++ b/src/_src/handler.js @@ -0,0 +1 @@ +module.exports.handler = () => 'Hello Serverless' From f333c6d1a8975b9cbb8fb53e14fe27ec50c7b041 Mon Sep 17 00:00:00 2001 From: Vicary A Date: Sun, 9 May 2021 22:03:41 +0800 Subject: [PATCH 7/7] better fallback value --- src/utils.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/utils.js b/src/utils.js index 8375495..8e5b79c 100644 --- a/src/utils.js +++ b/src/utils.js @@ -62,7 +62,7 @@ const prepareInputs = (inputs, instance) => { securityGroupIds: inputs.vpcConfig ? inputs.vpcConfig.securityGroupIds : false, subnetIds: inputs.vpcConfig ? inputs.vpcConfig.subnetIds : false, retry: inputs.retry || 0, - provisionedConcurrency: inputs.provisionedConcurrency, + provisionedConcurrency: inputs.provisionedConcurrency || 0, aliasName: (inputs.alias && inputs.alias.name) || 'provisioned' } }