Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[apigateway] ability to add methods and resources to an imported RestApi #1477

Closed
tjsteinhaus opened this issue Jan 3, 2019 · 27 comments · Fixed by #8270
Closed

[apigateway] ability to add methods and resources to an imported RestApi #1477

tjsteinhaus opened this issue Jan 3, 2019 · 27 comments · Fixed by #8270
Assignees
Labels
@aws-cdk/aws-apigateway Related to Amazon API Gateway effort/medium Medium work item – several days of effort feature-request A feature should be added or improved. in-progress This issue is being actively worked on.

Comments

@tjsteinhaus
Copy link

I currently have a Stack for both my Lambda's and API Gateway methods, but I'm now hitting my resource limit. By creating a new Stack I currently don't see the ability to access/use an existing api gateway, but instead it creates a new api instead.

Is there a way to access these gateways from other stacks?

@rix0rrr
Copy link
Contributor

rix0rrr commented Jan 4, 2019

You should be able to use the export/import mechanism

const restApiImportProps = restApi.export();

// ...

const api = apigw.RestApi.import(stack2, 'APIGW', restApiImportProps);

@rix0rrr rix0rrr added response-requested Waiting on additional info and feedback. Will move to "closing-soon" in 7 days. @aws-cdk/aws-apigateway Related to Amazon API Gateway labels Jan 4, 2019
@tjsteinhaus
Copy link
Author

tjsteinhaus commented Jan 4, 2019

So I just tried this approach this morning and this is the error that I receive.

UnhandledPromiseRejectionWarning: TypeError: Cannot read property 'addResource' of undefined

I was looking through the source code and I see that they do not exist in the ImportedRestApi class, only in the RestAPI class. Is this by design? If so, I'm assuming this means that it's a readonly state so I can't continue to add routes from another stack? Or is this portion incomplete?

Just seems to be an inconvenience, not really with the CDK but more with CloudFormation's limit of 200 resources.

@rix0rrr rix0rrr added bug This issue is a bug. and removed response-requested Waiting on additional info and feedback. Will move to "closing-soon" in 7 days. labels Jan 6, 2019
@rix0rrr
Copy link
Contributor

rix0rrr commented Jan 6, 2019

Thanks for trying, I'll classify this as a bug for now until we can get around to fixing it.

@eladb
Copy link
Contributor

eladb commented Jan 7, 2019

@tjsteinhaus indeed it is currently impossible to modify the model of a RestApi resource that was defined in another stack. This is why import returns an IRestApi, which doesn't have any of the addXxx methods. This is indeed a limitation of the current CDK implementation, which we should consider to revisit.

I am wondering, though, how did the typescript compiler let you use the api object returned from import in such a way that resulted in the above error? Can you share your code?

@eladb eladb changed the title Access API Gateway from another stack apigateway: unable to modify API model of an imported RestApi Jan 7, 2019
@eladb eladb added feature-request A feature should be added or improved. and removed bug This issue is a bug. labels Jan 7, 2019
@tjsteinhaus
Copy link
Author

@eladb I'm not using Typescript but just good ol' plain Javascript.

But I can show you an abbreviated version of what we're doing.

Stack 2

var api = new apigw.RestApi(this, 'myacctapi');
this.apiexport = api.export();

Stack 6

class MyStack6 extends cdk.Stack {
	constructor(parent, id, props) {
		super(parent, id, props);
		var api = apigw.RestApi.import(this, 'myacctapi', props.apiexports );
...

MyApp CDK Main Class

...
// API-gw and the backend lambda's
		var dev2 = new MyStack2(this, 'myaccount-p2', {
			...
		});

...
var dev6 = new MyStack6(this, 'myaccount-p6', {
			apiexports: dev2.apiexport,
			stack2: dev2,
			auth: dev2.auth
		});

Stack 6 is where we are trying to extend the api gateway instead of creating a new one.

@danielfariati
Copy link

This feature would be really useful for me as well.

I'm basically trying to use the same API Gateway for multiple microservices.
So, it would be nice if I could make a CDK stack register itself on an existing API Gateway, instead of manually configuring it every time a new microservice is added.

@eladb eladb self-assigned this Aug 12, 2019
@eladb eladb assigned nija-at and unassigned eladb Sep 3, 2019
@ggannio
Copy link

ggannio commented Sep 24, 2019

Does this have a release date already?

@zorrofox
Copy link
Contributor

zorrofox commented Oct 8, 2019

+1

@Poweranimal
Copy link

Poweranimal commented Oct 9, 2019

Did anyone of you mange to split the API Gateway resources into different stacks?
I'm facing the CloudFormation limit of 200 resources and I have a very hard time to separate my Api gateway into different stacks.

So far I was only able to put AWS::ApiGateway::Model and AWS::Lambda::Function and its dependencies in a separate stack. However, the major portion of my Api Gateway like AWS::ApiGateway:.Resource and so on must stay in the same stack in which the AWS::ApiGateway:.RestApi is defined.

Furthermore I faced a big problem regarding the AWS::Lambda::Function that I put in another stack: Since its references are exported and then imported by my Api Gateway stack, I'm not able to edit my Api's paths anymore. And if I use the latest released NestedStack resource (cdk version 1.12) I hit the stack input parameter limit of 60 very fast, since all of my Lambda's source code is uploaded to an S3 and cdk generates for each code resource that get uploaded to S3 three input parameters.

I really have to say that the most annoying thing of working with aws cdk are the resource limitations of AWS CloudFormation (e.g. 200 resource max in one stack and 60 parameters max in one stack). It cost so much time to work around such limitations...

I'd highly appreciate if someone could bring some light in the darkness. Maybe I'm missing something here...

@AlexRex
Copy link

AlexRex commented Oct 11, 2019

We're having the same issue here @Poweranimal. I think the best solution would be having one api-gateway in each stack together with the lambdas. Then if you have a custom domain map it to all the separated api-gateways.

@f-Stabby
Copy link

@AlexRex I also have this issue,

Like above, we have a large amount of lambdas that are related to a given api gateway, I have successfully split the lambdas into multiple stacks separate to the gateway, but am unable to figure out a way to split the gateway resources into separate stacks. I am concerned about scaling my API to be larger will hit the limit of the gateway stack, Any solutions for that?

@AmitBaranes
Copy link

Having the same issue here, Getting the 200 resources limit so I've tried to split it into 2 stacks, no luck so far.
Open a question in SOF maybe someone over there has an idea :
https://stackoverflow.com/questions/59393111/cdk-split-api-gateway-stack-into-2-small-stacks

@nija-at nija-at changed the title apigateway: unable to modify API model of an imported RestApi [apigateway] ability to add methods and resources to an imported RestApi Dec 19, 2019
@shafiqdanielAT
Copy link

shafiqdanielAT commented Dec 24, 2019

Edit: I've just realised that this might not correspond to the problem mentioned at the beginning. This will only help if the API Gateway resources alone does not exceed the 200 limit. If it does, then the solution by @AlexRex is the best one for now.

This is how we are doing it right now. We basically have multiple stack that share the same API Gateway class (RestApi)

class MultipleStackConstruct extends Construct {
  constructor(scope: Construct, id: string) {
    super(scope, id);

    // Main stack with shared components
    const commonStack = new CommonStack(
      scope,
      `common-stack`
    );

    // Module 1
    const moduleOneStack = new ModulOneStack(
      scope,
      `module-one`,
      {
        apiGatewayRestApi: commonStack.apiGatewayRestApi
      }
    );

    // Module 2, 3, etc.....
  }
}

This interface is used to pass the props to module stack:

export interface CommonProps extends cdk.StackProps {
  apiGatewayRestApi: apigw.RestApi;
}

The common module will create the API Gateway object:

export class CommonStack extends cdk.Stack {
  public readonly apiGatewayRestApi: apigw.RestApi;

  constructor(scope: cdk.Construct, id: string, props?: CommonProps) {
    super(scope, id, props);

    /******** SETUP API ********/
    this.apiGatewayRestApi = new apigw.RestApi(this, "MyAPI", {
      // Options here
    });
}

So the module stack itself will be something like this:

export class ModuleOneStack extends cdk.Stack {
  constructor(scope: cdk.Construct, id: string, props?: CommonProps) {
    super(scope, id, props);

    if (props && props.apiGatewayRestApi) {
      const apiGatewayRestApi = props.apiGatewayRestApi;
      
      // associate lambda with api gateway
    }
  }
}

In this case, we are using only one API Gateway with multiple Lambdas that are divided into multiple stack, because we've also encountered the limit problem.

There is a documentation from AWS that is doing the same thing using VPC:
https://docs.aws.amazon.com/cdk/api/latest/docs/aws-ec2-readme.html#sharing-vpcs-between-stacks

@nassahmed
Copy link

nassahmed commented Jan 13, 2020

Hi,
The issue seems to concern :

  • Lambda invoke permissions (AWS::Lambda::Permission)
  • Api resources (AWS::ApiGateway::Resource)
  • Api methods (AWS::ApiGateway::Method)
  • Api models (AWS::ApiGateway::Model)

This makes it quite easy to reach the limit with no way of decoupling the API from the stacks where the methods / resources / models / lambda invoke permissions are declared.
One possible work around is to export the RestApi ID and use Cfn constructs to build the API Gateway which defeats some of the purpose of using CDK.
Another possibility is to implement your own constructs.

@nija-at nija-at added the effort/medium Medium work item – several days of effort label Feb 25, 2020
@bomkamp
Copy link

bomkamp commented Mar 10, 2020

We are looking for the ability to deploy API Gateways & their resources as separate stacks. Currently, the RestApi.fromRestApiId() returns an IRestApi which does not allow use of many of the methods to create resources/methods like getRootResourceId, addMethod, addResource, getResourceByName etc.

We've tried using CfnMethod & CfnResource and are able to get it to work, but as stated in the comment above, it defeats the point of using CDK as lines as simple as:

const restApi: RestApi = RestApi.fromRestApiId(this, "RestApiGateway", 'id-here')
restApi.root.addResource('myLambda').addMethod('GET', new LambdaIntegration(myLambda));

turns into:

const apiGatewayRootResourceId: string = Fn.importValue(`${StackConfiguration.apiGatewayName}-root-resource-id`);
const apiGatewayId: string = Fn.importValue(`${StackConfiguration.apiGatewayName}-id`);

const route: CfnResource = new CfnResource(this, "ExampleResource", {
      restApiId: apiGatewayId,
      parentId: apiGatewayRootResourceId,
      pathPart: "myLambda",
});

const method: CfnMethod = new CfnMethod(this, "ExampleMethod", {
      httpMethod: HttpMethods.GET,
      resourceId: route.ref,
      restApiId: apiGatewayId,
      authorizationType: AuthorizationType.CUSTOM,
      authorizerId: authorizerId,
      integration: {
        type: IntegrationType.AWS_PROXY,
        uri: `arn:aws:apigateway:${Aws.REGION}:lambda:path/2015-03-31/functions/${myLambda.functionArn}/invocations`, 
        integrationHttpMethod: HttpMethods.POST,
      },
      operationName: "getMyLambdaResponse",
});

Forcing the use of imports/exports to pass root resource id & api gateway id. This allow disallows you to utilize defaultMethodOptions that are applied to the deployed gateway itself as you're required to provide the authorization type on the CfnMethod defined above.

@maxsap
Copy link

maxsap commented Mar 24, 2020

+1 on this, is there any update on it?

@Townsheriff
Copy link

I had same issue and since there is no answer, I'm posting my temporarily solution.

In short I'm recreating minimal context for addResource and addMethod methods, I don't know if other methods will work.

When using nested stacks pass parameters by calling exportRestApiResource and when you want to use gateway.Resource, call importRestApiResource.

Code for making: gateway.Resource work in NestedStack:

import * as gateway from "@aws-cdk/aws-apigateway";
import * as cdk from "@aws-cdk/core";
import * as assert from "assert";

interface RestApiResourceContext {
  restApi: RestApiImported;
  defaultCorsPreflightOptions: gateway.ResourceProps["defaultCorsPreflightOptions"];
  defaultMethodOptions: gateway.ResourceProps["defaultMethodOptions"];
  defaultIntegration: gateway.ResourceProps["defaultIntegration"];
  path: string;
  resourceId: string;
}

interface RestApiImportedProps {
  restApiId: string;
  latestDeployment: gateway.Deployment;
}

class RestApiImported extends cdk.Construct {
  restApiId: string;
  latestDeployment: gateway.Deployment;
  deploymentStage: gateway.Stage;

  constructor(scope: cdk.Construct, id: string, props: RestApiImportedProps) {
    super(scope, id);

    this.restApiId = props.restApiId;
    this.latestDeployment = props.latestDeployment;

    const stageName = 'prod';
    this.deploymentStage = {
      restApi: this.restApiId,
      stageName: 'prod',
      urlForPath: (path: string = '/'): string => {
        if (!path.startsWith('/')) {
          throw new Error(`Path must begin with "/": ${path}`);
        }

        return `https://${this.restApiId}.execute-api.${cdk.Stack.of(this).region}.${cdk.Stack.of(this).urlSuffix}/${stageName}${path}`;
      }
    } as any;
  }

  public _attachMethod() {

  };

  public arnForExecuteApi(method: string = '*', path: string = '/*', stage: string = '*') {
    if (!path.startsWith('/')) {
      throw new Error(`"path" must begin with a "/": '${path}'`);
    }

    if (method.toUpperCase() === 'ANY') {
      method = '*';
    }

    return cdk.Stack.of(this).formatArn({
      service: 'execute-api',
      resource: this.restApiId,
      sep: '/',
      resourceName: `${stage}/${method}${path}`
    });
  }
}

export class RestApiResource extends cdk.Construct implements RestApiResourceContext {
  public defaultCorsPreflightOptions: gateway.ResourceProps["defaultCorsPreflightOptions"];
  public defaultIntegration: gateway.ResourceProps["defaultIntegration"];
  public defaultMethodOptions: gateway.ResourceProps["defaultMethodOptions"];
  public path: string;
  public resourceId: string;
  public restApi: RestApiImported;

  constructor(scope: cdk.Construct, id: string, props: RestApiResourceContext) {
    super(scope, id);

    this.restApi = props.restApi;
    this.defaultCorsPreflightOptions = props.defaultCorsPreflightOptions;
    this.defaultMethodOptions = props.defaultMethodOptions;
    this.defaultIntegration = props.defaultIntegration;
    this.path = props.path;
    this.resourceId = props.resourceId;
  }

  public addResource(pathPart: string, options: gateway.ResourceOptions = {}): gateway.Resource {
    return gateway.ResourceBase.prototype.addResource.call(this, pathPart, options);
  }

  public addMethod(httpMethod: string, integration?: gateway.Integration, options: gateway.MethodOptions = {}): gateway.Method {
    return gateway.ResourceBase.prototype.addMethod.call(this, httpMethod, integration, options);
  }
}

export interface SerializedRestApiResource {
  restApiId: string;
  resourcePath: string;
  resourceId: string;
}

export function exportRestApiResource(
  resource: gateway.Resource
): SerializedRestApiResource {
  return {
    restApiId: resource.restApi.restApiId,
    resourceId: resource.resourceId,
    resourcePath: resource.path,
  };
}

export function importRestApiResource(
  stack: cdk.Stack,
  id: string,
  parameters: SerializedRestApiResource,
): RestApiResource {
  const restApiId = parameters.restApiId;
  const resourcePath = parameters.resourcePath;
  const resourceId = parameters.resourceId;

  assert(restApiId, "restApi is is falsy");
  assert(resourcePath, "resourcePath is is falsy");
  assert(resourceId, "resourceId is is falsy");

  return new RestApiResource(stack, `${id}-resource`, {
    restApi: new RestApiImported(stack, `${id}-imported`, {
      restApiId: restApiId,
      latestDeployment: new gateway.Deployment(stack, `${id}-deployment`, {
        api: gateway.RestApi.fromRestApiId(stack, `${id}-rest-api`, restApiId)
      }),
    }),
    path: resourcePath,
    resourceId: resourceId,
    defaultCorsPreflightOptions: {
      allowOrigins: gateway.Cors.ALL_ORIGINS,
      allowMethods: gateway.Cors.ALL_METHODS,
    },
    defaultMethodOptions: {},
    defaultIntegration: undefined,
  });
}

@fwippe
Copy link

fwippe commented Apr 24, 2020

+1

Is there any progress on this issue?

We're currently struggling with this issue as well. We've got several APIs relying on relatively large payload models, each of which is heavily documented to provide a comprehensive Swagger experience. Of course, every documentation part is counted towards the resource limit, so a limit of 200 resources is easy to exceed.

Without the means to use an imported RestApi (via RestApi.fromRestApiId()), we cannot separate models from methods, nor split the methods into multiple Stacks to circumvent resource limits.

@dsdavis4
Copy link

dsdavis4 commented May 6, 2020

+1

Any updates on this?

@moxue1989
Copy link

+1, would really love to have this feature. Putting everything in one stack is not ideal. and no great workarounds

@nija-at nija-at added the in-progress This issue is being actively worked on. label May 28, 2020
nija-at pushed a commit that referenced this issue May 29, 2020
The apigateway CDK construct library creates a large number of
CloudFormation resources.
To define a single Method that is backed with a Lambda function and
authorizer with an Authorizer, ~8 CloudFormation resources are created.
This quickly grows and a reasonable sized RestApi is bound to hit the
200 resource limit on their stack.

Re-organizing the CDK app into multiple `Stack`s that share the same
instance of `RestApi` will not resolve this problem, since the CDK
still makes an executive decision to keep the Methods and Resources in
the same stack as the `RestApi` construct. (see #7391)

This change introduces a new import style API `fromRestApiAttributes()`
that returns an instance of `IRestApi` that allows for new Resources to
be defined across stacks.

As a nice side effect, this change also adds the ability to define
Resources on SpecRestApi in addition to what has already been defined in
the OpenAPI spec.

closes #1477
nija-at pushed a commit that referenced this issue May 29, 2020
The apigateway CDK construct library creates a large number of
CloudFormation resources.
To define a single Method that is backed with a Lambda function and
authorizer with an Authorizer, ~8 CloudFormation resources are created.
This quickly grows and a reasonable sized RestApi is bound to hit the
200 resource limit on their stack.

Re-organizing the CDK app into multiple `Stack`s that share the same
instance of `RestApi` will not resolve this problem, since the CDK
still makes an executive decision to keep the Methods and Resources in
the same stack as the `RestApi` construct. (see #7391)

This change introduces a new import style API `fromRestApiAttributes()`
that returns an instance of `IRestApi` that allows for new Resources to
be defined across stacks.

As a nice side effect, this change also adds the ability to define
Resources on SpecRestApi in addition to what has already been defined in
the OpenAPI spec.

closes #1477
@zoonderkins
Copy link

+1

@mergify mergify bot closed this as completed in #8270 Jun 12, 2020
mergify bot pushed a commit that referenced this issue Jun 12, 2020
The apigateway CDK construct library creates a large number of
CloudFormation resources.
To define a single Method that is backed with a Lambda function and
authorizer with an Authorizer, ~8 CloudFormation resources are created.
This quickly grows and a reasonable sized RestApi is bound to hit the
200 resource limit on their stack.

Re-organizing the CDK app into multiple `Stack`s that share the same
instance of `RestApi` will not resolve this problem, since the CDK
still makes an executive decision to keep the Methods and Resources in
the same stack as the `RestApi` construct. (see #7391)

This change introduces a new import style API `fromRestApiAttributes()`
that returns an instance of `IRestApi` that allows for new Resources to
be defined across stacks.

As a nice side effect, this change also adds the ability to define
Resources on SpecRestApi in addition to what has already been defined in
the OpenAPI spec.

closes #1477
closes #7391
fixes #8347


----

*By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license*
@aaa-miketecson
Copy link

aaa-miketecson commented Aug 14, 2020

+1 Would love the feature to add resources/methods to an existing api gateway stack.

@tuanardouin
Copy link

tuanardouin commented Oct 21, 2020

+1 having the same limit issue

Edit :
A good explanation on the official documentation on how to handle the resource limit problem :
https://docs.aws.amazon.com/cdk/api/latest/docs/aws-apigateway-readme.html#breaking-up-methods-and-resources-across-stacks

Note :
apigateway.RestApi.fromRestApiAttributes returns an interface IRestApi not a class RestAPI so you can't use addModel anymore. You have to create the Model using the construct manually and passing the IRestApi to it.

@nateiler
Copy link

Another note is that this stack splitting currently works for adding root level resources. Ex: /books and /authors

For example it wouldn't be possible to add a 'reviews' nested stack such as /books/{id}/reviews

@AmitBaranes
Copy link

AWS CloudFormation now supports increased limits on five service quotas - template size, resources, parameters, mappings, and outputs. The new per template limits for the maximum number of resources is 500 (previously 200)
https://aws.amazon.com/about-aws/whats-new/2020/10/aws-cloudformation-now-supports-increased-limits-on-five-service-quotas/

@tvb
Copy link

tvb commented Apr 8, 2022

Despite the possibility to increase the Cloudformation resource limit, I still would like to split my Rest API Methods over multiple stacks. I believe this is still not possible?

@Dan-Wuensch
Copy link

Huge problem for CDK projects with more than a few API endpoints that include documentation. What's the latest on this?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
@aws-cdk/aws-apigateway Related to Amazon API Gateway effort/medium Medium work item – several days of effort feature-request A feature should be added or improved. in-progress This issue is being actively worked on.
Projects
None yet
Development

Successfully merging a pull request may close this issue.