serverless: Deploy: pathmapping - Invalid stage identifier specified.

This is a Bug Report

Description

For bug reports:

  • What went wrong? - pathmapping failed, invalid stage identifier
  • What did you expect should have happened? - deployment
  • What was the config you used?

Pathmapping section

pathmapping:
      Type: AWS::ApiGateway::BasePathMapping
      Properties:
        BasePath: ${self:service}
        DomainName: ${self:custom.domains.${opt:stage, self:provider.stage}}
        RestApiId: 
          Ref: ApiGatewayRestApi
        Stage: ${opt:stage}

Produces this in the serverless-state.json

"resources": {
      "Resources": {
        "pathmapping": {
          "Type": "AWS::ApiGateway::BasePathMapping",
          "Properties": {
            "BasePath": "demolambda",
            "DomainName": "staging-api.<REDACTED>",
            "RestApiId": {
              "Ref": "ApiGatewayRestApi"
            },
            "Stage": "staging"
          }
        }
      }
  • What stacktrace or error message from your provider did you see?

Verbose logging of CloudFormation process:

CloudFormation - CREATE_FAILED - AWS::ApiGateway::BasePathMapping - pathmapping

Ending error:

An error occurred while provisioning your stack: pathmapping - Invalid stage identifier specified.

Additional Data

  • Serverless Framework Version you’re using: 1.17.0
  • Operating System: Linux (Ubuntu 14.04)
  • Stack Trace:
  • Provider Error messages:

About this issue

  • Original URL
  • State: closed
  • Created 7 years ago
  • Reactions: 17
  • Comments: 26 (1 by maintainers)

Commits related to this issue

Most upvoted comments

Closing since this is an old issue and a DependsOn does the trick here.

After some digging I figured out that the cause here was basically order of ops on the deploy. Since I was deploying a new stack APIGateway didn’t have a staging stage and it would try to run the pathmapping before the stage existed, hence Invalid Stage.

I saw a couple posts about adding a bogus DependsOn to force it to “wait” but that approach didn’t work so I was left doing 2 deploys; the first without pathmapping in the config to get the stage and Lambda functions created, followed by a second (and then all subsequent deploys) where pathmapping is present and the stage exists.

This doesn’t seem like a major issue since you can pretty easily get around it and once it’s fixed (and the stage exists) it doesn’t happen again, but would be nice to have a better way to get this working on new deploys.

@RPDeshaies I ran into the same issue. I’ve addressed it by declaring an additional Deployment resource in my serverless.yml file, and creating a DependsOn attribute for the BasePathMapping to depend on the Deployment resource. This forces the Stage resource to be created before the BasePathMapping:

    resApiGatewayDeployment:
      Type: "AWS::ApiGateway::Deployment"
      DependsOn: ApiGatewayMethodMyFunctionNameAny # This can be any one Method on the API
      Properties:
        Description: ${opt:stage} Environment
        RestApiId:
          Ref: ApiGatewayRestApi
        StageName: ${opt:stage}

    resApiBasePathMapping:
      Type: AWS::ApiGateway::BasePathMapping
      DependsOn: resApiGatewayDeployment
      Properties:
        BasePath: ${opt:feature-branch, self:provider.environment.SERVICE_NAME} # Change this to whatever you want your base path to be
        DomainName: ${self:custom.environmentMappings.domain.${opt:stage}, self:custom.environmentMappings.domain.pr} # Change this to however you manage your domains
        RestApiId:
          Ref: ApiGatewayRestApi
        Stage: ${opt:stage}

This could be better addressed by the Serverless tools with any of the following solutions:

  1. If Serverless didn’t add a random generated identifier to the end of the generated Deployment resource, then you could add a DependsOn property to refer directly to the serverless Deployment resource;
  2. If Serverless generated the CFN to create the Stage resource instead of relying on the Deployment resource to force creation of the Stage resource, then you could add a DependsOn property to refer directly to the Stage resource.
  3. If Serverless created any kind of resource that was guaranteed to be the last resource created, then you could add any addition resources such as BasePathMapping to be dependent on the completion of all the serverless resources.

Ran into the same issue. It’s a real pity that due to a bug, the configuration needs to stop being simple and elegant. Serverless guys - please reopen it!!!

For anyone still hitting this I believe you can do the following:

  ApiMapping:
    Type: AWS::ApiGateway::BasePathMapping
    DependsOn: "ApiGatewayDeployment${sls:instanceId}"
    Properties:
      DomainName:
        Ref: CustomDomain
      RestApiId:
        Ref: ApiGatewayRestApi
      Stage: "${opt:stage, 'dev'}"

where the ${sls:instanceId} should be the random string that is appended to the serverless created deployment resource.

See https://serverless.com/framework/docs/providers/aws/guide/resources#aws-cloudformation-resource-reference for more details.

Closing since this is an old issue and a DependsOn does the trick here.

Workaround is not a solution from its definition. And as @veetikaleva said: is there any variable to just “depend on” without writing so much boilerplate code? i.e. creating simple service with one function and two stages should end in using serverless-domain-manager plugin without writing so much “resources” by hand.

@pmuens Does Serverless provide a resource to depend on (for example, a variable with the API gateway deployment name)? I would not consider adding ugly hacks like fake resources “doing the trick”.

This is definitely not solved.

Though I am not using serverless, googling for the issue always points me to this Github issue. So, anyone stuck in this, I did the following.

  RandomApi:
    Type: AWS::Serverless::Api
    Properties:
      DefinitionBody:
        swagger: "2.0"
        info:
          title: !Sub ${AWS::StackName}
        schemes:
        - "https"
        paths:
        definitions:
        securityDefinitions:
      StageName: Prod

  RandomMapping:
    Type: 'AWS::ApiGateway::BasePathMapping'
    DependsOn:
    - RandomApiProdStage
    Properties:
      BasePath: randoms
      DomainName:
        Fn::ImportValue:
          Fn::Sub: ${Environment}-SomeDomainName
      RestApiId: !Ref RandomApi
      Stage: Prod

As you can see, the DependsOn takes the value RandomApiProdStage. RandomApiProdStage equals to {ApiGateway}{StageName}Stage. If you deploy without BasePathMapping then, you can see under the Events tab in the CloudFormation that RandomApiProdStage is automatically created by CloudFormation. So, what I did after observing the events in CloudFormation was I added RandomApiProdStage as DependsOn value in the BasePathMapping. I hope this helps.

Please guide me if there is any better way to do it.

Got into the same issue a few days ago. I tried to create multiple resources and run deployment few times, but honestly if you using ci/cd it’s not a choice. I have ended up with defining dependencies in the specific order… here is serverless.yml example:

service: lambda-APITest

custom:
  stage: ${opt:stage, 'beta'}

provider:
  name: aws
  runtime: python3.6
  region: ${opt:region, 'us-west-2'}
  stackName: "${self:custom.stage}-${self:service}"
  role: { Fn::GetAtt: ["Role","Arn"] }
  endpointType: regional

functions:
  App:
    name: ${self:provider.stackName}
    description: "ApiTest [${self:provider.stackName}]"
    handler: lambda.handler
    package:
      include: [ "lambda.py" ]
    events:
    - http:
        path: ""
        method: get

package:
  individually: true
  exclude: [ "./**" ]

resources:
  Description: "Serverless: Test Api App"

  Mappings: ${file(./mappings-values.yml)}

  Resources:

    Domain:
      Type: AWS::ApiGateway::DomainName
      Properties:
        DomainName: { Fn::FindInMap : [ Domains, "${self:custom.stage}", "domain" ] }
        EndpointConfiguration:
          Types: [ "REGIONAL" ]
        RegionalCertificateArn:  { Fn::FindInMap : [ Certs, "${self:custom.stage}", "Arn" ] }
      DependsOn: AppLambdaFunction

    ApiGatewayRestApi:
      Type: AWS::ApiGateway::RestApi
      Properties:
        Name: ${self:provider.stackName}
      DependsOn: AppLambdaFunction

    ApiGatewayDeployment:
      Type: "AWS::ApiGateway::Deployment"
      Properties:
        Description: "${opt:stage} Environment"
        RestApiId: { Ref: ApiGatewayRestApi }
        StageName: "${self:custom.stage}"
      DependsOn: [ Domain ]

    BasePathMapping:
      Type: AWS::ApiGateway::BasePathMapping
      Properties:
        DomainName: { Fn::FindInMap : [ Domains, "${self:custom.stage}", "domain" ] }
        RestApiId: { Ref: ApiGatewayRestApi }
        Stage: "${self:custom.stage}"
      DependsOn: ApiGatewayDeployment

    Role:
      Description: "Role [${self:provider.stackName}]"
      Type: "AWS::IAM::Role"
      Properties:
        ManagedPolicyArns: [ Ref: Policy ]
        RoleName: "${self:provider.stackName}"
        AssumeRolePolicyDocument:
          Version: '2012-10-17'
          Statement:
          - Effect: Allow
            Principal:
              Service: [ "lambda.amazonaws.com" ]
            Action: [ "sts:AssumeRole" ]

    Policy:
      Description: "Policy [${self:provider.stackName}]"
      Type: "AWS::IAM::ManagedPolicy"
      Properties:
        ManagedPolicyName: ${self:provider.stackName}
        PolicyDocument:
          Version: '2012-10-17'
          Statement:

          - Effect: Allow
            Action:
            - logs:CreateLogGroup
            - logs:CreateLogStream
            - logs:PutLogEvents
            Resource: [ "arn:aws:logs:*:*:*" ]

And all important things is just to create order Lambda -> (Domain & RestApi) -> Deployment -> BasePathMapping

The trick is to create fake deployment and ask serverless to wait for it. BasePathMapping will wait and catch actual Serverless generated Deployment.

Adding the following block on the AWS::ApiGateway::BasePathMapping:

DependsOn:
        - "ApiGatewayDeployment${sls:instanceId}"
        - ApiGatewayDomainName

(where ApiGatewayDomainName is my own logical ID for AWS::ApiGateway::DomainName) finally did the trick for me. Thank you @ghost (whoever you were).

I can finally get rid of sls-domain-manager plugin.


Do note that solution which @hle-aashish provided two comments above is valid but not for SLS Framework! That code is for Serverless Application Model (SAM) which is completely different framework.

@michaelj-smith I apologize for the confusion. By “does not work for me” I mean “is not acceptable.” It works, but I can’t put this in production. I’m going back to the “serverless create_domain” method.

Thanks though!

@Fluidbyte what do you mean by

the first without pathmapping in the config

Would you mine providing an example ? I’m having the same issue right now and I’m trying to fix it 😃

EDIT: nvm that. I understand now. Have you ever encountered that issue afterwards ? I’m getting that problem even though my API Gateways and lamdbas are created.

@michaelj-smith We haven’t yet run into the issue you’re describing and that could be because we’re using Serverless Framework, not SAM. Thanks for the heads up just in case!

For anyone still hitting this I believe you can do the following:

  ApiMapping:
    Type: AWS::ApiGateway::BasePathMapping
    DependsOn: "ApiGatewayDeployment${sls:instanceId}"
    Properties:
      DomainName:
        Ref: CustomDomain
      RestApiId:
        Ref: ApiGatewayRestApi
      Stage: "${opt:stage, 'dev'}"

where the ${sls:instanceId} should be the random string that is appended to the serverless created deployment resource.

See https://serverless.com/framework/docs/providers/aws/guide/resources#aws-cloudformation-resource-reference for more details.

Using DependsOn: "ApiGatewayDeployment${sls:instanceId}" also did not solve the issue. Using the plugins is an option but it should be resolved by serverless deploy itself since it is a common case to create a custom domain with base path mapping.

@bisaacson If you post your yaml, I’ll review it and help if I can.