Repository: serverless/serverless (JavaScript)
Date: 2019-10-24 16:34:30+00:00
Task type: Bug Fix

Issue:
SNS cross region trigger breaks pseudo parameters
# Bug Report

For reference, see the bug report here: https://github.com/svdgraaf/serverless-pseudo-parameters/issues/38

Also for reference, currently the plugin uses `after:aws:package:finalize:mergeCustomProviderResources` to trigger, perhaps this needs to run at a different stage in the process, but I'm not sure about that.

I'm happy to create a PR with a fix, but I'd like to discuss here what we want it to do.

## Description
**1. What did you do?**
Since 1.48.0 and up, when creating an sns event, the value no longer works with a pseudo parameter.

**2. What happened?**
Since this PR: https://github.com/serverless/serverless/pull/6366 was merged, the sns event tries to be smart, and detect if a cross-region sns topic is referenced. If so, it creates a cross-region event trigger.
However, when this value is set with a `!Ref` or `!Sub` parameter, this breaks the logic, as noted by @bzemms in the actual PR: https://github.com/serverless/serverless/pull/6366#pullrequestreview-275160827
If the logic doesn't see the same region in the value, as the script is running from, it tries to fetch the region from the value. Which of course doesn't work when using a reference value, as there is no actual region in there at all.

**3. What should've happened?**
It shouldn't break on a valid arn  😅 

**4. What's the content of your `serverless.yml` file?**

Here's an example setup which breaks:
```
plugins:
  - serverless-pseudo-parameters
functions:
  alert:
    handler: src/foo.bar
    events:
      - sns:
          topicName: foobar
          arn: arn:aws:sns:#{AWS::Region}:#{AWS::AccountId}:foobar
```

**Similar or dependent issues:**

- #6444 
- #6445 


## Proposed solution
I think a valid solution would be to perhaps check if the found region adheres to a list of valid regions from the `aws-sdk`, or we could do a quick regex match. If it doesn't match, the region should not be set and be left alone. Perhaps issue a warning or info log when this occurs.

Not sure what is preferred. Let's discuss and I can provide a PR


Issue discussion:

Solution patch:
diff --git a/lib/plugins/aws/package/compile/events/sns/index.js b/lib/plugins/aws/package/compile/events/sns/index.js
index 358733b1c08..1ede2b364a2 100644
--- a/lib/plugins/aws/package/compile/events/sns/index.js
+++ b/lib/plugins/aws/package/compile/events/sns/index.js
@@ -62,7 +62,14 @@ class AwsCompileSNSEvents {
                   }
                 } else if (typeof topicArn === 'string') {
                   if (topicArn.indexOf('arn:') === 0) {
-                    const splitArn = topicArn.split(':');
+                    // NOTE: we need to process the topic ARN that way due to lacking
+                    // support of regex lookbehind in Node.js 6.
+                    // Once Node.js 6 support is dropped we can change this to:
+                    // `const splitArn = topicArn.split(/(?<!:):(?!:)/);`
+                    const splitArn = topicArn
+                      .replace(/::/g, '@@')
+                      .split(':')
+                      .map(s => s.replace(/@@/g, '::'));
                     topicName = splitArn[splitArn.length - 1];
                     if (splitArn[3] !== this.options.region) {
                       region = splitArn[3];


Test patch:
diff --git a/lib/plugins/aws/package/compile/events/sns/index.test.js b/lib/plugins/aws/package/compile/events/sns/index.test.js
index 46fbc64d09d..1eeab35c410 100644
--- a/lib/plugins/aws/package/compile/events/sns/index.test.js
+++ b/lib/plugins/aws/package/compile/events/sns/index.test.js
@@ -309,6 +309,39 @@ describe('AwsCompileSNSEvents', () => {
       ).to.equal('AWS::Lambda::Permission');
     });
 
+    it('should create a cross region subscription when SNS topic arn in a different region is using pseudo params', () => {
+      awsCompileSNSEvents.serverless.service.functions = {
+        first: {
+          events: [
+            {
+              sns: {
+                arn: 'arn:aws:sns:${AWS::Region}:${AWS::AccountId}:foo',
+              },
+            },
+          ],
+        },
+      };
+
+      awsCompileSNSEvents.compileSNSEvents();
+      expect(
+        Object.keys(
+          awsCompileSNSEvents.serverless.service.provider.compiledCloudFormationTemplate.Resources
+        )
+      ).to.have.length(2);
+      expect(
+        awsCompileSNSEvents.serverless.service.provider.compiledCloudFormationTemplate.Resources
+          .FirstSnsSubscriptionFoo.Type
+      ).to.equal('AWS::SNS::Subscription');
+      expect(
+        awsCompileSNSEvents.serverless.service.provider.compiledCloudFormationTemplate.Resources
+          .FirstSnsSubscriptionFoo.Properties.Region
+      ).to.equal('${AWS::Region}');
+      expect(
+        awsCompileSNSEvents.serverless.service.provider.compiledCloudFormationTemplate.Resources
+          .FirstLambdaPermissionFooSNS.Type
+      ).to.equal('AWS::Lambda::Permission');
+    });
+
     it('should throw an error when the arn an object and the value is not a string', () => {
       awsCompileSNSEvents.serverless.service.functions = {
         first: {