@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.
@@ -1,47 +0,0 @@
1
- import type { aws_efs as efs } from 'aws-cdk-lib'
2
- import type { Construct } from 'constructs'
3
- import type { NestedCloudProps } from '../types'
4
- import { aws_ec2 as ec2, aws_iam as iam, CfnOutput as Output } from 'aws-cdk-lib'
5
-
6
- export interface JumpBoxStackProps extends NestedCloudProps {
7
- vpc: ec2.Vpc
8
- fileSystem: efs.FileSystem
9
- }
10
-
11
- // export class DocsStack extends NestedStack {
12
- export class JumpBoxStack {
13
- jumpBox?: ec2.Instance
14
-
15
- constructor(scope: Construct, props: JumpBoxStackProps) {
16
- const role = new iam.Role(scope, 'JumpBoxInstanceRole', {
17
- assumedBy: new iam.ServicePrincipal('ec2.amazonaws.com'),
18
- managedPolicies: [
19
- iam.ManagedPolicy.fromAwsManagedPolicyName('AmazonSSMManagedInstanceCore'),
20
- iam.ManagedPolicy.fromAwsManagedPolicyName('CloudWatchAgentServerPolicy'),
21
- ],
22
- })
23
-
24
- // this instance needs to be created once to mount the EFS & clone the Stacks repo
25
- this.jumpBox = new ec2.Instance(scope, 'JumpBox', {
26
- vpc: props.vpc,
27
- instanceType: ec2.InstanceType.of(ec2.InstanceClass.T2, ec2.InstanceSize.MICRO),
28
- machineImage: new ec2.AmazonLinuxImage(),
29
- role,
30
- userData: ec2.UserData.custom(`
31
- #!/bin/bash
32
- yum update -y
33
- yum install -y amazon-efs-utils
34
- yum install -y git
35
- yum install -y https://s3.us-east-1.amazonaws.com/amazon-ssm-us-east-1/latest/linux_amd64/amazon-ssm-agent.rpm
36
- mkdir /mnt/efs
37
- mount -t efs ${props.fileSystem.fileSystemId}:/ /mnt/efs
38
- git clone https://github.com/stacksjs/stacks.git /mnt/efs
39
- `),
40
- })
41
-
42
- new Output(scope, 'JumpBoxInstanceId', {
43
- value: this.jumpBox.instanceId,
44
- description: 'The ID of the EC2 instance that can be used to SSH into the Stacks Cloud.',
45
- })
46
- }
47
- }
@@ -1,32 +0,0 @@
1
- import type { Construct } from 'constructs'
2
- import type { NestedCloudProps } from '../types'
3
- import { aws_ec2 as ec2 } from 'aws-cdk-lib'
4
-
5
- export interface NetworkStackProps extends NestedCloudProps {
6
- //
7
- }
8
-
9
- export class NetworkStack {
10
- vpc: ec2.Vpc
11
-
12
- constructor(scope: Construct, props: NetworkStackProps) {
13
- this.vpc = new ec2.Vpc(scope, 'Network', {
14
- vpcName: `${props.slug}-${props.appEnv}-vpc`,
15
- ipAddresses: ec2.IpAddresses.cidr('10.0.0.0/16'),
16
- maxAzs: 3,
17
- natGateways: 0,
18
- subnetConfiguration: [
19
- {
20
- name: 'public-subnet-1',
21
- subnetType: ec2.SubnetType.PUBLIC,
22
- cidrMask: 24,
23
- },
24
- {
25
- name: 'private-subnet-1',
26
- subnetType: ec2.SubnetType.PRIVATE_ISOLATED,
27
- cidrMask: 28,
28
- },
29
- ],
30
- })
31
- }
32
- }
@@ -1,33 +0,0 @@
1
- import type { Construct } from 'constructs'
2
- import type { NestedCloudProps } from '../types'
3
- import { config } from '@stacksjs/config'
4
- import { env } from '@stacksjs/env'
5
- import { string } from '@stacksjs/strings'
6
- import { aws_iam as iam, SecretValue } from 'aws-cdk-lib'
7
-
8
- export interface PermissionsStackProps extends NestedCloudProps {
9
- //
10
- }
11
-
12
- export class PermissionsStack {
13
- constructor(scope: Construct) {
14
- const teamName = config.team.name
15
- const users = config.team.members
16
- const password = env.AWS_DEFAULT_PASSWORD || string.random()
17
-
18
- for (const name in users) {
19
- // const userEmail = users[userName]
20
- const id = `User${string.pascalCase(teamName)}${string.pascalCase(name)}`
21
- const userName = string.slug(`${teamName}-${name}`)
22
- const user = new iam.User(scope, id, {
23
- userName,
24
- password: SecretValue.unsafePlainText(password),
25
- passwordResetRequired: true,
26
- })
27
-
28
- user.addManagedPolicy(iam.ManagedPolicy.fromAwsManagedPolicyName('AdministratorAccess'))
29
-
30
- // TODO: email the userEmail their credentials
31
- }
32
- }
33
- }
@@ -1,164 +0,0 @@
1
- import type { Cluster, TaskDefinition } from 'aws-cdk-lib/aws-ecs'
2
- import type { Construct } from 'constructs'
3
- import type { NestedCloudProps } from '../types'
4
- import { path } from '@stacksjs/path'
5
- import { fs } from '@stacksjs/storage'
6
- import { pascalCase, slug } from '@stacksjs/strings'
7
- import { aws_ec2 as ec2 } from 'aws-cdk-lib'
8
- import { Rule, Schedule } from 'aws-cdk-lib/aws-events'
9
- import { EcsTask } from 'aws-cdk-lib/aws-events-targets'
10
-
11
- export interface QueueStackProps extends NestedCloudProps {
12
- cluster: Cluster
13
- taskDefinition: TaskDefinition
14
- }
15
-
16
- export class QueueStack {
17
- scope: Construct
18
- props: QueueStackProps
19
-
20
- constructor(scope: Construct, props: QueueStackProps) {
21
- this.scope = scope
22
- this.props = props
23
- }
24
-
25
- async init(): Promise<void> {
26
- const jobsDir = path.jobsPath()
27
- const actionsDir = path.appPath('Actions')
28
- const ormActionDir = path.builtUserActionsPath('src')
29
-
30
- const jobFiles = await fs.readdir(jobsDir)
31
- const actionFiles = await fs.readdir(actionsDir)
32
- const ormActionFiles = await fs.readdir(ormActionDir)
33
- const jobs = []
34
-
35
- // then, need to loop through all app/Jobs/*.ts and create a rule for each, potentially overwriting the Schedule.ts jobs
36
- for (const file of jobFiles) {
37
- if (!file.endsWith('.ts'))
38
- continue
39
-
40
- const jobPath = path.jobsPath(file)
41
-
42
- // Await the loading of the job module
43
- const job = await this.loadModule(jobPath)
44
- this.createQueueRule(job, file)
45
- jobs.push(job)
46
- }
47
-
48
- for (const ormFile of ormActionFiles) {
49
- if (!ormFile.endsWith('.ts'))
50
- continue
51
-
52
- const ormActionPath = path.builtUserActionsPath(`src/${ormFile}`)
53
-
54
- // Await the loading of the job module
55
- const ormAction = await this.loadModule(ormActionPath)
56
- this.createQueueRule(ormAction, ormFile)
57
- jobs.push(ormAction)
58
- }
59
-
60
- for (const file of actionFiles) {
61
- if (!file.endsWith('.ts'))
62
- continue
63
-
64
- const actionPath = path.appPath(`Actions/${file}`)
65
-
66
- // Await the loading of the job module
67
- const action = await this.loadModule(actionPath)
68
- this.createQueueRule(action, file)
69
- jobs.push(action)
70
- }
71
- }
72
-
73
- // Helper function to convert a rate string to a cron object for AWS Schedule
74
- cronScheduleFromRate(rate: string): {
75
- minute?: string
76
- hour?: string
77
- month?: string
78
- weekDay?: string
79
- year?: string
80
- } {
81
- // Assuming the rate is in standard cron format, split it to map to the AWS Schedule.cron() parameters
82
- const parts = rate.split(' ')
83
- return {
84
- minute: parts[0],
85
- hour: parts[1],
86
- month: parts[2],
87
- weekDay: parts[3],
88
- year: parts[4],
89
- }
90
- }
91
-
92
- async loadModule(filePath: string): Promise<{ default: any }> {
93
- const jobModule = await import(filePath)
94
-
95
- return jobModule
96
- }
97
-
98
- // TODO: narrow any type -> jobs & actions are allowed
99
- createQueueRule(module: { default: any }, file: string): void {
100
- // Now you can safely access module.default.rate
101
- const rate = module.default?.rate
102
-
103
- // if no rate or job is disabled, no need to schedule, skip
104
- if (!rate || module.default?.enabled === false)
105
- return
106
-
107
- // Convert the rate to a Schedule object
108
- const schedule = Schedule.cron(this.cronScheduleFromRate(rate))
109
-
110
- const id = `QueueRule${pascalCase(file.replace('.ts', ''))}`
111
-
112
- // Perform operations with the jobModule.default as needed
113
- const rule = new Rule(this.scope, id, {
114
- // schedule to run every second
115
- ruleName: `${this.props.appName}-${this.props.appEnv}-queue-rule-${slug(file.replace('.ts', ''))}`,
116
- schedule,
117
- })
118
-
119
- rule.addTarget(
120
- new EcsTask({
121
- cluster: this.props.cluster,
122
- taskDefinition: this.props.taskDefinition,
123
- containerOverrides: [
124
- {
125
- containerName: `${this.props.appName}-${this.props.appEnv}-api`,
126
- environment: [
127
- {
128
- name: 'QUEUE_WORKER',
129
- value: 'true',
130
- },
131
- {
132
- name: 'JOB',
133
- value: file,
134
- },
135
- {
136
- name: 'JOB_BACKOFF_FACTOR',
137
- value: module.default?.backoffFactor,
138
- },
139
- {
140
- name: 'JOB_RETRIES',
141
- value: module.default?.tries,
142
- },
143
- {
144
- name: 'JOB_INITIAL_DELAY',
145
- value: module.default?.initialDelay,
146
- },
147
- {
148
- name: 'JOB_JITTER',
149
- value: module.default?.jitter,
150
- },
151
- ],
152
- },
153
- ],
154
-
155
- retryAttempts: 1, // we utilize a custom retry mechanism in the job itself
156
- // retryAttempts: module.default.tries || 3,
157
-
158
- subnetSelection: {
159
- subnetType: ec2.SubnetType.PUBLIC, // SubnetType.PRIVATE_WITH_EGRESS
160
- },
161
- }),
162
- )
163
- }
164
- }
@@ -1,52 +0,0 @@
1
- import type { Construct } from 'constructs'
2
- import type { NestedCloudProps } from '../types'
3
- import { config } from '@stacksjs/config'
4
- import { RemovalPolicy, aws_route53 as route53, aws_s3 as s3 } from 'aws-cdk-lib'
5
-
6
- export interface RedirectsStackProps extends NestedCloudProps {
7
- //
8
- }
9
-
10
- export class RedirectsStack {
11
- redirectZones: route53.IHostedZone[] = []
12
-
13
- constructor(scope: Construct, props: RedirectsStackProps) {
14
- // for each redirect, create a bucket & redirect it to the APP_URL
15
- config.dns.redirects?.forEach((redirect: string) => {
16
- // TODO: use string-ts function here instead
17
- const slug = redirect
18
- .split('.')
19
- .map((part: string, index: number) => (index === 0 ? part : part.charAt(0).toUpperCase() + part.slice(1)))
20
- .join('') // creates a CamelCase slug from the redirect
21
- const hostedZone = route53.HostedZone.fromLookup(scope, 'HostedZone', {
22
- domainName: redirect,
23
- })
24
-
25
- const redirectBucket = new s3.Bucket(scope, `RedirectBucket${slug}`, {
26
- bucketName: `${redirect}-redirect`,
27
- websiteRedirect: {
28
- hostName: props.domain,
29
- protocol: s3.RedirectProtocol.HTTPS,
30
- },
31
- removalPolicy: RemovalPolicy.DESTROY,
32
- autoDeleteObjects: true,
33
- })
34
-
35
- new route53.CnameRecord(scope, `RedirectRecord${slug}`, {
36
- zone: hostedZone,
37
- recordName: 'redirect',
38
- domainName: redirectBucket.bucketWebsiteDomainName,
39
- })
40
- })
41
-
42
- // TODO: fix this – redirects do not work yet
43
- config.dns.redirects?.forEach((redirect: string) => {
44
- const slug = redirect
45
- .split('.')
46
- .map((part: string, index: number) => (index === 0 ? part : part.charAt(0).toUpperCase() + part.slice(1)))
47
- .join('') // creates a CamelCase slug from the redirect
48
- const hostedZone = route53.HostedZone.fromLookup(scope, `RedirectHostedZone${slug}`, { domainName: redirect })
49
- this.redirectZones.push(hostedZone)
50
- })
51
- }
52
- }
@@ -1,15 +0,0 @@
1
- {
2
- "name": "stacks-router-layer",
3
- "version": "0.65.0",
4
- "description": "",
5
- "author": "",
6
- "license": "MIT",
7
- "keywords": [],
8
- "main": "index.js",
9
- "scripts": {
10
- "test": "echo \"Error: no test specified\" && exit 1"
11
- },
12
- "dependencies": {
13
- "@stacksjs/router": "^0.64.6"
14
- }
15
- }
@@ -1,108 +0,0 @@
1
- // async manageSearchEngine() {
2
- // const vpc = this.vpc
3
-
4
- // // Security Group
5
- // const bastionSecurityGroup = new ec2.SecurityGroup(this, 'BastionSecurityGroup', {
6
- // vpc,
7
- // allowAllOutbound: true,
8
- // securityGroupName: `${this.appName}-${appEnv}-bastion-sg`,
9
- // })
10
-
11
- // const opensearchSecurityGroup = new ec2.SecurityGroup(this, 'OpenSearchSecurityGroup', {
12
- // vpc,
13
- // securityGroupName: `${this.appName}-${appEnv}-opensearch-sg`,
14
- // })
15
-
16
- // opensearchSecurityGroup.addIngressRule(bastionSecurityGroup, ec2.Port.tcp(443))
17
-
18
- // // Service-linked role that Amazon OpenSearch Service will use
19
- // const iamClient = new IAMClient({})
20
- // const response = await iamClient.send(
21
- // new ListRolesCommand({
22
- // PathPrefix: '/aws-service-role/opensearchservice.amazonaws.com/',
23
- // }),
24
- // )
25
-
26
- // // Only if the role for OpenSearch Service doesn't exist, it will be created.
27
- // if (response.Roles && response.Roles?.length === 0) {
28
- // new iam.CfnServiceLinkedRole(this, 'OpenSearchServiceLinkedRole', {
29
- // awsServiceName: 'es.amazonaws.com',
30
- // })
31
- // }
32
-
33
- // // Bastion host to access Opensearch Dashboards
34
- // new ec2.BastionHostLinux(this, 'BastionHost', {
35
- // vpc,
36
- // securityGroup: bastionSecurityGroup,
37
- // machineImage: ec2.MachineImage.latestAmazonLinux2023(),
38
- // blockDevices: [
39
- // {
40
- // deviceName: '/dev/xvda',
41
- // volume: ec2.BlockDeviceVolume.ebs(10, {
42
- // encrypted: true,
43
- // }),
44
- // },
45
- // ],
46
- // })
47
-
48
- // // OpenSearch domain
49
- // const domain = new opensearch.Domain(this, 'OpenSearchDomain', {
50
- // version: opensearch.EngineVersion.OPENSEARCH_2_9,
51
- // nodeToNodeEncryption: true,
52
- // enforceHttps: true,
53
- // encryptionAtRest: {
54
- // enabled: true,
55
- // },
56
- // vpc,
57
- // // unsure if there are "better" ways to do this
58
- // vpcSubnets: [
59
- // { subnetGroupName: `${this.appName}-${appEnv}-private-subnet-1` },
60
- // { subnetGroupName: `${this.appName}-${appEnv}-private-subnet-2` },
61
- // ],
62
-
63
- // capacity: {
64
- // masterNodes: 2,
65
- // dataNodes: 2,
66
- // multiAzWithStandbyEnabled: true,
67
- // },
68
- // ebs: {
69
- // volumeSize: 10,
70
- // volumeType: ec2.EbsDeviceVolumeType.GP3, // or opensearch.EbsVolumeType.IO1
71
- // },
72
- // removalPolicy: RemovalPolicy.DESTROY,
73
- // zoneAwareness: {
74
- // enabled: true,
75
- // availabilityZoneCount: 2,
76
- // },
77
- // securityGroups: [opensearchSecurityGroup],
78
- // })
79
-
80
- // domain.addAccessPolicies(
81
- // new iam.PolicyStatement({
82
- // principals: [new iam.AnyPrincipal()],
83
- // actions: ['es:ESHttp*'],
84
- // resources: [`${domain.domainArn}/*`],
85
- // }),
86
- // )
87
-
88
- // // // Lambda
89
- // // const dataIndexFunction = PythonFunction(this, 'DataIndex', {
90
- // // runtime: lambda.Runtime.PYTHON_3_10,
91
- // // entry: 'lambda',
92
- // // vpc,
93
- // // environment: {
94
- // // OPENSEARCH_HOST: domain.domainEndpoint,
95
- // // },
96
- // // })
97
-
98
- // // domain.connections.allowFrom(dataIndexFunction, Port.tcp(443))
99
-
100
- // // Outputs
101
- // new Output(this, 'OpenSearchDomainHost', {
102
- // value: domain.domainEndpoint,
103
- // })
104
-
105
- // // new Output(this, 'IndexingFunctionName', {
106
- // // value: dataIndexFunction.functionName,
107
- // // })
108
- // }