serverless: Error provisioning Usage: API Stage not found: 63zybslnqk:demo.

This is a Bug Report

Description

I hope to deploy and remove a stack as a part of our CI environment. The stack includes an Api Gateway with a Usage Plan. Creating the Api Gateway and Usage Plan together fails. If I break up the process over two deploys and firstly comment out the Cloudformation directive specific to Usage Plan the creation of the stack is successful. Following that I remove the commented Usage Plan configuration and deploy again, the deployment works with the included UsagePlan.

The workaround of multiple deployments for new a stack is not optimal.

  • What went wrong? Deploying a brand new stack with API Gateway and a Usage Plan fails.

  • What did you expect should have happened? For the CloudFormation to honor the UsagePlan directive

  • What was the config you used?

service: usage-plan-fail

provider:
  name: aws
  runtime: nodejs4.3
  stage: demo

functions:
  hello:
    handler: handler.hello
    events:
      - http:
          path: usage
          method: get
          private: true

resources:
  Resources:
    UsagePlan:
      Type: AWS::ApiGateway::UsagePlan
      Properties:
        ApiStages:
        - ApiId:
            Ref: "ApiGatewayRestApi"
          Stage: ${self:provider.stage}
        Description: ${self:provider.stage} usage plan
        UsagePlanName: "${self:service}-${self:provider.stage}"
  • What stacktrace or error message from your provider did you see?
CloudFormation - CREATE_FAILED - AWS::ApiGateway::UsagePlan - UsagePlan

Similar or dependent issues:

Additional Data

  • Serverless Framework Version you’re using: 1.2.1
  • Operating System: OS X
  • Stack Trace:
$ sls deploy -v
Serverless: Creating Stack...
Serverless: Checking Stack create progress...
CloudFormation - CREATE_IN_PROGRESS - AWS::CloudFormation::Stack - usage-plan-fail-demo
CloudFormation - CREATE_IN_PROGRESS - AWS::S3::Bucket - ServerlessDeploymentBucket
CloudFormation - CREATE_IN_PROGRESS - AWS::S3::Bucket - ServerlessDeploymentBucket
CloudFormation - CREATE_COMPLETE - AWS::S3::Bucket - ServerlessDeploymentBucket
CloudFormation - CREATE_COMPLETE - AWS::CloudFormation::Stack - usage-plan-fail-demo
Serverless: Stack create finished...
Serverless: Packaging service...
Serverless: Uploading CloudFormation file to S3...
Serverless: Uploading service .zip file to S3 (733 B)...
Serverless: Updating Stack...
Serverless: Checking Stack update progress...
CloudFormation - UPDATE_IN_PROGRESS - AWS::CloudFormation::Stack - usage-plan-fail-demo
CloudFormation - CREATE_IN_PROGRESS - AWS::ApiGateway::RestApi - ApiGatewayRestApi
CloudFormation - CREATE_IN_PROGRESS - AWS::IAM::Role - IamRoleLambdaExecution
CloudFormation - CREATE_IN_PROGRESS - AWS::IAM::Role - IamRoleLambdaExecution
CloudFormation - CREATE_IN_PROGRESS - AWS::ApiGateway::RestApi - ApiGatewayRestApi
CloudFormation - CREATE_COMPLETE - AWS::ApiGateway::RestApi - ApiGatewayRestApi
CloudFormation - CREATE_IN_PROGRESS - AWS::ApiGateway::UsagePlan - UsagePlan
CloudFormation - CREATE_FAILED - AWS::ApiGateway::UsagePlan - UsagePlan
CloudFormation - CREATE_IN_PROGRESS - AWS::ApiGateway::Resource - ApiGatewayResourceUsage
CloudFormation - CREATE_IN_PROGRESS - AWS::ApiGateway::Resource - ApiGatewayResourceUsage
CloudFormation - CREATE_COMPLETE - AWS::ApiGateway::Resource - ApiGatewayResourceUsage
CloudFormation - CREATE_FAILED - AWS::IAM::Role - IamRoleLambdaExecution
CloudFormation - UPDATE_ROLLBACK_IN_PROGRESS - AWS::CloudFormation::Stack - usage-plan-fail-demo
CloudFormation - UPDATE_ROLLBACK_COMPLETE_CLEANUP_IN_PROGRESS - AWS::CloudFormation::Stack - usage-plan-fail-demo
CloudFormation - DELETE_IN_PROGRESS - AWS::ApiGateway::Resource - ApiGatewayResourceUsage
CloudFormation - DELETE_COMPLETE - AWS::ApiGateway::UsagePlan - UsagePlan
CloudFormation - DELETE_COMPLETE - AWS::ApiGateway::Resource - ApiGatewayResourceUsage
CloudFormation - DELETE_IN_PROGRESS - AWS::IAM::Role - IamRoleLambdaExecution
CloudFormation - DELETE_IN_PROGRESS - AWS::ApiGateway::RestApi - ApiGatewayRestApi
CloudFormation - DELETE_COMPLETE - AWS::IAM::Role - IamRoleLambdaExecution
CloudFormation - DELETE_COMPLETE - AWS::ApiGateway::RestApi - ApiGatewayRestApi
CloudFormation - UPDATE_ROLLBACK_COMPLETE - AWS::CloudFormation::Stack - usage-plan-fail-demo
Serverless: Deployment failed!

  Serverless Error ---------------------------------------

     An error occurred while provisioning your stack: UsagePlan
     - API Stage not found: 63zybslnqk:demo.

  Get Support --------------------------------------------
     Docs:          docs.serverless.com
     Bugs:          github.com/serverless/serverless/issues

  Your Environment Information -----------------------------
     OS:                 darwin
     Node Version:       6.7.0
     Serverless Version: 1.2.1

  • Provider Error messages:
     An error occurred while provisioning your stack: UsagePlan
     - API Stage not found: 63zybslnqk:demo.

About this issue

  • Original URL
  • State: closed
  • Created 8 years ago
  • Comments: 17 (10 by maintainers)

Most upvoted comments

If you guys still experiencing this error, here is my solution:

  • Create user in the AWS IAM. Create a group of that user and assign full admin access.
  • Get the access key id and secret key under the ‘Security Credentials’ tab of the user in AWS IAM. Option 1: If deploying using CircleCI: * Set the AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY in the ‘Environment Variables’ in CircleCI for that service you like to deploy. Option 2: If deploying using terminal on your local machine: * Set the key id and secret key to your /.aws credentials file on local PC. [default] aws_access_key_id = *****************ABC aws_secret_access_key = *********************************************eFG

As mentioned by @agouz this helps, however when I tried it serverless was generating 2 usage plans:

"UsagePlan": {
  "Type": "AWS::ApiGateway::UsagePlan",
    "DependsOn": [
      "ApiGatewayStage"
    ],
  ...

"ApiGatewayUsagePlan": {
  "Type": "AWS::ApiGateway::UsagePlan",
  "DependsOn": "ApiGatewayDeployment1551923774977",
  ...

The 2nd plan was unexpected and was always failing as it entered on a race condition with the APIStage. For me the solution was to rename my UsagePlan from “UsagePlan” to “ApiGatewayUsagePlan”. This way serverless generated only one and with the right DependsOn. The only way to find about this was to check the generated template.

serverless v1.38.0

Adding both the stage and plan to your resources + dependsOn solved for me

resources:
  Resources:
    onlyBody: 
      Type: "AWS::ApiGateway::RequestValidator" 
      Properties: 
        Name: 'only-body' 
        RestApiId: 
          Ref: ApiGatewayRestApi 
        ValidateRequestBody: true 
        ValidateRequestParameters: false
    ApiGatewayStage:
      Type: AWS::ApiGateway::Stage
      Properties:
        RestApiId:
          Ref: ApiGatewayRestApi
        MethodSettings:
          - DataTraceEnabled: true
            HttpMethod: "*"
            LoggingLevel: ERROR
            ResourcePath: "/*"
            MetricsEnabled: true
    ApiGatewayUsagePlan:
      Type: AWS::ApiGateway::UsagePlan
      DependsOn:
        - "ApiGatewayStage"
      Properties:
        ApiStages:
        - ApiId:
            Ref: "ApiGatewayRestApi"
          Stage: ${self:provider.stage}
        Description: ${self:provider.stage} usage plan
        UsagePlanName: "${self:service}-${self:provider.stage}"

Edit: Updated the usage plan name based on @fschroder comment, thanks!

I’ve had a plugin to get around this, if there is interest I can package it.

Used in combination with the above mentioned serverless.yml with the following addition:

plugins:
  - usage-plan-injection-plugin

The plugin lives in .serverless_plugins/usage-plan-injection-plugin.js:

'use strict';

class UsagePlanInjectionPlugin {
  constructor(serverless, options) {
    this.serverless = serverless;
    this.options = options;
    this.provider = 'aws';

    if (this.options.noDeploy) {
      this.hooks = {
        'before:deploy:deploy': this.beforeDeployDeploy.bind(this),
      };
    } else {
      this.hooks = {
        'before:deploy:deploy': this.beforeDeployDeploy.bind(this),
        'before:remove:remove': this.beforeRemoveRemove.bind(this),
        'after:deploy:deploy': this.afterDeployDeploy.bind(this),
      };
    }
  }

  beforeDeployDeploy() {
    let resources = this.serverless.service.provider.compiledCloudFormationTemplate.Resources;
    let resourceKeys = Object.keys(resources);
    let apiGatewayDeployment;
    resourceKeys.forEach(resource => {
      if (resource.match(/ApiGatewayDeployment/)) {
        apiGatewayDeployment = resource;
      }
    });

    if (apiGatewayDeployment === undefined) {
      return;
    }

    if (resources['ApiGatewayRestApi'] === undefined) {
      return;
    }

    const userResources = this.serverless.service.resources.Resources;
    Object.keys(userResources).forEach(userResource => {
      if (userResources[userResource].Type !== 'AWS::ApiGateway::UsagePlan') {
        return;
      }

      if (this.serverless.service.resources.Resources[userResource].DependsOn === undefined) {
        return this.serverless.service.resources.Resources[userResource].DependsOn = [apiGatewayDeployment];
      }

      this.serverless.service.resources.Resources[userResource].DependsOn.push(apiGatewayDeployment);
    });
  }

  beforeRemoveRemove() {
    const apiGateway = new this.serverless.providers.aws.sdk.APIGateway(this.serverless.providers.aws.getCredentials());
    const cloudFormation = new this.serverless.providers.aws.sdk.CloudFormation(this.serverless.providers.aws.getCredentials());

    const stackName = this.serverless.providers.aws.naming.getStackName();

    let restApiId;
    let usagePlanId;
    let apiKeysIds = [];
    const stageName = this.options.stage || this.serverless.service.provider.stage;

    return cloudFormation.listStackResources({StackName: stackName}).promise()
      .then((resources) => {
        resources.StackResourceSummaries.forEach(resource => {
          if (resource.LogicalResourceId === "ApiGatewayRestApi") {
            restApiId = resource.PhysicalResourceId;
          }

          if (resource.ResourceType === "AWS::ApiGateway::UsagePlan") {
            usagePlanId = resource.PhysicalResourceId;
          }

          if (resource.ResourceType === "AWS::ApiGateway::ApiKey") {
            apiKeysIds.push(resource.PhysicalResourceId);
          }
        });
      })
      .then(() => {
        if (apiKeysIds.length > 0) {
          console.log(usagePlanId);
          const params = {
            keyId: apiKeysIds[0], // Only supporting one API Key.
          };

          return apiGateway.getUsagePlans(params).promise()
            .then(data => {
              let usagePlanExists = false;
              data.items.forEach(item => {
                if (item.id === usagePlanId) {
                  usagePlanExists = true;
                }
              });

              if (usagePlanExists) {
                return apiGateway.deleteUsagePlanKey({usagePlanId: usagePlanId, keyId: apiKeysIds[0]}).promise();
              }
            });
        }
      })
      .then(() => {
        return apiGateway.deleteStage({restApiId: restApiId, stageName: stageName}).promise();
      })
      .then(() => {
        return apiGateway.deleteUsagePlan({ usagePlanId: usagePlanId }).promise();
      });
  }

  afterDeployDeploy() {
    const apiGateway = new this.serverless.providers.aws.sdk.APIGateway(this.serverless.providers.aws.getCredentials());
    const cloudFormation = new this.serverless.providers.aws.sdk.CloudFormation(this.serverless.providers.aws.getCredentials());
    const stackName = this.serverless.providers.aws.naming.getStackName();
    let usagePlanId;
    let apiKeysIds = [];
    return cloudFormation.listStackResources({StackName: stackName}).promise()
      .then((resources) => {
        resources.StackResourceSummaries.forEach(resource => {
          if (resource.ResourceType === "AWS::ApiGateway::UsagePlan") {
            usagePlanId = resource.PhysicalResourceId;
          }

          if (resource.ResourceType === "AWS::ApiGateway::ApiKey") {
            apiKeysIds.push(resource.PhysicalResourceId);
          }
        });
      })
      .then(() => {
        const params = {
          keyId: apiKeysIds[0], // Only supporting one API Key.
        };

        // Would prefer to use getUsagePlanKey but it doesn't behave as expected.
        return apiGateway.getUsagePlans(params).promise();
      })
      .then(data => {
        let createUsagePlanKey = true;
        data.items.forEach(item => {
          if (item.id === usagePlanId) {
            createUsagePlanKey = false;
          }
        });

        if (!createUsagePlanKey) {
          return;
        }

        const params = {
          keyId: apiKeysIds[0],
          keyType: 'API_KEY',
          usagePlanId: usagePlanId,
        };
        return apiGateway.createUsagePlanKey(params).promise();
      })
      .catch(err => {
        console.error("APIGateway error: ", err);
      });
  }
}

module.exports = UsagePlanInjectionPlugin;

You closed an issue to keep the issue count low, despite the fact that this is still an issue? Maybe better to actually solve the problem to close the issue rather than artificially deflate your issue count.