From e23ddc5aa1085ee34a04f8fef99214248160e495 Mon Sep 17 00:00:00 2001 From: Nick Lynch Date: Tue, 16 Jun 2020 10:27:28 +0100 Subject: [PATCH 01/11] RFC: 0171 CloudFront Redesign - initial rough ideas --- text/0171-cloudfront-redesign.md | 289 +++++++++++++++++++++++++++++++ 1 file changed, 289 insertions(+) create mode 100644 text/0171-cloudfront-redesign.md diff --git a/text/0171-cloudfront-redesign.md b/text/0171-cloudfront-redesign.md new file mode 100644 index 000000000..e9050d3ea --- /dev/null +++ b/text/0171-cloudfront-redesign.md @@ -0,0 +1,289 @@ +--- +feature name: CloudFront redesign +start date: 2020-06-15 +rfc pr: +related issue: https://github.com/aws/aws-cdk-rfcs/issues/171 +--- + +# Summary + +(Draft) Proposal to redesign the @aws-cdk/aws-cloudfront module. + +The current module could be enhanced to be friendlier and less error-prone. + +# README + +Example usages: + +## Use case #1 - Simple S3-bucket distribution + +Creates a distribution based on an S3 bucket, and sets up an origin access identity so the +distribution can access the contents of the bucket (if non-public). + +**Before:** + +```ts +new CloudFrontWebDistribution(this, 'dist', { + originConfigs: [{ + s3OriginSource: { + s3BucketSource: myBucket, + originAccessIdentity: OriginAccessIdentity.fromOriginAccessIdentityName(this, 'oai', 'distOAI'), + }, + behaviors: [{ isDefaultBehavior: true }], + }] +}); +``` + +**After:** + +```ts +new Distribution(this, 'dist', { + origins: [Origin.fromS3Bucket(myBucket)], +}); +``` + +## Use case #2 - S3-bucket distribution with a Lamda@Edge function + +Creates a distribution based on an S3 bucket, and adds a Lambda function as a Lambda@Edge association +to origin responses. + +**Before:** + +```ts +new CloudFrontWebDistribution(this, 'dist', { + originConfigs: [{ + s3OriginSource: { + s3BucketSource: myBucket, + originAccessIdentity: OriginAccessIdentity.fromOriginAccessIdentityName(this, 'oai', 'distOAI'), + }, + behaviors: [{ + isDefaultBehavior: true, + lambdaFunctionAssociations: [{ + lambdaFunction: myFunctionVersion, + eventType: LambdaEdgeEventType.ORIGIN_RESPONSE, + }] + }], + }] +}); +``` + +**After:** + +```ts +const myBucketOrigin = Origin.fromS3Bucket(myBucket); +new Distribution(this, 'dist', { + origins: [myBucketOrigin], + edgeFunctions: [{ + lambdaFunctionVersion: myFunctionVersion, + eventType: LambdaEdgeEventType.ORIGIN_RESPONSE, + origin: myBucketOrigin, + }], +}); +``` + +## Use Case #3 - S3 bucket distribution with certificate and custom TTL + +**Before:** + +```ts +new CloudFrontWebDistribution(this, 'dist', { + originConfigs: [{ + s3OriginSource: { + s3BucketSource: myBucket, + originAccessIdentity: OriginAccessIdentity.fromOriginAccessIdentityName(this, 'oai', 'distOAI'), + }, + behaviors: [{ + isDefaultBehavior: true, + defaultTtl: Duration.minutes(10), + }], + }], + viewerCertificate: ViewerCertificate.fromAcmCertificate(myCertificate), +}); +``` + +**After:** + +```ts +new Distribution(this, 'dist', { + origins: [Origin.fromS3Bucket(myBucket)], + behaviors: [{defaultTtl: Duration.minutes(10)}], + certificate: myCertificate, +}); +``` + +## Use case #4 - Complicate multi-origin setup with multiple behaviors per origin and a Lambda@Edge function + +Stress test. This is what the config looks like with more stuff. An S3 bucket origin with the default and +one custom behavior, and a LoadBalancedFargateService origin that is HTTPS only. + +**Before:** + +```ts +new CloudFrontWebDistribution(this, 'dist', { + originConfigs: [{ + s3OriginSource: { + s3BucketSource: websiteBucket, + originAccessIdentity: OriginAccessIdentity.fromOriginAccessIdentityName(this, 'oai', 'distOAI'), + }, + behaviors: [ + { isDefaultBehavior: true }, + { + forwardedValues: { queryString: true }, + pathPattern: 'images/*.jpg', + } + ], + }, + { + customOriginSource: { + domainName: lbFargateService.loadBalancer.loadBalancerDnsName, + originProtocolPolicy: OriginProtocolPolicy.HTTPS_ONLY, + }, + behaviors: [{ isDefaultBehavior: true }], + }], +}); +``` + +**After:** + +```ts +const myBucketOrigin = Origin.fromS3Bucket(myBucket); +new Distribution(this, 'dist', { + origins: [ + myBucketOrigin, + Origin.fromApplicationLoadBalancer(lbFargateService.loadBalancer), + ], + behaviors: [{ + origin: myBucketOrigin, + forwardedValues: { queryString: true }, + pathPattern: 'images/*.jpg',], + }, +}); +``` + +# Motivation + +> Why are we doing this? What use cases does it support? What is the expected +> outcome? + +***TODO*** + +# Design Summary + +> Summarize the approach of the feature design in a couple of sentences. Call out +> any known patterns or best practices the design is based around. + +***TODO*** + +# Detailed Design + +> This is the bulk of the RFC. Explain the design in enough detail for somebody +> familiar with CDK to understand, and for somebody familiar with the +> implementation to implement. This should get into specifics and corner-cases, +> and include examples of how the feature is used. Any new terminology should be +> defined here. +> +> Include any diagrams and/or visualizations that help to demonstrate the design. +> Here are some tools that we often use: +> +> - [Graphviz](http://graphviz.it/#/gallery/structs.gv) +> - [PlantText](https://www.planttext.com) + +Proposed interfaces: + +```ts +export interface DistributionProps { + origins?: Origin[]; + behaviors?: Behavior[]; + edgeFunctions?: LambdaEdgeFunction[]; + aliases?: string[]; + comment?: string; + defaultRootObject?: string; + enableIpV6?: boolean; + httpVersion?: HttpVersion; + priceClass?: PriceClass; + viewerProtocolPolicy?: ViewerProtocolPolicy; + webACLId?: string; + errorConfigurations?: ErrorConfiguration[]; + loggingBucket?: IBucket; + loggingIncludeCookies?: boolean; + loggingPrefix?: string; + certificate?: certificatemanager.ICertificate; + sslMinimumProtocolVersion?: SslProtocolVersion; + sslSupportMethod?: SslSupportMethod; + geoLocations?: string[]; // Is there a CDK element for country codes? + geoRestrictionType?: GeoRestrictionType; +} + +export interface BaseOriginProps { + domainName: string, + + connectionAttempts?: int, + connectionTimeoutSeconds?: int, + id?: string, + originCustomHeaders?: { [key: string]: string }, + originPath?: string, +} + +... +``` + +# Drawbacks + +> Why should we _not_ do this? Please consider: +> +> - implementation cost, both in term of code size and complexity +> - whether the proposed feature can be implemented in user space +> - the impact on teaching people how to use CDK +> - integration of this feature with other existing and planned features +> - cost of migrating existing CDK applications (is it a breaking change?) +> +> There are tradeoffs to choosing any path. Attempt to identify them here. + +***TODO*** + +* There is a significant user base that consumes the existing interface. Despite being marked as +'experimental', CloudFront is one of the oldest modules in the CDK and is relied on by many customers. +Any breaking changes to the existing interface(s) will need to be carefully considered. + +# Rationale and Alternatives + +> - Why is this design the best in the space of possible designs? +> - What other designs have been considered and what is the rationale for not +> choosing them? +> - What is the impact of not doing this? + +***TODO*** + +# Adoption Strategy + +> If we implement this proposal, how will existing CDK developers adopt it? Is +> this a breaking change? How can we assist in adoption? + +***TODO*** - Great question! + +# Unresolved questions + +> - What parts of the design do you expect to resolve through the RFC process +> before this gets merged? +> - What parts of the design do you expect to resolve through the implementation +> of this feature before stabilization? +> - What related issues do you consider out of scope for this RFC that could be +> addressed in the future independently of the solution that comes out of this +> RFC? + +***TODO*** + +# Future Possibilities + +> Think about what the natural extension and evolution of your proposal would be +> and how it would affect CDK as whole. Try to use this section as a tool to more +> fully consider all possible interactions with the project and ecosystem in your +> proposal. Also consider how this fits into the roadmap for the project. +> +> This is a good place to "dump ideas", if they are out of scope for the RFC you +> are writing but are otherwise related. +> +> If you have tried and cannot think of any future possibilities, you may simply +> state that you cannot think of anything. + +***TODO*** From 40c57ed1c47700b7c7df3fc49518303e1d909608 Mon Sep 17 00:00:00 2001 From: Nick Lynch Date: Thu, 18 Jun 2020 10:17:51 +0100 Subject: [PATCH 02/11] Iteration #2 of the API, sans the Lambda@Edge functionality. Still in draft state, but hopefully getting closer. --- text/0171-cloudfront-redesign.md | 288 ++++++++++++++----------------- 1 file changed, 131 insertions(+), 157 deletions(-) diff --git a/text/0171-cloudfront-redesign.md b/text/0171-cloudfront-redesign.md index e9050d3ea..3688b05ba 100644 --- a/text/0171-cloudfront-redesign.md +++ b/text/0171-cloudfront-redesign.md @@ -9,170 +9,186 @@ related issue: https://github.com/aws/aws-cdk-rfcs/issues/171 (Draft) Proposal to redesign the @aws-cdk/aws-cloudfront module. -The current module could be enhanced to be friendlier and less error-prone. +The current module does not adhere to the best practice naming conventions or ease-of-use patterns +that are present in the other CDK modules. A redesign of the API will allow for friendly, easier +access to common patterns and usages. + +This RFC does not attempt to lay out the entire API; rather, it focuses on a complete re-write of +the module README with a focus on the most common use cases and how they work with the new design. +More detailed designs and incremental API improvements will be tracked as part of GitHub Project Board +once the RFC is approved. + +--- # README -Example usages: +Amazon CloudFront is a web service that speeds up distribution of your static and dynamic web content, such as .html, .css, .js, and image files, to +your users. CloudFront delivers your content through a worldwide network of data centers called edge locations. When a user requests content that +you're serving with CloudFront, the user is routed to the edge location that provides the lowest latency, so that content is delivered with the best +possible performance. -## Use case #1 - Simple S3-bucket distribution +## Creating a distribution -Creates a distribution based on an S3 bucket, and sets up an origin access identity so the -distribution can access the contents of the bucket (if non-public). +CloudFront distributions deliver your content from one or more origins; an origin is the location where you store the original version of your +content. Origins can be created from S3 buckets or a custom origin (HTTP server). -**Before:** +An S3 bucket can be added as an origin. If the bucket is configured as a website endpoint, the distribution can use S3 redirects and S3 custom error +documents. ```ts -new CloudFrontWebDistribution(this, 'dist', { - originConfigs: [{ - s3OriginSource: { - s3BucketSource: myBucket, - originAccessIdentity: OriginAccessIdentity.fromOriginAccessIdentityName(this, 'oai', 'distOAI'), - }, - behaviors: [{ isDefaultBehavior: true }], - }] +// Creates a distribution for a S3 bucket. +const myBucket = new s3.Bucket(...); +new Distribution(this, 'myDist', { + origin: Origin.fromBucket(this, 'myOrigin', myBucket) +}); + +// Creates a distribution for a S3 bucket that has been configured for website hosting. +const myWebsiteBucket = new s3.Bucket(...); +new Distribution(this, 'myDist', { + origin: Origin.fromWebsiteBucket(this, 'myOrigin', myBucket) }); ``` -**After:** +Both of the S3 Origin options will automatically create an origin access identity and grant it access to the underlying bucket. This can be used in +conjunction with a bucket that is not public to require that your users access your content using CloudFront URLs and not S3 URLs directly. + +Origins can also be created from other resources (e.g., load balancers, API gateways), or from any accessible HTTP server. ```ts -new Distribution(this, 'dist', { - origins: [Origin.fromS3Bucket(myBucket)], +// Creates a distribution for an application load balancer. +const myLoadBalancer = new elbv2.ApplicationLoadBalancer(...); +new Distribution(this, 'myDist', { + origin: Origin.fromLoadBalancerV2(this, 'myOrigin', myLoadBalancer) }); -``` -## Use case #2 - S3-bucket distribution with a Lamda@Edge function +// Creates a distribution for an HTTP server. +new Distribution(this, 'myDist', { + origin: Origin.fromHTTPServer(this, 'myOrigin', { + domainName: 'www.example.com', + originProtocolPolicy: OriginProtocolPolicy.HTTPS_ONLY + }) +}); +``` -Creates a distribution based on an S3 bucket, and adds a Lambda function as a Lambda@Edge association -to origin responses. +## Domain Names and Certificates -**Before:** +When you create a distribution, CloudFront returns a domain name for the distribution, for example: `d111111abcdef8.cloudfront.net`. If you want to +use your own domain name, such as `www.example.com`, you can add an alternate domain name to your distribution. ```ts -new CloudFrontWebDistribution(this, 'dist', { - originConfigs: [{ - s3OriginSource: { - s3BucketSource: myBucket, - originAccessIdentity: OriginAccessIdentity.fromOriginAccessIdentityName(this, 'oai', 'distOAI'), - }, - behaviors: [{ - isDefaultBehavior: true, - lambdaFunctionAssociations: [{ - lambdaFunction: myFunctionVersion, - eventType: LambdaEdgeEventType.ORIGIN_RESPONSE, - }] - }], - }] -}); +new Distribution(this, 'myDist', { + origin: Origin.fromBucket(myBucket), + aliases: ['www.example.com'] +}) ``` -**After:** +CloudFront distributions use a default certificate (`*.cloudfront.net`) to support HTTPS by default. If you want to support HTTPS with your own domain +name, you must associate a certificate with your distribution that contains your domain name. The certificate must be present in the AWS Certificate +Manager (ACM) service in the US East (N. Virginia) region; the certificate may either be created by ACM, or created elsewhere and imported into ACM. ```ts -const myBucketOrigin = Origin.fromS3Bucket(myBucket); -new Distribution(this, 'dist', { - origins: [myBucketOrigin], - edgeFunctions: [{ - lambdaFunctionVersion: myFunctionVersion, - eventType: LambdaEdgeEventType.ORIGIN_RESPONSE, - origin: myBucketOrigin, - }], +const myCertificate = new certmgr.DnsValidatedCertificate(this, 'mySiteCert', { + domainName: 'www.example.com', + hostedZone, +}); +new Distribution(this, 'myDist', { + origin: Origin.fromBucket(myBucket), + certificate: myCertificate, }); ``` -## Use Case #3 - S3 bucket distribution with certificate and custom TTL +Note that in the above example the aliases are inferred from the certificate and do not need to be explicitly provided. -**Before:** +## Caching Behaviors + +Each distribution has a default cache behavior which applies to all requests to that distribution; additional cache behaviors may be specified for a +given URL path pattern. Cache behaviors allowing routing with multiple origins, controlling which HTTP methods to support, whether to require users to +use HTTPS, and what query strings or cookies to forward to your origin, among other behaviors. + +The properties of the default cache behavior can be adjusted as part of the distribution creation. The following example shows configuring the HTTP +methods and viewer protocol policy of the cache. ```ts -new CloudFrontWebDistribution(this, 'dist', { - originConfigs: [{ - s3OriginSource: { - s3BucketSource: myBucket, - originAccessIdentity: OriginAccessIdentity.fromOriginAccessIdentityName(this, 'oai', 'distOAI'), - }, - behaviors: [{ - isDefaultBehavior: true, - defaultTtl: Duration.minutes(10), - }], - }], - viewerCertificate: ViewerCertificate.fromAcmCertificate(myCertificate), +const myWebDistribution = new Distribution(this, 'myDist', { + origin: Origin.fromHTTPServer(this, 'myOrigin', { + domainName: 'www.example.com', + originProtocolPolicy: OriginProtocolPolicy.HTTPS_ONLY + }), + behavior: { + allowedMethods: AllowedMethods.ALL, + viewerProtocolPolicy: ViewerProtocolPolicy.REDIRECT_TO_HTTPS + } }); ``` -**After:** +Additional cache behaviors can be specified at creation, or added to the origin(s) after the initial creation. These additional cache behaviors enable +customization on a specific set of resources based on a URL path pattern. For example, we can add a behavior to `myWebDistribution` to override the +default time-to-live (TTL) for all of the images. ```ts -new Distribution(this, 'dist', { - origins: [Origin.fromS3Bucket(myBucket)], - behaviors: [{defaultTtl: Duration.minutes(10)}], - certificate: myCertificate, -}); +myWebDistribution.origin.addBehavior('/images/*.jpg', { + viewerProtocolPolicy: ViewerProtocolPolicy.REDIRECT_TO_HTTPS, + defaultTtl: cdk.Duration.days(7), +}) ``` -## Use case #4 - Complicate multi-origin setup with multiple behaviors per origin and a Lambda@Edge function +## Multiple Origins -Stress test. This is what the config looks like with more stuff. An S3 bucket origin with the default and -one custom behavior, and a LoadBalancedFargateService origin that is HTTPS only. - -**Before:** +A distribution may have multiple origins in addition to the default origin; each additional origin must have (at least) one behavior to route requests +to that origin. A common pattern might be to serve all static assets from an S3 bucket, but all dynamic content served from a web server. The +following example shows how such a setup might be created: ```ts -new CloudFrontWebDistribution(this, 'dist', { - originConfigs: [{ - s3OriginSource: { - s3BucketSource: websiteBucket, - originAccessIdentity: OriginAccessIdentity.fromOriginAccessIdentityName(this, 'oai', 'distOAI'), - }, - behaviors: [ - { isDefaultBehavior: true }, - { - forwardedValues: { queryString: true }, - pathPattern: 'images/*.jpg', - } - ], - }, - { - customOriginSource: { - domainName: lbFargateService.loadBalancer.loadBalancerDnsName, - originProtocolPolicy: OriginProtocolPolicy.HTTPS_ONLY, - }, - behaviors: [{ isDefaultBehavior: true }], - }], +const myWebsiteBucket = new s3.Bucket(...); +const myMultiOriginDistribution = new Distribution(this, 'myDist', { + origin: Origin.fromWebsiteBucket(this, 'myOrigin', myBucket), + additionalOrigins: [Origin.fromLoadBalancerV2(this, 'myOrigin', myLoadBalancer, { + pathPattern: '/api/*', + allowedMethods: AllowedMethods.ALL, + forwardQueryString: true, + })]; }); ``` -**After:** +You can specify an origin group for your CloudFront origin if, for example, you want to configure origin failover for scenarios when you need high +availability. Use origin failover to designate a primary origin for CloudFront plus a second origin that CloudFront automatically switches to when the +primary origin returns specific HTTP status code failure responses. An origin group can be created and specified as the primary (or additional) origin +for the distribution. ```ts -const myBucketOrigin = Origin.fromS3Bucket(myBucket); -new Distribution(this, 'dist', { - origins: [ - myBucketOrigin, - Origin.fromApplicationLoadBalancer(lbFargateService.loadBalancer), - ], - behaviors: [{ - origin: myBucketOrigin, - forwardedValues: { queryString: true }, - pathPattern: 'images/*.jpg',], - }, -}); +const myOriginGroup = Origin.groupFromOrigins( + primaryOrigin: Origin.fromLoadBalancerV2(this, 'myOrigin', myLoadBalancer), + fallbackOrigin: Origin.fromBucket(this, 'myFallbackOrigin', myBucket), + fallbackStatusCodes: [500, 503] +); +new Distribution(this, 'myDist', { origin: myOriginGroup }); ``` +The above will create both origins and a single origin group with the load balancer origin falling back to the S3 bucket in case of 500 or 503 errors. + +## Lambda@Edge + +Lambda@Edge is an extension of AWS Lambda, a compute service that lets you execute functions that customize the content that CloudFront delivers. You +can author Node.js or Python functions in the US East (N. Virginia) region, and then execute them in AWS locations globally that are closer to the +viewer, without provisioning or managing servers. Lambda@Edge functions are associated with a specific behavior and + +**TODO:** Design the Lambda@Edge API. + +--- + # Motivation > Why are we doing this? What use cases does it support? What is the expected > outcome? -***TODO*** +**_TODO_** # Design Summary > Summarize the approach of the feature design in a couple of sentences. Call out > any known patterns or best practices the design is based around. -***TODO*** +**_TODO_** # Detailed Design @@ -188,45 +204,6 @@ new Distribution(this, 'dist', { > - [Graphviz](http://graphviz.it/#/gallery/structs.gv) > - [PlantText](https://www.planttext.com) -Proposed interfaces: - -```ts -export interface DistributionProps { - origins?: Origin[]; - behaviors?: Behavior[]; - edgeFunctions?: LambdaEdgeFunction[]; - aliases?: string[]; - comment?: string; - defaultRootObject?: string; - enableIpV6?: boolean; - httpVersion?: HttpVersion; - priceClass?: PriceClass; - viewerProtocolPolicy?: ViewerProtocolPolicy; - webACLId?: string; - errorConfigurations?: ErrorConfiguration[]; - loggingBucket?: IBucket; - loggingIncludeCookies?: boolean; - loggingPrefix?: string; - certificate?: certificatemanager.ICertificate; - sslMinimumProtocolVersion?: SslProtocolVersion; - sslSupportMethod?: SslSupportMethod; - geoLocations?: string[]; // Is there a CDK element for country codes? - geoRestrictionType?: GeoRestrictionType; -} - -export interface BaseOriginProps { - domainName: string, - - connectionAttempts?: int, - connectionTimeoutSeconds?: int, - id?: string, - originCustomHeaders?: { [key: string]: string }, - originPath?: string, -} - -... -``` - # Drawbacks > Why should we _not_ do this? Please consider: @@ -239,12 +216,6 @@ export interface BaseOriginProps { > > There are tradeoffs to choosing any path. Attempt to identify them here. -***TODO*** - -* There is a significant user base that consumes the existing interface. Despite being marked as -'experimental', CloudFront is one of the oldest modules in the CDK and is relied on by many customers. -Any breaking changes to the existing interface(s) will need to be carefully considered. - # Rationale and Alternatives > - Why is this design the best in the space of possible designs? @@ -252,14 +223,17 @@ Any breaking changes to the existing interface(s) will need to be carefully cons > choosing them? > - What is the impact of not doing this? -***TODO*** +## Flat vs nested origin:behaviors + +**TODO:** The behaviors are technically flat, allowing for arbitrary ordering of behaviors across origins. The nested approach here, while it + generally makes sense, makes this functionality a bit more difficult. Discuss trade-offs. # Adoption Strategy > If we implement this proposal, how will existing CDK developers adopt it? Is > this a breaking change? How can we assist in adoption? -***TODO*** - Great question! +**_TODO_** # Unresolved questions @@ -271,9 +245,9 @@ Any breaking changes to the existing interface(s) will need to be carefully cons > addressed in the future independently of the solution that comes out of this > RFC? -***TODO*** +**_TODO_** -# Future Possibilities +## Future Possibilities > Think about what the natural extension and evolution of your proposal would be > and how it would affect CDK as whole. Try to use this section as a tool to more @@ -286,4 +260,4 @@ Any breaking changes to the existing interface(s) will need to be carefully cons > If you have tried and cannot think of any future possibilities, you may simply > state that you cannot think of anything. -***TODO*** +**_TODO - Discuss "L3s" like StaticWebsiteDistribution that are all-in-one._** From 258426d30425fc25d211daaf1f5459d2ed685001 Mon Sep 17 00:00:00 2001 From: Nick Lynch Date: Mon, 22 Jun 2020 18:14:08 +0100 Subject: [PATCH 03/11] Lambda@Edge API v1, plus some of the open questions and other sections filled in. --- text/0171-cloudfront-redesign.md | 135 ++++++++++++++++++++----------- 1 file changed, 90 insertions(+), 45 deletions(-) diff --git a/text/0171-cloudfront-redesign.md b/text/0171-cloudfront-redesign.md index 3688b05ba..e5cdb21ee 100644 --- a/text/0171-cloudfront-redesign.md +++ b/text/0171-cloudfront-redesign.md @@ -122,14 +122,14 @@ const myWebDistribution = new Distribution(this, 'myDist', { ``` Additional cache behaviors can be specified at creation, or added to the origin(s) after the initial creation. These additional cache behaviors enable -customization on a specific set of resources based on a URL path pattern. For example, we can add a behavior to `myWebDistribution` to override the +customization for a specific set of resources based on a URL path pattern. For example, we can add a behavior to `myWebDistribution` to override the default time-to-live (TTL) for all of the images. ```ts myWebDistribution.origin.addBehavior('/images/*.jpg', { viewerProtocolPolicy: ViewerProtocolPolicy.REDIRECT_TO_HTTPS, defaultTtl: cdk.Duration.days(7), -}) +}); ``` ## Multiple Origins @@ -170,25 +170,63 @@ The above will create both origins and a single origin group with the load balan Lambda@Edge is an extension of AWS Lambda, a compute service that lets you execute functions that customize the content that CloudFront delivers. You can author Node.js or Python functions in the US East (N. Virginia) region, and then execute them in AWS locations globally that are closer to the -viewer, without provisioning or managing servers. Lambda@Edge functions are associated with a specific behavior and +viewer, without provisioning or managing servers. Lambda@Edge functions are associated with a specific behavior and event type. Lambda@Edge can be +used rewrite URLs, alter responses based on headers or cookies, or authorize requests based on headers or authorization tokens. + +By default, Lambda@Edge functions are attached to the default behavior: -**TODO:** Design the Lambda@Edge API. +```ts +const myFunc = new lambda.Function(...); +const myDist = new Distribution(...); +myDist.addLambdaFunctionAssociation({ + functionVersion: myFunc.currentVersion, + eventType: EventType.VIEWER_REQUEST, +}); +``` + +Lambda@Edge functions can also be associated with additional behaviors, either at behavior creation or after the fact, either by attaching +directly to the behavior, or to the distribution and referencing the behavior. + +```ts +// Assigning at behavior creation. +myOrigin.addBehavior('/images/*.jpg', { + viewerProtocolPolicy: ViewerProtocolPolicy.REDIRECT_TO_HTTPS, + defaultTtl: cdk.Duration.days(7), + lambdaFunctionAssociations: [{ + functionVersion: myFunc.currentVersion, + eventType: EventType.VIEWER_REQUEST, + }] +}); + +// Assigning after creation. +const myImagesBehavior = myOrigin.addBehavior('/images/*.jpg', ...); +myImagesBehavior.addLambdaFunctionAssociation({ + functionVersion: myFunc.currentVersion, + eventType: EventType.VIEWER_REQUEST, +}); + +myDist.addLambdaFunctionAssociation({ + functionVersion: myFunc.currentVersion, + eventType: EventType.ORIGIN_REQUEST, + behavior: myImagesBehavior, +}); +``` --- # Motivation -> Why are we doing this? What use cases does it support? What is the expected -> outcome? - -**_TODO_** +The existing aws-cloudfront module doesn't adhere to standard naming convention, lacks convenience methods for more easily interacting with +distributions, origins, and behaviors, and has been in an "experimental" state for years. This proposal aims to bring a friendlier, more ergonic +interface to the module, and advance the module to a GA-ready state. # Design Summary -> Summarize the approach of the feature design in a couple of sentences. Call out -> any known patterns or best practices the design is based around. - -**_TODO_** +The approach will create a new top-level Construct (`Distribution`) to replace the existing `CloudFrontWebDistribution`, as well as new constructs +to represent the other logical resources for a distribution (i.e., `Origin`, `Behavior`). The new L2s will be created in the same aws-cloudfront +module and no changes will be made to the existing L2s to preserve the existing experience. Unlike the existing L2, the new L2s will feature a +variety of convenience methods (e.g., `addBehavior`) to aid in the creation of the distribution, and provide several out-of-the-box defaults for +building distributions off of other resources (e.g., buckets, load balanced services). # Detailed Design @@ -204,17 +242,15 @@ viewer, without provisioning or managing servers. Lambda@Edge functions are asso > - [Graphviz](http://graphviz.it/#/gallery/structs.gv) > - [PlantText](https://www.planttext.com) +**_TODO_** - What is important to capture here for this RFC? + # Drawbacks -> Why should we _not_ do this? Please consider: -> -> - implementation cost, both in term of code size and complexity -> - whether the proposed feature can be implemented in user space -> - the impact on teaching people how to use CDK -> - integration of this feature with other existing and planned features -> - cost of migrating existing CDK applications (is it a breaking change?) -> -> There are tradeoffs to choosing any path. Attempt to identify them here. +The primary drawback to this work is one of adoption. The aws-cloudfront module is one of the oldest in the CDK, and is used by many customers. +These changes won't break any of the existing customers, but all existing customers would need to rewrite their CloudFront constructs to take +advantage of the functionality and ease-of-use benefits of the new L2s. For building an entirely new set of L2s to be worth the return on investment, +the improvements to functionality and ergonomics needs to be substantial. Feedback is welcome on how to best capitalize on this opportunity and make +the friendliest interface possible. # Rationale and Alternatives @@ -225,8 +261,8 @@ viewer, without provisioning or managing servers. Lambda@Edge functions are asso ## Flat vs nested origin:behaviors -**TODO:** The behaviors are technically flat, allowing for arbitrary ordering of behaviors across origins. The nested approach here, while it - generally makes sense, makes this functionality a bit more difficult. Discuss trade-offs. +**_TODO:_** _The behaviors are technically flat, allowing for arbitrary ordering of behaviors across origins. The nested approach here, while it + generally makes sense, makes this functionality a bit more difficult. Discuss the trade-offs here._ # Adoption Strategy @@ -237,27 +273,36 @@ viewer, without provisioning or managing servers. Lambda@Edge functions are asso # Unresolved questions -> - What parts of the design do you expect to resolve through the RFC process -> before this gets merged? -> - What parts of the design do you expect to resolve through the implementation -> of this feature before stabilization? -> - What related issues do you consider out of scope for this RFC that could be -> addressed in the future independently of the solution that comes out of this -> RFC? - -**_TODO_** - -## Future Possibilities +1. Are `fromBucket` and `fromWebsiteBucket` (potentially) redundant? There isn't enough information +on the `IBucket` interface to determine if the bucket has been configured for static web hosting and +we should treat as such. However, we could have an additional parameter to `fromBucket` trigger this +behavior (e.g., `isConfiguredAsWebsite`?). +2. Does the nested (origin->behavior) model make sense? It's the most straightforward for 99% of use +cases; however, the actual CloudFront (and CloudFormation) model is that there is a single default +behavior and then a list of ordered behaviors, each associated with an origin. This is important in +the case where the order of behaviors matters. For example, +`[{origin-1,'images/*.jpg'},{origin-2,'images/*'},{origin-1,'*.gif'}]`. +If the order of the last two is reversed (all origin behaviors grouped), the outcome changes. Possible +alternatives to "flattening" the relationship would be to expose a property (`behaviorOrder`) to explicitly +set the order. This is most important for the all-in-one scenarios where all origins and behaviors are passed +to the constructor at once. In other scenarios, the order behaviors are created in can be used. +3. Naming question -- `Distribution` seems the most natural name to replace `CloudFrontWebDistribution`; however, +`IDistribution` already exists and doesn't exactly match the desired interface. `IDistribution` is used in the +`aws-s3-deployment` module, which is also experimental. Would it be acceptable to break this interface, given that +all CDK usages are in experimental modules? + +# Future Possibilities + +One extension point for this redesign is building all-in-one "L3s" on top of the new L2s. One extremely common use case for CloudFront is +to be used as a globally-distributed front on top of a S3 bucket configured for website hosting. One potential L3 for CloudFront would wrap +this entire set of constructs into one easy-to-use construct. For example: -> Think about what the natural extension and evolution of your proposal would be -> and how it would affect CDK as whole. Try to use this section as a tool to more -> fully consider all possible interactions with the project and ecosystem in your -> proposal. Also consider how this fits into the roadmap for the project. -> -> This is a good place to "dump ideas", if they are out of scope for the RFC you -> are writing but are otherwise related. -> -> If you have tried and cannot think of any future possibilities, you may simply -> state that you cannot think of anything. +```ts +// Creates the hosted zone, S3 bucket, CloudFront distribution, ACM certificate, and wires everything together. +new StaticWebsiteDistribution(this, 'SiteDistribution', { + domainName: 'www.example.com', + source: s3.Source.asset('static-website/'), +}); +``` -**_TODO - Discuss "L3s" like StaticWebsiteDistribution that are all-in-one._** +**_TODO_**: Add more examples? From cc80963aea2bcc768500c88973df9f10d3ed8222 Mon Sep 17 00:00:00 2001 From: Nick Lynch Date: Tue, 23 Jun 2020 16:04:37 +0100 Subject: [PATCH 04/11] Added detailed design section, including before/after examples --- text/0171-cloudfront-redesign.md | 267 +++++++++++++++++++++++++------ 1 file changed, 218 insertions(+), 49 deletions(-) diff --git a/text/0171-cloudfront-redesign.md b/text/0171-cloudfront-redesign.md index e5cdb21ee..0155e5155 100644 --- a/text/0171-cloudfront-redesign.md +++ b/text/0171-cloudfront-redesign.md @@ -39,13 +39,13 @@ documents. // Creates a distribution for a S3 bucket. const myBucket = new s3.Bucket(...); new Distribution(this, 'myDist', { - origin: Origin.fromBucket(this, 'myOrigin', myBucket) + origin: Origin.fromBucket(myBucket) }); // Creates a distribution for a S3 bucket that has been configured for website hosting. const myWebsiteBucket = new s3.Bucket(...); new Distribution(this, 'myDist', { - origin: Origin.fromWebsiteBucket(this, 'myOrigin', myBucket) + origin: Origin.fromWebsiteBucket(myBucket) }); ``` @@ -58,12 +58,12 @@ Origins can also be created from other resources (e.g., load balancers, API gate // Creates a distribution for an application load balancer. const myLoadBalancer = new elbv2.ApplicationLoadBalancer(...); new Distribution(this, 'myDist', { - origin: Origin.fromLoadBalancerV2(this, 'myOrigin', myLoadBalancer) + origin: Origin.fromLoadBalancerV2(myLoadBalancer) }); // Creates a distribution for an HTTP server. new Distribution(this, 'myDist', { - origin: Origin.fromHTTPServer(this, 'myOrigin', { + origin: Origin.fromHTTPServer({ domainName: 'www.example.com', originProtocolPolicy: OriginProtocolPolicy.HTTPS_ONLY }) @@ -110,7 +110,7 @@ methods and viewer protocol policy of the cache. ```ts const myWebDistribution = new Distribution(this, 'myDist', { - origin: Origin.fromHTTPServer(this, 'myOrigin', { + origin: Origin.fromHTTPServer({ domainName: 'www.example.com', originProtocolPolicy: OriginProtocolPolicy.HTTPS_ONLY }), @@ -141,8 +141,8 @@ following example shows how such a setup might be created: ```ts const myWebsiteBucket = new s3.Bucket(...); const myMultiOriginDistribution = new Distribution(this, 'myDist', { - origin: Origin.fromWebsiteBucket(this, 'myOrigin', myBucket), - additionalOrigins: [Origin.fromLoadBalancerV2(this, 'myOrigin', myLoadBalancer, { + origin: Origin.fromWebsiteBucket(myBucket), + additionalOrigins: [Origin.fromLoadBalancerV2(myLoadBalancer, { pathPattern: '/api/*', allowedMethods: AllowedMethods.ALL, forwardQueryString: true, @@ -157,8 +157,8 @@ for the distribution. ```ts const myOriginGroup = Origin.groupFromOrigins( - primaryOrigin: Origin.fromLoadBalancerV2(this, 'myOrigin', myLoadBalancer), - fallbackOrigin: Origin.fromBucket(this, 'myFallbackOrigin', myBucket), + primaryOrigin: Origin.fromLoadBalancerV2(myLoadBalancer), + fallbackOrigin: Origin.fromBucket(myBucket), fallbackStatusCodes: [500, 503] ); new Distribution(this, 'myDist', { origin: myOriginGroup }); @@ -192,7 +192,7 @@ directly to the behavior, or to the distribution and referencing the behavior. myOrigin.addBehavior('/images/*.jpg', { viewerProtocolPolicy: ViewerProtocolPolicy.REDIRECT_TO_HTTPS, defaultTtl: cdk.Duration.days(7), - lambdaFunctionAssociations: [{ + functionAssociation: [{ functionVersion: myFunc.currentVersion, eventType: EventType.VIEWER_REQUEST, }] @@ -200,12 +200,12 @@ myOrigin.addBehavior('/images/*.jpg', { // Assigning after creation. const myImagesBehavior = myOrigin.addBehavior('/images/*.jpg', ...); -myImagesBehavior.addLambdaFunctionAssociation({ +myImagesBehavior.addFunctionAssociation({ functionVersion: myFunc.currentVersion, eventType: EventType.VIEWER_REQUEST, }); -myDist.addLambdaFunctionAssociation({ +myDist.addFunctionAssociation({ functionVersion: myFunc.currentVersion, eventType: EventType.ORIGIN_REQUEST, behavior: myImagesBehavior, @@ -230,19 +230,190 @@ building distributions off of other resources (e.g., buckets, load balanced serv # Detailed Design -> This is the bulk of the RFC. Explain the design in enough detail for somebody -> familiar with CDK to understand, and for somebody familiar with the -> implementation to implement. This should get into specifics and corner-cases, -> and include examples of how the feature is used. Any new terminology should be -> defined here. -> -> Include any diagrams and/or visualizations that help to demonstrate the design. -> Here are some tools that we often use: -> -> - [Graphviz](http://graphviz.it/#/gallery/structs.gv) -> - [PlantText](https://www.planttext.com) +The design creates one new resource (`Distribution`) and two new classes (`Origin` and `Behavior`) to replace the current +`CloudFrontWebDistribution` construct. Each of these new classes comes with helper methods (e.g., `addBehavior`) to make assembling more complex +distributions easier, as well as factory constructors to make it easier to build common Origin and Behavior patterns (e.g., `Origin.fromBucket`). + +The following is an incomplete, but representative, listing of the API: + +```ts +class Distribution extends BaseDistribution { + static fromArn(scope: Construct, id: string, distributionArn: string): IDistribution; + static fromAttributes(scope: Construct, id: string, distributionArn: string): IDistribution; + + constructor(scope: Construct, id: string, props: DistributionProps) {} + + addOrigin(options: OriginOptions): Origin + addBehavior(pathPattern: string, options: BehaviorOptions): Behavior +} + +class Origin { + static fromBucket(bucket: s3.IBucket, behaviorOptions?: BehaviorProps): Origin + static fromWebsiteBucket(bucket: s3.IBucket, behaviorOptions?: BehaviorProps): Origin + static fromLoadBalancerV2(loadBalancer: elbv2.ApplicationLoadBalancer, behaviorOptions?: BehaviorProps): Origin + static fromHttpServer(options: ServerOriginOptions, behaviorOptions?: BehaviorProps): Origin + + constructor(props: OriginProps) {} + + addBehavior(pathPattern: string, options: BehaviorOptions): Behavior +} + +class Behavior { + constructor(props: BehaviorProps) {} + + addFunctionAssociation(options: FunctionAssociationOptions): FunctionAssociation +} +``` + +## Nested structure and relationships + +The `Distribution` has one top-level origin and behavior, which aligns to how the vast majority of customers use CloudFront today (based on public +CDK examples). Customers can add additional origins (and origin groups) and behaviors, which are modeled as `additionalOrigins` and +`additionalBehaviors`, respectively. Each origin may have multiple behaviors associated with it, so the relationship is modeled such that behaviors +are added to origins. However, an authoritative list of behaviors is kept on the distribution to preserve ordering. This is done similarly to how +ECS clusters keep track of Fargate profiles as they are created and associated with the cluster. In the below example, the '/api/\*' behavior for +the load balancer origin will be ordered first, then the '/api/errors/\*' behavior on the bucket. + +```ts +const myWebsiteBucket = new s3.Bucket(...); +const myMultiOriginDistribution = new Distribution(this, 'myDist', { + origin: Origin.fromWebsiteBucket(this, 'myOrigin', myBucket), + additionalOrigins: [Origin.fromLoadBalancerV2(this, 'myOrigin', myLoadBalancer, { + pathPattern: '/api/*', + allowedMethods: AllowedMethods.ALL, + forwardQueryString: true, + })]; +}); +myMultiOriginDistribution.origin.addBehavior('/api/errors/*', ...); +``` + +This approach was chosen as the simplest pattern to work with for the majority of customers that don't add multiple origins and behaviors, while +still giving those power users control over behavior ordering, albeit implicitly. See the Rationale and Alternatives section for a discussion of +other ways this was considered. -**_TODO_** - What is important to capture here for this RFC? +## Interaction with other L2s + +The existing @aws-cdk/aws-cloudfront module is used in three other modules of the CDK: (1) aws-route53-patterns, (2) aws-route53-targets, and +(3) aws-s3-deployment. + +1. In aws-route53-patterns, the CloudFrontWebDistribution is used internally to the HttpsRedirect class; no CloudFront +properties or classes are exposed to the consumer. This usage can be swapped out when the new L2s are ready without impact to customers. +2. In aws-route53-targets, the CloudFrontTarget constructor takes a CloudFrontWebDistribution as the sole parameter. This usage could actually +be replaced with an IDistribution, and then work for both Distribution and CloudFrontWebDistribution. I _believe_ this is a backwards-compatible +change; please correct me if I'm wrong. +3. In aws-s3-deployment, BucketDeploymentProps has an optional IDistribution member, which does not need to be changed. + +## Comparison with the existing API + +The primary drawback of this work is that an existing CloudFront L2 already exists and is in wide use. To justify the creation of a new API, this +section provides examples of what the user experience of common (and some uncommon) use cases will be before and after the redesign. + +### Use Case #1 - S3 bucket origin + +The simplest use case is to have a single S3 bucket origin, and no customized behaviors. + +**Before:** + +```ts +new CloudFrontWebDistribution(this, 'MyDistribution', { + originConfigs: [{ + s3OriginSource: { s3BucketSource: sourceBucket }, + behaviors : [ { isDefaultBehavior: true }] + }] +}); +``` + +**After:** + +```ts +new Distribution(this, 'MyDistribution', { + origin: Origin.fromBucket(sourceBucket) +}); +``` + +### Use Case #2 - S3 bucket origin with certificate and custom behavior + +This example keeps the same bucket, but adds one custom behavior and a certificate. + +**Before:** + +```ts +new CloudFrontWebDistribution(this, 'MyDistribution', { + originConfigs: [{ + s3OriginSource: { s3BucketSource: sourceBucket }, + behaviors : [ + { isDefaultBehavior: true }, + { + pathPattern: 'images/*', + defaultTtl: cdk.Duration.days(7) + } + ] + }], + viewerCertificate: ViewerCertificate.fromAcmCertificate(myCertificate), +}); +``` + +**After:** + +```ts +const dist = new Distribution(this, 'MyDistribution', { + origin: Origin.fromBucket(sourceBucket), + certificate: myCertificate +}); +dist.origin.addBehavior('images/*', { defaultTtl: cdk.Duration.days(7) }); +``` + +### Use Case #3 - Multi-origin, multi-behavior distribution with a Lambda@Edge function + +Both S3 and LoadBalancedFargateService origins, custom behaviors, and a Lambda function to top it all off. + +**Before:** + +```ts +new CloudFrontWebDistribution(this, 'dist', { + originConfigs: [ + { + customOriginSource: { + domainName: lbFargateService.loadBalancer.loadBalancerDnsName, + originProtocolPolicy: OriginProtocolPolicy.HTTPS_ONLY, + }, + behaviors: [{ + isDefaultBehavior: true, + allowedMethods: CloudFrontAllowedMethods.ALL, + forwardedValues: { queryString: true }, + lambdaFunctionAssociations: [{ + lambdaFunction: myFunctionVersion, + eventType: LambdaEdgeEventType.ORIGIN_RESPONSE, + }] + }] + }, + { + s3OriginSource: { + s3BucketSource: sourceBucket, + originAccessIdentity: OriginAccessIdentity.fromOriginAccessIdentityName(this, 'oai', 'distOAI'), + }, + behaviors: [ { pathPattern: 'static/*' } ] + }, + ], +}); +``` + +**After:** + +```ts +const dist = new Distribution(this, 'MyDistribution', { + origin: Origin.fromLoadBalancerV2(lbFargateService.loadBalancer), + behavior: { + allowedMethods: AllowedMethods.ALL, + forwardQueryString: true, + functionAssociations: [{ + function: myFunctionVersion, + eventType: EventType.ORIGIN_RESPONSE + }] + } +}); +dist.addOrigin(Origin.fromBucket(sourceBucket), { pathPattern: 'static/*' }); +``` # Drawbacks @@ -254,22 +425,26 @@ the friendliest interface possible. # Rationale and Alternatives -> - Why is this design the best in the space of possible designs? -> - What other designs have been considered and what is the rationale for not -> choosing them? -> - What is the impact of not doing this? +This RFC aims to take one of the older modules in the CDK and update it to the current set of design standards. By introducing secondary resource +creation methods, factories for common L2-based origins, and flattening some of the top-level nested properties, we can offer an easier-to-use +experience. -## Flat vs nested origin:behaviors +The interface aims to make it easiest for the 90%+ use cases of having a single origin based on an S3 bucket (or other CDK construct), with a single +default behavior, optionally slightly customized; it accomplishes this by exposing a single top-level `origin` and `behavior` and making us of +factory methods to construct origins from buckets, load balancers, and other common origins. Beyond that straightforward use-case, the decision was +made to represent the behaviors as members of origins, as each behavior must be associated with an origin. This introduces one area of cognitive +complexity in terms of behavior ordering. -**_TODO:_** _The behaviors are technically flat, allowing for arbitrary ordering of behaviors across origins. The nested approach here, while it - generally makes sense, makes this functionality a bit more difficult. Discuss the trade-offs here._ +Behaviors are ordered and precedence is used to determine how to route requests to origin(s). For example, origin #1 could have custom behaviors with +path patterns of 'images/\*.jpg' and '\*.gif', and origin #2 could have a behavior on 'images/\*'. Depending on the ordering, a request for +'images/foo.gif' may either be routed to origin #1 or #2. The approach taken ties behavior precedence to order of creation. An alternative would +be to expose a flat list of behaviors, and allow the user to manipulate that list to change precedence. Ultimately, this was discarded as an overly- +complex interface with diminishing benefits. However, feedback is welcome on a more elegant way to give users control of behavior ordering. # Adoption Strategy -> If we implement this proposal, how will existing CDK developers adopt it? Is -> this a breaking change? How can we assist in adoption? - -**_TODO_** +Once created, the new L2s can be used by existing CDK developers for new use cases, or by converting their existing CloudFrontWebDistribution usages +to the new Distribution resource. # Unresolved questions @@ -277,19 +452,12 @@ the friendliest interface possible. on the `IBucket` interface to determine if the bucket has been configured for static web hosting and we should treat as such. However, we could have an additional parameter to `fromBucket` trigger this behavior (e.g., `isConfiguredAsWebsite`?). -2. Does the nested (origin->behavior) model make sense? It's the most straightforward for 99% of use -cases; however, the actual CloudFront (and CloudFormation) model is that there is a single default -behavior and then a list of ordered behaviors, each associated with an origin. This is important in -the case where the order of behaviors matters. For example, -`[{origin-1,'images/*.jpg'},{origin-2,'images/*'},{origin-1,'*.gif'}]`. -If the order of the last two is reversed (all origin behaviors grouped), the outcome changes. Possible -alternatives to "flattening" the relationship would be to expose a property (`behaviorOrder`) to explicitly -set the order. This is most important for the all-in-one scenarios where all origins and behaviors are passed -to the constructor at once. In other scenarios, the order behaviors are created in can be used. -3. Naming question -- `Distribution` seems the most natural name to replace `CloudFrontWebDistribution`; however, -`IDistribution` already exists and doesn't exactly match the desired interface. `IDistribution` is used in the -`aws-s3-deployment` module, which is also experimental. Would it be acceptable to break this interface, given that -all CDK usages are in experimental modules? +2. What are the advantage/disadvantages to making both `Origin` and `Behavior` extend `cdk.Construct` (or not)? +3. Other examples of constructs which modify the underlying Cfn* resources post-creation? For example, calling `addBehavior` after +construction alters the synthesized construct (not by adding new resources, but by changing the original one). Any best practices or +patterns to be aware of here for the implementation? +4. Should the current (CloudFrontWebDistribution) construct be marked as "stable" to indicate we won't be making future updates to it? Any other +suggestions on how we message the "v2" on the README to highlight the new option to customers? # Future Possibilities @@ -305,4 +473,5 @@ new StaticWebsiteDistribution(this, 'SiteDistribution', { }); ``` -**_TODO_**: Add more examples? +This would be relatively easy to piece together from the existing constructs, and would follow the patterns of the aws-s3-deployment module +to deploy the assets. From acbd00b68ac9665384b62f45703624d08290a611 Mon Sep 17 00:00:00 2001 From: Nick Lynch Date: Mon, 29 Jun 2020 11:12:04 +0100 Subject: [PATCH 05/11] Proper namespacing in the examples, and updated the open questions --- text/0171-cloudfront-redesign.md | 121 ++++++++++++++++--------------- 1 file changed, 63 insertions(+), 58 deletions(-) diff --git a/text/0171-cloudfront-redesign.md b/text/0171-cloudfront-redesign.md index 0155e5155..5d327fa60 100644 --- a/text/0171-cloudfront-redesign.md +++ b/text/0171-cloudfront-redesign.md @@ -36,16 +36,18 @@ An S3 bucket can be added as an origin. If the bucket is configured as a website documents. ```ts +import * as cloudfront from '@aws-cdk/aws-cloudfront'; + // Creates a distribution for a S3 bucket. const myBucket = new s3.Bucket(...); -new Distribution(this, 'myDist', { - origin: Origin.fromBucket(myBucket) +new cloudfront.Distribution(this, 'myDist', { + origin: cloudfront.Origin.fromBucket(myBucket), }); // Creates a distribution for a S3 bucket that has been configured for website hosting. const myWebsiteBucket = new s3.Bucket(...); -new Distribution(this, 'myDist', { - origin: Origin.fromWebsiteBucket(myBucket) +new cloudfront.Distribution(this, 'myDist', { + origin: cloudfront.Origin.fromWebsiteBucket(myBucket), }); ``` @@ -57,15 +59,15 @@ Origins can also be created from other resources (e.g., load balancers, API gate ```ts // Creates a distribution for an application load balancer. const myLoadBalancer = new elbv2.ApplicationLoadBalancer(...); -new Distribution(this, 'myDist', { - origin: Origin.fromLoadBalancerV2(myLoadBalancer) +new cloudfront.Distribution(this, 'myDist', { + origin: cloudfront.Origin.fromLoadBalancerV2(myLoadBalancer), }); // Creates a distribution for an HTTP server. -new Distribution(this, 'myDist', { - origin: Origin.fromHTTPServer({ +new cloudfront.Distribution(this, 'myDist', { + origin: cloudfront.Origin.fromHTTPServer({ domainName: 'www.example.com', - originProtocolPolicy: OriginProtocolPolicy.HTTPS_ONLY + originProtocolPolicy: OriginProtocolPolicy.HTTPS_ONLY, }) }); ``` @@ -76,9 +78,9 @@ When you create a distribution, CloudFront returns a domain name for the distrib use your own domain name, such as `www.example.com`, you can add an alternate domain name to your distribution. ```ts -new Distribution(this, 'myDist', { - origin: Origin.fromBucket(myBucket), - aliases: ['www.example.com'] +new cloudfront.Distribution(this, 'myDist', { + origin: cloudfront.Origin.fromBucket(myBucket), + aliases: ['www.example.com'], }) ``` @@ -91,8 +93,8 @@ const myCertificate = new certmgr.DnsValidatedCertificate(this, 'mySiteCert', { domainName: 'www.example.com', hostedZone, }); -new Distribution(this, 'myDist', { - origin: Origin.fromBucket(myBucket), +new cloudfront.Distribution(this, 'myDist', { + origin: cloudfront.Origin.fromBucket(myBucket), certificate: myCertificate, }); ``` @@ -109,14 +111,14 @@ The properties of the default cache behavior can be adjusted as part of the dist methods and viewer protocol policy of the cache. ```ts -const myWebDistribution = new Distribution(this, 'myDist', { - origin: Origin.fromHTTPServer({ +const myWebDistribution = new cloudfront.Distribution(this, 'myDist', { + origin: cloudfront.Origin.fromHTTPServer({ domainName: 'www.example.com', - originProtocolPolicy: OriginProtocolPolicy.HTTPS_ONLY + originProtocolPolicy: OriginProtocolPolicy.HTTPS_ONLY, }), behavior: { allowedMethods: AllowedMethods.ALL, - viewerProtocolPolicy: ViewerProtocolPolicy.REDIRECT_TO_HTTPS + viewerProtocolPolicy: ViewerProtocolPolicy.REDIRECT_TO_HTTPS, } }); ``` @@ -140,13 +142,15 @@ following example shows how such a setup might be created: ```ts const myWebsiteBucket = new s3.Bucket(...); -const myMultiOriginDistribution = new Distribution(this, 'myDist', { - origin: Origin.fromWebsiteBucket(myBucket), - additionalOrigins: [Origin.fromLoadBalancerV2(myLoadBalancer, { - pathPattern: '/api/*', - allowedMethods: AllowedMethods.ALL, - forwardQueryString: true, - })]; +const myMultiOriginDistribution = new cloudfront.Distribution(this, 'myDist', { + origin: cloudfront.Origin.fromWebsiteBucket(myBucket), + additionalOrigins: [ + cloudfront.Origin.fromLoadBalancerV2(myLoadBalancer, { + pathPattern: '/api/*', + allowedMethods: AllowedMethods.ALL, + forwardQueryString: true, + }), + ], }); ``` @@ -156,12 +160,12 @@ primary origin returns specific HTTP status code failure responses. An origin gr for the distribution. ```ts -const myOriginGroup = Origin.groupFromOrigins( - primaryOrigin: Origin.fromLoadBalancerV2(myLoadBalancer), - fallbackOrigin: Origin.fromBucket(myBucket), - fallbackStatusCodes: [500, 503] -); -new Distribution(this, 'myDist', { origin: myOriginGroup }); +const myOriginGroup = cloudfront.Origin.fromOriginGroup({ + primaryOrigin: cloudfront.Origin.fromLoadBalancerV2(myLoadBalancer), + fallbackOrigin: cloudfront.Origin.fromBucket(myBucket), + fallbackStatusCodes: [500, 503], +}); +new cloudfront.Distribution(this, 'myDist', { origin: myOriginGroup }); ``` The above will create both origins and a single origin group with the load balancer origin falling back to the S3 bucket in case of 500 or 503 errors. @@ -177,7 +181,7 @@ By default, Lambda@Edge functions are attached to the default behavior: ```ts const myFunc = new lambda.Function(...); -const myDist = new Distribution(...); +const myDist = new cloudfront.Distribution(...); myDist.addLambdaFunctionAssociation({ functionVersion: myFunc.currentVersion, eventType: EventType.VIEWER_REQUEST, @@ -252,6 +256,7 @@ class Origin { static fromWebsiteBucket(bucket: s3.IBucket, behaviorOptions?: BehaviorProps): Origin static fromLoadBalancerV2(loadBalancer: elbv2.ApplicationLoadBalancer, behaviorOptions?: BehaviorProps): Origin static fromHttpServer(options: ServerOriginOptions, behaviorOptions?: BehaviorProps): Origin + static fromOriginGroup(options: OriginGroupOptions): Origin constructor(props: OriginProps) {} @@ -276,13 +281,15 @@ the load balancer origin will be ordered first, then the '/api/errors/\*' behavi ```ts const myWebsiteBucket = new s3.Bucket(...); -const myMultiOriginDistribution = new Distribution(this, 'myDist', { - origin: Origin.fromWebsiteBucket(this, 'myOrigin', myBucket), - additionalOrigins: [Origin.fromLoadBalancerV2(this, 'myOrigin', myLoadBalancer, { - pathPattern: '/api/*', - allowedMethods: AllowedMethods.ALL, - forwardQueryString: true, - })]; +const myMultiOriginDistribution = new cloudfront.Distribution(this, 'myDist', { + origin: cloudfront.Origin.fromWebsiteBucket(this, 'myOrigin', myBucket), + additionalOrigins: [ + cloudfront.Origin.fromLoadBalancerV2(this, 'myOrigin', myLoadBalancer, { + pathPattern: '/api/*', + allowedMethods: AllowedMethods.ALL, + forwardQueryString: true, + }), + ]; }); myMultiOriginDistribution.origin.addBehavior('/api/errors/*', ...); ``` @@ -318,7 +325,7 @@ The simplest use case is to have a single S3 bucket origin, and no customized be new CloudFrontWebDistribution(this, 'MyDistribution', { originConfigs: [{ s3OriginSource: { s3BucketSource: sourceBucket }, - behaviors : [ { isDefaultBehavior: true }] + behaviors : [ { isDefaultBehavior: true }], }] }); ``` @@ -326,8 +333,8 @@ new CloudFrontWebDistribution(this, 'MyDistribution', { **After:** ```ts -new Distribution(this, 'MyDistribution', { - origin: Origin.fromBucket(sourceBucket) +new cloudfront.Distribution(this, 'MyDistribution', { + origin: cloudfront.Origin.fromBucket(sourceBucket), }); ``` @@ -345,7 +352,7 @@ new CloudFrontWebDistribution(this, 'MyDistribution', { { isDefaultBehavior: true }, { pathPattern: 'images/*', - defaultTtl: cdk.Duration.days(7) + defaultTtl: cdk.Duration.days(7), } ] }], @@ -356,9 +363,9 @@ new CloudFrontWebDistribution(this, 'MyDistribution', { **After:** ```ts -const dist = new Distribution(this, 'MyDistribution', { - origin: Origin.fromBucket(sourceBucket), - certificate: myCertificate +const dist = new cloudfront.Distribution(this, 'MyDistribution', { + origin: cloudfront.Origin.fromBucket(sourceBucket), + certificate: myCertificate, }); dist.origin.addBehavior('images/*', { defaultTtl: cdk.Duration.days(7) }); ``` @@ -392,7 +399,7 @@ new CloudFrontWebDistribution(this, 'dist', { s3BucketSource: sourceBucket, originAccessIdentity: OriginAccessIdentity.fromOriginAccessIdentityName(this, 'oai', 'distOAI'), }, - behaviors: [ { pathPattern: 'static/*' } ] + behaviors: [ { pathPattern: 'static/*' } ], }, ], }); @@ -401,18 +408,18 @@ new CloudFrontWebDistribution(this, 'dist', { **After:** ```ts -const dist = new Distribution(this, 'MyDistribution', { - origin: Origin.fromLoadBalancerV2(lbFargateService.loadBalancer), +const dist = new cloudfront.Distribution(this, 'MyDistribution', { + origin: cloudfront.Origin.fromLoadBalancerV2(lbFargateService.loadBalancer), behavior: { allowedMethods: AllowedMethods.ALL, forwardQueryString: true, functionAssociations: [{ function: myFunctionVersion, - eventType: EventType.ORIGIN_RESPONSE + eventType: EventType.ORIGIN_RESPONSE, }] } }); -dist.addOrigin(Origin.fromBucket(sourceBucket), { pathPattern: 'static/*' }); +dist.addOrigin(cloudfront.Origin.fromBucket(sourceBucket), { pathPattern: 'static/*' }); ``` # Drawbacks @@ -444,7 +451,7 @@ complex interface with diminishing benefits. However, feedback is welcome on a m # Adoption Strategy Once created, the new L2s can be used by existing CDK developers for new use cases, or by converting their existing CloudFrontWebDistribution usages -to the new Distribution resource. +to the new cloudfront.Distribution resource. # Unresolved questions @@ -452,12 +459,10 @@ to the new Distribution resource. on the `IBucket` interface to determine if the bucket has been configured for static web hosting and we should treat as such. However, we could have an additional parameter to `fromBucket` trigger this behavior (e.g., `isConfiguredAsWebsite`?). -2. What are the advantage/disadvantages to making both `Origin` and `Behavior` extend `cdk.Construct` (or not)? -3. Other examples of constructs which modify the underlying Cfn* resources post-creation? For example, calling `addBehavior` after -construction alters the synthesized construct (not by adding new resources, but by changing the original one). Any best practices or -patterns to be aware of here for the implementation? -4. Should the current (CloudFrontWebDistribution) construct be marked as "stable" to indicate we won't be making future updates to it? Any other -suggestions on how we message the "v2" on the README to highlight the new option to customers? +2. What level of breaking changes are acceptable for the existing `IDistribution` and `CloudFrontWebDistribution` resources? Notably, the +`IDistribution` interface should extend `IResource`, and `CloudFrontWebDistribution` changed from extending `Construct` to `Resource`. +3. Related to the above, should the current (CloudFrontWebDistribution) construct be marked as "stable" to indicate we won't be making future +updates to it? Any other suggestions on how we message the "v2" on the README to highlight the new option to customers? # Future Possibilities From a8c7119da6348cb82e7277ecf12bc1e664e65308 Mon Sep 17 00:00:00 2001 From: Nick Lynch Date: Mon, 29 Jun 2020 15:01:38 +0100 Subject: [PATCH 06/11] Minor update to force pull request refresh --- text/0171-cloudfront-redesign.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/text/0171-cloudfront-redesign.md b/text/0171-cloudfront-redesign.md index 5d327fa60..0e1d16c39 100644 --- a/text/0171-cloudfront-redesign.md +++ b/text/0171-cloudfront-redesign.md @@ -460,7 +460,9 @@ on the `IBucket` interface to determine if the bucket has been configured for st we should treat as such. However, we could have an additional parameter to `fromBucket` trigger this behavior (e.g., `isConfiguredAsWebsite`?). 2. What level of breaking changes are acceptable for the existing `IDistribution` and `CloudFrontWebDistribution` resources? Notably, the -`IDistribution` interface should extend `IResource`, and `CloudFrontWebDistribution` changed from extending `Construct` to `Resource`. +`IDistribution` interface should extend `IResource`, and `CloudFrontWebDistribution` changed from extending `Construct` to `Resource`. Is this +worth it, given the breaking changes to existing consumers? The alternative is to leave both `IDistribution` and `CloudFrontWebDistribution` as-is, +and have `Distribution` directly extend `Resource`. 3. Related to the above, should the current (CloudFrontWebDistribution) construct be marked as "stable" to indicate we won't be making future updates to it? Any other suggestions on how we message the "v2" on the README to highlight the new option to customers? From eda308de685f6a23c9ee66a88d43dbe1f3fe5508 Mon Sep 17 00:00:00 2001 From: Nick Lynch Date: Tue, 7 Jul 2020 11:18:49 +0100 Subject: [PATCH 07/11] Minor changes per feedback; removing the 'draft' tag --- text/0171-cloudfront-redesign.md | 66 ++++++++++++++++---------------- 1 file changed, 33 insertions(+), 33 deletions(-) diff --git a/text/0171-cloudfront-redesign.md b/text/0171-cloudfront-redesign.md index 0e1d16c39..3ac6f477b 100644 --- a/text/0171-cloudfront-redesign.md +++ b/text/0171-cloudfront-redesign.md @@ -7,7 +7,7 @@ related issue: https://github.com/aws/aws-cdk-rfcs/issues/171 # Summary -(Draft) Proposal to redesign the @aws-cdk/aws-cloudfront module. +Proposal to redesign the @aws-cdk/aws-cloudfront module. The current module does not adhere to the best practice naming conventions or ease-of-use patterns that are present in the other CDK modules. A redesign of the API will allow for friendly, easier @@ -32,6 +32,8 @@ possible performance. CloudFront distributions deliver your content from one or more origins; an origin is the location where you store the original version of your content. Origins can be created from S3 buckets or a custom origin (HTTP server). +### From an S3 Bucket + An S3 bucket can be added as an origin. If the bucket is configured as a website endpoint, the distribution can use S3 redirects and S3 custom error documents. @@ -54,6 +56,8 @@ new cloudfront.Distribution(this, 'myDist', { Both of the S3 Origin options will automatically create an origin access identity and grant it access to the underlying bucket. This can be used in conjunction with a bucket that is not public to require that your users access your content using CloudFront URLs and not S3 URLs directly. +### From an HTTP endpoint + Origins can also be created from other resources (e.g., load balancers, API gateways), or from any accessible HTTP server. ```ts @@ -67,7 +71,7 @@ new cloudfront.Distribution(this, 'myDist', { new cloudfront.Distribution(this, 'myDist', { origin: cloudfront.Origin.fromHTTPServer({ domainName: 'www.example.com', - originProtocolPolicy: OriginProtocolPolicy.HTTPS_ONLY, + protocolPolicy: OriginProtocolPolicy.HTTPS_ONLY, }) }); ``` @@ -114,7 +118,7 @@ methods and viewer protocol policy of the cache. const myWebDistribution = new cloudfront.Distribution(this, 'myDist', { origin: cloudfront.Origin.fromHTTPServer({ domainName: 'www.example.com', - originProtocolPolicy: OriginProtocolPolicy.HTTPS_ONLY, + protocolPolicy: OriginProtocolPolicy.HTTPS_ONLY, }), behavior: { allowedMethods: AllowedMethods.ALL, @@ -154,6 +158,8 @@ const myMultiOriginDistribution = new cloudfront.Distribution(this, 'myDist', { }); ``` +## Origin Groups + You can specify an origin group for your CloudFront origin if, for example, you want to configure origin failover for scenarios when you need high availability. Use origin failover to designate a primary origin for CloudFront plus a second origin that CloudFront automatically switches to when the primary origin returns specific HTTP status code failure responses. An origin group can be created and specified as the primary (or additional) origin @@ -177,26 +183,30 @@ can author Node.js or Python functions in the US East (N. Virginia) region, and viewer, without provisioning or managing servers. Lambda@Edge functions are associated with a specific behavior and event type. Lambda@Edge can be used rewrite URLs, alter responses based on headers or cookies, or authorize requests based on headers or authorization tokens. -By default, Lambda@Edge functions are attached to the default behavior: +The following shows a Lambda@Edge function added to the default behavior and triggered on every request. ```ts const myFunc = new lambda.Function(...); -const myDist = new cloudfront.Distribution(...); -myDist.addLambdaFunctionAssociation({ - functionVersion: myFunc.currentVersion, - eventType: EventType.VIEWER_REQUEST, +new cloudfront.Distribution(this, 'myDist', { + origin: cloudfront.Origin.fromBucket(myBucket), + behavior: { + edgeFunctions: [{ + functionVersion: myFunc.currentVersion, + eventType: EventType.VIEWER_REQUEST, + }] + }, }); ``` -Lambda@Edge functions can also be associated with additional behaviors, either at behavior creation or after the fact, either by attaching -directly to the behavior, or to the distribution and referencing the behavior. +Lambda@Edge functions can also be associated with additional behaviors, either at behavior creation (associated with the origin) or after behavior +creation. ```ts // Assigning at behavior creation. myOrigin.addBehavior('/images/*.jpg', { viewerProtocolPolicy: ViewerProtocolPolicy.REDIRECT_TO_HTTPS, defaultTtl: cdk.Duration.days(7), - functionAssociation: [{ + edgeFunctions: [{ functionVersion: myFunc.currentVersion, eventType: EventType.VIEWER_REQUEST, }] @@ -204,16 +214,10 @@ myOrigin.addBehavior('/images/*.jpg', { // Assigning after creation. const myImagesBehavior = myOrigin.addBehavior('/images/*.jpg', ...); -myImagesBehavior.addFunctionAssociation({ +myImagesBehavior.addEdgeFunction({ functionVersion: myFunc.currentVersion, eventType: EventType.VIEWER_REQUEST, }); - -myDist.addFunctionAssociation({ - functionVersion: myFunc.currentVersion, - eventType: EventType.ORIGIN_REQUEST, - behavior: myImagesBehavior, -}); ``` --- @@ -226,11 +230,12 @@ interface to the module, and advance the module to a GA-ready state. # Design Summary -The approach will create a new top-level Construct (`Distribution`) to replace the existing `CloudFrontWebDistribution`, as well as new constructs -to represent the other logical resources for a distribution (i.e., `Origin`, `Behavior`). The new L2s will be created in the same aws-cloudfront -module and no changes will be made to the existing L2s to preserve the existing experience. Unlike the existing L2, the new L2s will feature a -variety of convenience methods (e.g., `addBehavior`) to aid in the creation of the distribution, and provide several out-of-the-box defaults for -building distributions off of other resources (e.g., buckets, load balanced services). +The approach will create a new top-level Construct (`Distribution`) to replace the existing `CloudFrontWebDistribution`, as well as new constructs to +represent the other logical resources for a distribution (i.e., `Origin`, `Behavior`). The new construct is optimized for the most common use cases of +creating a distribution with a single origin and behavior. The new L2s will be created in the same aws-cloudfront module and no changes will be made +to the existing L2s to preserve the existing experience. Unlike the existing L2, the new L2s will feature a variety of convenience methods (e.g., +`addBehavior`) to aid in the creation of the distribution, and provide several out-of-the-box defaults for building distributions off of other +resources (e.g., buckets, load balanced services). # Detailed Design @@ -248,7 +253,6 @@ class Distribution extends BaseDistribution { constructor(scope: Construct, id: string, props: DistributionProps) {} addOrigin(options: OriginOptions): Origin - addBehavior(pathPattern: string, options: BehaviorOptions): Behavior } class Origin { @@ -266,7 +270,7 @@ class Origin { class Behavior { constructor(props: BehaviorProps) {} - addFunctionAssociation(options: FunctionAssociationOptions): FunctionAssociation + addEdgeFunction(options: EdgeFunctionOptions): EdgeFunction } ``` @@ -382,7 +386,7 @@ new CloudFrontWebDistribution(this, 'dist', { { customOriginSource: { domainName: lbFargateService.loadBalancer.loadBalancerDnsName, - originProtocolPolicy: OriginProtocolPolicy.HTTPS_ONLY, + protocolPolicy: OriginProtocolPolicy.HTTPS_ONLY, }, behaviors: [{ isDefaultBehavior: true, @@ -413,7 +417,7 @@ const dist = new cloudfront.Distribution(this, 'MyDistribution', { behavior: { allowedMethods: AllowedMethods.ALL, forwardQueryString: true, - functionAssociations: [{ + edgeFunctions: [{ function: myFunctionVersion, eventType: EventType.ORIGIN_RESPONSE, }] @@ -455,15 +459,11 @@ to the new cloudfront.Distribution resource. # Unresolved questions -1. Are `fromBucket` and `fromWebsiteBucket` (potentially) redundant? There isn't enough information -on the `IBucket` interface to determine if the bucket has been configured for static web hosting and -we should treat as such. However, we could have an additional parameter to `fromBucket` trigger this -behavior (e.g., `isConfiguredAsWebsite`?). -2. What level of breaking changes are acceptable for the existing `IDistribution` and `CloudFrontWebDistribution` resources? Notably, the +1. What level of breaking changes are acceptable for the existing `IDistribution` and `CloudFrontWebDistribution` resources? Notably, the `IDistribution` interface should extend `IResource`, and `CloudFrontWebDistribution` changed from extending `Construct` to `Resource`. Is this worth it, given the breaking changes to existing consumers? The alternative is to leave both `IDistribution` and `CloudFrontWebDistribution` as-is, and have `Distribution` directly extend `Resource`. -3. Related to the above, should the current (CloudFrontWebDistribution) construct be marked as "stable" to indicate we won't be making future +2. Related to the above, should the current (CloudFrontWebDistribution) construct be marked as "stable" to indicate we won't be making future updates to it? Any other suggestions on how we message the "v2" on the README to highlight the new option to customers? # Future Possibilities From 5f2951286c09dc70ce4e1ded0cdd3f1c250e77fa Mon Sep 17 00:00:00 2001 From: Nick Lynch Date: Tue, 7 Jul 2020 17:57:30 +0100 Subject: [PATCH 08/11] Distribution.for* syntactic sugar --- text/0171-cloudfront-redesign.md | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/text/0171-cloudfront-redesign.md b/text/0171-cloudfront-redesign.md index 3ac6f477b..4d491e25a 100644 --- a/text/0171-cloudfront-redesign.md +++ b/text/0171-cloudfront-redesign.md @@ -46,11 +46,19 @@ new cloudfront.Distribution(this, 'myDist', { origin: cloudfront.Origin.fromBucket(myBucket), }); +// Equivalent to the above +const myBucket = new s3.Bucket(...); +cloudfront.Distribution.forBucket(this, 'myDist', myBucket); + // Creates a distribution for a S3 bucket that has been configured for website hosting. const myWebsiteBucket = new s3.Bucket(...); new cloudfront.Distribution(this, 'myDist', { origin: cloudfront.Origin.fromWebsiteBucket(myBucket), }); + +// Equivalent to the above +const myBucket = new s3.Bucket(...); +cloudfront.Distribution.forWebsiteBucket(this, 'myDist', myBucket); ``` Both of the S3 Origin options will automatically create an origin access identity and grant it access to the underlying bucket. This can be used in @@ -225,7 +233,7 @@ myImagesBehavior.addEdgeFunction({ # Motivation The existing aws-cloudfront module doesn't adhere to standard naming convention, lacks convenience methods for more easily interacting with -distributions, origins, and behaviors, and has been in an "experimental" state for years. This proposal aims to bring a friendlier, more ergonic +distributions, origins, and behaviors, and has been in an "experimental" state for years. This proposal aims to bring a friendlier, more ergonomic interface to the module, and advance the module to a GA-ready state. # Design Summary @@ -250,6 +258,9 @@ class Distribution extends BaseDistribution { static fromArn(scope: Construct, id: string, distributionArn: string): IDistribution; static fromAttributes(scope: Construct, id: string, distributionArn: string): IDistribution; + static forBucket(scope: Construct, id: string, bucket: IBucket): IDistribution; + static forWebsiteBucket(scope: Construct, id: string, bucket: IBucket): IDistribution; + constructor(scope: Construct, id: string, props: DistributionProps) {} addOrigin(options: OriginOptions): Origin From 4d1b6ca8a9eec30f25c44ecd51ae8eb88afe476b Mon Sep 17 00:00:00 2001 From: Nick Lynch Date: Tue, 7 Jul 2020 18:04:57 +0100 Subject: [PATCH 09/11] Removing Distribution.behavior, keeping origin->behavior nesting consistent --- text/0171-cloudfront-redesign.md | 32 +++++++++++++------------------- 1 file changed, 13 insertions(+), 19 deletions(-) diff --git a/text/0171-cloudfront-redesign.md b/text/0171-cloudfront-redesign.md index 4d491e25a..48d805a93 100644 --- a/text/0171-cloudfront-redesign.md +++ b/text/0171-cloudfront-redesign.md @@ -113,25 +113,21 @@ new cloudfront.Distribution(this, 'myDist', { Note that in the above example the aliases are inferred from the certificate and do not need to be explicitly provided. -## Caching Behaviors +## Behaviors -Each distribution has a default cache behavior which applies to all requests to that distribution; additional cache behaviors may be specified for a -given URL path pattern. Cache behaviors allowing routing with multiple origins, controlling which HTTP methods to support, whether to require users to -use HTTPS, and what query strings or cookies to forward to your origin, among other behaviors. +Each distribution has a default behavior which applies to all requests to that distribution; additional behaviors may be specified for a +given URL path pattern. Behaviors allow routing with multiple origins, controlling which HTTP methods to support, whether to require users to +use HTTPS, and what query strings or cookies to forward to your origin, among others. -The properties of the default cache behavior can be adjusted as part of the distribution creation. The following example shows configuring the HTTP +The properties of the default behavior can be adjusted as part of the distribution creation. The following example shows configuring the HTTP methods and viewer protocol policy of the cache. ```ts const myWebDistribution = new cloudfront.Distribution(this, 'myDist', { - origin: cloudfront.Origin.fromHTTPServer({ - domainName: 'www.example.com', - protocolPolicy: OriginProtocolPolicy.HTTPS_ONLY, - }), - behavior: { + origin: cloudfront.Origin.fromLoadBalancerV2(myLoadBalancer, { allowedMethods: AllowedMethods.ALL, viewerProtocolPolicy: ViewerProtocolPolicy.REDIRECT_TO_HTTPS, - } + }), }); ``` @@ -196,13 +192,12 @@ The following shows a Lambda@Edge function added to the default behavior and tri ```ts const myFunc = new lambda.Function(...); new cloudfront.Distribution(this, 'myDist', { - origin: cloudfront.Origin.fromBucket(myBucket), - behavior: { + origin: cloudfront.Origin.fromBucket(myBucket, { edgeFunctions: [{ functionVersion: myFunc.currentVersion, eventType: EventType.VIEWER_REQUEST, - }] - }, + }], + }), }); ``` @@ -424,15 +419,14 @@ new CloudFrontWebDistribution(this, 'dist', { ```ts const dist = new cloudfront.Distribution(this, 'MyDistribution', { - origin: cloudfront.Origin.fromLoadBalancerV2(lbFargateService.loadBalancer), - behavior: { + origin: cloudfront.Origin.fromLoadBalancerV2(lbFargateService.loadBalancer, { allowedMethods: AllowedMethods.ALL, forwardQueryString: true, edgeFunctions: [{ function: myFunctionVersion, eventType: EventType.ORIGIN_RESPONSE, - }] - } + }], + }), }); dist.addOrigin(cloudfront.Origin.fromBucket(sourceBucket), { pathPattern: 'static/*' }); ``` From 5a57233e54053d22b025d60ee599878aa7d69f09 Mon Sep 17 00:00:00 2001 From: Nick Lynch Date: Thu, 9 Jul 2020 09:29:27 +0100 Subject: [PATCH 10/11] Minor API updates per feedback and lessons learned from implementation --- text/0171-cloudfront-redesign.md | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/text/0171-cloudfront-redesign.md b/text/0171-cloudfront-redesign.md index 48d805a93..6e0a3acaf 100644 --- a/text/0171-cloudfront-redesign.md +++ b/text/0171-cloudfront-redesign.md @@ -77,7 +77,7 @@ new cloudfront.Distribution(this, 'myDist', { // Creates a distribution for an HTTP server. new cloudfront.Distribution(this, 'myDist', { - origin: cloudfront.Origin.fromHTTPServer({ + origin: cloudfront.Origin.fromHttpServer({ domainName: 'www.example.com', protocolPolicy: OriginProtocolPolicy.HTTPS_ONLY, }) @@ -250,11 +250,10 @@ The following is an incomplete, but representative, listing of the API: ```ts class Distribution extends BaseDistribution { - static fromArn(scope: Construct, id: string, distributionArn: string): IDistribution; - static fromAttributes(scope: Construct, id: string, distributionArn: string): IDistribution; + static fromDistributionAttributes(scope: Construct, id: string, attributes: DistributionAttributes): IDistribution; - static forBucket(scope: Construct, id: string, bucket: IBucket): IDistribution; - static forWebsiteBucket(scope: Construct, id: string, bucket: IBucket): IDistribution; + static forBucket(scope: Construct, id: string, bucket: IBucket): Distribution; + static forWebsiteBucket(scope: Construct, id: string, bucket: IBucket): Distribution; constructor(scope: Construct, id: string, props: DistributionProps) {} From 563ba0bb5c6a1518402921978620f7934ec0c1b8 Mon Sep 17 00:00:00 2001 From: Nick Lynch Date: Thu, 9 Jul 2020 16:01:07 +0100 Subject: [PATCH 11/11] Added implementation note for capturing behavior ordering --- text/0171-cloudfront-redesign.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/text/0171-cloudfront-redesign.md b/text/0171-cloudfront-redesign.md index 6e0a3acaf..a5692ccd2 100644 --- a/text/0171-cloudfront-redesign.md +++ b/text/0171-cloudfront-redesign.md @@ -307,6 +307,12 @@ This approach was chosen as the simplest pattern to work with for the majority o still giving those power users control over behavior ordering, albeit implicitly. See the Rationale and Alternatives section for a discussion of other ways this was considered. +**Implementation Note:** The relationships as-is are modeled with one-way connections; Distributions know about Origins, but Origins have no +references to Distributions, for example. This design makes the initial creation much simpler for users, but makes having a per-Distribution +ordered list of Behaviors impossible. To correct this, the Origin will need some form of reference to the Distribution, either at creation or +when being associated with the Distribution. The current (inelegant) proposal is for the Origin to expose a method (`_attachDistribution`) which +is called by the Distribution to create the relationship. Feedback on this approach (or more elegent proposals) are welcome. + ## Interaction with other L2s The existing @aws-cdk/aws-cloudfront module is used in three other modules of the CDK: (1) aws-route53-patterns, (2) aws-route53-targets, and @@ -469,6 +475,7 @@ worth it, given the breaking changes to existing consumers? The alternative is t and have `Distribution` directly extend `Resource`. 2. Related to the above, should the current (CloudFrontWebDistribution) construct be marked as "stable" to indicate we won't be making future updates to it? Any other suggestions on how we message the "v2" on the README to highlight the new option to customers? +3. Any better patterns for associating the Origin with the Distribution than something like the proposed `_attachDistribution`? # Future Possibilities