From d09778d7e7465db73005738d9ce0b6e635c15fc3 Mon Sep 17 00:00:00 2001 From: Jonathan Goldwasser Date: Thu, 19 May 2022 18:11:14 +0200 Subject: [PATCH] feat(cloudfront): REST API origin (#20335) Add an origin to work with API Gateway REST APIs. Extracts the domain name and origin path from the API url. ---- ### All Submissions: * [x] Have you followed the guidelines in our [Contributing guide?](https://github.com/aws/aws-cdk/blob/master/CONTRIBUTING.md) ### Adding new Unconventional Dependencies: * [ ] This PR adds new unconventional dependencies following the process described [here](https://github.com/aws/aws-cdk/blob/master/CONTRIBUTING.md/#adding-new-unconventional-dependencies) ### New Features * [x] Have you added the new feature to an [integration test](https://github.com/aws/aws-cdk/blob/master/INTEGRATION_TESTS.md)? * [x] Did you use `yarn integ` to deploy the infrastructure and generate the snapshot (i.e. `yarn integ` without `--dry-run`)? *By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license* --- .../@aws-cdk/aws-cloudfront-origins/README.md | 14 + .../aws-cloudfront-origins/lib/http-origin.ts | 12 +- .../aws-cloudfront-origins/lib/index.ts | 1 + .../lib/private/utils.ts | 12 + .../lib/rest-api-origin.ts | 59 +++ .../aws-cloudfront-origins/package.json | 2 + .../rosetta/default.ts-fixture | 1 + .../test/integ.rest-api-origin.ts | 17 + .../rest-api-origin.integ.snapshot/cdk.out | 1 + ...g-cloudfront-rest-api-origin.template.json | 237 +++++++++++ .../rest-api-origin.integ.snapshot/integ.json | 14 + .../manifest.json | 70 ++++ .../rest-api-origin.integ.snapshot/tree.json | 370 ++++++++++++++++++ .../test/rest-api-origin.test.ts | 62 +++ .../@aws-cdk/aws-cloudfront/lib/origin.ts | 27 +- 15 files changed, 877 insertions(+), 22 deletions(-) create mode 100644 packages/@aws-cdk/aws-cloudfront-origins/lib/private/utils.ts create mode 100644 packages/@aws-cdk/aws-cloudfront-origins/lib/rest-api-origin.ts create mode 100644 packages/@aws-cdk/aws-cloudfront-origins/test/integ.rest-api-origin.ts create mode 100644 packages/@aws-cdk/aws-cloudfront-origins/test/rest-api-origin.integ.snapshot/cdk.out create mode 100644 packages/@aws-cdk/aws-cloudfront-origins/test/rest-api-origin.integ.snapshot/integ-cloudfront-rest-api-origin.template.json create mode 100644 packages/@aws-cdk/aws-cloudfront-origins/test/rest-api-origin.integ.snapshot/integ.json create mode 100644 packages/@aws-cdk/aws-cloudfront-origins/test/rest-api-origin.integ.snapshot/manifest.json create mode 100644 packages/@aws-cdk/aws-cloudfront-origins/test/rest-api-origin.integ.snapshot/tree.json create mode 100644 packages/@aws-cdk/aws-cloudfront-origins/test/rest-api-origin.test.ts diff --git a/packages/@aws-cdk/aws-cloudfront-origins/README.md b/packages/@aws-cdk/aws-cloudfront-origins/README.md index d4e948b1e887e..b15c7627c8dcd 100644 --- a/packages/@aws-cdk/aws-cloudfront-origins/README.md +++ b/packages/@aws-cdk/aws-cloudfront-origins/README.md @@ -118,3 +118,17 @@ new cloudfront.Distribution(this, 'myDist', { }, }); ``` + +## From an API Gateway REST API + +Origins can be created from an API Gateway REST API. It is recommended to use a +[regional API](https://docs.aws.amazon.com/apigateway/latest/developerguide/api-gateway-api-endpoint-types.html) in this case. + +```ts +declare const api: apigateway.RestApi; +new cloudfront.Distribution(this, 'Distribution', { + defaultBehavior: { origin: new origins.RestApiOrigin(api) }, +}); +``` + +The origin path will automatically be set as the stage name. diff --git a/packages/@aws-cdk/aws-cloudfront-origins/lib/http-origin.ts b/packages/@aws-cdk/aws-cloudfront-origins/lib/http-origin.ts index e4d6ac190dcff..19a4631221573 100644 --- a/packages/@aws-cdk/aws-cloudfront-origins/lib/http-origin.ts +++ b/packages/@aws-cdk/aws-cloudfront-origins/lib/http-origin.ts @@ -1,5 +1,6 @@ import * as cloudfront from '@aws-cdk/aws-cloudfront'; import * as cdk from '@aws-cdk/core'; +import { validateSecondsInRangeOrUndefined } from './private/utils'; /** * Properties for an Origin backed by an S3 website-configured bucket, load balancer, or custom HTTP server. @@ -79,14 +80,3 @@ export class HttpOrigin extends cloudfront.OriginBase { }; } } - -/** - * Throws an error if a duration is defined and not an integer number of seconds within a range. - */ -function validateSecondsInRangeOrUndefined(name: string, min: number, max: number, duration?: cdk.Duration) { - if (duration === undefined) { return; } - const value = duration.toSeconds(); - if (!Number.isInteger(value) || value < min || value > max) { - throw new Error(`${name}: Must be an int between ${min} and ${max} seconds (inclusive); received ${value}.`); - } -} diff --git a/packages/@aws-cdk/aws-cloudfront-origins/lib/index.ts b/packages/@aws-cdk/aws-cloudfront-origins/lib/index.ts index 6fc3bf1750637..00ac116b126a3 100644 --- a/packages/@aws-cdk/aws-cloudfront-origins/lib/index.ts +++ b/packages/@aws-cdk/aws-cloudfront-origins/lib/index.ts @@ -2,3 +2,4 @@ export * from './http-origin'; export * from './load-balancer-origin'; export * from './s3-origin'; export * from './origin-group'; +export * from './rest-api-origin'; diff --git a/packages/@aws-cdk/aws-cloudfront-origins/lib/private/utils.ts b/packages/@aws-cdk/aws-cloudfront-origins/lib/private/utils.ts new file mode 100644 index 0000000000000..ad6dfc2e69392 --- /dev/null +++ b/packages/@aws-cdk/aws-cloudfront-origins/lib/private/utils.ts @@ -0,0 +1,12 @@ +import * as cdk from '@aws-cdk/core'; + +/** + * Throws an error if a duration is defined and not an integer number of seconds within a range. + */ +export function validateSecondsInRangeOrUndefined(name: string, min: number, max: number, duration?: cdk.Duration) { + if (duration === undefined) { return; } + const value = duration.toSeconds(); + if (!Number.isInteger(value) || value < min || value > max) { + throw new Error(`${name}: Must be an int between ${min} and ${max} seconds (inclusive); received ${value}.`); + } +} diff --git a/packages/@aws-cdk/aws-cloudfront-origins/lib/rest-api-origin.ts b/packages/@aws-cdk/aws-cloudfront-origins/lib/rest-api-origin.ts new file mode 100644 index 0000000000000..53401b575566f --- /dev/null +++ b/packages/@aws-cdk/aws-cloudfront-origins/lib/rest-api-origin.ts @@ -0,0 +1,59 @@ +import * as apigateway from '@aws-cdk/aws-apigateway'; +import * as cloudfront from '@aws-cdk/aws-cloudfront'; +import * as cdk from '@aws-cdk/core'; +import { validateSecondsInRangeOrUndefined } from './private/utils'; + +/** + * Properties for an Origin for an API Gateway REST API. + */ +export interface RestApiOriginProps extends cloudfront.OriginOptions { + /** + * Specifies how long, in seconds, CloudFront waits for a response from the origin, also known as the origin response timeout. + * The valid range is from 1 to 180 seconds, inclusive. + * + * Note that values over 60 seconds are possible only after a limit increase request for the origin response timeout quota + * has been approved in the target account; otherwise, values over 60 seconds will produce an error at deploy time. + * + * @default Duration.seconds(30) + */ + readonly readTimeout?: cdk.Duration; + + /** + * Specifies how long, in seconds, CloudFront persists its connection to the origin. + * The valid range is from 1 to 180 seconds, inclusive. + * + * Note that values over 60 seconds are possible only after a limit increase request for the origin response timeout quota + * has been approved in the target account; otherwise, values over 60 seconds will produce an error at deploy time. + * + * @default Duration.seconds(5) + */ + readonly keepaliveTimeout?: cdk.Duration; +} + +/** + * An Origin for an API Gateway REST API. + */ +export class RestApiOrigin extends cloudfront.OriginBase { + + constructor(restApi: apigateway.RestApi, private readonly props: RestApiOriginProps = {}) { + // urlForPath() is of the form 'https://.execute-api..amazonaws.com/' + // Splitting on '/' gives: ['https', '', '.execute-api..amazonaws.com', ''] + // The element at index 2 is the domain name, the element at index 3 is the stage name + super(cdk.Fn.select(2, cdk.Fn.split('/', restApi.url)), { + originPath: `/${cdk.Fn.select(3, cdk.Fn.split('/', restApi.url))}`, + ...props, + }); + + validateSecondsInRangeOrUndefined('readTimeout', 1, 180, props.readTimeout); + validateSecondsInRangeOrUndefined('keepaliveTimeout', 1, 180, props.keepaliveTimeout); + } + + protected renderCustomOriginConfig(): cloudfront.CfnDistribution.CustomOriginConfigProperty | undefined { + return { + originSslProtocols: [cloudfront.OriginSslPolicy.TLS_V1_2], + originProtocolPolicy: cloudfront.OriginProtocolPolicy.HTTPS_ONLY, + originReadTimeout: this.props.readTimeout?.toSeconds(), + originKeepaliveTimeout: this.props.keepaliveTimeout?.toSeconds(), + }; + } +} diff --git a/packages/@aws-cdk/aws-cloudfront-origins/package.json b/packages/@aws-cdk/aws-cloudfront-origins/package.json index 0a6922fd79233..ebd025912e116 100644 --- a/packages/@aws-cdk/aws-cloudfront-origins/package.json +++ b/packages/@aws-cdk/aws-cloudfront-origins/package.json @@ -86,6 +86,7 @@ "aws-sdk": "^2.848.0" }, "dependencies": { + "@aws-cdk/aws-apigateway": "0.0.0", "@aws-cdk/aws-cloudfront": "0.0.0", "@aws-cdk/aws-elasticloadbalancingv2": "0.0.0", "@aws-cdk/aws-iam": "0.0.0", @@ -95,6 +96,7 @@ }, "homepage": "https://github.com/aws/aws-cdk", "peerDependencies": { + "@aws-cdk/aws-apigateway": "0.0.0", "@aws-cdk/aws-cloudfront": "0.0.0", "@aws-cdk/aws-elasticloadbalancingv2": "0.0.0", "@aws-cdk/aws-iam": "0.0.0", diff --git a/packages/@aws-cdk/aws-cloudfront-origins/rosetta/default.ts-fixture b/packages/@aws-cdk/aws-cloudfront-origins/rosetta/default.ts-fixture index 73800aee2e589..3c7781d356955 100644 --- a/packages/@aws-cdk/aws-cloudfront-origins/rosetta/default.ts-fixture +++ b/packages/@aws-cdk/aws-cloudfront-origins/rosetta/default.ts-fixture @@ -1,6 +1,7 @@ // Fixture with packages imported, but nothing else import { Duration, Stack } from '@aws-cdk/core'; import { Construct } from 'constructs'; +import * as apigateway from '@aws-cdk/aws-apigateway'; import * as cloudfront from '@aws-cdk/aws-cloudfront'; import * as origins from '@aws-cdk/aws-cloudfront-origins'; import * as s3 from '@aws-cdk/aws-s3'; diff --git a/packages/@aws-cdk/aws-cloudfront-origins/test/integ.rest-api-origin.ts b/packages/@aws-cdk/aws-cloudfront-origins/test/integ.rest-api-origin.ts new file mode 100644 index 0000000000000..5c130da58aa00 --- /dev/null +++ b/packages/@aws-cdk/aws-cloudfront-origins/test/integ.rest-api-origin.ts @@ -0,0 +1,17 @@ +import * as apigateway from '@aws-cdk/aws-apigateway'; +import * as cloudfront from '@aws-cdk/aws-cloudfront'; +import * as cdk from '@aws-cdk/core'; +import * as origins from '../lib'; + +const app = new cdk.App(); + +const stack = new cdk.Stack(app, 'integ-cloudfront-rest-api-origin'); + +const api = new apigateway.RestApi(stack, 'RestApi', { endpointTypes: [apigateway.EndpointType.REGIONAL] }); +api.root.addMethod('GET'); + +new cloudfront.Distribution(stack, 'Distribution', { + defaultBehavior: { origin: new origins.RestApiOrigin(api) }, +}); + +app.synth(); diff --git a/packages/@aws-cdk/aws-cloudfront-origins/test/rest-api-origin.integ.snapshot/cdk.out b/packages/@aws-cdk/aws-cloudfront-origins/test/rest-api-origin.integ.snapshot/cdk.out new file mode 100644 index 0000000000000..2efc89439fab8 --- /dev/null +++ b/packages/@aws-cdk/aws-cloudfront-origins/test/rest-api-origin.integ.snapshot/cdk.out @@ -0,0 +1 @@ +{"version":"18.0.0"} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-cloudfront-origins/test/rest-api-origin.integ.snapshot/integ-cloudfront-rest-api-origin.template.json b/packages/@aws-cdk/aws-cloudfront-origins/test/rest-api-origin.integ.snapshot/integ-cloudfront-rest-api-origin.template.json new file mode 100644 index 0000000000000..e2df5f2ac56bd --- /dev/null +++ b/packages/@aws-cdk/aws-cloudfront-origins/test/rest-api-origin.integ.snapshot/integ-cloudfront-rest-api-origin.template.json @@ -0,0 +1,237 @@ +{ + "Resources": { + "RestApi0C43BF4B": { + "Type": "AWS::ApiGateway::RestApi", + "Properties": { + "EndpointConfiguration": { + "Types": [ + "REGIONAL" + ] + }, + "Name": "RestApi" + } + }, + "RestApiCloudWatchRoleE3ED6605": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "Service": "apigateway.amazonaws.com" + } + } + ], + "Version": "2012-10-17" + }, + "ManagedPolicyArns": [ + { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":iam::aws:policy/service-role/AmazonAPIGatewayPushToCloudWatchLogs" + ] + ] + } + ] + } + }, + "RestApiAccount7C83CF5A": { + "Type": "AWS::ApiGateway::Account", + "Properties": { + "CloudWatchRoleArn": { + "Fn::GetAtt": [ + "RestApiCloudWatchRoleE3ED6605", + "Arn" + ] + } + }, + "DependsOn": [ + "RestApi0C43BF4B" + ] + }, + "RestApiDeployment180EC50368af6d4b358eff290c08cb2de07c4042": { + "Type": "AWS::ApiGateway::Deployment", + "Properties": { + "RestApiId": { + "Ref": "RestApi0C43BF4B" + }, + "Description": "Automatically created by the RestApi construct" + }, + "DependsOn": [ + "RestApiGET0F59260B" + ] + }, + "RestApiDeploymentStageprod3855DE66": { + "Type": "AWS::ApiGateway::Stage", + "Properties": { + "RestApiId": { + "Ref": "RestApi0C43BF4B" + }, + "DeploymentId": { + "Ref": "RestApiDeployment180EC50368af6d4b358eff290c08cb2de07c4042" + }, + "StageName": "prod" + }, + "DependsOn": [ + "RestApiAccount7C83CF5A" + ] + }, + "RestApiGET0F59260B": { + "Type": "AWS::ApiGateway::Method", + "Properties": { + "HttpMethod": "GET", + "ResourceId": { + "Fn::GetAtt": [ + "RestApi0C43BF4B", + "RootResourceId" + ] + }, + "RestApiId": { + "Ref": "RestApi0C43BF4B" + }, + "AuthorizationType": "NONE", + "Integration": { + "Type": "MOCK" + } + } + }, + "Distribution830FAC52": { + "Type": "AWS::CloudFront::Distribution", + "Properties": { + "DistributionConfig": { + "DefaultCacheBehavior": { + "CachePolicyId": "658327ea-f89d-4fab-a63d-7e88639e58f6", + "Compress": true, + "TargetOriginId": "integcloudfrontrestapioriginDistributionOrigin1B3EE0E72", + "ViewerProtocolPolicy": "allow-all" + }, + "Enabled": true, + "HttpVersion": "http2", + "IPV6Enabled": true, + "Origins": [ + { + "CustomOriginConfig": { + "OriginProtocolPolicy": "https-only", + "OriginSSLProtocols": [ + "TLSv1.2" + ] + }, + "DomainName": { + "Fn::Select": [ + 2, + { + "Fn::Split": [ + "/", + { + "Fn::Join": [ + "", + [ + "https://", + { + "Ref": "RestApi0C43BF4B" + }, + ".execute-api.", + { + "Ref": "AWS::Region" + }, + ".", + { + "Ref": "AWS::URLSuffix" + }, + "/", + { + "Ref": "RestApiDeploymentStageprod3855DE66" + }, + "/" + ] + ] + } + ] + } + ] + }, + "Id": "integcloudfrontrestapioriginDistributionOrigin1B3EE0E72", + "OriginPath": { + "Fn::Join": [ + "", + [ + "/", + { + "Fn::Select": [ + 3, + { + "Fn::Split": [ + "/", + { + "Fn::Join": [ + "", + [ + "https://", + { + "Ref": "RestApi0C43BF4B" + }, + ".execute-api.", + { + "Ref": "AWS::Region" + }, + ".", + { + "Ref": "AWS::URLSuffix" + }, + "/", + { + "Ref": "RestApiDeploymentStageprod3855DE66" + }, + "/" + ] + ] + } + ] + } + ] + } + ] + ] + } + } + ] + } + } + } + }, + "Outputs": { + "RestApiEndpoint0551178A": { + "Value": { + "Fn::Join": [ + "", + [ + "https://", + { + "Ref": "RestApi0C43BF4B" + }, + ".execute-api.", + { + "Ref": "AWS::Region" + }, + ".", + { + "Ref": "AWS::URLSuffix" + }, + "/", + { + "Ref": "RestApiDeploymentStageprod3855DE66" + }, + "/" + ] + ] + } + } + } +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-cloudfront-origins/test/rest-api-origin.integ.snapshot/integ.json b/packages/@aws-cdk/aws-cloudfront-origins/test/rest-api-origin.integ.snapshot/integ.json new file mode 100644 index 0000000000000..a6290c917cec4 --- /dev/null +++ b/packages/@aws-cdk/aws-cloudfront-origins/test/rest-api-origin.integ.snapshot/integ.json @@ -0,0 +1,14 @@ +{ + "version": "18.0.0", + "testCases": { + "integ.rest-api-origin": { + "stacks": [ + "integ-cloudfront-rest-api-origin" + ], + "diffAssets": false, + "stackUpdateWorkflow": true + } + }, + "synthContext": {}, + "enableLookups": false +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-cloudfront-origins/test/rest-api-origin.integ.snapshot/manifest.json b/packages/@aws-cdk/aws-cloudfront-origins/test/rest-api-origin.integ.snapshot/manifest.json new file mode 100644 index 0000000000000..6c1cd993b1fe4 --- /dev/null +++ b/packages/@aws-cdk/aws-cloudfront-origins/test/rest-api-origin.integ.snapshot/manifest.json @@ -0,0 +1,70 @@ +{ + "version": "18.0.0", + "artifacts": { + "Tree": { + "type": "cdk:tree", + "properties": { + "file": "tree.json" + } + }, + "integ-cloudfront-rest-api-origin": { + "type": "aws:cloudformation:stack", + "environment": "aws://unknown-account/unknown-region", + "properties": { + "templateFile": "integ-cloudfront-rest-api-origin.template.json", + "validateOnSynth": false + }, + "metadata": { + "/integ-cloudfront-rest-api-origin/RestApi/Resource": [ + { + "type": "aws:cdk:logicalId", + "data": "RestApi0C43BF4B" + } + ], + "/integ-cloudfront-rest-api-origin/RestApi/CloudWatchRole/Resource": [ + { + "type": "aws:cdk:logicalId", + "data": "RestApiCloudWatchRoleE3ED6605" + } + ], + "/integ-cloudfront-rest-api-origin/RestApi/Account": [ + { + "type": "aws:cdk:logicalId", + "data": "RestApiAccount7C83CF5A" + } + ], + "/integ-cloudfront-rest-api-origin/RestApi/Deployment/Resource": [ + { + "type": "aws:cdk:logicalId", + "data": "RestApiDeployment180EC50368af6d4b358eff290c08cb2de07c4042" + } + ], + "/integ-cloudfront-rest-api-origin/RestApi/DeploymentStage.prod/Resource": [ + { + "type": "aws:cdk:logicalId", + "data": "RestApiDeploymentStageprod3855DE66" + } + ], + "/integ-cloudfront-rest-api-origin/RestApi/Endpoint": [ + { + "type": "aws:cdk:logicalId", + "data": "RestApiEndpoint0551178A" + } + ], + "/integ-cloudfront-rest-api-origin/RestApi/Default/GET/Resource": [ + { + "type": "aws:cdk:logicalId", + "data": "RestApiGET0F59260B" + } + ], + "/integ-cloudfront-rest-api-origin/Distribution/Resource": [ + { + "type": "aws:cdk:logicalId", + "data": "Distribution830FAC52" + } + ] + }, + "displayName": "integ-cloudfront-rest-api-origin" + } + } +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-cloudfront-origins/test/rest-api-origin.integ.snapshot/tree.json b/packages/@aws-cdk/aws-cloudfront-origins/test/rest-api-origin.integ.snapshot/tree.json new file mode 100644 index 0000000000000..21004e47d97fb --- /dev/null +++ b/packages/@aws-cdk/aws-cloudfront-origins/test/rest-api-origin.integ.snapshot/tree.json @@ -0,0 +1,370 @@ +{ + "version": "tree-0.1", + "tree": { + "id": "App", + "path": "", + "children": { + "Tree": { + "id": "Tree", + "path": "Tree", + "constructInfo": { + "fqn": "@aws-cdk/core.Construct", + "version": "0.0.0" + } + }, + "integ-cloudfront-rest-api-origin": { + "id": "integ-cloudfront-rest-api-origin", + "path": "integ-cloudfront-rest-api-origin", + "children": { + "RestApi": { + "id": "RestApi", + "path": "integ-cloudfront-rest-api-origin/RestApi", + "children": { + "Resource": { + "id": "Resource", + "path": "integ-cloudfront-rest-api-origin/RestApi/Resource", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::ApiGateway::RestApi", + "aws:cdk:cloudformation:props": { + "endpointConfiguration": { + "types": [ + "REGIONAL" + ] + }, + "name": "RestApi" + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-apigateway.CfnRestApi", + "version": "0.0.0" + } + }, + "CloudWatchRole": { + "id": "CloudWatchRole", + "path": "integ-cloudfront-rest-api-origin/RestApi/CloudWatchRole", + "children": { + "Resource": { + "id": "Resource", + "path": "integ-cloudfront-rest-api-origin/RestApi/CloudWatchRole/Resource", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::IAM::Role", + "aws:cdk:cloudformation:props": { + "assumeRolePolicyDocument": { + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "Service": "apigateway.amazonaws.com" + } + } + ], + "Version": "2012-10-17" + }, + "managedPolicyArns": [ + { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":iam::aws:policy/service-role/AmazonAPIGatewayPushToCloudWatchLogs" + ] + ] + } + ] + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-iam.CfnRole", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-iam.Role", + "version": "0.0.0" + } + }, + "Account": { + "id": "Account", + "path": "integ-cloudfront-rest-api-origin/RestApi/Account", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::ApiGateway::Account", + "aws:cdk:cloudformation:props": { + "cloudWatchRoleArn": { + "Fn::GetAtt": [ + "RestApiCloudWatchRoleE3ED6605", + "Arn" + ] + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-apigateway.CfnAccount", + "version": "0.0.0" + } + }, + "Deployment": { + "id": "Deployment", + "path": "integ-cloudfront-rest-api-origin/RestApi/Deployment", + "children": { + "Resource": { + "id": "Resource", + "path": "integ-cloudfront-rest-api-origin/RestApi/Deployment/Resource", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::ApiGateway::Deployment", + "aws:cdk:cloudformation:props": { + "restApiId": { + "Ref": "RestApi0C43BF4B" + }, + "description": "Automatically created by the RestApi construct" + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-apigateway.CfnDeployment", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-apigateway.Deployment", + "version": "0.0.0" + } + }, + "DeploymentStage.prod": { + "id": "DeploymentStage.prod", + "path": "integ-cloudfront-rest-api-origin/RestApi/DeploymentStage.prod", + "children": { + "Resource": { + "id": "Resource", + "path": "integ-cloudfront-rest-api-origin/RestApi/DeploymentStage.prod/Resource", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::ApiGateway::Stage", + "aws:cdk:cloudformation:props": { + "restApiId": { + "Ref": "RestApi0C43BF4B" + }, + "deploymentId": { + "Ref": "RestApiDeployment180EC50368af6d4b358eff290c08cb2de07c4042" + }, + "stageName": "prod" + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-apigateway.CfnStage", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/core.Resource", + "version": "0.0.0" + } + }, + "Endpoint": { + "id": "Endpoint", + "path": "integ-cloudfront-rest-api-origin/RestApi/Endpoint", + "constructInfo": { + "fqn": "@aws-cdk/core.CfnOutput", + "version": "0.0.0" + } + }, + "Default": { + "id": "Default", + "path": "integ-cloudfront-rest-api-origin/RestApi/Default", + "children": { + "GET": { + "id": "GET", + "path": "integ-cloudfront-rest-api-origin/RestApi/Default/GET", + "children": { + "Resource": { + "id": "Resource", + "path": "integ-cloudfront-rest-api-origin/RestApi/Default/GET/Resource", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::ApiGateway::Method", + "aws:cdk:cloudformation:props": { + "httpMethod": "GET", + "resourceId": { + "Fn::GetAtt": [ + "RestApi0C43BF4B", + "RootResourceId" + ] + }, + "restApiId": { + "Ref": "RestApi0C43BF4B" + }, + "authorizationType": "NONE", + "integration": { + "type": "MOCK" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-apigateway.CfnMethod", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-apigateway.Method", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-apigateway.ResourceBase", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/core.Resource", + "version": "0.0.0" + } + }, + "Distribution": { + "id": "Distribution", + "path": "integ-cloudfront-rest-api-origin/Distribution", + "children": { + "Origin1": { + "id": "Origin1", + "path": "integ-cloudfront-rest-api-origin/Distribution/Origin1", + "constructInfo": { + "fqn": "@aws-cdk/core.Construct", + "version": "0.0.0" + } + }, + "Resource": { + "id": "Resource", + "path": "integ-cloudfront-rest-api-origin/Distribution/Resource", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::CloudFront::Distribution", + "aws:cdk:cloudformation:props": { + "distributionConfig": { + "enabled": true, + "origins": [ + { + "domainName": { + "Fn::Select": [ + 2, + { + "Fn::Split": [ + "/", + { + "Fn::Join": [ + "", + [ + "https://", + { + "Ref": "RestApi0C43BF4B" + }, + ".execute-api.", + { + "Ref": "AWS::Region" + }, + ".", + { + "Ref": "AWS::URLSuffix" + }, + "/", + { + "Ref": "RestApiDeploymentStageprod3855DE66" + }, + "/" + ] + ] + } + ] + } + ] + }, + "id": "integcloudfrontrestapioriginDistributionOrigin1B3EE0E72", + "originPath": { + "Fn::Join": [ + "", + [ + "/", + { + "Fn::Select": [ + 3, + { + "Fn::Split": [ + "/", + { + "Fn::Join": [ + "", + [ + "https://", + { + "Ref": "RestApi0C43BF4B" + }, + ".execute-api.", + { + "Ref": "AWS::Region" + }, + ".", + { + "Ref": "AWS::URLSuffix" + }, + "/", + { + "Ref": "RestApiDeploymentStageprod3855DE66" + }, + "/" + ] + ] + } + ] + } + ] + } + ] + ] + }, + "customOriginConfig": { + "originSslProtocols": [ + "TLSv1.2" + ], + "originProtocolPolicy": "https-only" + } + } + ], + "defaultCacheBehavior": { + "pathPattern": "*", + "targetOriginId": "integcloudfrontrestapioriginDistributionOrigin1B3EE0E72", + "cachePolicyId": "658327ea-f89d-4fab-a63d-7e88639e58f6", + "compress": true, + "viewerProtocolPolicy": "allow-all" + }, + "httpVersion": "http2", + "ipv6Enabled": true + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-cloudfront.CfnDistribution", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-cloudfront.Distribution", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/core.Stack", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/core.App", + "version": "0.0.0" + } + } +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-cloudfront-origins/test/rest-api-origin.test.ts b/packages/@aws-cdk/aws-cloudfront-origins/test/rest-api-origin.test.ts new file mode 100644 index 0000000000000..cc89ec8e59976 --- /dev/null +++ b/packages/@aws-cdk/aws-cloudfront-origins/test/rest-api-origin.test.ts @@ -0,0 +1,62 @@ +import * as apigateway from '@aws-cdk/aws-apigateway'; +import { Stack } from '@aws-cdk/core'; +import { RestApiOrigin } from '../lib'; + +let stack: Stack; + +beforeEach(() => { + stack = new Stack(); +}); + +test('Correctly renders the origin', () => { + const api = new apigateway.RestApi(stack, 'RestApi'); + api.root.addMethod('GET'); + + const origin = new RestApiOrigin(api); + const originBindConfig = origin.bind(stack, { originId: 'StackOrigin029E19582' }); + + expect(stack.resolve(originBindConfig.originProperty)).toEqual({ + id: 'StackOrigin029E19582', + domainName: { + 'Fn::Select': [2, { + 'Fn::Split': ['/', { + 'Fn::Join': ['', [ + 'https://', { Ref: 'RestApi0C43BF4B' }, + '.execute-api.', + { Ref: 'AWS::Region' }, + '.', + { Ref: 'AWS::URLSuffix' }, + '/', + { Ref: 'RestApiDeploymentStageprod3855DE66' }, + '/', + ]], + }], + }], + }, + originPath: { + 'Fn::Join': ['', ['/', { + 'Fn::Select': [3, { + 'Fn::Split': ['/', { + 'Fn::Join': ['', [ + 'https://', + { Ref: 'RestApi0C43BF4B' }, + '.execute-api.', + { Ref: 'AWS::Region' }, + '.', + { Ref: 'AWS::URLSuffix' }, + '/', + { Ref: 'RestApiDeploymentStageprod3855DE66' }, + '/', + ]], + }], + }], + }]], + }, + customOriginConfig: { + originProtocolPolicy: 'https-only', + originSslProtocols: [ + 'TLSv1.2', + ], + }, + }); +}); diff --git a/packages/@aws-cdk/aws-cloudfront/lib/origin.ts b/packages/@aws-cdk/aws-cloudfront/lib/origin.ts index 12672e5406abb..7addd7d4ebaca 100644 --- a/packages/@aws-cdk/aws-cloudfront/lib/origin.ts +++ b/packages/@aws-cdk/aws-cloudfront/lib/origin.ts @@ -51,17 +51,9 @@ export interface IOrigin { } /** - * Properties to define an Origin. + * Options to define an Origin. */ -export interface OriginProps { - /** - * An optional path that CloudFront appends to the origin domain name when CloudFront requests content from the origin. - * Must begin, but not end, with '/' (e.g., '/production/images'). - * - * @default '/' - */ - readonly originPath?: string; - +export interface OriginOptions { /** * The number of seconds that CloudFront waits when trying to establish a connection to the origin. * Valid values are 1-10 seconds, inclusive. @@ -94,6 +86,19 @@ export interface OriginProps { readonly originShieldRegion?: string; } +/** + * Properties to define an Origin. + */ +export interface OriginProps extends OriginOptions { + /** + * An optional path that CloudFront appends to the origin domain name when CloudFront requests content from the origin. + * Must begin, but not end, with '/' (e.g., '/production/images'). + * + * @default '/' + */ + readonly originPath?: string; +} + /** * Options passed to Origin.bind(). */ @@ -236,4 +241,4 @@ function validateCustomHeaders(customHeaders?: Record) { if (prohibitedHeaderPrefixMatches.length !== 0) { throw new Error(`The following headers cannot be used as prefixes for custom origin headers: ${prohibitedHeaderPrefixMatches.join(', ')}`); } -} \ No newline at end of file +}