Skip to content

This issue was moved to a discussion.

You can continue the conversation there. Go to discussion →

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Closed
ashwgupt opened this issue Mar 29, 2018 · 53 comments

Comments

@ashwgupt
Copy link

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 :-(

@honglu
Copy link
Contributor

honglu commented Mar 29, 2018

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

@ashwgupt
Copy link
Author

ashwgupt commented Apr 1, 2018

@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.

@lestephane
Copy link
Contributor

Hi Folks, #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.

@saratitan
Copy link

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

@mjbuonaccorsi
Copy link

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?

@dehli
Copy link

dehli commented Jan 9, 2019

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.

@keetonian
Copy link
Contributor

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.

@dehli
Copy link

dehli commented Jan 11, 2019

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).

@keetonian
Copy link
Contributor

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?

@dehli
Copy link

dehli commented Jan 11, 2019

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

@keetonian keetonian removed their assignment Jan 22, 2019
@jlhood
Copy link
Contributor

jlhood commented Jan 29, 2019

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.

@cshenrik
Copy link

@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?

@jlhood
Copy link
Contributor

jlhood commented Feb 26, 2019

@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.

@brettstack
Copy link
Contributor

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

@juniorclarke
Copy link

juniorclarke commented Feb 27, 2019

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
Copy link

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?

@brettstack
Copy link
Contributor

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.

@jlhood
Copy link
Contributor

jlhood commented Feb 28, 2019

@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.

@brettstack
Copy link
Contributor

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).

@juniorclarke
Copy link

@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.

@sgates
Copy link

sgates commented Mar 11, 2019

@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!

@TeddyHandleman
Copy link

@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!

@derekslarson
Copy link

derekslarson commented May 21, 2022

This is the approach I took:

import {
  Stack,
  NestedStack,
  aws_lambda as Lambda
} from "aws-cdk-lib";
import * as ApiGatewayV2 from "@aws-cdk/aws-apigatewayv2-alpha";

class HttpApi extends ApiGatewayV2.HttpApi {
  public addRoute(handler: Lambda.IFunction, method: ApiGatewayV2.HttpMethod, path: string): void {
    new ApiGatewayV2.HttpRoute(this, `${method}${path}`, {
      httpApi: this,
      routeKey: ApiGatewayV2.HttpRouteKey.with(path, method),
      integration: new ApiGatewayV2Integrations.HttpLambdaIntegration(`${method}${path}LambdaIntegration`, handler),
    });
  }
}

class ChildStack extends NestedStack {
  constructor(scope: Stack, id: string, httpApi: HttpApi) {
    super(scope, id);

    const handlerFunction = new Lambda.Function(this, "Handler", {
      functionName: "Handler",
      runtime: Lambda.Runtime.NODEJS_14_X,
      handler: `function.handler`,
      code: Lambda.Code.fromAsset('path/to/code'),
    });

    httpApi.addRoute(handlerFunction, ApiGatewayV2.HttpMethod.GET, "/mock/path")
  }
}

class ParentStack extends Stack {
  constructor(scope: Construct, id: string) {
    super(scope, id);

    const httpApi = new  HttpApi(this, "HttpApi");

    new ChildStack(this, "ChildStack", httpApi);
  }
}

This allowed me to create a single HttpApi in the parent stack, and eight child stacks, all using it. Not sure if this approach will work with a RestApi or not.

@mhbarros
Copy link

I'm having this issue right now, no workarounds for now :/

@affanshahab
Copy link

surprised to see we are in 2022 and this issue still exists

@xpdable
Copy link

xpdable commented Sep 1, 2022

as for now I have to drop sam and deploy apigw + lambda via cli in Jenkinsfile, LOL.

@beatrizdemiguelperez
Copy link

Same issue here, any update???

@normand1
Copy link

Also looking for a solution here

@omardoma
Copy link

omardoma commented Nov 6, 2022

Any updates on this?

@allanchua101
Copy link
Contributor

Seriously guys, we really need to pass down RestAPI to child stacks :D

@siddharthmudgal
Copy link

The number of people and orgs looking for this feature. Is it really that hard to AWS SAM peeps?

@awood45
Copy link
Member

awood45 commented Dec 2, 2022

So to explain, yes, this has technical limitations getting in the way. Right now, when you create an Api event source, it does two things:

  1. Creates an AWS::Lambda::Permission resource policy.
  2. Modifies the OpenAPI definition to associate a route/verb with a Lambda function, and adding any authorizer/etc properties to that route.

Doing step 2 is the tricky part, because it has to happen right now where the API is defined. You could use AWS::Serverless::Connector to do 1 if you have a fully defined OpenAPI spec. Though it likely would be easier for the Lambda handlers to be inputs into the template with the API definition in that case.

One possible option we could explore if there was high demand for this would be to add some sort of route helpers to the AWS::Serverless::Api resource, which would point to Lambda functions. In that case they could be in separate templates. But with how event sources are defined, it's not likely possible in a backwards compatible or maintainable way.

@troycampano
Copy link

troycampano commented Dec 7, 2022

This doesn't answer the original question exactly (using a single API Gateway), but if you're OK using an API Gateway per project (let's say each project is a microservice made up of multiple lambdas in each project/microservice), then this is how I did it. Posting in case it's useful to anyone. This enables some modularity so you can break your services down across multiple projects. The downside is that you create an API Gateway per project (which may OK for you if you're want to use the "Multiple API Gateway" pattern referenced here: https://docs.aws.amazon.com/prescriptive-guidance/latest/modernization-integrating-microservices/api-gateway-pattern.html#multiple-api-gateways).

Details posted here:
#2703 (comment)

@hoffa
Copy link
Contributor

hoffa commented Dec 8, 2022

Adding a reproducible example for posterity.

Save the following as template.yaml:

Transform: AWS::Serverless-2016-10-31
Resources:
  MyApi:
    Type: AWS::Serverless::Api
    Properties:
      StageName: prod
  MyFunction:
    Type: AWS::Serverless::Function
    Properties:
      Runtime: python3.8
      Handler: foo
      InlineCode: bar
      Events:
        ApiEvent:
          Type: Api
          Properties:
            Method: get
            Path: /
            RestApiId: !Ref MyApi

Deploy:

sam deploy --region us-west-2 --resolve-s3 --capabilities CAPABILITY_IAM --stack-name test-349 --template template.yaml

It transforms into the following template:

Transformed CloudFormation template

{
  "Resources": {
    "MyFunction": {
      "Type": "AWS::Lambda::Function",
      "Metadata": {
        "SamResourceId": "MyFunction"
      },
      "Properties": {
        "Code": {
          "ZipFile": "bar"
        },
        "Handler": "foo",
        "Role": {
          "Fn::GetAtt": [
            "MyFunctionRole",
            "Arn"
          ]
        },
        "Runtime": "python3.8",
        "Tags": [
          {
            "Key": "lambda:createdBy",
            "Value": "SAM"
          }
        ]
      }
    },
    "MyFunctionApiEventPermissionprod": {
      "Type": "AWS::Lambda::Permission",
      "Properties": {
        "Action": "lambda:InvokeFunction",
        "FunctionName": {
          "Ref": "MyFunction"
        },
        "Principal": "apigateway.amazonaws.com",
        "SourceArn": {
          "Fn::Sub": [
            "arn:aws:execute-api:${AWS::Region}:${AWS::AccountId}:${__ApiId__}/${__Stage__}/GET/",
            {
              "__ApiId__": {
                "Ref": "MyApi"
              },
              "__Stage__": "*"
            }
          ]
        }
      }
    },
    "MyApiprodStage": {
      "Type": "AWS::ApiGateway::Stage",
      "Properties": {
        "DeploymentId": {
          "Ref": "MyApiDeployment1e98ccf811"
        },
        "RestApiId": {
          "Ref": "MyApi"
        },
        "StageName": "prod"
      }
    },
    "MyFunctionRole": {
      "Type": "AWS::IAM::Role",
      "Properties": {
        "AssumeRolePolicyDocument": {
          "Version": "2012-10-17",
          "Statement": [
            {
              "Action": [
                "sts:AssumeRole"
              ],
              "Effect": "Allow",
              "Principal": {
                "Service": [
                  "lambda.amazonaws.com"
                ]
              }
            }
          ]
        },
        "ManagedPolicyArns": [
          "arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole"
        ],
        "Tags": [
          {
            "Key": "lambda:createdBy",
            "Value": "SAM"
          }
        ]
      }
    },
    "MyApiDeployment1e98ccf811": {
      "Type": "AWS::ApiGateway::Deployment",
      "Properties": {
        "Description": "RestApi deployment id: 1e98ccf811ae5bf67840ade2be6857ddf75298a9",
        "RestApiId": {
          "Ref": "MyApi"
        },
        "StageName": "Stage"
      }
    },
    "MyApi": {
      "Type": "AWS::ApiGateway::RestApi",
      "Metadata": {
        "SamResourceId": "MyApi"
      },
      "Properties": {
        "Body": {
          "info": {
            "version": "1.0",
            "title": {
              "Ref": "AWS::StackName"
            }
          },
          "paths": {
            "/": {
              "get": {
                "x-amazon-apigateway-integration": {
                  "httpMethod": "POST",
                  "type": "aws_proxy",
                  "uri": {
                    "Fn::Sub": "arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${MyFunction.Arn}/invocations"
                  }
                },
                "responses": {}
              }
            }
          },
          "swagger": "2.0"
        }
      }
    }
  }
}

The transformed template shows that MyApi (the transformed AWS::Serverless::Api) includes the reference to the Lambda function:

          "paths": {
            "/": {
              "get": {
                "x-amazon-apigateway-integration": {
                  "httpMethod": "POST",
                  "type": "aws_proxy",
                  "uri": {
                    "Fn::Sub": "arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${MyFunction.Arn}/invocations"
                  }
                },
                "responses": {}
              }
            }
          },

The API definition of the AWS::Serverless::Api is mutated here:

api["DefinitionBody"] = editor.swagger

And here:

api["DefinitionBody"] = editor.swagger

But since the API resource is not in the same template as where the event is defined, the SAM transform is unable to edit the API definition. Which is the current technical limitation described in the issue.

@wludwiniak
Copy link

Any plans to fix this? It will be 5 years soon

@aws aws locked and limited conversation to collaborators Dec 15, 2022
@SimonCMoore SimonCMoore converted this issue into discussion #2734 Dec 15, 2022

This issue was moved to a discussion.

You can continue the conversation there. Go to discussion →

Projects
None yet
Development

No branches or pull requests