@gugananuvem/aws-local-simulator 1.0.34 → 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.
Files changed (150) hide show
  1. package/README.md +662 -658
  2. package/package.json +11 -4
  3. package/src/config/default-config.js +71 -0
  4. package/src/config/env-loader.js +4 -2
  5. package/src/middleware/authorization.js +271 -0
  6. package/src/middleware/cloudtrail-logger.js +333 -0
  7. package/src/middleware/error-handler.js +138 -0
  8. package/src/middleware/index.js +79 -0
  9. package/src/middleware/policy-enforcement.js +407 -0
  10. package/src/server.js +74 -4
  11. package/src/services/acm/index.js +66 -0
  12. package/src/services/acm/models/certificate.js +202 -0
  13. package/src/services/acm/server.js +224 -0
  14. package/src/services/acm/simulator.js +293 -0
  15. package/src/services/alb/index.js +104 -0
  16. package/src/services/alb/models/loadBalancer.js +88 -0
  17. package/src/services/alb/models/targetGroup.js +128 -0
  18. package/src/services/alb/server.js +160 -0
  19. package/src/services/alb/simulator.js +262 -0
  20. package/src/services/apigateway/index.js +22 -0
  21. package/src/services/apigateway/server.js +247 -35
  22. package/src/services/apigateway/simulator.js +780 -26
  23. package/src/services/asg/index.js +100 -0
  24. package/src/services/asg/models/autoScalingGroup.js +110 -0
  25. package/src/services/asg/models/launchTemplate.js +41 -0
  26. package/src/services/asg/models/scalingPolicy.js +51 -0
  27. package/src/services/asg/server.js +236 -0
  28. package/src/services/asg/simulator.js +410 -0
  29. package/src/services/aurora/cluster-manager.js +731 -0
  30. package/src/services/aurora/data-api.js +514 -0
  31. package/src/services/aurora/db-engine.js +270 -0
  32. package/src/services/aurora/index.js +159 -0
  33. package/src/services/aurora/rds-api.js +189 -0
  34. package/src/services/aurora/server.js +213 -0
  35. package/src/services/aurora/sql-translator.js +334 -0
  36. package/src/services/cloudformation/index.js +2 -2
  37. package/src/services/cloudformation/server.js +1 -0
  38. package/src/services/cloudfront/cache-engine.js +225 -0
  39. package/src/services/cloudfront/geo-restrictions.js +377 -0
  40. package/src/services/cloudfront/index.js +220 -0
  41. package/src/services/cloudfront/models/behavior.js +158 -0
  42. package/src/services/cloudfront/models/distribution.js +284 -0
  43. package/src/services/cloudfront/models/index.js +99 -0
  44. package/src/services/cloudfront/models/invalidation.js +109 -0
  45. package/src/services/cloudfront/models/origin.js +155 -0
  46. package/src/services/cloudfront/origin-router.js +552 -0
  47. package/src/services/cloudfront/origin-validator.js +398 -0
  48. package/src/services/cloudfront/server.js +512 -0
  49. package/src/services/cloudfront/signed-url-generator.js +377 -0
  50. package/src/services/cloudfront/simulator.js +425 -0
  51. package/src/services/cloudfront/store.js +326 -0
  52. package/src/services/cloudshell/env-builder.js +27 -0
  53. package/src/services/cloudshell/index.js +48 -0
  54. package/src/services/cloudshell/routes.js +60 -0
  55. package/src/services/cloudshell/session-manager.js +226 -0
  56. package/src/services/cloudshell/static/.gitkeep +0 -0
  57. package/src/services/cloudshell/static/index.html +406 -0
  58. package/src/services/cloudshell/websocket-handler.js +107 -0
  59. package/src/services/cloudtrail/index.js +2 -3
  60. package/src/services/cloudtrail/server.js +68 -0
  61. package/src/services/cognito/server.js +4 -1
  62. package/src/services/dynamodb/server.js +4 -1
  63. package/src/services/dynamodb/simulator.js +2 -1
  64. package/src/services/ec2/index.js +101 -0
  65. package/src/services/ec2/instanceServer.js +311 -0
  66. package/src/services/ec2/models/ami.js +39 -0
  67. package/src/services/ec2/models/instance.js +98 -0
  68. package/src/services/ec2/models/networkInterface.js +74 -0
  69. package/src/services/ec2/models/securityGroup.js +71 -0
  70. package/src/services/ec2/server.js +350 -0
  71. package/src/services/ec2/simulator.js +436 -0
  72. package/src/services/eventbridge/server.js +36 -0
  73. package/src/services/eventbridge/simulator.js +230 -45
  74. package/src/services/iam/index.js +65 -0
  75. package/src/services/iam/middleware.js +99 -0
  76. package/src/services/iam/server.js +331 -0
  77. package/src/services/iam/simulator.js +1432 -0
  78. package/src/services/index.js +28 -0
  79. package/src/services/kinesis-analytics/index.js +105 -0
  80. package/src/services/kinesis-analytics/models/application.js +325 -0
  81. package/src/services/kinesis-analytics/models/index.js +12 -0
  82. package/src/services/kinesis-analytics/server.js +250 -0
  83. package/src/services/kinesis-analytics/simulator.js +407 -0
  84. package/src/services/kinesis-analytics/store.js +176 -0
  85. package/src/services/kinesis-data-streams/index.js +106 -0
  86. package/src/services/kinesis-data-streams/models/index.js +12 -0
  87. package/src/services/kinesis-data-streams/models/record.js +70 -0
  88. package/src/services/kinesis-data-streams/models/stream.js +280 -0
  89. package/src/services/kinesis-data-streams/server.js +255 -0
  90. package/src/services/kinesis-data-streams/simulator.js +514 -0
  91. package/src/services/kinesis-data-streams/store.js +163 -0
  92. package/src/services/kinesis-firehose/index.js +106 -0
  93. package/src/services/kinesis-firehose/models/delivery-stream.js +265 -0
  94. package/src/services/kinesis-firehose/models/index.js +10 -0
  95. package/src/services/kinesis-firehose/server.js +215 -0
  96. package/src/services/kinesis-firehose/simulator.js +388 -0
  97. package/src/services/kinesis-firehose/store.js +163 -0
  98. package/src/services/kms/server.js +4 -1
  99. package/src/services/lambda/handler-loader.js +50 -5
  100. package/src/services/lambda/index.js +26 -0
  101. package/src/services/lambda/server.js +18 -12
  102. package/src/services/lambda/simulator.js +73 -17
  103. package/src/services/opensearch/index.js +105 -0
  104. package/src/services/opensearch/models/document.js +115 -0
  105. package/src/services/opensearch/models/index.js +73 -0
  106. package/src/services/opensearch/server.js +310 -0
  107. package/src/services/opensearch/simulator.js +605 -0
  108. package/src/services/opensearch/store.js +162 -0
  109. package/src/services/organizations/control-tower-simulator.js +321 -0
  110. package/src/services/organizations/control-tower-store.js +182 -0
  111. package/src/services/organizations/iam-integration.js +413 -0
  112. package/src/services/organizations/index.js +172 -0
  113. package/src/services/organizations/models/index.js +13 -0
  114. package/src/services/organizations/models/invitation.js +48 -0
  115. package/src/services/organizations/models/organization.js +566 -0
  116. package/src/services/organizations/models/role.js +244 -0
  117. package/src/services/organizations/models/scp.js +131 -0
  118. package/src/services/organizations/server.js +736 -0
  119. package/src/services/organizations/simulator.js +593 -0
  120. package/src/services/organizations/store.js +235 -0
  121. package/src/services/organizations/vpc-integration.js +453 -0
  122. package/src/services/route53/index.js +67 -0
  123. package/src/services/route53/models/healthCheck.js +101 -0
  124. package/src/services/route53/models/hostedZone.js +37 -0
  125. package/src/services/route53/models/resourceRecord.js +106 -0
  126. package/src/services/route53/server.js +841 -0
  127. package/src/services/route53/simulator.js +350 -0
  128. package/src/services/s3/index.js +62 -0
  129. package/src/services/s3/presigned-url-generator.js +337 -0
  130. package/src/services/s3/server.js +231 -0
  131. package/src/services/s3/simulator.js +551 -4
  132. package/src/services/sns/index.js +31 -0
  133. package/src/services/sns/server.js +9 -0
  134. package/src/services/sqs/index.js +38 -0
  135. package/src/services/sqs/server.js +57 -4
  136. package/src/services/sqs/simulator.js +423 -33
  137. package/src/services/sts/simulator.js +209 -17
  138. package/src/services/vpc/README.md +459 -0
  139. package/src/services/vpc/ec2ResponseFormatter.js +431 -0
  140. package/src/services/vpc/index.js +90 -0
  141. package/src/services/vpc/models/internetGateway.js +71 -0
  142. package/src/services/vpc/models/routeTable.js +160 -0
  143. package/src/services/vpc/models/subnet.js +48 -0
  144. package/src/services/vpc/models/vpc.js +37 -0
  145. package/src/services/vpc/models/vpcEndpoint.js +100 -0
  146. package/src/services/vpc/models/vpcGateway.js +134 -0
  147. package/src/services/vpc/server.js +836 -0
  148. package/src/services/vpc/simulator.js +613 -0
  149. package/src/utils/policy-cache.js +215 -0
  150. package/aws-config +0 -154
@@ -0,0 +1,436 @@
1
+ /**
2
+ * EC2 Simulator
3
+ * Gerencia instâncias, AMIs, security groups e network interfaces com persistência
4
+ */
5
+
6
+ const path = require('path');
7
+ const Instance = require('./models/instance');
8
+ const AMI = require('./models/ami');
9
+ const SecurityGroup = require('./models/securityGroup');
10
+ const NetworkInterface = require('./models/networkInterface');
11
+ const InstanceServer = require('./instanceServer');
12
+ const LocalStore = require('../../utils/local-store');
13
+ const logger = require('../../utils/logger');
14
+
15
+ class EC2Simulator {
16
+ constructor(config = {}) {
17
+ this.config = config;
18
+ this.instances = new Map();
19
+ this.amis = new Map();
20
+ this.securityGroups = new Map();
21
+ this.networkInterfaces = new Map();
22
+ this.instanceServers = new Map();
23
+ this.instanceCounter = 0;
24
+
25
+ // Inicializar persistência
26
+ const dataDir = process.env.AWS_LOCAL_SIMULATOR_DATA_DIR || config.dataDir || './.aws-local-simulator-data';
27
+ this.dataDir = path.join(dataDir, 'ec2');
28
+ this.store = new LocalStore(this.dataDir);
29
+ }
30
+
31
+ async initialize() {
32
+ // 1. Carregar dados persistidos
33
+ await this.loadFromPersistence();
34
+
35
+ // 2. Se não houver dados, criar AMIs e security groups padrão
36
+ if (this.amis.size === 0) {
37
+ this.createDefaultAMIs();
38
+ }
39
+
40
+ if (this.securityGroups.size === 0) {
41
+ this.createDefaultSecurityGroup();
42
+ }
43
+
44
+ // 3. Pré-provisionar instâncias se configurado e não existirem
45
+ if (this.instances.size === 0 && this.config.instances && Array.isArray(this.config.instances)) {
46
+ logger.info('📚 Carregando Instâncias EC2 da configuração inicial...');
47
+ for (const instanceConfig of this.config.instances) {
48
+ const instance = this.runInstances(
49
+ instanceConfig.ImageId || 'ami-default',
50
+ 1,
51
+ instanceConfig.InstanceType || 't2.micro',
52
+ instanceConfig
53
+ )[0];
54
+
55
+ // Se houver website dir, iniciar servidor
56
+ if (instanceConfig.WebServerDir) {
57
+ await this.startInstanceServer(instance.InstanceId, instanceConfig.WebServerDir);
58
+ }
59
+ }
60
+ }
61
+ }
62
+
63
+ /**
64
+ * Carrega dados persistidos do storage
65
+ */
66
+ async loadFromPersistence() {
67
+ try {
68
+ // Carregar AMIs
69
+ const amisData = this.store.read('amis');
70
+ if (Array.isArray(amisData)) {
71
+ logger.info(`📂 Carregando ${amisData.length} AMIs do storage...`);
72
+ for (const amiData of amisData) {
73
+ const ami = new AMI(amiData.Name, amiData.Architecture, amiData.RootDeviceType);
74
+ ami.ImageId = amiData.ImageId;
75
+ this.amis.set(ami.ImageId, ami);
76
+ }
77
+ }
78
+
79
+ // Carregar Security Groups
80
+ const sgsData = this.store.read('security-groups');
81
+ if (Array.isArray(sgsData)) {
82
+ logger.info(`📂 Carregando ${sgsData.length} Security Groups do storage...`);
83
+ for (const sgData of sgsData) {
84
+ const sg = new SecurityGroup(sgData.GroupName, sgData.Description, sgData.VpcId);
85
+ sg.GroupId = sgData.GroupId;
86
+ sg.IngressRules = sgData.IngressRules || [];
87
+ sg.EgressRules = sgData.EgressRules || [];
88
+ this.securityGroups.set(sg.GroupId, sg);
89
+ }
90
+ }
91
+
92
+ // Carregar Network Interfaces
93
+ const nisData = this.store.read('network-interfaces');
94
+ if (Array.isArray(nisData)) {
95
+ logger.info(`📂 Carregando ${nisData.length} Network Interfaces do storage...`);
96
+ for (const niData of nisData) {
97
+ const ni = new NetworkInterface(niData.SubnetId, niData.VpcId);
98
+ ni.NetworkInterfaceId = niData.NetworkInterfaceId;
99
+ ni.PrivateIpAddress = niData.PrivateIpAddress;
100
+ ni.SecurityGroups = niData.SecurityGroups || [];
101
+ this.networkInterfaces.set(ni.NetworkInterfaceId, ni);
102
+ }
103
+ }
104
+
105
+ // Carregar Instâncias
106
+ const instancesData = this.store.read('instances');
107
+ if (Array.isArray(instancesData)) {
108
+ logger.info(`📂 Carregando ${instancesData.length} Instâncias do storage...`);
109
+ for (const instData of instancesData) {
110
+ const instance = new Instance(instData.ImageId, instData.InstanceType, instData.SecurityGroupIds);
111
+ instance.InstanceId = instData.InstanceId;
112
+ instance.State = instData.State;
113
+ instance.LaunchTime = instData.LaunchTime;
114
+ instance.PublicIpAddress = instData.PublicIpAddress;
115
+ instance.PrivateIpAddress = instData.PrivateIpAddress;
116
+ instance.Tags = instData.Tags || [];
117
+ this.instances.set(instance.InstanceId, instance);
118
+ }
119
+ }
120
+ } catch (error) {
121
+ logger.warn(`⚠️ Erro ao carregar dados persistidos EC2: ${error.message}`);
122
+ }
123
+ }
124
+
125
+ /**
126
+ * Persiste estado atual em storage
127
+ */
128
+ persistData() {
129
+ try {
130
+ this.store.write('amis', Array.from(this.amis.values()).map(a => a.toJSON ? a.toJSON() : a));
131
+ this.store.write('security-groups', Array.from(this.securityGroups.values()).map(sg => sg.toJSON ? sg.toJSON() : sg));
132
+ this.store.write('network-interfaces', Array.from(this.networkInterfaces.values()).map(ni => ni.toJSON ? ni.toJSON() : ni));
133
+ this.store.write('instances', Array.from(this.instances.values()).map(i => i.toJSON ? i.toJSON() : i));
134
+ } catch (error) {
135
+ logger.error(`Erro ao persistir dados EC2: ${error.message}`);
136
+ }
137
+ }
138
+
139
+ createDefaultAMIs() {
140
+ const amis = [
141
+ { name: 'ubuntu-20.04', desc: 'Ubuntu 20.04 LTS' },
142
+ { name: 'ubuntu-22.04', desc: 'Ubuntu 22.04 LTS' },
143
+ { name: 'amazon-linux-2', desc: 'Amazon Linux 2' },
144
+ { name: 'debian-11', desc: 'Debian 11' }
145
+ ];
146
+
147
+ amis.forEach(ami => {
148
+ const newAmi = new AMI(ami.name, ami.desc);
149
+ this.amis.set(newAmi.ImageId, newAmi);
150
+ });
151
+
152
+ logger.debug(`✅ ${amis.length} AMIs padrão criadas`);
153
+ }
154
+
155
+ createDefaultSecurityGroup() {
156
+ const sg = new SecurityGroup('default', 'Security group padrão');
157
+ sg.addIngressRule('0.0.0.0/0', null, 80, 80, 'tcp', 'HTTP');
158
+ sg.addIngressRule('0.0.0.0/0', null, 443, 443, 'tcp', 'HTTPS');
159
+ sg.addIngressRule('0.0.0.0/0', null, 22, 22, 'tcp', 'SSH');
160
+ this.securityGroups.set(sg.GroupId, sg);
161
+ logger.debug(`✅ Security group padrão criado: ${sg.GroupId}`);
162
+ }
163
+
164
+ // ========== Instance Operations ==========
165
+
166
+ runInstances(imageId, count = 1, instanceType = 't2.micro', config = {}) {
167
+ const ami = this.amis.get(imageId) || this.getFirstAMI();
168
+ if (!ami) {
169
+ throw new Error(`AMI ${imageId} not found`);
170
+ }
171
+
172
+ const instances = [];
173
+
174
+ for (let i = 0; i < count; i++) {
175
+ const instance = new Instance(imageId, instanceType, {
176
+ SubnetId: config.SubnetId,
177
+ VpcId: config.VpcId,
178
+ SecurityGroupIds: config.SecurityGroupIds || ['sg-default'],
179
+ KeyName: config.KeyName,
180
+ Tags: config.Tags,
181
+ WebServerDir: config.WebServerDir,
182
+ ProxyUrl: config.ProxyUrl // Passar ProxyUrl se configurado
183
+ });
184
+
185
+ this.instances.set(instance.InstanceId, instance);
186
+
187
+ // Criar network interface para a instância
188
+ if (config.SubnetId && config.VpcId) {
189
+ const eni = new NetworkInterface(config.SubnetId, config.VpcId);
190
+ eni.attachToInstance(instance.InstanceId, 0);
191
+ this.networkInterfaces.set(eni.NetworkInterfaceId, eni);
192
+ }
193
+
194
+ instances.push(instance);
195
+ logger.info(`✅ Instância criada: ${instance.InstanceId} (${instanceType})`);
196
+ }
197
+
198
+ // ✅ Persistir mudanças
199
+ this.persistData();
200
+
201
+ return instances;
202
+ }
203
+
204
+ describeInstances(filters = {}) {
205
+ let instances = Array.from(this.instances.values());
206
+
207
+ if (filters.instanceIds && Array.isArray(filters.instanceIds)) {
208
+ instances = instances.filter(i => filters.instanceIds.includes(i.InstanceId));
209
+ }
210
+
211
+ if (filters.state) {
212
+ instances = instances.filter(i => i.State === filters.state);
213
+ }
214
+
215
+ if (filters.vpcIds && Array.isArray(filters.vpcIds)) {
216
+ instances = instances.filter(i => filters.vpcIds.includes(i.VpcId));
217
+ }
218
+
219
+ return instances;
220
+ }
221
+
222
+ getInstance(instanceId) {
223
+ const instance = this.instances.get(instanceId);
224
+ if (!instance) {
225
+ throw new Error(`Instance ${instanceId} not found`);
226
+ }
227
+ return instance;
228
+ }
229
+
230
+ startInstances(instanceIds) {
231
+ const instances = [];
232
+ for (const instanceId of instanceIds) {
233
+ const instance = this.getInstance(instanceId);
234
+ instance.setState('running', 'User initiated');
235
+ instances.push(instance);
236
+ logger.info(`✅ Instância iniciada: ${instanceId}`);
237
+ }
238
+ // ✅ Persistir mudanças
239
+ this.persistData();
240
+ return instances;
241
+ }
242
+
243
+ stopInstances(instanceIds) {
244
+ const instances = [];
245
+ for (const instanceId of instanceIds) {
246
+ const instance = this.getInstance(instanceId);
247
+ instance.setState('stopped', 'User initiated');
248
+ instances.push(instance);
249
+ logger.info(`⏸️ Instância parada: ${instanceId}`);
250
+ }
251
+ // ✅ Persistir mudanças
252
+ this.persistData();
253
+ return instances;
254
+ }
255
+
256
+ rebootInstances(instanceIds) {
257
+ const instances = [];
258
+ for (const instanceId of instanceIds) {
259
+ const instance = this.getInstance(instanceId);
260
+ // Simular reinício: running -> stopping -> running
261
+ instance.setState('stopping', 'Rebooting');
262
+ setTimeout(() => {
263
+ instance.setState('running', 'Reboot complete');
264
+ this.persistData();
265
+ }, 100);
266
+ instances.push(instance);
267
+ logger.info(`🔄 Instância reiniciada: ${instanceId}`);
268
+ }
269
+ return instances;
270
+ }
271
+
272
+ terminateInstances(instanceIds) {
273
+ const instances = [];
274
+ for (const instanceId of instanceIds) {
275
+ const instance = this.getInstance(instanceId);
276
+ instance.setState('terminated', 'User initiated');
277
+
278
+ // Parar servidor web se estiver rodando
279
+ if (this.instanceServers.has(instanceId)) {
280
+ this.stopInstanceServer(instanceId);
281
+ }
282
+
283
+ instances.push(instance);
284
+ logger.info(`🗑️ Instância terminada: ${instanceId}`);
285
+ }
286
+ // ✅ Persistir mudanças
287
+ this.persistData();
288
+ return instances;
289
+ }
290
+
291
+ // ========== Instance Server Operations ==========
292
+
293
+ async startInstanceServer(instanceId, websiteDir = null, proxyUrl = null) {
294
+ const instance = this.getInstance(instanceId);
295
+
296
+ if (this.instanceServers.has(instanceId)) {
297
+ throw new Error(`Instance server already running for ${instanceId}`);
298
+ }
299
+
300
+ // Se ProxyUrl foi fornecido, atualizar a instância
301
+ if (proxyUrl) {
302
+ instance.ProxyUrl = proxyUrl;
303
+ }
304
+
305
+ try {
306
+ const server = new InstanceServer(instance, websiteDir);
307
+ await server.start();
308
+ this.instanceServers.set(instanceId, server);
309
+
310
+ const serverType = proxyUrl ? '🔗 Proxy' : '🌐 Web';
311
+ const target = proxyUrl ? `→ ${proxyUrl}` : websiteDir ? `→ ${websiteDir}` : '';
312
+ logger.info(`${serverType} servidor iniciado para ${instanceId} ${target}`);
313
+ } catch (error) {
314
+ logger.error(`Erro ao iniciar servidor web para ${instanceId}:`, error.message);
315
+ throw error;
316
+ }
317
+ }
318
+
319
+ async stopInstanceServer(instanceId) {
320
+ const server = this.instanceServers.get(instanceId);
321
+ if (server) {
322
+ await server.stop();
323
+ this.instanceServers.delete(instanceId);
324
+ logger.info(`🌐 Servidor web parado para ${instanceId}`);
325
+ }
326
+ }
327
+
328
+ getInstanceServerPort(instanceId) {
329
+ const instance = this.getInstance(instanceId);
330
+ return instance.WebServerPort;
331
+ }
332
+
333
+ // ========== AMI Operations ==========
334
+
335
+ describeImages(filters = {}) {
336
+ let amis = Array.from(this.amis.values());
337
+
338
+ if (filters.imageIds && Array.isArray(filters.imageIds)) {
339
+ amis = amis.filter(a => filters.imageIds.includes(a.ImageId));
340
+ }
341
+
342
+ return amis;
343
+ }
344
+
345
+ getFirstAMI() {
346
+ return this.amis.values().next().value;
347
+ }
348
+
349
+ // ========== Security Group Operations ==========
350
+
351
+ createSecurityGroup(name, description = '', vpcId = null, config = {}) {
352
+ const sg = new SecurityGroup(name, description, vpcId, config);
353
+ this.securityGroups.set(sg.GroupId, sg);
354
+ return sg;
355
+ }
356
+
357
+ describeSecurityGroups(filters = {}) {
358
+ let sgs = Array.from(this.securityGroups.values());
359
+
360
+ if (filters.groupIds && Array.isArray(filters.groupIds)) {
361
+ sgs = sgs.filter(sg => filters.groupIds.includes(sg.GroupId));
362
+ }
363
+
364
+ if (filters.groupNames && Array.isArray(filters.groupNames)) {
365
+ sgs = sgs.filter(sg => filters.groupNames.includes(sg.GroupName));
366
+ }
367
+
368
+ return sgs;
369
+ }
370
+
371
+ getSecurityGroup(groupId) {
372
+ const sg = this.securityGroups.get(groupId);
373
+ if (!sg) {
374
+ throw new Error(`Security Group ${groupId} not found`);
375
+ }
376
+ return sg;
377
+ }
378
+
379
+ deleteSecurityGroup(groupId) {
380
+ // Verificar se está em uso
381
+ const inUse = Array.from(this.instances.values()).some(i =>
382
+ i.SecurityGroupIds.includes(groupId)
383
+ );
384
+
385
+ if (inUse) {
386
+ throw new Error(`Cannot delete security group ${groupId}: in use`);
387
+ }
388
+
389
+ this.securityGroups.delete(groupId);
390
+ return true;
391
+ }
392
+
393
+ // ========== Network Interface Operations ==========
394
+
395
+ createNetworkInterface(subnetId, vpcId, config = {}) {
396
+ const eni = new NetworkInterface(subnetId, vpcId, config);
397
+ this.networkInterfaces.set(eni.NetworkInterfaceId, eni);
398
+ return eni;
399
+ }
400
+
401
+ describeNetworkInterfaces(filters = {}) {
402
+ let enis = Array.from(this.networkInterfaces.values());
403
+
404
+ if (filters.networkInterfaceIds && Array.isArray(filters.networkInterfaceIds)) {
405
+ enis = enis.filter(e => filters.networkInterfaceIds.includes(e.NetworkInterfaceId));
406
+ }
407
+
408
+ if (filters.subnetIds && Array.isArray(filters.subnetIds)) {
409
+ enis = enis.filter(e => filters.subnetIds.includes(e.SubnetId));
410
+ }
411
+
412
+ return enis;
413
+ }
414
+
415
+ getNetworkInterface(eniId) {
416
+ const eni = this.networkInterfaces.get(eniId);
417
+ if (!eni) {
418
+ throw new Error(`Network Interface ${eniId} not found`);
419
+ }
420
+ return eni;
421
+ }
422
+
423
+ // ========== Utility ==========
424
+
425
+ getAllResources() {
426
+ return {
427
+ instances: Array.from(this.instances.values()),
428
+ amis: Array.from(this.amis.values()),
429
+ securityGroups: Array.from(this.securityGroups.values()),
430
+ networkInterfaces: Array.from(this.networkInterfaces.values()),
431
+ instanceServersCount: this.instanceServers.size
432
+ };
433
+ }
434
+ }
435
+
436
+ module.exports = EC2Simulator;
@@ -59,6 +59,42 @@ function createEventBridgeServer(simulator, config, logger) {
59
59
  } catch (err) { sendError(res, err); }
60
60
  });
61
61
 
62
+ // ==================== JSON routes for React frontend ====================
63
+
64
+ app.post('/json/eventbridge/rules', async (req, res) => {
65
+ try {
66
+ const result = await simulator.putRule(req.body);
67
+ res.status(201).json(result);
68
+ } catch (err) { sendError(res, err); }
69
+ });
70
+
71
+ app.delete('/json/eventbridge/rules/:name', async (req, res) => {
72
+ try {
73
+ await simulator.deleteRule({ Name: req.params.name, EventBusName: req.query.eventBusName });
74
+ res.status(200).json({});
75
+ } catch (err) { sendError(res, err); }
76
+ });
77
+
78
+ app.get('/json/eventbridge/rules', (req, res) => {
79
+ try {
80
+ const result = simulator.listRules({
81
+ EventBusName: req.query.EventBusName || 'default',
82
+ NamePrefix: req.query.NamePrefix,
83
+ Limit: req.query.Limit ? parseInt(req.query.Limit) : undefined
84
+ });
85
+ // Return just the array, not the wrapper object
86
+ res.json(result.Rules || []);
87
+ } catch (err) { sendError(res, err); }
88
+ });
89
+
90
+ app.get('/json/eventbridge/buses', (req, res) => {
91
+ try {
92
+ const result = simulator.listEventBuses();
93
+ // Return just the array, not the wrapper object
94
+ res.json(result.EventBuses || []);
95
+ } catch (err) { sendError(res, err); }
96
+ });
97
+
62
98
  // ==================== Rules ====================
63
99
 
64
100
  app.put('/rules', async (req, res) => {