Skip to content

Commit

Permalink
feat: Introduce enforce-hash-update flag to help hashing migration
Browse files Browse the repository at this point in the history
  • Loading branch information
pgrzesik committed Nov 9, 2021
1 parent e8c8d25 commit afd0a5b
Show file tree
Hide file tree
Showing 6 changed files with 86 additions and 4 deletions.
2 changes: 1 addition & 1 deletion docs/deprecations.md
Expand Up @@ -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).

<a name="LOAD_VARIABLES_FROM_ENV_FILES"><div>&nbsp;</div></a>

Expand Down
25 changes: 25 additions & 0 deletions docs/guides/upgrading-v3.md
Expand Up @@ -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 an improved algorithm (fixes 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 the old algorithm by using the following configuration:

```
provider:
lambdaHashingVersion: 20200924
```

However, we highly encourage an upgrade to the new algorithm. To upgrade, you must redeploy your service with code or configuration change in all functions. You can do it by 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: that flag will override the description for Lambda functions, which will force the creation of new versions.
2. Set `provider.lambdaHashingVersion` to `20201221` in your configuration: your service will now always deploy with the new Lambda version hashes (which is the new defualt in v3).
3. Run `sls deploy`, this time without additional `--enforce-hash-update` flag: that will restore the original descriptions on all Lambda functions.

Now your whole service is fully migrated to the 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:
Expand Down
5 changes: 5 additions & 0 deletions lib/cli/commands-schema/aws-service.js
Expand Up @@ -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: [
Expand Down
12 changes: 11 additions & 1 deletion lib/plugins/aws/deploy/index.js
Expand Up @@ -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');
Expand Down Expand Up @@ -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.'
);
}
},
};
}
Expand Down
10 changes: 8 additions & 2 deletions lib/plugins/aws/package/compile/functions.js
Expand Up @@ -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 ||
Expand Down Expand Up @@ -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 {
Expand All @@ -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(
Expand Down Expand Up @@ -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(
Expand Down Expand Up @@ -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 {
Expand Down
36 changes: 36 additions & 0 deletions test/unit/lib/plugins/aws/package/compile/functions.test.js
Expand Up @@ -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', () => {
Expand Down

0 comments on commit afd0a5b

Please sign in to comment.