From f7dfa3f7c4eed8d36bf10c144060333defc2084d Mon Sep 17 00:00:00 2001 From: Niranjan Jayakar Date: Thu, 11 Jun 2020 16:01:31 +0100 Subject: [PATCH] unfortunately RestApiBase needs to be exported --- .../@aws-cdk/aws-apigateway/lib/method.ts | 3 +- .../lib/private/restapi-base.ts | 207 ------------------ .../@aws-cdk/aws-apigateway/lib/restapi.ts | 207 +++++++++++++++++- 3 files changed, 203 insertions(+), 214 deletions(-) delete mode 100644 packages/@aws-cdk/aws-apigateway/lib/private/restapi-base.ts diff --git a/packages/@aws-cdk/aws-apigateway/lib/method.ts b/packages/@aws-cdk/aws-apigateway/lib/method.ts index 37aade2bcb254..78a5075b446f7 100644 --- a/packages/@aws-cdk/aws-apigateway/lib/method.ts +++ b/packages/@aws-cdk/aws-apigateway/lib/method.ts @@ -5,10 +5,9 @@ import { ConnectionType, Integration } from './integration'; import { MockIntegration } from './integrations/mock'; import { MethodResponse } from './methodresponse'; import { IModel } from './model'; -import { RestApiBase } from './private/restapi-base'; import { IRequestValidator, RequestValidatorOptions } from './requestvalidator'; import { IResource } from './resource'; -import { IRestApi, RestApi } from './restapi'; +import { IRestApi, RestApi, RestApiBase } from './restapi'; import { validateHttpMethod } from './util'; export interface MethodOptions { diff --git a/packages/@aws-cdk/aws-apigateway/lib/private/restapi-base.ts b/packages/@aws-cdk/aws-apigateway/lib/private/restapi-base.ts deleted file mode 100644 index 8ef12d91c8ed5..0000000000000 --- a/packages/@aws-cdk/aws-apigateway/lib/private/restapi-base.ts +++ /dev/null @@ -1,207 +0,0 @@ -import * as iam from '@aws-cdk/aws-iam'; -import { CfnOutput, Construct, Resource, Stack } from '@aws-cdk/core'; -import { CfnAccount, CfnRestApi } from '../apigateway.generated'; -import { Deployment } from '../deployment'; -import { DomainName, DomainNameOptions } from '../domain-name'; -import { GatewayResponse, GatewayResponseOptions } from '../gateway-response'; -import { Method } from '../method'; -import { IResource } from '../resource'; -import { IRestApi, RestApiOptions } from '../restapi'; -import { Stage } from '../stage'; -import { UsagePlan, UsagePlanProps } from '../usage-plan'; - -const RESTAPI_SYMBOL = Symbol.for('@aws-cdk/aws-apigateway.RestApiBase'); - -/** - * @internal - */ -export abstract class RestApiBase extends Resource implements IRestApi { - - /** - * Checks if the given object is an instance of RestApiBase. - * @internal - */ - public static _isRestApiBase(x: any): x is RestApiBase { - return x !== null && typeof(x) === 'object' && RESTAPI_SYMBOL in x; - } - - /** - * API Gateway deployment that represents the latest changes of the API. - * This resource will be automatically updated every time the REST API model changes. - * This will be undefined if `deploy` is false. - */ - public get latestDeployment() { - return this._latestDeployment; - } - - /** - * The first domain name mapped to this API, if defined through the `domainName` - * configuration prop, or added via `addDomainName` - */ - public get domainName() { - return this._domainName; - } - - /** - * The ID of this API Gateway RestApi. - */ - public abstract readonly restApiId: string; - - /** - * The resource ID of the root resource. - * - * @attribute - */ - public abstract readonly restApiRootResourceId: string; - - /** - * Represents the root resource of this API endpoint ('/'). - * Resources and Methods are added to this resource. - */ - public abstract readonly root: IResource; - - /** - * API Gateway stage that points to the latest deployment (if defined). - * - * If `deploy` is disabled, you will need to explicitly assign this value in order to - * set up integrations. - */ - public deploymentStage!: Stage; - - private _latestDeployment?: Deployment; - private _domainName?: DomainName; - - constructor(scope: Construct, id: string, props: RestApiOptions = { }) { - super(scope, id, { - physicalName: props.restApiName || id, - }); - - Object.defineProperty(this, RESTAPI_SYMBOL, { value: true }); - } - - /** - * Returns the URL for an HTTP path. - * - * Fails if `deploymentStage` is not set either by `deploy` or explicitly. - */ - public urlForPath(path: string = '/'): string { - if (!this.deploymentStage) { - throw new Error('Cannot determine deployment stage for API from "deploymentStage". Use "deploy" or explicitly set "deploymentStage"'); - } - - return this.deploymentStage.urlForPath(path); - } - - /** - * Defines an API Gateway domain name and maps it to this API. - * @param id The construct id - * @param options custom domain options - */ - public addDomainName(id: string, options: DomainNameOptions): DomainName { - const domainName = new DomainName(this, id, { - ...options, - mapping: this, - }); - if (!this._domainName) { - this._domainName = domainName; - } - return domainName; - } - - /** - * Adds a usage plan. - */ - public addUsagePlan(id: string, props: UsagePlanProps = {}): UsagePlan { - return new UsagePlan(this, id, props); - } - - /** - * Gets the "execute-api" ARN - * @returns The "execute-api" ARN. - * @default "*" returns the execute API ARN for all methods/resources in - * this API. - * @param method The method (default `*`) - * @param path The resource path. Must start with '/' (default `*`) - * @param stage The stage (default `*`) - */ - 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 Stack.of(this).formatArn({ - service: 'execute-api', - resource: this.restApiId, - sep: '/', - resourceName: `${stage}/${method}${path}`, - }); - } - - /** - * Adds a new gateway response. - */ - public addGatewayResponse(id: string, options: GatewayResponseOptions): GatewayResponse { - return new GatewayResponse(this, id, { - restApi: this, - ...options, - }); - } - - /** - * Internal API used by `Method` to keep an inventory of methods at the API - * level for validation purposes. - * - * @internal - */ - public _attachMethod(method: Method) { - ignore(method); - } - - protected configureCloudWatchRole(apiResource: CfnRestApi) { - const role = new iam.Role(this, 'CloudWatchRole', { - assumedBy: new iam.ServicePrincipal('apigateway.amazonaws.com'), - managedPolicies: [iam.ManagedPolicy.fromAwsManagedPolicyName('service-role/AmazonAPIGatewayPushToCloudWatchLogs')], - }); - - const resource = new CfnAccount(this, 'Account', { - cloudWatchRoleArn: role.roleArn, - }); - - resource.node.addDependency(apiResource); - } - - protected configureDeployment(props: RestApiOptions) { - const deploy = props.deploy === undefined ? true : props.deploy; - if (deploy) { - - this._latestDeployment = new Deployment(this, 'Deployment', { - description: 'Automatically created by the RestApi construct', - api: this, - retainDeployments: props.retainDeployments, - }); - - // encode the stage name into the construct id, so if we change the stage name, it will recreate a new stage. - // stage name is part of the endpoint, so that makes sense. - const stageName = (props.deployOptions && props.deployOptions.stageName) || 'prod'; - - this.deploymentStage = new Stage(this, `DeploymentStage.${stageName}`, { - deployment: this._latestDeployment, - ...props.deployOptions, - }); - - new CfnOutput(this, 'Endpoint', { exportName: props.endpointExportName, value: this.urlForPath() }); - } else { - if (props.deployOptions) { - throw new Error('Cannot set \'deployOptions\' if \'deploy\' is disabled'); - } - } - } -} - -function ignore(_x: any) { - return; -} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-apigateway/lib/restapi.ts b/packages/@aws-cdk/aws-apigateway/lib/restapi.ts index 90ef5e65f05a0..4d08a0b01ce36 100644 --- a/packages/@aws-cdk/aws-apigateway/lib/restapi.ts +++ b/packages/@aws-cdk/aws-apigateway/lib/restapi.ts @@ -1,19 +1,22 @@ import { IVpcEndpoint } from '@aws-cdk/aws-ec2'; import * as iam from '@aws-cdk/aws-iam'; -import { Construct, IResource as IResourceBase, Resource } from '@aws-cdk/core'; +import { CfnOutput, Construct, IResource as IResourceBase, Resource, Stack } from '@aws-cdk/core'; import { ApiDefinition } from './api-definition'; import { ApiKey, ApiKeyOptions, IApiKey } from './api-key'; -import { CfnRestApi } from './apigateway.generated'; +import { CfnAccount, CfnRestApi } from './apigateway.generated'; import { CorsOptions } from './cors'; import { Deployment } from './deployment'; -import { DomainNameOptions } from './domain-name'; +import { DomainName, DomainNameOptions } from './domain-name'; +import { GatewayResponse, GatewayResponseOptions } from './gateway-response'; import { Integration } from './integration'; import { Method, MethodOptions } from './method'; import { Model, ModelOptions } from './model'; -import { RestApiBase } from './private/restapi-base'; import { RequestValidator, RequestValidatorOptions } from './requestvalidator'; import { IResource, ResourceBase, ResourceOptions } from './resource'; -import { StageOptions } from './stage'; +import { Stage, StageOptions } from './stage'; +import { UsagePlan, UsagePlanProps } from './usage-plan'; + +const RESTAPI_SYMBOL = Symbol.for('@aws-cdk/aws-apigateway.RestApiBase'); export interface IRestApi extends IResourceBase { /** @@ -218,6 +221,196 @@ export interface SpecRestApiProps extends RestApiOptions { readonly apiDefinition: ApiDefinition; } +/** + * Base implementation that are common to various implementations of IRestApi + */ +export abstract class RestApiBase extends Resource implements IRestApi { + + /** + * Checks if the given object is an instance of RestApiBase. + * @internal + */ + public static _isRestApiBase(x: any): x is RestApiBase { + return x !== null && typeof(x) === 'object' && RESTAPI_SYMBOL in x; + } + + /** + * API Gateway deployment that represents the latest changes of the API. + * This resource will be automatically updated every time the REST API model changes. + * This will be undefined if `deploy` is false. + */ + public get latestDeployment() { + return this._latestDeployment; + } + + /** + * The first domain name mapped to this API, if defined through the `domainName` + * configuration prop, or added via `addDomainName` + */ + public get domainName() { + return this._domainName; + } + + /** + * The ID of this API Gateway RestApi. + */ + public abstract readonly restApiId: string; + + /** + * The resource ID of the root resource. + * + * @attribute + */ + public abstract readonly restApiRootResourceId: string; + + /** + * Represents the root resource of this API endpoint ('/'). + * Resources and Methods are added to this resource. + */ + public abstract readonly root: IResource; + + /** + * API Gateway stage that points to the latest deployment (if defined). + * + * If `deploy` is disabled, you will need to explicitly assign this value in order to + * set up integrations. + */ + public deploymentStage!: Stage; + + private _latestDeployment?: Deployment; + private _domainName?: DomainName; + + constructor(scope: Construct, id: string, props: RestApiOptions = { }) { + super(scope, id, { + physicalName: props.restApiName || id, + }); + + Object.defineProperty(this, RESTAPI_SYMBOL, { value: true }); + } + + /** + * Returns the URL for an HTTP path. + * + * Fails if `deploymentStage` is not set either by `deploy` or explicitly. + */ + public urlForPath(path: string = '/'): string { + if (!this.deploymentStage) { + throw new Error('Cannot determine deployment stage for API from "deploymentStage". Use "deploy" or explicitly set "deploymentStage"'); + } + + return this.deploymentStage.urlForPath(path); + } + + /** + * Defines an API Gateway domain name and maps it to this API. + * @param id The construct id + * @param options custom domain options + */ + public addDomainName(id: string, options: DomainNameOptions): DomainName { + const domainName = new DomainName(this, id, { + ...options, + mapping: this, + }); + if (!this._domainName) { + this._domainName = domainName; + } + return domainName; + } + + /** + * Adds a usage plan. + */ + public addUsagePlan(id: string, props: UsagePlanProps = {}): UsagePlan { + return new UsagePlan(this, id, props); + } + + /** + * Gets the "execute-api" ARN + * @returns The "execute-api" ARN. + * @default "*" returns the execute API ARN for all methods/resources in + * this API. + * @param method The method (default `*`) + * @param path The resource path. Must start with '/' (default `*`) + * @param stage The stage (default `*`) + */ + 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 Stack.of(this).formatArn({ + service: 'execute-api', + resource: this.restApiId, + sep: '/', + resourceName: `${stage}/${method}${path}`, + }); + } + + /** + * Adds a new gateway response. + */ + public addGatewayResponse(id: string, options: GatewayResponseOptions): GatewayResponse { + return new GatewayResponse(this, id, { + restApi: this, + ...options, + }); + } + + /** + * Internal API used by `Method` to keep an inventory of methods at the API + * level for validation purposes. + * + * @internal + */ + public _attachMethod(method: Method) { + ignore(method); + } + + protected configureCloudWatchRole(apiResource: CfnRestApi) { + const role = new iam.Role(this, 'CloudWatchRole', { + assumedBy: new iam.ServicePrincipal('apigateway.amazonaws.com'), + managedPolicies: [iam.ManagedPolicy.fromAwsManagedPolicyName('service-role/AmazonAPIGatewayPushToCloudWatchLogs')], + }); + + const resource = new CfnAccount(this, 'Account', { + cloudWatchRoleArn: role.roleArn, + }); + + resource.node.addDependency(apiResource); + } + + protected configureDeployment(props: RestApiOptions) { + const deploy = props.deploy === undefined ? true : props.deploy; + if (deploy) { + + this._latestDeployment = new Deployment(this, 'Deployment', { + description: 'Automatically created by the RestApi construct', + api: this, + retainDeployments: props.retainDeployments, + }); + + // encode the stage name into the construct id, so if we change the stage name, it will recreate a new stage. + // stage name is part of the endpoint, so that makes sense. + const stageName = (props.deployOptions && props.deployOptions.stageName) || 'prod'; + + this.deploymentStage = new Stage(this, `DeploymentStage.${stageName}`, { + deployment: this._latestDeployment, + ...props.deployOptions, + }); + + new CfnOutput(this, 'Endpoint', { exportName: props.endpointExportName, value: this.urlForPath() }); + } else { + if (props.deployOptions) { + throw new Error('Cannot set \'deployOptions\' if \'deploy\' is disabled'); + } + } + } +} + /** * Represents a REST API in Amazon API Gateway, created with an OpenAPI specification. * @@ -541,4 +734,8 @@ class RootResource extends ResourceBase { } return this._restApi; } +} + +function ignore(_x: any) { + return; } \ No newline at end of file