forked from rolling-scopes-school/nodejs-aws-fe
-
Notifications
You must be signed in to change notification settings - Fork 786
task2: added s3 bucket and cloudfront distribution #1
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
Merged
Merged
Changes from all commits
Commits
Show all changes
2 commits
Select commit
Hold shift + click to select a range
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
151 changes: 151 additions & 0 deletions
151
.serverless_plugins/serverless-single-page-app-plugin.js
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,151 @@ | ||
'use strict'; | ||
|
||
const spawnSync = require('child_process').spawnSync; | ||
|
||
class ServerlessPlugin { | ||
constructor(serverless, options) { | ||
this.serverless = serverless; | ||
this.options = options; | ||
this.commands = { | ||
syncToS3: { | ||
usage: 'Deploys the `app` directory to your bucket', | ||
lifecycleEvents: [ | ||
'sync', | ||
], | ||
}, | ||
domainInfo: { | ||
usage: 'Fetches and prints out the deployed CloudFront domain names', | ||
lifecycleEvents: [ | ||
'domainInfo', | ||
], | ||
}, | ||
invalidateCloudFrontCache: { | ||
usage: 'Invalidates CloudFront cache', | ||
lifecycleEvents: [ | ||
'invalidateCache', | ||
], | ||
}, | ||
}; | ||
|
||
this.hooks = { | ||
'syncToS3:sync': this.syncDirectory.bind(this), | ||
'domainInfo:domainInfo': this.domainInfo.bind(this), | ||
'invalidateCloudFrontCache:invalidateCache': this.invalidateCache.bind( | ||
this, | ||
), | ||
}; | ||
} | ||
|
||
runAwsCommand(args) { | ||
let command = 'aws'; | ||
if (this.serverless.variables.service.provider.region) { | ||
command = `${command} --region ${this.serverless.variables.service.provider.region}`; | ||
} | ||
if (this.serverless.variables.service.provider.profile) { | ||
command = `${command} --profile ${this.serverless.variables.service.provider.profile}`; | ||
} | ||
const result = spawnSync(command, args, { shell: true }); | ||
const stdout = result.stdout.toString(); | ||
const sterr = result.stderr.toString(); | ||
if (stdout) { | ||
this.serverless.cli.log(stdout); | ||
} | ||
if (sterr) { | ||
this.serverless.cli.log(sterr); | ||
} | ||
|
||
return { stdout, sterr }; | ||
} | ||
|
||
// syncs the `app` directory to the provided bucket | ||
syncDirectory() { | ||
const s3Bucket = this.serverless.variables.service.custom.s3Bucket; | ||
const buildFolder = this.serverless.variables.service.custom.client.distributionFolder; | ||
const args = [ | ||
's3', | ||
'sync', | ||
`${buildFolder}/`, | ||
`s3://${s3Bucket}/`, | ||
'--delete', | ||
]; | ||
const { sterr } = this.runAwsCommand(args); | ||
if (!sterr) { | ||
this.serverless.cli.log('Successfully synced to the S3 bucket'); | ||
} else { | ||
throw new Error('Failed syncing to the S3 bucket'); | ||
} | ||
} | ||
|
||
// fetches the domain name from the CloudFront outputs and prints it out | ||
async domainInfo() { | ||
const provider = this.serverless.getProvider('aws'); | ||
const stackName = provider.naming.getStackName(this.options.stage); | ||
const result = await provider.request( | ||
'CloudFormation', | ||
'describeStacks', | ||
{ StackName: stackName }, | ||
this.options.stage, | ||
this.options.region, | ||
); | ||
|
||
const outputs = result.Stacks[0].Outputs; | ||
const output = outputs.find( | ||
entry => entry.OutputKey === 'WebAppCloudFrontDistributionOutput', | ||
); | ||
|
||
if (output && output.OutputValue) { | ||
this.serverless.cli.log(`Web App Domain: ${output.OutputValue}`); | ||
return output.OutputValue; | ||
} | ||
|
||
this.serverless.cli.log('Web App Domain: Not Found'); | ||
const error = new Error('Could not extract Web App Domain'); | ||
throw error; | ||
} | ||
|
||
async invalidateCache() { | ||
const provider = this.serverless.getProvider('aws'); | ||
|
||
const domain = await this.domainInfo(); | ||
|
||
const result = await provider.request( | ||
'CloudFront', | ||
'listDistributions', | ||
{}, | ||
this.options.stage, | ||
this.options.region, | ||
); | ||
|
||
const distributions = result.DistributionList.Items; | ||
const distribution = distributions.find( | ||
entry => entry.DomainName === domain, | ||
); | ||
|
||
if (distribution) { | ||
this.serverless.cli.log( | ||
`Invalidating CloudFront distribution with id: ${distribution.Id}`, | ||
); | ||
const args = [ | ||
'cloudfront', | ||
'create-invalidation', | ||
'--distribution-id', | ||
distribution.Id, | ||
'--paths', | ||
'"/*"', | ||
]; | ||
const { sterr } = this.runAwsCommand(args); | ||
if (!sterr) { | ||
this.serverless.cli.log('Successfully invalidated CloudFront cache'); | ||
} else { | ||
throw new Error('Failed invalidating CloudFront cache'); | ||
} | ||
} else { | ||
const message = `Could not find distribution with domain ${domain}`; | ||
const error = new Error(message); | ||
this.serverless.cli.log(message); | ||
throw error; | ||
} | ||
} | ||
} | ||
|
||
module.exports = ServerlessPlugin; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,128 @@ | ||
service: node-in-aws-web | ||
|
||
frameworkVersion: '2' | ||
|
||
provider: | ||
name: aws | ||
runtime: nodejs12.x | ||
# setup profile for AWS CLI. | ||
# profile: node-aws | ||
|
||
plugins: | ||
- serverless-finch | ||
# 'serverless-single-page-app-plugin' is a custom plugin that located .serverless_plugins folder. | ||
# Existing plugin (https://www.npmjs.com/package/serverless-single-page-app-plugin) doesn't have invalidate cache feature that often used during CI/CD events. | ||
# How to build your own plugins: https://www.serverless.com/framework/docs/providers/aws/guide/plugins#service-local-plugin | ||
- serverless-single-page-app-plugin | ||
|
||
custom: | ||
client: | ||
bucketName: node-in-aws-web-bucket | ||
distributionFolder: build | ||
s3BucketName: ${self:custom.client.bucketName} | ||
|
||
## Serverless-single-page-app-plugin configuration: | ||
s3LocalPath: ${self:custom.client.distributionFolder}/ | ||
|
||
resources: | ||
Resources: | ||
## Specifying the S3 Bucket | ||
WebAppS3Bucket: | ||
Type: AWS::S3::Bucket | ||
Properties: | ||
BucketName: ${self:custom.s3BucketName} | ||
AccessControl: PublicRead | ||
WebsiteConfiguration: | ||
IndexDocument: index.html | ||
ErrorDocument: index.html | ||
# VersioningConfiguration: | ||
# Status: Enabled | ||
|
||
## Specifying the policies to make sure all files inside the Bucket are avaialble to CloudFront | ||
WebAppS3BucketPolicy: | ||
Type: AWS::S3::BucketPolicy | ||
Properties: | ||
Bucket: | ||
Ref: WebAppS3Bucket | ||
PolicyDocument: | ||
Statement: | ||
- Sid: 'AllowCloudFrontAccessIdentity' | ||
Effect: Allow | ||
Action: s3:GetObject | ||
Resource: arn:aws:s3:::${self:custom.s3BucketName}/* | ||
Principal: | ||
AWS: | ||
Fn::Join: | ||
- ' ' | ||
- - 'arn:aws:iam::cloudfront:user/CloudFront Origin Access Identity' | ||
- !Ref OriginAccessIdentity | ||
|
||
OriginAccessIdentity: | ||
Type: AWS::CloudFront::CloudFrontOriginAccessIdentity | ||
Properties: | ||
CloudFrontOriginAccessIdentityConfig: | ||
Comment: Access identity between CloudFront and S3 bucket | ||
|
||
## Specifying the CloudFront Distribution to server your Web Application | ||
WebAppCloudFrontDistribution: | ||
Type: AWS::CloudFront::Distribution | ||
Properties: | ||
DistributionConfig: | ||
Origins: | ||
- DomainName: ${self:custom.s3BucketName}.s3.amazonaws.com | ||
## An identifier for the origin which must be unique within the distribution | ||
Id: myS3Origin | ||
## In case you don't want to restrict the bucket access use CustomOriginConfig and remove S3OriginConfig | ||
S3OriginConfig: | ||
OriginAccessIdentity: !Sub origin-access-identity/cloudfront/${OriginAccessIdentity} | ||
# CustomOriginConfig: | ||
# HTTPPort: 80 | ||
# HTTPSPort: 443 | ||
# OriginProtocolPolicy: https-only | ||
Enabled: true | ||
IPV6Enabled: true | ||
HttpVersion: http2 | ||
## Uncomment the following section in case you are using a custom domain | ||
# Aliases: | ||
# - mysite.example.com | ||
DefaultRootObject: index.html | ||
## Since the Single Page App is taking care of the routing we need to make sure ever path is served with index.html | ||
## The only exception are files that actually exist e.h. app.js, reset.css | ||
CustomErrorResponses: | ||
- ErrorCode: 404 | ||
ResponseCode: 200 | ||
ResponsePagePath: /index.html | ||
DefaultCacheBehavior: | ||
AllowedMethods: [ 'GET', 'HEAD', 'OPTIONS' ] | ||
CachedMethods: [ 'GET', 'HEAD', 'OPTIONS' ] | ||
ForwardedValues: | ||
Headers: | ||
- Access-Control-Request-Headers | ||
- Access-Control-Request-Method | ||
- Origin | ||
- Authorization | ||
## Defining if and how the QueryString and Cookies are forwarded to the origin which in this case is S3 | ||
QueryString: false | ||
Cookies: | ||
Forward: none | ||
## The origin id defined above | ||
TargetOriginId: myS3Origin | ||
## The protocol that users can use to access the files in the origin. To allow HTTP use `allow-all` | ||
ViewerProtocolPolicy: redirect-to-https | ||
Compress: true | ||
DefaultTTL: 0 | ||
## The certificate to use when viewers use HTTPS to request objects. | ||
ViewerCertificate: | ||
CloudFrontDefaultCertificate: 'true' | ||
## Uncomment the following section in case you want to enable logging for CloudFront requests | ||
# Logging: | ||
# IncludeCookies: 'false' | ||
# Bucket: mylogs.s3.amazonaws.com | ||
# Prefix: myprefix | ||
|
||
## In order to print out the hosted domain via `serverless info` we need to define the DomainName output for CloudFormation | ||
Outputs: | ||
WebAppS3BucketOutput: | ||
Value: !Ref WebAppS3Bucket | ||
WebAppCloudFrontDistributionOutput: | ||
Value: !GetAtt WebAppCloudFrontDistribution.DomainName |
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
add comments why it was used
The main reason was 'cache invalidation'
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
added in
serverless.yml
file