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.35k stars 2.38k forks source link

Inject StageVariable into API resource definition that uses a Swagger file #8

Closed sanathkr closed 6 years ago

sanathkr commented 7 years ago

When using a Swagger file to define an API, users have to explicitly specify Stage Variables to hold the Lambda function ARN and use the stage variable in their Swagger template to specify the integration. This is not intuitive.

We could simplify this process by automatically injecting the Stage Variable when users specify an API Event source to their Lambda Function

When customers specify a template like this:

LambdaFunction:
  Type: AWS::Serverless::Function
  Properties:
      Handler: index.handler
      Runtime: nodejs4.3
      Events:
         GetApi:
             Type: API
              Properties:
                  Path: /
                  Method: GET
                  RestApiId: !Ref MyApiResource

MyApiResource:
   Type: AWS::Serverless::Api
   Properties:
        DefinitionUri: s3://bucket/myswagger.yaml

we could inject a StageVariable to the API resource where variable name is equal to the function resource's logical name and value equal to the function's ARN

MyApiResource:
   Type: AWS::Serverless::Api
   Properties:
        DefinitionUri: s3://bucket/myswagger.yaml
        Variables:
             LambdaFunction: !Ref LambdaFunction
dinvlad commented 7 years ago

_examples/2016-10-31/api_swaggercors seems to inject the function logical name. Could we somehow get the full ARN such that we don't have to hard-code region and accountId into Swagger (or defining them as stage variables) as well? Thanks

mparaz commented 7 years ago

Yes, getting the full ARN would be useful. This will solve my problem with using a stage variable for the account ID - #87

dinvlad commented 7 years ago

Our current cheapo workaround (since we cannot use the inlined body directly in other tools like Swagger Editor, or for input validation) is to maintain a separate swagger.yaml, but format everything as if it was inside the SAM template (i.e. we can reference parameters through ${} syntax).

We then terminate the actual SAM template with the Api resource, but instead of specifying a value of the DefinitionBody as the last parameter (after an obvious !Sub |-), we concatenate the Swagger file before packaging the application (YAML lets you do that if you also properly indent all lines of the Swagger file, either explicitly in the file or using a regular expression in gulp-replace or something similar).

dinvlad commented 7 years ago

Nice, it seems like now we will be able to use http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/create-reusable-transform-function-snippets-and-add-to-your-template-with-aws-include-transform.html to directly insert the Swagger YAML stored on S3 (but before any substitutions are made). Will report back once I test it.

gertjvr commented 7 years ago

Eager to see what possible with include transform, wonder if they will allow you to point to a local template like aws package does.

dinvlad commented 7 years ago

Finally got it working! In my SAM template:

Api:
  Type: AWS::Serverless::Api
  Properties:
    StageName: Latest
    DefinitionBody:
      Fn::Transform:
        Name: AWS::Include
        Parameters:
          Location: !Sub s3://${ArtifactsBucket}/swagger.yaml

where ArtifactsBucket refers to the bucket where I upload my Swagger spec prior to 'packaging' the SAM template. Then, in the Swagger template itself I can use the intrinsics, e.g.

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

I'm using the long Fn::Sub notation here instead of just !Sub, because Swagger doesn't natively support the latter, and also because the docs on AWS::Include Transform say that shorthand forms are not supported yet.

On a side note, just wasted ~4 hours troubleshooting the Invalid ARN specified in the request error, thinking that Fn::Sub for function ARNs doesn't work. Turns out, I only forgot to prepend an ARN reference for authorizerCredentials role in the spec (I'm using a custom authorizer). I really wish API Gateway import errors were more informative! Like specifying the concrete line that fails.. In any case, just make sure to prepend Fn::Sub/Fn::Ref/Fn::GetAtt to ALL of your CloudFormation references in the spec.

P.S. Not sure about local references, that would be awesome though!

mouscous commented 7 years ago

Any update on using local reference with AWS::Include?

Also, I tried passing in the Lambda ARN and the region as stage variables as shown above by @sanathkr, but this did not work. Still got an "Invalid ARN specified in the request" error. Is this pattern no longer supported?

absynthe commented 6 years ago

I tried the stage variables approach too and it did not work for me either. Will try the approach @dinvlad suggested.

alexcasalboni commented 6 years ago

@dinvlad your approach looks great, but is there a way to avoid the manual swagger file upload? I would rather reference a local file (as I'm currently doing with DefinitionUri), and I'd expect cloudformation package to take care of the packaging for me.

Does it make sense? Am I missing something obvious?

dinvlad commented 6 years ago

@alexcasalboni agreed, that would be awesome. I don't think CF currently supports that though. package might though, but that is part of CLI so perhaps we should ask there.

dinvlad commented 6 years ago

Also, you can paste your entire swagger inside CF template, and reference variables the same way. But that blows up the size of the SAM template, which is currently limited to 51.2 KB (last time I checked).

alexcasalboni commented 6 years ago

@dinvlad Yes, that's what I'm doing right now. Inlining a small API sounded reasonable. And thanks for the template size limit, that's good to know!

bluepeter commented 6 years ago

Note that, at least for me, the method @dinvlad recommends doesn't work if the Include'd Swagger file is > whatever the size limit is. Gets the following error in CF console:

Errors found during import: Unable to put integration on 'GET' for resource at path '/account': The item is too large. Please reduce the item size.
sanathkr commented 6 years ago

Template size limit is 460KB if you upload it to S3 and pass the S3 URL to CloudFormation.

I don't see us doing this at the moment for external swagger files. Best is to either inline the Swagger file or use AWS::Include to let CloudFormation inline the file on runtime.