@byaga/cdk-patterns 0.6.1 → 0.8.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 (62) hide show
  1. package/.fleet/run.json +11 -0
  2. package/lib/ApiCertificate.d.ts +2 -2
  3. package/lib/ApiCertificate.js +4 -5
  4. package/lib/CognitoApiGatewayAuthorizer.d.ts +19 -0
  5. package/lib/CognitoApiGatewayAuthorizer.js +24 -0
  6. package/lib/DeployStack.d.ts +20 -0
  7. package/lib/DeployStack.js +48 -0
  8. package/lib/DynamoDbTable.d.ts +12 -0
  9. package/lib/DynamoDbTable.js +34 -0
  10. package/lib/FunctionIntegration.d.ts +21 -0
  11. package/lib/FunctionIntegration.js +61 -0
  12. package/lib/ICorsConfig.d.ts +5 -0
  13. package/lib/ICorsConfig.js +2 -0
  14. package/lib/NodeJsLambda.d.ts +13 -0
  15. package/lib/NodeJsLambda.js +28 -0
  16. package/lib/Output.d.ts +5 -0
  17. package/lib/Output.js +13 -0
  18. package/lib/RestApi.d.ts +28 -0
  19. package/lib/RestApi.js +142 -0
  20. package/lib/Role.d.ts +2 -2
  21. package/lib/Role.js +2 -5
  22. package/lib/SsmParameter.d.ts +18 -0
  23. package/lib/SsmParameter.js +40 -0
  24. package/lib/StaticWebSite.d.ts +12 -0
  25. package/lib/StaticWebSite.js +107 -0
  26. package/lib/index.d.ts +7 -1
  27. package/lib/index.js +15 -3
  28. package/lib/methods/apply-honeycomb-to-lambda.d.ts +3 -0
  29. package/lib/methods/apply-honeycomb-to-lambda.js +22 -0
  30. package/lib/methods/build-node-source.d.ts +13 -0
  31. package/lib/methods/build-node-source.js +105 -0
  32. package/lib/methods/duration.d.ts +8 -0
  33. package/lib/methods/duration.js +22 -0
  34. package/lib/methods/empty-directory.d.ts +2 -0
  35. package/lib/methods/empty-directory.js +47 -0
  36. package/lib/methods/generate-hash.d.ts +15 -0
  37. package/lib/methods/generate-hash.js +59 -0
  38. package/lib/methods/get-source-directory.d.ts +4 -0
  39. package/lib/methods/get-source-directory.js +37 -0
  40. package/lib/methods/walk-directory.d.ts +14 -0
  41. package/lib/methods/walk-directory.js +48 -0
  42. package/package.json +4 -2
  43. package/src/ApiCertificate.ts +6 -6
  44. package/src/CognitoApiGatewayAuthorizer.ts +25 -0
  45. package/src/{IDeployStack.ts → DeployStack.ts} +4 -4
  46. package/src/DynamoDbTable.ts +40 -0
  47. package/src/FunctionIntegration.ts +83 -0
  48. package/src/ICorsConfig.ts +5 -0
  49. package/src/NodeJsLambda.ts +31 -0
  50. package/src/Output.ts +11 -0
  51. package/src/RestApi.ts +178 -0
  52. package/src/Role.ts +5 -7
  53. package/src/SsmParameter.ts +47 -0
  54. package/src/StaticWebSite.ts +99 -0
  55. package/src/index.ts +7 -1
  56. package/src/methods/apply-honeycomb-to-lambda.ts +22 -0
  57. package/src/methods/build-node-source.ts +97 -0
  58. package/src/methods/duration.ts +24 -0
  59. package/src/methods/empty-directory.ts +19 -0
  60. package/src/methods/generate-hash.ts +49 -0
  61. package/src/methods/get-source-directory.ts +11 -0
  62. package/src/methods/walk-directory.ts +30 -0
@@ -0,0 +1,25 @@
1
+ import { Construct } from 'constructs'
2
+ import { CfnAuthorizer, IAuthorizer } from 'aws-cdk-lib/aws-apigateway'
3
+ import { CfnAuthorizerProps } from 'aws-cdk-lib/aws-apigateway/lib/apigateway.generated'
4
+
5
+ /**
6
+ * Custom construct that implements a Cognito based API Gateway Authorizer.
7
+ *
8
+ * @see https://docs.aws.amazon.com/cdk/latest/guide/constructs.html#constructs_author
9
+ *
10
+ * @see https://docs.aws.amazon.com/cdk/api/latest/docs/@aws-cdk_aws-apigateway.CfnAuthorizer.html
11
+ * @see https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-apigateway-authorizer.html
12
+ * @see https://docs.aws.amazon.com/apigateway/latest/developerguide/apigateway-integrate-with-cognito.html
13
+ * @see https://docs.aws.amazon.com/apigateway/latest/developerguide/apigateway-enable-cognito-user-pool.html
14
+ *
15
+ * @see https://github.com/aws/aws-cdk/issues/5618#issuecomment-666922559
16
+ */
17
+ export class CognitoApiGatewayAuthorizer extends CfnAuthorizer implements IAuthorizer {
18
+ public readonly authorizerId: string
19
+
20
+ constructor(scope: Construct, id: string, props: CfnAuthorizerProps) {
21
+ super(scope, id, props)
22
+
23
+ this.authorizerId = this.ref
24
+ }
25
+ }
@@ -2,17 +2,17 @@ import {Stack} from 'aws-cdk-lib';
2
2
  import IStackArguments from './IStackArguments'
3
3
  import {IConstruct} from "constructs";
4
4
 
5
- export class IDeployStack extends Stack {
5
+ export class DeployStack extends Stack {
6
6
  registry: { [t: string]: { [n: string]: any } } = {}
7
7
  stage: string
8
8
  name: string
9
9
 
10
10
  genName(...name: string[]): string {
11
- return IDeployStack.genStackResourceName(this.name, this.stage, name.filter(n => !!n).join('-'))
11
+ return DeployStack.genStackResourceName(this.name, this.stage, name.filter(n => !!n).join('-'))
12
12
  }
13
13
 
14
14
  genId(...name: string[]): string {
15
- return IDeployStack.genStackResourceId(this.name, this.stage, name.filter(n => !!n).join('-'))
15
+ return DeployStack.genStackResourceId(this.name, this.stage, name.filter(n => !!n).join('-'))
16
16
  }
17
17
 
18
18
  static genStackResourceName(stackName: string, resource: string, stage = 'develop') {
@@ -53,4 +53,4 @@ export class IDeployStack extends Stack {
53
53
  return instance;
54
54
  }
55
55
  }
56
- export default IDeployStack
56
+ export default DeployStack
@@ -0,0 +1,40 @@
1
+ import {CfnOutput, RemovalPolicy} from 'aws-cdk-lib';
2
+ import {Table, BillingMode, AttributeType} from 'aws-cdk-lib/aws-dynamodb'
3
+ import {Effect, Policy, PolicyStatement} from "aws-cdk-lib/aws-iam";
4
+ import {DeployStack} from "./DeployStack"
5
+
6
+ interface DynamoDbTableConfig {
7
+ partitionKey: string,
8
+ sortKey?: string
9
+ }
10
+
11
+ export class DynamoDbTable extends Table {
12
+ getPolicy: Policy
13
+
14
+ constructor(stack: DeployStack, id: string, props: DynamoDbTableConfig) {
15
+ console.log('Creating DynamoDb Table', stack.genName(id, 'data-table'))
16
+ super(stack, stack.genId(id, 'data-table'), {
17
+ tableName: stack.genName(id, 'data-table'),
18
+ partitionKey: {name: props.partitionKey, type: AttributeType.STRING},
19
+ sortKey: props.sortKey ? {name: props.sortKey, type: AttributeType.STRING} : undefined,
20
+ billingMode: BillingMode.PAY_PER_REQUEST,
21
+ removalPolicy: RemovalPolicy.DESTROY
22
+ });
23
+ stack.set('dynamo', id, this)
24
+
25
+ new CfnOutput(stack, stack.genId(id, 'table'), {
26
+ value: this.tableName,
27
+ exportName: stack.genName(id, 'table')
28
+ })
29
+
30
+ this.getPolicy = new Policy(stack, stack.genId(id, 'get-policy'), {
31
+ statements: [
32
+ new PolicyStatement({
33
+ actions: ['dynamodb:GetItem'],
34
+ effect: Effect.ALLOW,
35
+ resources: [this.tableArn]
36
+ })
37
+ ]
38
+ })
39
+ }
40
+ }
@@ -0,0 +1,83 @@
1
+ import {DeployStack} from "./DeployStack";
2
+ import {Function, FunctionProps} from "aws-cdk-lib/aws-lambda";
3
+ import {
4
+ JsonSchema,
5
+ LambdaIntegration,
6
+ MethodOptions,
7
+ Model,
8
+ RequestValidator,
9
+ IResource
10
+ } from "aws-cdk-lib/aws-apigateway";
11
+ import {CfnOutput, Duration} from "aws-cdk-lib";
12
+ import {applyHoneycombToLambda} from "./methods/apply-honeycomb-to-lambda";
13
+ import {RetentionDays} from "aws-cdk-lib/aws-logs";
14
+ import {RestApi} from "./RestApi";
15
+
16
+ interface AddToApiOptions {
17
+ requestSchema?: JsonSchema,
18
+ methodOptions?: MethodOptions
19
+ }
20
+
21
+ export interface FunctionIntegrationProps {
22
+ funcProps: FunctionProps,
23
+ timeout?: Duration
24
+ memory?: number
25
+ }
26
+
27
+ export class FunctionIntegration extends Function {
28
+ stack: DeployStack
29
+ name: string
30
+
31
+ constructor(stack: DeployStack, id: string, options: FunctionIntegrationProps) {
32
+ const props = applyHoneycombToLambda(stack, {
33
+ functionName: stack.genName(id),
34
+ memorySize: options.memory || 256,
35
+ timeout: options.timeout || Duration.seconds(16),
36
+ logRetention: RetentionDays.ONE_WEEK,
37
+ ...options.funcProps
38
+ })
39
+
40
+ super(stack, stack.genId(id, 'lambda'), props);
41
+ this.stack = stack;
42
+ this.name = id;
43
+ stack.set('lambda', id, this)
44
+ new CfnOutput(stack, stack.genId(id, 'function-name'), {
45
+ value: this.functionName,
46
+ exportName: stack.genName(id, 'function-name')
47
+ });
48
+ }
49
+
50
+ attach(api: RestApi, httpMethod: string, path: string, props: AddToApiOptions) {
51
+ const resource: IResource = api.path(path)
52
+ const integration = new LambdaIntegration(this, {
53
+ requestTemplates: {
54
+ 'application/json': '{ "statusCode": "200" }'
55
+ }
56
+ })
57
+
58
+ let options: MethodOptions = props?.methodOptions || {}
59
+ const schema = props?.requestSchema
60
+ if (schema) options = applySchema(this.stack, this.name, schema, options, resource)
61
+
62
+ const method = resource.addMethod(httpMethod, integration, options)
63
+ }
64
+ }
65
+
66
+ function applySchema(stack: DeployStack, name: string, schema: JsonSchema, options: MethodOptions, path: IResource): MethodOptions {
67
+ return {
68
+ ...options,
69
+ requestValidator: new RequestValidator(stack, stack.genId(name, 'validator'), {
70
+ restApi: path.api,
71
+ requestValidatorName: stack.genName(name, 'validator'),
72
+ validateRequestBody: true
73
+ }),
74
+ requestModels: {
75
+ 'application/json': new Model(stack, stack.genId(name, 'model'), {
76
+ schema: schema,
77
+ contentType: 'application/json',
78
+ restApi: path.api,
79
+ modelName: stack.genId(name, 'model')
80
+ })
81
+ }
82
+ }
83
+ }
@@ -0,0 +1,5 @@
1
+ export interface ICorsConfig {
2
+ maxAge?: number
3
+ exposeHeaders?: string[],
4
+ allowOrigin?: string|string[]
5
+ }
@@ -0,0 +1,31 @@
1
+ import {Code, FunctionOptions, Runtime} from "aws-cdk-lib/aws-lambda"
2
+ import {DeployStack} from "./DeployStack";
3
+ import {FunctionIntegration} from "./FunctionIntegration";
4
+ import {buildNodeSource} from "./methods/build-node-source";
5
+ import {Duration} from "aws-cdk-lib";
6
+ import duration from "./methods/duration";
7
+
8
+ interface NodeFunctionProps {
9
+ funcProps?: FunctionOptions
10
+ timeout?: Duration
11
+ memory?: number
12
+ }
13
+
14
+ export class NodeJsLambda extends FunctionIntegration {
15
+ constructor(stack: DeployStack, id: string, options?: NodeFunctionProps) {
16
+ console.log('Defining Node Lambda', id)
17
+ const done = duration()
18
+ const {buildDir} = buildNodeSource('lambda', id)
19
+ console.log('Total Build Duration (ms)', done())
20
+
21
+ super(stack, id, {
22
+ ...options,
23
+ funcProps: {
24
+ ...options?.funcProps,
25
+ code: Code.fromAsset(buildDir),
26
+ handler: `${id}.handler`,
27
+ runtime: Runtime.NODEJS_18_X
28
+ }
29
+ })
30
+ }
31
+ }
package/src/Output.ts ADDED
@@ -0,0 +1,11 @@
1
+ import {CfnOutput} from 'aws-cdk-lib/core';
2
+ import {DeployStack} from './DeployStack';
3
+
4
+ export class Output extends CfnOutput {
5
+ constructor(stack: DeployStack, name: string, value: string) {
6
+ super(stack, stack.genId(name, 'output'), {
7
+ value,
8
+ exportName: stack.genName(name)
9
+ })
10
+ }
11
+ }
package/src/RestApi.ts ADDED
@@ -0,0 +1,178 @@
1
+ import {
2
+ RestApi as RestApiBase,
3
+ BasePathMapping,
4
+ ContentHandling,
5
+ DomainName,
6
+ EndpointType,
7
+ MockIntegration,
8
+ IDomainName,
9
+ IResource
10
+ } from "aws-cdk-lib/aws-apigateway";
11
+ import {DeployStack} from "./DeployStack"
12
+ import {ICorsConfig} from "./ICorsConfig";
13
+ import {Duration, Size} from "aws-cdk-lib";
14
+ import {Effect, Policy, PolicyStatement, Role} from "aws-cdk-lib/aws-iam";
15
+
16
+ interface IRestApiConfig {
17
+ cors?: ICorsConfig,
18
+ allowCredentials: boolean
19
+ }
20
+
21
+ interface IBasePathMappingConfig {
22
+ domain?: IDomainName,
23
+ domainName?: string,
24
+ hostedZoneId?: string
25
+ }
26
+
27
+ interface IResourceNode {
28
+ children: { [key: string]: IResourceNode }
29
+ node: IResource
30
+ }
31
+
32
+ const DEFAULT_EXPOSE_HEADERS = ['Date']
33
+
34
+ export class RestApi extends RestApiBase {
35
+ _id: string
36
+ _tree: IResourceNode
37
+
38
+
39
+ constructor(stack: DeployStack, id: string, props: IRestApiConfig) {
40
+ const {stage} = stack
41
+
42
+ console.log('Defining Rest API', stack.genId(id, 'api'))
43
+
44
+ const cors: ICorsConfig = props.cors || {}
45
+ const allowOrigins: string[] = Array.isArray(cors.allowOrigin) ? cors.allowOrigin : [cors.allowOrigin || '*']
46
+ super(stack, stack.genId(id, 'api'), {
47
+ restApiName: stack.genName('api'),
48
+ deploy: true,
49
+ deployOptions: {
50
+ stageName: stage,
51
+ tracingEnabled: true
52
+ },
53
+ minCompressionSize: Size.bytes(1000),
54
+ endpointConfiguration: {
55
+ types: [EndpointType.EDGE]
56
+ },
57
+ defaultCorsPreflightOptions: {
58
+ allowHeaders: ['*'],
59
+ allowMethods: ['*'],
60
+ allowCredentials: props.allowCredentials,
61
+ allowOrigins,
62
+ maxAge: Duration.seconds(cors.maxAge || 86400),
63
+ exposeHeaders: cors.exposeHeaders || DEFAULT_EXPOSE_HEADERS
64
+ }
65
+ })
66
+ this._id = id
67
+
68
+ // APIs MUST have at least one endpoint, so this will give it one
69
+ console.log('Adding health endpoint', '/health')
70
+ const health = this.root.addResource('health')
71
+ health.addMethod("GET", new MockIntegration({
72
+ requestTemplates: {
73
+ "application/json": "{\"statusCode\": 200}"
74
+ },
75
+ integrationResponses: [{
76
+ statusCode: '200',
77
+ contentHandling: ContentHandling.CONVERT_TO_TEXT,
78
+ responseTemplates: {
79
+ 'application/json': `
80
+ ## See http://docs.aws.amazon.com/apigateway/latest/developerguide/api-gateway-mapping-template-reference.html
81
+ ## This template will pass through all parameters including path, querystring, header, stage variables, and context through to the integration endpoint via the body/payload
82
+ #set($allParams = $input.params())
83
+ {
84
+ "body-json" : $input.json('$'),
85
+ "params" : {
86
+ #foreach($type in $allParams.keySet())
87
+ #set($params = $allParams.get($type))
88
+ "$type" : {
89
+ #foreach($paramName in $params.keySet())
90
+ "$paramName" : "$util.escapeJavaScript($params.get($paramName))"
91
+ #if($foreach.hasNext),#end
92
+ #end
93
+ }
94
+ #if($foreach.hasNext),#end
95
+ #end
96
+ },
97
+ "stage-variables" : {
98
+ #foreach($key in $stageVariables.keySet())
99
+ "$key" : "$util.escapeJavaScript($stageVariables.get($key))"
100
+ #if($foreach.hasNext),#end
101
+ #end
102
+ },
103
+ "context" : {
104
+ "account-id" : "$context.identity.accountId",
105
+ "api-id" : "$context.apiId",
106
+ "api-key" : "$context.identity.apiKey",
107
+ "authorizer-principal-id" : "$context.authorizer.principalId",
108
+ "caller" : "$context.identity.caller",
109
+ "cognito-authentication-provider" : "$context.identity.cognitoAuthenticationProvider",
110
+ "cognito-authentication-type" : "$context.identity.cognitoAuthenticationType",
111
+ "cognito-identity-id" : "$context.identity.cognitoIdentityId",
112
+ "cognito-identity-pool-id" : "$context.identity.cognitoIdentityPoolId",
113
+ "http-method" : "$context.httpMethod",
114
+ "stage" : "$context.stage",
115
+ "source-ip" : "$context.identity.sourceIp",
116
+ "user" : "$context.identity.user",
117
+ "user-agent" : "$context.identity.userAgent",
118
+ "user-arn" : "$context.identity.userArn",
119
+ "request-id" : "$context.requestId",
120
+ "resource-id" : "$context.resourceId",
121
+ "resource-path" : "$context.resourcePath"
122
+ }
123
+ }`
124
+ }
125
+ }]
126
+ }), {
127
+ methodResponses: [{statusCode: "200"}]
128
+ })
129
+
130
+ this._tree = {node: this.root, children: {}}
131
+ }
132
+
133
+ addBasePathMapping(stack: DeployStack, config: IBasePathMappingConfig) {
134
+ let domain = config.domain
135
+ if (!domain && config.domainName && config.hostedZoneId) {
136
+ domain = DomainName.fromDomainNameAttributes(stack, stack.genName('domain-name'), {
137
+ domainName: config.domainName,
138
+ domainNameAliasHostedZoneId: config.hostedZoneId,
139
+ domainNameAliasTarget: this.restApiRootResourceId
140
+ })
141
+ }
142
+ if (!domain) throw new Error('Missing Domain Configuration For Base Path Mapping')
143
+
144
+ new BasePathMapping(stack, stack.genId(this._id, 'base-path-mapping'), {
145
+ domainName: domain,
146
+ stage: this.deploymentStage,
147
+ restApi: this,
148
+ basePath: this._id
149
+ })
150
+ }
151
+
152
+ addAuthorizedRole(stack: DeployStack, roleArn: string): PolicyStatement {
153
+ const policyStatement = new PolicyStatement({
154
+ effect: Effect.ALLOW,
155
+ actions: ['execute-api:Invoke']
156
+ })
157
+
158
+ const authRole = Role.fromRoleArn(stack, stack.genId('authenticated-role'), roleArn)
159
+ authRole.attachInlinePolicy(new Policy(stack, stack.genId('api-access-policy'), {
160
+ statements: [policyStatement]
161
+ }))
162
+ return policyStatement;
163
+ }
164
+
165
+ path(uri: string): IResource {
166
+ const {node} = uri.split('/').reduce((resource, path) => {
167
+ if (!resource.children[path]) {
168
+ resource.children[path] = {
169
+ children: {},
170
+ node: resource.node.addResource(path)
171
+ }
172
+ }
173
+ return resource.children[path];
174
+ }, this._tree);
175
+
176
+ return node
177
+ }
178
+ }
package/src/Role.ts CHANGED
@@ -1,6 +1,7 @@
1
1
  import {FederatedPrincipal, IPrincipal, Role as CdkRole} from "aws-cdk-lib/aws-iam";
2
- import {CfnOutput, Duration} from "aws-cdk-lib";
3
- import {IDeployStack} from "./IDeployStack";
2
+ import {Duration} from "aws-cdk-lib";
3
+ import {Output} from './Output'
4
+ import {DeployStack} from "./DeployStack";
4
5
  import {IManagedPolicy} from "aws-cdk-lib/aws-iam/lib/managed-policy";
5
6
  import {PolicyDocument} from "aws-cdk-lib/aws-iam/lib/policy-document";
6
7
 
@@ -137,7 +138,7 @@ interface IRoleProps {
137
138
  }
138
139
 
139
140
  export class Role extends CdkRole {
140
- constructor(stack: IDeployStack, id: string, props: IRoleProps) {
141
+ constructor(stack: DeployStack, id: string, props: IRoleProps) {
141
142
  console.log('Defining Role', stack.genId(id))
142
143
  if (!props.assumedBy) {
143
144
  props.assumedBy = new FederatedPrincipal(
@@ -159,9 +160,6 @@ export class Role extends CdkRole {
159
160
  roleName: stack.genName(props.roleName || id),
160
161
  assumedBy: props.assumedBy
161
162
  })
162
- new CfnOutput(stack, stack.genId(id, 'arn-output'), {
163
- value: this.roleArn,
164
- exportName: stack.genName(id, 'arn')
165
- })
163
+ new Output(stack, `${id}-arn`, this.roleArn)
166
164
  }
167
165
  }
@@ -0,0 +1,47 @@
1
+ import {DeployStack} from "./DeployStack";
2
+ import {IStringParameter, StringParameter} from "aws-cdk-lib/aws-ssm";
3
+ import {Effect, IGrantable, PolicyStatement} from "aws-cdk-lib/aws-iam";
4
+ import {IKey} from "aws-cdk-lib/aws-kms";
5
+
6
+ const paramCache: { [name: string]: IStringParameter } = {}
7
+
8
+ interface SsmParameterOptions {
9
+ decryptWithKey?: IKey
10
+ }
11
+
12
+ export class SsmParameter {
13
+ parameterName: string
14
+ parameterArn: string
15
+ decryptWithKey?: IKey
16
+ _stack: DeployStack
17
+
18
+ constructor(stack: DeployStack, name: string, options?: SsmParameterOptions) {
19
+ if (name[0] !== '/') name = '/' + name
20
+ this.parameterName = `/${stack.name}${name}`;
21
+ this.parameterArn = `arn:aws:ssm:${stack.region}:${stack.account}:parameter${this.parameterName}`
22
+ this._stack = stack;
23
+ this.decryptWithKey = options?.decryptWithKey
24
+ }
25
+
26
+ grantRead(grantee: IGrantable) {
27
+ grantee.grantPrincipal.addToPrincipalPolicy(new PolicyStatement({
28
+ effect: Effect.ALLOW,
29
+ actions: ['ssm:GetParameter'],
30
+ resources: [this.parameterArn]
31
+ }))
32
+ if (this.decryptWithKey) {
33
+ this.decryptWithKey.grantDecrypt(grantee)
34
+ }
35
+ }
36
+
37
+ get stringValue() {
38
+ return this.getParameter().stringValue
39
+ }
40
+
41
+ getParameter() {
42
+ if (!paramCache[this.parameterArn]) {
43
+ paramCache[this.parameterArn] = StringParameter.fromStringParameterName(this._stack, this._stack.genId(this.parameterName.substring(1).replace('/', '_')), this.parameterName)
44
+ }
45
+ return paramCache[this.parameterArn]
46
+ }
47
+ }
@@ -0,0 +1,99 @@
1
+ import {RemovalPolicy} from "aws-cdk-lib/core";
2
+ import {Output} from './Output'
3
+ import {DeployStack} from "./DeployStack";
4
+ import {execSync} from "child_process";
5
+ import * as fs from 'fs-extra'
6
+ import * as path from 'path'
7
+ import {Bucket} from "aws-cdk-lib/aws-s3"
8
+ import {BucketDeployment, Source} from "aws-cdk-lib/aws-s3-deployment"
9
+ import ApiCertificate from "./ApiCertificate";
10
+ import {
11
+ CloudFrontAllowedMethods,
12
+ CloudFrontWebDistribution,
13
+ OriginAccessIdentity,
14
+ SSLMethod,
15
+ ViewerCertificate,
16
+ ViewerProtocolPolicy
17
+ } from "aws-cdk-lib/aws-cloudfront"
18
+ import IDomainConfig from "./IDomainConfig";
19
+ import {PolicyStatement} from "aws-cdk-lib/aws-iam";
20
+ import {ARecord, RecordTarget} from "aws-cdk-lib/aws-route53";
21
+ import {CloudFrontTarget} from "aws-cdk-lib/aws-route53-targets";
22
+
23
+ interface StaticWebSiteConfig {
24
+ srcDir?: string,
25
+ domain: IDomainConfig,
26
+ env: NodeJS.ProcessEnv
27
+ }
28
+
29
+ export class StaticWebSite {
30
+ constructor(stack: DeployStack, id: string, props: StaticWebSiteConfig) {
31
+ console.log('Deploying Static Web Site', id)
32
+ const srcDir = path.resolve("../src/", props.srcDir || '')
33
+ const buildDir = path.resolve("../dist/", props.srcDir || '')
34
+
35
+ console.log('Installing Prod Dependencies', id)
36
+ execSync('npm i --production --quiet', {
37
+ cwd: srcDir
38
+ })
39
+ console.log('Building UI Source', id)
40
+ execSync('npm run export', {
41
+ cwd: srcDir,
42
+ //env: props.env
43
+ })
44
+ fs.copySync(path.resolve(srcDir, "./out"), buildDir);
45
+
46
+ console.log('Creating HTTPS Certificate', id + '-certificate')
47
+ const certificate = new ApiCertificate(stack, id + '-certificate', props.domain);
48
+ console.log('Deploying Site Content Bucket', stack.genId(id, "content-bucket"))
49
+ const s3BucketSource = new Bucket(stack, stack.genId(id, "content-bucket"), {
50
+ bucketName: certificate.domain,
51
+ websiteIndexDocument: "index.html",
52
+ websiteErrorDocument: "error.html",
53
+ publicReadAccess: true,
54
+ removalPolicy: RemovalPolicy.DESTROY,
55
+ autoDeleteObjects: true
56
+ });
57
+ new Output(stack, `${id}-content-bucket`, s3BucketSource.bucketName);
58
+
59
+ const cloudFrontPolicy = new PolicyStatement({
60
+ actions: ['s3:GetBucket*', 's3:GetObject*', 's3:List*'],
61
+ resources: [s3BucketSource.bucketArn, s3BucketSource.bucketArn + '/*'],
62
+ })
63
+
64
+ new BucketDeployment(stack, stack.genId(id, 'bucket-deployment'), {
65
+ sources: [Source.asset(buildDir)],
66
+ destinationBucket: s3BucketSource
67
+ })
68
+ const originAccessIdentity = new OriginAccessIdentity(stack, "WebsiteOAI");
69
+ cloudFrontPolicy.addArnPrincipal(originAccessIdentity.cloudFrontOriginAccessIdentityS3CanonicalUserId)
70
+
71
+ const distro = new CloudFrontWebDistribution(stack, stack.genId(id, 'cloudfront-web-distribution'), {
72
+ enabled: true,
73
+ //priceClass:
74
+ originConfigs: [{
75
+ s3OriginSource: {
76
+ s3BucketSource,
77
+ originAccessIdentity
78
+ },
79
+ behaviors: [{
80
+ allowedMethods: CloudFrontAllowedMethods.GET_HEAD_OPTIONS,
81
+ isDefaultBehavior: true
82
+ }]
83
+ }],
84
+ viewerProtocolPolicy: ViewerProtocolPolicy.REDIRECT_TO_HTTPS,
85
+ viewerCertificate: ViewerCertificate.fromAcmCertificate(certificate, {
86
+ aliases: [certificate.domain],
87
+ sslMethod: SSLMethod.SNI
88
+ })
89
+ })
90
+ new Output(stack, `${id}-base-url`, "https://" + certificate.domain);
91
+
92
+ console.log('Setting Route53 A-Name Record', props.domain.domainName)
93
+ new ARecord(stack, stack.genId('alias'), {
94
+ zone: certificate.hostedZone,
95
+ recordName: certificate.domain,
96
+ target: RecordTarget.fromAlias(new CloudFrontTarget(distro))
97
+ })
98
+ }
99
+ }
package/src/index.ts CHANGED
@@ -1,6 +1,12 @@
1
1
  export {ApiCertificate} from "./ApiCertificate";
2
- export {IDeployStack} from "./IDeployStack";
2
+ export {DeployStack} from "./DeployStack";
3
3
  export {IDomainConfig} from "./IDomainConfig";
4
4
  export {IHostedZoneConfig} from "./IHostedZoneConfig";
5
5
  export {IStackArguments} from "./IStackArguments";
6
6
  export {Role} from "./Role";
7
+ export {StaticWebSite} from "./StaticWebSite";
8
+ export {Output} from "./Output";
9
+ export {RestApi} from "./RestApi";
10
+ export {NodeJsLambda} from "./NodeJsLambda";
11
+ export {DynamoDbTable} from "./DynamoDbTable";
12
+ export {FunctionIntegration} from "./FunctionIntegration";
@@ -0,0 +1,22 @@
1
+ import {FunctionProps, ILayerVersion, LayerVersion} from "aws-cdk-lib/aws-lambda"
2
+ import {DeployStack} from "../DeployStack";
3
+ import {SsmParameter} from "../SsmParameter";
4
+
5
+ let honeyCombLayer: ILayerVersion
6
+ export function applyHoneycombToLambda(stack: DeployStack, props: FunctionProps): FunctionProps {
7
+ if (!honeyCombLayer) honeyCombLayer = LayerVersion.fromLayerVersionArn(stack, stack.genId('hc-layer'), `arn:aws:lambda:${stack.region}:702835727665:layer:honeycomb-lambda-extension-x86_64-v11-1-1:1`)
8
+
9
+ const layers = props.layers ? [...props.layers] : []
10
+ const environment = props.environment || {}
11
+
12
+ layers.push(honeyCombLayer)
13
+ environment.LIBHONEY_API_KEY = new SsmParameter(stack, `/honeycomb/api-key`).stringValue
14
+ environment.LIBHONEY_DATASET =new SsmParameter(stack, `/honeycomb/dataset`).stringValue
15
+ environment.LOGS_API_DISABLE_PLATFORM_MSGS = "true"
16
+
17
+ return {
18
+ ...props,
19
+ layers,
20
+ environment
21
+ }
22
+ }