-
Notifications
You must be signed in to change notification settings - Fork 30
/
Copy pathRecipe.cs
226 lines (183 loc) · 9.27 KB
/
Recipe.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using Amazon.CDK;
using Amazon.CDK.AWS.CloudFront;
using Amazon.CDK.AWS.CloudFront.Origins;
using Amazon.CDK.AWS.S3;
using Amazon.CDK.AWS.S3.Deployment;
using AWS.Deploy.Recipes.CDK.Common;
using BlazorWasm.Configurations;
using Constructs;
// This is a generated file from the original deployment recipe. It is recommended to not modify this file in order
// to allow easy updates to the file when the original recipe that this project was created from has updates.
// To customize the CDK constructs created in this file you should use the AppStack.CustomizeCDKProps() method.
namespace BlazorWasm
{
using static AWS.Deploy.Recipes.CDK.Common.CDKRecipeCustomizer<Recipe>;
public class Recipe : Construct
{
public Bucket? ContentS3Bucket { get; private set; }
public BucketDeployment? ContentS3Deployment { get; private set; }
public Distribution? CloudFrontDistribution { get; private set; }
public string AccessLoggingBucket { get; } = "AccessLoggingBucket";
public string BackendRestApiHttpOrigin { get; } = "BackendRestApiHttpOrigin";
public string BackendRestApiCacheBehavior { get; } = "BackendRestApiCacheBehavior";
public Recipe(Construct scope, IRecipeProps<Configuration> props)
// The "Recipe" construct ID will be used as part of the CloudFormation logical ID. If the value is changed this will
// change the expected values for the "DisplayedResources" in the corresponding recipe file.
: base(scope, "Recipe")
{
ConfigureS3ContentBucket();
ConfigureCloudFrontDistribution(props.Settings);
ConfigureS3Deployment(props);
}
private void ConfigureS3ContentBucket()
{
var bucketProps = new BucketProps
{
// Turn on delete objects so deployed Blazor application is deleted when the stack is deleted.
AutoDeleteObjects = true,
RemovalPolicy = RemovalPolicy.DESTROY
};
ContentS3Bucket = new Bucket(this, nameof(ContentS3Bucket), InvokeCustomizeCDKPropsEvent(nameof(ContentS3Bucket), this, bucketProps));
new CfnOutput(this, "S3ContentBucket", new CfnOutputProps
{
Description = "S3 bucket where Blazor application is uploaded to",
Value = ContentS3Bucket.BucketName
});
}
private void ConfigureCloudFrontDistribution(Configuration settings)
{
if (ContentS3Bucket == null)
throw new InvalidOperationException($"{nameof(ContentS3Bucket)} has not been set. The {nameof(ConfigureS3ContentBucket)} method should be called before {nameof(ConfigureCloudFrontDistribution)}");
var distributionProps = new DistributionProps
{
DefaultBehavior = new BehaviorOptions
{
Origin = new S3Origin(ContentS3Bucket, new S3OriginProps())
},
DefaultRootObject = settings.IndexDocument,
EnableIpv6 = settings.EnableIpv6,
HttpVersion = settings.MaxHttpVersion,
PriceClass = settings.PriceClass
};
var errorResponses = new List<ErrorResponse>();
if (!string.IsNullOrEmpty(settings.ErrorDocument))
{
errorResponses.Add(
new ErrorResponse
{
ResponsePagePath = settings.ErrorDocument
}
);
}
if (settings.Redirect404ToRoot)
{
errorResponses.Add(
new ErrorResponse
{
HttpStatus = 404,
ResponseHttpStatus = 200,
ResponsePagePath = "/"
}
);
// Since S3 returns back an access denied for objects that don't exist to CloudFront treat 403 as 404 not found.
errorResponses.Add(
new ErrorResponse
{
HttpStatus = 403,
ResponseHttpStatus = 200,
ResponsePagePath = "/"
}
);
}
if (errorResponses.Any())
{
distributionProps.ErrorResponses = errorResponses.ToArray();
}
if(settings.AccessLogging?.Enable == true)
{
distributionProps.EnableLogging = true;
if(settings.AccessLogging.CreateLoggingS3Bucket)
{
var loggingBucket = new Bucket(this, nameof(AccessLoggingBucket), InvokeCustomizeCDKPropsEvent(nameof(AccessLoggingBucket), this, new BucketProps
{
RemovalPolicy = RemovalPolicy.RETAIN,
}));
distributionProps.LogBucket = loggingBucket;
new CfnOutput(this, "S3AccessLoggingBucket", new CfnOutputProps
{
Description = "S3 bucket storing access logs. Bucket and logs will be retained after deployment is deleted.",
Value = distributionProps.LogBucket.BucketName
});
}
else if(!string.IsNullOrEmpty(settings.AccessLogging.ExistingS3LoggingBucket))
{
distributionProps.LogBucket = Bucket.FromBucketName(this, nameof(AccessLoggingBucket), settings.AccessLogging.ExistingS3LoggingBucket);
}
if(!string.IsNullOrEmpty(settings.AccessLogging.LoggingS3KeyPrefix))
{
distributionProps.LogFilePrefix = settings.AccessLogging.LoggingS3KeyPrefix;
}
distributionProps.LogIncludesCookies = settings.AccessLogging.LogIncludesCookies;
}
if(!string.IsNullOrEmpty(settings.WebAclId))
{
distributionProps.WebAclId = settings.WebAclId;
}
CloudFrontDistribution = new Distribution(this, nameof(CloudFrontDistribution), InvokeCustomizeCDKPropsEvent(nameof(CloudFrontDistribution), this, distributionProps));
if (settings.BackendApi?.Enable == true)
{
var backendApiUri = new Uri(settings.BackendApi.Uri);
var httpOriginProps = new HttpOriginProps
{
OriginPath = backendApiUri.PathAndQuery
};
if (string.Equals("https", backendApiUri.Scheme, StringComparison.OrdinalIgnoreCase))
{
httpOriginProps.ProtocolPolicy = OriginProtocolPolicy.HTTPS_ONLY;
httpOriginProps.HttpsPort = backendApiUri.Port;
}
else
{
httpOriginProps.ProtocolPolicy = OriginProtocolPolicy.HTTP_ONLY;
httpOriginProps.HttpPort = backendApiUri.Port;
}
var httpOrigin = new HttpOrigin(backendApiUri.Host, InvokeCustomizeCDKPropsEvent(nameof(BackendRestApiHttpOrigin), this, httpOriginProps));
// Since this is a backend API where the business logic for the Blazor app caching must be disabled.
var addBehavorOptions = new AddBehaviorOptions
{
AllowedMethods = AllowedMethods.ALLOW_ALL,
CachePolicy = CachePolicy.CACHING_DISABLED
};
CloudFrontDistribution.AddBehavior(settings.BackendApi.ResourcePathPattern, httpOrigin, InvokeCustomizeCDKPropsEvent(nameof(BackendRestApiCacheBehavior), this, addBehavorOptions));
}
new CfnOutput(this, "EndpointURL", new CfnOutputProps
{
Description = "Endpoint to access application",
Value = $"https://{CloudFrontDistribution.DomainName}/"
});
}
private void ConfigureS3Deployment(IRecipeProps<Configuration> props)
{
if (ContentS3Bucket == null)
throw new InvalidOperationException($"{nameof(ContentS3Bucket)} has not been set. The {nameof(ConfigureS3ContentBucket)} method should be called before {nameof(ContentS3Bucket)}");
if (string.IsNullOrEmpty(props.DotnetPublishOutputDirectory))
throw new InvalidOrMissingConfigurationException("The provided path containing the dotnet publish output is null or empty.");
var bucketDeploymentProps = new BucketDeploymentProps
{
Sources = new ISource[] { Source.Asset(Path.Combine(props.DotnetPublishOutputDirectory, "wwwroot")) },
DestinationBucket = ContentS3Bucket,
MemoryLimit = 3008,
Distribution = CloudFrontDistribution,
DistributionPaths = new string[] { "/*" }
};
ContentS3Deployment = new BucketDeployment(this, nameof(ContentS3Deployment), InvokeCustomizeCDKPropsEvent(nameof(ContentS3Deployment), this, bucketDeploymentProps));
}
}
}