Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Serverless Shared API Gateway Error when deploying to different stages

I'm using serverless version 1.29.2

I have a created an initial cloudformation script that creates an API GateWay REST API that will be used by other services. So Here is the cloudformation script responsible for it.

{
   "AWSTemplateFormatVersion":"2010-09-09",
   "Description":"API",
   "Resources":{
      "APIGw":{
         "Type":"AWS::ApiGateway::RestApi",
         "Properties":{
            "Name":"API-GW"
         }
      }
   },
   "Outputs":{
      "ApiGwRestApiId":{
         "Value":{
            "Ref":"APIGw"
         },
         "Export":{
            "Name":"apigw-restApiId"
         }
      },
      "eyesApiGwRestApiRootResourceId":{
         "Value":{
            "Fn::GetAtt":[
               "APIGw",
               "RootResourceId"
            ]
         },
         "Export":{
            "Name":"apigw-rootResourceId"
         }
      }
   }
}

Here is serverless.yml for the application I was trying to deploy.

service: template-test-service

provider:
  name: aws
  runtime: python3.6
  region: eu-central-1
  stage: ${self:custom.environment.stage}
  environment:
    stage: ${self:custom.environment.stage}
  apiGateway:
    restApiId:
      'Fn::ImportValue': apigw-restApiId
    restApiRootResourceId:
      'Fn::ImportValue': apigw-rootResourceId

When I perform an sls deploy --stage dev everything works fine, However when I perform another deploy to sls deploy --stage prod

This error shows up.

Another resource with the same parent already has this name
like image 695
KyelJmD Avatar asked Mar 06 '23 07:03

KyelJmD


1 Answers

I've struggled with this one for a week now and the issue is to do with the way API Gateway is constructred out of resources and methods. From the documentation

In Amazon API Gateway, you build a REST API as a collection of programmable entities known as API Gateway resources. For example, you use a RestApi resource to represent an API that can contain a collection of Resource entities. Each Resource entity can in turn have one or more Method resources. Expressed in the request parameters and body, a Method defines the application programming interface for the client to access the exposed Resource and represents an incoming request submitted by the client.

Serverless CLI creates all the resource/methods for you when you have a function trigged by an http event.

functions:
  GetScenesInGame:
    handler: handler.hello
    layers: arn:aws:lambda:eu-west-1:xxxxxxxxx:layer:pynamodb-layer:1
    events:
      - http:
          method: GET
          path: api/v1/game/{gameId}/scene

From the example above this creates five resources ( api, v1, game, gameIdParam, scene) and finally adds a GET method on the the final resource.

Unfortunately when you have two seperate stacks (as you might in a microservice setup) if they are any part of the above methods then it errors with Another resource with the same parent already has this name

The solution is highlighted in this article from serverless deploy serverless microservice on aws although its not very explict and easily missed.

Firstly there is a top level cloudformation template which configures the required resources.

For the resource you want to add a serverless microservice to you export the id of the resource as an output variable in your stack.

Then in the serverless.yml file you import the api gateway reference and apigateway resource id.

You can then deploy each service without getting a clash of resource names in the api structure.

The templates below show having a top level set of resources.

api/v1/game/{gameId}/page

api/v1/game/{gameId}/scene

And then attaching PageService to the page resource and SceneService to the scene resource.

api-gateway.yml

    AWSTemplateFormatVersion: "2010-09-09"
Description: "S3 template for deploying S3 to be used by ACS s3 connector."

Resources:
    TestApiGw:
            Type: AWS::ApiGateway::RestApi
            Properties:
                Name: !Sub 'test-apigw-throwaway'

    ApiResource:
        Type: AWS::ApiGateway::Resource
        Properties:
            RestApiId: !Ref TestApiGw
            ParentId: !GetAtt 
                - TestApiGw
                - RootResourceId
            PathPart: "api"

    VersionResource:
        Type: AWS::ApiGateway::Resource
        Properties:
            RestApiId: !Ref TestApiGw
            ParentId: !Ref ApiResource
            PathPart: "v1"

    GameResource:
        Type: AWS::ApiGateway::Resource
        Properties:
            RestApiId: !Ref TestApiGw
            ParentId: !Ref VersionResource
            PathPart: "game"

    GameParamResource:
        Type: AWS::ApiGateway::Resource
        Properties:
            RestApiId: !Ref TestApiGw
            ParentId: !Ref GameResource
            PathPart: "{gameId}"

    SceneResource:
        Type: AWS::ApiGateway::Resource
        Properties:
            RestApiId: !Ref TestApiGw
            ParentId: !Ref GameParamResource
            PathPart: "scene"

    PageResource:
            Type: AWS::ApiGateway::Resource
            Properties:
                RestApiId: !Ref TestApiGw
                ParentId: !Ref GameParamResource
                PathPart: "page"

Outputs:
    ApiRestApiId:
        Value: !Ref TestApiGw
        Export:
            Name: !Sub ${AWS::StackName}-TestApiId

    ApiRootResourceId:
        Value:
            Fn::GetAtt:
                - TestApiGw
                - RootResourceId
        Export:
            Name: !Sub ${AWS::StackName}-ApiRootResourceVar

    ApiSceneResourceVar:
        Value: !Ref SceneResource
        Export:
        # variable names are global so this will need more work to make it unique across stages.
            Name: !Sub ${AWS::StackName}-ApiSceneResourceVar

    ApiPageResourceVar:
        Value: !Ref PageResource
        Export:
        # variable names are global so this will need more work to make it unique across stages.
            Name: !Sub ${AWS::StackName}-ApiPageResourceVar

serverless.yml ( Serverless Cli file for Page Service)

service: scrap-page-service

provider:
  name: aws
  runtime: python2.7
  apiGateway:
    restApiId: 
      "Fn::ImportValue": throw-stack-1-TestApiId
    restApiRootResourceId: 
      "Fn::ImportValue": throw-stack-1-ApiPageResourceVar

functions:
  hello:
    handler: handler.hello
    events:
      - http:
          path: ""
          method: get

serverless.yml ( Serverless Cli file for Scene Service)

service: scrap-scene-service

provider:
  name: aws
  runtime: python2.7
  apiGateway:
    restApiId: 
      "Fn::ImportValue": throw-stack-1-TestApiId
    restApiRootResourceId: 
      "Fn::ImportValue": throw-stack-1-ApiSceneResourceVar

functions:
  hello:
    handler: handler.hello
    events:
      - http:
          path: ""
          method: get

Hopefully this helps others getting this issue and if somebody has a better way of doing it I'd be keen to know :-)

like image 141
Alex Ward Avatar answered May 05 '23 01:05

Alex Ward