serverless-application-model icon indicating copy to clipboard operation
serverless-application-model copied to clipboard

Possibility to get AWS::Serverless::RestApi passed a template variable to Template files with Functions only

Open ashwgupt opened this issue 6 years ago • 44 comments

We have got a problem where the resource limit of CF is hit when transformed from our SAM Template file.

Given all our Lambdas are behind APIs and we want to maintain all APIs from Single API Gateway, and that we now have to split the SAM Template into multiples to make them independently deploy-able, is there a way that we define one template that has only AWS::Serverless::Api to build out API Gateway with given Swagger file, and then to pass its reference to other template files that will be deploying our AWS::Serverless::Functions.

We simply want to pass the reference of RestApiId to the template files as a CFN Parameter.

Is this possible at this moment? As the error on the attempt says otherwise - RestApiId must be a valid reference to an 'AWS::Serverless::Api' resource in same template, which is quite dis-heartening :-(

ashwgupt avatar Mar 29 '18 16:03 ashwgupt

You can use CloudFormation export and import to pass the parameters from one stack to a different stack: https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/using-cfn-stack-exports.html

To deal with resource limit issue, CloudFormation recommends to use nested stacks: https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/using-cfn-nested-stacks.html

~~The caveat here is that nested stack does not support SAM. One way to get around that is to only define the non serverless resources in nested stacks and still leave the serverless resources in the top level SAM template.~~ Edit: Nested stacks are now supported, starting November 2018

honglu avatar Mar 29 '18 23:03 honglu

@honglu thanks for your reply.

We have been doing what you suggested above and now only serverless resources are left in the SAM when we hit the CF limit after transformation.

There were 3 options we came up with and tried but all failed:

  1. Define API gateway as a separate SAM file, take export of created Gateway. Split Lambdas into multiple Lambda-only SAM files, with all of these taking already created API Gateway as input for their event.properties.RestApiId => That fails due to the limitation that SAM has i.e. the RestApiId must be a valid reference of serverless:api type resource in the same template file.
  2. Define API gateway as a separate SAM file and create Role/Policies such that it is allowed to invoke required functions. Split Lambdas into multiple Lambda-only SAM files but without any Api type Event defined under it. That way we can create our Lambdas and API Gateway independent of each other and integrate using Swagger and arrange for permissions separately => That fails at SAM Local level, upon which we depend a lot for local run and testing, as that now doesn't know against what event a lambda should listen.
  3. Define API gateway as a separate SAM file and create Role/Policies such that it is allowed to invoke required functions. Split Lambdas into multiple Lambda-only SAM files but without RestApiId property for event => That gets even worse as now every lambda creates a new Api Gateway for itself assuming that is what it's asked for. So at the end we are still blocked in that catch-22 situation due to variety of limits and limitations of SAM and CF.

So at the end we are still blocked in that catch-22 situation due to variety of limits and limitations of SAM and CF.

Is there any other way or ideas that can help us overcome it?

PS: However I also came across another issue - #335 that indicates the option-1 was working until a few days back, which is really interesting and worth watching progressing on that issue.

ashwgupt avatar Apr 01 '18 16:04 ashwgupt

Hi Folks, https://github.com/awslabs/serverless-application-model/issues/149 seems to imply one cannot split the api resource from the function resources. This issue seems to imply it is indeed possible, without giving template examples. I would be forever grateful if someone who's managed to do this would post an actual example that works. Many thanks in advance.

lestephane avatar Apr 22 '18 11:04 lestephane

We managed to split them before, but no longer. It suddenly stopped working so we had to redo all of our stack structure which meant a downtime in production. See #335

saratitan avatar May 14 '18 11:05 saratitan

Does anyone know if this issue is dead? Or does anyone know if there is a way to use the aws lambda update-function-code CLI method to update the code and roll it out with checkpoints?

mjbuonaccorsi avatar Oct 11 '18 15:10 mjbuonaccorsi

It would also be great if RestApiId could be of type AWS::ApiGateway::RestApi instead of just AWS::Serverless::Api. We're currently using CloudFormation and would like to dry up some code by slowly switching to SAM but this prevents us from doing that.

dehli avatar Jan 09 '19 13:01 dehli

Nested stacks are now supported in SAM, meaning you can nest resources in other stacks and use the outputs as values in the current stack.

keetonian avatar Jan 11 '19 18:01 keetonian

Even though they are supported, when I created the AWS::Serverless::Api in a separate stack from an AWS::Serverless::Function I still got the above error (this was two days ago).

dehli avatar Jan 11 '19 18:01 dehli

Can you post an example that creates this issue? Is it as simple as defining an API in one stack and referencing a Function in that API that is from a nested stack?

keetonian avatar Jan 11 '19 18:01 keetonian

Sure! These are two stacks that cause an error.

template.yml

---
AWSTemplateFormatVersion: 2010-09-09
Transform: 'AWS::Serverless-2016-10-31'
Resources:
  RestApi:
    Type: AWS::Serverless::Api
    Properties:
      StageName: LATEST

  Function:
    Type: AWS::Serverless::Function
    Properties:
      Handler: index.handler
      Runtime: nodejs8.10
      InlineCode: >-
        exports.handler = async function(event) {
          return { hello: 'world' };
        }
      Events:
        Request:
          Type: Api
          Properties:
            Path: /hello
            Method: GET
            RestApiId: !Ref RestApi

  Substack:
    Type: AWS::CloudFormation::Stack
    Properties:
      TemplateURL: "./substack.yml"
      Parameters:
        RestApi: !Ref RestApi

substack.yml

---
AWSTemplateFormatVersion: 2010-09-09
Transform: 'AWS::Serverless-2016-10-31'
Parameters:
  RestApi:
    Type: String

Resources:
 Function:
    Type: AWS::Serverless::Function
    Properties:
      Handler: index.handler
      Runtime: nodejs8.10
      InlineCode: >-
        exports.handler = async function(event) {
          return { hello: 'world' };
        }
      Events:
        Request:
          Type: Api
          Properties:
            Path: /world
            Method: GET
            RestApiId: !Ref RestApi

image

dehli avatar Jan 11 '19 19:01 dehli

Thanks for the example. SAM currently requires the API and lambda functions invoked by the API Gateway to be in the same template in order to use the implicit API generation functionality of SAM. This is because the swagger is generated in the template and references the serverless functions using !Ref.

Are you saying you are hitting the CFN 200 resource limit when your template only contains the API Gateway resources and lambda function (and IAM roles) directly invoked by API Gateway and all other resources are in nested stacks? This would imply that you have maybe ~70 resource+methods all being serviced by separate Lambda functions. Is that correct?

Some alternatives you could try:

  1. Route multiple resource+methods to the same Lambda function. This requires your Lambda function to be able to route different requests to different handlers in your code. There are a few language-specific frameworks that make this fairly easy. Examples: aws-serverless-express for Node.js, aws-serverless-java-container for Java.
  2. Use the Role property of AWS::Serverless::Function to reuse the same role across multiple Lambda functions that handle APIs. There's a security trade-off you may need to make here, but it will save resources.

Adding SAM support for the same intuitive API mapping within the Events section of an AWS::Serverless::Function without the API and function being defined in the same template is non-trivial since the AWS::ApiGateway::RestApi resource expects the full API definition, including function integrations to be defined. If anyone wants to submit an RFC issue proposing how it could be done in SAM, that'd be awesome, but I don't see us prioritizing this without more feedback from customers.

jlhood avatar Jan 29 '19 18:01 jlhood

@jlhood: Let med add some more feedback. I am composing serverless applications using the AWS::Serverless::Application resource. In one SAM template I need to nest two applications that both use an API gateway. In this scenario it's not possible to make the two nested applications share the api gateway because:

RestApiId must be a valid reference to an 'AWS::Serverless::Api' resource in same template

Any ideas for workarounds?

cshenrik avatar Feb 24 '19 07:02 cshenrik

@cshenrik Really appreciate the additional context and feedback. Agreed, adding triggers based on resources in your current template to functions defined within a nested application is not made easy by SAM today.

Currently, you would have to manage your own API swagger document to add the nested app function as an integration.

What do you think about the possibility of being able to specify event sources on the AWS::Serverless::Application resource similar to how it's done on AWS::Serverless::Function today? Something like this?

  NestedApp:
    Type: AWS::Serverless::Application
    Properties:
      Location:
        ApplicationId: <app id>
        SemanticVersion: 1.0.0
      Events:
        GetRoot:
          Type: Api
          Properties:
            Path: /
            Method: GET
            OutputFunctionName: MyHandlerFunctionName
            OutputFunctionArn: MyHandlerFunctionArn

So here I have a nested SAR app called NestedApp that defines two outputs: MyHandlerFunctionName and MyHandlerFunctionArn, i.e., what would normally be referenced via !GetAtt NestedApp.Outputs.MyHandlerFunctionName and !GetAtt NestedApp.Outputs.MyHandlerFunctionArn. SAM would then automatically add the needed AWS::Lambda::Permission and update the implicit API swagger document to add an integration to call MyHandlerFunction defined inside NestedApp.

Need to flesh out the exact syntax and some more details but wanted to put it out there to get some feedback.

jlhood avatar Feb 26 '19 18:02 jlhood

I think adding a new property to Serverless::Api would solve this problem and more. Maybe we call this Paths for Swagger symmetry.

brettstack avatar Feb 26 '19 19:02 brettstack

Just hit the CFN 200 limit today (yes, @jlhood, I am reusing roles) and looking for a workaround led me to this issue.

I initially tried to pass the "AWS::Serverless::Api" as a parameter to "AWS::Serverless::Application" but then I got the dreaded "The REST API doesn't contain any methods" error as it seems CFN is trying to deploy the API before it builds the other nested stacks.

Any workarounds?

EDIT: Looking at my last created CFN stack the problem seems to be the Lambda Permissions is being created 2 times for each lambda function as pointed out in #285. I would love to move to a nested stack solution but #349 prevents me from this.

I really don't want to create a different API endpoint for different resources but that's all I that comes to mind right now.

juniorclarke avatar Feb 27 '19 19:02 juniorclarke

I think adding a new property to Serverless::Api would solve this problem and more. Maybe we call this Paths for Swagger symmetry.

@brettstack I'm thinking of your proposed solution and I'm not sure I follow. How can adding a property to the Serverless:Api make the nested stacks all share the same Serverless:Api reference?

juniorclarke avatar Feb 28 '19 00:02 juniorclarke

You would use GetAtt on the Lambda Functions Output from the nested stack. The API would then create the permissions and set up the route just like it does when you define API Events on a Function.

brettstack avatar Feb 28 '19 04:02 brettstack

@brettstack Yeah it would be interesting to also have a simplified syntax around adding function integrations on the AWS::Serverless::Api resource itself. I think that would be a nice feature to add. However, that doesn't solve the same problem for if my nested stack contains a function that I want triggered by a non API resource, e.g., an S3 bucket or SNS Topic. The solution I'm proposing would work for all existing event types that you can add to AWS::Serverless::Function, including API events. Maybe this is beyond the scope of this specific issue, but I've had conversations with other SAR users who have talked about this exact problem for other Lambda event sources so I figure this can solve both problems in one feature.

Although again, I'm not opposed to adding a way to connect functions to AWS::Serverless::Api resources through properties on AWS::Serverless::Api instead of only allowing it through AWS::Serverless::Function.

jlhood avatar Feb 28 '19 04:02 jlhood

Are you saying the S3 bucket is defined in the parent template? Can't you just pass through the S3 bucket ARN and SAM will create the permission for you? I suppose SAM also modifies the notification configuration on the bucket (not sure off the top of my head if something similar happens for SNS), so you would lose that (though you can set it yourself).

brettstack avatar Feb 28 '19 05:02 brettstack

@brettstack and @jlhood this sounds great. Are you going to put out an RFC, or get this prioritized. I think more people with hit the 200 CFN limit sooner than later due to #285. If #285 is fixed it may give you some breathing room to flesh out the RFC with more customer feedback. My current workaround is to have multiple API endpoints. It’s ugly but it works for now.

BTW keep up the great work. Thanks a million for doing what you guys do.

juniorclarke avatar Mar 01 '19 00:03 juniorclarke

@brettstack I got excited for a minute because I thought you were saying there is an existing workaround for shared Gateway - but I see you were thinking out loud. +1 for needing this, we're currently breaking up our API set into several separate gateways because of the limit. It's that permission per endpoint that's killing us - we have maybe a dozen lambdas that each handle maybe another dozen endpoints, and with 2 permissions per endpoint, we're blowing right through the max. My first thought was to break up into sub stacks, but the circular dependency/chicken&egg with the gateway and the functions had me running in circles. I'll be looking forward to enhancements in this area!

sgates avatar Mar 11 '19 17:03 sgates

@jlhood Has there been any traction on this? Not only is this great for applications with a large number of endpoints, but also for the sake of of modularity.

In addition, I'm wondering if another, slightly similar, solution might be the possibility of being able to modularize our Swagger/OpenAPI documents for each function? I apologize if this is slightly out of scope, but I think this ticket illuminates the issue with modularity and AWS SAM. I also apologize if I overlook a reality that would prevent this solution from being implemented in CF. I'm relatively new to architecting serverless backends.

Say I have a few lambda functions each acting as a separate endpoint (GET foo/bar/, POST foo/bar, etc...). For each I could write the specific paths and definitions for that endpoint. On deployment CF would add each APIG endpoint (resources, methods, models, etc..) based off the modular definition.

In the subtemplate of the function, you could have a resource like AWS::Serverless::ApiEndpoint with a DefinitionBody or DefinitionUri` parameter the same way you can reference a complete document in an Api resource.

In the master template you would have the AWS::Serverless::Api resource and the property Paths property as @brettstack suggested . The paths could reference these endpoint resources and combine them into a single API Gateway instance.

A few caveats with this approach might be sacrificing some of the ability to combine definitions provided by OpenAPI. For example my GET /foo/bar might return an object very similar to the payload for POST /foo/bar plus a couple of extra parameters. In OpenAPI I could define the model for the GET response model to be allOf the POST model plus those extra params. If we took the approach for modularization, then these definitions might live in separate templates. However, I think this trade off would be overshadowed by the ability to actually modularize our OpenAPI documents; a feature that is already built in to the OpenAPI spec.

I realize that this is a non-trivial solution 😄. Despite this issue, I really enjoy using SAM to deploy my backend. Thanks for all the awesome work!

TeddyHandleman avatar Mar 14 '19 19:03 TeddyHandleman

@juniorclarke @sgates @TeddyHandleman Really appreciate the detailed comments and feedback! I think the best path forward is to split this out into a separate RFC issue so we can focus on defining the right syntax/experience for this. I'll go ahead and create that RFC issue.

jlhood avatar Mar 22 '19 18:03 jlhood

@jlhood that's great to hear, and thanks. Looking forward to progress on this!

sgates avatar Mar 22 '19 18:03 sgates

@jlhood Awesome! Thank you so much.

TeddyHandleman avatar Mar 22 '19 18:03 TeddyHandleman

@cshenrik @juniorclarke @sgates @TeddyHandleman Opened #866 with a more generalized proposal that would support more than just the nested app use case. Would really appreciate your feedback!

jlhood avatar Mar 22 '19 19:03 jlhood

@elver Your comment has been removed, because it violates our Code of Conduct.

jlhood avatar Jun 18 '19 23:06 jlhood

I decided to write my own macro function that will allow me to define shared api gateway in parent stack and then use reference to exported api gateway from separated SAM stacks. You can find my project here https://github.com/4-cube/cf-shared-apigwv2

Currently, only ApiGatewayV2 (HttpApi) is supported and there is no support to specify Auth per event (I plan to add support for this).

markoradinovic avatar May 19 '20 10:05 markoradinovic

I hit this issue when trying to use nested stacks to get around the 200 resource limit. I was able to get things working by following this tutorial. https://mechanicalrock.github.io/2020/04/28/sam-split-lambdas.html

derekslarson avatar Jun 10 '20 01:06 derekslarson

@cstst The tutorial seems to work only with AWS::Serverless::HttpApi. It didn't work on AWS::Serverless::RestApi.

yusuken1983 avatar Oct 23 '20 09:10 yusuken1983