Repository: HyperBrain/serverless-aws-alias (js)

Issue:
Cannot read property 'Fn::Join' of undefined when specifying a User Pools authorizer
Attempting to deploy  i get error ` Cannot read property 'Fn::Join' of undefined`, this is the logs terminals.

> Serverless: Packaging service...
> Serverless: Preparing alias ...
>  
>   Type Error ---------------------------------------------
>  
>      Cannot read property 'Fn::Join' of undefined
>  
>      For debugging logs, run again after setting the "SLS_DEBUG=*" environment variable.
>  
>   Get Support --------------------------------------------
>      Docs:          docs.serverless.com
>      Bugs:          github.com/serverless/serverless/issues
>      Forums:        forum.serverless.com
>      Chat:          gitter.im/serverless/serverless
>  
>   Your Environment Information -----------------------------
>      OS:                 darwin
>      Node Version:       6.9.1
>      Serverless Version: 1.14.0

can you help me, thanks?

Pull request:
Only change authorizer uris on TOKEN type authorizers
Fixes #51 

The plugin should leave all non-Lambda authorizers as they are. Only Lambda authorizers have an URI that needs to be adapted for alias deployments.

Patch:
diff --git a/lib/stackops/apiGateway.js b/lib/stackops/apiGateway.js
index 6333162..0dd270d 100644
--- a/lib/stackops/apiGateway.js
+++ b/lib/stackops/apiGateway.js
@@ -129,11 +129,13 @@ module.exports = function(currentTemplate, aliasStackTemplates, currentAliasStac
 
 		// Audjust authorizer Uri and name (stage variables are not allowed in Uris here)
 		_.forOwn(authorizers, (authorizer, name) => {
-			const uriParts = authorizer.Properties.AuthorizerUri['Fn::Join'][1];
-			const funcIndex = _.findIndex(uriParts, part => _.has(part, 'Fn::GetAtt'));
+			if (_.get(authorizer, 'Properties.Type') === 'TOKEN') {
+				const uriParts = authorizer.Properties.AuthorizerUri['Fn::Join'][1];
+				const funcIndex = _.findIndex(uriParts, part => _.has(part, 'Fn::GetAtt'));
 
-			// Use the SERVERLESS_ALIAS stage variable to determine the called function alias
-			uriParts.splice(funcIndex + 1, 0, ':${stageVariables.SERVERLESS_ALIAS}');
+				// Use the SERVERLESS_ALIAS stage variable to determine the called function alias
+				uriParts.splice(funcIndex + 1, 0, ':${stageVariables.SERVERLESS_ALIAS}');
+			}
 
 			authorizer.Properties.Name = `${authorizer.Properties.Name}-${this._alias}`;
 


Test patch:
diff --git a/test/data/auth-stack.json b/test/data/auth-stack.json
new file mode 100644
index 0000000..994543d
--- /dev/null
+++ b/test/data/auth-stack.json
@@ -0,0 +1,400 @@
+{
+  "AWSTemplateFormatVersion": "2010-09-09",
+  "Description": "The AWS CloudFormation template for this Serverless application",
+  "Resources": {
+    "ServerlessDeploymentBucket": {
+      "Type": "AWS::S3::Bucket"
+    },
+    "Testfct1LogGroup": {
+      "Type": "AWS::Logs::LogGroup",
+      "Properties": {
+        "LogGroupName": "/aws/lambda/sls-test-project-dev-testfct1"
+      }
+    },
+    "TestauthLogGroup": {
+      "Type": "AWS::Logs::LogGroup",
+      "Properties": {
+        "LogGroupName": "/aws/lambda/sls-test-project-dev-testauth"
+      }
+    },
+    "IamRoleLambdaExecution": {
+      "Type": "AWS::IAM::Role",
+      "Properties": {
+        "AssumeRolePolicyDocument": {
+          "Version": "2012-10-17",
+          "Statement": [
+            {
+              "Effect": "Allow",
+              "Principal": {
+                "Service": [
+                  "lambda.amazonaws.com"
+                ]
+              },
+              "Action": [
+                "sts:AssumeRole"
+              ]
+            }
+          ]
+        },
+        "Policies": [
+          {
+            "PolicyName": {
+              "Fn::Join": [
+                "-",
+                [
+                  "dev",
+                  "sls-test-project",
+                  "lambda"
+                ]
+              ]
+            },
+            "PolicyDocument": {
+              "Version": "2012-10-17",
+              "Statement": [
+                {
+                  "Effect": "Allow",
+                  "Action": [
+                    "logs:CreateLogStream"
+                  ],
+                  "Resource": [
+                    {
+                      "Fn::Sub": "arn:aws:logs:${AWS::Region}:${AWS::AccountId}:log-group:/aws/lambda/sls-test-project-dev-testfct1:*"
+                    },
+                    {
+                      "Fn::Sub": "arn:aws:logs:${AWS::Region}:${AWS::AccountId}:log-group:/aws/lambda/sls-test-project-dev-testauth:*"
+                    }
+                  ]
+                },
+                {
+                  "Effect": "Allow",
+                  "Action": [
+                    "logs:PutLogEvents"
+                  ],
+                  "Resource": [
+                    {
+                      "Fn::Sub": "arn:aws:logs:${AWS::Region}:${AWS::AccountId}:log-group:/aws/lambda/sls-test-project-dev-testfct1:*:*"
+                    },
+                    {
+                      "Fn::Sub": "arn:aws:logs:${AWS::Region}:${AWS::AccountId}:log-group:/aws/lambda/sls-test-project-dev-testauth:*:*"
+                    }
+                  ]
+                }
+              ]
+            }
+          }
+        ],
+        "Path": "/",
+        "RoleName": {
+          "Fn::Join": [
+            "-",
+            [
+              "sls-test-project",
+              "dev",
+              "us-east-1",
+              "lambdaRole"
+            ]
+          ]
+        }
+      }
+    },
+    "Testfct1LambdaFunction": {
+      "Type": "AWS::Lambda::Function",
+      "Properties": {
+        "Code": {
+          "S3Bucket": {
+            "Ref": "ServerlessDeploymentBucket"
+          },
+          "S3Key": "serverless/sls-test-project/dev/1496754891214-2017-06-06T13:14:51.214Z/sls-test-project.zip"
+        },
+        "FunctionName": "sls-test-project-dev-testfct1",
+        "Handler": "handlers/testfct1/handler.handle",
+        "MemorySize": 512,
+        "Role": {
+          "Fn::GetAtt": [
+            "IamRoleLambdaExecution",
+            "Arn"
+          ]
+        },
+        "Runtime": "nodejs4.3",
+        "Timeout": 15,
+        "Description": "Echo function echoes alias",
+        "Environment": {
+          "Variables": {
+            "SERVERLESS_PROJECT_NAME": "sls-test-project",
+            "SERVERLESS_PROJECT": "sls-test-project",
+            "SERVERLESS_STAGE": "dev",
+            "SERVERLESS_REGION": "us-east-1",
+            "TEST_TABLE_NAME": {
+              "Ref": "TestDynamoDbTable"
+            }
+          }
+        }
+      },
+      "DependsOn": [
+        "Testfct1LogGroup",
+        "IamRoleLambdaExecution"
+      ]
+    },
+    "Testfct1LambdaVersionrvZ7KY7UgzQ2OKbOvZoG1zLgodltc7toF3qYeORU": {
+      "Type": "AWS::Lambda::Version",
+      "DeletionPolicy": "Retain",
+      "Properties": {
+        "FunctionName": {
+          "Ref": "Testfct1LambdaFunction"
+        },
+        "CodeSha256": "rvZ7KY7UgzQ2OKbOvZoG1zLgodltc7+to/F3q+YeORU=",
+        "Description": "Echo function echoes alias"
+      }
+    },
+    "TestauthLambdaFunction": {
+      "Type": "AWS::Lambda::Function",
+      "Properties": {
+        "Code": {
+          "S3Bucket": {
+            "Ref": "ServerlessDeploymentBucket"
+          },
+          "S3Key": "serverless/sls-test-project/dev/1496754891214-2017-06-06T13:14:51.214Z/sls-test-project.zip"
+        },
+        "FunctionName": "sls-test-project-dev-testauth",
+        "Handler": "handlers/testauth/handler.handle",
+        "MemorySize": 512,
+        "Role": {
+          "Fn::GetAtt": [
+            "IamRoleLambdaExecution",
+            "Arn"
+          ]
+        },
+        "Runtime": "nodejs4.3",
+        "Timeout": 15,
+        "Description": "A custom authorizer",
+        "Environment": {
+          "Variables": {
+            "SERVERLESS_PROJECT_NAME": "sls-test-project",
+            "SERVERLESS_PROJECT": "sls-test-project",
+            "SERVERLESS_STAGE": "dev",
+            "SERVERLESS_REGION": "us-east-1",
+            "TEST_TABLE_NAME": {
+              "Ref": "TestDynamoDbTable"
+            }
+          }
+        }
+      },
+      "DependsOn": [
+        "TestauthLogGroup",
+        "IamRoleLambdaExecution"
+      ]
+    },
+    "TestauthLambdaVersionrvZ7KY7UgzQ2OKbOvZoG1zLgodltc7toF3qYeORU": {
+      "Type": "AWS::Lambda::Version",
+      "DeletionPolicy": "Retain",
+      "Properties": {
+        "FunctionName": {
+          "Ref": "TestauthLambdaFunction"
+        },
+        "CodeSha256": "rvZ7KY7UgzQ2OKbOvZoG1zLgodltc7+to/F3q+YeORU=",
+        "Description": "A custom authorizer"
+      }
+    },
+    "ApiGatewayRestApi": {
+      "Type": "AWS::ApiGateway::RestApi",
+      "Properties": {
+        "Name": "dev-sls-test-project"
+      }
+    },
+    "ApiGatewayResourceFunc1": {
+      "Type": "AWS::ApiGateway::Resource",
+      "Properties": {
+        "ParentId": {
+          "Fn::GetAtt": [
+            "ApiGatewayRestApi",
+            "RootResourceId"
+          ]
+        },
+        "PathPart": "func1",
+        "RestApiId": {
+          "Ref": "ApiGatewayRestApi"
+        }
+      }
+    },
+    "ApiGatewayMethodFunc1Get": {
+      "Type": "AWS::ApiGateway::Method",
+      "Properties": {
+        "HttpMethod": "GET",
+        "RequestParameters": {},
+        "ResourceId": {
+          "Ref": "ApiGatewayResourceFunc1"
+        },
+        "RestApiId": {
+          "Ref": "ApiGatewayRestApi"
+        },
+        "AuthorizationType": "CUSTOM",
+        "AuthorizerId": {
+          "Ref": "TestauthApiGatewayAuthorizer"
+        },
+        "Integration": {
+          "IntegrationHttpMethod": "POST",
+          "Type": "AWS_PROXY",
+          "Uri": {
+            "Fn::Join": [
+              "",
+              [
+                "arn:aws:apigateway:",
+                {
+                  "Ref": "AWS::Region"
+                },
+                ":lambda:path/2015-03-31/functions/",
+                {
+                  "Fn::GetAtt": [
+                    "Testfct1LambdaFunction",
+                    "Arn"
+                  ]
+                },
+                "/invocations"
+              ]
+            ]
+          }
+        },
+        "MethodResponses": []
+      },
+      "DependsOn": "TestauthApiGatewayAuthorizer"
+    },
+    "TestauthApiGatewayAuthorizer": {
+      "Type": "AWS::ApiGateway::Authorizer",
+      "Properties": {
+        "AuthorizerResultTtlInSeconds": 0,
+        "IdentitySource": "method.request.header.Authorization",
+        "Name": "testauth",
+        "RestApiId": {
+          "Ref": "ApiGatewayRestApi"
+        },
+        "AuthorizerUri": {
+          "Fn::Join": [
+            "",
+            [
+              "arn:aws:apigateway:",
+              {
+                "Ref": "AWS::Region"
+              },
+              ":lambda:path/2015-03-31/functions/",
+              {
+                "Fn::GetAtt": [
+                  "TestauthLambdaFunction",
+                  "Arn"
+                ]
+              },
+              "/invocations"
+            ]
+          ]
+        },
+        "Type": "TOKEN"
+      }
+    },
+    "CognitoTestApiGatewayAuthorizer": {
+      "Type": "AWS::ApiGateway::Authorizer",
+      "Properties": {
+        "AuthorizerResultTtlInSeconds": 0,
+        "IdentitySource": "method.request.header.Authorization",
+        "Name": "cognitoauth",
+        "RestApiId": {
+          "Ref": "ApiGatewayRestApi"
+        },
+        "ProviderARNs": [
+          "arn:aws:cognito-idp:us-west-2:xxxxx:userpool/us-west-xxxx"
+        ],
+        "Type": "COGNITO_USER_POOLS"
+      }
+    },
+    "ApiGatewayDeployment1496754891256": {
+      "Type": "AWS::ApiGateway::Deployment",
+      "Properties": {
+        "RestApiId": {
+          "Ref": "ApiGatewayRestApi"
+        },
+        "StageName": "dev"
+      },
+      "DependsOn": [
+        "ApiGatewayMethodFunc1Get"
+      ]
+    },
+    "Testfct1LambdaPermissionApiGateway": {
+      "Type": "AWS::Lambda::Permission",
+      "Properties": {
+        "FunctionName": {
+          "Fn::GetAtt": [
+            "Testfct1LambdaFunction",
+            "Arn"
+          ]
+        },
+        "Action": "lambda:InvokeFunction",
+        "Principal": "apigateway.amazonaws.com",
+        "SourceArn": {
+          "Fn::Join": [
+            "",
+            [
+              "arn:aws:execute-api:",
+              {
+                "Ref": "AWS::Region"
+              },
+              ":",
+              {
+                "Ref": "AWS::AccountId"
+              },
+              ":",
+              {
+                "Ref": "ApiGatewayRestApi"
+              },
+              "/*/*"
+            ]
+          ]
+        }
+      }
+    },
+    "TestauthLambdaPermissionApiGateway": {
+      "Type": "AWS::Lambda::Permission",
+      "Properties": {
+        "FunctionName": {
+          "Fn::GetAtt": [
+            "TestauthLambdaFunction",
+            "Arn"
+          ]
+        },
+        "Action": "lambda:InvokeFunction",
+        "Principal": "apigateway.amazonaws.com"
+      }
+    }
+  },
+  "Outputs": {
+    "ServerlessDeploymentBucketName": {
+      "Value": {
+        "Ref": "ServerlessDeploymentBucket"
+      }
+    },
+    "Testfct1LambdaFunctionQualifiedArn": {
+      "Description": "Current Lambda function version",
+      "Value": {
+        "Ref": "Testfct1LambdaVersionrvZ7KY7UgzQ2OKbOvZoG1zLgodltc7toF3qYeORU"
+      }
+    },
+    "TestauthLambdaFunctionQualifiedArn": {
+      "Description": "Current Lambda function version",
+      "Value": {
+        "Ref": "TestauthLambdaVersionrvZ7KY7UgzQ2OKbOvZoG1zLgodltc7toF3qYeORU"
+      }
+    },
+    "ServiceEndpoint": {
+      "Description": "URL of the service endpoint",
+      "Value": {
+        "Fn::Join": [
+          "",
+          [
+            "https://",
+            {
+              "Ref": "ApiGatewayRestApi"
+            },
+            ".execute-api.us-east-1.amazonaws.com/dev"
+          ]
+        ]
+      }
+    }
+  }
+}
diff --git a/test/stackops/apiGateway.test.js b/test/stackops/apiGateway.test.js
new file mode 100644
index 0000000..071bdaa
--- /dev/null
+++ b/test/stackops/apiGateway.test.js
@@ -0,0 +1,160 @@
+'use strict';
+/**
+ * Unit tests for SNS events.
+ */
+
+const getInstalledPath = require('get-installed-path');
+const BbPromise = require('bluebird');
+const _ = require('lodash');
+const chai = require('chai');
+const sinon = require('sinon');
+const AWSAlias = require('../../index');
+
+const serverlessPath = getInstalledPath.sync('serverless', { local: true });
+const AwsProvider = require(`${serverlessPath}/lib/plugins/aws/provider/awsProvider`);
+const Serverless = require(`${serverlessPath}/lib/Serverless`);
+
+chai.use(require('chai-as-promised'));
+chai.use(require('sinon-chai'));
+const expect = chai.expect;
+
+describe('API Gateway', () => {
+	let serverless;
+	let options;
+	let awsAlias;
+	// Sinon and stubs for SLS CF access
+	let sandbox;
+	let logStub;
+
+	before(() => {
+		sandbox = sinon.sandbox.create();
+	});
+
+	beforeEach(() => {
+		options = {
+			alias: 'myAlias',
+			stage: 'myStage',
+			region: 'us-east-1',
+		};
+		serverless = new Serverless(options);
+		serverless.service.service = 'testService';
+		serverless.setProvider('aws', new AwsProvider(serverless, options));
+		serverless.cli = new serverless.classes.CLI(serverless);
+		serverless.service.provider.compiledCloudFormationAliasTemplate = {};
+		awsAlias = new AWSAlias(serverless, options);
+
+		// Disable logging
+		logStub = sandbox.stub(serverless.cli, 'log');
+		logStub.returns();
+
+		// Validate before each test to set the variables correctly.
+		return awsAlias.validate();
+	});
+
+	afterEach(() => {
+		sandbox.restore();
+	});
+
+	describe('#aliasHandleApiGateway()', () => {
+		it('should succeed with standard template', () => {
+			serverless.service.provider.compiledCloudFormationTemplate = require('../data/sls-stack-1.json');
+			serverless.service.provider.compiledCloudFormationAliasTemplate = require('../data/alias-stack-1.json');
+			return expect(awsAlias.aliasHandleApiGateway({}, [], {})).to.be.fulfilled;
+		});
+
+		describe('authorizer transform', () => {
+			let stackTemplate;
+			let aliasTemplate;
+
+			beforeEach(() => {
+				stackTemplate = _.cloneDeep(require('../data/auth-stack.json'));
+				aliasTemplate = _.cloneDeep(require('../data/alias-stack-1.json'));
+			});
+
+			it('should handle only Lambda authorizers', () => {
+				const template = serverless.service.provider.compiledCloudFormationTemplate = stackTemplate;
+				const cogAuth = _.cloneDeep(template.Resources.CognitoTestApiGatewayAuthorizer);
+				cogAuth.Properties.Name += "-myAlias";
+				serverless.service.provider.compiledCloudFormationAliasTemplate = aliasTemplate;
+				return expect(awsAlias.aliasHandleApiGateway({}, [], {})).to.be.fulfilled
+				.then(() => BbPromise.all([
+					expect(template).to.not.have.a.deep.property('Resources.TestauthApiGatewayAuthorizer'),
+					expect(template).to.have.a.deep.property('Resources.TestauthApiGatewayAuthorizermyAlias')
+						.that.has.a.deep.property("Properties.AuthorizerUri")
+						.that.deep.equals({
+							"Fn::Join": [
+								"",
+								[
+									"arn:aws:apigateway:",
+									{
+										"Ref": "AWS::Region"
+									},
+									":lambda:path/2015-03-31/functions/",
+									{
+										"Fn::GetAtt": [
+											"TestauthLambdaFunction",
+											"Arn"
+										]
+									},
+									":${stageVariables.SERVERLESS_ALIAS}",
+									"/invocations"
+								]
+							]
+						}),
+					expect(template).to.have.a.deep.property('Resources.CognitoTestApiGatewayAuthorizermyAlias')
+						.that.deep.equals(cogAuth)
+				]));
+			});
+
+			it('should transform string dependencies and references to authorizers', () => {
+				const template = serverless.service.provider.compiledCloudFormationTemplate = stackTemplate;
+				serverless.service.provider.compiledCloudFormationAliasTemplate = aliasTemplate;
+				return expect(awsAlias.aliasHandleApiGateway({}, [], {})).to.be.fulfilled
+				.then(() => BbPromise.all([
+					expect(template)
+						.to.have.a.deep.property("Resources.ApiGatewayMethodFunc1Get.Properties.AuthorizerId")
+							.that.deep.equals({ Ref: "TestauthApiGatewayAuthorizermyAlias" }),
+					expect(template)
+						.to.have.a.deep.property("Resources.ApiGatewayMethodFunc1Get.DependsOn")
+							.that.equals("TestauthApiGatewayAuthorizermyAlias")
+				]));
+			});
+
+			it('should transform dependency arrays', () => {
+				const template = serverless.service.provider.compiledCloudFormationTemplate = stackTemplate;
+				const deps = [ "myDep1", "TestauthApiGatewayAuthorizer", "myDep2" ];
+				serverless.service.provider.compiledCloudFormationAliasTemplate = aliasTemplate;
+				template.Resources.ApiGatewayMethodFunc1Get.DependsOn = deps;
+				return expect(awsAlias.aliasHandleApiGateway({}, [], {})).to.be.fulfilled
+				.then(() => BbPromise.all([
+					expect(template)
+						.to.have.a.deep.property("Resources.ApiGatewayMethodFunc1Get.DependsOn")
+							.that.deep.equals([ "myDep1", "myDep2", "TestauthApiGatewayAuthorizermyAlias" ])
+				]));
+			});
+
+			it('should handle user resource overwrites', () => {
+				const template = serverless.service.provider.compiledCloudFormationTemplate = stackTemplate;
+				serverless.service.provider.compiledCloudFormationAliasTemplate = aliasTemplate;
+				_.set(serverless, "service.resources", {
+					Resources: {
+						TestauthApiGatewayAuthorizer: {
+							Properties: {
+								AuthorizerResultTtlInSeconds: 100
+							}
+						}
+					},
+					Outputs: {}
+				});
+				return expect(awsAlias.aliasHandleApiGateway({}, [], {})).to.be.fulfilled
+				.then(() => BbPromise.all([
+					expect(template)
+						.to.have.a.deep.property("Resources.TestauthApiGatewayAuthorizermyAlias.Properties.AuthorizerResultTtlInSeconds")
+							.that.equals(100),
+					expect(serverless).to.not.have.a.deep.property('service.resources.Resources.TestauthApiGatewayAuthorizer'),
+				]));
+			});
+
+		});
+	});
+});