@stacksjs/ts-cloud-core 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/package.json +31 -0
- package/src/advanced-features.test.ts +465 -0
- package/src/aws/cloudformation.ts +421 -0
- package/src/aws/cloudfront.ts +158 -0
- package/src/aws/credentials.test.ts +132 -0
- package/src/aws/credentials.ts +545 -0
- package/src/aws/index.ts +87 -0
- package/src/aws/s3.test.ts +188 -0
- package/src/aws/s3.ts +1088 -0
- package/src/aws/signature.test.ts +670 -0
- package/src/aws/signature.ts +1155 -0
- package/src/backup/disaster-recovery.test.ts +726 -0
- package/src/backup/disaster-recovery.ts +500 -0
- package/src/backup/index.ts +34 -0
- package/src/backup/manager.test.ts +498 -0
- package/src/backup/manager.ts +432 -0
- package/src/cicd/circleci.ts +430 -0
- package/src/cicd/github-actions.ts +424 -0
- package/src/cicd/gitlab-ci.ts +255 -0
- package/src/cicd/index.ts +8 -0
- package/src/cli/history.ts +396 -0
- package/src/cli/index.ts +10 -0
- package/src/cli/progress.ts +458 -0
- package/src/cli/repl.ts +454 -0
- package/src/cli/suggestions.ts +327 -0
- package/src/cli/table.test.ts +319 -0
- package/src/cli/table.ts +332 -0
- package/src/cloudformation/builder.test.ts +327 -0
- package/src/cloudformation/builder.ts +378 -0
- package/src/cloudformation/builders/api-gateway.ts +449 -0
- package/src/cloudformation/builders/cache.ts +334 -0
- package/src/cloudformation/builders/cdn.ts +278 -0
- package/src/cloudformation/builders/compute.ts +485 -0
- package/src/cloudformation/builders/database.ts +392 -0
- package/src/cloudformation/builders/functions.ts +343 -0
- package/src/cloudformation/builders/messaging.ts +140 -0
- package/src/cloudformation/builders/monitoring.ts +300 -0
- package/src/cloudformation/builders/network.ts +264 -0
- package/src/cloudformation/builders/queue.ts +147 -0
- package/src/cloudformation/builders/security.ts +399 -0
- package/src/cloudformation/builders/storage.ts +285 -0
- package/src/cloudformation/index.ts +30 -0
- package/src/cloudformation/types.ts +173 -0
- package/src/compliance/aws-config.ts +543 -0
- package/src/compliance/cloudtrail.ts +376 -0
- package/src/compliance/compliance.test.ts +423 -0
- package/src/compliance/guardduty.ts +446 -0
- package/src/compliance/index.ts +66 -0
- package/src/compliance/security-hub.ts +456 -0
- package/src/containers/build-optimization.ts +416 -0
- package/src/containers/containers.test.ts +508 -0
- package/src/containers/image-scanning.ts +360 -0
- package/src/containers/index.ts +9 -0
- package/src/containers/registry.ts +293 -0
- package/src/containers/service-mesh.ts +520 -0
- package/src/database/database.test.ts +762 -0
- package/src/database/index.ts +9 -0
- package/src/database/migrations.ts +444 -0
- package/src/database/performance.ts +528 -0
- package/src/database/replicas.ts +534 -0
- package/src/database/users.ts +494 -0
- package/src/dependency-graph.ts +143 -0
- package/src/deployment/ab-testing.ts +582 -0
- package/src/deployment/blue-green.ts +452 -0
- package/src/deployment/canary.ts +500 -0
- package/src/deployment/deployment.test.ts +526 -0
- package/src/deployment/index.ts +61 -0
- package/src/deployment/progressive.ts +62 -0
- package/src/dns/dns.test.ts +641 -0
- package/src/dns/dnssec.ts +315 -0
- package/src/dns/index.ts +8 -0
- package/src/dns/resolver.ts +496 -0
- package/src/dns/routing.ts +593 -0
- package/src/email/advanced/analytics.ts +445 -0
- package/src/email/advanced/index.ts +11 -0
- package/src/email/advanced/rules.ts +465 -0
- package/src/email/advanced/scheduling.ts +352 -0
- package/src/email/advanced/search.ts +412 -0
- package/src/email/advanced/shared-mailboxes.ts +404 -0
- package/src/email/advanced/templates.ts +455 -0
- package/src/email/advanced/threading.ts +281 -0
- package/src/email/analytics.ts +467 -0
- package/src/email/bounce-handling.ts +425 -0
- package/src/email/email.test.ts +431 -0
- package/src/email/handlers/__tests__/inbound.test.ts +38 -0
- package/src/email/handlers/__tests__/outbound.test.ts +37 -0
- package/src/email/handlers/converter.ts +227 -0
- package/src/email/handlers/feedback.ts +228 -0
- package/src/email/handlers/inbound.ts +169 -0
- package/src/email/handlers/outbound.ts +178 -0
- package/src/email/index.ts +15 -0
- package/src/email/reputation.ts +303 -0
- package/src/email/templates.ts +352 -0
- package/src/errors/index.test.ts +434 -0
- package/src/errors/index.ts +416 -0
- package/src/health-checks/index.ts +40 -0
- package/src/index.ts +360 -0
- package/src/intrinsic-functions.ts +118 -0
- package/src/lambda/concurrency.ts +330 -0
- package/src/lambda/destinations.ts +345 -0
- package/src/lambda/dlq.ts +425 -0
- package/src/lambda/index.ts +11 -0
- package/src/lambda/lambda.test.ts +840 -0
- package/src/lambda/layers.ts +263 -0
- package/src/lambda/versions.ts +376 -0
- package/src/lambda/vpc.ts +399 -0
- package/src/local/config.ts +114 -0
- package/src/local/index.ts +6 -0
- package/src/local/mock-aws.ts +351 -0
- package/src/modules/ai.ts +340 -0
- package/src/modules/api.ts +478 -0
- package/src/modules/auth.ts +805 -0
- package/src/modules/cache.ts +417 -0
- package/src/modules/cdn.ts +1062 -0
- package/src/modules/communication.ts +1094 -0
- package/src/modules/compute.ts +3348 -0
- package/src/modules/database.ts +554 -0
- package/src/modules/deployment.ts +1079 -0
- package/src/modules/dns.ts +337 -0
- package/src/modules/email.ts +1538 -0
- package/src/modules/filesystem.ts +515 -0
- package/src/modules/index.ts +32 -0
- package/src/modules/messaging.ts +486 -0
- package/src/modules/monitoring.ts +2086 -0
- package/src/modules/network.ts +664 -0
- package/src/modules/parameter-store.ts +325 -0
- package/src/modules/permissions.ts +1081 -0
- package/src/modules/phone.ts +494 -0
- package/src/modules/queue.ts +1260 -0
- package/src/modules/redirects.ts +464 -0
- package/src/modules/registry.ts +699 -0
- package/src/modules/search.ts +401 -0
- package/src/modules/secrets.ts +416 -0
- package/src/modules/security.ts +731 -0
- package/src/modules/sms.ts +389 -0
- package/src/modules/storage.ts +1120 -0
- package/src/modules/workflow.ts +680 -0
- package/src/multi-account/config.ts +521 -0
- package/src/multi-account/index.ts +7 -0
- package/src/multi-account/manager.ts +427 -0
- package/src/multi-region/cross-region.ts +410 -0
- package/src/multi-region/index.ts +8 -0
- package/src/multi-region/manager.ts +483 -0
- package/src/multi-region/regions.ts +435 -0
- package/src/network-security/index.ts +48 -0
- package/src/observability/index.ts +9 -0
- package/src/observability/logs.ts +522 -0
- package/src/observability/metrics.ts +460 -0
- package/src/observability/observability.test.ts +782 -0
- package/src/observability/synthetics.ts +568 -0
- package/src/observability/xray.ts +358 -0
- package/src/phone/advanced/analytics.ts +349 -0
- package/src/phone/advanced/callbacks.ts +428 -0
- package/src/phone/advanced/index.ts +8 -0
- package/src/phone/advanced/ivr-builder.ts +504 -0
- package/src/phone/advanced/recording.ts +310 -0
- package/src/phone/handlers/__tests__/incoming-call.test.ts +40 -0
- package/src/phone/handlers/incoming-call.ts +117 -0
- package/src/phone/handlers/missed-call.ts +116 -0
- package/src/phone/handlers/voicemail.ts +179 -0
- package/src/phone/index.ts +9 -0
- package/src/presets/api-backend.ts +134 -0
- package/src/presets/data-pipeline.ts +204 -0
- package/src/presets/extend.test.ts +295 -0
- package/src/presets/extend.ts +297 -0
- package/src/presets/fullstack-app.ts +144 -0
- package/src/presets/index.ts +27 -0
- package/src/presets/jamstack.ts +135 -0
- package/src/presets/microservices.ts +167 -0
- package/src/presets/ml-api.ts +208 -0
- package/src/presets/nodejs-server.ts +104 -0
- package/src/presets/nodejs-serverless.ts +114 -0
- package/src/presets/realtime-app.ts +184 -0
- package/src/presets/static-site.ts +64 -0
- package/src/presets/traditional-web-app.ts +339 -0
- package/src/presets/wordpress.ts +138 -0
- package/src/preview/github.test.ts +249 -0
- package/src/preview/github.ts +297 -0
- package/src/preview/index.ts +37 -0
- package/src/preview/manager.test.ts +440 -0
- package/src/preview/manager.ts +326 -0
- package/src/preview/notifications.test.ts +582 -0
- package/src/preview/notifications.ts +341 -0
- package/src/queue/batch-processing.ts +402 -0
- package/src/queue/dlq-monitoring.ts +402 -0
- package/src/queue/fifo.ts +342 -0
- package/src/queue/index.ts +9 -0
- package/src/queue/management.ts +428 -0
- package/src/queue/queue.test.ts +429 -0
- package/src/resource-mgmt/index.ts +39 -0
- package/src/resource-naming.ts +62 -0
- package/src/s3/index.ts +523 -0
- package/src/schema/cloud-config.schema.json +554 -0
- package/src/schema/index.ts +68 -0
- package/src/security/certificate-manager.ts +492 -0
- package/src/security/index.ts +9 -0
- package/src/security/scanning.ts +545 -0
- package/src/security/secrets-manager.ts +476 -0
- package/src/security/secrets-rotation.ts +456 -0
- package/src/security/security.test.ts +738 -0
- package/src/sms/advanced/ab-testing.ts +389 -0
- package/src/sms/advanced/analytics.ts +336 -0
- package/src/sms/advanced/campaigns.ts +523 -0
- package/src/sms/advanced/chatbot.ts +224 -0
- package/src/sms/advanced/index.ts +10 -0
- package/src/sms/advanced/link-tracking.ts +248 -0
- package/src/sms/advanced/mms.ts +308 -0
- package/src/sms/handlers/__tests__/send.test.ts +40 -0
- package/src/sms/handlers/delivery-status.ts +133 -0
- package/src/sms/handlers/receive.ts +162 -0
- package/src/sms/handlers/send.ts +174 -0
- package/src/sms/index.ts +9 -0
- package/src/stack-diff.ts +389 -0
- package/src/static-site/index.ts +85 -0
- package/src/template-builder.ts +110 -0
- package/src/template-validator.ts +574 -0
- package/src/utils/cache.ts +291 -0
- package/src/utils/diff.ts +269 -0
- package/src/utils/hash.ts +227 -0
- package/src/utils/index.ts +8 -0
- package/src/utils/parallel.ts +294 -0
- package/src/validators/credentials.test.ts +274 -0
- package/src/validators/credentials.ts +233 -0
- package/src/validators/quotas.test.ts +434 -0
- package/src/validators/quotas.ts +217 -0
- package/test/ai.test.ts +327 -0
- package/test/api.test.ts +511 -0
- package/test/auth.test.ts +632 -0
- package/test/cache.test.ts +406 -0
- package/test/cdn.test.ts +247 -0
- package/test/compute.test.ts +861 -0
- package/test/database.test.ts +523 -0
- package/test/deployment.test.ts +499 -0
- package/test/dns.test.ts +270 -0
- package/test/email.test.ts +439 -0
- package/test/filesystem.test.ts +382 -0
- package/test/integration.test.ts +350 -0
- package/test/messaging.test.ts +514 -0
- package/test/monitoring.test.ts +634 -0
- package/test/network.test.ts +425 -0
- package/test/permissions.test.ts +488 -0
- package/test/queue.test.ts +484 -0
- package/test/registry.test.ts +306 -0
- package/test/security.test.ts +462 -0
- package/test/storage.test.ts +463 -0
- package/test/template-validator.test.ts +559 -0
- package/test/workflow.test.ts +592 -0
- package/tsconfig.json +16 -0
- package/tsconfig.tsbuildinfo +1 -0
|
@@ -0,0 +1,434 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Service Quota Validation Tests
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { describe, expect, it } from 'bun:test'
|
|
6
|
+
import {
|
|
7
|
+
DEFAULT_SERVICE_LIMITS,
|
|
8
|
+
checkServiceQuotas,
|
|
9
|
+
getQuotaUsageSummary,
|
|
10
|
+
suggestQuotaIncrease,
|
|
11
|
+
} from './quotas'
|
|
12
|
+
|
|
13
|
+
describe('DEFAULT_SERVICE_LIMITS', () => {
|
|
14
|
+
it('should have EC2 limits', () => {
|
|
15
|
+
expect(DEFAULT_SERVICE_LIMITS.ec2).toBeDefined()
|
|
16
|
+
expect(DEFAULT_SERVICE_LIMITS.ec2['Running On-Demand Instances']).toBe(20)
|
|
17
|
+
expect(DEFAULT_SERVICE_LIMITS.ec2['VPCs']).toBe(5)
|
|
18
|
+
expect(DEFAULT_SERVICE_LIMITS.ec2['Security Groups']).toBe(500)
|
|
19
|
+
})
|
|
20
|
+
|
|
21
|
+
it('should have RDS limits', () => {
|
|
22
|
+
expect(DEFAULT_SERVICE_LIMITS.rds).toBeDefined()
|
|
23
|
+
expect(DEFAULT_SERVICE_LIMITS.rds['DB Instances']).toBe(40)
|
|
24
|
+
})
|
|
25
|
+
|
|
26
|
+
it('should have S3 limits', () => {
|
|
27
|
+
expect(DEFAULT_SERVICE_LIMITS.s3).toBeDefined()
|
|
28
|
+
expect(DEFAULT_SERVICE_LIMITS.s3['Buckets']).toBe(100)
|
|
29
|
+
})
|
|
30
|
+
|
|
31
|
+
it('should have Lambda limits', () => {
|
|
32
|
+
expect(DEFAULT_SERVICE_LIMITS.lambda).toBeDefined()
|
|
33
|
+
expect(DEFAULT_SERVICE_LIMITS.lambda['Concurrent Executions']).toBe(1000)
|
|
34
|
+
})
|
|
35
|
+
|
|
36
|
+
it('should have DynamoDB limits', () => {
|
|
37
|
+
expect(DEFAULT_SERVICE_LIMITS.dynamodb).toBeDefined()
|
|
38
|
+
expect(DEFAULT_SERVICE_LIMITS.dynamodb['Tables']).toBe(256)
|
|
39
|
+
})
|
|
40
|
+
})
|
|
41
|
+
|
|
42
|
+
describe('checkServiceQuotas', () => {
|
|
43
|
+
it('should return empty array for minimal config', async () => {
|
|
44
|
+
const quotas = await checkServiceQuotas({
|
|
45
|
+
project: { name: 'Test', slug: 'test' },
|
|
46
|
+
})
|
|
47
|
+
|
|
48
|
+
expect(Array.isArray(quotas)).toBe(true)
|
|
49
|
+
expect(quotas.length).toBe(0)
|
|
50
|
+
})
|
|
51
|
+
|
|
52
|
+
it('should check EC2 instance quotas', async () => {
|
|
53
|
+
const quotas = await checkServiceQuotas({
|
|
54
|
+
project: { name: 'Test', slug: 'test' },
|
|
55
|
+
infrastructure: {
|
|
56
|
+
compute: {
|
|
57
|
+
server: {
|
|
58
|
+
autoScaling: {
|
|
59
|
+
max: 10,
|
|
60
|
+
},
|
|
61
|
+
},
|
|
62
|
+
},
|
|
63
|
+
},
|
|
64
|
+
})
|
|
65
|
+
|
|
66
|
+
const ec2Quota = quotas.find(q => q.quotaName === 'Running On-Demand Instances')
|
|
67
|
+
expect(ec2Quota).toBeDefined()
|
|
68
|
+
expect(ec2Quota?.service).toBe('EC2')
|
|
69
|
+
expect(ec2Quota?.currentValue).toBe(10)
|
|
70
|
+
expect(ec2Quota?.limit).toBe(20)
|
|
71
|
+
expect(ec2Quota?.percentage).toBe(50)
|
|
72
|
+
expect(ec2Quota?.warning).toBe(false)
|
|
73
|
+
})
|
|
74
|
+
|
|
75
|
+
it('should warn when EC2 quota exceeds 80%', async () => {
|
|
76
|
+
const quotas = await checkServiceQuotas({
|
|
77
|
+
project: { name: 'Test', slug: 'test' },
|
|
78
|
+
infrastructure: {
|
|
79
|
+
compute: {
|
|
80
|
+
server: {
|
|
81
|
+
autoScaling: {
|
|
82
|
+
max: 17,
|
|
83
|
+
},
|
|
84
|
+
},
|
|
85
|
+
},
|
|
86
|
+
},
|
|
87
|
+
})
|
|
88
|
+
|
|
89
|
+
const ec2Quota = quotas.find(q => q.quotaName === 'Running On-Demand Instances')
|
|
90
|
+
expect(ec2Quota?.warning).toBe(true)
|
|
91
|
+
})
|
|
92
|
+
|
|
93
|
+
it('should check VPC quotas', async () => {
|
|
94
|
+
const quotas = await checkServiceQuotas({
|
|
95
|
+
project: { name: 'Test', slug: 'test' },
|
|
96
|
+
infrastructure: {
|
|
97
|
+
network: {
|
|
98
|
+
vpc: {},
|
|
99
|
+
},
|
|
100
|
+
},
|
|
101
|
+
})
|
|
102
|
+
|
|
103
|
+
const vpcQuota = quotas.find(q => q.quotaName === 'VPCs')
|
|
104
|
+
expect(vpcQuota).toBeDefined()
|
|
105
|
+
expect(vpcQuota?.service).toBe('EC2')
|
|
106
|
+
expect(vpcQuota?.currentValue).toBe(1)
|
|
107
|
+
expect(vpcQuota?.limit).toBe(5)
|
|
108
|
+
})
|
|
109
|
+
|
|
110
|
+
it('should check RDS database quotas', async () => {
|
|
111
|
+
const quotas = await checkServiceQuotas({
|
|
112
|
+
project: { name: 'Test', slug: 'test' },
|
|
113
|
+
infrastructure: {
|
|
114
|
+
database: {
|
|
115
|
+
postgres: {},
|
|
116
|
+
},
|
|
117
|
+
},
|
|
118
|
+
})
|
|
119
|
+
|
|
120
|
+
const rdsQuota = quotas.find(q => q.quotaName === 'DB Instances')
|
|
121
|
+
expect(rdsQuota).toBeDefined()
|
|
122
|
+
expect(rdsQuota?.service).toBe('RDS')
|
|
123
|
+
expect(rdsQuota?.currentValue).toBe(1)
|
|
124
|
+
expect(rdsQuota?.limit).toBe(40)
|
|
125
|
+
})
|
|
126
|
+
|
|
127
|
+
it('should check S3 bucket quotas', async () => {
|
|
128
|
+
const quotas = await checkServiceQuotas({
|
|
129
|
+
project: { name: 'Test', slug: 'test' },
|
|
130
|
+
infrastructure: {
|
|
131
|
+
storage: {
|
|
132
|
+
uploads: {},
|
|
133
|
+
assets: {},
|
|
134
|
+
backups: {},
|
|
135
|
+
},
|
|
136
|
+
},
|
|
137
|
+
})
|
|
138
|
+
|
|
139
|
+
const s3Quota = quotas.find(q => q.quotaName === 'Buckets')
|
|
140
|
+
expect(s3Quota).toBeDefined()
|
|
141
|
+
expect(s3Quota?.service).toBe('S3')
|
|
142
|
+
expect(s3Quota?.currentValue).toBe(3)
|
|
143
|
+
expect(s3Quota?.limit).toBe(100)
|
|
144
|
+
})
|
|
145
|
+
|
|
146
|
+
it('should warn when S3 bucket quota exceeds 80%', async () => {
|
|
147
|
+
const storage: any = {}
|
|
148
|
+
for (let i = 0; i < 85; i++) {
|
|
149
|
+
storage[`bucket${i}`] = {}
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
const quotas = await checkServiceQuotas({
|
|
153
|
+
project: { name: 'Test', slug: 'test' },
|
|
154
|
+
infrastructure: {
|
|
155
|
+
storage,
|
|
156
|
+
},
|
|
157
|
+
})
|
|
158
|
+
|
|
159
|
+
const s3Quota = quotas.find(q => q.quotaName === 'Buckets')
|
|
160
|
+
expect(s3Quota?.warning).toBe(true)
|
|
161
|
+
})
|
|
162
|
+
|
|
163
|
+
it('should check Lambda function quotas', async () => {
|
|
164
|
+
const quotas = await checkServiceQuotas({
|
|
165
|
+
project: { name: 'Test', slug: 'test' },
|
|
166
|
+
infrastructure: {
|
|
167
|
+
functions: {
|
|
168
|
+
api: [
|
|
169
|
+
{ name: 'handler1', runtime: 'nodejs20.x' },
|
|
170
|
+
{ name: 'handler2', runtime: 'nodejs20.x' },
|
|
171
|
+
],
|
|
172
|
+
},
|
|
173
|
+
},
|
|
174
|
+
})
|
|
175
|
+
|
|
176
|
+
const lambdaQuota = quotas.find(q => q.quotaName === 'Functions (estimated)')
|
|
177
|
+
expect(lambdaQuota).toBeDefined()
|
|
178
|
+
expect(lambdaQuota?.service).toBe('Lambda')
|
|
179
|
+
expect(lambdaQuota?.currentValue).toBe(2)
|
|
180
|
+
expect(lambdaQuota?.limit).toBe(1000)
|
|
181
|
+
})
|
|
182
|
+
|
|
183
|
+
it('should check DynamoDB table quotas', async () => {
|
|
184
|
+
const quotas = await checkServiceQuotas({
|
|
185
|
+
project: { name: 'Test', slug: 'test' },
|
|
186
|
+
infrastructure: {
|
|
187
|
+
database: {
|
|
188
|
+
dynamodb: {
|
|
189
|
+
tables: [
|
|
190
|
+
{ name: 'users', partitionKey: 'id' },
|
|
191
|
+
{ name: 'posts', partitionKey: 'id' },
|
|
192
|
+
],
|
|
193
|
+
},
|
|
194
|
+
},
|
|
195
|
+
},
|
|
196
|
+
})
|
|
197
|
+
|
|
198
|
+
const dynamoQuota = quotas.find(q => q.quotaName === 'Tables')
|
|
199
|
+
expect(dynamoQuota).toBeDefined()
|
|
200
|
+
expect(dynamoQuota?.service).toBe('DynamoDB')
|
|
201
|
+
expect(dynamoQuota?.currentValue).toBe(2)
|
|
202
|
+
expect(dynamoQuota?.limit).toBe(256)
|
|
203
|
+
})
|
|
204
|
+
|
|
205
|
+
it('should check multiple quotas at once', async () => {
|
|
206
|
+
const quotas = await checkServiceQuotas({
|
|
207
|
+
project: { name: 'Test', slug: 'test' },
|
|
208
|
+
infrastructure: {
|
|
209
|
+
compute: {
|
|
210
|
+
server: {
|
|
211
|
+
autoScaling: { max: 5 },
|
|
212
|
+
},
|
|
213
|
+
},
|
|
214
|
+
network: {
|
|
215
|
+
vpc: {},
|
|
216
|
+
},
|
|
217
|
+
storage: {
|
|
218
|
+
uploads: {},
|
|
219
|
+
},
|
|
220
|
+
database: {
|
|
221
|
+
postgres: {},
|
|
222
|
+
},
|
|
223
|
+
},
|
|
224
|
+
})
|
|
225
|
+
|
|
226
|
+
expect(quotas.length).toBeGreaterThan(0)
|
|
227
|
+
expect(quotas.some(q => q.service === 'EC2')).toBe(true)
|
|
228
|
+
expect(quotas.some(q => q.service === 'S3')).toBe(true)
|
|
229
|
+
expect(quotas.some(q => q.service === 'RDS')).toBe(true)
|
|
230
|
+
})
|
|
231
|
+
})
|
|
232
|
+
|
|
233
|
+
describe('getQuotaUsageSummary', () => {
|
|
234
|
+
it('should return message for empty quotas', () => {
|
|
235
|
+
const summary = getQuotaUsageSummary([])
|
|
236
|
+
|
|
237
|
+
expect(summary).toBe('No quotas to check')
|
|
238
|
+
})
|
|
239
|
+
|
|
240
|
+
it('should format quota usage summary', () => {
|
|
241
|
+
const summary = getQuotaUsageSummary([
|
|
242
|
+
{
|
|
243
|
+
service: 'EC2',
|
|
244
|
+
quotaName: 'Running Instances',
|
|
245
|
+
currentValue: 10,
|
|
246
|
+
limit: 20,
|
|
247
|
+
percentage: 50,
|
|
248
|
+
warning: false,
|
|
249
|
+
},
|
|
250
|
+
{
|
|
251
|
+
service: 'S3',
|
|
252
|
+
quotaName: 'Buckets',
|
|
253
|
+
currentValue: 5,
|
|
254
|
+
limit: 100,
|
|
255
|
+
percentage: 5,
|
|
256
|
+
warning: false,
|
|
257
|
+
},
|
|
258
|
+
])
|
|
259
|
+
|
|
260
|
+
expect(summary).toContain('Service Quota Usage')
|
|
261
|
+
expect(summary).toContain('EC2')
|
|
262
|
+
expect(summary).toContain('Running Instances')
|
|
263
|
+
expect(summary).toContain('10/20')
|
|
264
|
+
expect(summary).toContain('50.0%')
|
|
265
|
+
expect(summary).toContain('S3')
|
|
266
|
+
expect(summary).toContain('Buckets')
|
|
267
|
+
})
|
|
268
|
+
|
|
269
|
+
it('should show warning indicator for quota warnings', () => {
|
|
270
|
+
const summary = getQuotaUsageSummary([
|
|
271
|
+
{
|
|
272
|
+
service: 'EC2',
|
|
273
|
+
quotaName: 'Running Instances',
|
|
274
|
+
currentValue: 18,
|
|
275
|
+
limit: 20,
|
|
276
|
+
percentage: 90,
|
|
277
|
+
warning: true,
|
|
278
|
+
},
|
|
279
|
+
])
|
|
280
|
+
|
|
281
|
+
expect(summary).toContain('⚠')
|
|
282
|
+
})
|
|
283
|
+
|
|
284
|
+
it('should show checkmark for normal usage', () => {
|
|
285
|
+
const summary = getQuotaUsageSummary([
|
|
286
|
+
{
|
|
287
|
+
service: 'EC2',
|
|
288
|
+
quotaName: 'Running Instances',
|
|
289
|
+
currentValue: 5,
|
|
290
|
+
limit: 20,
|
|
291
|
+
percentage: 25,
|
|
292
|
+
warning: false,
|
|
293
|
+
},
|
|
294
|
+
])
|
|
295
|
+
|
|
296
|
+
expect(summary).toContain('✓')
|
|
297
|
+
})
|
|
298
|
+
|
|
299
|
+
it('should group quotas by service', () => {
|
|
300
|
+
const summary = getQuotaUsageSummary([
|
|
301
|
+
{
|
|
302
|
+
service: 'EC2',
|
|
303
|
+
quotaName: 'Instances',
|
|
304
|
+
currentValue: 5,
|
|
305
|
+
limit: 20,
|
|
306
|
+
percentage: 25,
|
|
307
|
+
warning: false,
|
|
308
|
+
},
|
|
309
|
+
{
|
|
310
|
+
service: 'EC2',
|
|
311
|
+
quotaName: 'VPCs',
|
|
312
|
+
currentValue: 2,
|
|
313
|
+
limit: 5,
|
|
314
|
+
percentage: 40,
|
|
315
|
+
warning: false,
|
|
316
|
+
},
|
|
317
|
+
{
|
|
318
|
+
service: 'S3',
|
|
319
|
+
quotaName: 'Buckets',
|
|
320
|
+
currentValue: 3,
|
|
321
|
+
limit: 100,
|
|
322
|
+
percentage: 3,
|
|
323
|
+
warning: false,
|
|
324
|
+
},
|
|
325
|
+
])
|
|
326
|
+
|
|
327
|
+
// Should have EC2 section with 2 items
|
|
328
|
+
const ec2Section = summary.split('S3:')[0]
|
|
329
|
+
expect(ec2Section).toContain('Instances')
|
|
330
|
+
expect(ec2Section).toContain('VPCs')
|
|
331
|
+
})
|
|
332
|
+
})
|
|
333
|
+
|
|
334
|
+
describe('suggestQuotaIncrease', () => {
|
|
335
|
+
it('should return empty array for no warnings', () => {
|
|
336
|
+
const suggestions = suggestQuotaIncrease([
|
|
337
|
+
{
|
|
338
|
+
service: 'EC2',
|
|
339
|
+
quotaName: 'Instances',
|
|
340
|
+
currentValue: 5,
|
|
341
|
+
limit: 20,
|
|
342
|
+
percentage: 25,
|
|
343
|
+
warning: false,
|
|
344
|
+
},
|
|
345
|
+
])
|
|
346
|
+
|
|
347
|
+
expect(suggestions).toEqual([])
|
|
348
|
+
})
|
|
349
|
+
|
|
350
|
+
it('should suggest increase for quotas at 100%', () => {
|
|
351
|
+
const suggestions = suggestQuotaIncrease([
|
|
352
|
+
{
|
|
353
|
+
service: 'EC2',
|
|
354
|
+
quotaName: 'Instances',
|
|
355
|
+
currentValue: 20,
|
|
356
|
+
limit: 20,
|
|
357
|
+
percentage: 100,
|
|
358
|
+
warning: true,
|
|
359
|
+
},
|
|
360
|
+
])
|
|
361
|
+
|
|
362
|
+
expect(suggestions).toHaveLength(1)
|
|
363
|
+
expect(suggestions[0]).toContain('Request quota increase')
|
|
364
|
+
expect(suggestions[0]).toContain('EC2')
|
|
365
|
+
expect(suggestions[0]).toContain('Instances')
|
|
366
|
+
expect(suggestions[0]).toContain('20')
|
|
367
|
+
})
|
|
368
|
+
|
|
369
|
+
it('should suggest consideration for quotas with warnings', () => {
|
|
370
|
+
const suggestions = suggestQuotaIncrease([
|
|
371
|
+
{
|
|
372
|
+
service: 'S3',
|
|
373
|
+
quotaName: 'Buckets',
|
|
374
|
+
currentValue: 85,
|
|
375
|
+
limit: 100,
|
|
376
|
+
percentage: 85,
|
|
377
|
+
warning: true,
|
|
378
|
+
},
|
|
379
|
+
])
|
|
380
|
+
|
|
381
|
+
expect(suggestions).toHaveLength(1)
|
|
382
|
+
expect(suggestions[0]).toContain('Consider requesting quota increase')
|
|
383
|
+
expect(suggestions[0]).toContain('S3')
|
|
384
|
+
expect(suggestions[0]).toContain('Buckets')
|
|
385
|
+
expect(suggestions[0]).toContain('85.0%')
|
|
386
|
+
})
|
|
387
|
+
|
|
388
|
+
it('should provide multiple suggestions', () => {
|
|
389
|
+
const suggestions = suggestQuotaIncrease([
|
|
390
|
+
{
|
|
391
|
+
service: 'EC2',
|
|
392
|
+
quotaName: 'Instances',
|
|
393
|
+
currentValue: 20,
|
|
394
|
+
limit: 20,
|
|
395
|
+
percentage: 100,
|
|
396
|
+
warning: true,
|
|
397
|
+
},
|
|
398
|
+
{
|
|
399
|
+
service: 'S3',
|
|
400
|
+
quotaName: 'Buckets',
|
|
401
|
+
currentValue: 85,
|
|
402
|
+
limit: 100,
|
|
403
|
+
percentage: 85,
|
|
404
|
+
warning: true,
|
|
405
|
+
},
|
|
406
|
+
])
|
|
407
|
+
|
|
408
|
+
expect(suggestions).toHaveLength(2)
|
|
409
|
+
})
|
|
410
|
+
|
|
411
|
+
it('should prioritize 100% quotas over warnings', () => {
|
|
412
|
+
const suggestions = suggestQuotaIncrease([
|
|
413
|
+
{
|
|
414
|
+
service: 'EC2',
|
|
415
|
+
quotaName: 'Instances',
|
|
416
|
+
currentValue: 20,
|
|
417
|
+
limit: 20,
|
|
418
|
+
percentage: 100,
|
|
419
|
+
warning: true,
|
|
420
|
+
},
|
|
421
|
+
{
|
|
422
|
+
service: 'S3',
|
|
423
|
+
quotaName: 'Buckets',
|
|
424
|
+
currentValue: 85,
|
|
425
|
+
limit: 100,
|
|
426
|
+
percentage: 85,
|
|
427
|
+
warning: true,
|
|
428
|
+
},
|
|
429
|
+
])
|
|
430
|
+
|
|
431
|
+
expect(suggestions[0]).toContain('Request quota increase')
|
|
432
|
+
expect(suggestions[1]).toContain('Consider requesting')
|
|
433
|
+
})
|
|
434
|
+
})
|
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AWS Service Quota Checking
|
|
3
|
+
* Check if deployment will exceed service limits
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { DebugLogger } from '../errors'
|
|
7
|
+
|
|
8
|
+
export interface ServiceQuota {
|
|
9
|
+
service: string
|
|
10
|
+
quotaName: string
|
|
11
|
+
currentValue: number
|
|
12
|
+
limit: number
|
|
13
|
+
percentage: number
|
|
14
|
+
warning: boolean
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Common AWS service limits by region
|
|
19
|
+
*/
|
|
20
|
+
export const DEFAULT_SERVICE_LIMITS = {
|
|
21
|
+
ec2: {
|
|
22
|
+
'Running On-Demand Instances': 20,
|
|
23
|
+
'Elastic IPs': 5,
|
|
24
|
+
'VPCs': 5,
|
|
25
|
+
'Internet Gateways': 5,
|
|
26
|
+
'NAT Gateways': 5,
|
|
27
|
+
'Security Groups': 500,
|
|
28
|
+
},
|
|
29
|
+
rds: {
|
|
30
|
+
'DB Instances': 40,
|
|
31
|
+
'DB Snapshots': 100,
|
|
32
|
+
'DB Parameter Groups': 50,
|
|
33
|
+
},
|
|
34
|
+
lambda: {
|
|
35
|
+
'Concurrent Executions': 1000,
|
|
36
|
+
'Function Storage': 75 * 1024 * 1024 * 1024 as number, // 75 GB
|
|
37
|
+
},
|
|
38
|
+
s3: {
|
|
39
|
+
'Buckets': 100,
|
|
40
|
+
},
|
|
41
|
+
cloudformation: {
|
|
42
|
+
'Stacks': 200,
|
|
43
|
+
'StackSets': 100,
|
|
44
|
+
},
|
|
45
|
+
elasticache: {
|
|
46
|
+
'Nodes': 100,
|
|
47
|
+
'Clusters': 100,
|
|
48
|
+
},
|
|
49
|
+
dynamodb: {
|
|
50
|
+
'Tables': 256,
|
|
51
|
+
},
|
|
52
|
+
ecs: {
|
|
53
|
+
'Clusters': 10000,
|
|
54
|
+
'Services per Cluster': 2000,
|
|
55
|
+
},
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Check service quotas for deployment
|
|
60
|
+
*/
|
|
61
|
+
export async function checkServiceQuotas(config: any): Promise<ServiceQuota[]> {
|
|
62
|
+
const quotas: ServiceQuota[] = []
|
|
63
|
+
|
|
64
|
+
DebugLogger.verbose('Checking AWS service quotas...')
|
|
65
|
+
|
|
66
|
+
// Check EC2 quotas
|
|
67
|
+
if (config.infrastructure?.compute?.server) {
|
|
68
|
+
const instanceCount = config.infrastructure.compute.server.autoScaling?.max || 1
|
|
69
|
+
|
|
70
|
+
quotas.push({
|
|
71
|
+
service: 'EC2',
|
|
72
|
+
quotaName: 'Running On-Demand Instances',
|
|
73
|
+
currentValue: instanceCount,
|
|
74
|
+
limit: DEFAULT_SERVICE_LIMITS.ec2['Running On-Demand Instances'],
|
|
75
|
+
percentage: (instanceCount / DEFAULT_SERVICE_LIMITS.ec2['Running On-Demand Instances']) * 100,
|
|
76
|
+
warning: instanceCount > DEFAULT_SERVICE_LIMITS.ec2['Running On-Demand Instances'] * 0.8,
|
|
77
|
+
})
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// Check VPC quotas
|
|
81
|
+
if (config.infrastructure?.network?.vpc) {
|
|
82
|
+
quotas.push({
|
|
83
|
+
service: 'EC2',
|
|
84
|
+
quotaName: 'VPCs',
|
|
85
|
+
currentValue: 1,
|
|
86
|
+
limit: DEFAULT_SERVICE_LIMITS.ec2.VPCs,
|
|
87
|
+
percentage: (1 / DEFAULT_SERVICE_LIMITS.ec2.VPCs) * 100,
|
|
88
|
+
warning: false,
|
|
89
|
+
})
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// Check RDS quotas
|
|
93
|
+
if (config.infrastructure?.database?.postgres || config.infrastructure?.database?.mysql) {
|
|
94
|
+
quotas.push({
|
|
95
|
+
service: 'RDS',
|
|
96
|
+
quotaName: 'DB Instances',
|
|
97
|
+
currentValue: 1,
|
|
98
|
+
limit: DEFAULT_SERVICE_LIMITS.rds['DB Instances'],
|
|
99
|
+
percentage: (1 / DEFAULT_SERVICE_LIMITS.rds['DB Instances']) * 100,
|
|
100
|
+
warning: false,
|
|
101
|
+
})
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// Check S3 quotas
|
|
105
|
+
if (config.infrastructure?.storage) {
|
|
106
|
+
const bucketCount = Object.keys(config.infrastructure.storage).length
|
|
107
|
+
|
|
108
|
+
quotas.push({
|
|
109
|
+
service: 'S3',
|
|
110
|
+
quotaName: 'Buckets',
|
|
111
|
+
currentValue: bucketCount,
|
|
112
|
+
limit: DEFAULT_SERVICE_LIMITS.s3.Buckets,
|
|
113
|
+
percentage: (bucketCount / DEFAULT_SERVICE_LIMITS.s3.Buckets) * 100,
|
|
114
|
+
warning: bucketCount > DEFAULT_SERVICE_LIMITS.s3.Buckets * 0.8,
|
|
115
|
+
})
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// Check Lambda quotas
|
|
119
|
+
if (config.infrastructure?.functions) {
|
|
120
|
+
let functionCount = 0
|
|
121
|
+
for (const category of Object.values(config.infrastructure.functions)) {
|
|
122
|
+
if (Array.isArray(category)) {
|
|
123
|
+
functionCount += category.length
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// Lambda doesn't have a function count limit, but we can check storage
|
|
128
|
+
quotas.push({
|
|
129
|
+
service: 'Lambda',
|
|
130
|
+
quotaName: 'Functions (estimated)',
|
|
131
|
+
currentValue: functionCount,
|
|
132
|
+
limit: 1000, // Soft limit
|
|
133
|
+
percentage: (functionCount / 1000) * 100,
|
|
134
|
+
warning: functionCount > 800,
|
|
135
|
+
})
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
// Check DynamoDB quotas
|
|
139
|
+
if (config.infrastructure?.database?.dynamodb) {
|
|
140
|
+
const tableCount = config.infrastructure.database.dynamodb.tables?.length || 0
|
|
141
|
+
|
|
142
|
+
quotas.push({
|
|
143
|
+
service: 'DynamoDB',
|
|
144
|
+
quotaName: 'Tables',
|
|
145
|
+
currentValue: tableCount,
|
|
146
|
+
limit: DEFAULT_SERVICE_LIMITS.dynamodb.Tables,
|
|
147
|
+
percentage: (tableCount / DEFAULT_SERVICE_LIMITS.dynamodb.Tables) * 100,
|
|
148
|
+
warning: tableCount > DEFAULT_SERVICE_LIMITS.dynamodb.Tables * 0.8,
|
|
149
|
+
})
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
// Log warnings
|
|
153
|
+
const warnings = quotas.filter(q => q.warning)
|
|
154
|
+
if (warnings.length > 0) {
|
|
155
|
+
DebugLogger.warn('Service quota warnings detected:')
|
|
156
|
+
for (const warning of warnings) {
|
|
157
|
+
DebugLogger.warn(` ${warning.service} - ${warning.quotaName}: ${warning.currentValue}/${warning.limit} (${warning.percentage.toFixed(1)}%)`)
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
else {
|
|
161
|
+
DebugLogger.verbose('All service quotas are within limits')
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
return quotas
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* Get quota usage summary
|
|
169
|
+
*/
|
|
170
|
+
export function getQuotaUsageSummary(quotas: ServiceQuota[]): string {
|
|
171
|
+
if (quotas.length === 0) {
|
|
172
|
+
return 'No quotas to check'
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
let summary = 'Service Quota Usage:\n\n'
|
|
176
|
+
|
|
177
|
+
const byService = quotas.reduce((acc, quota) => {
|
|
178
|
+
if (!acc[quota.service]) {
|
|
179
|
+
acc[quota.service] = []
|
|
180
|
+
}
|
|
181
|
+
acc[quota.service].push(quota)
|
|
182
|
+
return acc
|
|
183
|
+
}, {} as Record<string, ServiceQuota[]>)
|
|
184
|
+
|
|
185
|
+
for (const [service, serviceQuotas] of Object.entries(byService)) {
|
|
186
|
+
summary += `${service}:\n`
|
|
187
|
+
for (const quota of serviceQuotas) {
|
|
188
|
+
const indicator = quota.warning ? '⚠' : '✓'
|
|
189
|
+
summary += ` ${indicator} ${quota.quotaName}: ${quota.currentValue}/${quota.limit} (${quota.percentage.toFixed(1)}%)\n`
|
|
190
|
+
}
|
|
191
|
+
summary += '\n'
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
return summary
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
/**
|
|
198
|
+
* Suggest quota increase if needed
|
|
199
|
+
*/
|
|
200
|
+
export function suggestQuotaIncrease(quotas: ServiceQuota[]): string[] {
|
|
201
|
+
const suggestions: string[] = []
|
|
202
|
+
|
|
203
|
+
for (const quota of quotas) {
|
|
204
|
+
if (quota.percentage >= 100) {
|
|
205
|
+
suggestions.push(
|
|
206
|
+
`Request quota increase for ${quota.service} - ${quota.quotaName} (currently ${quota.limit})`,
|
|
207
|
+
)
|
|
208
|
+
}
|
|
209
|
+
else if (quota.warning) {
|
|
210
|
+
suggestions.push(
|
|
211
|
+
`Consider requesting quota increase for ${quota.service} - ${quota.quotaName} (${quota.percentage.toFixed(1)}% used)`,
|
|
212
|
+
)
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
return suggestions
|
|
217
|
+
}
|