in Infra/lib/stack/StaticWebsiteStack.ts [30:121]
constructor(scope: Construct, id: string, props: StaticWebsiteStackProps) {
super(scope, id);
// S3 Website
const websiteBucket = new s3.Bucket(this, 'WebsiteBucket', {
websiteIndexDocument: 'index.html',
blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL,
});
// Cloudfront distribution
const cloudFrontOia = new cloudfront.OriginAccessIdentity(this, 'CloudfrontOia');
websiteBucket.grantRead(cloudFrontOia);
const cloudFrontDistribution = new cloudfront.CloudFrontWebDistribution(this, 'CloudfrontDistribution', {
originConfigs: [
{
s3OriginSource: {
s3BucketSource: websiteBucket,
originAccessIdentity: cloudFrontOia,
},
behaviors: [{ isDefaultBehavior: true }],
}
],
// We need to redirect "key not found errors" to index.html since our app is a Single Page App
errorConfigurations: [{
errorCode: 404,
responseCode: 200,
responsePagePath: '/index.html',
}],
});
const websiteDeployment = new s3Deployment.BucketDeployment(this, 'WebsiteDeployment', {
sources: [s3Deployment.Source.asset('../Website/build')],
destinationBucket: websiteBucket,
// Files in the distribution's edge caches will be invalidated after files are uploaded to the destination bucket.
distribution: cloudFrontDistribution,
serverSideEncryption: s3Deployment.ServerSideEncryption.AES_256,
});
const uploadWebsiteConfigFunction = new lambda.Function(this, 'UploadWebsiteConfigFunction', {
runtime: lambda.Runtime.PYTHON_3_7,
handler: 'app.on_event',
code: lambda.Code.fromAsset(path.join(__dirname, '../custom-resources/upload-website-config')),
timeout: cdk.Duration.seconds(30),
initialPolicy: [
new iam.PolicyStatement({
effect: iam.Effect.ALLOW,
actions: ['cloudfront:GetInvalidation', 'cloudfront:CreateInvalidation'],
resources: ['*'],
}),
],
});
websiteBucket.grantWrite(uploadWebsiteConfigFunction);
const uploadWebsiteConfigProvider = new cr.Provider(this, 'UploadWebsiteConfigProvider', {
onEventHandler: uploadWebsiteConfigFunction,
});
const websiteConfiguration = `window['runConfig'] = {
region: "${this.region}",
userPoolId: "${props.userPoolId}",
userPoolWebClientId: "${props.userPoolClientId}",
identityPoolId: "${props.identityPoolId}",
graphqlEndpoint: "${props.graphqlEndpoint}",
apiUrl: "${props.apiUrl}"
}`;
const uploadWebsiteConfigResource = new cdk.CustomResource(this, 'UploadWebsiteConfigResource', {
serviceToken: uploadWebsiteConfigProvider.serviceToken,
// Pass the mapping file attributes as a property. Every time the mapping file changes, the custom resource will be updated which will trigger the corresponding Lambda.
properties: {
'S3_BUCKET': websiteBucket.bucketName,
'S3_CONFIG_FILE_KEY': this.s3ConfigFileKey,
'WEBSITE_CONFIG': websiteConfiguration,
'CLOUDFRONT_DISTRIBUTION_ID': cloudFrontDistribution.distributionId,
// The bucket deployment clears the s3 bucket, so we must always run the custom resource to write the config
'ALWAYS_UPDATE': new Date().toISOString(),
},
});
uploadWebsiteConfigResource.node.addDependency(websiteDeployment);
// Output
new cdk.CfnOutput(this, 'CloudFrontUrl', {
value: cloudFrontDistribution.distributionDomainName,
});
}