@stacksjs/cloud 0.58.51 → 0.58.52
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 +167 -806
- package/package.json +2 -3
- package/src/cloud/aws-sdk-layer/nodejs/package.json +1 -1
- package/src/cloud/cdn.ts +59 -6
- package/src/cloud/compute.ts +29 -19
- package/src/cloud/dns.ts +8 -0
- package/src/cloud/index.ts +8 -2
- package/src/cloud/queue.ts +46 -0
- package/src/cloud/router-layer/nodejs/package.json +1 -1
- package/src/cloud/security.ts +1 -1
- package/src/helpers.ts +13 -1
- package/src/cloud/package/README.md +0 -59
- package/src/cloud/package/package.json +0 -63
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@stacksjs/cloud",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "0.58.
|
|
4
|
+
"version": "0.58.52",
|
|
5
5
|
"description": "The Stacks cloud/serverless integration & implementation.",
|
|
6
6
|
"author": "Chris Breuer",
|
|
7
7
|
"license": "MIT",
|
|
@@ -68,7 +68,6 @@
|
|
|
68
68
|
"@aws-sdk/client-ses": "^3.504.0",
|
|
69
69
|
"@aws-sdk/client-sesv2": "^3.504.0",
|
|
70
70
|
"@aws-sdk/client-ssm": "^3.504.0",
|
|
71
|
-
"@aws-sdk/lib-dynamodb": "^3.506.0",
|
|
72
71
|
"@stacksjs/config": "latest",
|
|
73
72
|
"@stacksjs/env": "latest",
|
|
74
73
|
"@stacksjs/logging": "latest",
|
|
@@ -114,7 +113,7 @@
|
|
|
114
113
|
"@stacksjs/development": "latest",
|
|
115
114
|
"@stacksjs/env": "latest",
|
|
116
115
|
"jszip": "^3.10.1",
|
|
117
|
-
"oclif": "^4.4.
|
|
116
|
+
"oclif": "^4.4.4",
|
|
118
117
|
"source-map-support": "^0.5.21"
|
|
119
118
|
}
|
|
120
119
|
}
|
package/src/cloud/cdn.ts
CHANGED
|
@@ -6,6 +6,8 @@ import { config } from '@stacksjs/config'
|
|
|
6
6
|
import { hasFiles } from '@stacksjs/storage'
|
|
7
7
|
import { path as p } from '@stacksjs/path'
|
|
8
8
|
import { env } from '@stacksjs/env'
|
|
9
|
+
import type { ApplicationLoadBalancer } from 'aws-cdk-lib/aws-elasticloadbalancingv2'
|
|
10
|
+
import * as kinesis from 'aws-cdk-lib/aws-kinesis'
|
|
9
11
|
import type { NestedCloudProps } from '../types'
|
|
10
12
|
import type { EnvKey } from '../../../../env'
|
|
11
13
|
|
|
@@ -21,6 +23,7 @@ export interface CdnStackProps extends NestedCloudProps {
|
|
|
21
23
|
cliSetupUrl: lambda.FunctionUrl
|
|
22
24
|
askAiUrl: lambda.FunctionUrl
|
|
23
25
|
summarizeAiUrl: lambda.FunctionUrl
|
|
26
|
+
lb?: ApplicationLoadBalancer
|
|
24
27
|
}
|
|
25
28
|
|
|
26
29
|
export class CdnStack {
|
|
@@ -29,6 +32,7 @@ export class CdnStack {
|
|
|
29
32
|
cdnCachePolicy: cloudfront.CachePolicy
|
|
30
33
|
apiCachePolicy: cloudfront.CachePolicy | undefined
|
|
31
34
|
vanityUrl: string
|
|
35
|
+
realtimeLogConfig: cloudfront.RealtimeLogConfig
|
|
32
36
|
props: CdnStackProps
|
|
33
37
|
|
|
34
38
|
constructor(scope: Construct, props: CdnStackProps) {
|
|
@@ -45,6 +49,47 @@ export class CdnStack {
|
|
|
45
49
|
cookieBehavior: this.getCookieBehavior(config.cloud.cdn?.cookieBehavior),
|
|
46
50
|
})
|
|
47
51
|
|
|
52
|
+
// Step 1: Create a Kinesis Firehose delivery stream for the logs
|
|
53
|
+
const logStream = new kinesis.Stream(scope, 'StacksCdnRealtimeLogStream', {
|
|
54
|
+
streamName: 'StacksCdnRealtimeLogStream',
|
|
55
|
+
retentionPeriod: Duration.days(1),
|
|
56
|
+
shardCount: 1,
|
|
57
|
+
encryption: kinesis.StreamEncryption.UNENCRYPTED,
|
|
58
|
+
})
|
|
59
|
+
|
|
60
|
+
// Create an IAM role for CloudFront to write logs to Kinesis Firehose
|
|
61
|
+
// new iam.Role(scope, 'LoggingRole', {
|
|
62
|
+
// assumedBy: new iam.ServicePrincipal('cloudfront.amazonaws.com'),
|
|
63
|
+
// inlinePolicies: {
|
|
64
|
+
// loggingPolicy: new iam.PolicyDocument({
|
|
65
|
+
// statements: [
|
|
66
|
+
// new iam.PolicyStatement({
|
|
67
|
+
// actions: ['kinesis:PutRecord', 'kinesis:PutRecordBatch'],
|
|
68
|
+
// resources: [logStream.streamArn],
|
|
69
|
+
// }),
|
|
70
|
+
// ],
|
|
71
|
+
// }),
|
|
72
|
+
// },
|
|
73
|
+
// })
|
|
74
|
+
|
|
75
|
+
// TODO: make this configurable
|
|
76
|
+
this.realtimeLogConfig = new cloudfront.RealtimeLogConfig(scope, 'StacksRealTimeLogConfig', {
|
|
77
|
+
endPoints: [
|
|
78
|
+
cloudfront.Endpoint.fromKinesisStream(logStream),
|
|
79
|
+
],
|
|
80
|
+
fields: [
|
|
81
|
+
'timestamp',
|
|
82
|
+
'c-ip',
|
|
83
|
+
'cs-method',
|
|
84
|
+
'cs-uri-stem',
|
|
85
|
+
'cs-uri-query',
|
|
86
|
+
'cs-referer',
|
|
87
|
+
'cs-user-agent',
|
|
88
|
+
'sc-status',
|
|
89
|
+
],
|
|
90
|
+
samplingRate: 100, // Adjust the sampling rate as needed
|
|
91
|
+
})
|
|
92
|
+
|
|
48
93
|
// the actual CDN distribution
|
|
49
94
|
this.distribution = new cloudfront.Distribution(scope, 'Cdn', {
|
|
50
95
|
domainNames: [props.domain],
|
|
@@ -75,6 +120,7 @@ export class CdnStack {
|
|
|
75
120
|
cachedMethods: this.cachedMethods(),
|
|
76
121
|
viewerProtocolPolicy: cloudfront.ViewerProtocolPolicy.REDIRECT_TO_HTTPS,
|
|
77
122
|
cachePolicy: this.cdnCachePolicy,
|
|
123
|
+
realtimeLogConfig: this.realtimeLogConfig,
|
|
78
124
|
},
|
|
79
125
|
|
|
80
126
|
additionalBehaviors: this.additionalBehaviors(scope, props),
|
|
@@ -188,10 +234,11 @@ export class CdnStack {
|
|
|
188
234
|
}
|
|
189
235
|
|
|
190
236
|
apiBehaviorOptions(scope: Construct, props: CdnStackProps): Record<string, cloudfront.BehaviorOptions> {
|
|
191
|
-
const hostname =
|
|
192
|
-
|
|
237
|
+
const hostname = `api.${props.domain}`
|
|
238
|
+
|
|
239
|
+
const origin = () => {
|
|
193
240
|
return new origins.HttpOrigin(hostname, {
|
|
194
|
-
originPath:
|
|
241
|
+
originPath: '/',
|
|
195
242
|
protocolPolicy: cloudfront.OriginProtocolPolicy.HTTPS_ONLY,
|
|
196
243
|
})
|
|
197
244
|
}
|
|
@@ -204,14 +251,16 @@ export class CdnStack {
|
|
|
204
251
|
cachedMethods: cloudfront.CachedMethods.CACHE_GET_HEAD_OPTIONS,
|
|
205
252
|
cachePolicy: this.setApiCachePolicy(scope),
|
|
206
253
|
viewerProtocolPolicy: cloudfront.ViewerProtocolPolicy.REDIRECT_TO_HTTPS,
|
|
254
|
+
realtimeLogConfig: this.realtimeLogConfig,
|
|
207
255
|
},
|
|
208
256
|
'/api/*': {
|
|
209
|
-
origin: origin(
|
|
257
|
+
origin: origin(),
|
|
210
258
|
compress: true,
|
|
211
259
|
allowedMethods: cloudfront.AllowedMethods.ALLOW_ALL,
|
|
212
260
|
cachedMethods: cloudfront.CachedMethods.CACHE_GET_HEAD_OPTIONS,
|
|
213
261
|
cachePolicy: this.apiCachePolicy,
|
|
214
262
|
viewerProtocolPolicy: cloudfront.ViewerProtocolPolicy.REDIRECT_TO_HTTPS,
|
|
263
|
+
realtimeLogConfig: this.realtimeLogConfig,
|
|
215
264
|
},
|
|
216
265
|
}
|
|
217
266
|
}
|
|
@@ -228,6 +277,7 @@ export class CdnStack {
|
|
|
228
277
|
cachedMethods: this.cachedMethodsFromString(config.cloud.cdn?.cachedMethods),
|
|
229
278
|
viewerProtocolPolicy: cloudfront.ViewerProtocolPolicy.REDIRECT_TO_HTTPS,
|
|
230
279
|
cachePolicy: cloudfront.CachePolicy.CACHING_OPTIMIZED,
|
|
280
|
+
realtimeLogConfig: this.realtimeLogConfig,
|
|
231
281
|
},
|
|
232
282
|
'/docs/*': {
|
|
233
283
|
origin: new origins.S3Origin(props.publicBucket, {
|
|
@@ -239,6 +289,7 @@ export class CdnStack {
|
|
|
239
289
|
cachedMethods: this.cachedMethodsFromString(config.cloud.cdn?.cachedMethods),
|
|
240
290
|
viewerProtocolPolicy: cloudfront.ViewerProtocolPolicy.REDIRECT_TO_HTTPS,
|
|
241
291
|
cachePolicy: cloudfront.CachePolicy.CACHING_OPTIMIZED,
|
|
292
|
+
realtimeLogConfig: this.realtimeLogConfig,
|
|
242
293
|
},
|
|
243
294
|
}
|
|
244
295
|
}
|
|
@@ -267,6 +318,7 @@ export class CdnStack {
|
|
|
267
318
|
allowedMethods: cloudfront.AllowedMethods.ALLOW_ALL,
|
|
268
319
|
viewerProtocolPolicy: cloudfront.ViewerProtocolPolicy.REDIRECT_TO_HTTPS,
|
|
269
320
|
cachePolicy: aiCachePolicy,
|
|
321
|
+
realtimeLogConfig: this.realtimeLogConfig,
|
|
270
322
|
},
|
|
271
323
|
'/ai/summary': {
|
|
272
324
|
origin: new origins.HttpOrigin(summaryHostname, {
|
|
@@ -277,6 +329,7 @@ export class CdnStack {
|
|
|
277
329
|
allowedMethods: cloudfront.AllowedMethods.ALLOW_ALL,
|
|
278
330
|
viewerProtocolPolicy: cloudfront.ViewerProtocolPolicy.REDIRECT_TO_HTTPS,
|
|
279
331
|
cachePolicy: aiCachePolicy,
|
|
332
|
+
realtimeLogConfig: this.realtimeLogConfig,
|
|
280
333
|
},
|
|
281
334
|
}
|
|
282
335
|
}
|
|
@@ -287,7 +340,6 @@ export class CdnStack {
|
|
|
287
340
|
return {
|
|
288
341
|
'/install': {
|
|
289
342
|
origin: new origins.HttpOrigin(hostname, {
|
|
290
|
-
// origin: new origins.HttpOrigin('tipevv3dfx35fb7ptyq7nrxtga0qkcgc.lambda-url.us-east-1.on.aws', {
|
|
291
343
|
originPath: '/cli-setup',
|
|
292
344
|
protocolPolicy: cloudfront.OriginProtocolPolicy.HTTPS_ONLY,
|
|
293
345
|
}),
|
|
@@ -303,6 +355,7 @@ export class CdnStack {
|
|
|
303
355
|
headerBehavior: cloudfront.CacheHeaderBehavior.none(),
|
|
304
356
|
queryStringBehavior: cloudfront.CacheQueryStringBehavior.none(),
|
|
305
357
|
}),
|
|
358
|
+
realtimeLogConfig: this.realtimeLogConfig, // we potentially want to allow for tracking 100% of the traffic here?
|
|
306
359
|
},
|
|
307
360
|
}
|
|
308
361
|
}
|
|
@@ -326,7 +379,7 @@ export class CdnStack {
|
|
|
326
379
|
const keysToRemove = ['_HANDLER', '_X_AMZN_TRACE_ID', 'AWS_REGION', 'AWS_EXECUTION_ENV', 'AWS_LAMBDA_FUNCTION_NAME', 'AWS_LAMBDA_FUNCTION_MEMORY_SIZE', 'AWS_LAMBDA_FUNCTION_VERSION', 'AWS_LAMBDA_INITIALIZATION_TYPE', 'AWS_LAMBDA_LOG_GROUP_NAME', 'AWS_LAMBDA_LOG_STREAM_NAME', 'AWS_ACCESS_KEY', 'AWS_ACCESS_KEY_ID', 'AWS_SECRET_ACCESS_KEY', 'AWS_SESSION_TOKEN', 'AWS_LAMBDA_RUNTIME_API', 'LAMBDA_TASK_ROOT', 'LAMBDA_RUNTIME_DIR', '_']
|
|
327
380
|
keysToRemove.forEach(key => delete env[key as EnvKey])
|
|
328
381
|
|
|
329
|
-
|
|
382
|
+
behaviorOptions = this.apiBehaviorOptions(scope, props)
|
|
330
383
|
}
|
|
331
384
|
|
|
332
385
|
// if docMode is used, we don't need to add a behavior for the docs
|
package/src/cloud/compute.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/* eslint-disable no-new */
|
|
2
|
-
import type { aws_certificatemanager as acm, aws_efs as efs
|
|
3
|
-
import { Duration, CfnOutput as Output, RemovalPolicy, aws_ec2 as ec2, aws_ecs as ecs, aws_secretsmanager as secretsmanager } from 'aws-cdk-lib'
|
|
2
|
+
import type { aws_certificatemanager as acm, aws_efs as efs } from 'aws-cdk-lib'
|
|
3
|
+
import { Duration, CfnOutput as Output, RemovalPolicy, aws_ec2 as ec2, aws_ecs as ecs, aws_route53 as route53, aws_route53_targets as route53Targets, aws_secretsmanager as secretsmanager } from 'aws-cdk-lib'
|
|
4
4
|
import type { Construct } from 'constructs'
|
|
5
5
|
import { path as p } from '@stacksjs/path'
|
|
6
6
|
import { env } from '@stacksjs/env'
|
|
@@ -18,8 +18,8 @@ export interface ComputeStackProps extends NestedCloudProps {
|
|
|
18
18
|
|
|
19
19
|
export class ComputeStack {
|
|
20
20
|
lb: elbv2.ApplicationLoadBalancer
|
|
21
|
-
|
|
22
|
-
|
|
21
|
+
cluster: ecs.Cluster
|
|
22
|
+
taskDefinition: ecs.FargateTaskDefinition
|
|
23
23
|
|
|
24
24
|
constructor(scope: Construct, props: ComputeStackProps) {
|
|
25
25
|
const vpc = props.vpc
|
|
@@ -28,12 +28,12 @@ export class ComputeStack {
|
|
|
28
28
|
if (!fileSystem)
|
|
29
29
|
throw new Error('The file system is missing. Please make sure it was created properly.')
|
|
30
30
|
|
|
31
|
-
|
|
31
|
+
this.cluster = new ecs.Cluster(scope, 'StacksCluster', {
|
|
32
32
|
clusterName: `${props.slug}-${props.appEnv}-web-server-cluster`,
|
|
33
33
|
vpc,
|
|
34
34
|
})
|
|
35
35
|
|
|
36
|
-
|
|
36
|
+
this.taskDefinition = new ecs.FargateTaskDefinition(scope, 'TaskDefinition', {
|
|
37
37
|
family: `${props.appName}-${props.appEnv}-api`,
|
|
38
38
|
memoryLimitMiB: 512, // Match your Lambda memory size
|
|
39
39
|
cpu: 256, // Choose an appropriate value
|
|
@@ -42,18 +42,18 @@ export class ComputeStack {
|
|
|
42
42
|
},
|
|
43
43
|
})
|
|
44
44
|
|
|
45
|
-
const container = taskDefinition.addContainer('WebServerContainer', {
|
|
45
|
+
const container = this.taskDefinition.addContainer('WebServerContainer', {
|
|
46
46
|
containerName: `${props.appName}-${props.appEnv}-api`,
|
|
47
47
|
image: ecs.ContainerImage.fromAsset(p.frameworkPath('server')),
|
|
48
48
|
logging: new ecs.AwsLogDriver({
|
|
49
|
-
streamPrefix: `${props.appName}-${props.appEnv}-web`,
|
|
49
|
+
streamPrefix: `${props.appName}-${props.appEnv}-web-server-logs`,
|
|
50
50
|
logGroup: new LogGroup(scope, 'StacksApiLogs', {
|
|
51
|
-
logGroupName: '/ecs/stacks-api',
|
|
51
|
+
logGroupName: '/aws/ecs/stacks-api',
|
|
52
52
|
removalPolicy: RemovalPolicy.DESTROY, // Automatically remove logs on stack deletion
|
|
53
53
|
}),
|
|
54
54
|
}),
|
|
55
55
|
healthCheck: {
|
|
56
|
-
command: ['CMD-SHELL', 'curl -f http://localhost:3000/health || exit 1'],
|
|
56
|
+
command: ['CMD-SHELL', 'curl -f http://localhost:3000/api/health || exit 1'], // requires curl inside the container which isn't available in the base image. I wonder if there is a better way
|
|
57
57
|
interval: Duration.seconds(10),
|
|
58
58
|
timeout: Duration.seconds(5),
|
|
59
59
|
retries: 3,
|
|
@@ -85,10 +85,8 @@ export class ComputeStack {
|
|
|
85
85
|
'Ingress from the public ALB',
|
|
86
86
|
)
|
|
87
87
|
|
|
88
|
-
// Assuming serviceSecurityGroup and publicLoadBalancerSG are already defined
|
|
89
|
-
serviceSecurityGroup.addIngressRule(publicLoadBalancerSG, ec2.Port.allTraffic(), 'Ingress from the public ALB')
|
|
90
|
-
|
|
91
88
|
this.lb = new elbv2.ApplicationLoadBalancer(scope, 'ApplicationLoadBalancer', {
|
|
89
|
+
http2Enabled: true,
|
|
92
90
|
loadBalancerName: `${props.appName}-${props.appEnv}-alb`,
|
|
93
91
|
vpc,
|
|
94
92
|
vpcSubnets: {
|
|
@@ -102,15 +100,21 @@ export class ComputeStack {
|
|
|
102
100
|
securityGroup: publicLoadBalancerSG,
|
|
103
101
|
})
|
|
104
102
|
|
|
103
|
+
new route53.ARecord(scope, 'ApiDomainAliasRecord', {
|
|
104
|
+
zone: props.zone,
|
|
105
|
+
recordName: 'api',
|
|
106
|
+
target: route53.RecordTarget.fromAlias(new route53Targets.LoadBalancerTarget(this.lb)),
|
|
107
|
+
})
|
|
108
|
+
|
|
105
109
|
const serviceTargetGroup = new elbv2.ApplicationTargetGroup(scope, 'ServiceTargetGroup', {
|
|
106
|
-
|
|
110
|
+
targetGroupName: `${props.appName}-${props.appEnv}-api-tg`,
|
|
107
111
|
vpc,
|
|
108
112
|
targetType: elbv2.TargetType.IP,
|
|
109
113
|
protocol: elbv2.ApplicationProtocol.HTTP,
|
|
110
114
|
port: 3000,
|
|
111
115
|
healthCheck: {
|
|
112
116
|
interval: Duration.seconds(6),
|
|
113
|
-
path: '/',
|
|
117
|
+
path: '/api/health',
|
|
114
118
|
protocol: elbv2.Protocol.HTTP,
|
|
115
119
|
timeout: Duration.seconds(5),
|
|
116
120
|
healthyThresholdCount: 2,
|
|
@@ -120,9 +124,9 @@ export class ComputeStack {
|
|
|
120
124
|
|
|
121
125
|
const service = new ecs.FargateService(scope, 'StacksApiService', {
|
|
122
126
|
serviceName: `${props.appName}-${props.appEnv}-api-service`,
|
|
123
|
-
cluster,
|
|
124
|
-
taskDefinition,
|
|
125
|
-
desiredCount:
|
|
127
|
+
cluster: this.cluster,
|
|
128
|
+
taskDefinition: this.taskDefinition,
|
|
129
|
+
desiredCount: 1,
|
|
126
130
|
assignPublicIp: true,
|
|
127
131
|
maxHealthyPercent: 200,
|
|
128
132
|
vpcSubnets: vpc.selectSubnets({
|
|
@@ -136,6 +140,12 @@ export class ComputeStack {
|
|
|
136
140
|
service.attachToApplicationTargetGroup(serviceTargetGroup)
|
|
137
141
|
publicLoadBalancerSG.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.allTraffic())
|
|
138
142
|
|
|
143
|
+
this.lb.addListener('HttpsListener', {
|
|
144
|
+
port: 443,
|
|
145
|
+
certificates: [props.certificate],
|
|
146
|
+
defaultAction: elbv2.ListenerAction.forward([serviceTargetGroup]),
|
|
147
|
+
})
|
|
148
|
+
|
|
139
149
|
this.lb.addListener('HttpListener', {
|
|
140
150
|
port: 80,
|
|
141
151
|
defaultAction: elbv2.ListenerAction.forward([serviceTargetGroup]),
|
|
@@ -144,7 +154,7 @@ export class ComputeStack {
|
|
|
144
154
|
props.fileSystem.connections.allowFromAnyIpv4(ec2.Port.tcp(2049)) // port 2049 (NFS) for EFS
|
|
145
155
|
|
|
146
156
|
const volumeName = `${props.slug}-${props.appEnv}-efs`
|
|
147
|
-
taskDefinition.addVolume({
|
|
157
|
+
this.taskDefinition.addVolume({
|
|
148
158
|
name: volumeName,
|
|
149
159
|
efsVolumeConfiguration: {
|
|
150
160
|
fileSystemId: props.fileSystem.fileSystemId,
|
package/src/cloud/dns.ts
CHANGED
|
@@ -30,5 +30,13 @@ export class DnsStack {
|
|
|
30
30
|
zone: this.zone,
|
|
31
31
|
target: route53.RecordTarget.fromAlias(new targets.BucketWebsiteTarget(wwwBucket)),
|
|
32
32
|
})
|
|
33
|
+
|
|
34
|
+
// TODO: this only needs to be created if Lemon Squeezy is being used
|
|
35
|
+
// Create a Route53 record for www.yourdomain.com
|
|
36
|
+
new route53.ARecord(scope, 'StoreAliasRecord', {
|
|
37
|
+
recordName: `store.${props.domain}`,
|
|
38
|
+
zone: this.zone,
|
|
39
|
+
target: route53.RecordTarget.fromIpAddresses('137.66.37.136'),
|
|
40
|
+
})
|
|
33
41
|
}
|
|
34
42
|
}
|
package/src/cloud/index.ts
CHANGED
|
@@ -18,6 +18,7 @@ import { RedirectsStack } from './redirects'
|
|
|
18
18
|
import { EmailStack } from './email'
|
|
19
19
|
import { PermissionsStack } from './permissions'
|
|
20
20
|
import { ComputeStack } from './compute'
|
|
21
|
+
import { QueueStack } from './queue'
|
|
21
22
|
|
|
22
23
|
// import { DashboardStack } from './dashboard'
|
|
23
24
|
|
|
@@ -72,6 +73,12 @@ export class Cloud extends Stack {
|
|
|
72
73
|
zone: dns.zone,
|
|
73
74
|
certificate: security.certificate,
|
|
74
75
|
})
|
|
76
|
+
|
|
77
|
+
new QueueStack(this, {
|
|
78
|
+
...props,
|
|
79
|
+
cluster: api.cluster,
|
|
80
|
+
taskDefinition: api.taskDefinition,
|
|
81
|
+
})
|
|
75
82
|
}
|
|
76
83
|
|
|
77
84
|
const ai = new AiStack(this, props)
|
|
@@ -89,8 +96,7 @@ export class Cloud extends Stack {
|
|
|
89
96
|
cliSetupUrl: cli.cliSetupUrl,
|
|
90
97
|
askAiUrl: ai.askAiUrl,
|
|
91
98
|
summarizeAiUrl: ai.summarizeAiUrl,
|
|
92
|
-
|
|
93
|
-
webServerUrl: api?.apiServerUrl,
|
|
99
|
+
lb: api?.lb,
|
|
94
100
|
})
|
|
95
101
|
|
|
96
102
|
new DeploymentStack(this, {
|
package/src/cloud/queue.ts
CHANGED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { aws_ec2 as ec2 } from 'aws-cdk-lib'
|
|
2
|
+
import { Rule, Schedule } from 'aws-cdk-lib/aws-events'
|
|
3
|
+
import { EcsTask } from 'aws-cdk-lib/aws-events-targets'
|
|
4
|
+
import type { Cluster, TaskDefinition } from 'aws-cdk-lib/aws-ecs'
|
|
5
|
+
import type { Construct } from 'constructs'
|
|
6
|
+
import type { NestedCloudProps } from '../types'
|
|
7
|
+
|
|
8
|
+
export interface QueueStackProps extends NestedCloudProps {
|
|
9
|
+
cluster: Cluster
|
|
10
|
+
taskDefinition: TaskDefinition
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export class QueueStack {
|
|
14
|
+
constructor(scope: Construct, props: QueueStackProps) {
|
|
15
|
+
const rule = new Rule(scope, 'Rule', {
|
|
16
|
+
// schedule to run every second
|
|
17
|
+
ruleName: `${props.appName}-${props.appEnv}-queue`,
|
|
18
|
+
schedule: Schedule.cron({ minute: '*', hour: '*', month: '*', weekDay: '*', year: '*' }),
|
|
19
|
+
// schedule: Schedule.cron({ minute: '0', hour: '0' }), // For example, every day at midnight
|
|
20
|
+
})
|
|
21
|
+
|
|
22
|
+
rule.addTarget(new EcsTask({
|
|
23
|
+
cluster: props.cluster,
|
|
24
|
+
taskDefinition: props.taskDefinition,
|
|
25
|
+
containerOverrides: [
|
|
26
|
+
{
|
|
27
|
+
containerName: `${props.appName}-${props.appEnv}-api`,
|
|
28
|
+
environment: [
|
|
29
|
+
{
|
|
30
|
+
name: 'QUEUE_WORKER',
|
|
31
|
+
value: 'true',
|
|
32
|
+
},
|
|
33
|
+
{
|
|
34
|
+
name: 'JOB',
|
|
35
|
+
value: 'DummyJob.ts',
|
|
36
|
+
},
|
|
37
|
+
],
|
|
38
|
+
},
|
|
39
|
+
],
|
|
40
|
+
retryAttempts: 3,
|
|
41
|
+
subnetSelection: {
|
|
42
|
+
subnetType: ec2.SubnetType.PUBLIC,
|
|
43
|
+
},
|
|
44
|
+
}))
|
|
45
|
+
}
|
|
46
|
+
}
|
package/src/cloud/security.ts
CHANGED
|
@@ -31,7 +31,7 @@ export class SecurityStack {
|
|
|
31
31
|
rules: this.getFirewallRules(),
|
|
32
32
|
}
|
|
33
33
|
|
|
34
|
-
this.firewall = new wafv2.CfnWebACL(scope, '
|
|
34
|
+
this.firewall = new wafv2.CfnWebACL(scope, 'StacksWebFirewall', options)
|
|
35
35
|
Tags.of(this.firewall).add('Name', 'waf-cloudfront', { priority: 300 })
|
|
36
36
|
Tags.of(this.firewall).add('Purpose', 'CloudFront', { priority: 300 })
|
|
37
37
|
Tags.of(this.firewall).add('CreatedBy', 'CloudFormation', { priority: 300 })
|
package/src/helpers.ts
CHANGED
|
@@ -186,6 +186,8 @@ export async function deleteJumpBox(stackName?: string) {
|
|
|
186
186
|
if (!jumpBoxId)
|
|
187
187
|
return err('Jump-box not found')
|
|
188
188
|
|
|
189
|
+
log.info(`Deleting jump-box ${jumpBoxId}...`)
|
|
190
|
+
|
|
189
191
|
return await deleteEc2Instance(jumpBoxId, stackName)
|
|
190
192
|
}
|
|
191
193
|
|
|
@@ -203,6 +205,7 @@ export async function deleteIamUsers() {
|
|
|
203
205
|
|
|
204
206
|
const promises = users.map(async (user) => {
|
|
205
207
|
const userName = user.UserName || ''
|
|
208
|
+
|
|
206
209
|
log.info(`Deleting IAM user: ${userName}`)
|
|
207
210
|
|
|
208
211
|
// Get the list of policies attached to the user
|
|
@@ -251,12 +254,15 @@ export async function deleteStacksBuckets() {
|
|
|
251
254
|
// List all objects in the bucket
|
|
252
255
|
const objects = await s3.listObjectsV2({ Bucket: bucketName })
|
|
253
256
|
log.info(`Finished listing bucket ${bucketName} objects`)
|
|
257
|
+
|
|
254
258
|
// Delete all objects
|
|
255
259
|
if (objects.Contents) {
|
|
256
260
|
log.info('Deleting bucket objects...')
|
|
261
|
+
|
|
257
262
|
await Promise.all(objects.Contents.map(object =>
|
|
258
263
|
s3.deleteObject({ Bucket: bucketName, Key: object.Key || '' }).catch(error => handleError(error)),
|
|
259
264
|
))
|
|
265
|
+
|
|
260
266
|
log.info(`Finished deleting objects from bucket ${bucketName}`)
|
|
261
267
|
}
|
|
262
268
|
|
|
@@ -273,10 +279,12 @@ export async function deleteStacksBuckets() {
|
|
|
273
279
|
|
|
274
280
|
// Delete all delete markers
|
|
275
281
|
log.info(`Deleting bucket ${bucketName} delete markers...`)
|
|
282
|
+
|
|
276
283
|
if (versions.DeleteMarkers) {
|
|
277
284
|
await Promise.all(versions.DeleteMarkers.map(marker =>
|
|
278
285
|
s3.deleteObject({ Bucket: bucketName, Key: marker.Key || '', VersionId: marker.VersionId }),
|
|
279
286
|
)).catch(error => handleError(error))
|
|
287
|
+
|
|
280
288
|
log.info(`Finished deleting delete markers from bucket ${bucketName}`)
|
|
281
289
|
}
|
|
282
290
|
|
|
@@ -284,13 +292,16 @@ export async function deleteStacksBuckets() {
|
|
|
284
292
|
const uploads = await s3.listMultipartUploads({ Bucket: bucketName })
|
|
285
293
|
if (uploads.Uploads) {
|
|
286
294
|
log.info('Aborting bucket multipart uploads...')
|
|
295
|
+
|
|
287
296
|
await Promise.all(uploads.Uploads.map(upload =>
|
|
288
297
|
s3.abortMultipartUpload({ Bucket: bucketName, Key: upload.Key || '', UploadId: upload.UploadId }),
|
|
289
298
|
)).catch(error => handleError(error))
|
|
299
|
+
|
|
290
300
|
log.info(`Finished aborting multipart uploads from bucket ${bucketName}`)
|
|
291
301
|
}
|
|
292
302
|
|
|
293
303
|
await s3.deleteBucket({ Bucket: bucketName }).catch(error => handleError(error))
|
|
304
|
+
|
|
294
305
|
log.info(`Bucket ${bucketName} deleted`)
|
|
295
306
|
}
|
|
296
307
|
catch (error) {
|
|
@@ -322,6 +333,7 @@ export async function deleteStacksFunctions() {
|
|
|
322
333
|
await Promise.all(promises).catch((error: Error) => {
|
|
323
334
|
if (error.message.includes('it is a replicated function')) {
|
|
324
335
|
log.info('Function is replicated, skipping...')
|
|
336
|
+
|
|
325
337
|
return ok('CloudFront is still deleting the some functions. Try again later.')
|
|
326
338
|
}
|
|
327
339
|
|
|
@@ -334,7 +346,6 @@ export async function deleteStacksFunctions() {
|
|
|
334
346
|
export async function deleteLogGroups() {
|
|
335
347
|
try {
|
|
336
348
|
const client = new CloudWatchLogsClient({ region: 'us-east-1' })
|
|
337
|
-
|
|
338
349
|
const logGroups: DescribeLogGroupsCommandOutput = await client.send(new DescribeLogGroupsCommand({}))
|
|
339
350
|
|
|
340
351
|
if (!logGroups?.logGroups)
|
|
@@ -391,6 +402,7 @@ export async function hasBeenDeployed() {
|
|
|
391
402
|
|
|
392
403
|
try {
|
|
393
404
|
const response = await s3.send(new ListBucketsCommand({}))
|
|
405
|
+
|
|
394
406
|
return ok(response.Buckets?.some(bucket => bucket.Name?.includes(config.app.name?.toLocaleLowerCase() || 'stacks')) || false)
|
|
395
407
|
}
|
|
396
408
|
catch (error) {
|
|
@@ -1,59 +0,0 @@
|
|
|
1
|
-
# Stacks Router
|
|
2
|
-
|
|
3
|
-
This package contains the Stacks Router.
|
|
4
|
-
|
|
5
|
-
## ☘️ Features
|
|
6
|
-
|
|
7
|
-
wip
|
|
8
|
-
|
|
9
|
-
- ⚡️
|
|
10
|
-
|
|
11
|
-
wip
|
|
12
|
-
|
|
13
|
-
## 🤖 Usage
|
|
14
|
-
|
|
15
|
-
wip
|
|
16
|
-
|
|
17
|
-
```bash
|
|
18
|
-
bun install -d @stacksjs/actions
|
|
19
|
-
```
|
|
20
|
-
|
|
21
|
-
Now, you can use it in your project:
|
|
22
|
-
|
|
23
|
-
```js
|
|
24
|
-
import * as router from '@stacksjs/router'
|
|
25
|
-
|
|
26
|
-
// wip
|
|
27
|
-
```
|
|
28
|
-
|
|
29
|
-
Learn more in the docs.
|
|
30
|
-
|
|
31
|
-
## 🧪 Testing
|
|
32
|
-
|
|
33
|
-
```bash
|
|
34
|
-
bun test
|
|
35
|
-
```
|
|
36
|
-
|
|
37
|
-
## 📈 Changelog
|
|
38
|
-
|
|
39
|
-
Please see our [releases](https://github.com/stacksjs/stacks/releases) page for more information on what has changed recently.
|
|
40
|
-
|
|
41
|
-
## 🚜 Contributing
|
|
42
|
-
|
|
43
|
-
Please review the [Contributing Guide](https://github.com/stacksjs/contributing) for details.
|
|
44
|
-
|
|
45
|
-
## 🏝 Community
|
|
46
|
-
|
|
47
|
-
For help, discussion about best practices, or any other conversation that would benefit from being searchable:
|
|
48
|
-
|
|
49
|
-
[Discussions on GitHub](https://github.com/stacksjs/stacks/discussions)
|
|
50
|
-
|
|
51
|
-
For casual chit-chat with others using this package:
|
|
52
|
-
|
|
53
|
-
[Join the Stacks Discord Server](https://discord.gg/stacksjs)
|
|
54
|
-
|
|
55
|
-
## 📄 License
|
|
56
|
-
|
|
57
|
-
The MIT License (MIT). Please see [LICENSE](https://github.com/stacksjs/stacks/tree/main/LICENSE.md) for more information.
|
|
58
|
-
|
|
59
|
-
Made with 💙
|
|
@@ -1,63 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "@stacksjs/router",
|
|
3
|
-
"type": "module",
|
|
4
|
-
"version": "0.58.51",
|
|
5
|
-
"description": "The Stacks framework router.",
|
|
6
|
-
"author": "Chris Breuer",
|
|
7
|
-
"license": "MIT",
|
|
8
|
-
"funding": "https://github.com/sponsors/chrisbbreuer",
|
|
9
|
-
"homepage": "https://github.com/stacksjs/stacks/tree/main/storage/framework/core/router#readme",
|
|
10
|
-
"repository": {
|
|
11
|
-
"type": "git",
|
|
12
|
-
"url": "git+https://github.com/stacksjs/stacks.git",
|
|
13
|
-
"directory": "./storage/framework/core/router"
|
|
14
|
-
},
|
|
15
|
-
"bugs": {
|
|
16
|
-
"url": "https://github.com/stacksjs/stacks/issues"
|
|
17
|
-
},
|
|
18
|
-
"keywords": [
|
|
19
|
-
"router",
|
|
20
|
-
"stacks",
|
|
21
|
-
"framework",
|
|
22
|
-
"typescript",
|
|
23
|
-
"javascript"
|
|
24
|
-
],
|
|
25
|
-
"exports": {
|
|
26
|
-
".": {
|
|
27
|
-
"bun": "./src/index.ts",
|
|
28
|
-
"import": "./dist/index.js"
|
|
29
|
-
},
|
|
30
|
-
"./*": {
|
|
31
|
-
"bun": "./src/*",
|
|
32
|
-
"import": "./dist/*"
|
|
33
|
-
}
|
|
34
|
-
},
|
|
35
|
-
"module": "dist/index.js",
|
|
36
|
-
"types": "dist/index.d.ts",
|
|
37
|
-
"contributors": [
|
|
38
|
-
"Chris Breuer <chris@stacksjs.org>"
|
|
39
|
-
],
|
|
40
|
-
"files": [
|
|
41
|
-
"README.md",
|
|
42
|
-
"dist",
|
|
43
|
-
"src"
|
|
44
|
-
],
|
|
45
|
-
"scripts": {
|
|
46
|
-
"build": "bun --bun build.ts",
|
|
47
|
-
"typecheck": "bun --bun tsc --noEmit",
|
|
48
|
-
"prepublishOnly": "bun --bun run build"
|
|
49
|
-
},
|
|
50
|
-
"peerDependencies": {
|
|
51
|
-
"@stacksjs/config": "latest",
|
|
52
|
-
"unplugin-vue-router": "^0.7.0",
|
|
53
|
-
"vue-router": "^4.2.5"
|
|
54
|
-
},
|
|
55
|
-
"dependencies": {
|
|
56
|
-
"@stacksjs/config": "latest",
|
|
57
|
-
"unplugin-vue-router": "^0.7.0",
|
|
58
|
-
"vue-router": "^4.2.5"
|
|
59
|
-
},
|
|
60
|
-
"devDependencies": {
|
|
61
|
-
"@stacksjs/development": "latest"
|
|
62
|
-
}
|
|
63
|
-
}
|