Skip to content

Commit 3c29630

Browse files
committed
progress towards cloudfront
1 parent 8341f3c commit 3c29630

File tree

6 files changed

+160
-11
lines changed

6 files changed

+160
-11
lines changed

src/frontend/assets.ts

+57
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
import { AWS, Resource as AwsResource } from 'cloudformation-declarations'
2+
import { merge } from 'lodash'
3+
4+
/**
5+
* This is the folder where assets are loaded from, implemented on top of
6+
* AWS S3. The web path `/assets/*` will be served from the path `/assets/*`
7+
* on the S3 bucket.
8+
*/
9+
export default class Assets {
10+
private appName: string
11+
private access: string
12+
private accountId: string
13+
14+
/**
15+
* Create a new S3 bucket for a given seagull app, protected by CloudFront.
16+
* The CloudFront-internal identifier for the bucket is `'appBucket'`.
17+
*
18+
* @param appName the actual name of the seagull app
19+
* @param accountId the AWS account ID, required for unique naming
20+
* @param access id-string for connecting the bucket with CloudFront
21+
*/
22+
constructor(appName: string, accountId: string, access: string) {
23+
this.appName = appName
24+
this.accountId = accountId
25+
this.access = access
26+
}
27+
28+
/**
29+
* Returns the CloudFormation Resources for a S3-Bucket with Policy
30+
*/
31+
get resources(): { [name: string]: AwsResource } {
32+
return merge({}, this.bucket(), this.policy())
33+
}
34+
35+
// generate CloudFormation Resource Template for a S3 Bucket
36+
private bucket(): { [name: string]: AWS.S3.Bucket } {
37+
const res: { [name: string]: AWS.S3.Bucket } = {}
38+
const Type = 'AWS::S3::Bucket'
39+
const BucketName = `${this.appName}-${this.accountId}-assets-bucket`
40+
const Properties = { AccessControl: 'Private', BucketName }
41+
return { appBucket: { Properties, Type } }
42+
}
43+
44+
// generate CloudFormation Resource Template for a S3 Bucket Policy
45+
private policy() {
46+
const Type = 'AWS::S3::BucketPolicy'
47+
const Ref = 'appBucket'
48+
const CanonicalUser = { 'Fn::GetAtt': [this.access, 'S3CanonicalUserId'] }
49+
const Principal = { CanonicalUser }
50+
const Action = ['s3:GetObject', 's3:GetObjectAcl']
51+
const Effect = 'Allow'
52+
const Resource = { 'Fn::Join': ['', ['arn:aws:s3:::', { Ref }, '/*']] }
53+
const Statement = [{ Action, Effect, Principal, Resource }]
54+
const Properties = { Bucket: { Ref }, PolicyDocument: { Statement } }
55+
return { [`${Ref}Permission`]: { Properties, Type } }
56+
}
57+
}

src/frontend/cloudfront.ts

+54
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
import { AWS, Resource } from 'cloudformation-declarations'
2+
import { merge } from 'lodash'
3+
import Assets from './assets'
4+
5+
/**
6+
* The CDN of AWS, takes care of caching and reducing load to your app.
7+
* It will split traffic between the app's API routes and static [[Assets]]
8+
* located at an S3 bucket.
9+
*/
10+
export default class CloudFront {
11+
/**
12+
* The S3 bucket for storing assets. The bucket will be private and only
13+
* accessible through CloudFront and only the folder `/assets/*`.
14+
*/
15+
assets: Assets
16+
17+
private accessOriginResourceName = 'appDistributionAccessIdentity'
18+
private appName: string
19+
private accountId: string
20+
private domains: string[]
21+
22+
/**
23+
* Create a new CloudFront Configuration Image. This will also contain
24+
* the dedicated Assets folder directly linked and with permissions
25+
* configured.
26+
*
27+
* @param appName the name of the target app
28+
* @param accountId the ID of the AWS acccount, used as "unique" hash
29+
* @param domains a list of domains pointing to your app
30+
*/
31+
constructor(appName: string, accountId: string, domains: string[] = []) {
32+
this.appName = appName
33+
this.accountId = accountId
34+
this.domains = domains
35+
}
36+
37+
/**
38+
* Transforms itself into a CloudFormation Resource Template.
39+
*/
40+
get resources(): { [name: string]: Resource } {
41+
const { appName, accountId, accessOriginResourceName: name } = this
42+
const assets = new Assets(appName, accountId, name)
43+
return merge({}, assets, this.accessIdentity)
44+
}
45+
46+
// identity-resource needed for communication between cloudfront and S3
47+
private get accessIdentity(): any {
48+
const Type = 'AWS::CloudFront::CloudFrontOriginAccessIdentity'
49+
const Comment = 'Default Access Identity for your seagull app'
50+
const CloudFrontOriginAccessIdentityConfig = { Comment }
51+
const Properties = { CloudFrontOriginAccessIdentityConfig }
52+
return { [this.accessOriginResourceName]: { Properties, Type } }
53+
}
54+
}

src/index.ts

+2
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
export { default as Account } from './meta/account'
22
export { default as Apis } from './backend/apis'
33
export { default as App } from './app'
4+
export { default as Assets } from './frontend/assets'
45
export { default as Backend } from './backend/backend'
6+
export { default as CloudFront } from './frontend/cloudfront'
57
export { default as Directory } from './abstract/directory'
68
export { default as Folder } from './abstract/folder'
79
export { default as Frontend } from './frontend/frontend'

src/services/s3.ts

-11
This file was deleted.

test/functional/frontend/assets.ts

+27
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
import { Assets } from '@lib'
2+
import 'chai/register-should'
3+
import * as fs from 'fs'
4+
import { skip, slow, suite, test, timeout } from 'mocha-typescript'
5+
import FunctionalTest from '../../helper/functional_test'
6+
7+
@suite('Assets')
8+
class Test extends FunctionalTest {
9+
@test
10+
'can be instantiated'() {
11+
const meta = new Assets('demoApp', '1234', 'accessId')
12+
meta.should.be.an('object')
13+
}
14+
15+
@test
16+
'can transform into resources template object'() {
17+
const meta = new Assets('demoApp', '1234', 'accessId')
18+
meta.resources.should.be.an('object')
19+
const { appBucket, appBucketPermission } = meta.resources
20+
appBucket.should.be.an('object')
21+
appBucket.Type.should.be.equal('AWS::S3::Bucket')
22+
appBucket.Properties.should.be.an('object')
23+
appBucketPermission.should.be.an('object')
24+
appBucketPermission.Type.should.be.equal('AWS::S3::BucketPolicy')
25+
appBucketPermission.Properties.should.be.an('object')
26+
}
27+
}
+20
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
import { CloudFront } from '@lib'
2+
import 'chai/register-should'
3+
import * as fs from 'fs'
4+
import { skip, slow, suite, test, timeout } from 'mocha-typescript'
5+
import FunctionalTest from '../../helper/functional_test'
6+
7+
@suite('Assets')
8+
class Test extends FunctionalTest {
9+
@test
10+
'can be instantiated'() {
11+
const cf = new CloudFront('demoApp', '1234', ['example.com'])
12+
cf.should.be.an('object')
13+
}
14+
15+
@test
16+
'can transform into resources template object'() {
17+
const cf = new CloudFront('demoApp', '1234', ['example.com'])
18+
cf.resources.should.be.an('object')
19+
}
20+
}

0 commit comments

Comments
 (0)