@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.
Files changed (71) hide show
  1. package/.editorconfig +12 -0
  2. package/dist/constructs/certificatemanager.d.ts +12 -0
  3. package/dist/constructs/certificatemanager.js +19 -0
  4. package/dist/constructs/cloudfront.d.ts +55 -0
  5. package/dist/constructs/cloudfront.js +84 -0
  6. package/dist/constructs/codebuild.d.ts +31 -0
  7. package/dist/constructs/codebuild.js +95 -0
  8. package/dist/constructs/codepipeline.d.ts +30 -0
  9. package/dist/constructs/codepipeline.js +63 -0
  10. package/dist/constructs/route53.d.ts +14 -0
  11. package/dist/constructs/route53.js +24 -0
  12. package/dist/constructs/s3.d.ts +11 -0
  13. package/dist/constructs/s3.js +17 -0
  14. package/dist/constructs/ssm.d.ts +9 -0
  15. package/dist/constructs/ssm.js +17 -0
  16. package/dist/constructs/waf.d.ts +21 -0
  17. package/dist/constructs/waf.js +89 -0
  18. package/dist/index.d.ts +10 -0
  19. package/dist/index.js +26 -0
  20. package/dist/lib/const.d.ts +1 -0
  21. package/dist/lib/const.js +2 -0
  22. package/dist/lib/csv-redirects.d.ts +3 -0
  23. package/dist/lib/csv-redirects.js +124 -0
  24. package/dist/lib/tags.d.ts +19 -0
  25. package/dist/lib/tags.js +47 -0
  26. package/dist/lib/types.d.ts +14 -0
  27. package/dist/lib/types.js +2 -0
  28. package/dist/lib/utils.d.ts +2 -0
  29. package/dist/lib/utils.js +7 -0
  30. package/dist/lib/version.d.ts +1 -0
  31. package/dist/lib/version.js +5 -0
  32. package/dist/stacks/web-static-serverless/codepipeline-stack.d.ts +17 -0
  33. package/dist/stacks/web-static-serverless/codepipeline-stack.js +28 -0
  34. package/dist/stacks/web-static-serverless/web-global-stack.d.ts +43 -0
  35. package/dist/stacks/web-static-serverless/web-global-stack.js +142 -0
  36. package/dist/stages/str8r-clm-stage.d.ts +17 -0
  37. package/dist/stages/str8r-clm-stage.js +41 -0
  38. package/dist/stages/web-static-serverless-stage.d.ts +19 -0
  39. package/dist/stages/web-static-serverless-stage.js +51 -0
  40. package/dist/templates/cffn-default-doc-basic-auth.js +155 -0
  41. package/dist/templates/cffn-default-doc.js +132 -0
  42. package/inizioevoke-evosynth.code-workspace +8 -0
  43. package/package.json +24 -0
  44. package/readme.md +114 -0
  45. package/samples/app.ts +103 -0
  46. package/samples/codebuild/buildspec.yml +24 -0
  47. package/samples/config/redirects.csv +6 -0
  48. package/src/constructs/certificatemanager.ts +28 -0
  49. package/src/constructs/cloudfront.ts +148 -0
  50. package/src/constructs/codebuild.ts +124 -0
  51. package/src/constructs/codepipeline.ts +93 -0
  52. package/src/constructs/route53.ts +34 -0
  53. package/src/constructs/s3.ts +25 -0
  54. package/src/constructs/ssm.ts +24 -0
  55. package/src/constructs/waf.ts +118 -0
  56. package/src/index.ts +35 -0
  57. package/src/lib/const.ts +1 -0
  58. package/src/lib/csv-redirects.ts +139 -0
  59. package/src/lib/tags.ts +55 -0
  60. package/src/lib/types.ts +17 -0
  61. package/src/lib/utils.ts +7 -0
  62. package/src/lib/version.ts +6 -0
  63. package/src/stacks/web-static-serverless/codepipeline-stack.ts +42 -0
  64. package/src/stacks/web-static-serverless/web-global-stack.ts +184 -0
  65. package/src/stages/str8r-clm-stage.ts +57 -0
  66. package/src/stages/web-static-serverless-stage.ts +69 -0
  67. package/src/templates/cffn-default-doc-basic-auth.js +155 -0
  68. package/src/templates/cffn-default-doc.js +132 -0
  69. package/tasks/clean.js +7 -0
  70. package/tasks/copy-templates.js +13 -0
  71. 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,6 @@
1
+ source,destination,http_code
2
+ /old-page,/new-page,301
3
+ /temp-redirect,/other-page,302
4
+ /moa,/moa-video,307
5
+ /legacy,/new,308
6
+ /about-us,/about,
@@ -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
+ }