Repository: serverless/serverless (JavaScript)
Date: 2018-10-03 16:41:57+00:00
Task type: Bug Fix

Issue:
Bad dynamic ARN property on sqs event
# This is a Bug Report

## Description
I'm trying to set an sqs event using a dynamic arn:
```yaml
service: myservice

custom:
  region: eu-central-1
  mySQSQueue:
    Fn::Join:
      - ":"
      - - arn
        - aws
        - sqs
        - Ref: AWS::Region
        - Ref: AWS::AccountId
        - ${env:MY_SQS_QUEUE}

provider:
  name: aws
  runtime: nodejs8.10
  region: ${self:custom.region}

functions:
  fun:
    handler: handler.myhandler
    events:
      - sqs:
          arn: ${self:custom.mySQSQueue}
          batchSize: 10
```

But I get this error:
```
  Bad dynamic ARN property on sqs event in function "fun" If you use a dynamic "arn" (such as with Fn::GetAtt or Fn::ImportValue) there must only be one key (either Fn::GetAtt or Fn::ImportValue) in the arn object. Please check the docs for more info.
```
even if I'm using `Fn::Join:` just once.

## Additional Data

* ***Serverless Framework Version you're using***: 1.32.0
* ***Operating System***: Fedora 28


Issue discussion:
Hey @AndreaAdvanon, sorry you're having this issue. I'll state the problem and then a few solutions.

**Problem:** If you're dynamically building an ARN with CloudFormation intrinsic functions, the [code expects you to use `Fn::GetAtt` or `Fn::ImportValue` only](https://github.com/serverless/serverless/blob/cbc5e3c4e199a08650b02def8d1f9e91a13e7844/lib/plugins/aws/package/compile/events/sqs/index.js#L51-L52). Thus, it's blocking your use of `Fn::Join`.

**Solutions:** I agree that the use of `Fn::Join` looks to be valid, so it's something we should fix.

Here are your choices:

1) Change the [validation code](https://github.com/serverless/serverless/blob/cbc5e3c4e199a08650b02def8d1f9e91a13e7844/lib/plugins/aws/package/compile/events/sqs/index.js#L51-L52) to also allow `Fn::Join`. This is a good long-term solution and something I'll do if you don't 😄. However, you will need to wait until it's merged and a new release is cut to use it yourself.

2. You could build the ARN with serverless variables rather than CloudFormation functions. You'll probably want to use the [serverless-pseudo-parameters plugin](https://github.com/svdgraaf/serverless-pseudo-parameters) so that you can use AccountId and Region in your ARN.

Example:

```
plugins:
  - serverless-pseudo-parameters

custom:
  mySQSQueue: "arn:aws:sqs:#{AWS::Region}:#{AWS::AccountId}:${env:MY_SQS_QUEUE}
```
@alexdebrie thank you! Can you roughly estimate when I can expect the inclusion of `Fn::Join` to be merged? :smile: 
I was refactoring our `yaml` files to avoid passing `arn`s as env variables but I can wait some time to have a proper solution (without 3rd parties solutions & tweaks on our side)

Solution patch:
diff --git a/docs/providers/aws/events/sqs.md b/docs/providers/aws/events/sqs.md
index 8ebb23f9c08..77c5041f98c 100644
--- a/docs/providers/aws/events/sqs.md
+++ b/docs/providers/aws/events/sqs.md
@@ -34,6 +34,16 @@ functions:
       - sqs:
           arn:
             Fn::ImportValue: MyExportedQueueArnId
+      - sqs:
+          arn:
+            Fn::Join:
+              - ":"
+              - - arn
+                - aws
+                - sqs
+                - Ref: AWS::Region
+                - Ref: AWS::AccountId
+                - MyOtherQueue
 ```
 
 ## Setting the BatchSize
diff --git a/lib/plugins/aws/package/compile/events/sqs/index.js b/lib/plugins/aws/package/compile/events/sqs/index.js
index 3c728f06c3b..3a4904e2732 100644
--- a/lib/plugins/aws/package/compile/events/sqs/index.js
+++ b/lib/plugins/aws/package/compile/events/sqs/index.js
@@ -49,7 +49,8 @@ class AwsCompileSQSEvents {
                 // for dynamic arns (GetAtt/ImportValue)
                 if (Object.keys(event.sqs.arn).length !== 1
                     || !(_.has(event.sqs.arn, 'Fn::ImportValue')
-                          || _.has(event.sqs.arn, 'Fn::GetAtt'))) {
+                          || _.has(event.sqs.arn, 'Fn::GetAtt')
+                            || _.has(event.sqs.arn, 'Fn::Join'))) {
                   const errorMessage = [
                     `Bad dynamic ARN property on sqs event in function "${functionName}"`,
                     ' If you use a dynamic "arn" (such as with Fn::GetAtt or Fn::ImportValue)',
@@ -84,6 +85,9 @@ class AwsCompileSQSEvents {
                 return EventSourceArn['Fn::GetAtt'][0];
               } else if (EventSourceArn['Fn::ImportValue']) {
                 return EventSourceArn['Fn::ImportValue'];
+              } else if (EventSourceArn['Fn::Join']) {
+                // [0] is the used delimiter, [1] is the array with values
+                return EventSourceArn['Fn::Join'][1].slice(-1).pop();
               }
               return EventSourceArn.split(':').pop();
             }());


Test patch:
diff --git a/lib/plugins/aws/package/compile/events/sqs/index.test.js b/lib/plugins/aws/package/compile/events/sqs/index.test.js
index a251c9d8723..2e21dfa15e0 100644
--- a/lib/plugins/aws/package/compile/events/sqs/index.test.js
+++ b/lib/plugins/aws/package/compile/events/sqs/index.test.js
@@ -391,18 +391,29 @@ describe('AwsCompileSQSEvents', () => {
                   arn: { 'Fn::ImportValue': 'ForeignQueue' },
                 },
               },
+              {
+                sqs: {
+                  arn: {
+                    'Fn::Join': [
+                      ':', [
+                        'arn', 'aws', 'sqs', {
+                          Ref: 'AWS::Region',
+                        },
+                        {
+                          Ref: 'AWS::AccountId',
+                        },
+                        'MyQueue',
+                      ],
+                    ],
+                  },
+                },
+              },
             ],
           },
         };
 
         awsCompileSQSEvents.compileSQSEvents();
 
-        expect(awsCompileSQSEvents.serverless.service
-          .provider.compiledCloudFormationTemplate.Resources
-          .FirstEventSourceMappingSQSSomeQueue.Properties.EventSourceArn
-        ).to.deep.equal(
-          { 'Fn::GetAtt': ['SomeQueue', 'Arn'] }
-        );
         expect(awsCompileSQSEvents.serverless.service
           .provider.compiledCloudFormationTemplate.Resources.IamRoleLambdaExecution
           .Properties.Policies[0].PolicyDocument.Statement[0]
@@ -424,18 +435,62 @@ describe('AwsCompileSQSEvents', () => {
               {
                 'Fn::ImportValue': 'ForeignQueue',
               },
+              {
+                'Fn::Join': [
+                  ':',
+                  [
+                    'arn',
+                    'aws',
+                    'sqs',
+                    {
+                      Ref: 'AWS::Region',
+                    },
+                    {
+                      Ref: 'AWS::AccountId',
+                    },
+                    'MyQueue',
+                  ],
+                ],
+              },
             ],
           }
         );
+        expect(awsCompileSQSEvents.serverless.service
+          .provider.compiledCloudFormationTemplate.Resources
+          .FirstEventSourceMappingSQSSomeQueue.Properties.EventSourceArn
+        ).to.deep.equal(
+          { 'Fn::GetAtt': ['SomeQueue', 'Arn'] }
+        );
         expect(awsCompileSQSEvents.serverless.service
           .provider.compiledCloudFormationTemplate.Resources
           .FirstEventSourceMappingSQSForeignQueue.Properties.EventSourceArn
         ).to.deep.equal(
           { 'Fn::ImportValue': 'ForeignQueue' }
         );
+        expect(awsCompileSQSEvents.serverless.service
+          .provider.compiledCloudFormationTemplate.Resources
+          .FirstEventSourceMappingSQSMyQueue.Properties.EventSourceArn
+        ).to.deep.equal(
+          {
+            'Fn::Join': [
+              ':',
+              [
+                'arn',
+                'aws',
+                'sqs',
+                {
+                  Ref: 'AWS::Region',
+                },
+                {
+                  Ref: 'AWS::AccountId',
+                },
+                'MyQueue',
+              ],
+            ],
+          });
       });
 
-      it('fails if keys other than Fn::GetAtt/ImportValue are used for dynamic queue ARN', () => {
+      it('fails if keys other than Fn::GetAtt/ImportValue/Join are used for dynamic ARNs', () => {
         awsCompileSQSEvents.serverless.service.functions = {
           first: {
             events: [