@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.
- package/.fleet/run.json +11 -0
- package/lib/ApiCertificate.d.ts +2 -2
- package/lib/ApiCertificate.js +4 -5
- package/lib/CognitoApiGatewayAuthorizer.d.ts +19 -0
- package/lib/CognitoApiGatewayAuthorizer.js +24 -0
- package/lib/DeployStack.d.ts +20 -0
- package/lib/DeployStack.js +48 -0
- package/lib/DynamoDbTable.d.ts +12 -0
- package/lib/DynamoDbTable.js +34 -0
- package/lib/FunctionIntegration.d.ts +21 -0
- package/lib/FunctionIntegration.js +61 -0
- package/lib/ICorsConfig.d.ts +5 -0
- package/lib/ICorsConfig.js +2 -0
- package/lib/NodeJsLambda.d.ts +13 -0
- package/lib/NodeJsLambda.js +28 -0
- package/lib/Output.d.ts +5 -0
- package/lib/Output.js +13 -0
- package/lib/RestApi.d.ts +28 -0
- package/lib/RestApi.js +142 -0
- package/lib/Role.d.ts +2 -2
- package/lib/Role.js +2 -5
- package/lib/SsmParameter.d.ts +18 -0
- package/lib/SsmParameter.js +40 -0
- package/lib/StaticWebSite.d.ts +12 -0
- package/lib/StaticWebSite.js +107 -0
- package/lib/index.d.ts +7 -1
- package/lib/index.js +15 -3
- package/lib/methods/apply-honeycomb-to-lambda.d.ts +3 -0
- package/lib/methods/apply-honeycomb-to-lambda.js +22 -0
- package/lib/methods/build-node-source.d.ts +13 -0
- package/lib/methods/build-node-source.js +105 -0
- package/lib/methods/duration.d.ts +8 -0
- package/lib/methods/duration.js +22 -0
- package/lib/methods/empty-directory.d.ts +2 -0
- package/lib/methods/empty-directory.js +47 -0
- package/lib/methods/generate-hash.d.ts +15 -0
- package/lib/methods/generate-hash.js +59 -0
- package/lib/methods/get-source-directory.d.ts +4 -0
- package/lib/methods/get-source-directory.js +37 -0
- package/lib/methods/walk-directory.d.ts +14 -0
- package/lib/methods/walk-directory.js +48 -0
- package/package.json +4 -2
- package/src/ApiCertificate.ts +6 -6
- package/src/CognitoApiGatewayAuthorizer.ts +25 -0
- package/src/{IDeployStack.ts → DeployStack.ts} +4 -4
- package/src/DynamoDbTable.ts +40 -0
- package/src/FunctionIntegration.ts +83 -0
- package/src/ICorsConfig.ts +5 -0
- package/src/NodeJsLambda.ts +31 -0
- package/src/Output.ts +11 -0
- package/src/RestApi.ts +178 -0
- package/src/Role.ts +5 -7
- package/src/SsmParameter.ts +47 -0
- package/src/StaticWebSite.ts +99 -0
- package/src/index.ts +7 -1
- package/src/methods/apply-honeycomb-to-lambda.ts +22 -0
- package/src/methods/build-node-source.ts +97 -0
- package/src/methods/duration.ts +24 -0
- package/src/methods/empty-directory.ts +19 -0
- package/src/methods/generate-hash.ts +49 -0
- package/src/methods/get-source-directory.ts +11 -0
- 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
|
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
|
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
|
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
|
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,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 {
|
3
|
-
import {
|
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:
|
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
|
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 {
|
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
|
+
}
|