@stacksjs/cloud 0.66.0 → 0.68.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/{src/cloud/ai.ts → dist/ai.d.ts} +5 -9
- package/{src/cloud/cdn.ts → dist/cdn.d.ts} +9 -28
- package/dist/cli.d.ts +12 -0
- package/{src/cloud/compute.ts → dist/compute.d.ts} +13 -37
- package/{src/cloud/database.ts → dist/database.d.ts} +5 -10
- package/{src/cloud/deployment.ts → dist/deployment.d.ts} +5 -16
- package/{src/cloud/dns.ts → dist/dns.d.ts} +4 -11
- package/dist/docs.d.ts +34 -0
- package/{src/cloud/email.ts → dist/email.d.ts} +9 -29
- package/{src/cloud/file-system.ts → dist/file-system.d.ts} +6 -9
- package/dist/helpers.d.ts +50 -50
- package/dist/index.d.ts +2 -2
- package/dist/index.js +20 -23
- package/{src/cloud/jump-box.ts → dist/jump-box.d.ts} +7 -12
- package/{src/cloud/network.ts → dist/network.d.ts} +5 -8
- package/dist/origin-request.d.ts +5 -0
- package/dist/origin-request.js +6 -6
- package/{src/cloud/permissions.ts → dist/permissions.d.ts} +5 -13
- package/{src/cloud/queue.ts → dist/queue.d.ts} +9 -29
- package/{src/cloud/redirects.ts → dist/redirects.d.ts} +7 -14
- package/{src/cloud/security.ts → dist/security.d.ts} +5 -128
- package/{src/cloud/storage.ts → dist/storage.d.ts} +7 -15
- package/dist/types.d.ts +26 -25
- package/package.json +30 -32
- package/dist/cloud/ai.d.ts +0 -6
- package/dist/cloud/cache.d.ts +0 -0
- package/dist/cloud/cdn.d.ts +0 -34
- package/dist/cloud/cli.d.ts +0 -6
- package/dist/cloud/compute.d.ts +0 -17
- package/dist/cloud/dashboard.d.ts +0 -0
- package/dist/cloud/database.d.ts +0 -11
- package/dist/cloud/deployment.d.ts +0 -17
- package/dist/cloud/dns.d.ts +0 -7
- package/dist/cloud/docs.d.ts +0 -8
- package/dist/cloud/email.d.ts +0 -10
- package/dist/cloud/file-system.d.ts +0 -12
- package/dist/cloud/index.d.ts +0 -42
- package/dist/cloud/jump-box.d.ts +0 -12
- package/dist/cloud/network.d.ts +0 -8
- package/dist/cloud/permissions.d.ts +0 -6
- package/dist/cloud/queue.d.ts +0 -22
- package/dist/cloud/redirects.d.ts +0 -8
- package/dist/cloud/search-engine.d.ts +0 -0
- package/dist/cloud/security.d.ts +0 -15
- package/dist/cloud/storage.d.ts +0 -21
- package/dist/edge/origin-request.d.ts +0 -1
- package/dist/index.js.map +0 -1867
- package/src/cloud/cache.ts +0 -0
- package/src/cloud/cli.ts +0 -14
- package/src/cloud/dashboard.ts +0 -85
- package/src/cloud/docs.ts +0 -50
- package/src/cloud/index.ts +0 -141
- package/src/cloud/router-layer/nodejs/package.json +0 -15
- package/src/cloud/search-engine.ts +0 -108
- 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/helpers.ts
DELETED
|
@@ -1,800 +0,0 @@
|
|
|
1
|
-
import type { DescribeLogGroupsCommandOutput } from '@aws-sdk/client-cloudwatch-logs'
|
|
2
|
-
import type { CountryCode, RegisterDomainCommandOutput } from '@aws-sdk/client-route-53-domains'
|
|
3
|
-
import { CloudFormation } from '@aws-sdk/client-cloudformation'
|
|
4
|
-
import { CloudWatchLogsClient, DeleteLogGroupCommand, DescribeLogGroupsCommand } from '@aws-sdk/client-cloudwatch-logs'
|
|
5
|
-
import {
|
|
6
|
-
DeleteNetworkInterfaceCommand,
|
|
7
|
-
DeleteSubnetCommand,
|
|
8
|
-
DeleteVpcCommand,
|
|
9
|
-
DescribeNetworkInterfacesCommand,
|
|
10
|
-
DescribeRegionsCommand,
|
|
11
|
-
DescribeSubnetsCommand,
|
|
12
|
-
DescribeVpcsCommand,
|
|
13
|
-
DetachNetworkInterfaceCommand,
|
|
14
|
-
EC2,
|
|
15
|
-
EC2Client,
|
|
16
|
-
_InstanceType as InstanceType,
|
|
17
|
-
TerminateInstancesCommand,
|
|
18
|
-
} from '@aws-sdk/client-ec2'
|
|
19
|
-
import { DescribeFileSystemsCommand, EFSClient } from '@aws-sdk/client-efs'
|
|
20
|
-
import { IAM } from '@aws-sdk/client-iam'
|
|
21
|
-
import { Lambda } from '@aws-sdk/client-lambda'
|
|
22
|
-
import { ContactType, Route53Domains } from '@aws-sdk/client-route-53-domains'
|
|
23
|
-
import { ListBucketsCommand, S3 } from '@aws-sdk/client-s3'
|
|
24
|
-
import { SSM } from '@aws-sdk/client-ssm'
|
|
25
|
-
import { config } from '@stacksjs/config'
|
|
26
|
-
import { err, handleError, ok, type Result } from '@stacksjs/error-handling'
|
|
27
|
-
import { log } from '@stacksjs/logging'
|
|
28
|
-
import { path as p } from '@stacksjs/path'
|
|
29
|
-
import { slug } from '@stacksjs/strings'
|
|
30
|
-
|
|
31
|
-
const appEnv = config.app.env === 'local' ? 'dev' : config.app.env
|
|
32
|
-
const cloudName = `stacks-cloud-${appEnv}`
|
|
33
|
-
|
|
34
|
-
export { InstanceType }
|
|
35
|
-
|
|
36
|
-
export async function getSecurityGroupId(securityGroupName: string): Promise<Result<string | undefined, string>> {
|
|
37
|
-
const ec2 = new EC2({ region: 'us-east-1' })
|
|
38
|
-
const { SecurityGroups } = await ec2.describeSecurityGroups({
|
|
39
|
-
Filters: [{ Name: 'group-name', Values: [securityGroupName] }],
|
|
40
|
-
})
|
|
41
|
-
|
|
42
|
-
if (!SecurityGroups)
|
|
43
|
-
return err(`Security group ${securityGroupName} not found`)
|
|
44
|
-
|
|
45
|
-
if (SecurityGroups[0])
|
|
46
|
-
return ok(SecurityGroups[0].GroupId)
|
|
47
|
-
|
|
48
|
-
return err(`Security group ${securityGroupName} not found`)
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
export interface PurchaseOptions {
|
|
52
|
-
domain: string
|
|
53
|
-
years: number
|
|
54
|
-
privacy: boolean
|
|
55
|
-
autoRenew: boolean
|
|
56
|
-
adminFirstName: string
|
|
57
|
-
adminLastName: string
|
|
58
|
-
adminOrganization: string
|
|
59
|
-
adminAddressLine1: string
|
|
60
|
-
adminAddressLine2: string
|
|
61
|
-
adminCity: string
|
|
62
|
-
adminState: string
|
|
63
|
-
adminCountry: CountryCode
|
|
64
|
-
adminZip: string
|
|
65
|
-
adminPhone: string
|
|
66
|
-
adminEmail: string
|
|
67
|
-
techFirstName: string
|
|
68
|
-
techLastName: string
|
|
69
|
-
techOrganization: string
|
|
70
|
-
techAddressLine1: string
|
|
71
|
-
techAddressLine2: string
|
|
72
|
-
techCity: string
|
|
73
|
-
techState: string
|
|
74
|
-
techCountry: CountryCode
|
|
75
|
-
techZip: string
|
|
76
|
-
techPhone: string
|
|
77
|
-
techEmail: string
|
|
78
|
-
registrantFirstName: string
|
|
79
|
-
registrantLastName: string
|
|
80
|
-
registrantOrganization: string
|
|
81
|
-
registrantAddressLine1: string
|
|
82
|
-
registrantAddressLine2: string
|
|
83
|
-
registrantCity: string
|
|
84
|
-
registrantState: string
|
|
85
|
-
registrantCountry: CountryCode
|
|
86
|
-
registrantZip: string
|
|
87
|
-
registrantPhone: string
|
|
88
|
-
registrantEmail: string
|
|
89
|
-
privacyAdmin: boolean
|
|
90
|
-
privacyTech: boolean
|
|
91
|
-
privacyRegistrant: boolean
|
|
92
|
-
contactType: ContactType
|
|
93
|
-
verbose: boolean
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
export function purchaseDomain(
|
|
97
|
-
domain: string,
|
|
98
|
-
options: PurchaseOptions,
|
|
99
|
-
): Result<Promise<RegisterDomainCommandOutput>, Error> {
|
|
100
|
-
const route53domains = new Route53Domains({ region: 'us-east-1' })
|
|
101
|
-
const contactType = options.contactType.toUpperCase() as ContactType
|
|
102
|
-
|
|
103
|
-
const params = {
|
|
104
|
-
DomainName: domain,
|
|
105
|
-
DurationInYears: options.years || 1,
|
|
106
|
-
AutoRenew: options.autoRenew || true,
|
|
107
|
-
AdminContact: {
|
|
108
|
-
FirstName: options.adminFirstName,
|
|
109
|
-
LastName: options.adminLastName,
|
|
110
|
-
ContactType: contactType || ContactType.PERSON,
|
|
111
|
-
OrganizationName: options.adminOrganization,
|
|
112
|
-
AddressLine1: options.adminAddressLine1,
|
|
113
|
-
AddressLine2: options.adminAddressLine2,
|
|
114
|
-
City: options.adminCity,
|
|
115
|
-
State: options.adminState,
|
|
116
|
-
CountryCode: options.adminCountry,
|
|
117
|
-
ZipCode: options.adminZip.toString(),
|
|
118
|
-
PhoneNumber: options.adminPhone.toString().includes('+')
|
|
119
|
-
? options.adminPhone.toString()
|
|
120
|
-
: `+${options.adminPhone.toString()}`,
|
|
121
|
-
Email: options.adminEmail,
|
|
122
|
-
},
|
|
123
|
-
RegistrantContact: {
|
|
124
|
-
FirstName: options.registrantFirstName,
|
|
125
|
-
LastName: options.registrantLastName,
|
|
126
|
-
ContactType: contactType || ContactType.PERSON,
|
|
127
|
-
OrganizationName: options.registrantOrganization,
|
|
128
|
-
AddressLine1: options.registrantAddressLine1,
|
|
129
|
-
AddressLine2: options.registrantAddressLine2,
|
|
130
|
-
City: options.registrantCity,
|
|
131
|
-
State: options.registrantState,
|
|
132
|
-
CountryCode: options.registrantCountry,
|
|
133
|
-
ZipCode: options.registrantZip.toString(),
|
|
134
|
-
PhoneNumber: options.registrantPhone.toString().includes('+')
|
|
135
|
-
? options.registrantPhone.toString()
|
|
136
|
-
: `+${options.registrantPhone.toString()}`,
|
|
137
|
-
Email: options.registrantEmail,
|
|
138
|
-
},
|
|
139
|
-
TechContact: {
|
|
140
|
-
FirstName: options.techFirstName,
|
|
141
|
-
LastName: options.techLastName,
|
|
142
|
-
ContactType: contactType || ContactType.PERSON,
|
|
143
|
-
OrganizationName: options.techOrganization,
|
|
144
|
-
AddressLine1: options.techAddressLine1,
|
|
145
|
-
AddressLine2: options.techAddressLine2,
|
|
146
|
-
City: options.techCity,
|
|
147
|
-
State: options.techState,
|
|
148
|
-
CountryCode: options.techCountry,
|
|
149
|
-
ZipCode: options.techZip.toString(),
|
|
150
|
-
PhoneNumber: options.techPhone.toString().includes('+')
|
|
151
|
-
? options.techPhone.toString()
|
|
152
|
-
: `+${options.techPhone.toString()}`,
|
|
153
|
-
Email: options.techEmail,
|
|
154
|
-
},
|
|
155
|
-
PrivacyProtectAdminContact: options.privacyAdmin || options.privacy || true,
|
|
156
|
-
PrivacyProtectRegistrantContact: options.privacyRegistrant || options.privacy || true,
|
|
157
|
-
PrivacyProtectTechContact: options.privacyTech || options.privacy || true,
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
try {
|
|
161
|
-
return ok(route53domains.registerDomain(params))
|
|
162
|
-
}
|
|
163
|
-
catch (error: any) {
|
|
164
|
-
return err(error)
|
|
165
|
-
}
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
export async function getJumpBoxInstanceId(name?: string): Promise<string | undefined> {
|
|
169
|
-
if (!name)
|
|
170
|
-
name = `${cloudName}/JumpBox`
|
|
171
|
-
|
|
172
|
-
const ec2 = new EC2({ region: 'us-east-1' })
|
|
173
|
-
const data = await ec2.describeInstances({
|
|
174
|
-
Filters: [
|
|
175
|
-
{
|
|
176
|
-
Name: 'tag:Name',
|
|
177
|
-
Values: [name],
|
|
178
|
-
},
|
|
179
|
-
],
|
|
180
|
-
})
|
|
181
|
-
|
|
182
|
-
if (data.Reservations?.[0]?.Instances?.[0])
|
|
183
|
-
return data.Reservations[0].Instances[0].InstanceId
|
|
184
|
-
|
|
185
|
-
return undefined
|
|
186
|
-
}
|
|
187
|
-
|
|
188
|
-
export async function deleteEc2Instance(id: string, stackName?: string): Promise<Result<string, string>> {
|
|
189
|
-
if (!stackName)
|
|
190
|
-
stackName = cloudName
|
|
191
|
-
|
|
192
|
-
if (!id)
|
|
193
|
-
return err(`Instance ${id} not found`)
|
|
194
|
-
|
|
195
|
-
const ec2 = new EC2({ region: 'us-east-1' })
|
|
196
|
-
await ec2.terminateInstances({ InstanceIds: [id] })
|
|
197
|
-
|
|
198
|
-
return ok(`Instance ${id} is being terminated`)
|
|
199
|
-
}
|
|
200
|
-
|
|
201
|
-
export async function deleteJumpBox(stackName?: string): Promise<Result<string, string>> {
|
|
202
|
-
if (!stackName)
|
|
203
|
-
stackName = cloudName
|
|
204
|
-
|
|
205
|
-
const jumpBoxId = await getJumpBoxInstanceId()
|
|
206
|
-
|
|
207
|
-
if (!jumpBoxId)
|
|
208
|
-
return err('Jump-box not found')
|
|
209
|
-
|
|
210
|
-
log.info(`Deleting jump-box ${jumpBoxId}...`)
|
|
211
|
-
|
|
212
|
-
return await deleteEc2Instance(jumpBoxId, stackName)
|
|
213
|
-
}
|
|
214
|
-
|
|
215
|
-
export async function deleteIamUsers(): Promise<Result<string, string>> {
|
|
216
|
-
const iam = new IAM({ region: 'us-east-1' })
|
|
217
|
-
const data = await iam.listUsers({})
|
|
218
|
-
const teamName = slug(config.team.name)
|
|
219
|
-
const users
|
|
220
|
-
= data.Users?.filter((user) => {
|
|
221
|
-
const userNameLower = user.UserName?.toLowerCase()
|
|
222
|
-
return (
|
|
223
|
-
userNameLower !== 'stacks'
|
|
224
|
-
&& userNameLower !== teamName.toLowerCase()
|
|
225
|
-
&& userNameLower?.includes(teamName.toLowerCase())
|
|
226
|
-
)
|
|
227
|
-
}) || []
|
|
228
|
-
|
|
229
|
-
if (!users || users.length === 0)
|
|
230
|
-
return ok(`No Stacks IAM users found for team ${teamName}`)
|
|
231
|
-
|
|
232
|
-
const promises = users.map(async (user) => {
|
|
233
|
-
const userName = user.UserName || ''
|
|
234
|
-
|
|
235
|
-
log.info(`Deleting IAM user: ${userName}`)
|
|
236
|
-
|
|
237
|
-
// Get the list of policies attached to the user
|
|
238
|
-
const policies = await iam.listAttachedUserPolicies({ UserName: userName })
|
|
239
|
-
|
|
240
|
-
// Detach each policy
|
|
241
|
-
await Promise.all(
|
|
242
|
-
policies.AttachedPolicies?.map(policy =>
|
|
243
|
-
iam.detachUserPolicy({
|
|
244
|
-
UserName: userName,
|
|
245
|
-
PolicyArn: policy.PolicyArn || '',
|
|
246
|
-
}),
|
|
247
|
-
) || [],
|
|
248
|
-
)
|
|
249
|
-
|
|
250
|
-
// Get the list of access keys for the user
|
|
251
|
-
const accessKeys = await iam.listAccessKeys({ UserName: userName })
|
|
252
|
-
|
|
253
|
-
// Delete each access key
|
|
254
|
-
await Promise.all(
|
|
255
|
-
accessKeys.AccessKeyMetadata?.map(key =>
|
|
256
|
-
iam.deleteAccessKey({
|
|
257
|
-
UserName: userName,
|
|
258
|
-
AccessKeyId: key.AccessKeyId || '',
|
|
259
|
-
}),
|
|
260
|
-
) || [],
|
|
261
|
-
)
|
|
262
|
-
|
|
263
|
-
// Now delete the user
|
|
264
|
-
return iam.deleteUser({ UserName: userName })
|
|
265
|
-
})
|
|
266
|
-
|
|
267
|
-
await Promise.all(promises).catch((error: Error) => {
|
|
268
|
-
console.error(error)
|
|
269
|
-
return err(handleError('Error deleting Stacks IAM users'))
|
|
270
|
-
})
|
|
271
|
-
|
|
272
|
-
return ok(`Stacks IAM users deleted for team ${teamName}`)
|
|
273
|
-
}
|
|
274
|
-
|
|
275
|
-
export async function deleteStacksBuckets(): Promise<Result<string, string | Error>> {
|
|
276
|
-
try {
|
|
277
|
-
const s3 = new S3({ region: 'us-east-1' })
|
|
278
|
-
const data = await s3.listBuckets({})
|
|
279
|
-
const stacksBuckets = data.Buckets?.filter(bucket => bucket.Name?.includes('stacks'))
|
|
280
|
-
|
|
281
|
-
if (!stacksBuckets)
|
|
282
|
-
return err('No stacks buckets found')
|
|
283
|
-
|
|
284
|
-
const promises = stacksBuckets.map(async (bucket) => {
|
|
285
|
-
const bucketName = bucket.Name || ''
|
|
286
|
-
|
|
287
|
-
// Delete the bucket
|
|
288
|
-
log.info(`Deleting bucket ${bucketName}...`)
|
|
289
|
-
|
|
290
|
-
// List all objects in the bucket
|
|
291
|
-
const objects = await s3.listObjectsV2({ Bucket: bucketName })
|
|
292
|
-
log.info(`Finished listing bucket ${bucketName} objects`)
|
|
293
|
-
|
|
294
|
-
// Delete all objects
|
|
295
|
-
if (objects.Contents) {
|
|
296
|
-
log.info('Deleting bucket objects...')
|
|
297
|
-
|
|
298
|
-
await Promise.all(
|
|
299
|
-
objects.Contents.map(object =>
|
|
300
|
-
s3.deleteObject({ Bucket: bucketName, Key: object.Key || '' }).catch(error => handleError(error)),
|
|
301
|
-
),
|
|
302
|
-
)
|
|
303
|
-
|
|
304
|
-
log.info(`Finished deleting objects from bucket ${bucketName}`)
|
|
305
|
-
}
|
|
306
|
-
|
|
307
|
-
log.info(`Deleting bucket ${bucketName} versions...`)
|
|
308
|
-
try {
|
|
309
|
-
const versions = await s3.listObjectVersions({ Bucket: bucketName })
|
|
310
|
-
|
|
311
|
-
if (versions.Versions) {
|
|
312
|
-
await Promise.all(
|
|
313
|
-
versions.Versions.map(version =>
|
|
314
|
-
s3.deleteObject({
|
|
315
|
-
Bucket: bucketName,
|
|
316
|
-
Key: version.Key || '',
|
|
317
|
-
VersionId: version.VersionId,
|
|
318
|
-
}),
|
|
319
|
-
),
|
|
320
|
-
).catch(error => handleError(error))
|
|
321
|
-
log.info(`Finished deleting versions from bucket ${bucketName}`)
|
|
322
|
-
}
|
|
323
|
-
|
|
324
|
-
// Delete all delete markers
|
|
325
|
-
log.info(`Deleting bucket ${bucketName} delete markers...`)
|
|
326
|
-
|
|
327
|
-
if (versions.DeleteMarkers) {
|
|
328
|
-
await Promise.all(
|
|
329
|
-
versions.DeleteMarkers.map(marker =>
|
|
330
|
-
s3.deleteObject({
|
|
331
|
-
Bucket: bucketName,
|
|
332
|
-
Key: marker.Key || '',
|
|
333
|
-
VersionId: marker.VersionId,
|
|
334
|
-
}),
|
|
335
|
-
),
|
|
336
|
-
).catch(error => handleError(error))
|
|
337
|
-
|
|
338
|
-
log.info(`Finished deleting delete markers from bucket ${bucketName}`)
|
|
339
|
-
}
|
|
340
|
-
|
|
341
|
-
// If the bucket has uncompleted multipart uploads, you need to abort them
|
|
342
|
-
const uploads = await s3.listMultipartUploads({ Bucket: bucketName })
|
|
343
|
-
if (uploads.Uploads) {
|
|
344
|
-
log.info('Aborting bucket multipart uploads...')
|
|
345
|
-
|
|
346
|
-
await Promise.all(
|
|
347
|
-
uploads.Uploads.map(upload =>
|
|
348
|
-
s3.abortMultipartUpload({
|
|
349
|
-
Bucket: bucketName,
|
|
350
|
-
Key: upload.Key || '',
|
|
351
|
-
UploadId: upload.UploadId,
|
|
352
|
-
}),
|
|
353
|
-
),
|
|
354
|
-
).catch(error => handleError(error))
|
|
355
|
-
|
|
356
|
-
log.info(`Finished aborting multipart uploads from bucket ${bucketName}`)
|
|
357
|
-
}
|
|
358
|
-
|
|
359
|
-
await s3.deleteBucket({ Bucket: bucketName }).catch(error => handleError(error))
|
|
360
|
-
|
|
361
|
-
log.info(`Bucket ${bucketName} deleted`)
|
|
362
|
-
}
|
|
363
|
-
catch (error) {
|
|
364
|
-
log.info(`Error listing bucket ${bucketName} versions`, error)
|
|
365
|
-
}
|
|
366
|
-
})
|
|
367
|
-
|
|
368
|
-
await Promise.all(promises).catch((error: Error) => {
|
|
369
|
-
console.error(error)
|
|
370
|
-
return err(handleError('Error deleting stacks buckets'))
|
|
371
|
-
})
|
|
372
|
-
|
|
373
|
-
return ok('Stacks buckets deleted')
|
|
374
|
-
}
|
|
375
|
-
catch (error) {
|
|
376
|
-
return err(handleError('Error deleting stacks buckets', error))
|
|
377
|
-
}
|
|
378
|
-
}
|
|
379
|
-
|
|
380
|
-
export async function deleteStacksFunctions(): Promise<Result<string, string>> {
|
|
381
|
-
const lambda = new Lambda({ region: 'us-east-1' })
|
|
382
|
-
const data = await lambda.listFunctions({})
|
|
383
|
-
const stacksFunctions = data.Functions?.filter(func => func.FunctionName?.includes('stacks')) || []
|
|
384
|
-
|
|
385
|
-
if (!stacksFunctions || stacksFunctions.length === 0)
|
|
386
|
-
return ok('No stacks functions found')
|
|
387
|
-
|
|
388
|
-
const promises = stacksFunctions.map(func => lambda.deleteFunction({ FunctionName: func.FunctionName || '' }))
|
|
389
|
-
|
|
390
|
-
await Promise.all(promises).catch((error: Error) => {
|
|
391
|
-
if (error.message.includes('it is a replicated function')) {
|
|
392
|
-
log.info('Function is replicated, skipping...')
|
|
393
|
-
|
|
394
|
-
return ok('CloudFront is still deleting the some functions. Try again later.')
|
|
395
|
-
}
|
|
396
|
-
|
|
397
|
-
return err(handleError('Error deleting stacks functions', error))
|
|
398
|
-
})
|
|
399
|
-
|
|
400
|
-
return ok('Stacks functions deleted')
|
|
401
|
-
}
|
|
402
|
-
|
|
403
|
-
export async function deleteLogGroups(): Promise<Result<string, Error>> {
|
|
404
|
-
try {
|
|
405
|
-
const ec2Client = new EC2Client({ region: 'us-east-1' })
|
|
406
|
-
const { Regions } = await ec2Client.send(new DescribeRegionsCommand({}))
|
|
407
|
-
const regions = Regions?.map(region => region.RegionName) || []
|
|
408
|
-
|
|
409
|
-
for (const region of regions) {
|
|
410
|
-
const client = new CloudWatchLogsClient({ region })
|
|
411
|
-
const logGroups: DescribeLogGroupsCommandOutput = await client.send(new DescribeLogGroupsCommand({}))
|
|
412
|
-
|
|
413
|
-
if (logGroups?.logGroups) {
|
|
414
|
-
for (const group of logGroups.logGroups) {
|
|
415
|
-
const appName = config.app.name?.toLocaleLowerCase() || 'stacks'
|
|
416
|
-
if (group.logGroupName?.includes(appName))
|
|
417
|
-
await client.send(new DeleteLogGroupCommand({ logGroupName: group.logGroupName }))
|
|
418
|
-
}
|
|
419
|
-
}
|
|
420
|
-
}
|
|
421
|
-
|
|
422
|
-
return ok('Log groups deleted in all regions')
|
|
423
|
-
}
|
|
424
|
-
catch (error) {
|
|
425
|
-
return err(handleError('Error deleting log groups', error))
|
|
426
|
-
}
|
|
427
|
-
}
|
|
428
|
-
|
|
429
|
-
export async function deleteParameterStore(): Promise<Result<string, string>> {
|
|
430
|
-
const ssm = new SSM({ region: 'us-east-1' })
|
|
431
|
-
const data = await ssm.describeParameters({})
|
|
432
|
-
|
|
433
|
-
if (!data.Parameters)
|
|
434
|
-
return ok('No parameters found')
|
|
435
|
-
|
|
436
|
-
const appName = config.app.name?.toLocaleLowerCase() || 'stacks'
|
|
437
|
-
const stacksParameters = data.Parameters.filter(param => param.Name?.includes(appName)) || []
|
|
438
|
-
|
|
439
|
-
if (!stacksParameters || stacksParameters.length === 0)
|
|
440
|
-
return ok('No stacks parameters found')
|
|
441
|
-
|
|
442
|
-
const promises = stacksParameters.map(param => ssm.deleteParameter({ Name: param.Name || '' }))
|
|
443
|
-
|
|
444
|
-
await Promise.all(promises).catch((error: Error) => {
|
|
445
|
-
return err(handleError('Error deleting parameter store', error))
|
|
446
|
-
})
|
|
447
|
-
|
|
448
|
-
return ok('Parameter store deleted')
|
|
449
|
-
}
|
|
450
|
-
|
|
451
|
-
export async function deleteVpcs(): Promise<Result<string, Error>> {
|
|
452
|
-
const ec2Client = new EC2Client({ region: 'us-east-1' })
|
|
453
|
-
const vpcNamePattern = config.app.name ? `${config.app.name.toLowerCase()}-` : 'stacks-'
|
|
454
|
-
|
|
455
|
-
try {
|
|
456
|
-
// Describe all VPCs
|
|
457
|
-
const describeVpcsCommand = new DescribeVpcsCommand({})
|
|
458
|
-
const { Vpcs } = await ec2Client.send(describeVpcsCommand)
|
|
459
|
-
|
|
460
|
-
if (!Vpcs || Vpcs.length === 0) {
|
|
461
|
-
return ok('No VPCs found')
|
|
462
|
-
}
|
|
463
|
-
|
|
464
|
-
// Filter VPCs based on the name pattern
|
|
465
|
-
const vpcsToDel = Vpcs.filter(vpc => vpc.Tags?.some(tag => tag.Key === 'Name' && tag.Value === vpcNamePattern))
|
|
466
|
-
|
|
467
|
-
if (vpcsToDel.length === 0) {
|
|
468
|
-
return ok(`No VPCs found matching the pattern: ${vpcNamePattern}`)
|
|
469
|
-
}
|
|
470
|
-
|
|
471
|
-
// Delete each matching VPC
|
|
472
|
-
for (const vpc of vpcsToDel) {
|
|
473
|
-
if (vpc.VpcId) {
|
|
474
|
-
const deleteVpcCommand = new DeleteVpcCommand({ VpcId: vpc.VpcId })
|
|
475
|
-
await ec2Client.send(deleteVpcCommand)
|
|
476
|
-
log.info(`Deleted VPC: ${vpc.VpcId} (${vpcNamePattern})`)
|
|
477
|
-
}
|
|
478
|
-
}
|
|
479
|
-
|
|
480
|
-
return ok(`Deleted ${vpcsToDel.length} VPCs matching the pattern: ${vpcNamePattern}`)
|
|
481
|
-
}
|
|
482
|
-
catch (error) {
|
|
483
|
-
return err(handleError(`Error deleting VPCs: ${error}`))
|
|
484
|
-
}
|
|
485
|
-
}
|
|
486
|
-
|
|
487
|
-
export async function deleteCdkRemnants(): Promise<Result<string, Error>> {
|
|
488
|
-
try {
|
|
489
|
-
await Bun.$`rm -rf ${p.cloudPath('cdk.out/')} ${p.cloudPath('cdk.context.json')}`.text()
|
|
490
|
-
return ok('CDK remnants deleted')
|
|
491
|
-
}
|
|
492
|
-
catch (error) {
|
|
493
|
-
return err(handleError('Error deleting CDK remnants', error))
|
|
494
|
-
}
|
|
495
|
-
}
|
|
496
|
-
|
|
497
|
-
export async function deleteSubnets(): Promise<Result<string, Error>> {
|
|
498
|
-
const ec2Client = new EC2Client({ region: 'us-east-1' })
|
|
499
|
-
const subnetNamePattern = config.app.name ? `${config.app.name.toLowerCase()}-` : 'stacks-'
|
|
500
|
-
|
|
501
|
-
try {
|
|
502
|
-
// Describe all subnets
|
|
503
|
-
const describeSubnetsCommand = new DescribeSubnetsCommand({})
|
|
504
|
-
const { Subnets } = await ec2Client.send(describeSubnetsCommand)
|
|
505
|
-
|
|
506
|
-
if (!Subnets || Subnets.length === 0) {
|
|
507
|
-
return ok('No subnets found')
|
|
508
|
-
}
|
|
509
|
-
|
|
510
|
-
// Filter subnets based on the name pattern
|
|
511
|
-
const subnetsToDel = Subnets.filter(subnet =>
|
|
512
|
-
subnet.Tags?.some(tag => tag.Key === 'Name' && tag.Value?.startsWith(subnetNamePattern)),
|
|
513
|
-
)
|
|
514
|
-
|
|
515
|
-
if (subnetsToDel.length === 0) {
|
|
516
|
-
return ok(`No subnets found matching the pattern: ${subnetNamePattern}`)
|
|
517
|
-
}
|
|
518
|
-
|
|
519
|
-
// Delete dependencies and subnets
|
|
520
|
-
for (const subnet of subnetsToDel) {
|
|
521
|
-
if (subnet.SubnetId) {
|
|
522
|
-
// Describe network interfaces in the subnet
|
|
523
|
-
const describeNIsCommand = new DescribeNetworkInterfacesCommand({
|
|
524
|
-
Filters: [{ Name: 'subnet-id', Values: [subnet.SubnetId] }],
|
|
525
|
-
})
|
|
526
|
-
const { NetworkInterfaces } = await ec2Client.send(describeNIsCommand)
|
|
527
|
-
|
|
528
|
-
// Delete network interfaces
|
|
529
|
-
for (const ni of NetworkInterfaces || []) {
|
|
530
|
-
if (ni.NetworkInterfaceId) {
|
|
531
|
-
// If the network interface is attached to an instance, terminate the instance
|
|
532
|
-
if (ni.Attachment?.InstanceId) {
|
|
533
|
-
const terminateInstanceCommand = new TerminateInstancesCommand({
|
|
534
|
-
InstanceIds: [ni.Attachment.InstanceId],
|
|
535
|
-
})
|
|
536
|
-
await ec2Client.send(terminateInstanceCommand)
|
|
537
|
-
log.info(`Terminated instance: ${ni.Attachment.InstanceId}`)
|
|
538
|
-
|
|
539
|
-
// Wait for the instance to terminate
|
|
540
|
-
await new Promise(resolve => setTimeout(resolve, 60000)) // Wait for 60 seconds
|
|
541
|
-
}
|
|
542
|
-
|
|
543
|
-
// Detach the network interface if it's attached
|
|
544
|
-
if (ni.Attachment?.AttachmentId) {
|
|
545
|
-
const detachCommand = new DetachNetworkInterfaceCommand({
|
|
546
|
-
AttachmentId: ni.Attachment.AttachmentId,
|
|
547
|
-
Force: true,
|
|
548
|
-
})
|
|
549
|
-
await ec2Client.send(detachCommand)
|
|
550
|
-
log.info(`Detached network interface: ${ni.NetworkInterfaceId}`)
|
|
551
|
-
|
|
552
|
-
// Wait for the detachment to complete
|
|
553
|
-
await new Promise(resolve => setTimeout(resolve, 10000)) // Wait for 10 seconds
|
|
554
|
-
}
|
|
555
|
-
|
|
556
|
-
// Delete the network interface
|
|
557
|
-
const deleteNICommand = new DeleteNetworkInterfaceCommand({
|
|
558
|
-
NetworkInterfaceId: ni.NetworkInterfaceId,
|
|
559
|
-
})
|
|
560
|
-
await ec2Client.send(deleteNICommand)
|
|
561
|
-
log.info(`Deleted network interface: ${ni.NetworkInterfaceId}`)
|
|
562
|
-
}
|
|
563
|
-
}
|
|
564
|
-
|
|
565
|
-
// Delete the subnet
|
|
566
|
-
const deleteSubnetCommand = new DeleteSubnetCommand({ SubnetId: subnet.SubnetId })
|
|
567
|
-
await ec2Client.send(deleteSubnetCommand)
|
|
568
|
-
log.info(`Deleted subnet: ${subnet.SubnetId} (${subnet.Tags?.find(tag => tag.Key === 'Name')?.Value})`)
|
|
569
|
-
}
|
|
570
|
-
}
|
|
571
|
-
|
|
572
|
-
return ok(`Deleted ${subnetsToDel.length} subnets matching the pattern: ${subnetNamePattern}`)
|
|
573
|
-
}
|
|
574
|
-
catch (error) {
|
|
575
|
-
return err(handleError(`Error deleting subnets: ${error}`))
|
|
576
|
-
}
|
|
577
|
-
}
|
|
578
|
-
|
|
579
|
-
export async function hasBeenDeployed(): Promise<Result<boolean, Error>> {
|
|
580
|
-
const s3 = new S3({ region: 'us-east-1' })
|
|
581
|
-
|
|
582
|
-
try {
|
|
583
|
-
const response = await s3.send(new ListBucketsCommand({}))
|
|
584
|
-
|
|
585
|
-
return ok(
|
|
586
|
-
response.Buckets?.some(bucket => bucket.Name?.includes(config.app.name?.toLocaleLowerCase() || 'stacks'))
|
|
587
|
-
|| false,
|
|
588
|
-
)
|
|
589
|
-
}
|
|
590
|
-
catch (error) {
|
|
591
|
-
console.error(error)
|
|
592
|
-
return err(handleError('Error checking if the app has been deployed'))
|
|
593
|
-
}
|
|
594
|
-
}
|
|
595
|
-
|
|
596
|
-
export async function getJumpBoxInstanceProfileName(): Promise<Result<string | undefined, string>> {
|
|
597
|
-
const iam = new IAM({ region: 'us-east-1' })
|
|
598
|
-
const data = await iam.listInstanceProfiles({})
|
|
599
|
-
const instanceProfile = data.InstanceProfiles?.find(profile => profile.InstanceProfileName?.includes('JumpBox'))
|
|
600
|
-
|
|
601
|
-
if (!instanceProfile)
|
|
602
|
-
return err('Jump-box IAM instance profile not found')
|
|
603
|
-
|
|
604
|
-
return ok(instanceProfile?.InstanceProfileName)
|
|
605
|
-
}
|
|
606
|
-
|
|
607
|
-
export async function addJumpBox(stackName?: string): Promise<Result<string, string>> {
|
|
608
|
-
if (!stackName)
|
|
609
|
-
stackName = cloudName
|
|
610
|
-
|
|
611
|
-
if (await getJumpBoxInstanceId()) {
|
|
612
|
-
return err(
|
|
613
|
-
'The jump–box you are trying to add already exists. Please remove it & wait until it finished terminating.',
|
|
614
|
-
)
|
|
615
|
-
}
|
|
616
|
-
|
|
617
|
-
const ec2 = new EC2({ region: 'us-east-1' })
|
|
618
|
-
const r = await getJumpBoxSecurityGroupName()
|
|
619
|
-
|
|
620
|
-
if (r.isErr())
|
|
621
|
-
return err(r.error)
|
|
622
|
-
if (!r.value)
|
|
623
|
-
return err('Security group not found when adding jump-box')
|
|
624
|
-
|
|
625
|
-
const result = await getSecurityGroupId(r.value)
|
|
626
|
-
if (result.isErr())
|
|
627
|
-
return err(result.error)
|
|
628
|
-
const sgId = result.value
|
|
629
|
-
|
|
630
|
-
if (!sgId)
|
|
631
|
-
return err('Security group not found when adding jump-box')
|
|
632
|
-
|
|
633
|
-
const client = new EFSClient({ region: 'us-east-1' })
|
|
634
|
-
const command = new DescribeFileSystemsCommand({})
|
|
635
|
-
const data = await client.send(command)
|
|
636
|
-
const fileSystemName = `stacks-${config.app.env}-efs`
|
|
637
|
-
const fileSystem = data.FileSystems?.find(fs => fs.Name === fileSystemName)
|
|
638
|
-
const fileSystemId = fileSystem?.FileSystemId
|
|
639
|
-
|
|
640
|
-
if (!fileSystem || !fileSystemId)
|
|
641
|
-
return err(`EFS file system ${fileSystemName} not found`)
|
|
642
|
-
|
|
643
|
-
const userDataScript = `
|
|
644
|
-
#!/bin/bash
|
|
645
|
-
yum update -y
|
|
646
|
-
yum install -y amazon-efs-utils
|
|
647
|
-
yum install -y git
|
|
648
|
-
yum install -y https://s3.us-east-1.amazonaws.com/amazon-ssm-us-east-1/latest/linux_amd64/amazon-ssm-agent.rpm
|
|
649
|
-
mkdir /mnt/efs
|
|
650
|
-
mount -t efs ${fileSystemId}:/ /mnt/efs
|
|
651
|
-
git clone https://github.com/stacksjs/stacks.git /mnt/efs
|
|
652
|
-
`
|
|
653
|
-
|
|
654
|
-
const base64UserData = btoa(userDataScript)
|
|
655
|
-
const res = await getJumpBoxInstanceProfileName()
|
|
656
|
-
|
|
657
|
-
if (res.isErr())
|
|
658
|
-
return err(res.error)
|
|
659
|
-
|
|
660
|
-
const jumpBoxInstanceProfileName: string | undefined = res.value
|
|
661
|
-
if (!jumpBoxInstanceProfileName)
|
|
662
|
-
return err('Jump-box IAM instance profile not found')
|
|
663
|
-
|
|
664
|
-
const instance = await ec2.runInstances({
|
|
665
|
-
ImageId: 'ami-03a6eaae9938c858c', // Amazon Linux 2023 AMI
|
|
666
|
-
// ImageId: new ec2.AmazonLinuxImage(),
|
|
667
|
-
InstanceType: InstanceType.t2_micro,
|
|
668
|
-
MaxCount: 1,
|
|
669
|
-
MinCount: 1,
|
|
670
|
-
SecurityGroupIds: [sgId],
|
|
671
|
-
SubnetId: 'subnet-004c5f196358b00f0',
|
|
672
|
-
TagSpecifications: [
|
|
673
|
-
{
|
|
674
|
-
ResourceType: 'instance',
|
|
675
|
-
Tags: [
|
|
676
|
-
{
|
|
677
|
-
Key: 'Name',
|
|
678
|
-
Value: `${cloudName}-jump-box`,
|
|
679
|
-
},
|
|
680
|
-
],
|
|
681
|
-
},
|
|
682
|
-
],
|
|
683
|
-
UserData: base64UserData,
|
|
684
|
-
IamInstanceProfile: {
|
|
685
|
-
Name: jumpBoxInstanceProfileName,
|
|
686
|
-
},
|
|
687
|
-
})
|
|
688
|
-
|
|
689
|
-
return instance.Instances?.[0]
|
|
690
|
-
? ok(`Jump-box created with id ${instance.Instances[0].InstanceId}`)
|
|
691
|
-
: err('Jump-box creation failed')
|
|
692
|
-
}
|
|
693
|
-
|
|
694
|
-
export async function getJumpBoxSecurityGroupName(): Promise<Result<string | undefined, string>> {
|
|
695
|
-
const jumpBoxId = await getJumpBoxInstanceId()
|
|
696
|
-
|
|
697
|
-
if (!jumpBoxId)
|
|
698
|
-
return err('Jump-box not found')
|
|
699
|
-
|
|
700
|
-
const ec2 = new EC2({ region: 'us-east-1' })
|
|
701
|
-
const data = await ec2.describeInstances({ InstanceIds: [jumpBoxId] })
|
|
702
|
-
|
|
703
|
-
if (data.Reservations?.[0]?.Instances?.[0]) {
|
|
704
|
-
const instance = data.Reservations[0].Instances[0]
|
|
705
|
-
const securityGroups = instance.SecurityGroups
|
|
706
|
-
|
|
707
|
-
if (securityGroups?.[0])
|
|
708
|
-
return ok(securityGroups[0].GroupName)
|
|
709
|
-
}
|
|
710
|
-
|
|
711
|
-
return err('Security group not found')
|
|
712
|
-
}
|
|
713
|
-
|
|
714
|
-
export async function getSecurityGroupFromInstanceId(instanceId: string): Promise<string | undefined> {
|
|
715
|
-
const ec2 = new EC2({ region: 'us-east-1' })
|
|
716
|
-
const data = await ec2.describeInstances({ InstanceIds: [instanceId] })
|
|
717
|
-
|
|
718
|
-
if (data.Reservations?.[0]?.Instances?.[0]) {
|
|
719
|
-
const instance = data.Reservations[0].Instances[0]
|
|
720
|
-
const securityGroups = instance.SecurityGroups
|
|
721
|
-
|
|
722
|
-
if (securityGroups?.[0])
|
|
723
|
-
return securityGroups[0].GroupId // Returns the ID of the first security group
|
|
724
|
-
}
|
|
725
|
-
|
|
726
|
-
return undefined
|
|
727
|
-
}
|
|
728
|
-
|
|
729
|
-
export async function isFirstDeployment(): Promise<boolean> {
|
|
730
|
-
const stackName = cloudName
|
|
731
|
-
const cloudFormation = new CloudFormation()
|
|
732
|
-
const data = await cloudFormation.listStacks({
|
|
733
|
-
StackStatusFilter: ['CREATE_COMPLETE', 'UPDATE_COMPLETE'],
|
|
734
|
-
})
|
|
735
|
-
const isStacksCloudPresent = data.StackSummaries?.some(stack => stack.StackName === stackName)
|
|
736
|
-
|
|
737
|
-
return !isStacksCloudPresent
|
|
738
|
-
}
|
|
739
|
-
|
|
740
|
-
export async function isFailedState(): Promise<boolean> {
|
|
741
|
-
const cloudFormation = new CloudFormation()
|
|
742
|
-
const data = await cloudFormation.listStacks({
|
|
743
|
-
StackStatusFilter: ['CREATE_FAILED', 'UPDATE_FAILED', 'ROLLBACK_COMPLETE', 'UPDATE_ROLLBACK_COMPLETE'],
|
|
744
|
-
})
|
|
745
|
-
const isStacksCloudPresent = data.StackSummaries?.some(stack => stack.StackName === cloudName)
|
|
746
|
-
|
|
747
|
-
return !isStacksCloudPresent
|
|
748
|
-
}
|
|
749
|
-
|
|
750
|
-
export async function getOrCreateTimestamp(): Promise<string> {
|
|
751
|
-
const parameterName = `/stacks/timestamp`
|
|
752
|
-
const ssm = new SSM({ region: 'us-east-1' })
|
|
753
|
-
|
|
754
|
-
try {
|
|
755
|
-
const response = await ssm.getParameter({ Name: parameterName })
|
|
756
|
-
const timestamp = response.Parameter ? response.Parameter.Value : undefined
|
|
757
|
-
|
|
758
|
-
if (!timestamp)
|
|
759
|
-
throw new Error('Timestamp parameter not found')
|
|
760
|
-
|
|
761
|
-
return timestamp
|
|
762
|
-
}
|
|
763
|
-
catch (error: any) {
|
|
764
|
-
const timestamp = new Date().getTime().toString()
|
|
765
|
-
log.debug(`Creating timestamp parameter ${parameterName} with value ${timestamp}`, error)
|
|
766
|
-
|
|
767
|
-
await ssm.putParameter({
|
|
768
|
-
Name: parameterName,
|
|
769
|
-
Value: timestamp,
|
|
770
|
-
Type: 'String',
|
|
771
|
-
})
|
|
772
|
-
|
|
773
|
-
return timestamp
|
|
774
|
-
}
|
|
775
|
-
}
|
|
776
|
-
|
|
777
|
-
// get the CloudFront distribution ID of the current stack
|
|
778
|
-
export async function getCloudFrontDistributionId(): Promise<string> {
|
|
779
|
-
return ''
|
|
780
|
-
// return await runCommand(`aws cloudfront list-distributions --query "DistributionList.Items[?Origins.Items[0].DomainName=='${config.app.url}'].Id"`)
|
|
781
|
-
}
|
|
782
|
-
|
|
783
|
-
// function isProductionEnv(env: string) {
|
|
784
|
-
// return env === 'production' || env === 'prod'
|
|
785
|
-
// }
|
|
786
|
-
|
|
787
|
-
// export async function getExistingBucketNameByPrefix(prefix: string): Promise<string | undefined | null> {
|
|
788
|
-
// const s3 = new S3({ region: 'us-east-1' })
|
|
789
|
-
|
|
790
|
-
// try {
|
|
791
|
-
// const response = await s3.send(new ListBucketsCommand({}))
|
|
792
|
-
// const bucket = response.Buckets?.find(bucket => bucket.Name?.startsWith(prefix))
|
|
793
|
-
|
|
794
|
-
// return bucket ? bucket.Name : null
|
|
795
|
-
// }
|
|
796
|
-
// catch (error) {
|
|
797
|
-
// console.error('Error fetching buckets', error)
|
|
798
|
-
// return `${prefix}-${timestamp}`
|
|
799
|
-
// }
|
|
800
|
-
// }
|