Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to set a stage name in a SAM template

I want to set a stage name for the API Gateway in a SAM template.yaml. But whatever I try I'm not succeeding. Without trying to name my stage, everything works as expected but with the default stage names Prod and Stage.

My sam-cli version is 0.47.0

I did find three comparable questions here on Stackoverflow but none of the answers work for me.

  • How can I change the name of the API stage in a SAM template?
  • How can I use api gateway stages via cloudformation or sam?
  • Using SAM file to remove default “Stages” in AWS ApiGateway?

I always get an error something like this:

Unresolved resource dependencies [ServerlessRestApi] in the Outputs block of the template

So how do I get a stage name I choose myself. I don't care much if Prod and Stage coexist with my chosen name.

Just to be complete, my template.yaml file is below:

AWSTemplateFormatVersion: '2010-09-09'
Transform: AWS::Serverless-2016-10-31
Description: >
  sam-app

  Sample SAM Template for sam-app
  
Globals:
  Function:
    Timeout: 3
  Api:
    Cors:
      AllowMethods: "'OPTIONS,PUT'"
      AllowHeaders: "'Content-Type,X-Amz-Date,Authorization,X-Api-Key,X-Amz-Security-Token'"
      AllowOrigin: "'*'"
  
Resources:

  HelloWorldFunction:
    Type: AWS::Serverless::Function
    Properties:
      CodeUri: hello-world/
      Handler: app.lambdaHandler
      Runtime: nodejs12.x
      Events:
        HelloWorld:
          Type: Api
          Properties:
            Path: /hello-world
            Method: put

Outputs:
  HelloWorldApi:
    Description: "API Gateway endpoint URL for Prod stage for Hello World function"
    Value: !Sub "https://${ServerlessRestApi}.execute-api.${AWS::Region}.amazonaws.com/dev/hello-world/"
  HelloWorldFunction:
    Description: "Hello World Lambda Function ARN"
    Value: !GetAtt HelloWorldFunction.Arn
  HelloWorldFunctionIamRole:
    Description: "Implicit IAM Role created for Hello World function"
    Value: !GetAtt HelloWorldFunctionRole.Arn

I probably don't understand the intended workflow behind this. Why have 2 stage names when the lambda function the API Gateway is pointing to, is the same?

I will have 'dev' and 'prod' environments but they will use different stack names so I can never mix up the different environments.

I always use deploy-dev.sh and deploy-pod.sh scripts that check if I'm on the development or master (production) branch before actually deploying something. So those scripts would point to a different template.yaml files because they are called from different git branches. I'm using this way for deployment already for a long time and it works well for me.

On a side note: Why the existing stage names start with a capital? It looks so ugly and unusual.

like image 917
Eric Dela Cruz Avatar asked Jun 23 '20 13:06

Eric Dela Cruz


People also ask

How does Sam deploy work?

Deploys an AWS SAM application. By default when you use this command, the AWS SAM CLI assumes that your current working directory is your project's root directory. The AWS SAM CLI first tries to locate a template file built using the sam build command, located in the .

Does AWS Sam use CloudFormation?

AWS SAM is an extension of AWS CloudFormation, so you get the reliable deployment capabilities of CloudFormation. You can also define resources using CloudFormation in your SAM template and use the full suite of resources, intrinsic functions, and other template features that are available in AWS CloudFormation.

How does AWS Sam work?

How does AWS SAM work? AWS SAM provides shorthand syntax to express functions, APIs, databases, and event source mappings. During deployment, SAM transforms and expands the SAM syntax into AWS CloudFormation syntax. Then, CloudFormation provisions your resources with reliable deployment capabilities.


2 Answers

So I found my own answer which is sort of a combination of two answers to the questions I found on StackOverflow that are mentioned in my question.

I still don't understand why this is so needlessly complicated.

I added a parameter to the top level of the template.yaml file. The use of a parameter is not strictly needed. I added this so I can have a single template file that is called from both my deploy-dev.sh and deploy-prod.sh scripts. Below is the parameter declaration:

Parameters:
  Stage:
    Type: String
    Default: dev

Then, under the Resources group, I added a new ApiDeployment resource. The name you use is totally up to you as long as you use the exact same name elsewhere as a !Ref. The only reason to add this resource is that you are not allowed to simply use the StageName in the properties of the Api section of the function event. You are also not allowed to put StageName in the Globals Api section.

ApiDeployment:
  Type: AWS::Serverless::Api
    Properties:
      StageName: !Ref Stage <- this is referencing the parameter but it could be a fixed value

Then, under the Events section of the Lambda function I added a property RestApiId that is referencing the ApiDeployment resource. The last line in the block below.

HelloWorldFunction:
    Type: AWS::Serverless::Function
    Properties:
      CodeUri: hello-world/
      Handler: app.lambdaHandler
      Runtime: nodejs12.x
      Events:
        HelloWorld:
          Type: Api
          Properties:
            Path: /hello-world
            Method: put
            RestApiId: !Ref ApiDeployment

As I mentioned in my question, I got errors complaining about the output section of the yaml file. It turns out that the output section is optional anyway. So when I commented it out, everything worked.

But I used the output section in my deploy script to show me the URL of the API Gateway so with some trying I got that working too. The error was caused in the 4th line. It originally had ${ServerlessRestApi}. Just replace it with the new resource name I added to the yaml file: ${ApiDeployment} and everything is fine.

Outputs:
  ApiDeployment:
    Description: "API Gateway endpoint URL for Prod stage for Hello World function"
    Value: !Sub "https://${ApiDeployment}.execute-api.${AWS::Region}.amazonaws.com/${Stage}/hello-world/"
  HelloWorldFunction:
    Description: "Hello World Lambda Function ARN"
    Value: !GetAtt HelloWorldFunction.Arn
  HelloWorldFunctionIamRole:
    Description: "Implicit IAM Role created for Hello World function"
    Value: !GetAtt HelloWorldFunctionRole.Arn

Because I use a parameter in the yaml file, you need to call sam deploy with the parameter name and value. The exact syntax for this is, like a lot of AWS's documentation, very well hidden. So below is the way you start your deployment:

sam deploy --parameter-overrides "ParameterKey=Stage,ParameterValue=dev"

You probably still have the Stage stage in the API Gateway console under Stages but you can delete that without any repercussions.

For completeness here is my full template.yaml file which is, by the way, the file you get when you do sam init

AWSTemplateFormatVersion: '2010-09-09'
Transform: AWS::Serverless-2016-10-31
Description: >
      sam-app: Sample SAM Template for sam-app

Parameters:
  Stage:
    Type: String
    Default: dev

Globals:
  Function:
    Timeout: 3
  Api:
    Cors:
      AllowMethods: "'OPTIONS,PUT'"
      AllowHeaders: "'Content-Type,X-Amz-Date,Authorization,X-Api-Key,X-Amz-Security-Token'"
      AllowOrigin: "'*'"
  
Resources:
  HelloWorldFunction:
    Type: AWS::Serverless::Function
    Properties:
      CodeUri: hello-world/
      Handler: app.lambdaHandler
      Runtime: nodejs12.x
      Events:
        HelloWorld:
          Type: Api
          Properties:
            Path: /hello-world
            Method: put
            RestApiId: !Ref ApiDeployment
  ApiDeployment:
    Type: AWS::Serverless::Api
    Properties:
      StageName: !Ref Stage

Outputs:
  ApiDeployment:
    Description: "API Gateway endpoint URL for Prod stage for Hello World function"
    Value: !Sub "https://${ApiDeployment}.execute-api.${AWS::Region}.amazonaws.com/${Stage}/hello-world/"
  HelloWorldFunction:
    Description: "Hello World Lambda Function ARN"
    Value: !GetAtt HelloWorldFunction.Arn
  HelloWorldFunctionIamRole:
    Description: "Implicit IAM Role created for Hello World function"
    Value: !GetAtt HelloWorldFunctionRole.Arn
like image 185
Eric Dela Cruz Avatar answered Oct 11 '22 11:10

Eric Dela Cruz


This way creates only your specified stage instead of creating one more stage named Stage.

This setup did trick.

Globals:
Api: OpenApiVersion: 3.0.1

I've also created the new AWS::Serverless::Api named RestApi to overwrite implicit ServerlessRestApi. Remember to set RestApi into RestApiId of every API event.

template.yaml

    AWSTemplateFormatVersion: 2010-09-09
    Description: >-
      app-sam
    
    
    Transform:
    - AWS::Serverless-2016-10-31
    
    # ====================================
    # PARAMETERS SETUP
    # ====================================
    Parameters:
      StageName:
        Type: String
        Default: dev
        Description: (Required) Enter dev, prod. Default is dev.
        AllowedValues:
          - dev
          - prod
      ProjectName:
        Type: String
        Default: sam-api
        Description: (Required) The name of the project
        MinLength: 3
        MaxLength: 50
        AllowedPattern: ^[A-Za-z_-]+$
        ConstraintDescription: "Required. Can be characters, hyphen, and underscore only. No numbers or special characters allowed."
      ExistingTable:
        Type: String
        Default: example-table
        Description: (Required) The name of existing DynamoDB
        MinLength: 3
        MaxLength: 50
        AllowedPattern: ^[A-Za-z_-]+$
        ConstraintDescription: "Required. Can be characters, hyphen, and underscore only. No numbers or special characters allowed."
    
    
    # ====================================
    # GLOBAL SETUP
    # ====================================
    Globals:
      Api:
        OpenApiVersion: 3.0.1
      Function:
        Runtime: nodejs14.x
        Timeout: 180
        MemorySize: 256
        Environment:
          Variables:
            TABLE_NAME: !Ref ExistingTable
    
    Resources:
      # Reference this one to overwrite implicit stage
      # https://github.com/aws/serverless-application-model/issues/191#issuecomment-580412747 
      RestApi:
        Type: AWS::Serverless::Api
        Properties:
          Name: !Ref ProjectName
          StageName: !Ref StageName
          
      # This is a Lambda function config associated with the source code: get-all-items.js
      getAllItemsFunction:
        Type: AWS::Serverless::Function
        Properties:
          Handler: src/handlers/get-all-items.getAllItemsHandler
          Description: A simple example includes a HTTP get method to get all items from a DynamoDB table.
          Policies:
            # Give Create/Read/Update/Delete Permissions to the ExistingTable
            - DynamoDBCrudPolicy:
                TableName: !Ref ExistingTable
          Events:
            Api:
              Type: Api
              Properties:
                Path: /
                Method: GET
                RestApiId: !Ref RestApi
      
    
    Outputs:
      WebEndpoint:
        Description: "API Gateway endpoint URL for Prod stage"
        Value: !Sub "https://${RestApi}.execute-api.${AWS::Region}.amazonaws.com/${StageName}/"

like image 41
Long Nguyen Avatar answered Oct 11 '22 10:10

Long Nguyen