Skip to content

Commit

Permalink
feat(apigateway): create RestApi from an OpenAPI spec
Browse files Browse the repository at this point in the history
Co-authored-by: Niranjan Jayakar <[email protected]>

Ability to import an OpenAPI definition to an API Gateway Rest API.
The definition can either be a file as a local asset, inline JSON or a
key in an S3 bucket.

closes #4421
  • Loading branch information
Ben Giles authored May 11, 2020
1 parent d8eec54 commit 31014ca
Show file tree
Hide file tree
Showing 12 changed files with 1,099 additions and 189 deletions.
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')
});
```
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
205 changes: 205 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,205 @@
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.
* @experimental
*/
export abstract class ApiDefinition {
/**
* Creates an API definition from a specification file in an S3 bucket
* @experimental
*/
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.
* @experimental
*/
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
* @experimental
*/
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
* @experimental
*/
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.
* @experimental
*/
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.
* @experimental
*/
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.
* @experimental
*/
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
Loading

0 comments on commit 31014ca

Please sign in to comment.