Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How do I get the ARN of an AWS Lambda function for a Cloud Formation specific resource property?

I can't seem to get Ref or Fn:GetAtt to return a valid value for use with setting up a resource.

serverless.yml

...etc...

functions:
  bearerTokenAuthentication:
    handler: app.bearerTokenAuthentication
    name: ${self:service}-auth-bearer

resources:
  - ${file(./serverless_resources.yml)}

serverless_resources.yml

Resources:
  ApiGateway:
    Type: AWS::ApiGateway::RestApi
    Properties:
        Name: restapi-${self:provider.stage}
        Description: Endpoints
        ApiKeySourceType: HEADER # (to read the API key from the X-API-Key header of a request)

  ApiGatewayBearerAuthorizer:
    Type: AWS::ApiGateway::Authorizer
    Properties:
      Type: token
      IdentitySource: method.request.header.Authorization
      Name: BearerAuthorization
      AuthorizerResultTtlInSeconds: 300
      AuthorizerUri: !Join  #arn:aws:apigateway:${self:provider.region}:lambda:path/${self:functions.bearerTokenAuthentication.name}
        - ''
        - - 'arn:aws:apigateway:'
          - !Ref 'AWS::Region'
          - ':lambda:path/2015-03-31/functions/'
          - !GetAtt 
            - bearerTokenAuthentication # also tried !Ref bearerTokenAuthentication and '${self:functions.bearerTokenAuthentication.name}'
            - Arn
          - /invocations
      RestApiId: !Ref ApiGateway

No matter what I do, GetAtt cannot find the ARN for the Lambda function declared in bearerTokenAuthentication. I just keep getting this error:

Error: The CloudFormation template is invalid: Template error: instance of Fn::GetAtt references undefined resource bearerTokenAuthentication

... or if trying Ref ...

Error: The CloudFormation template is invalid: Template format error: Unresolved resource dependencies [bearerTokenAuthentication] in the Resources block of the template

Is it possible to reference Lambda ARNs from the resource section? It seems by the error messages it is looking for "resource" names. I always thought the lambda function declaration was also considered a resource (besides the obvious Resources: block of course), perhaps I am misunderstanding something.

like image 862
James Wilkins Avatar asked Aug 26 '19 23:08

James Wilkins


1 Answers

I figured it out. I had a NodeJS project and was using the "serverless" command line (sls) to deploy using serverless.yml. It turns out it creates a .serverless sub-directroy with some files in it. One of them is a compiled template for AWS Cloud Formation called cloudformation-template-update-stack.json. It appears that the utility likes to mangle the names by making the first character uppercase and adding "LambdaFunction" to all the function names (for whatever reason). In this case, bearerTokenAuthentication was renamed to BearerTokenAuthenticationLambdaFunction (the actual resource name). After looking into the compiled template it all became clear. The utility also seems to figure out the dependencies as well, which was good to know. This was the final result:

  AuthorizerUri: !Join 
    - ''
    - - 'arn:aws:apigateway:'
      - !Ref 'AWS::Region'
      - ':lambda:path/2015-03-31/functions/'
      - !GetAtt [ BearerTokenAuthenticationLambdaFunction, Arn ]
      - '/invocations'

Other "Gotchas":

DO NOT define the AWS::ApiGateway::RestApi resource (like I did in my question) if you are also using event mappings with the functions, otherwise you will get 2 APIs created. event entries automatically cause an API to be created called "ApiGatewayRestApi" - which is the resource name generated by the sls utility. The last line of the last file was changed to this:

  RestApiId: !Ref ApiGatewayRestApi

And my ApiGateway: section was removed.

Credit goes to this post which helped make it more clear to me what was really going on: https://forum.serverless.com/t/fixed-how-do-i-get-reference-api-gateway-restapi-id-in-serverless-yml/3397/5

Previous Answer:

I found another way as well. This is what I resorted to doing until I found the proper (shorter) way. I was able to pull the lambda name and manually stitch together the required URI:

  AuthorizerUri: !Join
    - ''
    - - 'arn:aws:apigateway:'
      - !Ref 'AWS::Region'
      - ':lambda:path/2015-03-31/functions/arn:aws:lambda:'
      - !Ref 'AWS::Region'
      - ':'
      - !Ref 'AWS::AccountId'
      - ':function:'
      - '${self:functions.bearerTokenAuthentication.name}'
      - '/invocations'

I hope that helps save someone some time trying to understand the complicated .yml file. I also cannot understand why it is so hard to make it simple to understand. All someone had to do is say (for me) was "sls takes a 'serverless.yml' file, and optional include files (such as declarations specific to the cloud system itself, like AWS Cloud Formation), and generates a template JSON file that is used by the target cloud services system to deploy your solution. Also, the names you give may get mangled, so check the template." I'm also surprised that no one has created an editor to make all this easier by now - perhaps something I'll look into myself one day. ;)

like image 178
James Wilkins Avatar answered Sep 21 '22 13:09

James Wilkins