@gugananuvem/aws-local-simulator 1.0.35 → 1.0.39
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/README.md +662 -658
- package/package.json +10 -3
- package/src/config/default-config.js +71 -0
- package/src/config/env-loader.js +4 -2
- package/src/middleware/authorization.js +271 -0
- package/src/middleware/cloudtrail-logger.js +333 -0
- package/src/middleware/error-handler.js +138 -0
- package/src/middleware/index.js +79 -0
- package/src/middleware/policy-enforcement.js +407 -0
- package/src/server.js +74 -4
- package/src/services/acm/index.js +66 -0
- package/src/services/acm/models/certificate.js +202 -0
- package/src/services/acm/server.js +224 -0
- package/src/services/acm/simulator.js +293 -0
- package/src/services/alb/index.js +104 -0
- package/src/services/alb/models/loadBalancer.js +88 -0
- package/src/services/alb/models/targetGroup.js +128 -0
- package/src/services/alb/server.js +160 -0
- package/src/services/alb/simulator.js +262 -0
- package/src/services/apigateway/index.js +22 -0
- package/src/services/apigateway/server.js +247 -35
- package/src/services/apigateway/simulator.js +780 -26
- package/src/services/asg/index.js +100 -0
- package/src/services/asg/models/autoScalingGroup.js +110 -0
- package/src/services/asg/models/launchTemplate.js +41 -0
- package/src/services/asg/models/scalingPolicy.js +51 -0
- package/src/services/asg/server.js +236 -0
- package/src/services/asg/simulator.js +410 -0
- package/src/services/aurora/cluster-manager.js +731 -0
- package/src/services/aurora/data-api.js +514 -0
- package/src/services/aurora/db-engine.js +270 -0
- package/src/services/aurora/index.js +159 -0
- package/src/services/aurora/rds-api.js +189 -0
- package/src/services/aurora/server.js +213 -0
- package/src/services/aurora/sql-translator.js +334 -0
- package/src/services/cloudformation/index.js +2 -2
- package/src/services/cloudformation/server.js +1 -0
- package/src/services/cloudfront/cache-engine.js +225 -0
- package/src/services/cloudfront/geo-restrictions.js +377 -0
- package/src/services/cloudfront/index.js +220 -0
- package/src/services/cloudfront/models/behavior.js +158 -0
- package/src/services/cloudfront/models/distribution.js +284 -0
- package/src/services/cloudfront/models/index.js +99 -0
- package/src/services/cloudfront/models/invalidation.js +109 -0
- package/src/services/cloudfront/models/origin.js +155 -0
- package/src/services/cloudfront/origin-router.js +552 -0
- package/src/services/cloudfront/origin-validator.js +398 -0
- package/src/services/cloudfront/server.js +512 -0
- package/src/services/cloudfront/signed-url-generator.js +377 -0
- package/src/services/cloudfront/simulator.js +425 -0
- package/src/services/cloudfront/store.js +326 -0
- package/src/services/cloudshell/env-builder.js +27 -0
- package/src/services/cloudshell/index.js +48 -0
- package/src/services/cloudshell/routes.js +60 -0
- package/src/services/cloudshell/session-manager.js +226 -0
- package/src/services/cloudshell/static/.gitkeep +0 -0
- package/src/services/cloudshell/static/index.html +406 -0
- package/src/services/cloudshell/websocket-handler.js +107 -0
- package/src/services/cloudtrail/index.js +2 -3
- package/src/services/cloudtrail/server.js +68 -0
- package/src/services/cognito/server.js +4 -1
- package/src/services/dynamodb/server.js +4 -1
- package/src/services/dynamodb/simulator.js +2 -1
- package/src/services/ec2/index.js +101 -0
- package/src/services/ec2/instanceServer.js +311 -0
- package/src/services/ec2/models/ami.js +39 -0
- package/src/services/ec2/models/instance.js +98 -0
- package/src/services/ec2/models/networkInterface.js +74 -0
- package/src/services/ec2/models/securityGroup.js +71 -0
- package/src/services/ec2/server.js +350 -0
- package/src/services/ec2/simulator.js +436 -0
- package/src/services/eventbridge/server.js +36 -0
- package/src/services/eventbridge/simulator.js +230 -45
- package/src/services/iam/index.js +65 -0
- package/src/services/iam/middleware.js +99 -0
- package/src/services/iam/server.js +331 -0
- package/src/services/iam/simulator.js +1432 -0
- package/src/services/index.js +28 -0
- package/src/services/kinesis-analytics/index.js +105 -0
- package/src/services/kinesis-analytics/models/application.js +325 -0
- package/src/services/kinesis-analytics/models/index.js +12 -0
- package/src/services/kinesis-analytics/server.js +250 -0
- package/src/services/kinesis-analytics/simulator.js +407 -0
- package/src/services/kinesis-analytics/store.js +176 -0
- package/src/services/kinesis-data-streams/index.js +106 -0
- package/src/services/kinesis-data-streams/models/index.js +12 -0
- package/src/services/kinesis-data-streams/models/record.js +70 -0
- package/src/services/kinesis-data-streams/models/stream.js +280 -0
- package/src/services/kinesis-data-streams/server.js +255 -0
- package/src/services/kinesis-data-streams/simulator.js +514 -0
- package/src/services/kinesis-data-streams/store.js +163 -0
- package/src/services/kinesis-firehose/index.js +106 -0
- package/src/services/kinesis-firehose/models/delivery-stream.js +265 -0
- package/src/services/kinesis-firehose/models/index.js +10 -0
- package/src/services/kinesis-firehose/server.js +215 -0
- package/src/services/kinesis-firehose/simulator.js +388 -0
- package/src/services/kinesis-firehose/store.js +163 -0
- package/src/services/kms/server.js +4 -1
- package/src/services/lambda/index.js +26 -0
- package/src/services/lambda/server.js +12 -1
- package/src/services/lambda/simulator.js +4 -0
- package/src/services/opensearch/index.js +105 -0
- package/src/services/opensearch/models/document.js +115 -0
- package/src/services/opensearch/models/index.js +73 -0
- package/src/services/opensearch/server.js +310 -0
- package/src/services/opensearch/simulator.js +605 -0
- package/src/services/opensearch/store.js +162 -0
- package/src/services/organizations/control-tower-simulator.js +321 -0
- package/src/services/organizations/control-tower-store.js +182 -0
- package/src/services/organizations/iam-integration.js +413 -0
- package/src/services/organizations/index.js +172 -0
- package/src/services/organizations/models/index.js +13 -0
- package/src/services/organizations/models/invitation.js +48 -0
- package/src/services/organizations/models/organization.js +566 -0
- package/src/services/organizations/models/role.js +244 -0
- package/src/services/organizations/models/scp.js +131 -0
- package/src/services/organizations/server.js +736 -0
- package/src/services/organizations/simulator.js +593 -0
- package/src/services/organizations/store.js +235 -0
- package/src/services/organizations/vpc-integration.js +453 -0
- package/src/services/route53/index.js +67 -0
- package/src/services/route53/models/healthCheck.js +101 -0
- package/src/services/route53/models/hostedZone.js +37 -0
- package/src/services/route53/models/resourceRecord.js +106 -0
- package/src/services/route53/server.js +841 -0
- package/src/services/route53/simulator.js +350 -0
- package/src/services/s3/index.js +62 -0
- package/src/services/s3/presigned-url-generator.js +337 -0
- package/src/services/s3/server.js +231 -0
- package/src/services/s3/simulator.js +551 -4
- package/src/services/sns/index.js +31 -0
- package/src/services/sns/server.js +9 -0
- package/src/services/sqs/index.js +38 -0
- package/src/services/sqs/server.js +57 -4
- package/src/services/sqs/simulator.js +423 -33
- package/src/services/sts/simulator.js +209 -17
- package/src/services/vpc/README.md +459 -0
- package/src/services/vpc/ec2ResponseFormatter.js +431 -0
- package/src/services/vpc/index.js +90 -0
- package/src/services/vpc/models/internetGateway.js +71 -0
- package/src/services/vpc/models/routeTable.js +160 -0
- package/src/services/vpc/models/subnet.js +48 -0
- package/src/services/vpc/models/vpc.js +37 -0
- package/src/services/vpc/models/vpcEndpoint.js +100 -0
- package/src/services/vpc/models/vpcGateway.js +134 -0
- package/src/services/vpc/server.js +836 -0
- package/src/services/vpc/simulator.js +613 -0
- package/src/utils/policy-cache.js +215 -0
- package/aws-config +0 -154
|
@@ -0,0 +1,262 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ALB Simulator - Application Load Balancer com persistência
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
const path = require('path');
|
|
6
|
+
const LoadBalancer = require('./models/loadBalancer');
|
|
7
|
+
const { TargetGroup } = require('./models/targetGroup');
|
|
8
|
+
const LocalStore = require('../../utils/local-store');
|
|
9
|
+
const logger = require('../../utils/logger');
|
|
10
|
+
|
|
11
|
+
class ALBSimulator {
|
|
12
|
+
constructor(config = {}) {
|
|
13
|
+
this.config = config;
|
|
14
|
+
this.loadBalancers = new Map();
|
|
15
|
+
this.targetGroups = new Map();
|
|
16
|
+
|
|
17
|
+
// Inicializar persistência
|
|
18
|
+
const dataDir = process.env.AWS_LOCAL_SIMULATOR_DATA_DIR || config.dataDir || './.aws-local-simulator-data';
|
|
19
|
+
this.dataDir = path.join(dataDir, 'alb');
|
|
20
|
+
this.store = new LocalStore(this.dataDir);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
async initialize() {
|
|
24
|
+
// 1. Carregar dados persistidos
|
|
25
|
+
await this.loadFromPersistence();
|
|
26
|
+
|
|
27
|
+
// 2. Pré-provisionar ALBs se configurado e não existirem
|
|
28
|
+
if (this.loadBalancers.size === 0 && this.config.loadBalancers && Array.isArray(this.config.loadBalancers)) {
|
|
29
|
+
logger.info('📚 Carregando ALBs da configuração inicial...');
|
|
30
|
+
for (const albConfig of this.config.loadBalancers) {
|
|
31
|
+
this.createLoadBalancer(albConfig.Name, 'internet-facing', albConfig);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Carrega dados persistidos do storage
|
|
38
|
+
*/
|
|
39
|
+
async loadFromPersistence() {
|
|
40
|
+
try {
|
|
41
|
+
// Carregar Load Balancers
|
|
42
|
+
const albsData = this.store.read('load-balancers');
|
|
43
|
+
if (Array.isArray(albsData)) {
|
|
44
|
+
logger.info(`📂 Carregando ${albsData.length} ALBs do storage...`);
|
|
45
|
+
for (const albData of albsData) {
|
|
46
|
+
const alb = new LoadBalancer(albData.LoadBalancerName, albData.Scheme, albData);
|
|
47
|
+
alb.LoadBalancerArn = albData.LoadBalancerArn;
|
|
48
|
+
alb.State = albData.State;
|
|
49
|
+
alb.Listeners = albData.Listeners || [];
|
|
50
|
+
this.loadBalancers.set(alb.LoadBalancerArn, alb);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// Carregar Target Groups
|
|
55
|
+
const tgsData = this.store.read('target-groups');
|
|
56
|
+
if (Array.isArray(tgsData)) {
|
|
57
|
+
logger.info(`📂 Carregando ${tgsData.length} Target Groups do storage...`);
|
|
58
|
+
for (const tgData of tgsData) {
|
|
59
|
+
const tg = new TargetGroup(tgData.TargetGroupName, tgData.Protocol, tgData.Port, tgData.VpcId);
|
|
60
|
+
tg.TargetGroupArn = tgData.TargetGroupArn;
|
|
61
|
+
tg.Targets = tgData.Targets || [];
|
|
62
|
+
tg.HealthCheckConfig = tgData.HealthCheckConfig || {};
|
|
63
|
+
this.targetGroups.set(tg.TargetGroupArn, tg);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
} catch (error) {
|
|
67
|
+
logger.warn(`⚠️ Erro ao carregar dados persistidos ALB: ${error.message}`);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Persiste estado atual em storage
|
|
73
|
+
*/
|
|
74
|
+
persistData() {
|
|
75
|
+
try {
|
|
76
|
+
this.store.write('load-balancers', Array.from(this.loadBalancers.values()).map(a => a.toJSON ? a.toJSON() : a));
|
|
77
|
+
this.store.write('target-groups', Array.from(this.targetGroups.values()).map(tg => tg.toJSON ? tg.toJSON() : tg));
|
|
78
|
+
} catch (error) {
|
|
79
|
+
logger.error(`Erro ao persistir dados ALB: ${error.message}`);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// ========== Load Balancer Operations ==========
|
|
84
|
+
|
|
85
|
+
createLoadBalancer(name, scheme = 'internet-facing', config = {}) {
|
|
86
|
+
const alb = new LoadBalancer(name, scheme, config);
|
|
87
|
+
this.loadBalancers.set(alb.LoadBalancerArn, alb);
|
|
88
|
+
logger.info(`✅ ALB criado: ${name} (${alb.LoadBalancerArn})`);
|
|
89
|
+
// ✅ Persistir mudanças
|
|
90
|
+
this.persistData();
|
|
91
|
+
return alb;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
describeLoadBalancers(filters = {}) {
|
|
95
|
+
let albs = Array.from(this.loadBalancers.values());
|
|
96
|
+
|
|
97
|
+
if (filters.names && Array.isArray(filters.names)) {
|
|
98
|
+
albs = albs.filter(a => filters.names.includes(a.LoadBalancerName));
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
if (filters.arns && Array.isArray(filters.arns)) {
|
|
102
|
+
albs = albs.filter(a => filters.arns.includes(a.LoadBalancerArn));
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
return albs;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
getLoadBalancer(albArn) {
|
|
109
|
+
const alb = this.loadBalancers.get(albArn);
|
|
110
|
+
if (!alb) {
|
|
111
|
+
throw new Error(`Load Balancer ${albArn} not found`);
|
|
112
|
+
}
|
|
113
|
+
return alb;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
deleteLoadBalancer(albArn) {
|
|
117
|
+
// Deletar target groups associados
|
|
118
|
+
const alb = this.getLoadBalancer(albArn);
|
|
119
|
+
alb.TargetGroups.forEach(tg => {
|
|
120
|
+
this.targetGroups.delete(tg.TargetGroupArn);
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
this.loadBalancers.delete(albArn);
|
|
124
|
+
logger.info(`🗑️ ALB deletado: ${albArn}`);
|
|
125
|
+
// ✅ Persistir mudanças
|
|
126
|
+
this.persistData();
|
|
127
|
+
return true;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// ========== Target Group Operations ==========
|
|
131
|
+
|
|
132
|
+
createTargetGroup(name, protocol = 'HTTP', port = 80, vpcId = null, config = {}) {
|
|
133
|
+
const tg = new TargetGroup(name, protocol, port, vpcId, config);
|
|
134
|
+
this.targetGroups.set(tg.TargetGroupArn, tg);
|
|
135
|
+
logger.info(`✅ Target Group criado: ${name} (${tg.TargetGroupArn})`);
|
|
136
|
+
// ✅ Persistir mudanças
|
|
137
|
+
this.persistData();
|
|
138
|
+
return tg;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
describeTargetGroups(filters = {}) {
|
|
142
|
+
let tgs = Array.from(this.targetGroups.values());
|
|
143
|
+
|
|
144
|
+
if (filters.names && Array.isArray(filters.names)) {
|
|
145
|
+
tgs = tgs.filter(t => filters.names.includes(t.TargetGroupName));
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
if (filters.arns && Array.isArray(filters.arns)) {
|
|
149
|
+
tgs = tgs.filter(t => filters.arns.includes(t.TargetGroupArn));
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
return tgs;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
getTargetGroup(tgArn) {
|
|
156
|
+
const tg = this.targetGroups.get(tgArn);
|
|
157
|
+
if (!tg) {
|
|
158
|
+
throw new Error(`Target Group ${tgArn} not found`);
|
|
159
|
+
}
|
|
160
|
+
return tg;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
deleteTargetGroup(tgArn) {
|
|
164
|
+
// Verificar se está em uso por ALB
|
|
165
|
+
const inUse = Array.from(this.loadBalancers.values()).some(alb =>
|
|
166
|
+
alb.TargetGroups.some(tg => tg.TargetGroupArn === tgArn)
|
|
167
|
+
);
|
|
168
|
+
|
|
169
|
+
if (inUse) {
|
|
170
|
+
throw new Error(`Target Group ${tgArn} still in use by load balancer`);
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
this.targetGroups.delete(tgArn);
|
|
174
|
+
logger.info(`🗑️ Target Group deletado: ${tgArn}`);
|
|
175
|
+
// ✅ Persistir mudanças
|
|
176
|
+
this.persistData();
|
|
177
|
+
return true;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
// ========== Target Operations ==========
|
|
181
|
+
|
|
182
|
+
registerTarget(tgArn, targetId, port = null) {
|
|
183
|
+
const tg = this.getTargetGroup(tgArn);
|
|
184
|
+
const target = tg.registerTarget(targetId, port);
|
|
185
|
+
logger.info(`✅ Target registrado: ${targetId} em ${tg.TargetGroupName}`);
|
|
186
|
+
// ✅ Persistir mudanças
|
|
187
|
+
this.persistData();
|
|
188
|
+
return target;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
deregisterTarget(tgArn, targetId, port = null) {
|
|
192
|
+
const tg = this.getTargetGroup(tgArn);
|
|
193
|
+
const target = tg.deregisterTarget(targetId, port);
|
|
194
|
+
logger.info(`🗑️ Target removido: ${targetId} de ${tg.TargetGroupName}`);
|
|
195
|
+
// ✅ Persistir mudanças
|
|
196
|
+
this.persistData();
|
|
197
|
+
return target;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
describeTargetHealth(tgArn) {
|
|
201
|
+
const tg = this.getTargetGroup(tgArn);
|
|
202
|
+
return {
|
|
203
|
+
TargetGroupArn: tgArn,
|
|
204
|
+
HealthDescriptions: tg.Targets.map(t => ({
|
|
205
|
+
TargetId: t.TargetId,
|
|
206
|
+
Port: t.Port,
|
|
207
|
+
HealthState: t.HealthState,
|
|
208
|
+
HealthReason: t.HealthReason
|
|
209
|
+
}))
|
|
210
|
+
};
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
// ========== Listener Operations ==========
|
|
214
|
+
|
|
215
|
+
addListener(albArn, protocol = 'HTTP', port = 80, tgArn = null) {
|
|
216
|
+
const alb = this.getLoadBalancer(albArn);
|
|
217
|
+
|
|
218
|
+
if (tgArn) {
|
|
219
|
+
this.getTargetGroup(tgArn); // Validar que o TG existe
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
const listener = alb.addListener(protocol, port, tgArn);
|
|
223
|
+
logger.info(`✅ Listener adicionado: ${protocol}:${port} → ${tgArn}`);
|
|
224
|
+
// ✅ Persistir mudanças
|
|
225
|
+
this.persistData();
|
|
226
|
+
return listener;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
// ========== Load Balancing Logic ==========
|
|
230
|
+
|
|
231
|
+
routeRequest(albArn, path = '/') {
|
|
232
|
+
const alb = this.getLoadBalancer(albArn);
|
|
233
|
+
|
|
234
|
+
// Se não há target groups, retornar erro
|
|
235
|
+
if (alb.TargetGroups.length === 0) {
|
|
236
|
+
return null;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
// Usar o primeiro target group por padrão
|
|
240
|
+
const tg = alb.TargetGroups[0];
|
|
241
|
+
const nextTarget = tg.getNextTarget();
|
|
242
|
+
|
|
243
|
+
return nextTarget
|
|
244
|
+
? {
|
|
245
|
+
TargetId: nextTarget.TargetId,
|
|
246
|
+
Port: nextTarget.Port,
|
|
247
|
+
TargetGroupArn: tg.TargetGroupArn
|
|
248
|
+
}
|
|
249
|
+
: null;
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
// ========== Utility ==========
|
|
253
|
+
|
|
254
|
+
getAllResources() {
|
|
255
|
+
return {
|
|
256
|
+
loadBalancers: Array.from(this.loadBalancers.values()),
|
|
257
|
+
targetGroups: Array.from(this.targetGroups.values())
|
|
258
|
+
};
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
module.exports = ALBSimulator;
|
|
@@ -38,6 +38,28 @@ class APIGatewayService {
|
|
|
38
38
|
injectDependencies(server) {
|
|
39
39
|
const ct = server.getService('cloudtrail');
|
|
40
40
|
if (ct?.simulator) this.simulator.audit.setTrail(ct.simulator);
|
|
41
|
+
|
|
42
|
+
const lambda = server.getService('lambda');
|
|
43
|
+
if (lambda) {
|
|
44
|
+
this.lambdaService = lambda;
|
|
45
|
+
if (this.simulator) this.simulator.setLambdaService(lambda);
|
|
46
|
+
if (this.server) this.server.lambdaService = lambda;
|
|
47
|
+
}
|
|
48
|
+
const sqs = server.getService('sqs');
|
|
49
|
+
if (sqs) {
|
|
50
|
+
if (this.simulator) this.simulator.setSqsService(sqs);
|
|
51
|
+
if (this.server) this.server.sqsService = sqs;
|
|
52
|
+
}
|
|
53
|
+
const sns = server.getService('sns');
|
|
54
|
+
if (sns) {
|
|
55
|
+
if (this.simulator) this.simulator.setSnsService(sns);
|
|
56
|
+
if (this.server) this.server.snsService = sns;
|
|
57
|
+
}
|
|
58
|
+
const eb = server.getService('eventbridge');
|
|
59
|
+
if (eb) {
|
|
60
|
+
if (this.simulator) this.simulator.setEventbridgeService(eb);
|
|
61
|
+
if (this.server) this.server.eventbridgeService = eb;
|
|
62
|
+
}
|
|
41
63
|
}
|
|
42
64
|
|
|
43
65
|
async start() {
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
const { setupServiceMiddleware } = require('../../middleware');
|
|
1
2
|
/**
|
|
2
3
|
* API Gateway Server - Servidor HTTP para API Gateway
|
|
3
4
|
*/
|
|
@@ -8,6 +9,8 @@ const logger = require('../../utils/logger');
|
|
|
8
9
|
|
|
9
10
|
class APIGatewayServer {
|
|
10
11
|
constructor(port, config) {
|
|
12
|
+
this.organizationsService = null;
|
|
13
|
+
this.stsService = null;
|
|
11
14
|
this.port = port;
|
|
12
15
|
this.config = config;
|
|
13
16
|
this.app = express();
|
|
@@ -68,50 +71,159 @@ class APIGatewayServer {
|
|
|
68
71
|
const cognitoAuthorizer = this._buildCognitoAuthorizer(api.authorizer);
|
|
69
72
|
|
|
70
73
|
for (const endpoint of (api.endpoints || [])) {
|
|
71
|
-
const { path, method, lambdaName, integrationType, authorizerRequired } = endpoint;
|
|
74
|
+
const { path, method, lambdaName, integrationType, authorizerRequired, queueArn, queueUrl, eventBusName, topicArn } = endpoint;
|
|
72
75
|
if (!path || !method) continue;
|
|
73
76
|
|
|
74
77
|
const expressPath = path.replace(/\{([^}]+)\}/g, ':$1');
|
|
75
78
|
const httpMethod = method.toLowerCase();
|
|
76
79
|
|
|
77
|
-
|
|
80
|
+
const intType = (integrationType || '').toLowerCase();
|
|
81
|
+
logger.debug(`📡 Registrando rota: ${method} ${path} -> ${intType || 'lambda'} (${lambdaName || queueArn || topicArn || eventBusName || 'n/a'})`);
|
|
78
82
|
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
83
|
+
let handler;
|
|
84
|
+
if (intType === 'sqs' || queueArn || queueUrl) {
|
|
85
|
+
handler = async (req, res) => {
|
|
86
|
+
try {
|
|
87
|
+
const sqsService = this.sqsService;
|
|
88
|
+
if (!sqsService) {
|
|
89
|
+
return res.status(500).json({ error: 'SQS service not available' });
|
|
90
|
+
}
|
|
91
|
+
const sqsSim = sqsService.simulator;
|
|
92
|
+
let resolvedQueueUrl = queueUrl;
|
|
93
|
+
if (!resolvedQueueUrl && queueArn) {
|
|
94
|
+
const nameMatch = String(queueArn).match(/:([^:]+)$/);
|
|
95
|
+
if (nameMatch) {
|
|
96
|
+
const q = sqsSim.queues.get(nameMatch[1]);
|
|
97
|
+
resolvedQueueUrl = q && q.url;
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
const bodyContent = req.body ? (typeof req.body === 'string' ? req.body : JSON.stringify(req.body)) : '';
|
|
101
|
+
const messageBody = req.body?.MessageBody || bodyContent;
|
|
102
|
+
if (!resolvedQueueUrl) {
|
|
103
|
+
return res.status(400).json({ __type: 'MissingParameter', message: 'QueueUrl or queueArn must be configured' });
|
|
104
|
+
}
|
|
105
|
+
const result = sqsSim.sendMessage({
|
|
106
|
+
QueueUrl: resolvedQueueUrl,
|
|
107
|
+
MessageBody: messageBody,
|
|
108
|
+
DelaySeconds: req.body?.DelaySeconds,
|
|
109
|
+
MessageAttributes: req.body?.MessageAttributes
|
|
110
|
+
});
|
|
111
|
+
if (result.error) return res.status(result.status || 400).json(result.error);
|
|
112
|
+
res.status(200).json({
|
|
113
|
+
SendMessageResponse: {
|
|
114
|
+
SendMessageResult: { MessageId: result.messageId, MD5OfMessageBody: result.md5 },
|
|
115
|
+
ResponseMetadata: { RequestId: Math.random().toString(36).slice(2) }
|
|
116
|
+
}
|
|
117
|
+
});
|
|
118
|
+
} catch (err) {
|
|
119
|
+
logger.error('SQS send error:', err);
|
|
120
|
+
res.status(500).json({ error: err.message });
|
|
121
|
+
}
|
|
122
|
+
};
|
|
123
|
+
} else if (intType === 'eventbridge' || intType === 'events' || eventBusName) {
|
|
124
|
+
handler = async (req, res) => {
|
|
125
|
+
try {
|
|
126
|
+
const ebService = this.eventbridgeService;
|
|
127
|
+
if (!ebService) {
|
|
128
|
+
return res.status(500).json({ error: 'EventBridge service not available' });
|
|
129
|
+
}
|
|
130
|
+
const ebSim = ebService.simulator;
|
|
131
|
+
const entries = req.body?.Entries || [{
|
|
132
|
+
Source: req.body?.Source || 'aws.apigateway',
|
|
133
|
+
DetailType: req.body?.DetailType || 'API Gateway Request',
|
|
134
|
+
Detail: JSON.stringify(req.body?.Detail || req.body || {}),
|
|
135
|
+
EventBusName: eventBusName || req.body?.EventBusName || 'default',
|
|
136
|
+
Time: new Date().toISOString(),
|
|
137
|
+
Resources: []
|
|
138
|
+
}];
|
|
139
|
+
const norm = entries.map(e => ({
|
|
140
|
+
Time: e.Time || new Date().toISOString(),
|
|
141
|
+
Source: e.Source || 'aws.apigateway',
|
|
142
|
+
Resources: e.Resources || [],
|
|
143
|
+
DetailType: e.DetailType || 'API Gateway Request',
|
|
144
|
+
Detail: typeof e.Detail === 'string' ? e.Detail : JSON.stringify(e.Detail || {}),
|
|
145
|
+
EventBusName: eventBusName || e.EventBusName || 'default',
|
|
146
|
+
TraceHeader: e.TraceHeader
|
|
147
|
+
}));
|
|
148
|
+
const result = await ebSim.putEvents({ Entries: norm });
|
|
149
|
+
res.status(200).json({
|
|
150
|
+
Entries: (result.Entries || []).map(e => ({
|
|
151
|
+
EventId: e.EventId || Math.random().toString(36).slice(2),
|
|
152
|
+
ErrorCode: e.ErrorCode,
|
|
153
|
+
ErrorMessage: e.ErrorMessage
|
|
154
|
+
})),
|
|
155
|
+
FailedEntryCount: result.FailedEntryCount || 0
|
|
156
|
+
});
|
|
157
|
+
} catch (err) {
|
|
158
|
+
logger.error('EventBridge putEvents error:', err);
|
|
159
|
+
res.status(500).json({ error: err.message });
|
|
84
160
|
}
|
|
161
|
+
};
|
|
162
|
+
} else if (intType === 'sns' || topicArn) {
|
|
163
|
+
handler = async (req, res) => {
|
|
164
|
+
try {
|
|
165
|
+
const snsService = this.snsService;
|
|
166
|
+
if (!snsService) {
|
|
167
|
+
return res.status(500).json({ error: 'SNS service not available' });
|
|
168
|
+
}
|
|
169
|
+
const snsSim = snsService.simulator;
|
|
170
|
+
const message = req.body?.Message || (req.body ? (typeof req.body === 'string' ? req.body : JSON.stringify(req.body)) : '');
|
|
171
|
+
const result = await snsSim.publish({
|
|
172
|
+
TopicArn: topicArn || req.body?.TopicArn,
|
|
173
|
+
TargetArn: req.body?.TargetArn,
|
|
174
|
+
Message: message,
|
|
175
|
+
Subject: req.body?.Subject,
|
|
176
|
+
MessageAttributes: req.body?.MessageAttributes
|
|
177
|
+
});
|
|
178
|
+
res.status(200).json({
|
|
179
|
+
PublishResponse: {
|
|
180
|
+
PublishResult: { MessageId: result.messageId || Math.random().toString(36).slice(2) },
|
|
181
|
+
ResponseMetadata: { RequestId: Math.random().toString(36).slice(2) }
|
|
182
|
+
}
|
|
183
|
+
});
|
|
184
|
+
} catch (err) {
|
|
185
|
+
logger.error('SNS publish error:', err);
|
|
186
|
+
res.status(500).json({ error: err.message });
|
|
187
|
+
}
|
|
188
|
+
};
|
|
189
|
+
} else {
|
|
190
|
+
handler = async (req, res) => {
|
|
191
|
+
try {
|
|
192
|
+
const lambdaService = this.lambdaService;
|
|
193
|
+
if (!lambdaService) {
|
|
194
|
+
return res.status(500).json({ error: 'Lambda service not available' });
|
|
195
|
+
}
|
|
85
196
|
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
path: req.path,
|
|
89
|
-
headers: req.headers,
|
|
90
|
-
queryStringParameters: Object.keys(req.query).length ? req.query : null,
|
|
91
|
-
pathParameters: Object.keys(req.params).length ? req.params : null,
|
|
92
|
-
body: req.body ? JSON.stringify(req.body) : null,
|
|
93
|
-
isBase64Encoded: false,
|
|
94
|
-
requestContext: {
|
|
197
|
+
const event = {
|
|
198
|
+
httpMethod: req.method,
|
|
95
199
|
path: req.path,
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
200
|
+
headers: req.headers,
|
|
201
|
+
queryStringParameters: Object.keys(req.query).length ? req.query : null,
|
|
202
|
+
pathParameters: Object.keys(req.params).length ? req.params : null,
|
|
203
|
+
body: req.body ? JSON.stringify(req.body) : null,
|
|
204
|
+
isBase64Encoded: false,
|
|
205
|
+
requestContext: {
|
|
206
|
+
path: req.path,
|
|
207
|
+
stage: 'local',
|
|
208
|
+
requestId: Math.random().toString(36).substring(7),
|
|
209
|
+
identity: { sourceIp: req.ip },
|
|
210
|
+
authorizer: req.cognitoAuthorizer || null
|
|
211
|
+
}
|
|
212
|
+
};
|
|
213
|
+
|
|
214
|
+
const result = await lambdaService.simulator.invoke(lambdaName, event);
|
|
215
|
+
const payload = result.Payload || {};
|
|
216
|
+
const statusCode = payload.statusCode || 200;
|
|
217
|
+
const headers = payload.headers || { 'Content-Type': 'application/json' };
|
|
218
|
+
const body = payload.body;
|
|
219
|
+
|
|
220
|
+
res.status(statusCode).set(headers).send(body);
|
|
221
|
+
} catch (err) {
|
|
222
|
+
logger.error(`Lambda invoke error (${lambdaName}):`, err);
|
|
223
|
+
res.status(500).json({ error: err.message });
|
|
224
|
+
}
|
|
225
|
+
};
|
|
226
|
+
}
|
|
115
227
|
|
|
116
228
|
const middlewares = [];
|
|
117
229
|
if (authorizerRequired && cognitoAuthorizer) {
|
|
@@ -434,6 +546,106 @@ class APIGatewayServer {
|
|
|
434
546
|
}
|
|
435
547
|
});
|
|
436
548
|
|
|
549
|
+
// Models
|
|
550
|
+
this.app.post('/restapis/:apiId/models', (req, res) => {
|
|
551
|
+
try {
|
|
552
|
+
const result = this.simulator.createModel({ ...req.body, restApiId: req.params.apiId });
|
|
553
|
+
res.json(result);
|
|
554
|
+
} catch (error) {
|
|
555
|
+
res.status(400).json({ error: error.message });
|
|
556
|
+
}
|
|
557
|
+
});
|
|
558
|
+
|
|
559
|
+
this.app.get('/restapis/:apiId/models', (req, res) => {
|
|
560
|
+
try {
|
|
561
|
+
const result = this.simulator.getModels({ restApiId: req.params.apiId });
|
|
562
|
+
res.json(result);
|
|
563
|
+
} catch (error) {
|
|
564
|
+
res.status(404).json({ error: error.message });
|
|
565
|
+
}
|
|
566
|
+
});
|
|
567
|
+
|
|
568
|
+
this.app.get('/restapis/:apiId/models/:modelName', (req, res) => {
|
|
569
|
+
try {
|
|
570
|
+
const result = this.simulator.getModel({
|
|
571
|
+
restApiId: req.params.apiId,
|
|
572
|
+
modelName: req.params.modelName,
|
|
573
|
+
flatten: req.query.flatten
|
|
574
|
+
});
|
|
575
|
+
res.json(result);
|
|
576
|
+
} catch (error) {
|
|
577
|
+
res.status(404).json({ error: error.message });
|
|
578
|
+
}
|
|
579
|
+
});
|
|
580
|
+
|
|
581
|
+
this.app.delete('/restapis/:apiId/models/:modelName', (req, res) => {
|
|
582
|
+
try {
|
|
583
|
+
const result = this.simulator.deleteModel({
|
|
584
|
+
restApiId: req.params.apiId,
|
|
585
|
+
modelName: req.params.modelName
|
|
586
|
+
});
|
|
587
|
+
res.json(result);
|
|
588
|
+
} catch (error) {
|
|
589
|
+
res.status(404).json({ error: error.message });
|
|
590
|
+
}
|
|
591
|
+
});
|
|
592
|
+
|
|
593
|
+
// Request Validators
|
|
594
|
+
this.app.post('/restapis/:apiId/requestvalidators', (req, res) => {
|
|
595
|
+
try {
|
|
596
|
+
const result = this.simulator.createRequestValidator({ ...req.body, restApiId: req.params.apiId });
|
|
597
|
+
res.json(result);
|
|
598
|
+
} catch (error) {
|
|
599
|
+
res.status(400).json({ error: error.message });
|
|
600
|
+
}
|
|
601
|
+
});
|
|
602
|
+
|
|
603
|
+
this.app.get('/restapis/:apiId/requestvalidators', (req, res) => {
|
|
604
|
+
try {
|
|
605
|
+
const result = this.simulator.getRequestValidators({ restApiId: req.params.apiId });
|
|
606
|
+
res.json(result);
|
|
607
|
+
} catch (error) {
|
|
608
|
+
res.status(404).json({ error: error.message });
|
|
609
|
+
}
|
|
610
|
+
});
|
|
611
|
+
|
|
612
|
+
this.app.get('/restapis/:apiId/requestvalidators/:requestValidatorId', (req, res) => {
|
|
613
|
+
try {
|
|
614
|
+
const result = this.simulator.getRequestValidator({
|
|
615
|
+
restApiId: req.params.apiId,
|
|
616
|
+
requestValidatorId: req.params.requestValidatorId
|
|
617
|
+
});
|
|
618
|
+
res.json(result);
|
|
619
|
+
} catch (error) {
|
|
620
|
+
res.status(404).json({ error: error.message });
|
|
621
|
+
}
|
|
622
|
+
});
|
|
623
|
+
|
|
624
|
+
this.app.patch('/restapis/:apiId/requestvalidators/:requestValidatorId', (req, res) => {
|
|
625
|
+
try {
|
|
626
|
+
const result = this.simulator.updateRequestValidator({
|
|
627
|
+
...req.body,
|
|
628
|
+
restApiId: req.params.apiId,
|
|
629
|
+
requestValidatorId: req.params.requestValidatorId
|
|
630
|
+
});
|
|
631
|
+
res.json(result);
|
|
632
|
+
} catch (error) {
|
|
633
|
+
res.status(400).json({ error: error.message });
|
|
634
|
+
}
|
|
635
|
+
});
|
|
636
|
+
|
|
637
|
+
this.app.delete('/restapis/:apiId/requestvalidators/:requestValidatorId', (req, res) => {
|
|
638
|
+
try {
|
|
639
|
+
const result = this.simulator.deleteRequestValidator({
|
|
640
|
+
restApiId: req.params.apiId,
|
|
641
|
+
requestValidatorId: req.params.requestValidatorId
|
|
642
|
+
});
|
|
643
|
+
res.json(result);
|
|
644
|
+
} catch (error) {
|
|
645
|
+
res.status(404).json({ error: error.message });
|
|
646
|
+
}
|
|
647
|
+
});
|
|
648
|
+
|
|
437
649
|
// HTTP APIs
|
|
438
650
|
this.app.post('/httpapis', (req, res) => {
|
|
439
651
|
try {
|