diff --git a/docs/deprecations.md b/docs/deprecations.md index 4d4b560eecc..fa5fd998594 100644 --- a/docs/deprecations.md +++ b/docs/deprecations.md @@ -336,7 +336,7 @@ While not recommended, you can keep using the old hashing algorithm by setting ` An error occurred: FooLambdaVersion3IV5NZ3sE5T2UFimCOai2Tc6eCaW7yIYOP786U0Oc - A version for this Lambda function exists ( 11 ). Modify the function to create a new version.. ``` -It is an expected behavior. AWS complains here that received a different hash for very same lambda configuration. To workaround that, you need to modify your function(s) code and try to redeploy it again. One common approach is to modify an utility function that is used by all/most of your Lambda functions. +It is an expected behavior. AWS complains here that received a different hash for very same lambda configuration. To workaround that, you need to modify your function(s) code and try to redeploy it again. One common approach is to modify an utility function that is used by all/most of your Lambda functions. There's also a semi-automated migration available and described in [V3 Upgrade docs](./guides/upgrading-v3.md#lambda-hashing-algorithm).
 
diff --git a/docs/guides/upgrading-v3.md b/docs/guides/upgrading-v3.md index ad804866914..c246ab30cf0 100644 --- a/docs/guides/upgrading-v3.md +++ b/docs/guides/upgrading-v3.md @@ -196,6 +196,31 @@ That allowed us to make the KMS configuration consistent with all other AWS reso `alexaSkill` events now require an `appId` ([learn more](../deprecations.md#support-for-alexaskill-event-without-appid-is-to-be-removed)). That change was required to implement a more stable deployment, as well as to deploy more restricted IAM permissions. +### Lambda Hashing Algorithm + +By default, Lambda version hashes will now be generated using improved algorithm (fixed determinism issues). As it is a breaking change that requires more manual effort during migration, it is still possible (but not recommended) to keep using old algorithm by using the following configuration: + +``` +provider: + lambdaHashingVersion: 20200924 +``` + +However, we highly encourage an upgrade to the new algorithm. Safe upgrade requires change in configuration or code of all your functions. You can do it safely following the guide below: + +**NOTE**: Please keep in mind that these changes require two deployments with manual configuration adjustment between them. It also creates two additional versions and temporarily overrides descriptions of your functions. Migration will need to be done separately for each of your environments/stages. + +1. Run `sls deploy` with additional `--enforce-hash-update` flag to override description for all your functions and force creation of new versions. +2. Set `provider.lambdaHashingVersion` to `20201221` in your configuration. +3. Run `sls deploy`, this time without additional `--enforce-hash-update` flag. + +Now your whole service will be fully migrated to new Lambda Hashing Algorithm. + +If you do not want to temporarily override descriptions of your functions or would like to avoid creating unnecessary versions of your functions, you might want to use one of the following approaches: + +- Ensure that code for all your functions will change during deployment, set `provider.lambdaHashingVersion: 20201221` in your configuration, and run `sls deploy`. Due to the fact that all functions have code changed, all your functions will be migrated to new hashing algorithm. Please note that the change can be caused by e.g. upgrading a dependency used by all your functions so you can pair it with regular chores. +- Add a dummy file that will be included in deployment artifacts for all your functions, set `provider.lambdaHashingVersion: 20201221` in your configuration, and run `sls deploy`. Due to the fact that all functions have code changed, all your functions will be migrated to new hashing algorithm. +- If it is safe in your case (e.g. it's only development sandbox), you can also tear down the whole service by `sls remove`, set `provider.lambdaHashingVersion: 20201221` in your configuration, and run `sls deploy`. Newly recreated environment will be using new hashing algorithm. + ### Low-level changes Internal changes that may impact plugins or advanced use cases: diff --git a/lib/cli/commands-schema/aws-service.js b/lib/cli/commands-schema/aws-service.js index 520452ad3ef..0c4f8e71eea 100644 --- a/lib/cli/commands-schema/aws-service.js +++ b/lib/cli/commands-schema/aws-service.js @@ -34,6 +34,11 @@ commands.set('deploy', { usage: 'Enables S3 Transfer Acceleration making uploading artifacts much faster.', type: 'boolean', }, + 'enforce-hash-update': { + usage: + 'Enforces new function version by overriding descriptions across all your functions. To be used only when migrating to new hashing algorithm.', + type: 'boolean', + }, }, // TODO: Remove deprecated events with v3 lifecycleEvents: [ diff --git a/lib/plugins/aws/deploy/index.js b/lib/plugins/aws/deploy/index.js index 6cd94d2e434..4c79a575b36 100644 --- a/lib/plugins/aws/deploy/index.js +++ b/lib/plugins/aws/deploy/index.js @@ -12,7 +12,7 @@ const validateTemplate = require('./lib/validateTemplate'); const updateStack = require('../lib/updateStack'); const existsDeploymentBucket = require('./lib/existsDeploymentBucket'); const path = require('path'); -const { style, log, progress, writeText } = require('@serverless/utils/log'); +const { style, log, progress, writeText, legacy } = require('@serverless/utils/log'); const memoize = require('memoizee'); const mainProgress = progress.get('main'); @@ -201,6 +201,16 @@ class AwsDeploy { writeText(); writeServiceOutputs(this.serverless.serviceOutputs); writeServiceOutputs(this.serverless.servicePluginOutputs); + + if (this.options['enforce-hash-update']) { + legacy.log( + 'Your service has been deployed with new hashing algorithm. Please set "provider.lambdaHashingVersion: \'20201221\'" in your service configuration and re-deploy without "--enforce-hash-update" flag to restore function descriptions.' + ); + log.notice(); + log.notice( + 'Your service has been deployed with new hashing algorithm. Please set "provider.lambdaHashingVersion: \'20201221\'" in your service configuration and re-deploy without "--enforce-hash-update" flag to restore function descriptions.' + ); + } }, }; } diff --git a/lib/plugins/aws/package/compile/functions.js b/lib/plugins/aws/package/compile/functions.js index 7b1803a2010..f81788cf49a 100644 --- a/lib/plugins/aws/package/compile/functions.js +++ b/lib/plugins/aws/package/compile/functions.js @@ -51,6 +51,7 @@ class AwsCompileFunctions { } if ( !this.serverless.service.provider.lambdaHashingVersion && + !this.options['enforce-hash-update'] && Object.keys(this.serverless.service.functions).length && Object.values(this.serverless.service.functions).some(({ handler }) => handler) && (this.serverless.service.provider.versionFunctions || @@ -148,7 +149,7 @@ class AwsCompileFunctions { async addFileToHash(filePath, hash) { const lambdaHashingVersion = this.serverless.service.provider.lambdaHashingVersion; - if (lambdaHashingVersion >= 20201221) { + if (lambdaHashingVersion >= 20201221 || this.options['enforce-hash-update']) { const filePathHash = await getHashForFilePath(filePath); hash.write(filePathHash); } else { @@ -161,6 +162,7 @@ class AwsCompileFunctions { const functionResource = this.cfLambdaFunctionTemplate(); const functionObject = this.serverless.service.getFunction(functionName); functionObject.package = functionObject.package || {}; + const enforceHashUpdate = this.options['enforce-hash-update']; if (!functionObject.handler && !functionObject.image) { throw new ServerlessError( @@ -463,6 +465,10 @@ class AwsCompileFunctions { // to make sure that a new version is created on configuration changes and // not only on source changes. + if (enforceHashUpdate) { + functionResource.Properties.Description = 'temporary-description-to-enforce-hash-update'; + } + const versionHash = crypto.createHash('sha256'); versionHash.setEncoding('base64'); const layerConfigurations = _.cloneDeep( @@ -503,7 +509,7 @@ class AwsCompileFunctions { delete functionProperties.Tags; const lambdaHashingVersion = this.serverless.service.provider.lambdaHashingVersion; - if (lambdaHashingVersion >= 20201221) { + if (lambdaHashingVersion >= 20201221 || enforceHashUpdate) { functionProperties.layerConfigurations = layerConfigurations; versionHash.write(JSON.stringify(deepSortObjectByKey(functionProperties))); } else { diff --git a/test/unit/lib/plugins/aws/package/compile/functions.test.js b/test/unit/lib/plugins/aws/package/compile/functions.test.js index bdaaf195a0e..811c3302e74 100644 --- a/test/unit/lib/plugins/aws/package/compile/functions.test.js +++ b/test/unit/lib/plugins/aws/package/compile/functions.test.js @@ -2553,6 +2553,42 @@ describe('lib/plugins/aws/package/compile/functions/index.test.js', () => { describe('lambdaHashingVersion: 20201221', () => { testLambdaHashingVersion('20201221'); }); + + describe('lambdaHashingVersion migration', () => { + it('should enforce new description configuration and version with `--enforce-hash-update` flag', async () => { + const { servicePath: serviceDir } = await fixtures.setup('function', { + configExt: { + disabledDeprecations: ['LAMBDA_HASHING_VERSION_V2'], + provider: { + lambdaHashingVersion: null, + }, + }, + }); + + const { cfTemplate: originalTemplate, awsNaming } = await runServerless({ + cwd: serviceDir, + command: 'package', + }); + const originalVersionArn = + originalTemplate.Outputs.BasicLambdaFunctionQualifiedArn.Value.Ref; + + const { cfTemplate: updatedTemplate, stdoutData } = await runServerless({ + cwd: serviceDir, + command: 'deploy', + lastLifecycleHookName: 'before:deploy:deploy', + options: { + 'enforce-hash-update': true, + }, + }); + const updatedVersionArn = updatedTemplate.Outputs.BasicLambdaFunctionQualifiedArn.Value.Ref; + + expect(originalVersionArn).not.to.equal(updatedVersionArn); + expect( + updatedTemplate.Resources[awsNaming.getLambdaLogicalId('basic')].Properties.Description + ).to.equal('temporary-description-to-enforce-hash-update'); + expect(stdoutData).to.include('Your service has been deployed with new hashing algorithm'); + }); + }); }); describe.skip('TODO: Download package artifact from S3 bucket', () => {