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

feat(apigateway): create RestApi from an OpenAPI spec #7372

Merged
merged 16 commits into from
May 11, 2020
Merged
57 changes: 42 additions & 15 deletions packages/@aws-cdk/aws-apigateway/README.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
## Amazon API Gateway Construct Library

<!--BEGIN STABILITY BANNER-->
---

Expand Down Expand Up @@ -31,9 +32,10 @@ running on AWS Lambda, or any web application.
- [Deep dive: Invalidation of deployments](#deep-dive-invalidation-of-deployments)
- [Custom Domains](#custom-domains)
- [Access Logging](#access-logging)
- [Cross Origin Resource Sharing (CORS)](cross-origin-resource-sharing-cors)
- [Cross Origin Resource Sharing (CORS)](#cross-origin-resource-sharing-cors)
- [Endpoint Configuration](#endpoint-configuration)
- [Gateway Response](#gateway-response)
- [OpenAPI Definition](#openapi-definition)
- [APIGateway v2](#apigateway-v2)

## Defining APIs
Expand Down Expand Up @@ -101,11 +103,11 @@ item.addMethod('DELETE', new apigateway.HttpIntegration('http://amazon.com'));
Methods are associated with backend integrations, which are invoked when this
method is called. API Gateway supports the following integrations:

* `MockIntegration` - can be used to test APIs. This is the default
* `MockIntegration` - can be used to test APIs. This is the default
integration if one is not specified.
* `LambdaIntegration` - can be used to invoke an AWS Lambda function.
* `AwsIntegration` - can be used to invoke arbitrary AWS service APIs.
* `HttpIntegration` - can be used to invoke HTTP endpoints.
* `LambdaIntegration` - can be used to invoke an AWS Lambda function.
* `AwsIntegration` - can be used to invoke arbitrary AWS service APIs.
* `HttpIntegration` - can be used to invoke HTTP endpoints.

The following example shows how to integrate the `GET /book/{book_id}` method to
an AWS Lambda function:
Expand Down Expand Up @@ -179,6 +181,7 @@ This construct lets you specify rate limiting properties which should be applied
The API key created has the specified rate limits, such as quota and throttles, applied.

The following example shows how to use a rate limited api key :

```ts
const hello = new lambda.Function(this, 'hello', {
runtime: lambda.Runtime.NODEJS_10_X,
Expand Down Expand Up @@ -450,9 +453,9 @@ iamUser.attachInlinePolicy(new iam.Policy(this, 'AllowBooks', {
API Gateway also allows [lambda functions to be used as authorizers](https://docs.aws.amazon.com/apigateway/latest/developerguide/apigateway-use-lambda-authorizer.html).

This module provides support for token-based Lambda authorizers. When a client makes a request to an API's methods configured with such
an authorizer, API Gateway calls the Lambda authorizer, which takes the caller's identity as input and returns an IAM policy as output.
an authorizer, API Gateway calls the Lambda authorizer, which takes the caller's identity as input and returns an IAM policy as output.
A token-based Lambda authorizer (also called a token authorizer) receives the caller's identity in a bearer token, such as
a JSON Web Token (JWT) or an OAuth token.
a JSON Web Token (JWT) or an OAuth token.

API Gateway interacts with the authorizer Lambda function handler by passing input and expecting the output in a specific format.
The event object that the handler is called with contains the `authorizationToken` and the `methodArn` from the request to the
Expand Down Expand Up @@ -491,7 +494,7 @@ depending on where the defaults were specified.

This module provides support for request-based Lambda authorizers. When a client makes a request to an API's methods configured with such
an authorizer, API Gateway calls the Lambda authorizer, which takes specified parts of the request, known as identity sources,
as input and returns an IAM policy as output. A request-based Lambda authorizer (also called a request authorizer) receives
as input and returns an IAM policy as output. A request-based Lambda authorizer (also called a request authorizer) receives
the identity sources in a series of values pulled from the request, from the headers, stage variables, query strings, and the context.

API Gateway interacts with the authorizer Lambda function handler by passing input and expecting the output in a specific format.
Expand Down Expand Up @@ -634,16 +637,16 @@ new apigw.DomainName(this, 'custom-domain', {
```

Once you have a domain, you can map base paths of the domain to APIs.
The following example will map the URL https://example.com/go-to-api1
to the `api1` API and https://example.com/boom to the `api2` API.
The following example will map the URL <https://example.com/go-to-api1>
to the `api1` API and <https://example.com/boom> to the `api2` API.

```ts
domain.addBasePathMapping(api1, { basePath: 'go-to-api1' });
domain.addBasePathMapping(api2, { basePath: 'boom' });
```

You can specify the API `Stage` to which this base path URL will map to. By default, this will be the
`deploymentStage` of the `RestApi`.
`deploymentStage` of the `RestApi`.

```ts
const betaDeploy = new Deployment(this, 'beta-deployment', {
Expand Down Expand Up @@ -787,7 +790,7 @@ running at one origin, access to selected resources from a different origin. A
web application executes a cross-origin HTTP request when it requests a resource
that has a different origin (domain, protocol, or port) from its own.

You can add the CORS [preflight](https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS#Preflighted_requests) OPTIONS
You can add the CORS [preflight](https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS#Preflighted_requests) OPTIONS
HTTP method to any API resource via the `defaultCorsPreflightOptions` option or by calling the `addCorsPreflight` on a specific resource.

The following example will enable CORS for all methods and all origins on all resources of the API:
Expand All @@ -802,7 +805,7 @@ new apigateway.RestApi(this, 'api', {
```

The following example will add an OPTIONS method to the `myResource` API resource, which
only allows GET and PUT HTTP requests from the origin https://amazon.com.
only allows GET and PUT HTTP requests from the origin <https://amazon.com.>

```ts
myResource.addCorsPreflight({
Expand Down Expand Up @@ -833,8 +836,8 @@ features which are not yet supported.

## Endpoint Configuration

API gateway allows you to specify an
[API Endpoint Type](https://docs.aws.amazon.com/apigateway/latest/developerguide/api-gateway-api-endpoint-types.html).
API gateway allows you to specify an
[API Endpoint Type](https://docs.aws.amazon.com/apigateway/latest/developerguide/api-gateway-api-endpoint-types.html).
To define an endpoint type for the API gateway, use `endpointConfiguration` property:

```ts
Expand Down Expand Up @@ -894,6 +897,30 @@ api.addGatewayResponse('test-response', {
});
```

## OpenAPI Definition

CDK supports creating a REST API by importing an OpenAPI definition file. It currently supports OpenAPI v2.0 and OpenAPI
v3.0 definition files. Read more about [Configuring a REST API using
OpenAPI](https://docs.aws.amazon.com/apigateway/latest/developerguide/api-gateway-import-api.html).

The following code creates a REST API using an external OpenAPI definition JSON file -

```ts
const api = new apigateway.SpecRestApi(this, 'books-api', {
apiDefinition: apigateway.APIDefinition.fromAsset('path-to-file.json')
nija-at marked this conversation as resolved.
Show resolved Hide resolved
});
```

There are a number of limitations in using OpenAPI definitions in API Gateway. Read the [Amazon API Gateway important
notes for REST APIs](https://docs.aws.amazon.com/apigateway/latest/developerguide/api-gateway-known-issues.html#api-gateway-known-issues-rest-apis)
for more details.

**Note:** When starting off with an OpenAPI definition using `SpecRestApi`, it is not possible to configure some
properties that can be configured directly in the OpenAPI specification file. This is to prevent people duplication
of these properties and potential confusion.
Further, it is currently also not possible to configure Methods and Resources in addition to the ones in the
specification file.

## APIGateway v2

APIGateway v2 APIs are now moved to its own package named `aws-apigatewayv2`. For backwards compatibility, existing
Expand Down
197 changes: 197 additions & 0 deletions packages/@aws-cdk/aws-apigateway/lib/api-definition.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,197 @@
import * as s3 from '@aws-cdk/aws-s3';
import * as s3_assets from '@aws-cdk/aws-s3-assets';
import * as cdk from '@aws-cdk/core';

/**
* Represents an OpenAPI definition asset.
*/
export abstract class ApiDefinition {
/**
* Creates an API definition from a specification file in an S3 bucket
*/
public static fromBucket(bucket: s3.IBucket, key: string, objectVersion?: string): S3ApiDefinition {
return new S3ApiDefinition(bucket, key, objectVersion);
}

/**
* Create an API definition from an inline object. The inline object must follow the
* schema of OpenAPI 2.0 or OpenAPI 3.0
*
* @example
* ApiDefinition.fromInline({
* openapi: '3.0.2',
* paths: {
* '/pets': {
* get: {
* 'responses': {
* 200: {
* content: {
* 'application/json': {
* schema: {
* $ref: '#/components/schemas/Empty',
* },
* },
* },
* },
* },
* 'x-amazon-apigateway-integration': {
* responses: {
* default: {
* statusCode: '200',
* },
* },
* requestTemplates: {
* 'application/json': '{"statusCode": 200}',
* },
* passthroughBehavior: 'when_no_match',
* type: 'mock',
* },
* },
* },
* },
* components: {
* schemas: {
* Empty: {
* title: 'Empty Schema',
* type: 'object',
* },
* },
* },
* });
*/
public static fromInline(definition: any): InlineApiDefinition {
return new InlineApiDefinition(definition);
}

/**
* Loads the API specification from a local disk asset.
*/
public static fromAsset(file: string, options?: s3_assets.AssetOptions): AssetApiDefinition {
return new AssetApiDefinition(file, options);
}

/**
* Called when the specification is initialized to allow this object to bind
* to the stack, add resources and have fun.
*
* @param scope The binding scope. Don't be smart about trying to down-cast or
* assume it's initialized. You may just use it as a construct scope.
*/
public abstract bind(scope: cdk.Construct): ApiDefinitionConfig;
}

/**
* S3 location of the API definition file
*/
export interface ApiDefinitionS3Location {
/** The S3 bucket */
readonly bucket: string;
/** The S3 key */
readonly key: string;
/**
* An optional version
* @default - latest version
*/
readonly version?: string;
}

/**
* Post-Binding Configuration for a CDK construct
*/
export interface ApiDefinitionConfig {
/**
* The location of the specification in S3 (mutually exclusive with `inlineDefinition`).
*
* @default - API definition is not an S3 location
*/
readonly s3Location?: ApiDefinitionS3Location;

/**
* Inline specification (mutually exclusive with `s3Location`).
*
* @default - API definition is not defined inline
*/
readonly inlineDefinition?: any;
}

/**
* OpenAPI specification from an S3 archive.
*/
export class S3ApiDefinition extends ApiDefinition {
private bucketName: string;

constructor(bucket: s3.IBucket, private key: string, private objectVersion?: string) {
super();

if (!bucket.bucketName) {
throw new Error('bucketName is undefined for the provided bucket');
}

this.bucketName = bucket.bucketName;
}

public bind(_scope: cdk.Construct): ApiDefinitionConfig {
return {
s3Location: {
bucket: this.bucketName,
key: this.key,
version: this.objectVersion,
},
};
}
}

/**
* OpenAPI specification from an inline JSON object.
*/
export class InlineApiDefinition extends ApiDefinition {
constructor(private definition: any) {
super();

if (typeof(definition) !== 'object') {
throw new Error('definition should be of type object');
}

if (Object.keys(definition).length === 0) {
throw new Error('JSON definition cannot be empty');
}
}

public bind(_scope: cdk.Construct): ApiDefinitionConfig {
return {
inlineDefinition: this.definition,
};
}
}

/**
* OpenAPI specification from a local file.
*/
export class AssetApiDefinition extends ApiDefinition {
private asset?: s3_assets.Asset;

constructor(private readonly path: string, private readonly options: s3_assets.AssetOptions = { }) {
super();
}

public bind(scope: cdk.Construct): ApiDefinitionConfig {
// If the same AssetAPIDefinition is used multiple times, retain only the first instantiation.
if (this.asset === undefined) {
this.asset = new s3_assets.Asset(scope, 'APIDefinition', {
path: this.path,
...this.options,
});
}

if (this.asset.isZipArchive) {
throw new Error(`Asset cannot be a .zip file or a directory (${this.path})`);
}

return {
s3Location: {
bucket: this.asset.s3BucketName,
key: this.asset.s3ObjectKey,
},
};
}
}
1 change: 1 addition & 0 deletions packages/@aws-cdk/aws-apigateway/lib/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ export * from './base-path-mapping';
export * from './cors';
export * from './authorizers';
export * from './access-log';
export * from './api-definition';
export * from './gateway-response';

// AWS::ApiGateway CloudFormation Resources:
Expand Down