@aws-solutions-constructs/aws-cloudfront-s3
Version:
CDK Constructs for AWS Cloudfront to AWS S3 integration.
793 lines • 101 kB
JavaScript
"use strict";
/**
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance
* with the License. A copy of the License is located at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES
* OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions
* and limitations under the License.
*/
Object.defineProperty(exports, "__esModule", { value: true });
const assertions_1 = require("aws-cdk-lib/assertions");
const s3 = require("aws-cdk-lib/aws-s3");
const cdk = require("aws-cdk-lib");
const aws_cdk_lib_1 = require("aws-cdk-lib");
const lib_1 = require("../lib");
const acm = require("aws-cdk-lib/aws-certificatemanager");
const defaults = require("@aws-solutions-constructs/core");
const aws_kms_1 = require("aws-cdk-lib/aws-kms");
const origins = require("aws-cdk-lib/aws-cloudfront-origins");
function deploy(stack, props) {
return new lib_1.CloudFrontToS3(stack, 'test-cloudfront-s3', {
bucketProps: {
removalPolicy: cdk.RemovalPolicy.DESTROY,
},
...props
});
}
test('construct defaults set properties correctly', () => {
const stack = new cdk.Stack();
const construct = new lib_1.CloudFrontToS3(stack, 'test-cloudfront-s3', {});
expect(construct.cloudFrontWebDistribution).toBeDefined();
expect(construct.cloudFrontFunction).toBeDefined();
expect(construct.cloudFrontLoggingBucket).toBeDefined();
expect(construct.s3Bucket).toBeDefined();
expect(construct.s3LoggingBucket).toBeDefined();
expect(construct.s3BucketInterface).toBeDefined();
expect(construct.cloudFrontLoggingBucketAccessLogBucket).toBeDefined();
expect(construct.originAccessControl).toBeDefined();
});
test('check s3Bucket default encryption', () => {
const stack = new cdk.Stack();
deploy(stack);
const template = assertions_1.Template.fromStack(stack);
template.hasResourceProperties('AWS::S3::Bucket', {
BucketEncryption: {
ServerSideEncryptionConfiguration: [{
ServerSideEncryptionByDefault: {
SSEAlgorithm: "AES256"
}
}]
}
});
});
test('check s3Bucket public access block configuration', () => {
const stack = new cdk.Stack();
deploy(stack);
const template = assertions_1.Template.fromStack(stack);
template.hasResourceProperties('AWS::S3::Bucket', {
PublicAccessBlockConfiguration: {
BlockPublicAcls: true,
BlockPublicPolicy: true,
IgnorePublicAcls: true,
RestrictPublicBuckets: true
}
});
});
test('test s3Bucket override publicAccessBlockConfiguration', () => {
const stack = new cdk.Stack();
const props = {
bucketProps: {
blockPublicAccess: {
blockPublicAcls: false,
blockPublicPolicy: true,
ignorePublicAcls: false,
restrictPublicBuckets: true
}
}
};
new lib_1.CloudFrontToS3(stack, 'test-cloudfront-s3', props);
const template = assertions_1.Template.fromStack(stack);
template.hasResourceProperties("AWS::S3::Bucket", {
PublicAccessBlockConfiguration: {
BlockPublicAcls: false,
BlockPublicPolicy: true,
IgnorePublicAcls: false,
RestrictPublicBuckets: true
},
});
});
test('check existing bucket', () => {
const stack = new cdk.Stack();
const existingBucket = new s3.Bucket(stack, 'my-bucket', {
bucketName: 'my-bucket'
});
const props = {
existingBucketObj: existingBucket
};
new lib_1.CloudFrontToS3(stack, 'test-cloudfront-s3', props);
const template = assertions_1.Template.fromStack(stack);
template.hasResourceProperties("AWS::S3::Bucket", {
BucketName: "my-bucket"
});
});
test('check exception for Missing existingObj from props for deploy = false', () => {
const stack = new cdk.Stack();
try {
new lib_1.CloudFrontToS3(stack, 'test-cloudfront-s3', {});
}
catch (e) {
expect(e).toBeInstanceOf(Error);
}
});
test('check properties', () => {
const stack = new cdk.Stack();
const construct = deploy(stack);
expect(construct.cloudFrontWebDistribution).toBeDefined();
expect(construct.s3Bucket).toBeDefined();
});
test("Confirm CheckS3Props is called", () => {
// Stack
const stack = new cdk.Stack();
const testBucket = new s3.Bucket(stack, 'test-bucket', {});
const app = () => {
// Helper declaration
new lib_1.CloudFrontToS3(stack, "bad-s3-args", {
existingBucketObj: testBucket,
bucketProps: {
removalPolicy: aws_cdk_lib_1.RemovalPolicy.DESTROY
},
});
};
// Assertion
expect(app).toThrowError('Error - Either provide bucketProps or existingBucketObj, but not both.\n');
});
test("Test existingBucketObj", () => {
// Stack
const stack = new cdk.Stack();
const construct = new lib_1.CloudFrontToS3(stack, "existingIBucket", {
existingBucketObj: s3.Bucket.fromBucketName(stack, 'mybucket', 'mybucket')
});
// Assertion
expect(construct.cloudFrontWebDistribution).toBeDefined();
const template = assertions_1.Template.fromStack(stack);
template.hasResourceProperties("AWS::CloudFront::Distribution", {
DistributionConfig: {
Origins: [
{
DomainName: {
"Fn::Join": [
"",
[
"mybucket.s3.",
{
Ref: "AWS::Region"
},
".",
{
Ref: "AWS::URLSuffix"
}
]
]
},
Id: "existingIBucketCloudFrontDistributionOrigin1D5849125",
OriginAccessControlId: { "Fn::GetAtt": ["existingIBucketCloudFrontOacEB42E98F", "Id"] },
S3OriginConfig: {}
}
]
}
});
});
test('test cloudfront with custom domain names', () => {
const stack = new cdk.Stack();
const certificate = acm.Certificate.fromCertificateArn(stack, 'Cert', 'arn:${Aws.PARTITION}:acm:us-east-1:123456789012:certificate/11112222-3333-1234-1234-123456789012');
const props = {
cloudFrontDistributionProps: {
domainNames: ['mydomains'],
certificate
}
};
new lib_1.CloudFrontToS3(stack, 'test-cloudfront-s3', props);
const template = assertions_1.Template.fromStack(stack);
template.hasResourceProperties("AWS::CloudFront::Distribution", {
DistributionConfig: {
Aliases: [
"mydomains"
]
}
});
});
test('Cloudfront logging bucket with destroy removal policy and auto delete objects', () => {
const stack = new cdk.Stack();
const cloudfrontLogBucketName = 'cf-log-bucket';
new lib_1.CloudFrontToS3(stack, 'cloudfront-s3', {
cloudFrontLoggingBucketProps: {
removalPolicy: cdk.RemovalPolicy.DESTROY,
autoDeleteObjects: true,
bucketName: cloudfrontLogBucketName
}
});
const template = assertions_1.Template.fromStack(stack);
template.hasResourceProperties("AWS::S3::Bucket", {
OwnershipControls: { Rules: [{ ObjectOwnership: "ObjectWriter" }] },
BucketName: cloudfrontLogBucketName,
});
template.hasResourceProperties("Custom::S3AutoDeleteObjects", {
ServiceToken: {
"Fn::GetAtt": [
"CustomS3AutoDeleteObjectsCustomResourceProviderHandler9D90184F",
"Arn"
]
},
BucketName: {
Ref: "cloudfronts3CloudfrontLoggingBucket5B845143"
}
});
});
test('s3 bucket with one content bucket and no access logging of CONTENT bucket', () => {
const stack = new cdk.Stack();
const construct = new lib_1.CloudFrontToS3(stack, 'cloudfront-s3', {
bucketProps: {
removalPolicy: cdk.RemovalPolicy.DESTROY,
},
logS3AccessLogs: false
});
const template = assertions_1.Template.fromStack(stack);
// Content bucket+Cloudfront Logs bucket+
// Access Log bucket for Cloudfront Logs bucket = 3 buckets
template.resourceCountIs("AWS::S3::Bucket", 3);
expect(construct.s3LoggingBucket).toEqual(undefined);
});
test('CloudFront origin path present when provided', () => {
const stack = new cdk.Stack();
new lib_1.CloudFrontToS3(stack, 'cloudfront-s3', {
originPath: '/testPath'
});
const template = assertions_1.Template.fromStack(stack);
template.hasResourceProperties("AWS::CloudFront::Distribution", {
DistributionConfig: {
Origins: [
{
OriginPath: "/testPath",
}
]
}
});
});
test('CloudFront origin path should not be present if not provided', () => {
const stack = new cdk.Stack();
new lib_1.CloudFrontToS3(stack, 'cloudfront-s3', {});
defaults.expectNonexistence(stack, "AWS::CloudFront::Distribution", {
DistributionConfig: {
Origins: [
{
OriginPath: "/testPath",
}
]
}
});
});
test('Test the deployment with securityHeadersBehavior instead of HTTP security headers', () => {
// Initial setup
const stack = new aws_cdk_lib_1.Stack();
const cloudFrontToS3 = new lib_1.CloudFrontToS3(stack, 'test-cloudfront-s3', {
insertHttpSecurityHeaders: false,
responseHeadersPolicyProps: {
securityHeadersBehavior: {
strictTransportSecurity: {
accessControlMaxAge: aws_cdk_lib_1.Duration.seconds(63072),
includeSubdomains: true,
override: true,
preload: true
},
contentSecurityPolicy: {
contentSecurityPolicy: "upgrade-insecure-requests; default-src 'none';",
override: true
},
}
}
});
// Assertion
const template = assertions_1.Template.fromStack(stack);
template.hasResourceProperties("AWS::CloudFront::ResponseHeadersPolicy", {
ResponseHeadersPolicyConfig: {
SecurityHeadersConfig: {
ContentSecurityPolicy: {
ContentSecurityPolicy: "upgrade-insecure-requests; default-src 'none';",
Override: true
},
StrictTransportSecurity: {
AccessControlMaxAgeSec: 63072,
IncludeSubdomains: true,
Override: true,
Preload: true
}
}
}
});
expect(cloudFrontToS3.cloudFrontFunction).toEqual(undefined);
});
test("throw exception if insertHttpSecurityHeaders and responseHeadersPolicyProps are provided", () => {
const stack = new cdk.Stack();
expect(() => {
new lib_1.CloudFrontToS3(stack, "test-cloudfront-s3", {
insertHttpSecurityHeaders: true,
responseHeadersPolicyProps: {
securityHeadersBehavior: {
strictTransportSecurity: {
accessControlMaxAge: aws_cdk_lib_1.Duration.seconds(63072),
includeSubdomains: true,
override: false,
preload: true
}
}
}
});
}).toThrowError();
});
test("Confirm CheckCloudFrontProps is being called", () => {
const stack = new cdk.Stack();
expect(() => {
new lib_1.CloudFrontToS3(stack, "test-cloudfront-apigateway", {
insertHttpSecurityHeaders: true,
responseHeadersPolicyProps: {
securityHeadersBehavior: {
strictTransportSecurity: {
accessControlMaxAge: aws_cdk_lib_1.Duration.seconds(63072),
includeSubdomains: true,
override: false,
preload: true
}
}
}
});
}).toThrowError('responseHeadersPolicyProps.securityHeadersBehavior can only be passed if httpSecurityHeaders is set to `false`.');
});
test("Custom resource is provisioned if encryption key is provided as bucketProp", () => {
const stack = new cdk.Stack();
const encryptionKey = new aws_kms_1.Key(stack, 'cmkKey', {
enableKeyRotation: true,
removalPolicy: aws_cdk_lib_1.RemovalPolicy.DESTROY
});
deploy(stack, {
bucketProps: {
encryptionKey,
encryption: s3.BucketEncryption.KMS
}
});
const template = assertions_1.Template.fromStack(stack);
// 2 Functions - our custom resource and a function created by the CDK
template.resourceCountIs('AWS::Lambda::Function', 2);
template.hasResourceProperties('AWS::Lambda::Function', {
Description: "Custom resource function that updates a provided key policy to allow CloudFront access.",
Role: {
"Fn::GetAtt": ["testcloudfronts3LambdaFunctionServiceRole2A43EA92", "Arn"]
}
});
});
test("Custom resource is provisioned if CMK was used to encrypt an existing bucket", () => {
const stack = new cdk.Stack();
const encryptionKey = new aws_kms_1.Key(stack, 'cmkKey', {
enableKeyRotation: true,
removalPolicy: aws_cdk_lib_1.RemovalPolicy.DESTROY
});
const existingBucketObj = defaults.buildS3Bucket(stack, {
bucketProps: {
encryption: s3.BucketEncryption.KMS,
encryptionKey
}
}, 'existing-s3-bucket-encrypted-with-cmk').bucket;
new lib_1.CloudFrontToS3(stack, 'test-cloudfront-s3', {
existingBucketObj
});
const template = assertions_1.Template.fromStack(stack);
// 2 Functions - our custom resource and a function created by the CDK
template.resourceCountIs('AWS::Lambda::Function', 2);
// ensure that our Function has the correct role attached
template.hasResourceProperties('AWS::Lambda::Function', {
Description: "Custom resource function that updates a provided key policy to allow CloudFront access.",
Role: {
"Fn::GetAtt": ["testcloudfronts3LambdaFunctionServiceRole2A43EA92", "Arn"]
}
});
});
test("Custom resource is not provisioned if encryption key is not provided as bucketProp", () => {
const stack = new cdk.Stack();
deploy(stack);
const template = assertions_1.Template.fromStack(stack);
template.resourceCountIs('AWS::Lambda::Function', 0);
});
test("Custom resource is not provisioned if CMK was not used to encrypt an existing bucket", () => {
const stack = new cdk.Stack();
const existingBucketObj = defaults.buildS3Bucket(stack, {}, 'existing-s3-bucket-encrypted-with-cmk').bucket;
new lib_1.CloudFrontToS3(stack, 'test-cloudfront-s3', {
existingBucketObj
});
const template = assertions_1.Template.fromStack(stack);
template.resourceCountIs('AWS::Lambda::Function', 0);
});
test("HttpOrigin is provisioned if a static website bucket is used", () => {
const stack = new cdk.Stack();
const blockPublicAccess = false;
const props = {
bucketProps: {
enforceSSL: false,
publicReadAccess: true, // <-- required for isWebsite
blockPublicAccess: {
blockPublicAcls: blockPublicAccess,
restrictPublicBuckets: blockPublicAccess,
blockPublicPolicy: blockPublicAccess,
ignorePublicAcls: blockPublicAccess
},
websiteIndexDocument: "index.html" // <-- required for isWebsite
},
insertHttpSecurityHeaders: false
};
const construct = new lib_1.CloudFrontToS3(stack, 'test-cloudfront-s3', props);
const template = assertions_1.Template.fromStack(stack);
// Assert resources
template.resourceCountIs('AWS::CloudFront::OriginAccessControl', 0);
template.hasResourceProperties('AWS::CloudFront::Distribution', {
DistributionConfig: {
Origins: [
{
CustomOriginConfig: {
OriginProtocolPolicy: "http-only"
}
}
]
}
});
template.resourceCountIs('AWS::CloudFront::OriginAccessIdentity', 0);
// Assert pattern properties (output props)
expect(construct.originAccessControl).toBe(undefined);
});
test("OAC is provisioned in all other cases", () => {
const stack = new cdk.Stack();
const construct = new lib_1.CloudFrontToS3(stack, 'test-cloudfront-s3', {});
const template = assertions_1.Template.fromStack(stack);
// Assert resources
template.resourceCountIs('AWS::CloudFront::OriginAccessControl', 1);
template.resourceCountIs('AWS::CloudFront::OriginAccessIdentity', 0);
// Assert pattern properties (output props)
expect(construct.originAccessControl).not.toBe(undefined);
});
test("If a customer provides their own httpOrigin, or other origin type, use that one", () => {
const stack = new cdk.Stack();
const blockPublicAccess = false;
const props = {
bucketProps: {
enforceSSL: false,
publicReadAccess: true, // <-- required for isWebsite
blockPublicAccess: {
blockPublicAcls: blockPublicAccess,
restrictPublicBuckets: blockPublicAccess,
blockPublicPolicy: blockPublicAccess,
ignorePublicAcls: blockPublicAccess
},
websiteIndexDocument: "index.html" // <-- required for isWebsite
},
insertHttpSecurityHeaders: false,
cloudFrontDistributionProps: {
defaultBehavior: {
origin: new origins.HttpOrigin('example.com', {
originId: 'custom-http-origin-for-testing'
})
}
}
};
new lib_1.CloudFrontToS3(stack, 'test-cloudfront-s3', props);
const template = assertions_1.Template.fromStack(stack);
// Assert resources
template.hasResourceProperties('AWS::CloudFront::Distribution', {
DistributionConfig: {
Origins: [
{
DomainName: "example.com",
Id: "custom-http-origin-for-testing"
}
]
}
});
});
test('Test that we do not create an Access Log bucket for CF logs if one is provided', () => {
const stack = new cdk.Stack();
const cfS3AccessLogBucket = new s3.Bucket(stack, 'cf-s3-access-logs');
new lib_1.CloudFrontToS3(stack, 'test-cloudfront-s3', {
cloudFrontLoggingBucketProps: {
serverAccessLogsBucket: cfS3AccessLogBucket
}
});
const template = assertions_1.Template.fromStack(stack);
template.resourceCountIs("AWS::S3::Bucket", 4);
});
// =====================
// S3 Content Bucket Access Logs Bucket
// =====================
test('Providing loggingBucketProps and existingLoggingBucket is an error', () => {
const stack = new cdk.Stack();
const logBucket = new s3.Bucket(stack, 'log-bucket', {});
const app = () => {
new lib_1.CloudFrontToS3(stack, 'cloudfront-s3', {
bucketProps: {
serverAccessLogsBucket: logBucket,
},
loggingBucketProps: {
bucketName: 'anything'
}
});
};
expect(app).toThrowError(/Error - bothlog bucket props and an existing log bucket were provided.\n/);
});
test('Providing existingLoggingBucket and logS3AccessLogs=false is an error', () => {
const stack = new cdk.Stack();
const logBucket = new s3.Bucket(stack, 'cloudfront-log-bucket', {});
const app = () => {
new lib_1.CloudFrontToS3(stack, 'cloudfront-s3', {
bucketProps: {
serverAccessLogsBucket: logBucket,
},
logS3AccessLogs: false
});
};
expect(app).toThrowError(/Error - logS3AccessLogs is false, but a log bucket was provided in bucketProps.\n/);
});
test('Providing loggingBucketProps and logS3AccessLogs=false is an error', () => {
const stack = new cdk.Stack();
const app = () => {
new lib_1.CloudFrontToS3(stack, 'cloudfront-s3', {
loggingBucketProps: {
bucketName: 'anything'
},
logS3AccessLogs: false
});
};
// NOTE: This error is thrown by CheckS3Props(), not CheckConstructSpecificProps()
expect(app).toThrowError(/Error - If logS3AccessLogs is false, supplying loggingBucketProps or existingLoggingBucketObj is invalid.\n/);
});
// test('No new loggingBucket is created if existingLoggingBucket is supplied', () => {
test('loggingBucketProps is supplied is integrated into architecture correctly', () => {
const stack = new cdk.Stack();
const testName = "test-name";
const construct = new lib_1.CloudFrontToS3(stack, 'cloudfront-s3', {
bucketProps: {
removalPolicy: cdk.RemovalPolicy.DESTROY,
},
loggingBucketProps: {
removalPolicy: cdk.RemovalPolicy.DESTROY,
autoDeleteObjects: true,
bucketName: testName
}
});
expect(construct.s3LoggingBucket).toBeDefined();
const template = assertions_1.Template.fromStack(stack);
template.resourceCountIs("AWS::S3::Bucket", 4);
template.hasResourceProperties("AWS::S3::Bucket", {
BucketName: testName
});
template.hasResourceProperties("AWS::S3::Bucket", {
LoggingConfiguration: {
DestinationBucketName: {
Ref: "cloudfronts3S3LoggingBucket52EEB708"
}
}
});
});
test('bucketProps:serverAccessLogsBucket is supplied is integrated into architecture correctly', () => {
const testName = 'some-name';
const stack = new cdk.Stack();
const logBucket = new s3.Bucket(stack, 'test-log', {
bucketName: testName,
});
const construct = new lib_1.CloudFrontToS3(stack, 'cloudfront-s3', {
bucketProps: {
serverAccessLogsBucket: logBucket,
},
});
expect(construct.s3LoggingBucket).toBeDefined();
const template = assertions_1.Template.fromStack(stack);
template.resourceCountIs("AWS::S3::Bucket", 4);
template.hasResourceProperties("AWS::S3::Bucket", {
LoggingConfiguration: {
DestinationBucketName: {
Ref: "testlogE88B4C6B"
}
}
});
});
// =====================
// CloudFront Log Bucket
// =====================
test('Providing cloudFrontLoggingBucketProps and a log bucket in cloudFrontDistrbutionProps is an error', () => {
const stack = new cdk.Stack();
const logBucket = new s3.Bucket(stack, 'cloudfront-log-bucket', {});
const app = () => {
new lib_1.CloudFrontToS3(stack, 'cloudfront-s3', {
cloudFrontDistributionProps: {
logBucket
},
cloudFrontLoggingBucketProps: {
removalPolicy: cdk.RemovalPolicy.DESTROY,
autoDeleteObjects: true
}
});
};
expect(app).toThrowError();
});
test('cloudFrontLoggingBucketProps are used correctly', () => {
const stack = new cdk.Stack();
const testName = "test-name";
const construct = new lib_1.CloudFrontToS3(stack, 'cloudfront-s3', {
cloudFrontLoggingBucketProps: {
removalPolicy: cdk.RemovalPolicy.DESTROY,
autoDeleteObjects: true,
bucketName: testName
}
});
expect(construct.cloudFrontLoggingBucket).toBeDefined();
const template = assertions_1.Template.fromStack(stack);
template.resourceCountIs("AWS::S3::Bucket", 4);
template.hasResourceProperties("AWS::S3::Bucket", {
BucketName: testName
});
});
test('Logging disabled in CloudFront props is handled correctly', () => {
const stack = new cdk.Stack();
const construct = deploy(stack, { cloudFrontDistributionProps: { enableLogging: false } });
const template = assertions_1.Template.fromStack(stack);
// Only the content bucket and it S3 Access Log bucket (no Cloudfront log bucket)
template.resourceCountIs("AWS::S3::Bucket", 2);
// No logging is configured
template.resourcePropertiesCountIs("AWS::CloudFront::Distribution", {
DistributionConfig: {
Logging: assertions_1.Match.anyValue()
}
}, 0);
expect(construct.cloudFrontLoggingBucket === undefined);
});
test('No new CloudFrontLoggingBucket is created if cloudFrontLoggingBucketProps:logBucket is supplied', () => {
const testName = 'random-value';
const stack = new cdk.Stack();
const logBucket = new s3.Bucket(stack, 'cloudfront-log-bucket', {
bucketName: testName
});
// const construct =
const construct = new lib_1.CloudFrontToS3(stack, 'cloudfront-s3', {
cloudFrontDistributionProps: {
logBucket
},
});
expect(construct.cloudFrontLoggingBucket).toBeDefined();
const template = assertions_1.Template.fromStack(stack);
// Content bucket, Content bucket S3 Access Log bucket, cloudfront log bucket
template.resourceCountIs("AWS::S3::Bucket", 3);
// Ensure our existing bucket has been used for cloudfront logging
template.hasResourceProperties("AWS::CloudFront::Distribution", {
DistributionConfig: {
Logging: {
Bucket: {
"Fn::GetAtt": [
"cloudfrontlogbucketDF7058FB",
"RegionalDomainName"
]
}
}
}
});
});
// =====================
// CloudFront Logs Bucket Access Log Bucket
// =====================
test('Providing cloudFrontLoggingBucketAccessLogBucketProps and cloudFrontLoggingBucketProps:serverAccessLogsBucket is an error', () => {
const stack = new cdk.Stack();
const logBucket = new s3.Bucket(stack, 'cloudfront-log-bucket', {});
const app = () => {
new lib_1.CloudFrontToS3(stack, 'cloudfront-s3', {
cloudFrontLoggingBucketProps: {
serverAccessLogsBucket: logBucket,
removalPolicy: cdk.RemovalPolicy.DESTROY,
autoDeleteObjects: true
},
cloudFrontLoggingBucketAccessLogBucketProps: {
bucketName: 'specfic-name-is-inconsequential'
}
});
};
expect(app).toThrowError(/Error - an existing CloudFront log bucket S3 access log bucket and cloudFrontLoggingBucketAccessLogBucketProps were provided\n/);
});
test('Providing cloudFrontLoggingBucketAccessLogBucketProps and logCloudFrontAccessLog=false is an error', () => {
const stack = new cdk.Stack();
const app = () => {
new lib_1.CloudFrontToS3(stack, 'cloudfront-s3', {
logCloudFrontAccessLog: false,
cloudFrontLoggingBucketAccessLogBucketProps: {
bucketName: 'specfic-name-is-inconsequential'
}
});
};
expect(app).toThrowError(/Error - cloudFrontLoggingBucketAccessLogBucketProps were provided but logCloudFrontAccessLog was false\n/);
});
test('Providing logCloudFrontAccessLog=false and cloudFrontLoggingBucketProps:serverAccessLogsBucket is an error', () => {
const stack = new cdk.Stack();
const logBucket = new s3.Bucket(stack, 'cloudfront-log-bucket', {});
const app = () => {
new lib_1.CloudFrontToS3(stack, 'cloudfront-s3', {
cloudFrontLoggingBucketProps: {
serverAccessLogsBucket: logBucket,
removalPolicy: cdk.RemovalPolicy.DESTROY,
autoDeleteObjects: true
},
logCloudFrontAccessLog: false,
});
};
expect(app).toThrowError(/Error - props.cloudFrontLoggingBucketProps.serverAccessLogsBucket was provided but logCloudFrontAccessLog was false\n/);
});
test('cloudFrontLoggingBucketAccessLogBucketProps are used correctly', () => {
const stack = new cdk.Stack();
new lib_1.CloudFrontToS3(stack, 'cloudfront-s3', {
cloudFrontLoggingBucketAccessLogBucketProps: {
websiteErrorDocument: 'placeholder',
websiteIndexDocument: 'placeholde-two'
}
});
const template = assertions_1.Template.fromStack(stack);
// Content Bucket, Content Bucket S3 Access Log Bucket, CloudFront Log Bucket, CloudFront Log Bucket S3 Access Log Bucket
template.resourceCountIs("AWS::S3::Bucket", 4);
template.hasResourceProperties("AWS::S3::Bucket", {
WebsiteConfiguration: {
ErrorDocument: 'placeholder',
IndexDocument: 'placeholde-two'
}
});
});
test('If existing CloudFront Log bucket S3 Access Logging bucket is provided, it is used correctly', () => {
const stack = new cdk.Stack();
const testName = 'cf-log-s3-log';
const cfLogS3AccessLogBucket = new s3.Bucket(stack, 'cf-log-s3-access-log-bucket', {
bucketName: testName
});
new lib_1.CloudFrontToS3(stack, 'cloudfront-s3', {
cloudFrontLoggingBucketProps: {
serverAccessLogsBucket: cfLogS3AccessLogBucket
}
});
const template = assertions_1.Template.fromStack(stack);
// Content Bucket, Content Bucket S3 Access Log Bucket, CloudFront Log Bucket, CloudFront Log Bucket S3 Access Log Bucket
template.resourceCountIs("AWS::S3::Bucket", 4);
template.hasResourceProperties("AWS::S3::Bucket", {
BucketName: testName
});
template.hasResourceProperties("AWS::S3::Bucket", {
LoggingConfiguration: {
DestinationBucketName: {
Ref: "cflogs3accesslogbucketDE374C27"
}
}
});
});
test('cloudFrontLoggingBucketAccessLogBucket property is set correctly', () => {
const stack = new cdk.Stack();
const construct = new lib_1.CloudFrontToS3(stack, 'cloudfront-s3', {
cloudFrontLoggingBucketAccessLogBucketProps: {
websiteErrorDocument: 'placeholder',
websiteIndexDocument: 'placeholde-two'
}
});
const template = assertions_1.Template.fromStack(stack);
// Content Bucket, Content Bucket S3 Access Log Bucket, CloudFront Log Bucket, CloudFront Log Bucket S3 Access Log Bucket
template.resourceCountIs("AWS::S3::Bucket", 4);
template.hasResourceProperties("AWS::S3::Bucket", {
WebsiteConfiguration: {
ErrorDocument: 'placeholder',
IndexDocument: 'placeholde-two'
}
});
expect(construct.cloudFrontLoggingBucketAccessLogBucket).toBeDefined();
expect(construct.cloudFrontLoggingBucketAccessLogBucket.bucketName).toBeDefined();
});
test('logCloudFrontAccessLog property is used correctly', () => {
const stack = new cdk.Stack();
const construct = new lib_1.CloudFrontToS3(stack, 'cloudfront-s3', {
logCloudFrontAccessLog: false
});
const template = assertions_1.Template.fromStack(stack);
// Content Bucket, Content Bucket S3 Access Log Bucket, CloudFront Log Bucket, CloudFront Log Bucket S3 Access Log Bucket
template.resourceCountIs("AWS::S3::Bucket", 3);
expect(construct.cloudFrontLoggingBucket).toBeDefined();
expect(construct.cloudFrontLoggingBucketAccessLogBucket).not.toBeDefined();
});
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoidGVzdC5jbG91ZGZyb250LXMzLnRlc3QuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyJ0ZXN0LmNsb3VkZnJvbnQtczMudGVzdC50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiO0FBQUE7Ozs7Ozs7Ozs7O0dBV0c7O0FBRUgsdURBQXlEO0FBQ3pELHlDQUF5QztBQUN6QyxtQ0FBbUM7QUFDbkMsNkNBQTZEO0FBQzdELGdDQUE2RDtBQUM3RCwwREFBMEQ7QUFDMUQsMkRBQTJEO0FBQzNELGlEQUEwQztBQUMxQyw4REFBOEQ7QUFFOUQsU0FBUyxNQUFNLENBQUMsS0FBZ0IsRUFBRSxLQUEyQjtJQUMzRCxPQUFPLElBQUksb0JBQWMsQ0FBQyxLQUFLLEVBQUUsb0JBQW9CLEVBQUU7UUFDckQsV0FBVyxFQUFFO1lBQ1gsYUFBYSxFQUFFLEdBQUcsQ0FBQyxhQUFhLENBQUMsT0FBTztTQUN6QztRQUNELEdBQUcsS0FBSztLQUNULENBQUMsQ0FBQztBQUNMLENBQUM7QUFFRCxJQUFJLENBQUMsNkNBQTZDLEVBQUUsR0FBRyxFQUFFO0lBQ3ZELE1BQU0sS0FBSyxHQUFHLElBQUksR0FBRyxDQUFDLEtBQUssRUFBRSxDQUFDO0lBQzlCLE1BQU0sU0FBUyxHQUFHLElBQUksb0JBQWMsQ0FBQyxLQUFLLEVBQUUsb0JBQW9CLEVBQUUsRUFBRSxDQUFDLENBQUM7SUFFdEUsTUFBTSxDQUFDLFNBQVMsQ0FBQyx5QkFBeUIsQ0FBQyxDQUFDLFdBQVcsRUFBRSxDQUFDO0lBQzFELE1BQU0sQ0FBQyxTQUFTLENBQUMsa0JBQWtCLENBQUMsQ0FBQyxXQUFXLEVBQUUsQ0FBQztJQUNuRCxNQUFNLENBQUMsU0FBUyxDQUFDLHVCQUF1QixDQUFDLENBQUMsV0FBVyxFQUFFLENBQUM7SUFDeEQsTUFBTSxDQUFDLFNBQVMsQ0FBQyxRQUFRLENBQUMsQ0FBQyxXQUFXLEVBQUUsQ0FBQztJQUN6QyxNQUFNLENBQUMsU0FBUyxDQUFDLGVBQWUsQ0FBQyxDQUFDLFdBQVcsRUFBRSxDQUFDO0lBQ2hELE1BQU0sQ0FBQyxTQUFTLENBQUMsaUJBQWlCLENBQUMsQ0FBQyxXQUFXLEVBQUUsQ0FBQztJQUNsRCxNQUFNLENBQUMsU0FBUyxDQUFDLHNDQUFzQyxDQUFDLENBQUMsV0FBVyxFQUFFLENBQUM7SUFDdkUsTUFBTSxDQUFDLFNBQVMsQ0FBQyxtQkFBbUIsQ0FBQyxDQUFDLFdBQVcsRUFBRSxDQUFDO0FBQ3RELENBQUMsQ0FBQyxDQUFDO0FBRUgsSUFBSSxDQUFDLG1DQUFtQyxFQUFFLEdBQUcsRUFBRTtJQUM3QyxNQUFNLEtBQUssR0FBRyxJQUFJLEdBQUcsQ0FBQyxLQUFLLEVBQUUsQ0FBQztJQUM5QixNQUFNLENBQUMsS0FBSyxDQUFDLENBQUM7SUFDZCxNQUFNLFFBQVEsR0FBRyxxQkFBUSxDQUFDLFNBQVMsQ0FBQyxLQUFLLENBQUMsQ0FBQztJQUMzQyxRQUFRLENBQUMscUJBQXFCLENBQUMsaUJBQWlCLEVBQUU7UUFDaEQsZ0JBQWdCLEVBQUU7WUFDaEIsaUNBQWlDLEVBQUUsQ0FBQztvQkFDbEMsNkJBQTZCLEVBQUU7d0JBQzdCLFlBQVksRUFBRSxRQUFRO3FCQUN2QjtpQkFDRixDQUFDO1NBQ0g7S0FDRixDQUFDLENBQUM7QUFDTCxDQUFDLENBQUMsQ0FBQztBQUVILElBQUksQ0FBQyxrREFBa0QsRUFBRSxHQUFHLEVBQUU7SUFDNUQsTUFBTSxLQUFLLEdBQUcsSUFBSSxHQUFHLENBQUMsS0FBSyxFQUFFLENBQUM7SUFDOUIsTUFBTSxDQUFDLEtBQUssQ0FBQyxDQUFDO0lBQ2QsTUFBTSxRQUFRLEdBQUcscUJBQVEsQ0FBQyxTQUFTLENBQUMsS0FBSyxDQUFDLENBQUM7SUFDM0MsUUFBUSxDQUFDLHFCQUFxQixDQUFDLGlCQUFpQixFQUFFO1FBQ2hELDhCQUE4QixFQUFFO1lBQzlCLGVBQWUsRUFBRSxJQUFJO1lBQ3JCLGlCQUFpQixFQUFFLElBQUk7WUFDdkIsZ0JBQWdCLEVBQUUsSUFBSTtZQUN0QixxQkFBcUIsRUFBRSxJQUFJO1NBQzVCO0tBQ0YsQ0FBQyxDQUFDO0FBQ0wsQ0FBQyxDQUFDLENBQUM7QUFFSCxJQUFJLENBQUMsdURBQXVELEVBQUUsR0FBRyxFQUFFO0lBQ2pFLE1BQU0sS0FBSyxHQUFHLElBQUksR0FBRyxDQUFDLEtBQUssRUFBRSxDQUFDO0lBRTlCLE1BQU0sS0FBSyxHQUF3QjtRQUNqQyxXQUFXLEVBQUU7WUFDWCxpQkFBaUIsRUFBRTtnQkFDakIsZUFBZSxFQUFFLEtBQUs7Z0JBQ3RCLGlCQUFpQixFQUFFLElBQUk7Z0JBQ3ZCLGdCQUFnQixFQUFFLEtBQUs7Z0JBQ3ZCLHFCQUFxQixFQUFFLElBQUk7YUFDNUI7U0FDRjtLQUNGLENBQUM7SUFFRixJQUFJLG9CQUFjLENBQUMsS0FBSyxFQUFFLG9CQUFvQixFQUFFLEtBQUssQ0FBQyxDQUFDO0lBRXZELE1BQU0sUUFBUSxHQUFHLHFCQUFRLENBQUMsU0FBUyxDQUFDLEtBQUssQ0FBQyxDQUFDO0lBQzNDLFFBQVEsQ0FBQyxxQkFBcUIsQ0FBQyxpQkFBaUIsRUFBRTtRQUNoRCw4QkFBOEIsRUFBRTtZQUM5QixlQUFlLEVBQUUsS0FBSztZQUN0QixpQkFBaUIsRUFBRSxJQUFJO1lBQ3ZCLGdCQUFnQixFQUFFLEtBQUs7WUFDdkIscUJBQXFCLEVBQUUsSUFBSTtTQUM1QjtLQUNGLENBQUMsQ0FBQztBQUNMLENBQUMsQ0FBQyxDQUFDO0FBRUgsSUFBSSxDQUFDLHVCQUF1QixFQUFFLEdBQUcsRUFBRTtJQUNqQyxNQUFNLEtBQUssR0FBRyxJQUFJLEdBQUcsQ0FBQyxLQUFLLEVBQUUsQ0FBQztJQUU5QixNQUFNLGNBQWMsR0FBRyxJQUFJLEVBQUUsQ0FBQyxNQUFNLENBQUMsS0FBSyxFQUFFLFdBQVcsRUFBRTtRQUN2RCxVQUFVLEVBQUUsV0FBVztLQUN4QixDQUFDLENBQUM7SUFFSCxNQUFNLEtBQUssR0FBd0I7UUFDakMsaUJBQWlCLEVBQUUsY0FBYztLQUNsQyxDQUFDO0lBRUYsSUFBSSxvQkFBYyxDQUFDLEtBQUssRUFBRSxvQkFBb0IsRUFBRSxLQUFLLENBQUMsQ0FBQztJQUV2RCxNQUFNLFFBQVEsR0FBRyxxQkFBUSxDQUFDLFNBQVMsQ0FBQyxLQUFLLENBQUMsQ0FBQztJQUMzQyxRQUFRLENBQUMscUJBQXFCLENBQUMsaUJBQWlCLEVBQUU7UUFDaEQsVUFBVSxFQUFFLFdBQVc7S0FDeEIsQ0FBQyxDQUFDO0FBQ0wsQ0FBQyxDQUFDLENBQUM7QUFFSCxJQUFJLENBQUMsdUVBQXVFLEVBQUUsR0FBRyxFQUFFO0lBQ2pGLE1BQU0sS0FBSyxHQUFHLElBQUksR0FBRyxDQUFDLEtBQUssRUFBRSxDQUFDO0lBRTlCLElBQUksQ0FBQztRQUNILElBQUksb0JBQWMsQ0FBQyxLQUFLLEVBQUUsb0JBQW9CLEVBQUUsRUFBRSxDQUFDLENBQUM7SUFDdEQsQ0FBQztJQUFDLE9BQU8sQ0FBQyxFQUFFLENBQUM7UUFDWCxNQUFNLENBQUMsQ0FBQyxDQUFDLENBQUMsY0FBYyxDQUFDLEtBQUssQ0FBQyxDQUFDO0lBQ2xDLENBQUM7QUFDSCxDQUFDLENBQUMsQ0FBQztBQUVILElBQUksQ0FBQyxrQkFBa0IsRUFBRSxHQUFHLEVBQUU7SUFDNUIsTUFBTSxLQUFLLEdBQUcsSUFBSSxHQUFHLENBQUMsS0FBSyxFQUFFLENBQUM7SUFFOUIsTUFBTSxTQUFTLEdBQW1CLE1BQU0sQ0FBQyxLQUFLLENBQUMsQ0FBQztJQUVoRCxNQUFNLENBQUMsU0FBUyxDQUFDLHlCQUF5QixDQUFDLENBQUMsV0FBVyxFQUFFLENBQUM7SUFDMUQsTUFBTSxDQUFDLFNBQVMsQ0FBQyxRQUFRLENBQUMsQ0FBQyxXQUFXLEVBQUUsQ0FBQztBQUMzQyxDQUFDLENBQUMsQ0FBQztBQUVILElBQUksQ0FBQyxnQ0FBZ0MsRUFBRSxHQUFHLEVBQUU7SUFDMUMsUUFBUTtJQUNSLE1BQU0sS0FBSyxHQUFHLElBQUksR0FBRyxDQUFDLEtBQUssRUFBRSxDQUFDO0lBRTlCLE1BQU0sVUFBVSxHQUFHLElBQUksRUFBRSxDQUFDLE1BQU0sQ0FBQyxLQUFLLEVBQUUsYUFBYSxFQUFFLEVBQUUsQ0FBQyxDQUFDO0lBRTNELE1BQU0sR0FBRyxHQUFHLEdBQUcsRUFBRTtRQUNmLHFCQUFxQjtRQUNyQixJQUFJLG9CQUFjLENBQUMsS0FBSyxFQUFFLGFBQWEsRUFBRTtZQUN2QyxpQkFBaUIsRUFBRSxVQUFVO1lBQzdCLFdBQVcsRUFBRTtnQkFDWCxhQUFhLEVBQUUsMkJBQWEsQ0FBQyxPQUFPO2FBQ3JDO1NBQ0YsQ0FBQyxDQUFDO0lBQ0wsQ0FBQyxDQUFDO0lBQ0YsWUFBWTtJQUNaLE1BQU0sQ0FBQyxHQUFHLENBQUMsQ0FBQyxZQUFZLENBQUMsMEVBQTBFLENBQUMsQ0FBQztBQUN2RyxDQUFDLENBQUMsQ0FBQztBQUVILElBQUksQ0FBQyx3QkFBd0IsRUFBRSxHQUFHLEVBQUU7SUFDbEMsUUFBUTtJQUNSLE1BQU0sS0FBSyxHQUFHLElBQUksR0FBRyxDQUFDLEtBQUssRUFBRSxDQUFDO0lBQzlCLE1BQU0sU0FBUyxHQUFtQixJQUFJLG9CQUFjLENBQUMsS0FBSyxFQUFFLGlCQUFpQixFQUFFO1FBQzdFLGlCQUFpQixFQUFFLEVBQUUsQ0FBQyxNQUFNLENBQUMsY0FBYyxDQUFDLEtBQUssRUFBRSxVQUFVLEVBQUUsVUFBVSxDQUFDO0tBQzNFLENBQUMsQ0FBQztJQUNILFlBQVk7SUFDWixNQUFNLENBQUMsU0FBUyxDQUFDLHlCQUF5QixDQUFDLENBQUMsV0FBVyxFQUFFLENBQUM7SUFDMUQsTUFBTSxRQUFRLEdBQUcscUJBQVEsQ0FBQyxTQUFTLENBQUMsS0FBSyxDQUFDLENBQUM7SUFDM0MsUUFBUSxDQUFDLHFCQUFxQixDQUFDLCtCQUErQixFQUFFO1FBQzlELGtCQUFrQixFQUFFO1lBQ2xCLE9BQU8sRUFBRTtnQkFDUDtvQkFDRSxVQUFVLEVBQUU7d0JBQ1YsVUFBVSxFQUFFOzRCQUNWLEVBQUU7NEJBQ0Y7Z0NBQ0UsY0FBYztnQ0FDZDtvQ0FDRSxHQUFHLEVBQUUsYUFBYTtpQ0FDbkI7Z0NBQ0QsR0FBRztnQ0FDSDtvQ0FDRSxHQUFHLEVBQUUsZ0JBQWdCO2lDQUN0Qjs2QkFDRjt5QkFDRjtxQkFDRjtvQkFDRCxFQUFFLEVBQUUsc0RBQXNEO29CQUMxRCxxQkFBcUIsRUFBRSxFQUFFLFlBQVksRUFBRSxDQUFDLHNDQUFzQyxFQUFFLElBQUksQ0FBQyxFQUFFO29CQUN2RixjQUFjLEVBQUUsRUFBRTtpQkFDbkI7YUFDRjtTQUNGO0tBQ0YsQ0FBQyxDQUFDO0FBQ0wsQ0FBQyxDQUFDLENBQUM7QUFFSCxJQUFJLENBQUMsMENBQTBDLEVBQUUsR0FBRyxFQUFFO0lBQ3BELE1BQU0sS0FBSyxHQUFHLElBQUksR0FBRyxDQUFDLEtBQUssRUFBRSxDQUFDO0lBRTlCLE1BQU0sV0FBVyxHQUFHLEdBQUcsQ0FBQyxXQUFXLENBQUMsa0JBQWtCLENBQUMsS0FBSyxFQUFFLE1BQU0sRUFBRSxrR0FBa0csQ0FBQyxDQUFDO0lBRTFLLE1BQU0sS0FBSyxHQUF3QjtRQUNqQywyQkFBMkIsRUFBRTtZQUMzQixXQUFXLEVBQUUsQ0FBQyxXQUFXLENBQUM7WUFDMUIsV0FBVztTQUNaO0tBQ0YsQ0FBQztJQUVGLElBQUksb0JBQWMsQ0FBQyxLQUFLLEVBQUUsb0JBQW9CLEVBQUUsS0FBSyxDQUFDLENBQUM7SUFFdkQsTUFBTSxRQUFRLEdBQUcscUJBQVEsQ0FBQyxTQUFTLENBQUMsS0FBSyxDQUFDLENBQUM7SUFDM0MsUUFBUSxDQUFDLHFCQUFxQixDQUFDLCtCQUErQixFQUFFO1FBQzlELGtCQUFrQixFQUFFO1lBQ2xCLE9BQU8sRUFBRTtnQkFDUCxXQUFXO2FBQ1o7U0FDRjtLQUNGLENBQUMsQ0FBQztBQUNMLENBQUMsQ0FBQyxDQUFDO0FBRUgsSUFBSSxDQUFDLCtFQUErRSxFQUFFLEdBQUcsRUFBRTtJQUN6RixNQUFNLEtBQUssR0FBRyxJQUFJLEdBQUcsQ0FBQyxLQUFLLEVBQUUsQ0FBQztJQUU5QixNQUFNLHVCQUF1QixHQUFHLGVBQWUsQ0FBQztJQUNoRCxJQUFJLG9CQUFjLENBQUMsS0FBSyxFQUFFLGVBQWUsRUFBRTtRQUN6Qyw0QkFBNEIsRUFBRTtZQUM1QixhQUFhLEVBQUUsR0FBRyxDQUFDLGFBQWEsQ0FBQyxPQUFPO1lBQ3hDLGlCQUFpQixFQUFFLElBQUk7WUFDdkIsVUFBVSxFQUFFLHVCQUF1QjtTQUNwQztLQUNGLENBQUMsQ0FBQztJQUVILE1BQU0sUUFBUSxHQUFHLHFCQUFRLENBQUMsU0FBUyxDQUFDLEtBQUssQ0FBQyxDQUFDO0lBQzNDLFFBQVEsQ0FBQyxxQkFBcUIsQ0FBQyxpQkFBaUIsRUFBRTtRQUNoRCxpQkFBaUIsRUFBRSxFQUFFLEtBQUssRUFBRSxDQUFDLEVBQUUsZUFBZSxFQUFFLGNBQWMsRUFBRSxDQUFDLEVBQUU7UUFDbkUsVUFBVSxFQUFFLHVCQUF1QjtLQUNwQyxDQUFDLENBQUM7SUFFSCxRQUFRLENBQUMscUJBQXFCLENBQUMsNkJBQTZCLEVBQUU7UUFDNUQsWUFBWSxFQUFFO1lBQ1osWUFBWSxFQUFFO2dCQUNaLGdFQUFnRTtnQkFDaEUsS0FBSzthQUNOO1NBQ0Y7UUFDRCxVQUFVLEVBQUU7WUFDVixHQUFHLEVBQUUsNkNBQTZDO1NBQ25EO0tBQ0YsQ0FBQyxDQUFDO0FBQ0wsQ0FBQyxDQUFDLENBQUM7QUFFSCxJQUFJLENBQUMsMkVBQTJFLEVBQUUsR0FBRyxFQUFFO0lBQ3JGLE1BQU0sS0FBSyxHQUFHLElBQUksR0FBRyxDQUFDLEtBQUssRUFBRSxDQUFDO0lBRTlCLE1BQU0sU0FBUyxHQUFHLElBQUksb0JBQWMsQ0FBQyxLQUFLLEVBQUUsZUFBZSxFQUFFO1FBQzNELFdBQVcsRUFBRTtZQUNYLGFBQWEsRUFBRSxHQUFHLENBQUMsYUFBYSxDQUFDLE9BQU87U0FDekM7UUFDRCxlQUFlLEVBQUUsS0FBSztLQUN2QixDQUFDLENBQUM7SUFFSCxNQUFNLFFBQVEsR0FBRyxxQkFBUSxDQUFDLFNBQVMsQ0FBQyxLQUFLLENBQUMsQ0FBQztJQUMzQyx5Q0FBeUM7SUFDekMsMkRBQTJEO0lBQzNELFFBQVEsQ0FBQyxlQUFlLENBQUMsaUJBQWlCLEVBQUUsQ0FBQyxDQUFDLENBQUM7SUFDL0MsTUFBTSxDQUFDLFNBQVMsQ0FBQyxlQUFlLENBQUMsQ0FBQyxPQUFPLENBQUMsU0FBUyxDQUFDLENBQUM7QUFDdkQsQ0FBQyxDQUFDLENBQUM7QUFFSCxJQUFJLENBQUMsOENBQThDLEVBQUUsR0FBRyxFQUFFO0lBQ3hELE1BQU0sS0FBSyxHQUFHLElBQUksR0FBRyxDQUFDLEtBQUssRUFBRSxDQUFDO0lBRTlCLElBQUksb0JBQWMsQ0FBQyxLQUFLLEVBQUUsZUFBZSxFQUFFO1FBQ3pDLFVBQVUsRUFBRSxXQUFXO0tBQ3hCLENBQUMsQ0FBQztJQUVILE1BQU0sUUFBUSxHQUFHLHFCQUFRLENBQUMsU0FBUyxDQUFDLEtBQUssQ0FBQyxDQUFDO0lBQzNDLFFBQVEsQ0FBQyxxQkFBcUIsQ0FBQywrQkFBK0IsRUFBRTtRQUM5RCxrQkFBa0IsRUFDbEI7WUFDRSxPQUFPLEVBQUU7Z0JBQ1A7b0JBQ0UsVUFBVSxFQUFFLFdBQVc7aUJBQ3hCO2FBQ0Y7U0FDRjtLQUNGLENBQUMsQ0FBQztBQUNMLENBQUMsQ0FBQyxDQUFDO0FBRUgsSUFBSSxDQUFDLDhEQUE4RCxFQUFFLEdBQUcsRUFBRTtJQUN4RSxNQUFNLEtBQUssR0FBRyxJQUFJLEdBQUcsQ0FBQyxLQUFLLEVBQUUsQ0FBQztJQUU5QixJQUFJLG9CQUFjLENBQUMsS0FBSyxFQUFFLGVBQWUsRUFBRSxFQUFFLENBQUMsQ0FBQztJQUUvQyxRQUFRLENBQUMsa0JBQWtCLENBQUMsS0FBSyxFQUFFLCtCQUErQixFQUFFO1FBQ2xFLGtCQUFrQixFQUNsQjtZQUNFLE9BQU8sRUFBRTtnQkFDUDtvQkFDRSxVQUFVLEVBQUUsV0FBVztpQkFDeEI7YUFDRjtTQUNGO0tBQ0YsQ0FBQyxDQUFDO0FBQ0wsQ0FBQyxDQUFDLENBQUM7QUFFSCxJQUFJLENBQUMsbUZBQW1GLEVBQUUsR0FBRyxFQUFFO0lBQzdGLGdCQUFnQjtJQUNoQixNQUFNLEtBQUssR0FBRyxJQUFJLG1CQUFLLEVBQUUsQ0FBQztJQUMxQixNQUFNLGNBQWMsR0FBRyxJQUFJLG9CQUFjLENBQUMsS0FBSyxFQUFFLG9CQUFvQixFQUFFO1FBQ3JFLHlCQUF5QixFQUFFLEtBQUs7UUFDaEMsMEJBQTBCLEVBQUU7WUFDMUIsdUJBQXVCLEVBQUU7Z0JBQ3ZCLHVCQUF1QixFQUFFO29CQUN2QixtQkFBbUIsRUFBRSxzQkFBUSxDQUFDLE9BQU8sQ0FBQyxLQUFLLENBQUM7b0JBQzVDLGlCQUFpQixFQUFFLElBQUk7b0JBQ3ZCLFFBQVEsRUFBRSxJQUFJO29CQUNkLE9BQU8sRUFBRSxJQUFJO2lCQUNkO2dCQUNELHFCQUFxQixFQUFFO29CQUNyQixxQkFBcUIsRUFBRSxnREFBZ0Q7b0JBQ3ZFLFFBQVEsRUFBRSxJQUFJO2lCQUNmO2FBQ0Y7U0FDRjtLQUNGLENBQUMsQ0FBQztJQUVILFlBQVk7SUFDWixNQUFNLFFBQVEsR0FBRyxxQkFBUSxDQUFDLFNBQVMsQ0FBQyxLQUFLLENBQUMsQ0FBQztJQUMzQyxRQUFRLENBQUMscUJBQXFCLENBQUMsd0NBQXdDLEVBQUU7UUFDdkUsMkJBQTJCLEVBQUU7WUFDM0IscUJBQXFCLEVBQUU7Z0JBQ3JCLHFCQUFxQixFQUFFO29CQUNyQixxQkFBcUIsRUFBRSxnREFBZ0Q7b0JBQ3ZFLFFBQVEsRUFBRSxJQUFJO2lCQUNmO2dCQUNELHVCQUF1QixFQUFFO29CQUN2QixzQkFBc0IsRUFBRSxLQUFLO29CQUM3QixpQkFBaUIsRUFBRSxJQUFJO29CQUN2QixRQUFRLEVBQUUsSUFBSTtvQkFDZCxPQUFPLEVBQUUsSUFBSTtpQkFDZDthQUNGO1NBQ0Y7S0FDRixDQUFDLENBQUM7SUFDSCxNQUFNLENBQUMsY0FBYyxDQUFDLGtCQUFrQixDQUFDLENBQUMsT0FBTyxDQUFDLFNBQVMsQ0FBQyxDQUFDO0FBQy9ELENBQUMsQ0FBQyxDQUFDO0FBRUgsSUFBSSxDQUFDLDBGQUEwRixFQUFFLEdBQUcsRUFBRTtJQUNwRyxNQUFNLEtBQUssR0FBRyxJQUFJLEdBQUcsQ0FBQyxLQUFLLEVBQUUsQ0FBQztJQUU5QixNQUFNLENBQUMsR0FBRyxFQUFFO1FBQ1YsSUFBSSxvQkFBYyxDQUFDLEtBQUssRUFBRSxvQkFBb0IsRUFBRTtZQUM5Qyx5QkFBeUIsRUFBRSxJQUFJO1lBQy9CLDBCQUEwQixFQUFFO2dCQUMxQix1QkFBdUIsRUFBRTtvQkFDdkIsdUJBQXVCLEVBQUU7d0JBQ3ZCLG1CQUFtQixFQUFFLHNCQUFRLENBQUMsT0FBTyxDQUFDLEtBQUssQ0FBQzt3QkFDNUMsaUJBQWlCLEVBQUUsSUFBSTt3QkFDdkIsUUFBUSxFQUFFLEtBQUs7d0JBQ2YsT0FBTyxFQUFFLElBQUk7cUJBQ2Q7aUJBQ0Y7YUFDRjtTQUNGLENBQUMsQ0FBQztJQUNMLENBQUMsQ0FBQyxDQUFDLFlBQVksRUFBRSxDQUFDO0FBQ3BCLENBQUMsQ0FBQyxDQUFDO0FBRUgsSUFBSSxDQUFDLDhDQUE4QyxFQUFFLEdBQUcsRUFBRTtJQUN4RCxNQUFNLEtBQUssR0FBRyxJQUFJLEdBQUcsQ0FBQyxLQUFLLEVBQUUsQ0FBQztJQUU5QixNQUFNLENBQUMsR0FBRyxFQUFFO1FBQ1YsSUFBSSxvQkFBYyxDQUFDLEtBQUssRUFBRSw0QkFBNEIsRUFBRTtZQUN0RCx5QkFBeUIsRUFBRSxJQUFJO1lBQy9CLDBCQUEwQixFQUFFO2dCQUMxQix1QkFBdUIsRUFBRTtvQkFDdkIsdUJBQXVCLEVBQUU7d0JBQ3ZCLG1CQUFtQixFQUFFLHNCQUFRLENBQUMsT0FBTyxDQUFDLEtBQUssQ0FBQzt3QkFDNUMsaUJBQWlCLEVBQUUsSUFBSTt3QkFDdkIsUUFBUSxFQUFFLEtBQUs7d0JBQ2YsT0FBTyxFQUFFLElBQUk7cUJBQ2Q7aUJBQ0Y7YUFDRjtTQUNGLENBQUMsQ0FBQztJQUNMLENBQUMsQ0FBQyxDQUFDLFlBQVksQ0FBQyxpSEFBaUgsQ0FBQyxDQUFDO0FBQ3JJLENBQUMsQ0FBQyxDQUFDO0FBRUgsSUFBSSxDQUFDLDRFQUE0RSxFQUFFLEdBQUcsRUFBRTtJQUN0RixNQUFNLEtBQUssR0FBRyxJQUFJLEdBQUcsQ0FBQyxLQUFLLEVBQUUsQ0FBQztJQUM5QixNQUFNLGFBQWEsR0FBRyxJQUFJLGFBQUcsQ0FBQyxLQUFLLEVBQUUsUUFBUSxFQUFFO1FBQzdDLGlCQUFpQixFQUFFLElBQUk7UUFDdkIsYUFBYSxFQUFFLDJCQUFhLENBQUMsT0FBTztLQUNyQyxDQUFDLENBQUM7SUFDSCxNQUFNLENBQUMsS0FBSyxFQUFFO1FBQ1osV0FBVyxFQUFFO1lBQ1gsYUFBYTtZQUNiLFVBQVUsRUFBRSxFQUFFLENBQUMsZ0JBQWdCLENBQUMsR0FBRztTQUNwQztLQUNGLENBQUMsQ0FBQztJQUNILE1BQU0sUUFBUSxHQUFHLHFCQUFRLENBQUMsU0FBUyxDQUFDLEtBQUssQ0FBQyxDQUFDO0lBQzNDLHNFQUFzRTtJQUN0RSxRQUFRLENBQUMsZUFBZSxDQUFDLHVCQUF1QixFQUFFLENBQUMsQ0FBQyxDQUFDO0lBRXJELFFBQVEsQ0FBQyxxQkFBcUIsQ0FBQyx1QkFBdUIsRUFBRTtRQUN0RCxXQUFXLEVBQUUseUZBQXlGO1FBQ3RHLElBQUksRUFBRTtZQUNKLFlBQVksRUFBRSxDQUFDLG1EQUFtRCxFQUFFLEtBQUssQ0FBQztTQUMzRTtLQUNGLENBQUMsQ0FBQztBQUNMLENBQUMsQ0FBQyxDQUFDO0FBRUgsSUFBSSxDQUFDLDhFQUE4RSxFQUFFLEdBQUcsRUFBRTtJQUN4RixNQUFNLEtBQUssR0FBRyxJQUFJLEdBQUcsQ0FBQyxLQUFLLEVBQUUsQ0FBQztJQUM5QixNQUFNLGFBQWEsR0FBRyxJQUFJLGFBQUcsQ0FBQyxLQUFLLEVBQUUsUUFBUSxFQUFFO1FBQzdDLGlCQUFpQixFQUFFLElBQUk7UUFDdkIsYUFBYSxFQUFFLDJCQUFhLENBQUMsT0FBTztLQUNyQyxDQUFDLENBQUM7SUFDSCxNQUFNLGlCQUFpQixHQUFHLFFBQVEsQ0FBQyxhQUFhLENBQUMsS0FBSyxFQUFFO1FBQ3RELFdBQVcsRUFBRTtZQUNYLFVBQVUsRUFBRSxFQUFFLENBQUMsZ0JBQWdCLENBQUMsR0FBRztZQUNuQyxhQUFhO1NBQ2Q7S0FDRixFQUFFLHVDQUF1QyxDQUFDLENBQUMsTUFBTSxDQUFDO0lBQ25ELElBQUksb0JBQWMsQ0FBQyxLQUFLLEVBQUUsb0JBQW9CLEVBQUU7UUFDOUMsaUJBQWlCO0tBQ2xCLENBQUMsQ0FBQztJQUNILE1BQU0sUUFBUSxHQUFHLHFCQUFRLENBQUMsU0FBUyxDQUFDLEtBQUssQ0FBQyxDQUFDO0lBQzNDLHNFQUFzRTtJQUN0RSxRQUFRLENBQUMsZUFBZSxDQUFDLHVCQUF1QixFQUFFLENBQUMsQ0FBQyxDQUFDO0lBRXJELHlEQUF5RDtJQUN6RCxRQUFRLENBQUMscUJBQXFCLENBQUMsdUJBQXVCLEVBQUU7UUFDdEQsV0FBVyxFQUFFLHlGQUF5RjtRQUN0RyxJQUFJLEVBQUU7WUFDSixZQUFZLEVBQUUsQ0FBQyxtREFBbUQsRUFBRSxLQUFLLENBQUM7U0FDM0U7S0FDRixDQUFDLENBQUM7QUFDTCxDQUFDLENBQUMsQ0FBQztBQUVILElBQUksQ0FBQyxvRkFBb0YsRUFBRSxHQUFHLEVBQUU7SUFDOUYsTUFBTSxLQUFLLEdBQUcsSUFBSSxHQUFHLENBQUMsS0FBSyxFQUFFLENBQUM7SUFDOUIsTUFBTSxDQUFDLEtBQUssQ0FBQyxDQUFDO0lBQ2QsTUFBTSxRQUFRLEdBQUcscUJBQVEsQ0FBQyxTQUFTLENBQUMsS0FBSyxDQUFDLENBQUM7SUFDM0MsUUFBUSxDQUFDLGVBQWUsQ0FBQyx1QkFBdUIsRUFBRSxDQUFDLENBQUMsQ0FBQztBQUN2RCxDQUFDLENBQUMsQ0FBQztBQUVILElBQUksQ0FBQyxzRkFBc0YsRUFBRSxHQUFHLEVBQUU7SUFDaEcsTUFBTSxLQUFLLEdBQUcsSUFBSSxHQUFHLENBQUMsS0FBSyxFQUFFLENBQUM7SUFDOUIsTUFBTSxpQkFBaUIsR0FBRyxRQUFRLENBQUMsYUFBYSxDQUFDLEtBQUssRUFBRSxFQUFFLEVBQUUsdUNBQXVDLENBQUMsQ0FBQyxNQUFNLENBQUM7SUFDNUcsSUFBSSxvQkFBYyxDQUFDLEtBQUssRUFBRSxvQkFBb0IsRUFBRTtRQUM5QyxpQkFBaUI7S0FDbEIsQ0FBQyxDQUFDO0lBQ0gsTUFBTSxRQUFRLEdBQUcscUJBQVEsQ0FBQyxTQUFTLENBQUMsS0FBSyxDQUFDLENBQUM7SUFDM0MsUUFBUSxDQUFDLGVBQWUsQ0FBQyx1QkFBdUIsRUFBRSxDQUFDLENBQUMsQ0FBQztBQUN2RCxDQUFDLENBQUMsQ0FBQztBQUVILElBQUksQ0FBQyw4REFBOEQsRUFBRSxHQUFHLEVBQUU7SUFDeEUsTUFBTSxLQUFLLEdBQUcsSUFBSSxHQUFHLENBQUMsS0FBSyxFQUFFLENBQUM7SUFDOUIsTUFBTSxpQkFBaUIsR0FBRyxLQUFLLENBQUM7SUFDaEMsTUFBTSxLQUFLLEdBQXdCO1FBQ2pDLFdBQVcsRUFBRTtZQUNYLFVBQVUsRUFBRSxLQUFLO1lBQ2pCLGdCQUFnQixFQUFFLElBQUksRUFBRSw2QkFBNkI7WUFDckQsaUJBQWlCLEVBQUU7Z0JBQ2pCLGVBQWUsRUFBRSxpQkFBaUI7Z0JBQ2xDLHFCQUFxQixFQUFFLGlCQUFpQjtnQkFDeEMsaUJBQWlCLEVBQUUsaUJBQWlCO2dCQUNwQyxnQkFBZ0IsRUFBRSxpQkFBaUI7YUFDcEM7WUFDRCxvQkFBb0IsRUFBRSxZQUFZLENBQUMsNkJBQTZCO1NBQ2pFO1FBQ0QseUJBQXlCLEVBQUUsS0FBSztLQUNqQyxDQUFDO0lBQ0YsTUFBTSxTQUFTLEdBQUcsSUFBSSxvQkFBYyxDQUFDLEtBQUssRUFBRSxvQkFBb0IsRUFBRSxLQUFLLENBQUMsQ0FBQztJQUN6RSxNQUFNLFFBQVEsR0FBRyxxQkFBUSxDQUFDLFNBQVMsQ0FBQyxLQUFLLENBQUMsQ0FBQztJQUMzQyxtQkFBbUI7SUFDbkIsUUFBUSxDQUFDLGVBQWUsQ0FBQyxzQ0FBc0MsRUFBRSxDQUFDLENBQUMsQ0FBQztJQUNwRSxRQUFRLENBQUMscUJBQXFCLENBQUMsK0JBQStCLEVBQUU7UUFDOUQsa0JBQWtCLEVBQUU7WUFDbEIsT0FBTyxFQUFFO2dCQUNQO29CQUNFLGtCQUFrQixFQUFFO3dCQUNsQixvQkFBb0IsRUFBRSxXQUFXO3FCQUNsQztpQkFDRjthQUNGO1NBQ0Y7S0FDRixDQUFDLENBQUM7SUFDSCxRQUFRLENBQUMsZUFBZSxDQUFDLHVDQUF1QyxFQUFFLENBQUMsQ0FBQyxDQUFDO0lBQ3JFLDJDQUEyQztJQUMzQyxNQUFNLENBQUMsU0FBUyxDQUFDLG1CQUFtQixDQUFDLENBQUMsSUFBSSxDQUFDLFNBQVMsQ0FBQyxDQUFDO0FBQ3hELENBQUMsQ0FBQyxDQUFDO0FBRUgsSUFBSSxDQUFDLHVDQUF1QyxFQUFFLEdBQUcsRUFBRTtJQUNqRCxNQUFNLEtBQUssR0FBRyxJQUFJLEdBQUcsQ0FBQyxLQUFLLEVBQUUsQ0FBQztJQUM5QixNQUFNLFNBQVMsR0FBRyxJQUFJLG9CQUFjLENBQUMsS0FBSyxFQUFFLG9CQUFvQixFQUFFLEVBQUUsQ0FBQyxDQUFDO0lBQ3RFLE1BQU0sUUFBUSxHQUFHLHFCQUFRLENBQUMsU0FBUyxDQUFDLEtBQUssQ0FBQyxDQUFDO0lBQzNDLG1CQUFtQjtJQUNuQixRQUFRLENBQUMsZUFBZSxDQUFDLHNDQUFzQyxFQUFFLENBQUMsQ0FBQyxDQUFDO0lBQ3BFLFFBQVEsQ0FBQyxlQUFlLENBQUMsdUNBQXVDLEVBQUUsQ0FBQyxDQUFDLENBQUM7SUFDckUsMkNBQTJDO0lBQzNDLE1BQU0sQ0FBQyxTQUFTLENBQUMsbUJBQW1CLENBQUMsQ0FBQyxHQUFHLENBQUMsSUFBSSxDQUFDLFNBQVMsQ0FBQyxDQUFDO0FBQzVELENBQUMsQ0FBQyxDQUFDO0FBRUgsSUFBSSxDQUFDLGlGQUFpRixFQUFFLEdBQUcsRUFBRTtJQUMzRixNQUFNLEtBQUssR0FBRyxJQUFJLEdBQUcsQ0FBQyxLQUFLLEVBQUUsQ0FBQztJQUM5QixNQUFNLGlCQUFpQixHQUFHLEtBQUssQ0FBQztJQUNoQyxNQUFNLEtBQUssR0FBd0I7UUFDakMsV0FBVyxFQUFFO1lBQ1gsVUFBVSxFQUFFLEtBQUs7WUFDakIsZ0JBQWdCLEVBQUUsSUFBSSxFQUFFLDZCQUE2QjtZQUNyRCxpQkFBaUIsRUFBRTtnQkFDakIsZUFBZSxFQUFFLGlCQUFpQjtnQkFDbEMscUJBQXFCLEVBQUUsaUJBQWlCO2dCQUN4QyxpQkFBaUIsRUFBRSxpQkFBaUI7Z0JBQ3BDLGdCQUFnQixFQUFFLGlCQUFpQjthQUNwQztZQUNELG9CQUFvQixFQUFFLFlBQVksQ0FBQyw2QkFBNkI7U0FDakU7UUFDRCx5QkFBeUIsRUFBRSxLQUFLO1FBQ2hDLDJCQUEyQixFQUFFO1lBQzNCLGVBQWUsRUFBRTtnQkFDZixNQUFNLEVBQUUsSUFBSSxPQUFPLENBQUMsVUFBVSxDQUFDLGFBQWEsRUFBRTtvQkFDNUMsUUFBUSxFQUFFLGdDQUFnQztpQkFDM0MsQ0FBQzthQUNIO1NBQ0Y7S0FDRixDQUFDO0lBQ0YsSUFBSSxvQkFBYyxDQUFDLEtBQUssRUFBRSxvQkFBb0IsRUFBRSxLQUFLLENBQUMsQ0FBQztJQUN2RCxNQUFNLFFBQVEsR0FBRyxxQkFBUSxDQUFDLFNBQVMsQ0FBQyxLQUFLLENBQUMsQ0FBQztJQUMzQyxtQkFBbUI7SUFDbkIsUUFBUSxDQUFDLHFCQUFxQixDQUFDLCtCQUErQixFQUFFO1FBQzlELGtCQUFrQixFQUFFO1lBQ2xCLE9BQU8sRUFBRTtnQkFDUDtvQkFDRSxVQUFVLEVBQUUsYUFBYTtvQkFDekIsRUFBRSxFQUFFLGdDQUFnQztpQkFDckM7YUFDRjtTQUNGO0tBQ0YsQ0FBQyxDQUFDO0FBQ0wsQ0FBQyxDQUFDLENBQUM7QUFFSCxJQUFJLENBQUMsZ0ZBQWdGLEVBQUUsR0FBRyxFQUFFO0lBQzFGLE1BQU0sS0FBSyxHQUFHLElBQUksR0FBRyxDQUFDLEtBQUssRUFBRSxDQUFDO0lBQzlCLE1BQU0sbUJBQW1CLEdBQUcsSUFBSSxFQUFFLENBQUMsTUFBTSxDQUFDLEtBQUssRUFBRSxtQkFBbUIsQ0FBQyxDQUFDO0lBQ3RFLElBQUksb0JBQWMsQ0FBQyxLQUFLLEVBQUUsb0JBQW9CLEVBQUU7UUFDOUMsNEJBQTRCLEVBQUU7WUFDNUIsc0JBQXNCLEVBQUUsbUJBQW1CO1NBQzVDO0tBQ0YsQ0FBQyxDQUFDO0lBRUgsTUFBTSxRQUFRLEdBQUcscUJBQVEsQ0FBQyxTQUFTLENBQUMsS0FBSyxDQUFDLENBQUM7SUFDM0MsUUFBUSxDQUFDLGVBQWUsQ0FBQyxpQkFBaUIsRUFBRSxDQUFDLENBQUMsQ0FBQztBQUVqRCxDQUFDLENBQUMsQ0FBQztBQUVILHdCQUF3QjtBQUN4Qix1Q0FBdUM7QUFDdkMsd0JBQXdCO0FBQ3hCLElBQUksQ0FBQyxvRUFBb0UsRUFBRSxHQUFHLEVBQUU7SUFDOUUsTUFBTSx