Skip to content

Commit 1cbfb8f

Browse files
skinny85Curtis Eppel
authored and
Curtis Eppel
committed
chore(cloudfront): small refactoring of the Origin API (aws#9281)
Change the Origin to an interface, from an abstract class, and change its `bind` protocol to return an `OriginBindConfig` interface. This is in preparation for handling Origin Groups in aws#9109 - when time comes to handle Origin Groups, we will add a new (optional) property to `OriginBindConfig`, of type `CfnDistribution.OriginGroupProperty`, and handle it in `Distribution`. BREAKING CHANGE: the property Origin.domainName has been removed ---- *By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license*
1 parent 22ee7af commit 1cbfb8f

11 files changed

+121
-120
lines changed

packages/@aws-cdk/aws-cloudfront-origins/lib/s3-origin.ts

+4-17
Original file line numberDiff line numberDiff line change
@@ -26,9 +26,8 @@ export interface S3OriginProps {
2626
*
2727
* @experimental
2828
*/
29-
export class S3Origin extends cloudfront.Origin {
30-
31-
private readonly origin: cloudfront.Origin;
29+
export class S3Origin implements cloudfront.IOrigin {
30+
private readonly origin: cloudfront.IOrigin;
3231

3332
constructor(bucket: s3.IBucket, props: S3OriginProps = {}) {
3433
let proxyOrigin;
@@ -43,22 +42,10 @@ export class S3Origin extends cloudfront.Origin {
4342
...props,
4443
});
4544
}
46-
47-
super(proxyOrigin.domainName);
48-
4945
this.origin = proxyOrigin;
5046
}
5147

52-
public get id() {
53-
return this.origin.id;
54-
}
55-
56-
public bind(scope: cdk.Construct, options: cloudfront.OriginBindOptions) {
57-
this.origin.bind(scope, options);
58-
}
59-
60-
public renderOrigin() {
61-
return this.origin.renderOrigin();
48+
public bind(scope: cdk.Construct, options: cloudfront.OriginBindOptions): cloudfront.OriginBindConfig {
49+
return this.origin.bind(scope, options);
6250
}
63-
6451
}

packages/@aws-cdk/aws-cloudfront-origins/test/http-origin.test.ts

+4-4
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,9 @@ beforeEach(() => {
1515

1616
test('Renders minimal example with just a domain name', () => {
1717
const origin = new HttpOrigin('www.example.com');
18-
origin.bind(stack, { originIndex: 0 });
18+
const originBindConfig = origin.bind(stack, { originId: 'StackOrigin029E19582' });
1919

20-
expect(origin.renderOrigin()).toEqual({
20+
expect(originBindConfig.originProperty).toEqual({
2121
id: 'StackOrigin029E19582',
2222
domainName: 'www.example.com',
2323
customOriginConfig: {
@@ -32,9 +32,9 @@ test('Can customize properties of the origin', () => {
3232
readTimeout: Duration.seconds(10),
3333
protocolPolicy: cloudfront.OriginProtocolPolicy.MATCH_VIEWER,
3434
});
35-
origin.bind(stack, { originIndex: 0 });
35+
const originBindConfig = origin.bind(stack, { originId: 'StackOrigin029E19582' });
3636

37-
expect(origin.renderOrigin()).toEqual({
37+
expect(originBindConfig.originProperty).toEqual({
3838
id: 'StackOrigin029E19582',
3939
domainName: 'www.example.com',
4040
originCustomHeaders: [{

packages/@aws-cdk/aws-cloudfront-origins/test/integ.s3-origin.expected.json

+4-4
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@
2323
"Principal": {
2424
"CanonicalUser": {
2525
"Fn::GetAtt": [
26-
"DistributionS3Origin115FD918D",
26+
"DistributionOrigin1S3Origin5F5C0696",
2727
"S3CanonicalUserId"
2828
]
2929
}
@@ -56,7 +56,7 @@
5656
}
5757
}
5858
},
59-
"DistributionS3Origin115FD918D": {
59+
"DistributionOrigin1S3Origin5F5C0696": {
6060
"Type": "AWS::CloudFront::CloudFrontOriginAccessIdentity",
6161
"Properties": {
6262
"CloudFrontOriginAccessIdentityConfig": {
@@ -92,7 +92,7 @@
9292
[
9393
"origin-access-identity/cloudfront/",
9494
{
95-
"Ref": "DistributionS3Origin115FD918D"
95+
"Ref": "DistributionOrigin1S3Origin5F5C0696"
9696
}
9797
]
9898
]
@@ -104,4 +104,4 @@
104104
}
105105
}
106106
}
107-
}
107+
}

packages/@aws-cdk/aws-cloudfront-origins/test/load-balancer-origin.test.ts

+4-4
Original file line numberDiff line numberDiff line change
@@ -21,9 +21,9 @@ test('Renders minimal example with just a load balancer', () => {
2121
});
2222

2323
const origin = new LoadBalancerV2Origin(loadBalancer);
24-
origin.bind(stack, { originIndex: 0 });
24+
const originBindConfig = origin.bind(stack, { originId: 'StackOrigin029E19582' });
2525

26-
expect(origin.renderOrigin()).toEqual({
26+
expect(originBindConfig.originProperty).toEqual({
2727
id: 'StackOrigin029E19582',
2828
domainName: loadBalancer.loadBalancerDnsName,
2929
customOriginConfig: {
@@ -43,9 +43,9 @@ test('Can customize properties of the origin', () => {
4343
connectionTimeout: Duration.seconds(5),
4444
protocolPolicy: cloudfront.OriginProtocolPolicy.MATCH_VIEWER,
4545
});
46-
origin.bind(stack, { originIndex: 0 });
46+
const originBindConfig = origin.bind(stack, { originId: 'StackOrigin029E19582' });
4747

48-
expect(origin.renderOrigin()).toEqual({
48+
expect(originBindConfig.originProperty).toEqual({
4949
id: 'StackOrigin029E19582',
5050
domainName: loadBalancer.loadBalancerDnsName,
5151
connectionAttempts: 3,

packages/@aws-cdk/aws-cloudfront-origins/test/s3-origin.test.ts

+7-7
Original file line numberDiff line numberDiff line change
@@ -17,9 +17,9 @@ test('With non-website bucket, renders all required properties, including S3Orig
1717
const bucket = new s3.Bucket(stack, 'Bucket');
1818

1919
const origin = new S3Origin(bucket);
20-
origin.bind(stack, { originIndex: 0 });
20+
const originBindConfig = origin.bind(stack, { originId: 'StackOrigin029E19582' });
2121

22-
expect(origin.renderOrigin()).toEqual({
22+
expect(originBindConfig.originProperty).toEqual({
2323
id: 'StackOrigin029E19582',
2424
domainName: bucket.bucketRegionalDomainName,
2525
s3OriginConfig: {
@@ -34,9 +34,9 @@ test('With website bucket, renders all required properties, including custom ori
3434
});
3535

3636
const origin = new S3Origin(bucket);
37-
origin.bind(stack, { originIndex: 0 });
37+
const originBindConfig = origin.bind(stack, { originId: 'StackOrigin029E19582' });
3838

39-
expect(origin.renderOrigin()).toEqual({
39+
expect(originBindConfig.originProperty).toEqual({
4040
id: 'StackOrigin029E19582',
4141
domainName: bucket.bucketWebsiteDomainName,
4242
customOriginConfig: {
@@ -51,14 +51,14 @@ test('Respects props passed down to underlying origin', () => {
5151
});
5252

5353
const origin = new S3Origin(bucket, { originPath: '/website' });
54-
origin.bind(stack, { originIndex: 0 });
54+
const originBindConfig = origin.bind(stack, { originId: 'StackOrigin029E19582' });
5555

56-
expect(origin.renderOrigin()).toEqual({
56+
expect(originBindConfig.originProperty).toEqual({
5757
id: 'StackOrigin029E19582',
5858
domainName: bucket.bucketWebsiteDomainName,
5959
originPath: '/website',
6060
customOriginConfig: {
6161
originProtocolPolicy: 'http-only',
6262
},
6363
});
64-
});
64+
});

packages/@aws-cdk/aws-cloudfront/lib/distribution.ts

+29-14
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import * as acm from '@aws-cdk/aws-certificatemanager';
22
import * as lambda from '@aws-cdk/aws-lambda';
33
import { Construct, IResource, Lazy, Resource, Stack, Token, Duration } from '@aws-cdk/core';
44
import { CfnDistribution } from './cloudfront.generated';
5-
import { Origin } from './origin';
5+
import { IOrigin, OriginBindConfig, OriginBindOptions } from './origin';
66
import { CacheBehavior } from './private/cache-behavior';
77

88
/**
@@ -53,6 +53,10 @@ export interface DistributionAttributes {
5353
readonly distributionId: string;
5454
}
5555

56+
interface BoundOrigin extends OriginBindOptions, OriginBindConfig {
57+
readonly origin: IOrigin;
58+
}
59+
5660
/**
5761
* Properties for a Distribution
5862
*
@@ -127,7 +131,7 @@ export class Distribution extends Resource implements IDistribution {
127131

128132
private readonly defaultBehavior: CacheBehavior;
129133
private readonly additionalBehaviors: CacheBehavior[] = [];
130-
private readonly origins: Set<Origin> = new Set<Origin>();
134+
private readonly boundOrigins: BoundOrigin[] = [];
131135

132136
private readonly errorResponses: ErrorResponse[];
133137
private readonly certificate?: acm.ICertificate;
@@ -142,8 +146,8 @@ export class Distribution extends Resource implements IDistribution {
142146
}
143147
}
144148

145-
this.defaultBehavior = new CacheBehavior({ pathPattern: '*', ...props.defaultBehavior });
146-
this.addOrigin(this.defaultBehavior.origin);
149+
const originId = this.addOrigin(props.defaultBehavior.origin);
150+
this.defaultBehavior = new CacheBehavior(originId, { pathPattern: '*', ...props.defaultBehavior });
147151
if (props.additionalBehaviors) {
148152
Object.entries(props.additionalBehaviors).forEach(([pathPattern, behaviorOptions]) => {
149153
this.addBehavior(pathPattern, behaviorOptions.origin, behaviorOptions);
@@ -172,26 +176,38 @@ export class Distribution extends Resource implements IDistribution {
172176
* Adds a new behavior to this distribution for the given pathPattern.
173177
*
174178
* @param pathPattern the path pattern (e.g., 'images/*') that specifies which requests to apply the behavior to.
179+
* @param origin the origin to use for this behavior
175180
* @param behaviorOptions the options for the behavior at this path.
176181
*/
177-
public addBehavior(pathPattern: string, origin: Origin, behaviorOptions: AddBehaviorOptions = {}) {
182+
public addBehavior(pathPattern: string, origin: IOrigin, behaviorOptions: AddBehaviorOptions = {}) {
178183
if (pathPattern === '*') {
179184
throw new Error('Only the default behavior can have a path pattern of \'*\'');
180185
}
181-
this.additionalBehaviors.push(new CacheBehavior({ pathPattern, origin, ...behaviorOptions }));
182-
this.addOrigin(origin);
186+
const originId = this.addOrigin(origin);
187+
this.additionalBehaviors.push(new CacheBehavior(originId, { pathPattern, ...behaviorOptions }));
183188
}
184189

185-
private addOrigin(origin: Origin) {
186-
if (!this.origins.has(origin)) {
187-
this.origins.add(origin);
188-
origin.bind(this, { originIndex: this.origins.size });
190+
private addOrigin(origin: IOrigin): string {
191+
const existingOrigin = this.boundOrigins.find(boundOrigin => boundOrigin.origin === origin);
192+
if (existingOrigin) {
193+
return existingOrigin.originId;
194+
} else {
195+
const originIndex = this.boundOrigins.length + 1;
196+
const scope = new Construct(this, `Origin${originIndex}`);
197+
const originId = scope.node.uniqueId;
198+
const originBindConfig = origin.bind(scope, { originId });
199+
this.boundOrigins.push({ origin, originId, ...originBindConfig });
200+
return originId;
189201
}
190202
}
191203

192204
private renderOrigins(): CfnDistribution.OriginProperty[] {
193205
const renderedOrigins: CfnDistribution.OriginProperty[] = [];
194-
this.origins.forEach(origin => renderedOrigins.push(origin.renderOrigin()));
206+
this.boundOrigins.forEach(boundOrigin => {
207+
if (boundOrigin.originProperty) {
208+
renderedOrigins.push(boundOrigin.originProperty);
209+
}
210+
});
195211
return renderedOrigins;
196212
}
197213

@@ -229,7 +245,6 @@ export class Distribution extends Resource implements IDistribution {
229245
minimumProtocolVersion: SecurityPolicyProtocol.TLS_V1_2_2018,
230246
};
231247
}
232-
233248
}
234249

235250
/**
@@ -443,5 +458,5 @@ export interface BehaviorOptions extends AddBehaviorOptions {
443458
/**
444459
* The origin that you want CloudFront to route requests to when they match this behavior.
445460
*/
446-
readonly origin: Origin;
461+
readonly origin: IOrigin;
447462
}

packages/@aws-cdk/aws-cloudfront/lib/origin.ts

+35-23
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,28 @@ import { CfnDistribution } from './cloudfront.generated';
44
import { OriginProtocolPolicy } from './distribution';
55
import { OriginAccessIdentity } from './origin_access_identity';
66

7+
/** The struct returned from {@link IOrigin.bind}. */
8+
export interface OriginBindConfig {
9+
/**
10+
* The CloudFormation OriginProperty configuration for this Origin.
11+
*
12+
* @default - nothing is returned
13+
*/
14+
readonly originProperty?: CfnDistribution.OriginProperty;
15+
}
16+
17+
/**
18+
* Represents the concept of a CloudFront Origin.
19+
* You provide one or more origins when creating a Distribution.
20+
*/
21+
export interface IOrigin {
22+
/**
23+
* The method called when a given Origin is added
24+
* (for the first time) to a Distribution.
25+
*/
26+
bind(scope: Construct, options: OriginBindOptions): OriginBindConfig;
27+
}
28+
729
/**
830
* Properties to define an Origin.
931
*
@@ -48,9 +70,10 @@ export interface OriginProps {
4870
*/
4971
export interface OriginBindOptions {
5072
/**
51-
* The positional index of this origin within the distribution. Used for ensuring unique IDs.
73+
* The identifier of this Origin,
74+
* as assigned by the Distribution this Origin has been used added to.
5275
*/
53-
readonly originIndex: number;
76+
readonly originId: string;
5477
}
5578

5679
/**
@@ -59,13 +82,8 @@ export interface OriginBindOptions {
5982
*
6083
* @experimental
6184
*/
62-
export abstract class Origin {
63-
64-
/**
65-
* The domain name of the origin.
66-
*/
67-
public readonly domainName: string;
68-
85+
export abstract class Origin implements IOrigin {
86+
private readonly domainName: string;
6987
private readonly originPath?: string;
7088
private readonly connectionTimeout?: Duration;
7189
private readonly connectionAttempts?: number;
@@ -97,22 +115,17 @@ export abstract class Origin {
97115
/**
98116
* Binds the origin to the associated Distribution. Can be used to grant permissions, create dependent resources, etc.
99117
*/
100-
public bind(scope: Construct, options: OriginBindOptions): void {
101-
this.originId = new Construct(scope, `Origin${options.originIndex}`).node.uniqueId;
102-
}
118+
public bind(_scope: Construct, options: OriginBindOptions): OriginBindConfig {
119+
this.originId = options.originId;
103120

104-
/**
105-
* Creates and returns the CloudFormation representation of this origin.
106-
*/
107-
public renderOrigin(): CfnDistribution.OriginProperty {
108121
const s3OriginConfig = this.renderS3OriginConfig();
109122
const customOriginConfig = this.renderCustomOriginConfig();
110123

111124
if (!s3OriginConfig && !customOriginConfig) {
112125
throw new Error('Subclass must override and provide either s3OriginConfig or customOriginConfig');
113126
}
114127

115-
return {
128+
return { originProperty: {
116129
domainName: this.domainName,
117130
id: this.id,
118131
originPath: this.originPath,
@@ -121,7 +134,7 @@ export abstract class Origin {
121134
originCustomHeaders: this.renderCustomHeaders(),
122135
s3OriginConfig,
123136
customOriginConfig,
124-
};
137+
}};
125138
}
126139

127140
// Overridden by sub-classes to provide S3 origin config.
@@ -153,7 +166,6 @@ export abstract class Origin {
153166
if (path.endsWith('/')) { path = path.substr(0, path.length - 1); }
154167
return path;
155168
}
156-
157169
}
158170

159171
/**
@@ -184,12 +196,12 @@ export class S3Origin extends Origin {
184196
this.bucket = props.bucket;
185197
}
186198

187-
public bind(scope: Construct, options: OriginBindOptions) {
188-
super.bind(scope, options);
199+
public bind(scope: Construct, options: OriginBindOptions): OriginBindConfig {
189200
if (!this.originAccessIdentity) {
190-
this.originAccessIdentity = new OriginAccessIdentity(scope, `S3Origin${options.originIndex}`);
201+
this.originAccessIdentity = new OriginAccessIdentity(scope, 'S3Origin');
191202
this.bucket.grantRead(this.originAccessIdentity);
192203
}
204+
return super.bind(scope, options);
193205
}
194206

195207
protected renderS3OriginConfig(): CfnDistribution.S3OriginConfigProperty | undefined {
@@ -275,4 +287,4 @@ function validateIntInRangeOrUndefined(name: string, min: number, max: number, v
275287
const seconds = isDuration ? ' seconds' : '';
276288
throw new Error(`${name}: Must be an int between ${min} and ${max}${seconds} (inclusive); received ${value}.`);
277289
}
278-
}
290+
}

0 commit comments

Comments
 (0)