Your Startup Name Here
Your Tagline
We're $CompanyName, and we're changing what it means to $Task. Our innovative use of $Technology makes life easier for $JobTitles, so they can focus on what they're really good at instead of wasting time and effort on $MenialOrDifficultTask. Streamline your $TaskProcess with $Product and take to the skies!
```
In `style.css`, you'll make it pretty with some bright colors and a CSS background:
```css
@import url('https://fonts.googleapis.com/css?family=News+Cycle|Teko&display=swap');
body {
background-color: #f7f7fa;
opacity: 0.8;
background-image: radial-gradient(#f79645 0.5px, #f7f7fa 0.5px);
background-size: 10px 10px;
}
ul {
list-style-type: none;
}
ul li {
display: inline-block;
}
a {
color: white;
-webkit-transition: color .5s ease-out;
transition: color .5s ease-out;
text-decoration: none;
}
a:hover, a:active {
color: rgb(55, 188, 250);
}
header {
background-color: rgba(214, 73, 73, .6);
height: 80px;
position: absolute;
top: 0;
width: 100%;
box-shadow: 0px 2px 7px -1px rgba(0,0,0,0.75);
-webkit-box-shadow: 0px 2px 7px -1px rgba(0,0,0,0.75);
-moz-box-shadow: 0px 2px 7px -1px rgba(0,0,0,0.75);
}
header li {
color: white;
}
.active a {
color: rgb(255, 157, 112);
}
.social {
position: absolute;
right: 50px;
top: -5px;
font-size: 30px;
}
.social li {
margin: 0 5px 0 5px;
}
.logo {
font-family: Teko;
position: absolute;
left: 5px;
top: -60px;
font-size: 40px;
}
.banner {
width: 60vw;
font-family: Teko;
font-size: 2vw;
text-align: center;
margin-top: 15vw;
margin-left: 20vw;
}
.banner h1 {
color: rgb(214, 73, 73);
}
.banner p, .about p {
font-family: News Cycle;
}
```
To make sure styles display consistently across browsers, you also need to normalize some styles. [Copy normalize.css from GitHub](https://github.com/necolas/normalize.css/blob/master/normalize.css).
### Step 4. Creating a CDN
Next, you want to add a CDN to your website.
{{% chooser cloud "aws,gcp" %}}
{{% choosable cloud aws %}}
On AWS, this means you want to front your S3 bucket with Cloudfront. This is a pretty big object, but most of it can be copy and pasted without further thought.
```typescript
const s3OriginId = "myS3Origin";
const cloudfrontDistribution = new aws.cloudfront.Distribution(
"s3Distribution",
{
origins: [
{
domainName: bucket.bucketRegionalDomainName,
originId: s3OriginId,
},
],
enabled: true,
isIpv6Enabled: true,
comment: "Some comment",
defaultRootObject: "index.html",
defaultCacheBehavior: {
allowedMethods: [
"DELETE",
"GET",
"HEAD",
"OPTIONS",
"PATCH",
"POST",
"PUT",
],
cachedMethods: ["GET", "HEAD"],
targetOriginId: s3OriginId,
forwardedValues: {
queryString: false,
cookies: {
forward: "none",
},
},
viewerProtocolPolicy: "allow-all",
minTtl: 0,
defaultTtl: 3600,
maxTtl: 86400,
},
priceClass: "PriceClass_200",
restrictions: {
geoRestriction: {
restrictionType: "whitelist",
locations: ["US", "CA", "GB", "DE"],
},
},
viewerCertificate: {
cloudfrontDefaultCertificate: true,
},
}
);
```
{{% /choosable %}}
{{% choosable cloud gcp %}}
On Google Cloud, that means you want to front your Cloud Storage Bucket with a [Load Balancer](https://cloud.google.com/load-balancing/docs/https) and enable its [CDN capabilities](https://cloud.google.com/cdn/docs/overview). Add the following additional code to your `index.ts` file in order to create a Google Cloud Load Balancer, Public IP address and URL Map to your Google Cloud storage bucket.
```typescript
// Google Cloud Load Balancer Backend
const backendBucket = new gcp.compute.BackendBucket("backend-bucket", {
bucketName: bucket.name,
enableCdn: true,
});
// Provision a global IP address for the CDN
const ip = new gcp.compute.GlobalAddress("ip");
// Create a URLMap to route requests to the storage bucket
const urlMap = new gcp.compute.URLMap("url-map", {defaultService: backendBucket.selfLink});
// Create an HTTP proxy to route requests to the URLMap
const httpProxy = new gcp.compute.TargetHttpProxy("http-proxy", {urlMap: urlMap.selfLink});
// Create a GlobalForwardingRule rule to route requests to the HTTP proxy
const httpForwardingRule = new gcp.compute.GlobalForwardingRule("http-forwarding-rule", {
ipAddress: ip.address,
ipProtocol: "TCP",
portRange: "80",
target: httpProxy.selfLink,
});
```
{{% /choosable %}}
{{% /chooser %}}
### Step 5. Introducing ComponentResources
Now... you _could_ continue to add resource after resource, but Pulumi is more than that. You can build your own reusable components. To do that, refactor what you have above into a `CdnWebsite` component at `pulumi-challenge/cdn-website/index.ts`.
{{% chooser cloud "aws,gcp" %}}
{{% choosable cloud aws %}}
```typescript
import * as pulumi from "@pulumi/pulumi";
import * as aws from "@pulumi/aws";
import * as fs from "fs";
import * as mime from "mime";
// This is a simpler verison of:
// https://github.com/pulumi/pulumi-aws-static-website
export class CdnWebsite extends pulumi.ComponentResource {
private bucket: aws.s3.BucketV2;
private bucketAcl: aws.s3.BucketAclV2;
private cloudfrontDistribution: aws.cloudfront.Distribution;
private s3OriginId: string = "myS3Origin";
private staticWebsiteDirectory: string = "./website";
constructor(name: string, args: any, opts?: pulumi.ComponentResourceOptions) {
super("pulumi:challenge:CdnWebsite", name, args, opts);
this.bucket = new aws.s3.BucketV2(
"bucketV2",
{
tags: {
Name: "My bucket",
},
},
{
parent: this,
}
);
this.bucketAcl = new aws.s3.BucketAclV2(
"bAcl",
{
bucket: this.bucket.id,
acl: aws.s3.PublicReadAcl,
},
{
parent: this,
}
);
this.cloudfrontDistribution = new aws.cloudfront.Distribution(
"s3Distribution",
{
origins: [
{
domainName: this.bucket.bucketRegionalDomainName,
originId: this.s3OriginId,
},
],
enabled: true,
isIpv6Enabled: true,
comment: "Some comment",
defaultRootObject: "index.html",
defaultCacheBehavior: {
allowedMethods: [
"DELETE",
"GET",
"HEAD",
"OPTIONS",
"PATCH",
"POST",
"PUT",
],
cachedMethods: ["GET", "HEAD"],
targetOriginId: this.s3OriginId,
forwardedValues: {
queryString: false,
cookies: {
forward: "none",
},
},
viewerProtocolPolicy: "allow-all",
minTtl: 0,
defaultTtl: 3600,
maxTtl: 86400,
},
priceClass: "PriceClass_200",
restrictions: {
geoRestriction: {
restrictionType: "whitelist",
locations: ["US", "CA", "GB", "DE"],
},
},
viewerCertificate: {
cloudfrontDefaultCertificate: true,
},
},
{
parent: this,
}
);
fs.readdirSync(this.staticWebsiteDirectory).forEach((file) => {
const filePath = `${this.staticWebsiteDirectory}/${file}`;
const fileContent = fs.readFileSync(filePath).toString();
new aws.s3.BucketObject(
file,
{
bucket: this.bucket.id,
source: new pulumi.asset.FileAsset(filePath),
contentType: mime.getType(filePath) || undefined,
acl: aws.s3.PublicReadAcl,
},
{
parent: this.bucket,
}
);
});
// You also need to register all the expected outputs for this
// component resource that will get returned by default
this.registerOutputs({
bucketName: this.bucket.id,
cdnUrl: this.cloudfrontDistribution.domainName,
});
}
get url(): pulumi.Output