@stacksjs/ts-cloud 0.1.1
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/LICENSE.md +21 -0
- package/README.md +321 -0
- package/bin/cli.ts +133 -0
- package/bin/commands/analytics.ts +328 -0
- package/bin/commands/api.ts +379 -0
- package/bin/commands/assets.ts +221 -0
- package/bin/commands/audit.ts +501 -0
- package/bin/commands/backup.ts +682 -0
- package/bin/commands/cache.ts +294 -0
- package/bin/commands/cdn.ts +281 -0
- package/bin/commands/config.ts +202 -0
- package/bin/commands/container.ts +105 -0
- package/bin/commands/cost.ts +208 -0
- package/bin/commands/database.ts +401 -0
- package/bin/commands/deploy.ts +674 -0
- package/bin/commands/domain.ts +397 -0
- package/bin/commands/email.ts +423 -0
- package/bin/commands/environment.ts +285 -0
- package/bin/commands/events.ts +424 -0
- package/bin/commands/firewall.ts +145 -0
- package/bin/commands/function.ts +116 -0
- package/bin/commands/generate.ts +280 -0
- package/bin/commands/git.ts +139 -0
- package/bin/commands/iam.ts +464 -0
- package/bin/commands/index.ts +48 -0
- package/bin/commands/init.ts +120 -0
- package/bin/commands/logs.ts +148 -0
- package/bin/commands/network.ts +579 -0
- package/bin/commands/notify.ts +489 -0
- package/bin/commands/queue.ts +407 -0
- package/bin/commands/scheduler.ts +370 -0
- package/bin/commands/secrets.ts +54 -0
- package/bin/commands/server.ts +629 -0
- package/bin/commands/shared.ts +97 -0
- package/bin/commands/ssl.ts +138 -0
- package/bin/commands/stack.ts +325 -0
- package/bin/commands/status.ts +385 -0
- package/bin/commands/storage.ts +450 -0
- package/bin/commands/team.ts +96 -0
- package/bin/commands/tunnel.ts +489 -0
- package/bin/commands/utils.ts +202 -0
- package/build.ts +15 -0
- package/cloud +2 -0
- package/package.json +99 -0
- package/src/aws/acm.ts +768 -0
- package/src/aws/application-autoscaling.ts +845 -0
- package/src/aws/bedrock.ts +4074 -0
- package/src/aws/client.ts +878 -0
- package/src/aws/cloudformation.ts +896 -0
- package/src/aws/cloudfront.ts +1531 -0
- package/src/aws/cloudwatch-logs.ts +154 -0
- package/src/aws/comprehend.ts +839 -0
- package/src/aws/connect.ts +1056 -0
- package/src/aws/deploy-imap.ts +384 -0
- package/src/aws/dynamodb.ts +340 -0
- package/src/aws/ec2.ts +1385 -0
- package/src/aws/ecr.ts +621 -0
- package/src/aws/ecs.ts +615 -0
- package/src/aws/elasticache.ts +301 -0
- package/src/aws/elbv2.ts +942 -0
- package/src/aws/email.ts +928 -0
- package/src/aws/eventbridge.ts +248 -0
- package/src/aws/iam.ts +1689 -0
- package/src/aws/imap-server.ts +2100 -0
- package/src/aws/index.ts +213 -0
- package/src/aws/kendra.ts +1097 -0
- package/src/aws/lambda.ts +786 -0
- package/src/aws/opensearch.ts +158 -0
- package/src/aws/personalize.ts +977 -0
- package/src/aws/polly.ts +559 -0
- package/src/aws/rds.ts +888 -0
- package/src/aws/rekognition.ts +846 -0
- package/src/aws/route53-domains.ts +359 -0
- package/src/aws/route53.ts +1046 -0
- package/src/aws/s3.ts +2318 -0
- package/src/aws/scheduler.ts +571 -0
- package/src/aws/secrets-manager.ts +769 -0
- package/src/aws/ses.ts +1081 -0
- package/src/aws/setup-phone.ts +104 -0
- package/src/aws/setup-sms.ts +580 -0
- package/src/aws/sms.ts +1735 -0
- package/src/aws/smtp-server.ts +531 -0
- package/src/aws/sns.ts +758 -0
- package/src/aws/sqs.ts +382 -0
- package/src/aws/ssm.ts +807 -0
- package/src/aws/sts.ts +92 -0
- package/src/aws/support.ts +391 -0
- package/src/aws/test-imap.ts +86 -0
- package/src/aws/textract.ts +780 -0
- package/src/aws/transcribe.ts +108 -0
- package/src/aws/translate.ts +641 -0
- package/src/aws/voice.ts +1379 -0
- package/src/config.ts +35 -0
- package/src/deploy/index.ts +7 -0
- package/src/deploy/static-site-external-dns.ts +906 -0
- package/src/deploy/static-site.ts +1125 -0
- package/src/dns/godaddy.ts +412 -0
- package/src/dns/index.ts +183 -0
- package/src/dns/porkbun.ts +362 -0
- package/src/dns/route53-adapter.ts +414 -0
- package/src/dns/types.ts +114 -0
- package/src/dns/validator.ts +369 -0
- package/src/generators/index.ts +5 -0
- package/src/generators/infrastructure.ts +1660 -0
- package/src/index.ts +163 -0
- package/src/push/apns.ts +452 -0
- package/src/push/fcm.ts +506 -0
- package/src/push/index.ts +58 -0
- package/src/ssl/acme-client.ts +478 -0
- package/src/ssl/index.ts +7 -0
- package/src/ssl/letsencrypt.ts +747 -0
- package/src/types.ts +2 -0
- package/src/utils/cli.ts +398 -0
- package/src/validation/index.ts +5 -0
- package/src/validation/template.ts +405 -0
- package/test/index.test.ts +128 -0
- package/tsconfig.json +18 -0
|
@@ -0,0 +1,424 @@
|
|
|
1
|
+
import type { CLI } from '@stacksjs/clapp'
|
|
2
|
+
import * as cli from '../../src/utils/cli'
|
|
3
|
+
import { EventBridgeClient } from '../../src/aws/eventbridge'
|
|
4
|
+
import { loadValidatedConfig } from './shared'
|
|
5
|
+
|
|
6
|
+
export function registerEventsCommands(app: CLI): void {
|
|
7
|
+
app
|
|
8
|
+
.command('events:list', 'List all EventBridge rules')
|
|
9
|
+
.option('--region <region>', 'AWS region')
|
|
10
|
+
.option('--bus <name>', 'Event bus name', { default: 'default' })
|
|
11
|
+
.action(async (options: { region?: string; bus: string }) => {
|
|
12
|
+
cli.header('EventBridge Rules')
|
|
13
|
+
|
|
14
|
+
try {
|
|
15
|
+
const config = await loadValidatedConfig()
|
|
16
|
+
const region = options.region || config.project.region || 'us-east-1'
|
|
17
|
+
const eventbridge = new EventBridgeClient(region)
|
|
18
|
+
|
|
19
|
+
const spinner = new cli.Spinner('Fetching rules...')
|
|
20
|
+
spinner.start()
|
|
21
|
+
|
|
22
|
+
const result = await eventbridge.listRules({ EventBusName: options.bus })
|
|
23
|
+
const rules = result.Rules || []
|
|
24
|
+
|
|
25
|
+
spinner.succeed(`Found ${rules.length} rule(s)`)
|
|
26
|
+
|
|
27
|
+
if (rules.length === 0) {
|
|
28
|
+
cli.info('No EventBridge rules found')
|
|
29
|
+
cli.info('Use `cloud events:create` to create a new rule')
|
|
30
|
+
return
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
cli.table(
|
|
34
|
+
['Name', 'State', 'Schedule/Pattern', 'Description'],
|
|
35
|
+
rules.map(rule => [
|
|
36
|
+
rule.Name || 'N/A',
|
|
37
|
+
rule.State || 'N/A',
|
|
38
|
+
rule.ScheduleExpression || (rule.EventPattern ? 'Event Pattern' : 'N/A'),
|
|
39
|
+
(rule.Description || '').substring(0, 40),
|
|
40
|
+
]),
|
|
41
|
+
)
|
|
42
|
+
}
|
|
43
|
+
catch (error: any) {
|
|
44
|
+
cli.error(`Failed to list rules: ${error.message}`)
|
|
45
|
+
process.exit(1)
|
|
46
|
+
}
|
|
47
|
+
})
|
|
48
|
+
|
|
49
|
+
app
|
|
50
|
+
.command('events:create <name>', 'Create a new EventBridge rule')
|
|
51
|
+
.option('--region <region>', 'AWS region', { default: 'us-east-1' })
|
|
52
|
+
.option('--bus <name>', 'Event bus name', { default: 'default' })
|
|
53
|
+
.option('--schedule <expression>', 'Schedule expression (rate or cron)')
|
|
54
|
+
.option('--pattern <json>', 'Event pattern JSON')
|
|
55
|
+
.option('--pattern-file <path>', 'Event pattern from JSON file')
|
|
56
|
+
.option('--description <text>', 'Rule description')
|
|
57
|
+
.option('--disabled', 'Create in disabled state')
|
|
58
|
+
.action(async (name: string, options: {
|
|
59
|
+
region: string
|
|
60
|
+
bus: string
|
|
61
|
+
schedule?: string
|
|
62
|
+
pattern?: string
|
|
63
|
+
patternFile?: string
|
|
64
|
+
description?: string
|
|
65
|
+
disabled?: boolean
|
|
66
|
+
}) => {
|
|
67
|
+
cli.header('Create EventBridge Rule')
|
|
68
|
+
|
|
69
|
+
try {
|
|
70
|
+
if (!options.schedule && !options.pattern && !options.patternFile) {
|
|
71
|
+
cli.error('Either --schedule or --pattern/--pattern-file is required')
|
|
72
|
+
cli.info('\nExamples:')
|
|
73
|
+
cli.info(' Schedule: --schedule "rate(5 minutes)"')
|
|
74
|
+
cli.info(' Pattern: --pattern \'{"source": ["aws.ec2"]}\'')
|
|
75
|
+
return
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
const eventbridge = new EventBridgeClient(options.region)
|
|
79
|
+
|
|
80
|
+
let eventPattern: string | undefined
|
|
81
|
+
|
|
82
|
+
if (options.patternFile) {
|
|
83
|
+
const file = Bun.file(options.patternFile)
|
|
84
|
+
eventPattern = await file.text()
|
|
85
|
+
}
|
|
86
|
+
else if (options.pattern) {
|
|
87
|
+
eventPattern = options.pattern
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
cli.info(`Name: ${name}`)
|
|
91
|
+
cli.info(`Event Bus: ${options.bus}`)
|
|
92
|
+
if (options.schedule) {
|
|
93
|
+
cli.info(`Schedule: ${options.schedule}`)
|
|
94
|
+
}
|
|
95
|
+
if (eventPattern) {
|
|
96
|
+
cli.info(`Event Pattern: ${eventPattern}`)
|
|
97
|
+
}
|
|
98
|
+
cli.info(`State: ${options.disabled ? 'DISABLED' : 'ENABLED'}`)
|
|
99
|
+
|
|
100
|
+
const confirmed = await cli.confirm('\nCreate this rule?', true)
|
|
101
|
+
if (!confirmed) {
|
|
102
|
+
cli.info('Operation cancelled')
|
|
103
|
+
return
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
const spinner = new cli.Spinner('Creating rule...')
|
|
107
|
+
spinner.start()
|
|
108
|
+
|
|
109
|
+
const result = await eventbridge.putRule({
|
|
110
|
+
Name: name,
|
|
111
|
+
EventBusName: options.bus,
|
|
112
|
+
ScheduleExpression: options.schedule,
|
|
113
|
+
EventPattern: eventPattern,
|
|
114
|
+
Description: options.description,
|
|
115
|
+
State: options.disabled ? 'DISABLED' : 'ENABLED',
|
|
116
|
+
})
|
|
117
|
+
|
|
118
|
+
spinner.succeed('Rule created')
|
|
119
|
+
|
|
120
|
+
cli.success(`\nRule ARN: ${result.RuleArn}`)
|
|
121
|
+
cli.info('\nNote: Add targets to the rule with `cloud events:target`')
|
|
122
|
+
}
|
|
123
|
+
catch (error: any) {
|
|
124
|
+
cli.error(`Failed to create rule: ${error.message}`)
|
|
125
|
+
process.exit(1)
|
|
126
|
+
}
|
|
127
|
+
})
|
|
128
|
+
|
|
129
|
+
app
|
|
130
|
+
.command('events:delete <name>', 'Delete an EventBridge rule')
|
|
131
|
+
.option('--region <region>', 'AWS region', { default: 'us-east-1' })
|
|
132
|
+
.option('--bus <name>', 'Event bus name', { default: 'default' })
|
|
133
|
+
.option('--force', 'Remove all targets and delete rule')
|
|
134
|
+
.action(async (name: string, options: { region: string; bus: string; force?: boolean }) => {
|
|
135
|
+
cli.header('Delete EventBridge Rule')
|
|
136
|
+
|
|
137
|
+
try {
|
|
138
|
+
const eventbridge = new EventBridgeClient(options.region)
|
|
139
|
+
|
|
140
|
+
cli.warn(`This will delete rule: ${name}`)
|
|
141
|
+
|
|
142
|
+
const confirmed = await cli.confirm('\nDelete this rule?', false)
|
|
143
|
+
if (!confirmed) {
|
|
144
|
+
cli.info('Operation cancelled')
|
|
145
|
+
return
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
const spinner = new cli.Spinner('Checking targets...')
|
|
149
|
+
spinner.start()
|
|
150
|
+
|
|
151
|
+
// Check for targets
|
|
152
|
+
const targets = await eventbridge.listTargetsByRule({
|
|
153
|
+
Rule: name,
|
|
154
|
+
EventBusName: options.bus,
|
|
155
|
+
})
|
|
156
|
+
|
|
157
|
+
if (targets.Targets && targets.Targets.length > 0) {
|
|
158
|
+
if (!options.force) {
|
|
159
|
+
spinner.fail('Rule has targets')
|
|
160
|
+
cli.info(`\nThe rule has ${targets.Targets.length} target(s).`)
|
|
161
|
+
cli.info('Use --force to remove targets and delete the rule.')
|
|
162
|
+
return
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
spinner.text = 'Removing targets...'
|
|
166
|
+
|
|
167
|
+
await eventbridge.removeTargets({
|
|
168
|
+
Rule: name,
|
|
169
|
+
EventBusName: options.bus,
|
|
170
|
+
Ids: targets.Targets.map(t => t.Id!),
|
|
171
|
+
})
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
spinner.text = 'Deleting rule...'
|
|
175
|
+
|
|
176
|
+
await eventbridge.deleteRule({
|
|
177
|
+
Name: name,
|
|
178
|
+
EventBusName: options.bus,
|
|
179
|
+
})
|
|
180
|
+
|
|
181
|
+
spinner.succeed('Rule deleted')
|
|
182
|
+
}
|
|
183
|
+
catch (error: any) {
|
|
184
|
+
cli.error(`Failed to delete rule: ${error.message}`)
|
|
185
|
+
process.exit(1)
|
|
186
|
+
}
|
|
187
|
+
})
|
|
188
|
+
|
|
189
|
+
app
|
|
190
|
+
.command('events:describe <name>', 'Show EventBridge rule details')
|
|
191
|
+
.option('--region <region>', 'AWS region', { default: 'us-east-1' })
|
|
192
|
+
.option('--bus <name>', 'Event bus name', { default: 'default' })
|
|
193
|
+
.action(async (name: string, options: { region: string; bus: string }) => {
|
|
194
|
+
cli.header(`Rule: ${name}`)
|
|
195
|
+
|
|
196
|
+
try {
|
|
197
|
+
const eventbridge = new EventBridgeClient(options.region)
|
|
198
|
+
|
|
199
|
+
const spinner = new cli.Spinner('Fetching rule...')
|
|
200
|
+
spinner.start()
|
|
201
|
+
|
|
202
|
+
const rule = await eventbridge.describeRule({
|
|
203
|
+
Name: name,
|
|
204
|
+
EventBusName: options.bus,
|
|
205
|
+
})
|
|
206
|
+
|
|
207
|
+
if (!rule) {
|
|
208
|
+
spinner.fail('Rule not found')
|
|
209
|
+
return
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
// Get targets
|
|
213
|
+
const targets = await eventbridge.listTargetsByRule({
|
|
214
|
+
Rule: name,
|
|
215
|
+
EventBusName: options.bus,
|
|
216
|
+
})
|
|
217
|
+
|
|
218
|
+
spinner.succeed('Rule loaded')
|
|
219
|
+
|
|
220
|
+
cli.info('\nRule Information:')
|
|
221
|
+
cli.info(` Name: ${rule.Name}`)
|
|
222
|
+
cli.info(` ARN: ${rule.Arn}`)
|
|
223
|
+
cli.info(` State: ${rule.State}`)
|
|
224
|
+
cli.info(` Event Bus: ${rule.EventBusName}`)
|
|
225
|
+
|
|
226
|
+
if (rule.Description) {
|
|
227
|
+
cli.info(` Description: ${rule.Description}`)
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
if (rule.ScheduleExpression) {
|
|
231
|
+
cli.info(`\nSchedule Expression: ${rule.ScheduleExpression}`)
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
if (rule.EventPattern) {
|
|
235
|
+
cli.info('\nEvent Pattern:')
|
|
236
|
+
console.log(JSON.stringify(JSON.parse(rule.EventPattern), null, 2))
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
if (targets.Targets && targets.Targets.length > 0) {
|
|
240
|
+
cli.info(`\nTargets (${targets.Targets.length}):`)
|
|
241
|
+
for (const target of targets.Targets) {
|
|
242
|
+
cli.info(` - ${target.Id}: ${target.Arn}`)
|
|
243
|
+
if (target.Input) {
|
|
244
|
+
cli.info(` Input: ${target.Input}`)
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
else {
|
|
249
|
+
cli.info('\nNo targets configured.')
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
catch (error: any) {
|
|
253
|
+
cli.error(`Failed to get rule: ${error.message}`)
|
|
254
|
+
process.exit(1)
|
|
255
|
+
}
|
|
256
|
+
})
|
|
257
|
+
|
|
258
|
+
app
|
|
259
|
+
.command('events:target <ruleName>', 'Add a target to an EventBridge rule')
|
|
260
|
+
.option('--region <region>', 'AWS region', { default: 'us-east-1' })
|
|
261
|
+
.option('--bus <name>', 'Event bus name', { default: 'default' })
|
|
262
|
+
.option('--id <id>', 'Target ID')
|
|
263
|
+
.option('--arn <arn>', 'Target ARN (Lambda, SQS, SNS, etc.)')
|
|
264
|
+
.option('--role <arn>', 'IAM role ARN (for some targets)')
|
|
265
|
+
.option('--input <json>', 'Constant JSON input')
|
|
266
|
+
.option('--input-path <path>', 'JSONPath expression for input')
|
|
267
|
+
.action(async (ruleName: string, options: {
|
|
268
|
+
region: string
|
|
269
|
+
bus: string
|
|
270
|
+
id?: string
|
|
271
|
+
arn?: string
|
|
272
|
+
role?: string
|
|
273
|
+
input?: string
|
|
274
|
+
inputPath?: string
|
|
275
|
+
}) => {
|
|
276
|
+
cli.header('Add EventBridge Target')
|
|
277
|
+
|
|
278
|
+
try {
|
|
279
|
+
if (!options.arn) {
|
|
280
|
+
cli.error('--arn is required')
|
|
281
|
+
return
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
const eventbridge = new EventBridgeClient(options.region)
|
|
285
|
+
|
|
286
|
+
const targetId = options.id || `target-${Date.now()}`
|
|
287
|
+
|
|
288
|
+
cli.info(`Rule: ${ruleName}`)
|
|
289
|
+
cli.info(`Target ID: ${targetId}`)
|
|
290
|
+
cli.info(`Target ARN: ${options.arn}`)
|
|
291
|
+
|
|
292
|
+
const confirmed = await cli.confirm('\nAdd this target?', true)
|
|
293
|
+
if (!confirmed) {
|
|
294
|
+
cli.info('Operation cancelled')
|
|
295
|
+
return
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
const spinner = new cli.Spinner('Adding target...')
|
|
299
|
+
spinner.start()
|
|
300
|
+
|
|
301
|
+
const target: any = {
|
|
302
|
+
Id: targetId,
|
|
303
|
+
Arn: options.arn,
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
if (options.role) {
|
|
307
|
+
target.RoleArn = options.role
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
if (options.input) {
|
|
311
|
+
target.Input = options.input
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
if (options.inputPath) {
|
|
315
|
+
target.InputPath = options.inputPath
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
await eventbridge.putTargets({
|
|
319
|
+
Rule: ruleName,
|
|
320
|
+
EventBusName: options.bus,
|
|
321
|
+
Targets: [target],
|
|
322
|
+
})
|
|
323
|
+
|
|
324
|
+
spinner.succeed('Target added')
|
|
325
|
+
|
|
326
|
+
cli.success(`\nTarget ${targetId} added to rule ${ruleName}`)
|
|
327
|
+
}
|
|
328
|
+
catch (error: any) {
|
|
329
|
+
cli.error(`Failed to add target: ${error.message}`)
|
|
330
|
+
process.exit(1)
|
|
331
|
+
}
|
|
332
|
+
})
|
|
333
|
+
|
|
334
|
+
app
|
|
335
|
+
.command('events:buses', 'List event buses')
|
|
336
|
+
.option('--region <region>', 'AWS region')
|
|
337
|
+
.action(async (options: { region?: string }) => {
|
|
338
|
+
cli.header('Event Buses')
|
|
339
|
+
|
|
340
|
+
try {
|
|
341
|
+
const config = await loadValidatedConfig()
|
|
342
|
+
const region = options.region || config.project.region || 'us-east-1'
|
|
343
|
+
const eventbridge = new EventBridgeClient(region)
|
|
344
|
+
|
|
345
|
+
const spinner = new cli.Spinner('Fetching event buses...')
|
|
346
|
+
spinner.start()
|
|
347
|
+
|
|
348
|
+
const result = await eventbridge.listEventBuses()
|
|
349
|
+
const buses = result.EventBuses || []
|
|
350
|
+
|
|
351
|
+
spinner.succeed(`Found ${buses.length} event bus(es)`)
|
|
352
|
+
|
|
353
|
+
if (buses.length === 0) {
|
|
354
|
+
cli.info('No event buses found')
|
|
355
|
+
return
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
cli.table(
|
|
359
|
+
['Name', 'ARN', 'Policy'],
|
|
360
|
+
buses.map(bus => [
|
|
361
|
+
bus.Name || 'N/A',
|
|
362
|
+
bus.Arn || 'N/A',
|
|
363
|
+
bus.Policy ? 'Custom' : 'Default',
|
|
364
|
+
]),
|
|
365
|
+
)
|
|
366
|
+
}
|
|
367
|
+
catch (error: any) {
|
|
368
|
+
cli.error(`Failed to list event buses: ${error.message}`)
|
|
369
|
+
process.exit(1)
|
|
370
|
+
}
|
|
371
|
+
})
|
|
372
|
+
|
|
373
|
+
app
|
|
374
|
+
.command('events:enable <name>', 'Enable an EventBridge rule')
|
|
375
|
+
.option('--region <region>', 'AWS region', { default: 'us-east-1' })
|
|
376
|
+
.option('--bus <name>', 'Event bus name', { default: 'default' })
|
|
377
|
+
.action(async (name: string, options: { region: string; bus: string }) => {
|
|
378
|
+
cli.header('Enable EventBridge Rule')
|
|
379
|
+
|
|
380
|
+
try {
|
|
381
|
+
const eventbridge = new EventBridgeClient(options.region)
|
|
382
|
+
|
|
383
|
+
const spinner = new cli.Spinner('Enabling rule...')
|
|
384
|
+
spinner.start()
|
|
385
|
+
|
|
386
|
+
await eventbridge.enableRule({
|
|
387
|
+
Name: name,
|
|
388
|
+
EventBusName: options.bus,
|
|
389
|
+
})
|
|
390
|
+
|
|
391
|
+
spinner.succeed('Rule enabled')
|
|
392
|
+
}
|
|
393
|
+
catch (error: any) {
|
|
394
|
+
cli.error(`Failed to enable rule: ${error.message}`)
|
|
395
|
+
process.exit(1)
|
|
396
|
+
}
|
|
397
|
+
})
|
|
398
|
+
|
|
399
|
+
app
|
|
400
|
+
.command('events:disable <name>', 'Disable an EventBridge rule')
|
|
401
|
+
.option('--region <region>', 'AWS region', { default: 'us-east-1' })
|
|
402
|
+
.option('--bus <name>', 'Event bus name', { default: 'default' })
|
|
403
|
+
.action(async (name: string, options: { region: string; bus: string }) => {
|
|
404
|
+
cli.header('Disable EventBridge Rule')
|
|
405
|
+
|
|
406
|
+
try {
|
|
407
|
+
const eventbridge = new EventBridgeClient(options.region)
|
|
408
|
+
|
|
409
|
+
const spinner = new cli.Spinner('Disabling rule...')
|
|
410
|
+
spinner.start()
|
|
411
|
+
|
|
412
|
+
await eventbridge.disableRule({
|
|
413
|
+
Name: name,
|
|
414
|
+
EventBusName: options.bus,
|
|
415
|
+
})
|
|
416
|
+
|
|
417
|
+
spinner.succeed('Rule disabled')
|
|
418
|
+
}
|
|
419
|
+
catch (error: any) {
|
|
420
|
+
cli.error(`Failed to disable rule: ${error.message}`)
|
|
421
|
+
process.exit(1)
|
|
422
|
+
}
|
|
423
|
+
})
|
|
424
|
+
}
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
import type { CLI } from '@stacksjs/clapp'
|
|
2
|
+
import * as cli from '../../src/utils/cli'
|
|
3
|
+
|
|
4
|
+
export function registerFirewallCommands(app: CLI): void {
|
|
5
|
+
app
|
|
6
|
+
.command('firewall:rules', 'List WAF rules')
|
|
7
|
+
.option('--env <environment>', 'Environment (production, staging, development)')
|
|
8
|
+
.action(async (options?: { env?: string }) => {
|
|
9
|
+
cli.header('WAF Rules')
|
|
10
|
+
|
|
11
|
+
const environment = options?.env || 'production'
|
|
12
|
+
|
|
13
|
+
cli.info(`Environment: ${environment}\n`)
|
|
14
|
+
|
|
15
|
+
cli.table(
|
|
16
|
+
['Rule', 'Priority', 'Action', 'Requests Blocked'],
|
|
17
|
+
[
|
|
18
|
+
['Rate Limit', '1', 'Block', '1,234'],
|
|
19
|
+
['Geo Block (CN, RU)', '2', 'Block', '567'],
|
|
20
|
+
['SQL Injection', '3', 'Block', '89'],
|
|
21
|
+
['XSS Prevention', '4', 'Block', '23'],
|
|
22
|
+
],
|
|
23
|
+
)
|
|
24
|
+
})
|
|
25
|
+
|
|
26
|
+
app
|
|
27
|
+
.command('firewall:block <ip>', 'Block an IP address')
|
|
28
|
+
.option('--reason <reason>', 'Reason for blocking')
|
|
29
|
+
.action(async (ip: string, options?: { reason?: string }) => {
|
|
30
|
+
cli.header(`Blocking IP Address`)
|
|
31
|
+
|
|
32
|
+
const reason = options?.reason || 'Manual block'
|
|
33
|
+
|
|
34
|
+
cli.info(`IP: ${ip}`)
|
|
35
|
+
cli.info(`Reason: ${reason}`)
|
|
36
|
+
|
|
37
|
+
const confirm = await cli.confirm('\nBlock this IP address?', true)
|
|
38
|
+
if (!confirm) {
|
|
39
|
+
cli.info('Operation cancelled')
|
|
40
|
+
return
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const spinner = new cli.Spinner('Adding IP to WAF block list...')
|
|
44
|
+
spinner.start()
|
|
45
|
+
|
|
46
|
+
// TODO: Add IP to WAF IP set
|
|
47
|
+
await new Promise(resolve => setTimeout(resolve, 2000))
|
|
48
|
+
|
|
49
|
+
spinner.succeed(`IP ${ip} blocked successfully`)
|
|
50
|
+
|
|
51
|
+
cli.success('\nIP blocked!')
|
|
52
|
+
cli.info('The IP address will be blocked within 60 seconds')
|
|
53
|
+
})
|
|
54
|
+
|
|
55
|
+
app
|
|
56
|
+
.command('firewall:unblock <ip>', 'Unblock an IP address')
|
|
57
|
+
.action(async (ip: string) => {
|
|
58
|
+
cli.header(`Unblocking IP Address`)
|
|
59
|
+
|
|
60
|
+
cli.info(`IP: ${ip}`)
|
|
61
|
+
|
|
62
|
+
const confirm = await cli.confirm('\nUnblock this IP address?', true)
|
|
63
|
+
if (!confirm) {
|
|
64
|
+
cli.info('Operation cancelled')
|
|
65
|
+
return
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
const spinner = new cli.Spinner('Removing IP from WAF block list...')
|
|
69
|
+
spinner.start()
|
|
70
|
+
|
|
71
|
+
// TODO: Remove IP from WAF IP set
|
|
72
|
+
await new Promise(resolve => setTimeout(resolve, 2000))
|
|
73
|
+
|
|
74
|
+
spinner.succeed(`IP ${ip} unblocked successfully`)
|
|
75
|
+
|
|
76
|
+
cli.success('\nIP unblocked!')
|
|
77
|
+
})
|
|
78
|
+
|
|
79
|
+
app
|
|
80
|
+
.command('firewall:countries', 'Manage geo-blocking')
|
|
81
|
+
.option('--add <countries>', 'Comma-separated country codes to block (e.g., CN,RU)')
|
|
82
|
+
.option('--remove <countries>', 'Comma-separated country codes to unblock')
|
|
83
|
+
.option('--list', 'List currently blocked countries')
|
|
84
|
+
.action(async (options?: { add?: string, remove?: string, list?: boolean }) => {
|
|
85
|
+
cli.header('Geo-Blocking Management')
|
|
86
|
+
|
|
87
|
+
if (options?.list) {
|
|
88
|
+
cli.info('Currently blocked countries:\n')
|
|
89
|
+
cli.table(
|
|
90
|
+
['Country Code', 'Country Name', 'Blocked Since'],
|
|
91
|
+
[
|
|
92
|
+
['CN', 'China', '2024-01-15'],
|
|
93
|
+
['RU', 'Russia', '2024-01-15'],
|
|
94
|
+
['KP', 'North Korea', '2024-01-10'],
|
|
95
|
+
],
|
|
96
|
+
)
|
|
97
|
+
}
|
|
98
|
+
else if (options?.add) {
|
|
99
|
+
const countries = options.add.split(',').map(c => c.trim().toUpperCase())
|
|
100
|
+
|
|
101
|
+
cli.info(`Countries to block: ${countries.join(', ')}`)
|
|
102
|
+
|
|
103
|
+
const confirm = await cli.confirm('\nBlock these countries?', true)
|
|
104
|
+
if (!confirm) {
|
|
105
|
+
cli.info('Operation cancelled')
|
|
106
|
+
return
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
const spinner = new cli.Spinner('Updating geo-blocking rules...')
|
|
110
|
+
spinner.start()
|
|
111
|
+
|
|
112
|
+
// TODO: Update WAF geo match statement
|
|
113
|
+
await new Promise(resolve => setTimeout(resolve, 2000))
|
|
114
|
+
|
|
115
|
+
spinner.succeed('Geo-blocking rules updated')
|
|
116
|
+
|
|
117
|
+
cli.success('\nCountries blocked!')
|
|
118
|
+
}
|
|
119
|
+
else if (options?.remove) {
|
|
120
|
+
const countries = options.remove.split(',').map(c => c.trim().toUpperCase())
|
|
121
|
+
|
|
122
|
+
cli.info(`Countries to unblock: ${countries.join(', ')}`)
|
|
123
|
+
|
|
124
|
+
const confirm = await cli.confirm('\nUnblock these countries?', true)
|
|
125
|
+
if (!confirm) {
|
|
126
|
+
cli.info('Operation cancelled')
|
|
127
|
+
return
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
const spinner = new cli.Spinner('Updating geo-blocking rules...')
|
|
131
|
+
spinner.start()
|
|
132
|
+
|
|
133
|
+
// TODO: Update WAF geo match statement
|
|
134
|
+
await new Promise(resolve => setTimeout(resolve, 2000))
|
|
135
|
+
|
|
136
|
+
spinner.succeed('Geo-blocking rules updated')
|
|
137
|
+
|
|
138
|
+
cli.success('\nCountries unblocked!')
|
|
139
|
+
}
|
|
140
|
+
else {
|
|
141
|
+
cli.info('Use --list, --add, or --remove options')
|
|
142
|
+
cli.info('Example: cloud firewall:countries --add CN,RU')
|
|
143
|
+
}
|
|
144
|
+
})
|
|
145
|
+
}
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
import type { CLI } from '@stacksjs/clapp'
|
|
2
|
+
import * as cli from '../../src/utils/cli'
|
|
3
|
+
|
|
4
|
+
export function registerFunctionCommands(app: CLI): void {
|
|
5
|
+
app
|
|
6
|
+
.command('function:list', 'List all Lambda functions')
|
|
7
|
+
.action(async () => {
|
|
8
|
+
cli.header('Listing Functions')
|
|
9
|
+
|
|
10
|
+
const functions = [
|
|
11
|
+
['api-handler', '128 MB', '30s', '15', 'nodejs20.x'],
|
|
12
|
+
['worker', '512 MB', '60s', '3', 'nodejs20.x'],
|
|
13
|
+
]
|
|
14
|
+
|
|
15
|
+
cli.table(
|
|
16
|
+
['Name', 'Memory', 'Timeout', 'Invocations (24h)', 'Runtime'],
|
|
17
|
+
functions,
|
|
18
|
+
)
|
|
19
|
+
})
|
|
20
|
+
|
|
21
|
+
app
|
|
22
|
+
.command('function:logs <name>', 'View function logs')
|
|
23
|
+
.option('--tail', 'Tail logs in real-time')
|
|
24
|
+
.option('--filter <pattern>', 'Filter logs by pattern')
|
|
25
|
+
.action(async (name: string) => {
|
|
26
|
+
cli.header(`Logs for ${name}`)
|
|
27
|
+
cli.info('Streaming logs...')
|
|
28
|
+
// TODO: Implement log streaming
|
|
29
|
+
})
|
|
30
|
+
|
|
31
|
+
app
|
|
32
|
+
.command('function:invoke <name>', 'Test function invocation')
|
|
33
|
+
.option('--payload <json>', 'Event payload as JSON')
|
|
34
|
+
.action(async (name: string, options?: { payload?: string }) => {
|
|
35
|
+
cli.header(`Invoking ${name}`)
|
|
36
|
+
|
|
37
|
+
const spinner = new cli.Spinner('Invoking function...')
|
|
38
|
+
spinner.start()
|
|
39
|
+
|
|
40
|
+
// TODO: Implement invocation
|
|
41
|
+
await new Promise(resolve => setTimeout(resolve, 1000))
|
|
42
|
+
|
|
43
|
+
spinner.succeed('Function invoked successfully')
|
|
44
|
+
})
|
|
45
|
+
|
|
46
|
+
app
|
|
47
|
+
.command('function:create <name>', 'Create a new Lambda function')
|
|
48
|
+
.option('--runtime <runtime>', 'Runtime (nodejs20.x, python3.12, etc.)', { default: 'nodejs20.x' })
|
|
49
|
+
.option('--memory <mb>', 'Memory allocation in MB', { default: '128' })
|
|
50
|
+
.option('--timeout <seconds>', 'Timeout in seconds', { default: '30' })
|
|
51
|
+
.option('--handler <handler>', 'Function handler', { default: 'index.handler' })
|
|
52
|
+
.action(async (name: string, options?: { runtime?: string, memory?: string, timeout?: string, handler?: string }) => {
|
|
53
|
+
cli.header(`Creating Lambda Function: ${name}`)
|
|
54
|
+
|
|
55
|
+
const runtime = options?.runtime || 'nodejs20.x'
|
|
56
|
+
const memory = options?.memory || '128'
|
|
57
|
+
const timeout = options?.timeout || '30'
|
|
58
|
+
const handler = options?.handler || 'index.handler'
|
|
59
|
+
|
|
60
|
+
cli.info(`Runtime: ${runtime}`)
|
|
61
|
+
cli.info(`Memory: ${memory} MB`)
|
|
62
|
+
cli.info(`Timeout: ${timeout}s`)
|
|
63
|
+
cli.info(`Handler: ${handler}`)
|
|
64
|
+
|
|
65
|
+
const spinner = new cli.Spinner('Creating function...')
|
|
66
|
+
spinner.start()
|
|
67
|
+
|
|
68
|
+
// TODO: Create function directory structure
|
|
69
|
+
// TODO: Generate basic function code
|
|
70
|
+
// TODO: Create IAM role for function
|
|
71
|
+
// TODO: Package and upload to Lambda
|
|
72
|
+
|
|
73
|
+
await new Promise(resolve => setTimeout(resolve, 2000))
|
|
74
|
+
|
|
75
|
+
spinner.succeed(`Function ${name} created successfully`)
|
|
76
|
+
|
|
77
|
+
cli.success('\nFunction created!')
|
|
78
|
+
cli.info('\nNext steps:')
|
|
79
|
+
cli.info(` - Edit the function code in functions/${name}/index.js`)
|
|
80
|
+
cli.info(` - cloud function:deploy ${name} - Deploy the function`)
|
|
81
|
+
cli.info(` - cloud function:invoke ${name} - Test the function`)
|
|
82
|
+
})
|
|
83
|
+
|
|
84
|
+
app
|
|
85
|
+
.command('function:deploy <name>', 'Deploy specific Lambda function')
|
|
86
|
+
.option('--env <environment>', 'Environment (production, staging, development)')
|
|
87
|
+
.action(async (name: string, options?: { env?: string }) => {
|
|
88
|
+
cli.header(`Deploying Function: ${name}`)
|
|
89
|
+
|
|
90
|
+
const environment = options?.env || 'production'
|
|
91
|
+
|
|
92
|
+
cli.info(`Environment: ${environment}`)
|
|
93
|
+
|
|
94
|
+
const spinner = new cli.Spinner('Packaging function...')
|
|
95
|
+
spinner.start()
|
|
96
|
+
|
|
97
|
+
// TODO: Package function code
|
|
98
|
+
// TODO: Upload to S3
|
|
99
|
+
// TODO: Update Lambda function code
|
|
100
|
+
// TODO: Publish new version
|
|
101
|
+
|
|
102
|
+
spinner.text = 'Uploading to Lambda...'
|
|
103
|
+
await new Promise(resolve => setTimeout(resolve, 1500))
|
|
104
|
+
|
|
105
|
+
spinner.text = 'Updating function configuration...'
|
|
106
|
+
await new Promise(resolve => setTimeout(resolve, 1000))
|
|
107
|
+
|
|
108
|
+
spinner.succeed(`Function ${name} deployed successfully`)
|
|
109
|
+
|
|
110
|
+
cli.success('\nDeployment complete!')
|
|
111
|
+
cli.info('\nFunction details:')
|
|
112
|
+
cli.info(` - ARN: arn:aws:lambda:us-east-1:123456789:function:${name}`)
|
|
113
|
+
cli.info(` - Version: $LATEST`)
|
|
114
|
+
cli.info(` - Last Modified: ${new Date().toISOString()}`)
|
|
115
|
+
})
|
|
116
|
+
}
|