Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(cloudfront-origins): OriginGroup supports custom originId #31514

Open
wants to merge 6 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,13 @@ export interface OriginGroupProps {
* @default - 500, 502, 503 and 504
*/
readonly fallbackStatusCodes?: number[];

/**
* A unique identifier for the origin. This value must be unique within the distribution.
*
* @default - an originId will be generated for you
*/
readonly originId?: string;
}

/**
Expand All @@ -44,6 +51,7 @@ export class OriginGroup implements cloudfront.IOrigin {
failoverOrigin: this.props.fallbackOrigin,
statusCodes: this.props.fallbackStatusCodes,
},
originGroupId: this.props.originId,
};
}
}
124 changes: 114 additions & 10 deletions packages/aws-cdk-lib/aws-cloudfront-origins/test/origin-group.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,18 +7,21 @@ import * as origins from '../lib';
let stack: Stack;
let bucket: s3.IBucket;
let primaryOrigin: cloudfront.IOrigin;
let importedBucket: s3.IBucket;
let fallbackOrigin: cloudfront.IOrigin;
beforeEach(() => {
stack = new Stack();
bucket = new s3.Bucket(stack, 'Bucket');
primaryOrigin = new origins.S3Origin(bucket);
importedBucket = s3.Bucket.fromBucketName(stack, 'ImportedBucket', 'imported-bucket');
fallbackOrigin = new origins.S3Origin(importedBucket);
});

describe('Origin Groups', () => {
test('correctly render the OriginGroups property of DistributionConfig', () => {
const failoverOrigin = new origins.S3Origin(s3.Bucket.fromBucketName(stack, 'ImportedBucket', 'imported-bucket'));
const originGroup = new origins.OriginGroup({
primaryOrigin,
fallbackOrigin: failoverOrigin,
fallbackOrigin,
fallbackStatusCodes: [500],
});

Expand Down Expand Up @@ -95,10 +98,10 @@ describe('Origin Groups', () => {
});

test('correctly render the OriginGroups property of DistributionConfig with originId set', () => {
const failoverOrigin = new origins.S3Origin(s3.Bucket.fromBucketName(stack, 'ImportedBucket', 'imported-bucket'), { originId: 'MyCustomOrigin1' });
fallbackOrigin = new origins.S3Origin(importedBucket, { originId: 'MyCustomOrigin1' });
const originGroup = new origins.OriginGroup({
primaryOrigin,
fallbackOrigin: failoverOrigin,
fallbackOrigin,
fallbackStatusCodes: [500],
});

Expand Down Expand Up @@ -173,15 +176,117 @@ describe('Origin Groups', () => {
});
});

test('correctly render custom OriginGroup ID', () => {
const originGroupId = 'CustomOriginGroupId';
const originGroup = new origins.OriginGroup({
primaryOrigin,
fallbackOrigin,
fallbackStatusCodes: [500],
originId: originGroupId,
});

new cloudfront.Distribution(stack, 'Distribution', {
defaultBehavior: { origin: originGroup },
});

const primaryOriginId = 'DistributionOrigin13547B94F';
const failoverOriginId = 'DistributionOrigin2C85CC43B';
Template.fromStack(stack).hasResourceProperties('AWS::CloudFront::Distribution', {
DistributionConfig: {
DefaultCacheBehavior: {
TargetOriginId: originGroupId,
},
Origins: [
{
Id: primaryOriginId,
},
{
Id: failoverOriginId,
},
],
OriginGroups: {
Items: [
{
Id: originGroupId,
Members: {
Items: [
{ OriginId: primaryOriginId },
{ OriginId: failoverOriginId },
],
Quantity: 2,
},
},
],
Quantity: 1,
},
},
});
});

test('originId cannot be duplicated by another Origin added after the OriginGroup', () => {
const duplicateOriginId = 'DuplicateOrigin';
const originGroup = new origins.OriginGroup({
primaryOrigin,
fallbackOrigin,
fallbackStatusCodes: [500],
originId: duplicateOriginId,
});
const additionalOrigin = new origins.HttpOrigin('test', { originId: duplicateOriginId });

const distribution = new cloudfront.Distribution(stack, 'Distribution', {
defaultBehavior: { origin: originGroup },
});

expect(() => {
distribution.addBehavior('/x', additionalOrigin);
}).toThrow(`Origin with id ${duplicateOriginId} already exists`);
});

test('originId cannot duplicate a previously added Origin', () => {
const duplicateOriginId = 'DuplicateOrigin';
const originGroup = new origins.OriginGroup({
primaryOrigin,
fallbackOrigin,
fallbackStatusCodes: [500],
originId: duplicateOriginId,
});

const distribution = new cloudfront.Distribution(stack, 'Distribution', {
defaultBehavior: {
origin: new origins.HttpOrigin('test', { originId: duplicateOriginId }),
},
});

expect(() => {
distribution.addBehavior('/x', originGroup);
}).toThrow(`Origin with id ${duplicateOriginId} already exists`);
});

test('originId cannot duplicate primary Origin', () => {
const duplicateOriginId = 'DuplicateOrigin';
primaryOrigin = new origins.S3Origin(bucket, { originId: duplicateOriginId });
const originGroup = new origins.OriginGroup({
primaryOrigin,
fallbackOrigin,
fallbackStatusCodes: [500],
originId: duplicateOriginId,
});

expect(() => {
new cloudfront.Distribution(stack, 'Distribution', {
defaultBehavior: { origin: originGroup },
});
}).toThrow(`OriginGroup id ${duplicateOriginId} duplicates the primary Origin id`);
});

test('cannot have an Origin with their own failover configuration as the primary Origin', () => {
const failoverOrigin = new origins.S3Origin(s3.Bucket.fromBucketName(stack, 'ImportedBucket', 'imported-bucket'));
const originGroup = new origins.OriginGroup({
primaryOrigin,
fallbackOrigin: failoverOrigin,
fallbackOrigin,
});
const groupOfGroups = new origins.OriginGroup({
primaryOrigin: originGroup,
fallbackOrigin: failoverOrigin,
fallbackOrigin,
});

expect(() => {
Expand All @@ -194,7 +299,7 @@ describe('Origin Groups', () => {
test('cannot have an Origin with their own failover configuration as the fallback Origin', () => {
const originGroup = new origins.OriginGroup({
primaryOrigin,
fallbackOrigin: new origins.S3Origin(s3.Bucket.fromBucketName(stack, 'ImportedBucket', 'imported-bucket')),
fallbackOrigin,
});
const groupOfGroups = new origins.OriginGroup({
primaryOrigin,
Expand All @@ -209,10 +314,9 @@ describe('Origin Groups', () => {
});

test('cannot have an empty array of fallbackStatusCodes', () => {
const failoverOrigin = new origins.S3Origin(s3.Bucket.fromBucketName(stack, 'ImportedBucket', 'imported-bucket'));
const originGroup = new origins.OriginGroup({
primaryOrigin,
fallbackOrigin: failoverOrigin,
fallbackOrigin,
fallbackStatusCodes: [],
});

Expand Down
30 changes: 22 additions & 8 deletions packages/aws-cdk-lib/aws-cloudfront/lib/distribution.ts
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,6 @@ export interface DistributionAttributes {

interface BoundOrigin extends OriginBindOptions, OriginBindConfig {
readonly origin: IOrigin;
readonly originGroupId?: string;
}

/**
Expand Down Expand Up @@ -614,19 +613,16 @@ export class Distribution extends Resource implements IDistribution {
const distributionId = this.distributionId;
const originBindConfig = origin.bind(scope, { originId: generatedId, distributionId: Lazy.string({ produce: () => this.distributionId }) });
const originId = originBindConfig.originProperty?.id ?? generatedId;
const duplicateId = this.boundOrigins.find(boundOrigin => boundOrigin.originProperty?.id === originBindConfig.originProperty?.id);
if (duplicateId) {
throw new Error(`Origin with id ${duplicateId.originProperty?.id} already exists. OriginIds must be unique within a distribution`);
}
if (!originBindConfig.failoverConfig) {
this.boundOrigins.push({ origin, originId, distributionId, ...originBindConfig });
this.addBoundOrigin({ origin, originId, distributionId, ...originBindConfig });
} else {
if (isFailoverOrigin) {
throw new Error('An Origin cannot use an Origin with its own failover configuration as its fallback origin!');
}
const groupIndex = this.originGroups.length + 1;
const originGroupId = Names.uniqueId(new Construct(this, `OriginGroup${groupIndex}`)).slice(-ORIGIN_ID_MAX_LENGTH);
this.boundOrigins.push({ origin, originId, distributionId, originGroupId, ...originBindConfig });
const originGroupId = originBindConfig.originGroupId ??
Names.uniqueId(new Construct(this, `OriginGroup${groupIndex}`)).slice(-ORIGIN_ID_MAX_LENGTH);
this.addBoundOrigin({ origin, originId, distributionId, ...originBindConfig, originGroupId });

const failoverOriginId = this.addOrigin(originBindConfig.failoverConfig.failoverOrigin, true);
this.addOriginGroup(originGroupId, originBindConfig.failoverConfig.statusCodes, originId, failoverOriginId);
Expand All @@ -636,6 +632,24 @@ export class Distribution extends Resource implements IDistribution {
}
}

private addBoundOrigin(boundOrigin: BoundOrigin) {
const { originId } = boundOrigin;
if (originId === boundOrigin.originGroupId) {
throw new Error(`OriginGroup id ${originId} duplicates the primary Origin id. OriginIds must be unique within a distribution`);
}
const duplicate = this.findDuplicateOriginId(originId) ?? this.findDuplicateOriginId(boundOrigin.originGroupId);
if (duplicate) {
throw new Error(`Origin with id ${duplicate} already exists. OriginIds must be unique within a distribution`);
}
this.boundOrigins.push(boundOrigin);
}

private findDuplicateOriginId(originId: string | undefined): string | undefined {
const duplicate = originId && this.boundOrigins.some(boundOrigin =>
boundOrigin.originId === originId || boundOrigin.originGroupId === originId);
return duplicate ? originId : undefined;
}

private addOriginGroup(originGroupId: string, statusCodes: number[] | undefined, originId: string, failoverOriginId: string): void {
statusCodes = statusCodes ?? [500, 502, 503, 504];
if (statusCodes.length === 0) {
Expand Down
7 changes: 7 additions & 0 deletions packages/aws-cdk-lib/aws-cloudfront/lib/origin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,13 @@ export interface OriginBindConfig {
* @default - nothing is returned
*/
readonly failoverConfig?: OriginFailoverConfig;

/**
* The Origin ID, if this is an OriginGroup with an explicitly set ID.
*
* @default - nothing is returned
*/
readonly originGroupId?: string;
}

/**
Expand Down
Loading