aws / serverless-application-model

The AWS Serverless Application Model (AWS SAM) transform is a AWS CloudFormation macro that transforms SAM templates into CloudFormation templates.
https://aws.amazon.com/serverless/sam
Apache License 2.0
9.34k stars 2.38k forks source link

Feature request: configure API gateway extensions in SAM template #970

Open henrytk opened 5 years ago

henrytk commented 5 years ago

Description:

When using Swagger/Open API specifications to configure an API Gateway, you have to add your own API Gateway Swagger Extensions. This means your API specification is tainted with AWS-specific data. This is a feature request for some way of configuring functions and APIs from the SAM template and have it implicitly annotate the Open API specification.

Example

Extend the Api event source type so that you can map the request path and method to a particular Lambda invocation. This would generate the x-amazon-apigateway-integration field in the Open API specification implicitly when deploying. Example configuration (the Extensions field onwards):

Resources:
  HelloWorldFunction:
    Type: AWS::Serverless::Function
    Properties:
      CodeUri: hello-world/
      Handler: app.lambdaHandler
      Runtime: nodejs8.10
      Events:
        HelloWorld:
          Type: Api
          Properties:
            Path: /hello
            Method: get
            Extensions:
              IntegrationResponse:
                uri: Fn::Sub: arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${HelloWorldFunction.Arn}/invocations # This may also be implicit?
                contentHandling: "CONVERT_TO_TEXT"

Rationale

The idea here is to keep your Open API specification pure and free from AWS-specific data. This should make it much easier to configure the relationship between your Lambdas and API Gateway, because you only have to add configuration in the SAM template.

keetonian commented 5 years ago

SAM already generates all of this when no DefinitionBody or DefinitionUri property is supplied; it creates the swagger API definition based on what events are in the function.

Is this feature request specific to when a customer has already created a swagger specification, and you would like SAM to add in the lambda integration?

For example, if someone defined the following swagger:

         "paths": {
            "/hello": {
              "get": {
                "responses": {}
              },
          }

Would you like SAM to be able to add the integration (below) to it?

                "x-amazon-apigateway-integration": {
                  "httpMethod": "POST",
                  "type": "aws_proxy",
                  "uri": {
                    "Fn::Sub": "arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${HelloWorldFunction.Arn}/invocations"
                  }
                },
henrytk commented 5 years ago

Is this feature request specific to when a customer has already created a swagger specification, and you would like SAM to add in the lambda integration?

Yes. It would be an implicit conversion. The aim is to avoid having to know how to write an AWS extension in JSON, because the SAM template is the source of truth for how your Rest API integrates with other components.

praneetap commented 5 years ago

Thanks for the feature request! We have passed this along to our product team for possible prioritization. Please +1 on the feature to help with prioritization.

carpnick commented 4 years ago

We have this requirement too. Basically we want a vanilla OpenAPI file, and then use the event definitions on the AWS Serveless Function to map into the defined functions in the OpenAPI file. Then as part of the transform, if there are any endpoints defined in the OpenAPI file that dont have a matching function with event, it kills the deployment.

Reason for priority: Keeping contract logic separated from deployment logic is extremely important. It is near impossible to do currently due to us having to add the AWS extensions in either the template.yml (as events) or in the swagger.yml file today. It would be great to allow us to define the contract in the swagger, while the template.yaml hooks up implementation(functions) to said contracts.

It would keep separation of duties apparent: Swagger for contract and models, template.yaml for infrastructure and implementation hookup to the contract.

praneetap commented 4 years ago

@henrytk I see two asks here.

  1. Customizable extensions in given openapi doc. This would be a backwards incompatible change in SAM today, since it might break existing deployments if we start to better integrate integrations with customer provided OpenApi, which would mean that this would be an opt in feature. I can get this passed on to the product team for prioritization.
  2. Content Handling - Read this to know why we don't have content handling support added to SAM today.
henrytk commented 4 years ago

@praneetap My particular use case was only concerned with configuring the integration response, and not content handling, but I can see that if this type of mechanism was introduced there are lots of AWS-specific configuration, such as content handling, which I may not have considered properly.

To simplify (and generalise slightly), this request is about keeping AWS-specific data out of your Open API specification. For example, by making event source types allow optional configuration which gets implicitly translated into the x-amazon-* fields required to configure the Rest API body.

Regarding backwards compatibility, the fields, such as Events.HelloWorld.Properties.Extensions.IntegrationResponse in the example in this issue's description, would be optional. The compatibility issue I think would be reconciling which takes precedence if configuration is present in SAM and in the Open API specification. For backwards compatibility, I assume content in the Open API specification must have precendence.

IstvanSzilagyi commented 4 years ago

Hi, i stumbled upon this post and im curious how to do integrationResponse definition? is there any available examples how to define integrationResponse inside a SAM template?

Also stack overflow question i made: https://stackoverflow.com/questions/59576730/integrationresponse-sam-template

every help appreciated.

ShreyaGangishetty commented 4 years ago

@IstvanSzilagyi "For a proxy integration, API Gateway automatically passes the backend output to the client as an HTTP response. You do not set either an integration response or a method response". We do a Lambda PROXY integration in SAM. Please check API gateway documentation for more information

lisandrolan commented 4 years ago

Here is some of the code Im using.

ApiGateway:
    Type: AWS::Serverless::Api
    Properties:
      StageName: Prod
      Variables:
        db_host: !Ref host
        db_user: !Ref user
        db_password: !Ref password
        db_database: !Ref database
        url: !Ref defUrl
      Auth:
        # DefaultAuthorizer: CognitoAuthorizer
        Authorizers:
          CognitoAuthorizer:
            UserPoolArn: 
              Fn::GetAtt:
              - GlobalApp
              - Outputs.CognitoUserPoolArn
      MethodSettings:
        - HttpMethod: "*"
          ResourcePath: "/*"
          LoggingLevel: INFO      # INFO or ERROR
          DataTraceEnabled: true  # Put logs into cloudwatch
          MetricsEnabled: true    # Enable detailed metrics (error 404, latence, ...)
      DefinitionBody:
        swagger: 2.0
        info:
          title: ApiGateway
        paths:
##============================= /admin-info =============================  
          /admin-info:
            get:
              consumes:
              - "application/json"
              produces:
              - "application/json"
              security:
                - CognitoAuthorizer: []
              parameters:
              - name: "Authentication"
                in: "header"
                required: false
                type: "string"
              responses:
                '200':
                  description: "200 response"
                  schema:
                    $ref: "#/definitions/Empty"
                  headers:
                    Access-Control-Allow-Origin:
                      type: "string"
              x-amazon-apigateway-integration:
                uri:
                    Fn::Sub: "arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${AdminGetInfoFunction.Arn}/invocations"
                responses:
                  default:
                    statusCode: "200"
                    responseParameters:
                      method.response.header.Access-Control-Allow-Origin: "'*'"
                passthroughBehavior: "when_no_templates"
                httpMethod: "POST"
                requestTemplates:
                  application/json: "##  See http://docs.aws.amazon.com/apigateway/latest/developerguide/api-gateway-mapping-template-reference.html\n\
                    ##  This template will pass through all parameters including path, querystring,\
                    \ header, stage variables, and context through to the integration endpoint\
                    \ via the body/payload\n#set($allParams = $input.params())\n{\n\"body\"\
                    \ : $input.json('$'),\n\"params\" : {\n#foreach($type in $allParams.keySet())\n\
                    \    #set($params = $allParams.get($type))\n\"$type\" : {\n    #foreach($paramName\
                    \ in $params.keySet())\n    \"$paramName\" : \"$util.escapeJavaScript($params.get($paramName))\"\
                    \n        #if($foreach.hasNext),#end\n    #end\n}\n    #if($foreach.hasNext),#end\n\
                    #end\n},\n\"stage-variables\" : {\n#foreach($key in $stageVariables.keySet())\n\
                    \"$key\" : \"$util.escapeJavaScript($stageVariables.get($key))\"\n   \
                    \ #if($foreach.hasNext),#end\n#end\n},\n\"context\" : {\n    \"account-id\"\
                    \ : \"$context.identity.accountId\",\n    \"api-id\" : \"$context.apiId\"\
                    ,\n    \"api-key\" : \"$context.identity.apiKey\",\n    \"authorizer-principal-id\"\
                    \ : \"$context.authorizer.principalId\",\n    \"caller\" : \"$context.identity.caller\"\
                    ,\n    \"cognito-authentication-provider\" : \"$context.identity.cognitoAuthenticationProvider\"\
                    ,\n    \"cognito-authentication-type\" : \"$context.identity.cognitoAuthenticationType\"\
                    ,\n    \"cognito-identity-id\" : \"$context.identity.cognitoIdentityId\"\
                    ,\n    \"cognito-identity-pool-id\" : \"$context.identity.cognitoIdentityPoolId\"\
                    ,\n    \"http-method\" : \"$context.httpMethod\",\n    \"stage\" : \"\
                    $context.stage\",\n    \"source-ip\" : \"$context.identity.sourceIp\"\
                    ,\n    \"user\" : \"$context.identity.user\",\n    \"user-agent\" : \"\
                    $context.identity.userAgent\",\n    \"user-arn\" : \"$context.identity.userArn\"\
                    ,\n    \"request-id\" : \"$context.requestId\",\n    \"resource-id\" :\
                    \ \"$context.resourceId\",\n    \"resource-path\" : \"$context.resourcePath\"\
                    ,\n    \"sub\": \"$context.authorizer.claims.sub\"\n   \n\n\n    }\n}"
                contentHandling: "CONVERT_TO_TEXT"
                type: "aws"
            options:
              consumes:
              - "application/json"
              produces:
              - "application/json"
              responses:
                '200':
                  description: "200 response"
                  schema:
                    $ref: "#/definitions/Empty"
                  headers:
                    Access-Control-Allow-Origin:
                      type: "string"
                    Access-Control-Allow-Methods:
                      type: "string"
                    Access-Control-Allow-Headers:
                      type: "string"
              x-amazon-apigateway-integration:
                responses:
                  default:
                    statusCode: "200"
                    responseParameters:
                      method.response.header.Access-Control-Allow-Methods: "'GET,OPTIONS'"
                      method.response.header.Access-Control-Allow-Headers: "'Content-Type,X-Amz-Date,Authorization,X-Api-Key,X-Amz-Security-Token'"
                      method.response.header.Access-Control-Allow-Origin: "'*'"
                passthroughBehavior: "when_no_match"
                requestTemplates:
                  application/json: "{\"statusCode\": 200}"
                type: "mock"