@inizioevoke/evosynth 1.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.editorconfig +12 -0
- package/dist/constructs/certificatemanager.d.ts +12 -0
- package/dist/constructs/certificatemanager.js +19 -0
- package/dist/constructs/cloudfront.d.ts +55 -0
- package/dist/constructs/cloudfront.js +84 -0
- package/dist/constructs/codebuild.d.ts +31 -0
- package/dist/constructs/codebuild.js +95 -0
- package/dist/constructs/codepipeline.d.ts +30 -0
- package/dist/constructs/codepipeline.js +63 -0
- package/dist/constructs/route53.d.ts +14 -0
- package/dist/constructs/route53.js +24 -0
- package/dist/constructs/s3.d.ts +11 -0
- package/dist/constructs/s3.js +17 -0
- package/dist/constructs/ssm.d.ts +9 -0
- package/dist/constructs/ssm.js +17 -0
- package/dist/constructs/waf.d.ts +21 -0
- package/dist/constructs/waf.js +89 -0
- package/dist/index.d.ts +10 -0
- package/dist/index.js +26 -0
- package/dist/lib/const.d.ts +1 -0
- package/dist/lib/const.js +2 -0
- package/dist/lib/csv-redirects.d.ts +3 -0
- package/dist/lib/csv-redirects.js +124 -0
- package/dist/lib/tags.d.ts +19 -0
- package/dist/lib/tags.js +47 -0
- package/dist/lib/types.d.ts +14 -0
- package/dist/lib/types.js +2 -0
- package/dist/lib/utils.d.ts +2 -0
- package/dist/lib/utils.js +7 -0
- package/dist/lib/version.d.ts +1 -0
- package/dist/lib/version.js +5 -0
- package/dist/stacks/web-static-serverless/codepipeline-stack.d.ts +17 -0
- package/dist/stacks/web-static-serverless/codepipeline-stack.js +28 -0
- package/dist/stacks/web-static-serverless/web-global-stack.d.ts +43 -0
- package/dist/stacks/web-static-serverless/web-global-stack.js +142 -0
- package/dist/stages/str8r-clm-stage.d.ts +17 -0
- package/dist/stages/str8r-clm-stage.js +41 -0
- package/dist/stages/web-static-serverless-stage.d.ts +19 -0
- package/dist/stages/web-static-serverless-stage.js +51 -0
- package/dist/templates/cffn-default-doc-basic-auth.js +155 -0
- package/dist/templates/cffn-default-doc.js +132 -0
- package/inizioevoke-evosynth.code-workspace +8 -0
- package/package.json +24 -0
- package/readme.md +114 -0
- package/samples/app.ts +103 -0
- package/samples/codebuild/buildspec.yml +24 -0
- package/samples/config/redirects.csv +6 -0
- package/src/constructs/certificatemanager.ts +28 -0
- package/src/constructs/cloudfront.ts +148 -0
- package/src/constructs/codebuild.ts +124 -0
- package/src/constructs/codepipeline.ts +93 -0
- package/src/constructs/route53.ts +34 -0
- package/src/constructs/s3.ts +25 -0
- package/src/constructs/ssm.ts +24 -0
- package/src/constructs/waf.ts +118 -0
- package/src/index.ts +35 -0
- package/src/lib/const.ts +1 -0
- package/src/lib/csv-redirects.ts +139 -0
- package/src/lib/tags.ts +55 -0
- package/src/lib/types.ts +17 -0
- package/src/lib/utils.ts +7 -0
- package/src/lib/version.ts +6 -0
- package/src/stacks/web-static-serverless/codepipeline-stack.ts +42 -0
- package/src/stacks/web-static-serverless/web-global-stack.ts +184 -0
- package/src/stages/str8r-clm-stage.ts +57 -0
- package/src/stages/web-static-serverless-stage.ts +69 -0
- package/src/templates/cffn-default-doc-basic-auth.js +155 -0
- package/src/templates/cffn-default-doc.js +132 -0
- package/tasks/clean.js +7 -0
- package/tasks/copy-templates.js +13 -0
- package/tsconfig.json +32 -0
package/readme.md
ADDED
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
# @inizioevoke/evosynth
|
|
2
|
+
|
|
3
|
+
EvoSynth/EvoGen is an npm package providing reusable AWS CDK stacks for deploying static serverless websites. It targets S3 + CloudFront + Route53 + ACM + WAF + CodePipeline architectures.
|
|
4
|
+
|
|
5
|
+
## Prerequisites
|
|
6
|
+
- [CDK v2.238.x](https://docs.aws.amazon.com/cdk/v2/guide/home.html)
|
|
7
|
+
- Node version 22+
|
|
8
|
+
- AWS Account ID, a configured profile, or other credentials
|
|
9
|
+
- CodeConnection ARN or SSM Parameter Store path of a shared connection ARN
|
|
10
|
+
- A WAF web ACL ARN or SSM Parameter Store path of a shared ACL ARN
|
|
11
|
+
- The Bitbucket or GitHub repository
|
|
12
|
+
|
|
13
|
+
## Project configuration
|
|
14
|
+
|
|
15
|
+
- Copy the buildspec.yml on your Bitbucket Repo and the build configuration
|
|
16
|
+
- Update/Configure the Stack example below
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
## Example App
|
|
20
|
+
|
|
21
|
+
```typescript
|
|
22
|
+
import * as cdk from 'aws-cdk-lib/core';
|
|
23
|
+
import { CloudFrontFunctionRedirects } from '@inizioevoke/evosynth/constructs/cloudfront';
|
|
24
|
+
import { WebStaticServerlessStage } from '@inizioevoke/evosynth/stages/web-static-serverless-stage';
|
|
25
|
+
import { AddSourceStageParams } from '@inizioevoke/evosynth/constructs/codepipeline';
|
|
26
|
+
|
|
27
|
+
const app = new cdk.App();
|
|
28
|
+
|
|
29
|
+
const env = getEnv('us-east-1');
|
|
30
|
+
// const env = getEnv('000000000000');
|
|
31
|
+
// const env = getEnv({ account: '000000000000', region: 'us-east-1' });
|
|
32
|
+
|
|
33
|
+
const tags: Record<string, string> = {
|
|
34
|
+
'Account': 'Account Name',
|
|
35
|
+
'Brand': 'Brand Name'
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
const repository: Omit<AddSourceStageParams, 'branch'> = {
|
|
39
|
+
codestarConnection: 'ARN',
|
|
40
|
+
// codestarConnection: { path: '/my/connection/arn' },
|
|
41
|
+
repoOwner: 'evokegroup',
|
|
42
|
+
repo: 'myproject-v1'
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
const redirects: CloudFrontFunctionRedirects = {
|
|
46
|
+
paths: {
|
|
47
|
+
'/hello': '/world',
|
|
48
|
+
'/redirect': ['/here', 302]
|
|
49
|
+
}
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
new WebStaticServerlessStage(app, 'dev', {
|
|
53
|
+
env,
|
|
54
|
+
stageName: 'myproject-v1-dev',
|
|
55
|
+
tags,
|
|
56
|
+
envType: 'NOT_PROD',
|
|
57
|
+
description: 'My Project DEV',
|
|
58
|
+
hostedZone: 'evodev.net',
|
|
59
|
+
domainName: 'myproject-v1.evodev.net',
|
|
60
|
+
basicAuth: {
|
|
61
|
+
user: 'user',
|
|
62
|
+
password: 'password'
|
|
63
|
+
},
|
|
64
|
+
// basicAuth: 'dXNlcjpwYXNzd29yZA==',
|
|
65
|
+
// basicAuth: { path: '/my/basicAuth/credentials' }
|
|
66
|
+
webAcl: 'ARN',
|
|
67
|
+
// webAcl: { path: '/my/webacl/arn' },
|
|
68
|
+
// webAcl: true, // create new, but you probably should reuse one
|
|
69
|
+
redirects,
|
|
70
|
+
pipeline: {
|
|
71
|
+
sourceStage: {
|
|
72
|
+
...repository,
|
|
73
|
+
branch: 'develop'
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
new WebStaticServerlessStage(app, 'prd', {
|
|
79
|
+
env,
|
|
80
|
+
stageName: 'myproject-v1-prd',
|
|
81
|
+
tags,
|
|
82
|
+
envType: 'PROD',
|
|
83
|
+
description: 'My Project PRD',
|
|
84
|
+
hostedZone: 'myproject.com',
|
|
85
|
+
domainName: 'www.myproject.com',
|
|
86
|
+
webAcl: 'ARN',
|
|
87
|
+
// webAcl: { path: '/my/webacl/arn' },
|
|
88
|
+
// webAcl: true, // create new, but you probably should reuse one
|
|
89
|
+
redirects,
|
|
90
|
+
pipeline: {
|
|
91
|
+
sourceStage: {
|
|
92
|
+
...repository,
|
|
93
|
+
branch: 'prod'
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
});
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
Win
|
|
101
|
+
```
|
|
102
|
+
cdk deploy dev/*
|
|
103
|
+
```
|
|
104
|
+
```
|
|
105
|
+
cdk deploy prd/*
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
MacOS
|
|
109
|
+
```
|
|
110
|
+
cdk deploy "dev/*"
|
|
111
|
+
```
|
|
112
|
+
```
|
|
113
|
+
cdk deploy "prd/*"
|
|
114
|
+
```
|
package/samples/app.ts
ADDED
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import * as cdk from 'aws-cdk-lib/core';
|
|
3
|
+
import { getEnv } from '@inizioevoke/evosynth';
|
|
4
|
+
import { CloudFrontFunctionRedirects } from '@inizioevoke/evosynth/constructs/cloudfront';
|
|
5
|
+
import { WebStaticServerlessStage } from '@inizioevoke/evosynth/stages/web-static-serverless-stage';
|
|
6
|
+
import { AddSourceStageParams } from '@inizioevoke/evosynth/constructs/codepipeline';
|
|
7
|
+
|
|
8
|
+
const app = new cdk.App();
|
|
9
|
+
|
|
10
|
+
const env = getEnv('us-east-1');
|
|
11
|
+
// const env = getEnv('000000000000');
|
|
12
|
+
// const env = getEnv({ account: '000000000000', region: 'us-east-1' });
|
|
13
|
+
|
|
14
|
+
const tags: Record<string, string> = {
|
|
15
|
+
'Account': 'Account Name',
|
|
16
|
+
'Brand': 'Brand Name'
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
const repository: Omit<AddSourceStageParams, 'branch'> = {
|
|
20
|
+
codestarConnection: { path: '/evosynth/provider/codeconnection' },
|
|
21
|
+
// codestarConnection: 'ARN',
|
|
22
|
+
repoOwner: 'orgname',
|
|
23
|
+
repo: 'myproject-v1'
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
const redirects: CloudFrontFunctionRedirects = {
|
|
27
|
+
paths: {
|
|
28
|
+
'/hello': '/world',
|
|
29
|
+
'/redirect': ['/here', 302]
|
|
30
|
+
}
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
new WebStaticServerlessStage(app, 'dev', {
|
|
34
|
+
env,
|
|
35
|
+
stageName: 'myproject-v1-dev',
|
|
36
|
+
tags,
|
|
37
|
+
envType: 'NOT_PROD',
|
|
38
|
+
description: 'My Project DEV',
|
|
39
|
+
hostedZone: 'evodev.net',
|
|
40
|
+
domainName: 'myproject-v1.evodev.net',
|
|
41
|
+
basicAuth: { path: '/evosynth/web/security/basicauth' },
|
|
42
|
+
// basicAuth: {
|
|
43
|
+
// user: 'user',
|
|
44
|
+
// password: 'password'
|
|
45
|
+
// },
|
|
46
|
+
// basicAuth: 'dXNlcjpwYXNzd29yZA==',
|
|
47
|
+
webAcl: { path: '/evosynth/web/security/webacl' },
|
|
48
|
+
// webAcl: 'ARN',
|
|
49
|
+
// webAcl: true, // create new, but you probably should reuse one,
|
|
50
|
+
redirects,
|
|
51
|
+
pipeline: {
|
|
52
|
+
sourceStage: {
|
|
53
|
+
...repository,
|
|
54
|
+
branch: 'develop'
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
new WebStaticServerlessStage(app, 'uat', {
|
|
60
|
+
env,
|
|
61
|
+
stageName: 'myproject-v1-uat',
|
|
62
|
+
tags,
|
|
63
|
+
envType: 'NOT_PROD',
|
|
64
|
+
description: 'My Project UAT',
|
|
65
|
+
hostedZone: 'evostg.net',
|
|
66
|
+
domainName: 'myproject-v1.evostg.net',
|
|
67
|
+
basicAuth: { path: '/evosynth/web/security/basicauth' },
|
|
68
|
+
// basicAuth: {
|
|
69
|
+
// user: 'user',
|
|
70
|
+
// password: 'password'
|
|
71
|
+
// },
|
|
72
|
+
// basicAuth: 'dXNlcjpwYXNzd29yZA==',
|
|
73
|
+
webAcl: { path: '/evosynth/web/security/webacl' },
|
|
74
|
+
// webAcl: 'ARN',
|
|
75
|
+
// webAcl: true, // create new, but you probably should reuse one,
|
|
76
|
+
redirects,
|
|
77
|
+
pipeline: {
|
|
78
|
+
sourceStage: {
|
|
79
|
+
...repository,
|
|
80
|
+
branch: 'uat'
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
new WebStaticServerlessStage(app, 'prd', {
|
|
86
|
+
env,
|
|
87
|
+
stageName: 'myproject-v1-prd',
|
|
88
|
+
tags,
|
|
89
|
+
envType: 'PROD',
|
|
90
|
+
description: 'My Project PRD',
|
|
91
|
+
hostedZone: 'myproject.com',
|
|
92
|
+
domainName: 'www.myproject.com',
|
|
93
|
+
webAcl: { path: '/evosynth/web/security/webacl' },
|
|
94
|
+
// webAcl: 'ARN',
|
|
95
|
+
// webAcl: true, // create new, but you probably should reuse one
|
|
96
|
+
redirects,
|
|
97
|
+
pipeline: {
|
|
98
|
+
sourceStage: {
|
|
99
|
+
...repository,
|
|
100
|
+
branch: 'prod'
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
});
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
version: 0.2
|
|
2
|
+
|
|
3
|
+
env:
|
|
4
|
+
variables:
|
|
5
|
+
# NODE_ENV: production
|
|
6
|
+
S3_BUCKET_NAME:
|
|
7
|
+
CLOUDFRONT_DISTRIBUTION_ID:
|
|
8
|
+
|
|
9
|
+
phases:
|
|
10
|
+
install:
|
|
11
|
+
runtime-versions:
|
|
12
|
+
nodejs: 24
|
|
13
|
+
commands:
|
|
14
|
+
- npm install
|
|
15
|
+
build:
|
|
16
|
+
commands:
|
|
17
|
+
- npm run build
|
|
18
|
+
post_build:
|
|
19
|
+
commands:
|
|
20
|
+
- '[ ${CODEBUILD_BUILD_SUCCEEDING:-0} -eq 1 ] || exit 1'
|
|
21
|
+
- echo "Syncing to S3"
|
|
22
|
+
- aws s3 sync dist s3://${S3_BUCKET_NAME}/ --delete
|
|
23
|
+
- echo "Invalidating CloudFront"
|
|
24
|
+
- aws cloudfront create-invalidation --distribution-id ${CLOUDFRONT_DISTRIBUTION_ID} --paths "/*"
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { RemovalPolicy } from "aws-cdk-lib";
|
|
2
|
+
import { Construct } from 'constructs';
|
|
3
|
+
import { Certificate, CertificateValidation } from "aws-cdk-lib/aws-certificatemanager";
|
|
4
|
+
import { IHostedZone } from "aws-cdk-lib/aws-route53";
|
|
5
|
+
import { tagResource } from "../lib/tags.ts";
|
|
6
|
+
import { ResourceProps } from "../lib/types.js";
|
|
7
|
+
|
|
8
|
+
export interface SSLCertificateProps extends ResourceProps {
|
|
9
|
+
hostedZone: IHostedZone;
|
|
10
|
+
domainName: string;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export class SSLCertificate extends Construct {
|
|
14
|
+
public readonly certificate: Certificate;
|
|
15
|
+
|
|
16
|
+
constructor(scope: Construct, id: string, props: SSLCertificateProps) {
|
|
17
|
+
super(scope, id);
|
|
18
|
+
|
|
19
|
+
this.certificate = new Certificate(this, `Certificate-${props.domainName}`, {
|
|
20
|
+
domainName: props.domainName,
|
|
21
|
+
validation: CertificateValidation.fromDns(props.hostedZone),
|
|
22
|
+
});
|
|
23
|
+
tagResource(this.certificate, props.tags);
|
|
24
|
+
if (typeof props.destroy == 'boolean') {
|
|
25
|
+
this.certificate.applyRemovalPolicy(props.destroy ? RemovalPolicy.DESTROY : RemovalPolicy.RETAIN);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
}
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
import { resolve } from 'node:path';
|
|
2
|
+
import { readFileSync } from 'node:fs';
|
|
3
|
+
import { RemovalPolicy } from "aws-cdk-lib";
|
|
4
|
+
import { Construct } from 'constructs';
|
|
5
|
+
import { ICertificate } from 'aws-cdk-lib/aws-certificatemanager';
|
|
6
|
+
import { BehaviorOptions, Distribution, Function, FunctionCode, FunctionRuntime, PriceClass } from 'aws-cdk-lib/aws-cloudfront';
|
|
7
|
+
import { tagResource } from '../lib/tags.js';
|
|
8
|
+
import { parseCsvRedirects } from '../lib/csv-redirects.js';
|
|
9
|
+
import type { ResourceProps } from '../lib/types.js';
|
|
10
|
+
|
|
11
|
+
const __dirname = import.meta.dirname;
|
|
12
|
+
|
|
13
|
+
export interface CloudFrontDistributionProps extends ResourceProps {
|
|
14
|
+
domainName: string;
|
|
15
|
+
description?: string;
|
|
16
|
+
defaultBehavior: BehaviorOptions;
|
|
17
|
+
additionalBehaviors?: Record<string, BehaviorOptions>;
|
|
18
|
+
priceClass?: 'PRICE_CLASS_ALL' | 'PRICE_CLASS_100' | 'PRICE_CLASS_200' | 'NOT_PROD' | 'PROD';
|
|
19
|
+
certificate: ICertificate;
|
|
20
|
+
webAclId?: string;
|
|
21
|
+
}
|
|
22
|
+
export class CloudFrontDistribution extends Construct {
|
|
23
|
+
public readonly distribution: Distribution;
|
|
24
|
+
|
|
25
|
+
constructor(scope: Construct, id: string, props: CloudFrontDistributionProps) {
|
|
26
|
+
super(scope, id);
|
|
27
|
+
|
|
28
|
+
let priceClass = PriceClass.PRICE_CLASS_ALL;
|
|
29
|
+
switch (props.priceClass) {
|
|
30
|
+
case 'NOT_PROD':
|
|
31
|
+
case 'PRICE_CLASS_100':
|
|
32
|
+
priceClass = PriceClass.PRICE_CLASS_100;
|
|
33
|
+
break;
|
|
34
|
+
case 'PRICE_CLASS_200':
|
|
35
|
+
priceClass = PriceClass.PRICE_CLASS_200;
|
|
36
|
+
break;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
this.distribution = new Distribution(this, `${id}|CloudFrontDistribution`, {
|
|
40
|
+
comment: props.description,
|
|
41
|
+
defaultBehavior: props.defaultBehavior,
|
|
42
|
+
additionalBehaviors: props.additionalBehaviors,
|
|
43
|
+
domainNames: [props.domainName],
|
|
44
|
+
certificate: props.certificate,
|
|
45
|
+
priceClass,
|
|
46
|
+
webAclId: props.webAclId
|
|
47
|
+
});
|
|
48
|
+
tagResource(this.distribution, props.tags);
|
|
49
|
+
if (typeof props.destroy == 'boolean') {
|
|
50
|
+
this.distribution.applyRemovalPolicy(props.destroy ? RemovalPolicy.DESTROY : RemovalPolicy.RETAIN);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export type HttpRedirectCode = 301 | 302 | 303 | 307 | 308;
|
|
56
|
+
export type CloudFrontFunctionRedirectPaths = Record<string, string | [string, HttpRedirectCode]>;
|
|
57
|
+
|
|
58
|
+
export interface CloudFrontFunctionRedirects {
|
|
59
|
+
trailingSlash?: boolean;
|
|
60
|
+
defaultDocument?: string;
|
|
61
|
+
redirectDefaultDocument?: boolean;
|
|
62
|
+
paths?: CloudFrontFunctionRedirectPaths;
|
|
63
|
+
pathsCsvFile?: string;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export interface ViewerRequestResponseFunctionProps extends ResourceProps {
|
|
67
|
+
functionName?: string;
|
|
68
|
+
defaultDocument?: string;
|
|
69
|
+
redirects?: CloudFrontFunctionRedirects;
|
|
70
|
+
redirectsCsvPath?: string;
|
|
71
|
+
credentials?: string;
|
|
72
|
+
code: string;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
export interface DefaultDocFunctionProps extends ResourceProps, Pick<ViewerRequestResponseFunctionProps, 'functionName' | 'defaultDocument' | 'redirects' | 'redirectsCsvPath'> {
|
|
76
|
+
functionName?: string;
|
|
77
|
+
defaultDocument?: string;
|
|
78
|
+
redirects?: CloudFrontFunctionRedirects;
|
|
79
|
+
redirectsCsvPath?: string;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
export interface BasicAuthDefaultDocFunctionProps extends DefaultDocFunctionProps {
|
|
83
|
+
credentials: string;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
export interface CloudFrontFunctionProps extends ResourceProps {
|
|
88
|
+
code: string;
|
|
89
|
+
functionName?: string;
|
|
90
|
+
runtime?: 'JS_1_0' | 'JS_2_0';
|
|
91
|
+
}
|
|
92
|
+
export class CloudFrontFunction extends Construct {
|
|
93
|
+
public readonly fn: Function;
|
|
94
|
+
|
|
95
|
+
constructor(scope: Construct, id: string, props: CloudFrontFunctionProps) {
|
|
96
|
+
super(scope, id);
|
|
97
|
+
|
|
98
|
+
this.fn = new Function(this, 'CloudFrontFunction', {
|
|
99
|
+
functionName: props.functionName,
|
|
100
|
+
runtime: FunctionRuntime[props.runtime ?? 'JS_2_0'],
|
|
101
|
+
code: FunctionCode.fromInline(props.code)
|
|
102
|
+
});
|
|
103
|
+
tagResource(this.fn, props.tags);
|
|
104
|
+
|
|
105
|
+
if (typeof props.destroy == 'boolean') {
|
|
106
|
+
this.fn.applyRemovalPolicy(props.destroy ? RemovalPolicy.DESTROY : RemovalPolicy.RETAIN);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
static createDefaultDoc(scope: Construct, props: DefaultDocFunctionProps) {
|
|
111
|
+
const code = readFileSync(resolve(__dirname, '../templates/cffn-default-doc.js'), 'utf8');
|
|
112
|
+
|
|
113
|
+
return this.createViewerRequestResponseFunction(scope, 'DefaultDocFunction', {
|
|
114
|
+
...props,
|
|
115
|
+
credentials: '',
|
|
116
|
+
code
|
|
117
|
+
});
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
static createBasicAuthDefaultDoc(scope: Construct, props: BasicAuthDefaultDocFunctionProps) {
|
|
121
|
+
const code = readFileSync(resolve(__dirname, '../templates/cffn-default-doc-basic-auth.js'), 'utf8');
|
|
122
|
+
|
|
123
|
+
return this.createViewerRequestResponseFunction(scope, 'BasicAuthDefaultDocFunction', {
|
|
124
|
+
...props,
|
|
125
|
+
code
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
static createViewerRequestResponseFunction(scope: Construct, id: string | null, props: ViewerRequestResponseFunctionProps) {
|
|
131
|
+
const csvPath = props.redirects?.pathsCsvFile ?? props.redirectsCsvPath;
|
|
132
|
+
const redirectPaths = csvPath ? parseCsvRedirects(csvPath) : (props.redirects?.paths ?? {});
|
|
133
|
+
const hasRedirects = Object.keys(redirectPaths).length > 0;
|
|
134
|
+
|
|
135
|
+
props.code = props.code
|
|
136
|
+
.replace('__DEFAULT_DOCUMENT__', props.defaultDocument ?? 'index.html')
|
|
137
|
+
.replace('__CREDENTIALS__', props.credentials ?? 'Og==')
|
|
138
|
+
.replace('__REDIRECTS__', hasRedirects ? JSON.stringify(redirectPaths) : 'null')
|
|
139
|
+
.replace('__TRAILING_SLASH__', props.redirects?.trailingSlash?.toString() ?? 'null')
|
|
140
|
+
.replace('__REDIR_DEF_DOC__', props.redirects?.redirectDefaultDocument?.toString() ?? 'false');
|
|
141
|
+
|
|
142
|
+
return new CloudFrontFunction(scope, id || 'ViewerRequestResponseFunction', {
|
|
143
|
+
functionName: props.functionName,
|
|
144
|
+
runtime: 'JS_2_0',
|
|
145
|
+
code: props.code
|
|
146
|
+
});
|
|
147
|
+
}
|
|
148
|
+
}
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
import { RemovalPolicy } from "aws-cdk-lib";
|
|
2
|
+
import { Construct } from 'constructs';
|
|
3
|
+
import { BuildEnvironmentVariableType, BuildSpec, ComputeType, IBuildImage, LinuxBuildImage, PipelineProject } from 'aws-cdk-lib/aws-codebuild';
|
|
4
|
+
import { Repository } from "aws-cdk-lib/aws-ecr";
|
|
5
|
+
import { PolicyStatement } from 'aws-cdk-lib/aws-iam';
|
|
6
|
+
import { Bucket } from 'aws-cdk-lib/aws-s3';
|
|
7
|
+
import { IStringParameter, StringParameter } from 'aws-cdk-lib/aws-ssm';
|
|
8
|
+
import { tagResource } from "../lib/tags.js";
|
|
9
|
+
import { ResourceProps } from "../lib/types.js";
|
|
10
|
+
import { SsmStringParameter as _SsmStringParameter } from '../lib/types.ts';
|
|
11
|
+
|
|
12
|
+
export type EnvironmentVariables = Record<string, { value: any, type?: BuildEnvironmentVariableType }>;
|
|
13
|
+
|
|
14
|
+
interface SsmStringParameter extends _SsmStringParameter {
|
|
15
|
+
encrypted?: boolean;
|
|
16
|
+
}
|
|
17
|
+
export interface EcrBuildImage {
|
|
18
|
+
repository: string;
|
|
19
|
+
tag: string;
|
|
20
|
+
}
|
|
21
|
+
export interface CodeBuildPipelineProjectProps extends ResourceProps {
|
|
22
|
+
projectName?: string;
|
|
23
|
+
buildSpec?: string | object;
|
|
24
|
+
buildImage?: ('STANDARD_5_0' | 'STANDARD_6_0' | 'STANDARD_7_0') | IBuildImage;
|
|
25
|
+
ecrBuildImage?: EcrBuildImage,
|
|
26
|
+
computeType?: ('SMALL' | 'MEDIUM' | 'LARGE' | 'X_LARGE' | 'X2_LARGE') | string;
|
|
27
|
+
environmentVariables?: EnvironmentVariables;
|
|
28
|
+
cloudFrontDistributionArn?: string;
|
|
29
|
+
s3BucketName?: string;
|
|
30
|
+
ssmParameters?: SsmStringParameter[];
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export class CodeBuildPipelineProject extends Construct {
|
|
34
|
+
public readonly project: PipelineProject;
|
|
35
|
+
|
|
36
|
+
constructor(scope: Construct, id: string, props: CodeBuildPipelineProjectProps) {
|
|
37
|
+
super(scope, id);
|
|
38
|
+
|
|
39
|
+
if (!props.environmentVariables) {
|
|
40
|
+
props.environmentVariables = {};
|
|
41
|
+
}
|
|
42
|
+
if (props.cloudFrontDistributionArn && !props.environmentVariables.CLOUDFRONT_DISTRIBUTION_ID) {
|
|
43
|
+
props.environmentVariables.CLOUDFRONT_DISTRIBUTION_ID = {
|
|
44
|
+
value: props.cloudFrontDistributionArn.split('/').pop()
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
if (props.s3BucketName && !props.environmentVariables.S3_BUCKET_NAME) {
|
|
49
|
+
props.environmentVariables.S3_BUCKET_NAME = {
|
|
50
|
+
value: props.s3BucketName
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
let buildImage: IBuildImage | undefined = undefined;
|
|
55
|
+
if (props.ecrBuildImage) {
|
|
56
|
+
const repository = props.ecrBuildImage.repository.startsWith('arn:aws') ?
|
|
57
|
+
Repository.fromRepositoryArn(this, 'EcrRepository', props.ecrBuildImage.repository) :
|
|
58
|
+
Repository.fromRepositoryName(this, 'EcrRepository', props.ecrBuildImage.repository);
|
|
59
|
+
buildImage = LinuxBuildImage.fromEcrRepository(repository, props.ecrBuildImage.tag);
|
|
60
|
+
} else if (props.buildImage) {
|
|
61
|
+
if (typeof props.buildImage == 'string') {
|
|
62
|
+
buildImage = LinuxBuildImage[props.buildImage ?? 'STANDARD_7_0']
|
|
63
|
+
} else {
|
|
64
|
+
buildImage = props.buildImage as IBuildImage;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
this.project = new PipelineProject(this, 'CodeBuildPipelineProjectProps', {
|
|
69
|
+
projectName: props.projectName,
|
|
70
|
+
buildSpec: props.buildSpec ? typeof props.buildSpec == 'object' ? BuildSpec.fromObjectToYaml(props.buildSpec) : BuildSpec.fromSourceFilename(props.buildSpec ?? 'buildspec.yml') : undefined,
|
|
71
|
+
environment: {
|
|
72
|
+
buildImage: buildImage ?? LinuxBuildImage.STANDARD_7_0,
|
|
73
|
+
computeType: ComputeType[props.computeType as (keyof typeof ComputeType | undefined) ?? 'SMALL'],
|
|
74
|
+
environmentVariables: props.environmentVariables
|
|
75
|
+
}
|
|
76
|
+
});
|
|
77
|
+
tagResource(this.project, props.tags);
|
|
78
|
+
if (typeof props.destroy == 'boolean') {
|
|
79
|
+
this.project.applyRemovalPolicy(props.destroy ? RemovalPolicy.DESTROY : RemovalPolicy.RETAIN);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// Add CloudWatch log permissions
|
|
83
|
+
this.project.addToRolePolicy(new PolicyStatement({
|
|
84
|
+
actions: [
|
|
85
|
+
'logs:CreateLogGroup',
|
|
86
|
+
'logs:CreateLogStream',
|
|
87
|
+
'logs:PutLogEvents'
|
|
88
|
+
],
|
|
89
|
+
resources: [this.project.projectArn]
|
|
90
|
+
}));
|
|
91
|
+
|
|
92
|
+
// Allow the project to create an invalidation
|
|
93
|
+
if (props.cloudFrontDistributionArn) {
|
|
94
|
+
this.project.addToRolePolicy(new PolicyStatement({
|
|
95
|
+
actions: ['cloudfront:CreateInvalidation'],
|
|
96
|
+
resources: [props.cloudFrontDistributionArn]
|
|
97
|
+
}));
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// Allow the project to sync files to S3
|
|
101
|
+
if (props.s3BucketName) {
|
|
102
|
+
const s3Bucket = Bucket.fromBucketName(this, 'S3BucketByName', props.s3BucketName);
|
|
103
|
+
s3Bucket.grantReadWrite(this.project);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
if (props.ssmParameters) {
|
|
107
|
+
for (const param of props.ssmParameters) {
|
|
108
|
+
let strParam: IStringParameter;
|
|
109
|
+
if (param.encrypted) {
|
|
110
|
+
strParam = StringParameter.fromSecureStringParameterAttributes(this, param.path, {
|
|
111
|
+
parameterName: param.path,
|
|
112
|
+
version: param.version
|
|
113
|
+
});
|
|
114
|
+
} else {
|
|
115
|
+
strParam = StringParameter.fromStringParameterAttributes(this, param.path, {
|
|
116
|
+
parameterName: param.path,
|
|
117
|
+
version: param.version
|
|
118
|
+
});
|
|
119
|
+
}
|
|
120
|
+
strParam.grantRead(this.project);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
}
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import { RemovalPolicy, PhysicalName, Aws } from "aws-cdk-lib";
|
|
2
|
+
import { Construct } from 'constructs';
|
|
3
|
+
import { IProject } from "aws-cdk-lib/aws-codebuild";
|
|
4
|
+
import { Artifact, Pipeline } from "aws-cdk-lib/aws-codepipeline";
|
|
5
|
+
import { CodeBuildAction, CodeStarConnectionsSourceAction } from "aws-cdk-lib/aws-codepipeline-actions";
|
|
6
|
+
import { Bucket } from 'aws-cdk-lib/aws-s3';
|
|
7
|
+
import { StringParameter } from 'aws-cdk-lib/aws-ssm';
|
|
8
|
+
|
|
9
|
+
import type { EnvironmentVariables } from './codebuild.js';
|
|
10
|
+
import { tagResource } from "../lib/tags.js";
|
|
11
|
+
import { ResourceProps, SsmStringParameter } from "../lib/types.js";
|
|
12
|
+
|
|
13
|
+
export interface AddSourceStageParams {
|
|
14
|
+
stageName?: string;
|
|
15
|
+
codestarConnection: string | SsmStringParameter;
|
|
16
|
+
repoOwner: string;
|
|
17
|
+
repo: string;
|
|
18
|
+
branch: string;
|
|
19
|
+
artifactFormat: 'default' | 'fullClone'
|
|
20
|
+
}
|
|
21
|
+
export interface AddBuildStageParams {
|
|
22
|
+
stageName?: string;
|
|
23
|
+
codeBuildProject: IProject;
|
|
24
|
+
sourceArtifact: Artifact;
|
|
25
|
+
environmentVariables?: EnvironmentVariables;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export interface CodePipelineProjectProps extends ResourceProps {
|
|
29
|
+
pipelineName?: string;
|
|
30
|
+
}
|
|
31
|
+
export class CodePipelineProject extends Construct {
|
|
32
|
+
public readonly s3Bucket: Bucket;
|
|
33
|
+
public readonly pipeline: Pipeline;
|
|
34
|
+
|
|
35
|
+
constructor(scope: Construct, id: string, props: CodePipelineProjectProps) {
|
|
36
|
+
super(scope, id);
|
|
37
|
+
|
|
38
|
+
this.s3Bucket = new Bucket(this, 'CodePipelineArtifactBucket', {
|
|
39
|
+
bucketName: PhysicalName.GENERATE_IF_NEEDED,
|
|
40
|
+
removalPolicy: props.destroy !== undefined ? props.destroy ? RemovalPolicy.DESTROY : RemovalPolicy.RETAIN : undefined,
|
|
41
|
+
autoDeleteObjects: props.destroy === true ? true : undefined
|
|
42
|
+
});
|
|
43
|
+
tagResource(this.s3Bucket, props.tags);
|
|
44
|
+
|
|
45
|
+
this.pipeline = new Pipeline(this, 'CodePipelineProject', {
|
|
46
|
+
pipelineName: props.pipelineName,
|
|
47
|
+
artifactBucket: this.s3Bucket
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
tagResource(this.pipeline, props.tags);
|
|
51
|
+
if (typeof props.destroy == 'boolean') {
|
|
52
|
+
this.pipeline.applyRemovalPolicy(props.destroy ? RemovalPolicy.DESTROY : RemovalPolicy.RETAIN);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
addSourceStage(params: AddSourceStageParams): Artifact {
|
|
57
|
+
const artifact = new Artifact();
|
|
58
|
+
|
|
59
|
+
const connectionArn: string = typeof params.codestarConnection == 'string' ?
|
|
60
|
+
params.codestarConnection :
|
|
61
|
+
StringParameter.valueForStringParameter(this, (params.codestarConnection as SsmStringParameter).path, (params.codestarConnection as SsmStringParameter).version);
|
|
62
|
+
|
|
63
|
+
this.pipeline.addStage({
|
|
64
|
+
stageName: params.stageName ?? 'Source',
|
|
65
|
+
actions: [
|
|
66
|
+
new CodeStarConnectionsSourceAction({
|
|
67
|
+
actionName: params.stageName ?? 'Source',
|
|
68
|
+
output: artifact,
|
|
69
|
+
connectionArn,
|
|
70
|
+
owner: params.repoOwner,
|
|
71
|
+
repo: params.repo,
|
|
72
|
+
branch: params.branch,
|
|
73
|
+
codeBuildCloneOutput: params.artifactFormat === 'fullClone'
|
|
74
|
+
})
|
|
75
|
+
]
|
|
76
|
+
});
|
|
77
|
+
return artifact;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
addBuildStage(params: AddBuildStageParams) {
|
|
81
|
+
this.pipeline.addStage({
|
|
82
|
+
stageName: params.stageName ?? 'Build',
|
|
83
|
+
actions: [
|
|
84
|
+
new CodeBuildAction({
|
|
85
|
+
actionName: params.stageName ?? 'Build',
|
|
86
|
+
project: params.codeBuildProject,
|
|
87
|
+
input: params.sourceArtifact,
|
|
88
|
+
environmentVariables: params.environmentVariables
|
|
89
|
+
})
|
|
90
|
+
]
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { IDistribution } from "aws-cdk-lib/aws-cloudfront";
|
|
2
|
+
import { ARecord, AaaaRecord, HostedZone, IHostedZone, RecordTarget } from "aws-cdk-lib/aws-route53";
|
|
3
|
+
import { CloudFrontTarget } from "aws-cdk-lib/aws-route53-targets";
|
|
4
|
+
import { Construct } from "constructs";
|
|
5
|
+
|
|
6
|
+
export interface CloudFrontAliasProps {
|
|
7
|
+
hostedZone: IHostedZone;
|
|
8
|
+
domainName: string;
|
|
9
|
+
distribution: IDistribution;
|
|
10
|
+
}
|
|
11
|
+
export class CloudFrontAlias extends Construct {
|
|
12
|
+
public readonly aRecord: ARecord;
|
|
13
|
+
public readonly aaaaRecord: AaaaRecord;
|
|
14
|
+
|
|
15
|
+
constructor(scope: Construct, id: string, props: CloudFrontAliasProps) {
|
|
16
|
+
super(scope, id);
|
|
17
|
+
|
|
18
|
+
this.aRecord = new ARecord(this, `ARecord-${props.domainName}`, {
|
|
19
|
+
zone: props.hostedZone,
|
|
20
|
+
target: RecordTarget.fromAlias(new CloudFrontTarget(props.distribution)),
|
|
21
|
+
recordName: props.domainName
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
this.aaaaRecord = new AaaaRecord(this, `AaaaRecord-${props.domainName}`, {
|
|
25
|
+
zone: props.hostedZone,
|
|
26
|
+
target: RecordTarget.fromAlias(new CloudFrontTarget(props.distribution)),
|
|
27
|
+
recordName: props.domainName
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export function getHostedZone(scope: Construct, id: string, domainName: string) {
|
|
33
|
+
return HostedZone.fromLookup(scope, `getHostedZone-${domainName}`, { domainName });
|
|
34
|
+
}
|