@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.
- package/README.md +662 -658
- package/package.json +11 -4
- 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/handler-loader.js +50 -5
- package/src/services/lambda/index.js +26 -0
- package/src/services/lambda/server.js +18 -12
- package/src/services/lambda/simulator.js +73 -17
- 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,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) => {
|