From e43c889647f45bc93cf3cb1fd45d4a18ad95da58 Mon Sep 17 00:00:00 2001 From: Patrick Withams <59623218+pwithams@users.noreply.github.com> Date: Thu, 24 Sep 2020 04:19:43 -0600 Subject: [PATCH] fix(AWS Lambda): Ensure version hash is affected by layer changes (PR #8066) --- .../aws/package/compile/functions/index.js | 216 +++++++++------ .../package/compile/functions/index.test.js | 260 ++++++++++++++---- .../aws/package/compile/layers/index.js | 9 +- lib/plugins/aws/provider/awsProvider.js | 20 +- .../testLayerSourceChange/index.js | 6 + test/fixtures/functionLayers/index.js | 7 + test/fixtures/functionLayers/serverless.yml | 43 +++ .../functionLayers/testLayer/index.js | 5 + 8 files changed, 422 insertions(+), 144 deletions(-) create mode 100644 test/fixtures/functionLayers/extra_layers/testLayerSourceChange/index.js create mode 100644 test/fixtures/functionLayers/index.js create mode 100644 test/fixtures/functionLayers/serverless.yml create mode 100644 test/fixtures/functionLayers/testLayer/index.js diff --git a/lib/plugins/aws/package/compile/functions/index.js b/lib/plugins/aws/package/compile/functions/index.js index 6e1968eb422..9e676864809 100644 --- a/lib/plugins/aws/package/compile/functions/index.js +++ b/lib/plugins/aws/package/compile/functions/index.js @@ -368,6 +368,7 @@ class AwsCompileFunctions { : this.serverless.service.provider.versionFunctions; let versionCompilationPromise; + if (shouldVersionFunction || functionObject.provisionedConcurrency) { // Create hashes for the artifact and the logical id of the version resource // The one for the version resource must include the function configuration @@ -377,105 +378,120 @@ class AwsCompileFunctions { const versionHash = crypto.createHash('sha256'); fileHash.setEncoding('base64'); versionHash.setEncoding('base64'); + const layerConfigurations = _.cloneDeep( + extractLayerConfigurationsFromFunction(functionResource.Properties, cfTemplate) + ); - // Read the file in chunks and add them to the hash (saves memory and performance) - versionCompilationPromise = BbPromise.fromCallback(cb => { - const readStream = fs.createReadStream(artifactFilePath); - - readStream - .on('data', chunk => { - fileHash.write(chunk); - versionHash.write(chunk); - }) - .on('close', () => { - cb(); - }) - .on('error', error => { - cb(error); + // Include function and layer configuration in version id hash (without the Code part) + versionCompilationPromise = addFileContentsToHashes(artifactFilePath, [ + fileHash, + versionHash, + ]) + .then(async () => { + // Include all referenced layer code in the version id hash + const layerArtifactPaths = []; + layerConfigurations.forEach(layer => { + const layerArtifactPath = this.provider.resolveLayerArtifactName(layer.name); + layerArtifactPaths.push(layerArtifactPath); }); - }).then(() => { - // Include function configuration in version id hash (without the Code part) - const properties = _.omit( - _.get(functionResource, 'Properties', {}), - 'Code', - // Properties applied to function globally (not specific to version or alias) - 'ReservedConcurrentExecutions', - 'Tags' - ); - _.forOwn(properties, value => { - if (_.isObject(value)) { - versionHash.write(JSON.stringify(value)); - } else { - versionHash.write(value != null ? String(value) : ''); + + for (const layerArtifactPath of layerArtifactPaths.sort()) { + await addFileContentsToHashes(layerArtifactPath, [versionHash]); + } + }) + .then(() => { + // Include function and layer configuration details in the version id hash + for (const layerConfig of layerConfigurations) { + delete layerConfig.properties.Content.S3Key; } - }); - // Finalize hashes - fileHash.end(); - versionHash.end(); + // sort the layer conifigurations for hash consistency + const sortedLayerConfigurations = {}; + const byKey = ([key1], [key2]) => key1.localeCompare(key2); + for (const { name, properties: layerProperties } of layerConfigurations) { + sortedLayerConfigurations[name] = _.fromPairs( + Object.entries(layerProperties).sort(byKey) + ); + } - const fileDigest = fileHash.read(); - const versionDigest = versionHash.read(); + const functionProperties = _.cloneDeep(functionResource.Properties); + delete functionProperties.Code; + // Properties applied to function globally (not specific to version or alias) + delete functionProperties.ReservedConcurrentExecutions; + delete functionProperties.Tags; + functionProperties.layerConfigurations = sortedLayerConfigurations; - const versionResource = this.cfLambdaVersionTemplate(); + const sortedFunctionProperties = _.fromPairs( + Object.entries(functionProperties).sort(byKey) + ); + versionHash.write(JSON.stringify(sortedFunctionProperties)); - versionResource.Properties.CodeSha256 = fileDigest; - versionResource.Properties.FunctionName = { Ref: functionLogicalId }; - if (functionObject.description) { - versionResource.Properties.Description = functionObject.description; - } + // Finalize hashes + fileHash.end(); + versionHash.end(); - // use the version SHA in the logical resource ID of the version because - // AWS::Lambda::Version resource will not support updates - const versionLogicalId = this.provider.naming.getLambdaVersionLogicalId( - functionName, - versionDigest - ); - functionObject.versionLogicalId = versionLogicalId; - const newVersionObject = { - [versionLogicalId]: versionResource, - }; + const fileDigest = fileHash.read(); + const versionDigest = versionHash.read(); - Object.assign(cfTemplate.Resources, newVersionObject); + const versionResource = this.cfLambdaVersionTemplate(); - // Add function versions to Outputs section - const functionVersionOutputLogicalId = this.provider.naming.getLambdaVersionOutputLogicalId( - functionName - ); - const newVersionOutput = this.cfOutputLatestVersionTemplate(); + versionResource.Properties.CodeSha256 = fileDigest; + versionResource.Properties.FunctionName = { Ref: functionLogicalId }; + if (functionObject.description) { + versionResource.Properties.Description = functionObject.description; + } - newVersionOutput.Value = { Ref: versionLogicalId }; + // use the version SHA in the logical resource ID of the version because + // AWS::Lambda::Version resource will not support updates + const versionLogicalId = this.provider.naming.getLambdaVersionLogicalId( + functionName, + versionDigest + ); + functionObject.versionLogicalId = versionLogicalId; + const newVersionObject = { + [versionLogicalId]: versionResource, + }; - Object.assign(cfTemplate.Outputs, { - [functionVersionOutputLogicalId]: newVersionOutput, - }); + Object.assign(cfTemplate.Resources, newVersionObject); - if (!functionObject.provisionedConcurrency) return; - if (!shouldVersionFunction) delete versionResource.DeletionPolicy; + // Add function versions to Outputs section + const functionVersionOutputLogicalId = this.provider.naming.getLambdaVersionOutputLogicalId( + functionName + ); + const newVersionOutput = this.cfOutputLatestVersionTemplate(); - const provisionedConcurrency = Number(functionObject.provisionedConcurrency); - const aliasLogicalId = this.provider.naming.getLambdaProvisionedConcurrencyAliasLogicalId( - functionName - ); - const aliasName = this.provider.naming.getLambdaProvisionedConcurrencyAliasName(); - - functionObject.targetAlias = { name: aliasName, logicalId: aliasLogicalId }; - - const aliasResource = { - Type: 'AWS::Lambda::Alias', - Properties: { - FunctionName: { Ref: functionLogicalId }, - FunctionVersion: { 'Fn::GetAtt': [versionLogicalId, 'Version'] }, - Name: aliasName, - ProvisionedConcurrencyConfig: { - ProvisionedConcurrentExecutions: provisionedConcurrency, + newVersionOutput.Value = { Ref: versionLogicalId }; + + Object.assign(cfTemplate.Outputs, { + [functionVersionOutputLogicalId]: newVersionOutput, + }); + + if (!functionObject.provisionedConcurrency) return; + if (!shouldVersionFunction) delete versionResource.DeletionPolicy; + + const provisionedConcurrency = Number(functionObject.provisionedConcurrency); + const aliasLogicalId = this.provider.naming.getLambdaProvisionedConcurrencyAliasLogicalId( + functionName + ); + const aliasName = this.provider.naming.getLambdaProvisionedConcurrencyAliasName(); + + functionObject.targetAlias = { name: aliasName, logicalId: aliasLogicalId }; + + const aliasResource = { + Type: 'AWS::Lambda::Alias', + Properties: { + FunctionName: { Ref: functionLogicalId }, + FunctionVersion: { 'Fn::GetAtt': [versionLogicalId, 'Version'] }, + Name: aliasName, + ProvisionedConcurrencyConfig: { + ProvisionedConcurrentExecutions: provisionedConcurrency, + }, }, - }, - DependsOn: functionLogicalId, - }; + DependsOn: functionLogicalId, + }; - cfTemplate.Resources[aliasLogicalId] = aliasResource; - }); + cfTemplate.Resources[aliasLogicalId] = aliasResource; + }); } else { versionCompilationPromise = BbPromise.resolve(); } @@ -629,4 +645,38 @@ class AwsCompileFunctions { } } +function addFileContentsToHashes(filePath, hashes) { + return new Promise((resolve, reject) => { + const readStream = fs.createReadStream(filePath); + readStream + .on('data', chunk => { + hashes.forEach(hash => { + hash.write(chunk); + }); + }) + .on('close', () => { + resolve(); + }) + .on('error', error => { + reject(new Error(`Could not add file content to hash: ${error}`)); + }); + }); +} + +function extractLayerConfigurationsFromFunction(functionProperties, cfTemplate) { + const layerConfigurations = []; + if (!functionProperties.Layers) return layerConfigurations; + functionProperties.Layers.forEach(potentialLocalLayerObject => { + if (potentialLocalLayerObject.Ref) { + const configuration = cfTemplate.Resources[potentialLocalLayerObject.Ref]; + layerConfigurations.push({ + name: configuration._serverlessLayerName, + ref: potentialLocalLayerObject.Ref, + properties: configuration.Properties, + }); + } + }); + return layerConfigurations; +} + module.exports = AwsCompileFunctions; diff --git a/lib/plugins/aws/package/compile/functions/index.test.js b/lib/plugins/aws/package/compile/functions/index.test.js index 91e26f1c82e..a1fa9a441fd 100644 --- a/lib/plugins/aws/package/compile/functions/index.test.js +++ b/lib/plugins/aws/package/compile/functions/index.test.js @@ -1,7 +1,7 @@ 'use strict'; const AWS = require('aws-sdk'); -const fs = require('fs'); +const fse = require('fs-extra'); const _ = require('lodash'); const path = require('path'); const chai = require('chai'); @@ -87,7 +87,7 @@ describe('AwsCompileFunctions', () => { requestStub = sinon.stub(AWS, 'S3').returns({ getObject: () => ({ createReadStream() { - return fs.createReadStream(testFilePath); + return fse.createReadStream(testFilePath); }, }), }); @@ -2007,23 +2007,15 @@ describe('AwsCompileFunctions', () => { }, }; - const expectedOutputs = { - FuncLambdaFunctionQualifiedArn: { - Description: 'Current Lambda function version', - Value: { Ref: 'FuncLambdaVersionRk2uCHjn9BWyD0yH8GzU5kTmGVjCc6ZZx46sUUI1LQ' }, - }, - AnotherFuncLambdaFunctionQualifiedArn: { - Description: 'Current Lambda function version', - Value: { - Ref: 'AnotherFuncLambdaVersionha2TZXucQgvNN4zjzNnEFIaTGAQxdp7jICMvFkl0', - }, - }, - }; - return expect(awsCompileFunctions.compileFunctions()).to.be.fulfilled.then(() => { expect( awsCompileFunctions.serverless.service.provider.compiledCloudFormationTemplate.Outputs - ).to.deep.equal(expectedOutputs); + .FuncLambdaFunctionQualifiedArn + ).to.exist; + expect( + awsCompileFunctions.serverless.service.provider.compiledCloudFormationTemplate.Outputs + .AnotherFuncLambdaFunctionQualifiedArn + ).to.exist; }); }); @@ -2037,24 +2029,11 @@ describe('AwsCompileFunctions', () => { }, }; - const expectedOutputs = { - FuncLambdaFunctionQualifiedArn: { - Description: 'Current Lambda function version', - Value: { Ref: 'FuncLambdaVersionRk2uCHjn9BWyD0yH8GzU5kTmGVjCc6ZZx46sUUI1LQ' }, - }, - AnotherFuncLambdaFunctionQualifiedArn: { - Description: 'Current Lambda function version', - Value: { - Ref: 'AnotherFuncLambdaVersionha2TZXucQgvNN4zjzNnEFIaTGAQxdp7jICMvFkl0', - }, - }, - }; - + let firstOutputs; return expect(awsCompileFunctions.compileFunctions()) .to.be.fulfilled.then(() => { - expect( - awsCompileFunctions.serverless.service.provider.compiledCloudFormationTemplate.Outputs - ).to.deep.equal(expectedOutputs); + firstOutputs = + awsCompileFunctions.serverless.service.provider.compiledCloudFormationTemplate.Outputs; // Change configuration awsCompileFunctions.serverless.service.provider.compiledCloudFormationTemplate = { @@ -2071,31 +2050,32 @@ describe('AwsCompileFunctions', () => { return expect(awsCompileFunctions.compileFunctions()).to.be.fulfilled; }) .then(() => { - // Expect different version hash - _.set(expectedOutputs, 'FuncLambdaFunctionQualifiedArn', { - Description: 'Current Lambda function version', - Value: { Ref: 'FuncLambdaVersionYqfesG4U7X9KW8rtI9WywGLkurgescFGCy7rnMy8o' }, - }); - expect( awsCompileFunctions.serverless.service.provider.compiledCloudFormationTemplate.Outputs - ).to.deep.equal(expectedOutputs); + ).to.not.deep.equal(firstOutputs); }); }); it('should include description under version too if function is specified', () => { + const lambdaDescription = 'Lambda function description'; awsCompileFunctions.serverless.service.functions = { func: { handler: 'func.function.handler', - description: 'Lambda function description', + description: lambdaDescription, }, }; return expect(awsCompileFunctions.compileFunctions()).to.be.fulfilled.then(() => { - expect( + let versionDescription; + for (const [key, value] of _.entries( awsCompileFunctions.serverless.service.provider.compiledCloudFormationTemplate.Resources - .FuncLambdaVersionUF9qY6un3qUVhv62q1QPfZm4qaYxyEJXKjFTLIZ5Ig.Properties.Description - ).to.equal('Lambda function description'); + )) { + if (key.startsWith('FuncLambdaVersion')) { + versionDescription = value.Properties.Description; + break; + } + } + return expect(versionDescription).to.equal(lambdaDescription); }); }); @@ -2225,17 +2205,15 @@ describe('AwsCompileFunctions', () => { }, }; - const expectedOutputs = { - FuncLambdaFunctionQualifiedArn: { - Description: 'Current Lambda function version', - Value: { Ref: 'FuncLambdaVersionRk2uCHjn9BWyD0yH8GzU5kTmGVjCc6ZZx46sUUI1LQ' }, - }, - }; - return expect(awsCompileFunctions.compileFunctions()).to.be.fulfilled.then(() => { expect( awsCompileFunctions.serverless.service.provider.compiledCloudFormationTemplate.Outputs - ).to.deep.equal(expectedOutputs); + .FuncLambdaFunctionQualifiedArn + ).to.exist; + expect( + awsCompileFunctions.serverless.service.provider.compiledCloudFormationTemplate.Outputs + .AnotherFuncLambdaFunctionQualifiedArn + ).to.not.exist; }); }); }); @@ -2652,6 +2630,186 @@ describe('AwsCompileFunctions #2', () => { }); }); + describe('when function versions are used with layers', () => { + let firstCfTemplate; + let servicePath; + let updateConfig; + const mockDescribeStackResponse = { + CloudFormation: { + describeStacks: { Stacks: [{ Outputs: [{ OutputKey: 'test' }] }] }, + }, + }; + + beforeEach(async () => { + const serviceData = await fixtures.setup('functionLayers'); + ({ servicePath, updateConfig } = serviceData); + const data = await runServerless({ + cwd: servicePath, + cliArgs: ['package'], + awsRequestStubMap: mockDescribeStackResponse, + }); + firstCfTemplate = data.cfTemplate; + }); + + it('should create different version ids for identical lambdas with and without layers', () => { + expect(firstCfTemplate.Outputs.LayerFuncLambdaFunctionQualifiedArn.Value.Ref).to.not.equal( + firstCfTemplate.Outputs.NoLayerFuncLambdaFunctionQualifiedArn.Value.Ref + ); + }); + + it('should generate different lambda version id when lambda layer properties are different', async () => { + const firstVersionId = firstCfTemplate.Outputs.LayerFuncLambdaFunctionQualifiedArn.Value.Ref; + + await updateConfig({ + layers: { testLayer: { path: 'testLayer', description: 'Different description' } }, + }); + + const data = await runServerless({ + cwd: servicePath, + cliArgs: ['package'], + awsRequestStubMap: mockDescribeStackResponse, + }); + + expect(firstVersionId).to.not.equal( + data.cfTemplate.Outputs.LayerFuncLambdaFunctionQualifiedArn.Value.Ref + ); + }); + + it('should ignore changing character of S3Key paths when generating layer version id', async () => { + // the S3Key path is timestamped and so changes on every deployment regardless of layer changes, and should + // therefore not be included in the version id digest + const firstVersionId = firstCfTemplate.Outputs.LayerFuncLambdaFunctionQualifiedArn.Value.Ref; + const firstS3Key = firstCfTemplate.Resources.TestLayerLambdaLayer.Properties.Content.S3Key; + + const data = await runServerless({ + cwd: servicePath, + cliArgs: ['package'], + awsRequestStubMap: mockDescribeStackResponse, + }); + + expect(firstS3Key).to.not.equal( + data.cfTemplate.Resources.TestLayerLambdaLayer.Properties.Content.S3Key + ); + expect(firstVersionId).to.equal( + data.cfTemplate.Outputs.LayerFuncLambdaFunctionQualifiedArn.Value.Ref + ); + }); + + it('should ignore properties order when generating layer version id', async () => { + const firstVersionId = firstCfTemplate.Outputs.LayerFuncLambdaFunctionQualifiedArn.Value.Ref; + + await updateConfig({ + functions: { + layerFunc: { layers: [{ Ref: 'TestLayerLambdaLayer' }], handler: 'index.handler' }, + }, + }); + + const data = await runServerless({ + cwd: servicePath, + cliArgs: ['package'], + awsRequestStubMap: mockDescribeStackResponse, + }); + + expect(firstVersionId).to.equal( + data.cfTemplate.Outputs.LayerFuncLambdaFunctionQualifiedArn.Value.Ref + ); + }); + + it('should create different lambda version id for different property keys (but no different values)', async () => { + const firstVersionId = + firstCfTemplate.Outputs.LayerFuncWithConfigLambdaFunctionQualifiedArn.Value.Ref; + + await updateConfig({ + functions: { + layerFuncWithConfig: { handler: 'index.handler', timeout: 128 }, + }, + }); + + const data = await runServerless({ + cwd: servicePath, + cliArgs: ['package'], + awsRequestStubMap: mockDescribeStackResponse, + }); + + expect(firstVersionId).to.not.equal( + data.cfTemplate.Outputs.LayerFuncWithConfigLambdaFunctionQualifiedArn.Value.Ref + ); + }); + + it('should create same version id when layer source and config are unchanged', async () => { + const firstVersionId = firstCfTemplate.Outputs.LayerFuncLambdaFunctionQualifiedArn.Value.Ref; + + const data = await runServerless({ + cwd: servicePath, + cliArgs: ['package'], + awsRequestStubMap: mockDescribeStackResponse, + }); + + expect(firstVersionId).to.equal( + data.cfTemplate.Outputs.LayerFuncLambdaFunctionQualifiedArn.Value.Ref + ); + }); + + it('should generate different lambda version id when lambda layer arns are different', async () => { + const firstVersionId = + firstCfTemplate.Outputs.ArnLayerFuncLambdaFunctionQualifiedArn.Value.Ref; + + await updateConfig({ + functions: { + arnLayerFunc: { + handler: 'index.handler', + layers: ['arn:aws:lambda:us-east-2:123456789012:layer:my-layer:2'], + }, + }, + }); + + const data = await runServerless({ + cwd: servicePath, + cliArgs: ['package'], + awsRequestStubMap: mockDescribeStackResponse, + }); + + expect(firstVersionId).to.not.equal( + data.cfTemplate.Outputs.ArnLayerFuncLambdaFunctionQualifiedArn.Value.Ref + ); + }); + + describe('when layer content is changed', () => { + let originalLayer; + let sourceChangeLayer; + let backupLayer; + + beforeEach(async () => { + originalLayer = path.join(servicePath, 'testLayer'); + sourceChangeLayer = path.join(servicePath, 'extra_layers', 'testLayerSourceChange'); + backupLayer = path.join(servicePath, 'extra_layers', 'testLayerBackup'); + + await fse.rename(originalLayer, backupLayer); + await fse.rename(sourceChangeLayer, originalLayer); + }); + + afterEach(async () => { + await fse.rename(originalLayer, sourceChangeLayer); + await fse.rename(backupLayer, originalLayer); + }); + + it('should create different lambda version id', async () => { + const firstVersionId = + firstCfTemplate.Outputs.LayerFuncLambdaFunctionQualifiedArn.Value.Ref; + + const data = await runServerless({ + cwd: servicePath, + cliArgs: ['package'], + awsRequestStubMap: mockDescribeStackResponse, + }); + + expect(firstVersionId).to.not.equal( + data.cfTemplate.Outputs.LayerFuncLambdaFunctionQualifiedArn.Value.Ref + ); + }); + }); + }); + describe('function config', () => { it('should not create a new version object if only function-wide configuration changed', async () => { const { servicePath, updateConfig } = await fixtures.setup('function'); diff --git a/lib/plugins/aws/package/compile/layers/index.js b/lib/plugins/aws/package/compile/layers/index.js index 85b9dfc5f13..80e3043d906 100644 --- a/lib/plugins/aws/package/compile/layers/index.js +++ b/lib/plugins/aws/package/compile/layers/index.js @@ -26,12 +26,9 @@ class AwsCompileLayers { const newLayer = this.cfLambdaLayerTemplate(); const layerObject = this.serverless.service.getLayer(layerName); layerObject.package = layerObject.package || {}; + Object.defineProperty(newLayer, '_serverlessLayerName', { value: layerName }); - const artifactFileName = this.provider.naming.getLayerArtifactName(layerName); - const artifactFilePath = - layerObject.package && layerObject.package.artifact - ? layerObject.package.artifact - : path.join(this.serverless.config.servicePath, '.serverless', artifactFileName); + const artifactFilePath = this.provider.resolveLayerArtifactName(layerName); if (this.serverless.service.package.deploymentBucket) { newLayer.Properties.Content.S3Bucket = this.serverless.service.package.deploymentBucket; @@ -92,7 +89,7 @@ class AwsCompileLayers { }); } - _.merge( + Object.assign( this.serverless.service.provider.compiledCloudFormationTemplate.Resources, newLayerObject ); diff --git a/lib/plugins/aws/provider/awsProvider.js b/lib/plugins/aws/provider/awsProvider.js index 7371766d409..02d11b10f81 100644 --- a/lib/plugins/aws/provider/awsProvider.js +++ b/lib/plugins/aws/provider/awsProvider.js @@ -13,6 +13,7 @@ const objectHash = require('object-hash'); const PromiseQueue = require('promise-queue'); const getS3EndpointForRegion = require('../utils/getS3EndpointForRegion'); const readline = require('readline'); +const path = require('path'); const isLambdaArn = RegExp.prototype.test.bind(/^arn:[^:]+:lambda:/); @@ -880,10 +881,10 @@ class AwsProvider { credentials.useAccelerateEndpoint = true; // eslint-disable-line no-param-reassign } - getValues(source, paths) { - return paths.map(path => ({ - path, - value: _.get(source, path.join('.')), + getValues(source, objectPaths) { + return objectPaths.map(objectPath => ({ + path: objectPath, + value: _.get(source, objectPath.join('.')), })); } firstValue(values) { @@ -961,6 +962,17 @@ class AwsProvider { throw new Error(`Unrecognized function address ${functionAddress}`); } + resolveLayerArtifactName(layerName) { + const serverlessLayerObject = this.serverless.service.getLayer(layerName); + return serverlessLayerObject.package && serverlessLayerObject.package.artifact + ? serverlessLayerObject.package.artifact + : path.join( + this.serverless.config.servicePath, + '.serverless', + this.provider.naming.getLayerArtifactName(layerName) + ); + } + resolveFunctionIamRoleResourceName(functionObj) { const customRole = functionObj.role || this.serverless.service.provider.role; if (customRole) { diff --git a/test/fixtures/functionLayers/extra_layers/testLayerSourceChange/index.js b/test/fixtures/functionLayers/extra_layers/testLayerSourceChange/index.js new file mode 100644 index 00000000000..e7d713283f2 --- /dev/null +++ b/test/fixtures/functionLayers/extra_layers/testLayerSourceChange/index.js @@ -0,0 +1,6 @@ +'use strict'; + +module.exports.layer = (foo, bar) => { + const newVariable = 12; + return foo + bar + newVariable; +}; diff --git a/test/fixtures/functionLayers/index.js b/test/fixtures/functionLayers/index.js new file mode 100644 index 00000000000..11bceba2d4e --- /dev/null +++ b/test/fixtures/functionLayers/index.js @@ -0,0 +1,7 @@ +'use strict'; + +module.exports.handler = (event, context, callback) => + callback(null, { + statusCode: 200, + body: JSON.stringify({}), + }); diff --git a/test/fixtures/functionLayers/serverless.yml b/test/fixtures/functionLayers/serverless.yml new file mode 100644 index 00000000000..0ccc4edd3e8 --- /dev/null +++ b/test/fixtures/functionLayers/serverless.yml @@ -0,0 +1,43 @@ +service: service +provider: + name: aws + +layers: + testLayer: + name: default_test_layer + path: testLayer + description: 'test layer' + + TestLayerWithCapitals: + name: capitalized_test_layer + path: testLayer + + testLayerWithNoName: + path: testLayer + +package: + exclude: + - extra_layers/** + +functions: + layerFunc: + handler: index.handler + layers: + - { Ref: TestLayerLambdaLayer } + noLayerFunc: + handler: index.handler + layerFuncWithConfig: + handler: index.handler + memorySize: 128 + capitalLayerFunc: + handler: index.handler + layers: + - { Ref: TestLayerWithCapitalsLambdaLayer } + noNameLayerFunc: + handler: index.handler + layers: + - { Ref: TestLayerWithNoNameLambdaLayer } + arnLayerFunc: + handler: index.handler + layers: + - arn:aws:lambda:us-east-2:123456789012:layer:my-layer:1 diff --git a/test/fixtures/functionLayers/testLayer/index.js b/test/fixtures/functionLayers/testLayer/index.js new file mode 100644 index 00000000000..ad5d4c6949b --- /dev/null +++ b/test/fixtures/functionLayers/testLayer/index.js @@ -0,0 +1,5 @@ +'use strict'; + +module.exports.layer = (foo, bar) => { + return foo + bar; +};