diff --git a/docs/providers/aws/guide/functions.md b/docs/providers/aws/guide/functions.md index 51940b9f3..9a3ae2266 100644 --- a/docs/providers/aws/guide/functions.md +++ b/docs/providers/aws/guide/functions.md @@ -392,7 +392,7 @@ provider: functions: hello: handler: handler.hello - onError: arn:aws:sns:us-east-1:XXXXXX:test # Ref and Fn::ImportValue are supported as well + onError: arn:aws:sns:us-east-1:XXXXXX:test # Ref, Fn::GetAtt and Fn::ImportValue are supported as well ``` ### DLQ with SQS diff --git a/docs/providers/aws/guide/serverless.yml.md b/docs/providers/aws/guide/serverless.yml.md index 5a57daac3..315b67ed4 100644 --- a/docs/providers/aws/guide/serverless.yml.md +++ b/docs/providers/aws/guide/serverless.yml.md @@ -137,7 +137,7 @@ functions: runtime: nodejs6.10 # Runtime for this specific function. Overrides the default which is set on the provider level timeout: 10 # Timeout for this specific function. Overrides the default set above. role: arn:aws:iam::XXXXXX:role/role # IAM role which will be used for this function - onError: arn:aws:sns:us-east-1:XXXXXX:sns-topic # Optional SNS topic arn (Ref and Fn::ImportValue are supported as well) which will be used for the DeadLetterConfig + onError: arn:aws:sns:us-east-1:XXXXXX:sns-topic # Optional SNS topic / SQS arn (Ref, Fn::GetAtt and Fn::ImportValue are supported as well) which will be used for the DeadLetterConfig awsKmsKeyArn: arn:aws:kms:us-east-1:XXXXXX:key/some-hash # Optional KMS key arn which will be used for encryption (overwrites the one defined on the service level) environment: # Function level environment variables functionEnvVar: 12345678 diff --git a/lib/plugins/aws/package/compile/functions/index.js b/lib/plugins/aws/package/compile/functions/index.js index 315f6c950..8a130d1c9 100644 --- a/lib/plugins/aws/package/compile/functions/index.js +++ b/lib/plugins/aws/package/compile/functions/index.js @@ -184,14 +184,14 @@ class AwsCompileFunctions { const errorMessage = 'onError config must be a SNS topic arn or SQS queue arn'; return BbPromise.reject(new this.serverless.classes.Error(errorMessage)); } - } else if (this.isArnRefOrImportValue(arn)) { + } else if (this.isArnRefGetAttOrImportValue(arn)) { newFunction.Properties.DeadLetterConfig = { TargetArn: arn, }; } else { const errorMessage = [ 'onError config must be provided as an arn string,', - ' Ref or Fn::ImportValue', + ' Ref, Fn::GetAtt or Fn::ImportValue', ].join(''); return BbPromise.reject(new this.serverless.classes.Error(errorMessage)); } @@ -420,9 +420,9 @@ class AwsCompileFunctions { } // helper functions - isArnRefOrImportValue(arn) { + isArnRefGetAttOrImportValue(arn) { return typeof arn === 'object' && - _.some(_.keys(arn), (k) => _.includes(['Ref', 'Fn::ImportValue'], k)); + _.some(_.keys(arn), (k) => _.includes(['Ref', 'Fn::GetAtt', 'Fn::ImportValue'], k)); } cfLambdaFunctionTemplate() { diff --git a/lib/plugins/aws/package/compile/functions/index.test.js b/lib/plugins/aws/package/compile/functions/index.test.js index 3da800fc7..389204db1 100644 --- a/lib/plugins/aws/package/compile/functions/index.test.js +++ b/lib/plugins/aws/package/compile/functions/index.test.js @@ -61,14 +61,17 @@ describe('AwsCompileFunctions', () => { expect(awsCompileFunctions.provider).to.be.instanceof(AwsProvider)); }); - describe('#isArnRefOrImportValue()', () => { + describe('#isArnRefGetAttOrImportValue()', () => { it('should accept a Ref', () => - expect(awsCompileFunctions.isArnRefOrImportValue({ Ref: 'DLQ' })).to.equal(true)); + expect(awsCompileFunctions.isArnRefGetAttOrImportValue({ Ref: 'DLQ' })).to.equal(true)); + it('should accept a Fn::GetAtt', () => + expect(awsCompileFunctions.isArnRefGetAttOrImportValue({ 'Fn::GetAtt': ['DLQ', 'Arn'] })) + .to.equal(true)); it('should accept a Fn::ImportValue', () => - expect(awsCompileFunctions.isArnRefOrImportValue({ 'Fn::ImportValue': 'DLQ' })) + expect(awsCompileFunctions.isArnRefGetAttOrImportValue({ 'Fn::ImportValue': 'DLQ' })) .to.equal(true)); it('should reject other objects', () => - expect(awsCompileFunctions.isArnRefOrImportValue({ Blah: 'vtha' })).to.equal(false)); + expect(awsCompileFunctions.isArnRefGetAttOrImportValue({ Blah: 'vtha' })).to.equal(false)); }); describe('#compileFunctions()', () => { @@ -896,6 +899,52 @@ describe('AwsCompileFunctions', () => { expect(functionResource).to.deep.equal(compiledFunction); }); }); + + it('should create necessary resources if a Fn::GetAtt is provided', () => { + awsCompileFunctions.serverless.service.functions = { + func: { + handler: 'func.function.handler', + name: 'new-service-dev-func', + onError: { + 'Fn::GetAtt': ['DLQ', 'Arn'], + }, + }, + }; + + const compiledFunction = { + Type: 'AWS::Lambda::Function', + DependsOn: [ + 'FuncLogGroup', + 'IamRoleLambdaExecution', + ], + Properties: { + Code: { + S3Key: `${s3Folder}/${s3FileName}`, + S3Bucket: { Ref: 'ServerlessDeploymentBucket' }, + }, + FunctionName: 'new-service-dev-func', + Handler: 'func.function.handler', + MemorySize: 1024, + Role: { 'Fn::GetAtt': ['IamRoleLambdaExecution', 'Arn'] }, + Runtime: 'nodejs4.3', + Timeout: 6, + DeadLetterConfig: { + TargetArn: { + 'Fn::GetAtt': ['DLQ', 'Arn'], + }, + }, + }, + }; + + return expect(awsCompileFunctions.compileFunctions()).to.be.fulfilled + .then(() => { + const compiledCfTemplate = awsCompileFunctions.serverless.service.provider + .compiledCloudFormationTemplate; + + const functionResource = compiledCfTemplate.Resources.FuncLambdaFunction; + expect(functionResource).to.deep.equal(compiledFunction); + }); + }); }); describe('when IamRoleLambdaExecution is not used', () => {