Question:
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.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 |
{ "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.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
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.
1 2 |
Another resource with the same parent already has this name |
Answer:
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.
1 2 3 4 5 6 7 8 9 |
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
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 |
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)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
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)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
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 🙂