@stacksjs/cloud 0.65.0 → 0.67.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/dist/index.js +28 -22
- package/dist/origin-request.js +6 -6
- package/package.json +13 -15
- package/dist/index.js.map +0 -1875
- package/src/cloud/ai.ts +0 -24
- package/src/cloud/cache.ts +0 -0
- package/src/cloud/cdn.ts +0 -290
- package/src/cloud/cli.ts +0 -14
- package/src/cloud/compute.ts +0 -245
- package/src/cloud/dashboard.ts +0 -85
- package/src/cloud/database.ts +0 -34
- package/src/cloud/deployment.ts +0 -71
- package/src/cloud/dns.ts +0 -41
- package/src/cloud/docs.ts +0 -50
- package/src/cloud/email.ts +0 -356
- package/src/cloud/file-system.ts +0 -35
- package/src/cloud/index.ts +0 -141
- package/src/cloud/jump-box.ts +0 -47
- package/src/cloud/network.ts +0 -32
- package/src/cloud/permissions.ts +0 -33
- package/src/cloud/queue.ts +0 -164
- package/src/cloud/redirects.ts +0 -52
- package/src/cloud/router-layer/nodejs/package.json +0 -15
- package/src/cloud/search-engine.ts +0 -108
- package/src/cloud/security.ts +0 -272
- package/src/cloud/storage.ts +0 -197
- package/src/edge/origin-request.ts +0 -55
- package/src/helpers.ts +0 -800
- package/src/index.ts +0 -2
- package/src/types.ts +0 -28
package/src/cloud/ai.ts
DELETED
|
@@ -1,24 +0,0 @@
|
|
|
1
|
-
import type { Construct } from 'constructs'
|
|
2
|
-
import type { NestedCloudProps } from '../types'
|
|
3
|
-
import { config } from '@stacksjs/config'
|
|
4
|
-
import { aws_iam as iam } from 'aws-cdk-lib'
|
|
5
|
-
|
|
6
|
-
export interface AiStackProps extends NestedCloudProps {}
|
|
7
|
-
|
|
8
|
-
export class AiStack {
|
|
9
|
-
// eslint-disable-next-line unused-imports/no-unused-vars
|
|
10
|
-
constructor(scope: Construct, props: AiStackProps) {
|
|
11
|
-
const bedrockAccessPolicy = new iam.PolicyStatement({
|
|
12
|
-
effect: iam.Effect.ALLOW,
|
|
13
|
-
actions: ['bedrock:InvokeModel', 'bedrock:InvokeModelWithResponseStream'],
|
|
14
|
-
resources: config.ai.models?.map((model: string) => `arn:aws:bedrock:us-east-1::foundation-model/${model}`),
|
|
15
|
-
})
|
|
16
|
-
|
|
17
|
-
const bedrockAccessRole = new iam.Role(scope, 'BedrockAccessRole', {
|
|
18
|
-
assumedBy: new iam.ServicePrincipal('ecs-tasks.amazonaws.com'),
|
|
19
|
-
managedPolicies: [iam.ManagedPolicy.fromAwsManagedPolicyName('service-role/AmazonECSTaskExecutionRolePolicy')],
|
|
20
|
-
})
|
|
21
|
-
|
|
22
|
-
bedrockAccessRole.addToPolicy(bedrockAccessPolicy)
|
|
23
|
-
}
|
|
24
|
-
}
|
package/src/cloud/cache.ts
DELETED
|
File without changes
|
package/src/cloud/cdn.ts
DELETED
|
@@ -1,290 +0,0 @@
|
|
|
1
|
-
import type { aws_certificatemanager as acm, aws_s3 as s3, aws_wafv2 as wafv2 } from 'aws-cdk-lib'
|
|
2
|
-
import type { ApplicationLoadBalancer } from 'aws-cdk-lib/aws-elasticloadbalancingv2'
|
|
3
|
-
import type { Construct } from 'constructs'
|
|
4
|
-
import type { NestedCloudProps } from '../types'
|
|
5
|
-
import { config } from '@stacksjs/config'
|
|
6
|
-
import {
|
|
7
|
-
aws_cloudfront as cloudfront,
|
|
8
|
-
Duration,
|
|
9
|
-
aws_lambda as lambda,
|
|
10
|
-
aws_cloudfront_origins as origins,
|
|
11
|
-
CfnOutput as Output,
|
|
12
|
-
aws_route53 as route53,
|
|
13
|
-
aws_route53_targets as targets,
|
|
14
|
-
} from 'aws-cdk-lib'
|
|
15
|
-
|
|
16
|
-
export interface CdnStackProps extends NestedCloudProps {
|
|
17
|
-
certificate: acm.Certificate
|
|
18
|
-
logBucket: s3.Bucket
|
|
19
|
-
publicBucket: s3.Bucket
|
|
20
|
-
docsBucket?: s3.Bucket
|
|
21
|
-
firewall: wafv2.CfnWebACL
|
|
22
|
-
originRequestFunction: lambda.Function
|
|
23
|
-
zone: route53.IHostedZone
|
|
24
|
-
webServer?: lambda.Function
|
|
25
|
-
webServerUrl?: lambda.FunctionUrl
|
|
26
|
-
lb?: ApplicationLoadBalancer
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
export class CdnStack {
|
|
30
|
-
mainDistribution: cloudfront.Distribution
|
|
31
|
-
docsDistribution: cloudfront.Distribution | undefined
|
|
32
|
-
cdnCachePolicy: cloudfront.CachePolicy
|
|
33
|
-
mainVanityUrl!: string
|
|
34
|
-
docsVanityUrl: string | undefined
|
|
35
|
-
realtimeLogConfig!: cloudfront.RealtimeLogConfig
|
|
36
|
-
props: CdnStackProps
|
|
37
|
-
|
|
38
|
-
constructor(scope: Construct, props: CdnStackProps) {
|
|
39
|
-
this.props = props
|
|
40
|
-
|
|
41
|
-
this.cdnCachePolicy = new cloudfront.CachePolicy(scope, 'CdnCachePolicy', {
|
|
42
|
-
comment: 'Stacks CDN Cache Policy',
|
|
43
|
-
cachePolicyName: `${props.slug}-${props.appEnv}-cdn-cache-policy`,
|
|
44
|
-
minTtl: config.cloud.cdn?.minTtl ? Duration.seconds(config.cloud.cdn.minTtl) : undefined,
|
|
45
|
-
defaultTtl: config.cloud.cdn?.defaultTtl ? Duration.seconds(config.cloud.cdn.defaultTtl) : undefined,
|
|
46
|
-
maxTtl: config.cloud.cdn?.maxTtl ? Duration.seconds(config.cloud.cdn.maxTtl) : undefined,
|
|
47
|
-
cookieBehavior: this.getCookieBehavior(config.cloud.cdn?.cookieBehavior),
|
|
48
|
-
})
|
|
49
|
-
|
|
50
|
-
const originAccessControl = new cloudfront.S3OriginAccessControl(scope, 'WebOAC', {
|
|
51
|
-
originAccessControlName: `${props.slug}-${props.appEnv}-web-oac-${props.timestamp}`,
|
|
52
|
-
description: 'Access from CloudFront to the frontend bucket.',
|
|
53
|
-
signing: cloudfront.Signing.SIGV4_NO_OVERRIDE,
|
|
54
|
-
})
|
|
55
|
-
|
|
56
|
-
if (config.app.docMode) {
|
|
57
|
-
// In doc mode, create only one distribution for docs
|
|
58
|
-
this.mainDistribution = this.createDistribution(
|
|
59
|
-
scope,
|
|
60
|
-
props,
|
|
61
|
-
props.docsBucket as s3.Bucket,
|
|
62
|
-
this.createDocsOriginRequestFunction(scope),
|
|
63
|
-
props.domain,
|
|
64
|
-
'MainCdn',
|
|
65
|
-
originAccessControl,
|
|
66
|
-
)
|
|
67
|
-
}
|
|
68
|
-
else {
|
|
69
|
-
// Not in doc mode, create two distributions
|
|
70
|
-
this.mainDistribution = this.createDistribution(
|
|
71
|
-
scope,
|
|
72
|
-
props,
|
|
73
|
-
props.publicBucket,
|
|
74
|
-
props.originRequestFunction,
|
|
75
|
-
props.domain,
|
|
76
|
-
'MainCdn',
|
|
77
|
-
originAccessControl,
|
|
78
|
-
)
|
|
79
|
-
|
|
80
|
-
if (props.docsBucket) {
|
|
81
|
-
this.docsDistribution = this.createDistribution(
|
|
82
|
-
scope,
|
|
83
|
-
props,
|
|
84
|
-
props.docsBucket,
|
|
85
|
-
this.createDocsOriginRequestFunction(scope),
|
|
86
|
-
`docs.${props.domain}`,
|
|
87
|
-
'DocsCdn',
|
|
88
|
-
originAccessControl,
|
|
89
|
-
)
|
|
90
|
-
}
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
// Create Route53 records
|
|
94
|
-
this.createRoute53Records(scope, props)
|
|
95
|
-
|
|
96
|
-
// Create outputs
|
|
97
|
-
this.createOutputs(scope, props)
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
createDistribution(
|
|
101
|
-
scope: Construct,
|
|
102
|
-
props: CdnStackProps,
|
|
103
|
-
sourceBucket: s3.Bucket,
|
|
104
|
-
originRequestFunction: lambda.Function,
|
|
105
|
-
domainName: string,
|
|
106
|
-
id: string,
|
|
107
|
-
originAccessControl: cloudfront.S3OriginAccessControl,
|
|
108
|
-
): cloudfront.Distribution {
|
|
109
|
-
return new cloudfront.Distribution(scope, id, {
|
|
110
|
-
domainNames: [domainName],
|
|
111
|
-
defaultRootObject: 'index.html',
|
|
112
|
-
comment: `CDN for ${domainName}`,
|
|
113
|
-
certificate: props.certificate,
|
|
114
|
-
enableLogging: true,
|
|
115
|
-
logBucket: props.logBucket,
|
|
116
|
-
httpVersion: cloudfront.HttpVersion.HTTP2_AND_3,
|
|
117
|
-
priceClass: cloudfront.PriceClass.PRICE_CLASS_ALL,
|
|
118
|
-
enabled: true,
|
|
119
|
-
minimumProtocolVersion: cloudfront.SecurityPolicyProtocol.TLS_V1_2_2021,
|
|
120
|
-
webAclId: props.firewall.attrArn,
|
|
121
|
-
enableIpv6: true,
|
|
122
|
-
defaultBehavior: {
|
|
123
|
-
origin: new origins.S3StaticWebsiteOrigin(sourceBucket, {
|
|
124
|
-
originPath: '/',
|
|
125
|
-
originAccessControlId: originAccessControl.originAccessControlId,
|
|
126
|
-
}),
|
|
127
|
-
edgeLambdas: [
|
|
128
|
-
{
|
|
129
|
-
eventType: cloudfront.LambdaEdgeEventType.ORIGIN_REQUEST,
|
|
130
|
-
functionVersion: originRequestFunction.currentVersion,
|
|
131
|
-
},
|
|
132
|
-
],
|
|
133
|
-
compress: config.cloud.cdn?.compress,
|
|
134
|
-
allowedMethods: this.allowedMethods(),
|
|
135
|
-
cachedMethods: this.cachedMethods(),
|
|
136
|
-
viewerProtocolPolicy: cloudfront.ViewerProtocolPolicy.REDIRECT_TO_HTTPS,
|
|
137
|
-
cachePolicy: this.cdnCachePolicy,
|
|
138
|
-
},
|
|
139
|
-
errorResponses: [
|
|
140
|
-
{
|
|
141
|
-
httpStatus: 404,
|
|
142
|
-
responsePagePath: '/index.html',
|
|
143
|
-
responseHttpStatus: 200,
|
|
144
|
-
ttl: Duration.millis(0),
|
|
145
|
-
},
|
|
146
|
-
{
|
|
147
|
-
httpStatus: 403,
|
|
148
|
-
responsePagePath: '/index.html',
|
|
149
|
-
responseHttpStatus: 200,
|
|
150
|
-
ttl: Duration.millis(0),
|
|
151
|
-
},
|
|
152
|
-
],
|
|
153
|
-
})
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
createRoute53Records(scope: Construct, props: CdnStackProps): void {
|
|
157
|
-
new route53.ARecord(scope, 'MainAliasRecord', {
|
|
158
|
-
recordName: props.domain,
|
|
159
|
-
zone: props.zone,
|
|
160
|
-
target: route53.RecordTarget.fromAlias(new targets.CloudFrontTarget(this.mainDistribution)),
|
|
161
|
-
})
|
|
162
|
-
|
|
163
|
-
if (this.docsDistribution) {
|
|
164
|
-
new route53.ARecord(scope, 'DocsAliasRecord', {
|
|
165
|
-
recordName: `docs.${props.domain}`,
|
|
166
|
-
zone: props.zone,
|
|
167
|
-
target: route53.RecordTarget.fromAlias(new targets.CloudFrontTarget(this.docsDistribution)),
|
|
168
|
-
})
|
|
169
|
-
}
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
createOutputs(scope: Construct, props: CdnStackProps): void {
|
|
173
|
-
new Output(scope, 'MainDistributionId', {
|
|
174
|
-
value: this.mainDistribution.distributionId,
|
|
175
|
-
})
|
|
176
|
-
|
|
177
|
-
new Output(scope, 'MainUrl', {
|
|
178
|
-
value: `https://${props.domain}`,
|
|
179
|
-
description: 'The URL of the deployed main application',
|
|
180
|
-
})
|
|
181
|
-
|
|
182
|
-
this.mainVanityUrl = `https://${this.mainDistribution.domainName}`
|
|
183
|
-
new Output(scope, 'MainVanityUrl', {
|
|
184
|
-
value: this.mainVanityUrl,
|
|
185
|
-
description: 'The vanity URL of the deployed main application',
|
|
186
|
-
})
|
|
187
|
-
|
|
188
|
-
if (this.docsDistribution) {
|
|
189
|
-
new Output(scope, 'DocsDistributionId', {
|
|
190
|
-
value: this.docsDistribution.distributionId,
|
|
191
|
-
})
|
|
192
|
-
|
|
193
|
-
this.docsVanityUrl = `https://${this.docsDistribution.domainName}`
|
|
194
|
-
new Output(scope, 'DocsAppVanityUrl', {
|
|
195
|
-
value: this.docsVanityUrl,
|
|
196
|
-
description: 'The vanity URL of the deployed docs application',
|
|
197
|
-
})
|
|
198
|
-
}
|
|
199
|
-
}
|
|
200
|
-
|
|
201
|
-
createDocsOriginRequestFunction(scope: Construct): lambda.Function {
|
|
202
|
-
const docsOriginRequestFunction = new lambda.Function(scope, 'DocsOriginRequestFunction', {
|
|
203
|
-
functionName: `${this.props.slug}-${this.props.appEnv}-docs-origin-request-function-${this.props.timestamp}`,
|
|
204
|
-
description: 'Custom origin request function for the docs',
|
|
205
|
-
runtime: lambda.Runtime.NODEJS_20_X,
|
|
206
|
-
handler: 'index.handler',
|
|
207
|
-
code: lambda.Code.fromInline(`
|
|
208
|
-
const config = {
|
|
209
|
-
suffix: '.html',
|
|
210
|
-
}
|
|
211
|
-
|
|
212
|
-
const regexSuffixless = /\\/[^/.]+$/
|
|
213
|
-
const regexTrailingSlash = /.+\\/$/
|
|
214
|
-
|
|
215
|
-
exports.handler = (event, context, callback) => {
|
|
216
|
-
const request = event.Records[0].cf.request;
|
|
217
|
-
const uri = request.uri;
|
|
218
|
-
|
|
219
|
-
// Append index.html to root URI
|
|
220
|
-
if (uri === '/') {
|
|
221
|
-
request.uri = '/index.html';
|
|
222
|
-
callback(null, request);
|
|
223
|
-
return;
|
|
224
|
-
}
|
|
225
|
-
|
|
226
|
-
// Append .html to suffixless URI
|
|
227
|
-
if (uri.match(regexSuffixless)) {
|
|
228
|
-
request.uri = uri + '.html';
|
|
229
|
-
callback(null, request);
|
|
230
|
-
return;
|
|
231
|
-
}
|
|
232
|
-
|
|
233
|
-
// Remove trailing slash and append .html to origin request
|
|
234
|
-
if (uri.match(regexTrailingSlash)) {
|
|
235
|
-
request.uri = uri.slice(0, -1) + '.html';
|
|
236
|
-
callback(null, request);
|
|
237
|
-
return;
|
|
238
|
-
}
|
|
239
|
-
|
|
240
|
-
callback(null, request);
|
|
241
|
-
};
|
|
242
|
-
`),
|
|
243
|
-
})
|
|
244
|
-
|
|
245
|
-
new lambda.CfnPermission(scope, 'DocsOriginRequestFunctionPermission', {
|
|
246
|
-
action: 'lambda:InvokeFunction',
|
|
247
|
-
principal: 'edgelambda.amazonaws.com',
|
|
248
|
-
functionName: docsOriginRequestFunction.functionName,
|
|
249
|
-
})
|
|
250
|
-
|
|
251
|
-
return docsOriginRequestFunction
|
|
252
|
-
}
|
|
253
|
-
|
|
254
|
-
getCookieBehavior(cookieBehavior: string | undefined): cloudfront.CacheCookieBehavior | undefined {
|
|
255
|
-
switch (cookieBehavior) {
|
|
256
|
-
case 'none':
|
|
257
|
-
return cloudfront.CacheCookieBehavior.none()
|
|
258
|
-
case 'all':
|
|
259
|
-
return cloudfront.CacheCookieBehavior.all()
|
|
260
|
-
case 'allowList':
|
|
261
|
-
return cloudfront.CacheCookieBehavior.allowList(...(config.cloud.cdn?.allowList.cookies || []))
|
|
262
|
-
default:
|
|
263
|
-
return undefined
|
|
264
|
-
}
|
|
265
|
-
}
|
|
266
|
-
|
|
267
|
-
allowedMethods(): cloudfront.AllowedMethods {
|
|
268
|
-
switch (config.cloud.cdn?.allowedMethods) {
|
|
269
|
-
case 'ALL':
|
|
270
|
-
return cloudfront.AllowedMethods.ALLOW_ALL
|
|
271
|
-
case 'GET_HEAD':
|
|
272
|
-
return cloudfront.AllowedMethods.ALLOW_GET_HEAD
|
|
273
|
-
case 'GET_HEAD_OPTIONS':
|
|
274
|
-
return cloudfront.AllowedMethods.ALLOW_GET_HEAD_OPTIONS
|
|
275
|
-
default:
|
|
276
|
-
return cloudfront.AllowedMethods.ALLOW_ALL
|
|
277
|
-
}
|
|
278
|
-
}
|
|
279
|
-
|
|
280
|
-
cachedMethods(): cloudfront.CachedMethods {
|
|
281
|
-
switch (config.cloud.cdn?.cachedMethods) {
|
|
282
|
-
case 'GET_HEAD':
|
|
283
|
-
return cloudfront.CachedMethods.CACHE_GET_HEAD
|
|
284
|
-
case 'GET_HEAD_OPTIONS':
|
|
285
|
-
return cloudfront.CachedMethods.CACHE_GET_HEAD_OPTIONS
|
|
286
|
-
default:
|
|
287
|
-
return cloudfront.CachedMethods.CACHE_GET_HEAD
|
|
288
|
-
}
|
|
289
|
-
}
|
|
290
|
-
}
|
package/src/cloud/cli.ts
DELETED
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
import type { Construct } from 'constructs'
|
|
2
|
-
import type { NestedCloudProps } from '../types'
|
|
3
|
-
import { CfnOutput as Output } from 'aws-cdk-lib'
|
|
4
|
-
|
|
5
|
-
export interface CliStackProps extends NestedCloudProps {}
|
|
6
|
-
|
|
7
|
-
export class CliStack {
|
|
8
|
-
constructor(scope: Construct, props: CliStackProps) {
|
|
9
|
-
new Output(scope, 'CliSetupUrl', {
|
|
10
|
-
value: `https://api.${props.domain}/install`,
|
|
11
|
-
description: 'URL to trigger the CLI setup function',
|
|
12
|
-
})
|
|
13
|
-
}
|
|
14
|
-
}
|
package/src/cloud/compute.ts
DELETED
|
@@ -1,245 +0,0 @@
|
|
|
1
|
-
import type { aws_certificatemanager as acm, aws_efs as efs } from 'aws-cdk-lib'
|
|
2
|
-
import type { Construct } from 'constructs'
|
|
3
|
-
import type { EnvKey } from '../../../../env'
|
|
4
|
-
import type { NestedCloudProps } from '../types'
|
|
5
|
-
import { env } from '@stacksjs/env'
|
|
6
|
-
import { path as p } from '@stacksjs/path'
|
|
7
|
-
import {
|
|
8
|
-
Duration,
|
|
9
|
-
aws_ec2 as ec2,
|
|
10
|
-
aws_ecs as ecs,
|
|
11
|
-
CfnOutput as Output,
|
|
12
|
-
RemovalPolicy,
|
|
13
|
-
aws_route53 as route53,
|
|
14
|
-
aws_route53_targets as route53Targets,
|
|
15
|
-
aws_secretsmanager as secretsmanager,
|
|
16
|
-
} from 'aws-cdk-lib'
|
|
17
|
-
import * as elbv2 from 'aws-cdk-lib/aws-elasticloadbalancingv2'
|
|
18
|
-
import { LogGroup } from 'aws-cdk-lib/aws-logs'
|
|
19
|
-
|
|
20
|
-
export interface ComputeStackProps extends NestedCloudProps {
|
|
21
|
-
vpc: ec2.Vpc
|
|
22
|
-
fileSystem: efs.FileSystem
|
|
23
|
-
zone: route53.IHostedZone
|
|
24
|
-
certificate: acm.Certificate
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
export class ComputeStack {
|
|
28
|
-
lb: elbv2.ApplicationLoadBalancer
|
|
29
|
-
cluster: ecs.Cluster
|
|
30
|
-
taskDefinition: ecs.FargateTaskDefinition
|
|
31
|
-
|
|
32
|
-
constructor(scope: Construct, props: ComputeStackProps) {
|
|
33
|
-
const vpc = props.vpc
|
|
34
|
-
const fileSystem = props.fileSystem
|
|
35
|
-
|
|
36
|
-
if (!fileSystem)
|
|
37
|
-
throw new Error('The file system is missing. Please make sure it was created properly.')
|
|
38
|
-
|
|
39
|
-
this.cluster = new ecs.Cluster(scope, 'StacksCluster', {
|
|
40
|
-
clusterName: `${props.slug}-${props.appEnv}-web-server-cluster`,
|
|
41
|
-
vpc,
|
|
42
|
-
})
|
|
43
|
-
|
|
44
|
-
this.taskDefinition = new ecs.FargateTaskDefinition(scope, 'TaskDefinition', {
|
|
45
|
-
family: `${props.appName}-${props.appEnv}-api`,
|
|
46
|
-
memoryLimitMiB: 512, // Match your Lambda memory size
|
|
47
|
-
cpu: 256, // Choose an appropriate value
|
|
48
|
-
runtimePlatform: {
|
|
49
|
-
cpuArchitecture: ecs.CpuArchitecture.ARM64,
|
|
50
|
-
},
|
|
51
|
-
})
|
|
52
|
-
|
|
53
|
-
// const assetImage = new ecr_assets.DockerImageAsset(scope, 'DockerImageAsset', {
|
|
54
|
-
// directory: p.frameworkCloudPath(),
|
|
55
|
-
// })
|
|
56
|
-
|
|
57
|
-
const container = this.taskDefinition.addContainer('WebServerContainer', {
|
|
58
|
-
containerName: `${props.appName}-${props.appEnv}-api`,
|
|
59
|
-
// image: ecs.ContainerImage.fromDockerImageAsset(assetImage),
|
|
60
|
-
image: ecs.ContainerImage.fromAsset(p.frameworkPath('server')),
|
|
61
|
-
logging: new ecs.AwsLogDriver({
|
|
62
|
-
streamPrefix: `${props.appName}-${props.appEnv}-web-server-logs`,
|
|
63
|
-
logGroup: new LogGroup(scope, 'StacksApiLogs', {
|
|
64
|
-
logGroupName: '/aws/ecs/stacks-api',
|
|
65
|
-
removalPolicy: RemovalPolicy.DESTROY, // Automatically remove logs on stack deletion
|
|
66
|
-
}),
|
|
67
|
-
}),
|
|
68
|
-
healthCheck: {
|
|
69
|
-
command: ['CMD-SHELL', 'curl -f http://localhost:3000/health || exit 1'], // requires curl inside the container which isn't available in the base image. I wonder if there is a better way
|
|
70
|
-
interval: Duration.seconds(10),
|
|
71
|
-
timeout: Duration.seconds(5),
|
|
72
|
-
retries: 3,
|
|
73
|
-
startPeriod: Duration.seconds(10),
|
|
74
|
-
},
|
|
75
|
-
})
|
|
76
|
-
|
|
77
|
-
container.addPortMappings({
|
|
78
|
-
containerPort: 3000,
|
|
79
|
-
hostPort: 3000,
|
|
80
|
-
})
|
|
81
|
-
|
|
82
|
-
const serviceSecurityGroup = new ec2.SecurityGroup(scope, 'ServiceSecurityGroup', {
|
|
83
|
-
securityGroupName: `${props.appName}-${props.appEnv}-api-service-sg`,
|
|
84
|
-
vpc,
|
|
85
|
-
description: 'Stacks Security Group for API Service',
|
|
86
|
-
})
|
|
87
|
-
|
|
88
|
-
const publicLoadBalancerSG = new ec2.SecurityGroup(scope, 'PublicLoadBalancerSG', {
|
|
89
|
-
securityGroupName: `${props.appName}-${props.appEnv}-public-load-balancer-sg`,
|
|
90
|
-
vpc,
|
|
91
|
-
description: 'Access to the public facing load balancer',
|
|
92
|
-
})
|
|
93
|
-
|
|
94
|
-
// Assuming serviceSecurityGroup and publicLoadBalancerSG are already defined
|
|
95
|
-
serviceSecurityGroup.addIngressRule(publicLoadBalancerSG, ec2.Port.allTraffic(), 'Ingress from the public ALB')
|
|
96
|
-
|
|
97
|
-
this.lb = new elbv2.ApplicationLoadBalancer(scope, 'ApplicationLoadBalancer', {
|
|
98
|
-
http2Enabled: true,
|
|
99
|
-
loadBalancerName: `${props.appName}-${props.appEnv}-alb`,
|
|
100
|
-
vpc,
|
|
101
|
-
vpcSubnets: {
|
|
102
|
-
subnets: vpc.selectSubnets({
|
|
103
|
-
subnetType: ec2.SubnetType.PUBLIC,
|
|
104
|
-
onePerAz: true,
|
|
105
|
-
}).subnets,
|
|
106
|
-
},
|
|
107
|
-
internetFacing: true,
|
|
108
|
-
idleTimeout: Duration.seconds(30),
|
|
109
|
-
securityGroup: publicLoadBalancerSG,
|
|
110
|
-
})
|
|
111
|
-
|
|
112
|
-
new route53.ARecord(scope, 'ApiDomainAliasRecord', {
|
|
113
|
-
zone: props.zone,
|
|
114
|
-
recordName: 'api',
|
|
115
|
-
target: route53.RecordTarget.fromAlias(new route53Targets.LoadBalancerTarget(this.lb)),
|
|
116
|
-
})
|
|
117
|
-
|
|
118
|
-
const serviceTargetGroup = new elbv2.ApplicationTargetGroup(scope, 'ServiceTargetGroup', {
|
|
119
|
-
targetGroupName: `${props.appName}-${props.appEnv}-api-tg`,
|
|
120
|
-
vpc,
|
|
121
|
-
targetType: elbv2.TargetType.IP,
|
|
122
|
-
protocol: elbv2.ApplicationProtocol.HTTP,
|
|
123
|
-
port: 3000,
|
|
124
|
-
healthCheck: {
|
|
125
|
-
interval: Duration.seconds(6),
|
|
126
|
-
path: '/health',
|
|
127
|
-
protocol: elbv2.Protocol.HTTP,
|
|
128
|
-
timeout: Duration.seconds(5),
|
|
129
|
-
healthyThresholdCount: 2,
|
|
130
|
-
unhealthyThresholdCount: 10,
|
|
131
|
-
},
|
|
132
|
-
})
|
|
133
|
-
|
|
134
|
-
const service = new ecs.FargateService(scope, 'StacksApiService', {
|
|
135
|
-
serviceName: `${props.appName}-${props.appEnv}-api-service`,
|
|
136
|
-
cluster: this.cluster,
|
|
137
|
-
taskDefinition: this.taskDefinition,
|
|
138
|
-
desiredCount: 1,
|
|
139
|
-
assignPublicIp: true,
|
|
140
|
-
maxHealthyPercent: 200,
|
|
141
|
-
vpcSubnets: vpc.selectSubnets({
|
|
142
|
-
subnetType: ec2.SubnetType.PUBLIC,
|
|
143
|
-
onePerAz: true,
|
|
144
|
-
}),
|
|
145
|
-
minHealthyPercent: 75,
|
|
146
|
-
securityGroups: [serviceSecurityGroup],
|
|
147
|
-
})
|
|
148
|
-
|
|
149
|
-
service.attachToApplicationTargetGroup(serviceTargetGroup)
|
|
150
|
-
publicLoadBalancerSG.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.allTraffic())
|
|
151
|
-
|
|
152
|
-
this.lb.addListener('HttpsListener', {
|
|
153
|
-
port: 443,
|
|
154
|
-
certificates: [props.certificate],
|
|
155
|
-
defaultAction: elbv2.ListenerAction.forward([serviceTargetGroup]),
|
|
156
|
-
})
|
|
157
|
-
|
|
158
|
-
this.lb.addListener('HttpListener', {
|
|
159
|
-
port: 80,
|
|
160
|
-
defaultAction: elbv2.ListenerAction.forward([serviceTargetGroup]),
|
|
161
|
-
})
|
|
162
|
-
|
|
163
|
-
props.fileSystem.connections.allowFromAnyIpv4(ec2.Port.tcp(2049)) // port 2049 (NFS) for EFS
|
|
164
|
-
|
|
165
|
-
const volumeName = `${props.slug}-${props.appEnv}-efs`
|
|
166
|
-
this.taskDefinition.addVolume({
|
|
167
|
-
name: volumeName,
|
|
168
|
-
efsVolumeConfiguration: {
|
|
169
|
-
fileSystemId: props.fileSystem.fileSystemId,
|
|
170
|
-
},
|
|
171
|
-
})
|
|
172
|
-
|
|
173
|
-
container.addMountPoints({
|
|
174
|
-
sourceVolume: volumeName,
|
|
175
|
-
containerPath: '/mnt/efs',
|
|
176
|
-
readOnly: false,
|
|
177
|
-
})
|
|
178
|
-
|
|
179
|
-
// Setup AutoScaling policy
|
|
180
|
-
// TODO: make this configurable in cloud.compute
|
|
181
|
-
const scaling = service.autoScaleTaskCount({ maxCapacity: 2 })
|
|
182
|
-
|
|
183
|
-
scaling.scaleOnCpuUtilization('CpuScaling', {
|
|
184
|
-
targetUtilizationPercent: 50,
|
|
185
|
-
scaleInCooldown: Duration.seconds(60),
|
|
186
|
-
scaleOutCooldown: Duration.seconds(60),
|
|
187
|
-
})
|
|
188
|
-
|
|
189
|
-
scaling.scaleOnMemoryUtilization('MemoryScaling', {
|
|
190
|
-
targetUtilizationPercent: 60,
|
|
191
|
-
scaleInCooldown: Duration.seconds(60),
|
|
192
|
-
scaleOutCooldown: Duration.seconds(60),
|
|
193
|
-
})
|
|
194
|
-
|
|
195
|
-
const keysToRemove = [
|
|
196
|
-
'_HANDLER',
|
|
197
|
-
'_X_AMZN_TRACE_ID',
|
|
198
|
-
'AWS_REGION',
|
|
199
|
-
'AWS_EXECUTION_ENV',
|
|
200
|
-
'AWS_LAMBDA_FUNCTION_NAME',
|
|
201
|
-
'AWS_LAMBDA_FUNCTION_MEMORY_SIZE',
|
|
202
|
-
'AWS_LAMBDA_FUNCTION_VERSION',
|
|
203
|
-
'AWS_LAMBDA_INITIALIZATION_TYPE',
|
|
204
|
-
'AWS_LAMBDA_LOG_GROUP_NAME',
|
|
205
|
-
'AWS_LAMBDA_LOG_STREAM_NAME',
|
|
206
|
-
'AWS_ACCESS_KEY',
|
|
207
|
-
'AWS_ACCESS_KEY_ID',
|
|
208
|
-
'AWS_SECRET_ACCESS_KEY',
|
|
209
|
-
'AWS_SESSION_TOKEN',
|
|
210
|
-
'AWS_LAMBDA_RUNTIME_API',
|
|
211
|
-
'LAMBDA_TASK_ROOT',
|
|
212
|
-
'LAMBDA_RUNTIME_DIR',
|
|
213
|
-
'_',
|
|
214
|
-
]
|
|
215
|
-
keysToRemove.forEach(key => delete env[key as EnvKey])
|
|
216
|
-
|
|
217
|
-
const secrets = new secretsmanager.Secret(scope, 'StacksSecrets', {
|
|
218
|
-
secretName: `${props.slug}-${props.appEnv}-secrets`,
|
|
219
|
-
description: 'Secrets for the Stacks application',
|
|
220
|
-
generateSecretString: {
|
|
221
|
-
secretStringTemplate: JSON.stringify(env),
|
|
222
|
-
generateStringKey: Object.keys(env).join(',').length.toString(),
|
|
223
|
-
},
|
|
224
|
-
})
|
|
225
|
-
|
|
226
|
-
if (service.taskDefinition.executionRole) {
|
|
227
|
-
secrets.grantRead(service.taskDefinition.executionRole)
|
|
228
|
-
container.addEnvironment('SECRETS_ARN', secrets.secretArn)
|
|
229
|
-
}
|
|
230
|
-
else {
|
|
231
|
-
throw new Error('Service task execution role is undefined.')
|
|
232
|
-
}
|
|
233
|
-
|
|
234
|
-
const apiPrefix = 'api'
|
|
235
|
-
new Output(scope, 'ApiUrl', {
|
|
236
|
-
value: `https://${apiPrefix}.${props.domain}/`,
|
|
237
|
-
description: 'The URL of the deployed application',
|
|
238
|
-
})
|
|
239
|
-
|
|
240
|
-
new Output(scope, 'ApiVanityUrl', {
|
|
241
|
-
value: `http://${this.lb.loadBalancerDnsName}`,
|
|
242
|
-
description: 'The Vanity URL / DNS name of the load balancer',
|
|
243
|
-
})
|
|
244
|
-
}
|
|
245
|
-
}
|
package/src/cloud/dashboard.ts
DELETED
|
@@ -1,85 +0,0 @@
|
|
|
1
|
-
// // TODO: finish this cloudwatch dashboard
|
|
2
|
-
// // import type { aws_lambda as lambda } from 'aws-cdk-lib'
|
|
3
|
-
// import { Aws, CfnOutput as Output, aws_cloudwatch as cloudwatch } from 'aws-cdk-lib'
|
|
4
|
-
// import type { Construct } from 'constructs'
|
|
5
|
-
// import type { NestedCloudProps } from '../types'
|
|
6
|
-
//
|
|
7
|
-
// export interface DashboardStackProps extends NestedCloudProps {
|
|
8
|
-
// dashboardName?: string
|
|
9
|
-
// }
|
|
10
|
-
//
|
|
11
|
-
// export class DashboardStack {
|
|
12
|
-
// // lambdaFunction: lambda.Function
|
|
13
|
-
// dashboard: cloudwatch.Dashboard
|
|
14
|
-
//
|
|
15
|
-
// constructor(scope: Construct, props: DashboardStackProps) {
|
|
16
|
-
// const dashboardName = props.dashboardName || 'StacksDashboard'
|
|
17
|
-
//
|
|
18
|
-
// // Create Sample Lambda Function which will create metrics
|
|
19
|
-
// // this.lambdaFunction = new Function(this, 'SampleLambda', {
|
|
20
|
-
// // handler: 'lambda-handler.handler',
|
|
21
|
-
// // runtime: Runtime.PYTHON_3_7,
|
|
22
|
-
// // code: new AssetCode(`./lambda`),
|
|
23
|
-
// // memorySize: 512,
|
|
24
|
-
// // timeout: Duration.seconds(10),
|
|
25
|
-
// // })
|
|
26
|
-
//
|
|
27
|
-
// // Create CloudWatch Dashboard
|
|
28
|
-
// this.dashboard = new cloudwatch.Dashboard(scope, 'SampleLambdaDashboard', {
|
|
29
|
-
// dashboardName,
|
|
30
|
-
// })
|
|
31
|
-
//
|
|
32
|
-
// // Create Title for Dashboard
|
|
33
|
-
// this.dashboard.addWidgets(new cloudwatch.TextWidget({
|
|
34
|
-
// markdown: `# Dashboard: `,
|
|
35
|
-
// // markdown: `# Dashboard: ${this.lambdaFunction.functionName}`,
|
|
36
|
-
// height: 1,
|
|
37
|
-
// width: 24,
|
|
38
|
-
// }))
|
|
39
|
-
//
|
|
40
|
-
// // Create CloudWatch Dashboard Widgets: Errors, Invocations, Duration, Throttles
|
|
41
|
-
// this.dashboard.addWidgets(new cloudwatch.GraphWidget({
|
|
42
|
-
// title: 'Invocations',
|
|
43
|
-
// left: [this.lambdaFunction.metricInvocations()],
|
|
44
|
-
// width: 24,
|
|
45
|
-
// }))
|
|
46
|
-
//
|
|
47
|
-
// this.dashboard.addWidgets(new cloudwatch.GraphWidget({
|
|
48
|
-
// title: 'Errors',
|
|
49
|
-
// left: [this.lambdaFunction.metricErrors()],
|
|
50
|
-
// width: 24,
|
|
51
|
-
// }))
|
|
52
|
-
//
|
|
53
|
-
// this.dashboard.addWidgets(new cloudwatch.GraphWidget({
|
|
54
|
-
// title: 'Duration',
|
|
55
|
-
// left: [this.lambdaFunction.metricDuration()],
|
|
56
|
-
// width: 24,
|
|
57
|
-
// }))
|
|
58
|
-
//
|
|
59
|
-
// this.dashboard.addWidgets(new cloudwatch.GraphWidget({
|
|
60
|
-
// title: 'Throttles',
|
|
61
|
-
// left: [this.lambdaFunction.metricThrottles()],
|
|
62
|
-
// width: 24,
|
|
63
|
-
// }))
|
|
64
|
-
//
|
|
65
|
-
// // Create Widget to show last 20 Log Entries
|
|
66
|
-
// this.dashboard.addWidgets(new cloudwatch.LogQueryWidget({
|
|
67
|
-
// logGroupNames: [this.lambdaFunction.logGroup.logGroupName],
|
|
68
|
-
// queryLines: [
|
|
69
|
-
// 'fields @timestamp, @message',
|
|
70
|
-
// 'sort @timestamp desc',
|
|
71
|
-
// 'limit 20',
|
|
72
|
-
// ],
|
|
73
|
-
// width: 24,
|
|
74
|
-
// }))
|
|
75
|
-
//
|
|
76
|
-
// // Generate Output
|
|
77
|
-
// const cloudwatchDashboardURL = `https://${Aws.REGION}.console.aws.amazon.com/cloudwatch/home?region=${Aws.REGION}#dashboards:name=${dashboardName}`
|
|
78
|
-
//
|
|
79
|
-
// new Output(scope, 'DashboardOutput', {
|
|
80
|
-
// value: cloudwatchDashboardURL,
|
|
81
|
-
// description: 'The CloudWatch Dashboard URL',
|
|
82
|
-
// exportName: 'StacksDashboardURL',
|
|
83
|
-
// })
|
|
84
|
-
// }
|
|
85
|
-
// }
|
package/src/cloud/database.ts
DELETED
|
@@ -1,34 +0,0 @@
|
|
|
1
|
-
import type { aws_ec2 as ec2 } from 'aws-cdk-lib'
|
|
2
|
-
import type { Construct } from 'constructs'
|
|
3
|
-
import type { NestedCloudProps } from '../types'
|
|
4
|
-
import { aws_dynamodb as dynamodb, RemovalPolicy } from 'aws-cdk-lib'
|
|
5
|
-
|
|
6
|
-
export interface DatabaseStackProps extends NestedCloudProps {
|
|
7
|
-
vpc: ec2.Vpc
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
export class DatabaseStack {
|
|
11
|
-
database: dynamodb.Table
|
|
12
|
-
|
|
13
|
-
constructor(scope: Construct, props: DatabaseStackProps) {
|
|
14
|
-
this.database = new dynamodb.Table(scope, 'Database', {
|
|
15
|
-
tableName: `${props.slug}-${props.appEnv}-database`,
|
|
16
|
-
partitionKey: {
|
|
17
|
-
// wip
|
|
18
|
-
name: 'id',
|
|
19
|
-
type: dynamodb.AttributeType.STRING,
|
|
20
|
-
},
|
|
21
|
-
sortKey: {
|
|
22
|
-
// wip
|
|
23
|
-
name: 'sort',
|
|
24
|
-
type: dynamodb.AttributeType.STRING,
|
|
25
|
-
},
|
|
26
|
-
billingMode: dynamodb.BillingMode.PAY_PER_REQUEST,
|
|
27
|
-
readCapacity: 5,
|
|
28
|
-
writeCapacity: 5,
|
|
29
|
-
pointInTimeRecovery: true,
|
|
30
|
-
removalPolicy: RemovalPolicy.DESTROY,
|
|
31
|
-
encryption: dynamodb.TableEncryption.AWS_MANAGED,
|
|
32
|
-
})
|
|
33
|
-
}
|
|
34
|
-
}
|