@gugananuvem/aws-local-simulator 1.0.8 → 1.0.10

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 (41) hide show
  1. package/README.md +2 -1
  2. package/bin/aws-local-simulator.js +62 -1
  3. package/package.json +10 -7
  4. package/src/config/config-loader.js +113 -0
  5. package/src/config/default-config.js +65 -0
  6. package/src/config/env-loader.js +69 -0
  7. package/src/index.js +131 -1
  8. package/src/index.mjs +124 -0
  9. package/src/server.js +222 -0
  10. package/src/services/apigateway/index.js +67 -0
  11. package/src/services/apigateway/server.js +435 -0
  12. package/src/services/apigateway/simulator.js +1252 -0
  13. package/src/services/cognito/index.js +66 -0
  14. package/src/services/cognito/server.js +229 -0
  15. package/src/services/cognito/simulator.js +848 -0
  16. package/src/services/dynamodb/index.js +71 -0
  17. package/src/services/dynamodb/server.js +122 -0
  18. package/src/services/dynamodb/simulator.js +614 -0
  19. package/src/services/ecs/index.js +66 -0
  20. package/src/services/ecs/server.js +234 -0
  21. package/src/services/ecs/simulator.js +845 -0
  22. package/src/services/eventbridge/index.js +85 -0
  23. package/src/services/index.js +19 -0
  24. package/src/services/lambda/handler-loader.js +173 -0
  25. package/src/services/lambda/index.js +73 -0
  26. package/src/services/lambda/route-registry.js +275 -0
  27. package/src/services/lambda/server.js +153 -0
  28. package/src/services/lambda/simulator.js +285 -0
  29. package/src/services/s3/index.js +70 -0
  30. package/src/services/s3/server.js +239 -0
  31. package/src/services/s3/simulator.js +740 -0
  32. package/src/services/sns/index.js +76 -0
  33. package/src/services/sqs/index.js +96 -0
  34. package/src/services/sqs/server.js +274 -0
  35. package/src/services/sqs/simulator.js +660 -0
  36. package/src/template/aws-config-template.js +88 -0
  37. package/src/template/aws-config-template.mjs +91 -0
  38. package/src/template/config-template.json +203 -0
  39. package/src/utils/aws-config.js +92 -0
  40. package/src/utils/local-store.js +68 -0
  41. package/src/utils/logger.js +60 -0
@@ -0,0 +1,845 @@
1
+ /**
2
+ * ECS Simulator Core
3
+ * Simula clusters, serviços, tarefas e containers
4
+ */
5
+
6
+ const crypto = require('crypto');
7
+ const { spawn, exec } = require('child_process');
8
+ const path = require('path');
9
+ const fs = require('fs');
10
+ const logger = require('../../utils/logger');
11
+ const LocalStore = require('../../utils/local-store');
12
+
13
+ class ECSSimulator {
14
+ constructor(config) {
15
+ this.config = config;
16
+ this.dataDir = path.join(process.env.AWS_LOCAL_SIMULATOR_DATA_DIR, 'ecs');
17
+ this.store = new LocalStore(this.dataDir);
18
+ this.clusters = new Map();
19
+ this.services = new Map();
20
+ this.tasks = new Map();
21
+ this.containerProcesses = new Map();
22
+ this.availablePorts = new Set();
23
+ this.nextPort = 50000;
24
+ }
25
+
26
+ async initialize() {
27
+ logger.debug('Inicializando ECS Simulator...');
28
+ this.loadClusters();
29
+ this.loadServices();
30
+ this.loadTasks();
31
+
32
+ // Inicializa range de portas para containers
33
+ for (let i = 50000; i <= 51000; i++) {
34
+ this.availablePorts.add(i);
35
+ }
36
+ this.nextPort = 50000;
37
+
38
+ logger.debug(`✅ ECS Simulator inicializado com ${this.clusters.size} clusters, ${this.services.size} serviços, ${this.tasks.size} tarefas`);
39
+ }
40
+
41
+ loadClusters() {
42
+ const savedClusters = this.store.read('__clusters__');
43
+ if (savedClusters) {
44
+ for (const [name, data] of Object.entries(savedClusters)) {
45
+ this.clusters.set(name, {
46
+ name: data.name,
47
+ arn: data.arn,
48
+ status: data.status,
49
+ createdAt: new Date(data.createdAt),
50
+ services: data.services || [],
51
+ tasks: data.tasks || []
52
+ });
53
+ }
54
+ }
55
+
56
+ // Cria clusters da configuração
57
+ if (this.config.ecs?.clusters) {
58
+ for (const clusterName of this.config.ecs.clusters) {
59
+ this.createCluster(clusterName);
60
+ }
61
+ }
62
+ }
63
+
64
+ loadServices() {
65
+ const savedServices = this.store.read('__services__');
66
+ if (savedServices) {
67
+ for (const [name, data] of Object.entries(savedServices)) {
68
+ this.services.set(name, {
69
+ name: data.name,
70
+ arn: data.arn,
71
+ clusterArn: data.clusterArn,
72
+ taskDefinition: data.taskDefinition,
73
+ desiredCount: data.desiredCount,
74
+ runningCount: data.runningCount,
75
+ status: data.status,
76
+ loadBalancers: data.loadBalancers,
77
+ networkConfiguration: data.networkConfiguration,
78
+ schedulingStrategy: data.schedulingStrategy,
79
+ createdAt: new Date(data.createdAt)
80
+ });
81
+ }
82
+ }
83
+ }
84
+
85
+ loadTasks() {
86
+ const savedTasks = this.store.read('__tasks__');
87
+ if (savedTasks) {
88
+ for (const [id, data] of Object.entries(savedTasks)) {
89
+ this.tasks.set(id, {
90
+ taskArn: data.taskArn,
91
+ taskDefinitionArn: data.taskDefinitionArn,
92
+ clusterArn: data.clusterArn,
93
+ serviceName: data.serviceName,
94
+ containers: data.containers,
95
+ lastStatus: data.lastStatus,
96
+ desiredStatus: data.desiredStatus,
97
+ startedAt: new Date(data.startedAt),
98
+ stoppedAt: data.stoppedAt ? new Date(data.stoppedAt) : null,
99
+ stopCode: data.stopCode,
100
+ createdAt: new Date(data.createdAt)
101
+ });
102
+
103
+ // Reconstitui processos de container se estavam rodando
104
+ if (data.lastStatus === 'RUNNING') {
105
+ this.startContainerProcess(data.taskArn, data.containers);
106
+ }
107
+ }
108
+ }
109
+ }
110
+
111
+ // ============ Cluster Operations ============
112
+
113
+ createCluster(clusterName) {
114
+ if (this.clusters.has(clusterName)) {
115
+ return { error: { code: 'ClusterExists', message: 'Cluster already exists' }, status: 409 };
116
+ }
117
+
118
+ const cluster = {
119
+ name: clusterName,
120
+ arn: `arn:aws:ecs:local:000000000000:cluster/${clusterName}`,
121
+ status: 'ACTIVE',
122
+ createdAt: new Date(),
123
+ services: [],
124
+ tasks: []
125
+ };
126
+
127
+ this.clusters.set(clusterName, cluster);
128
+ this.persistClusters();
129
+
130
+ logger.debug(`✅ Cluster ECS criado: ${clusterName}`);
131
+
132
+ return { cluster };
133
+ }
134
+
135
+ listClusters() {
136
+ return Array.from(this.clusters.keys());
137
+ }
138
+
139
+ describeCluster(clusterName) {
140
+ const cluster = this.clusters.get(clusterName);
141
+ if (!cluster) {
142
+ throw new Error(`Cluster ${clusterName} not found`);
143
+ }
144
+
145
+ return {
146
+ cluster: {
147
+ clusterName: cluster.name,
148
+ clusterArn: cluster.arn,
149
+ status: cluster.status,
150
+ registeredContainerInstancesCount: 0,
151
+ runningTasksCount: this.getRunningTasksCount(clusterName),
152
+ pendingTasksCount: this.getPendingTasksCount(clusterName),
153
+ activeServicesCount: this.getActiveServicesCount(clusterName),
154
+ statistics: []
155
+ }
156
+ };
157
+ }
158
+
159
+ deleteCluster(clusterName) {
160
+ const cluster = this.clusters.get(clusterName);
161
+ if (!cluster) {
162
+ return { error: { code: 'ClusterNotFound', message: 'Cluster not found' }, status: 404 };
163
+ }
164
+
165
+ const services = this.getServicesInCluster(clusterName);
166
+ if (services.length > 0) {
167
+ return { error: { code: 'ClusterNotEmpty', message: 'Cluster has active services' }, status: 409 };
168
+ }
169
+
170
+ const tasks = this.getTasksInCluster(clusterName);
171
+ if (tasks.length > 0) {
172
+ return { error: { code: 'ClusterNotEmpty', message: 'Cluster has active tasks' }, status: 409 };
173
+ }
174
+
175
+ this.clusters.delete(clusterName);
176
+ this.persistClusters();
177
+
178
+ return { success: true };
179
+ }
180
+
181
+ // ============ Task Definition Operations ============
182
+
183
+ registerTaskDefinition(params) {
184
+ const { family, containerDefinitions, networkMode, cpu, memory, executionRoleArn, taskRoleArn } = params;
185
+
186
+ const taskDefinitionArn = `arn:aws:ecs:local:000000000000:task-definition/${family}:${this.getNextRevision(family)}`;
187
+
188
+ const taskDefinition = {
189
+ family,
190
+ taskDefinitionArn,
191
+ revision: this.getNextRevision(family),
192
+ containerDefinitions: containerDefinitions.map(this.normalizeContainerDefinition.bind(this)),
193
+ networkMode: networkMode || 'awsvpc',
194
+ cpu: cpu || '256',
195
+ memory: memory || '512',
196
+ executionRoleArn,
197
+ taskRoleArn,
198
+ status: 'ACTIVE',
199
+ registeredAt: new Date().toISOString()
200
+ };
201
+
202
+ this.store.write(`taskdef_${family}_${taskDefinition.revision}`, taskDefinition);
203
+ this.persistTaskDefinitions();
204
+
205
+ logger.debug(`✅ Task Definition registrada: ${taskDefinitionArn}`);
206
+
207
+ return { taskDefinition };
208
+ }
209
+
210
+ getNextRevision(family) {
211
+ const files = this.store.list();
212
+ const revisions = files
213
+ .filter(f => f.startsWith(`taskdef_${family}_`))
214
+ .map(f => parseInt(f.split('_')[2]) || 0);
215
+
216
+ return Math.max(0, ...revisions) + 1;
217
+ }
218
+
219
+ normalizeContainerDefinition(container) {
220
+ return {
221
+ name: container.name,
222
+ image: container.image,
223
+ cpu: container.cpu || 0,
224
+ memory: container.memory || 0,
225
+ memoryReservation: container.memoryReservation || 0,
226
+ essential: container.essential !== false,
227
+ portMappings: (container.portMappings || []).map(pm => ({
228
+ containerPort: pm.containerPort,
229
+ hostPort: pm.hostPort || 0,
230
+ protocol: pm.protocol || 'tcp'
231
+ })),
232
+ environment: container.environment || [],
233
+ environmentFiles: container.environmentFiles || [],
234
+ secrets: container.secrets || [],
235
+ mountPoints: container.mountPoints || [],
236
+ volumesFrom: container.volumesFrom || [],
237
+ linuxParameters: container.linuxParameters || {},
238
+ logConfiguration: container.logConfiguration || {
239
+ logDriver: 'awslogs',
240
+ options: {
241
+ 'awslogs-group': `/ecs/${container.name}`,
242
+ 'awslogs-region': 'local',
243
+ 'awslogs-stream-prefix': 'ecs'
244
+ }
245
+ }
246
+ };
247
+ }
248
+
249
+ // ============ Service Operations ============
250
+
251
+ createService(params) {
252
+ const { cluster, serviceName, taskDefinition, desiredCount, loadBalancers, networkConfiguration, schedulingStrategy } = params;
253
+
254
+ const clusterObj = this.clusters.get(cluster);
255
+ if (!clusterObj) {
256
+ return { error: { code: 'ClusterNotFound', message: 'Cluster not found' }, status: 404 };
257
+ }
258
+
259
+ if (this.services.has(serviceName)) {
260
+ return { error: { code: 'ServiceExists', message: 'Service already exists' }, status: 409 };
261
+ }
262
+
263
+ const serviceArn = `arn:aws:ecs:local:000000000000:service/${cluster}/${serviceName}`;
264
+
265
+ const service = {
266
+ name: serviceName,
267
+ arn: serviceArn,
268
+ clusterArn: clusterObj.arn,
269
+ taskDefinition,
270
+ desiredCount: desiredCount || 1,
271
+ runningCount: 0,
272
+ pendingCount: 0,
273
+ status: 'ACTIVE',
274
+ loadBalancers: loadBalancers || [],
275
+ networkConfiguration: networkConfiguration || {
276
+ awsvpcConfiguration: {
277
+ subnets: ['subnet-local'],
278
+ securityGroups: ['sg-local'],
279
+ assignPublicIp: 'ENABLED'
280
+ }
281
+ },
282
+ schedulingStrategy: schedulingStrategy || 'REPLICA',
283
+ createdAt: new Date(),
284
+ tasks: []
285
+ };
286
+
287
+ this.services.set(serviceName, service);
288
+ clusterObj.services.push(serviceName);
289
+ this.persistServices();
290
+ this.persistClusters();
291
+
292
+ // Inicia as tarefas do serviço
293
+ if (desiredCount > 0) {
294
+ this.scaleService(serviceName, desiredCount);
295
+ }
296
+
297
+ logger.debug(`✅ Serviço ECS criado: ${serviceName} (${desiredCount} tarefas)`);
298
+
299
+ return { service };
300
+ }
301
+
302
+ updateService(params) {
303
+ const { cluster, service, desiredCount, taskDefinition } = params;
304
+
305
+ const serviceObj = this.services.get(service);
306
+ if (!serviceObj) {
307
+ return { error: { code: 'ServiceNotFound', message: 'Service not found' }, status: 404 };
308
+ }
309
+
310
+ if (desiredCount !== undefined) {
311
+ serviceObj.desiredCount = desiredCount;
312
+ this.scaleService(service, desiredCount);
313
+ }
314
+
315
+ if (taskDefinition) {
316
+ serviceObj.taskDefinition = taskDefinition;
317
+ // Em produção, faria uma atualização gradual
318
+ this.updateServiceTasks(service, taskDefinition);
319
+ }
320
+
321
+ this.persistServices();
322
+
323
+ return { service: serviceObj };
324
+ }
325
+
326
+ scaleService(serviceName, desiredCount) {
327
+ const service = this.services.get(serviceName);
328
+ if (!service) return;
329
+
330
+ const currentCount = this.getRunningTasksCountForService(serviceName);
331
+ const diff = desiredCount - currentCount;
332
+
333
+ if (diff > 0) {
334
+ // Scale up
335
+ for (let i = 0; i < diff; i++) {
336
+ this.runTask({
337
+ cluster: service.clusterArn.split('/').pop(),
338
+ taskDefinition: service.taskDefinition,
339
+ serviceName: service.name
340
+ });
341
+ }
342
+ } else if (diff < 0) {
343
+ // Scale down
344
+ const tasks = this.getTasksForService(serviceName);
345
+ const toStop = tasks.slice(0, -diff);
346
+ for (const task of toStop) {
347
+ this.stopTask(task.taskArn);
348
+ }
349
+ }
350
+
351
+ service.runningCount = this.getRunningTasksCountForService(serviceName);
352
+ service.pendingCount = this.getPendingTasksCountForService(serviceName);
353
+ }
354
+
355
+ // ============ Task Operations ============
356
+
357
+ async runTask(params) {
358
+ const { cluster, taskDefinition, serviceName, overrides } = params;
359
+
360
+ const clusterObj = this.clusters.get(cluster);
361
+ if (!clusterObj) {
362
+ return { error: { code: 'ClusterNotFound', message: 'Cluster not found' }, status: 404 };
363
+ }
364
+
365
+ // Busca a task definition
366
+ const taskDef = this.getTaskDefinition(taskDefinition);
367
+ if (!taskDef) {
368
+ return { error: { code: 'TaskDefinitionNotFound', message: 'Task definition not found' }, status: 404 };
369
+ }
370
+
371
+ const taskId = crypto.randomUUID();
372
+ const taskArn = `arn:aws:ecs:local:000000000000:task/${cluster}/${taskId}`;
373
+
374
+ // Prepara containers com portas mapeadas
375
+ const containers = await this.prepareContainers(taskDef.containerDefinitions, overrides);
376
+
377
+ const task = {
378
+ taskArn,
379
+ taskDefinitionArn: taskDef.taskDefinitionArn,
380
+ clusterArn: clusterObj.arn,
381
+ serviceName: serviceName || null,
382
+ containers,
383
+ lastStatus: 'PROVISIONING',
384
+ desiredStatus: 'RUNNING',
385
+ startedAt: null,
386
+ stoppedAt: null,
387
+ stopCode: null,
388
+ createdAt: new Date(),
389
+ overrides: overrides || {}
390
+ };
391
+
392
+ this.tasks.set(taskArn, task);
393
+ clusterObj.tasks.push(taskArn);
394
+
395
+ if (serviceName) {
396
+ const service = this.services.get(serviceName);
397
+ if (service) {
398
+ service.tasks.push(taskArn);
399
+ service.pendingCount++;
400
+ }
401
+ }
402
+
403
+ this.persistTasks();
404
+ this.persistClusters();
405
+ this.persistServices();
406
+
407
+ logger.debug(`📦 Tarefa ECS criada: ${taskArn}`);
408
+
409
+ // Inicia os containers
410
+ this.startTask(task);
411
+
412
+ return { task };
413
+ }
414
+
415
+ async prepareContainers(containerDefs, overrides) {
416
+ const containers = [];
417
+
418
+ for (const containerDef of containerDefs) {
419
+ // Aloca portas para o container
420
+ const portMappings = [];
421
+ for (const mapping of containerDef.portMappings || []) {
422
+ const hostPort = this.allocatePort();
423
+ portMappings.push({
424
+ containerPort: mapping.containerPort,
425
+ hostPort,
426
+ protocol: mapping.protocol
427
+ });
428
+ }
429
+
430
+ containers.push({
431
+ name: containerDef.name,
432
+ image: containerDef.image,
433
+ containerArn: `arn:aws:ecs:local:container/${crypto.randomUUID()}`,
434
+ lastStatus: 'PROVISIONING',
435
+ desiredStatus: 'RUNNING',
436
+ portMappings,
437
+ environment: containerDef.environment,
438
+ command: overrides?.containerOverrides?.find(c => c.name === containerDef.name)?.command || null,
439
+ startedAt: null,
440
+ stoppedAt: null
441
+ });
442
+ }
443
+
444
+ return containers;
445
+ }
446
+
447
+ allocatePort() {
448
+ if (this.availablePorts.size === 0) {
449
+ // Expande range de portas
450
+ for (let i = this.nextPort; i <= this.nextPort + 100; i++) {
451
+ this.availablePorts.add(i);
452
+ }
453
+ this.nextPort += 100;
454
+ }
455
+
456
+ const port = Array.from(this.availablePorts)[0];
457
+ this.availablePorts.delete(port);
458
+ return port;
459
+ }
460
+
461
+ releasePort(port) {
462
+ this.availablePorts.add(port);
463
+ }
464
+
465
+ async startTask(task) {
466
+ task.lastStatus = 'PENDING';
467
+ this.persistTasks();
468
+
469
+ // Simula tempo de provisão
470
+ setTimeout(async () => {
471
+ task.lastStatus = 'RUNNING';
472
+ task.startedAt = new Date();
473
+
474
+ if (task.serviceName) {
475
+ const service = this.services.get(task.serviceName);
476
+ if (service) {
477
+ service.runningCount++;
478
+ service.pendingCount--;
479
+ this.persistServices();
480
+ }
481
+ }
482
+
483
+ this.persistTasks();
484
+
485
+ // Inicia cada container
486
+ for (const container of task.containers) {
487
+ await this.startContainer(task.taskArn, container);
488
+ }
489
+
490
+ logger.success(`✅ Tarefa ECS iniciada: ${task.taskArn}`);
491
+ }, 1000);
492
+ }
493
+
494
+ async startContainer(taskArn, container) {
495
+ const containerId = crypto.randomUUID();
496
+ container.lastStatus = 'RUNNING';
497
+ container.startedAt = new Date();
498
+ container.containerId = containerId;
499
+
500
+ logger.info(`🐳 Container iniciado: ${container.name} (${container.image})`);
501
+ logger.info(` Portas: ${container.portMappings.map(p => `${p.containerPort}:${p.hostPort}`).join(', ')}`);
502
+
503
+ // Simula execução do container
504
+ // Em um cenário real, aqui você poderia realmente executar o container Docker
505
+ this.containerProcesses.set(container.containerId, {
506
+ taskArn,
507
+ container,
508
+ running: true,
509
+ startTime: new Date()
510
+ });
511
+
512
+ this.persistTasks();
513
+ }
514
+
515
+ async stopTask(taskArn) {
516
+ const task = this.tasks.get(taskArn);
517
+ if (!task) {
518
+ return { error: { code: 'TaskNotFound', message: 'Task not found' }, status: 404 };
519
+ }
520
+
521
+ // Para todos os containers
522
+ for (const container of task.containers) {
523
+ if (container.containerId) {
524
+ const process = this.containerProcesses.get(container.containerId);
525
+ if (process) {
526
+ process.running = false;
527
+ this.containerProcesses.delete(container.containerId);
528
+ }
529
+ container.lastStatus = 'STOPPED';
530
+ container.stoppedAt = new Date();
531
+ this.releasePorts(container.portMappings);
532
+ }
533
+ }
534
+
535
+ task.lastStatus = 'STOPPED';
536
+ task.desiredStatus = 'STOPPED';
537
+ task.stoppedAt = new Date();
538
+ task.stopCode = 'UserInitiated';
539
+
540
+ if (task.serviceName) {
541
+ const service = this.services.get(task.serviceName);
542
+ if (service && service.runningCount > 0) {
543
+ service.runningCount--;
544
+ this.persistServices();
545
+ }
546
+ }
547
+
548
+ this.persistTasks();
549
+
550
+ logger.debug(`🛑 Tarefa ECS parada: ${taskArn}`);
551
+
552
+ return { task };
553
+ }
554
+
555
+ releasePorts(portMappings) {
556
+ for (const mapping of portMappings) {
557
+ if (mapping.hostPort) {
558
+ this.releasePort(mapping.hostPort);
559
+ }
560
+ }
561
+ }
562
+
563
+ listTasks(params) {
564
+ const { cluster, serviceName } = params;
565
+ let tasks = Array.from(this.tasks.values());
566
+
567
+ if (cluster) {
568
+ tasks = tasks.filter(t => t.clusterArn.includes(cluster));
569
+ }
570
+
571
+ if (serviceName) {
572
+ tasks = tasks.filter(t => t.serviceName === serviceName);
573
+ }
574
+
575
+ return {
576
+ taskArns: tasks.map(t => t.taskArn)
577
+ };
578
+ }
579
+
580
+ describeTasks(params) {
581
+ const { cluster, tasks } = params;
582
+ const taskList = [];
583
+
584
+ for (const taskArn of tasks) {
585
+ const task = this.tasks.get(taskArn);
586
+ if (task) {
587
+ taskList.push({
588
+ taskArn: task.taskArn,
589
+ taskDefinitionArn: task.taskDefinitionArn,
590
+ clusterArn: task.clusterArn,
591
+ serviceName: task.serviceName,
592
+ containers: task.containers.map(c => ({
593
+ name: c.name,
594
+ containerArn: c.containerArn,
595
+ lastStatus: c.lastStatus,
596
+ networkBindings: c.portMappings.map(pm => ({
597
+ containerPort: pm.containerPort,
598
+ hostPort: pm.hostPort,
599
+ protocol: pm.protocol
600
+ }))
601
+ })),
602
+ lastStatus: task.lastStatus,
603
+ desiredStatus: task.desiredStatus,
604
+ startedAt: task.startedAt,
605
+ stoppedAt: task.stoppedAt,
606
+ stopCode: task.stopCode,
607
+ createdAt: task.createdAt
608
+ });
609
+ }
610
+ }
611
+
612
+ return { tasks: taskList, failures: [] };
613
+ }
614
+
615
+ // ============ Helper Methods ============
616
+
617
+ getTaskDefinition(taskDefinition) {
618
+ const [family, revision] = taskDefinition.split(':').pop().split('/').pop().split(':');
619
+ return this.store.read(`taskdef_${family}_${revision || this.getLatestRevision(family)}`);
620
+ }
621
+
622
+ getLatestRevision(family) {
623
+ const files = this.store.list();
624
+ const revisions = files
625
+ .filter(f => f.startsWith(`taskdef_${family}_`))
626
+ .map(f => parseInt(f.split('_')[2]) || 0);
627
+
628
+ return Math.max(0, ...revisions);
629
+ }
630
+
631
+ getRunningTasksCount(clusterName) {
632
+ const cluster = this.clusters.get(clusterName);
633
+ if (!cluster) return 0;
634
+
635
+ let count = 0;
636
+ for (const taskArn of cluster.tasks) {
637
+ const task = this.tasks.get(taskArn);
638
+ if (task && task.lastStatus === 'RUNNING') {
639
+ count++;
640
+ }
641
+ }
642
+ return count;
643
+ }
644
+
645
+ getPendingTasksCount(clusterName) {
646
+ const cluster = this.clusters.get(clusterName);
647
+ if (!cluster) return 0;
648
+
649
+ let count = 0;
650
+ for (const taskArn of cluster.tasks) {
651
+ const task = this.tasks.get(taskArn);
652
+ if (task && task.lastStatus === 'PENDING') {
653
+ count++;
654
+ }
655
+ }
656
+ return count;
657
+ }
658
+
659
+ getActiveServicesCount(clusterName) {
660
+ const cluster = this.clusters.get(clusterName);
661
+ if (!cluster) return 0;
662
+ return cluster.services.length;
663
+ }
664
+
665
+ getServicesInCluster(clusterName) {
666
+ const cluster = this.clusters.get(clusterName);
667
+ if (!cluster) return [];
668
+ return cluster.services;
669
+ }
670
+
671
+ getTasksInCluster(clusterName) {
672
+ const cluster = this.clusters.get(clusterName);
673
+ if (!cluster) return [];
674
+ return cluster.tasks;
675
+ }
676
+
677
+ getRunningTasksCountForService(serviceName) {
678
+ const service = this.services.get(serviceName);
679
+ if (!service) return 0;
680
+
681
+ let count = 0;
682
+ for (const taskArn of service.tasks) {
683
+ const task = this.tasks.get(taskArn);
684
+ if (task && task.lastStatus === 'RUNNING') {
685
+ count++;
686
+ }
687
+ }
688
+ return count;
689
+ }
690
+
691
+ getPendingTasksCountForService(serviceName) {
692
+ const service = this.services.get(serviceName);
693
+ if (!service) return 0;
694
+
695
+ let count = 0;
696
+ for (const taskArn of service.tasks) {
697
+ const task = this.tasks.get(taskArn);
698
+ if (task && task.lastStatus === 'PENDING') {
699
+ count++;
700
+ }
701
+ }
702
+ return count;
703
+ }
704
+
705
+ getTasksForService(serviceName) {
706
+ const service = this.services.get(serviceName);
707
+ if (!service) return [];
708
+
709
+ const tasks = [];
710
+ for (const taskArn of service.tasks) {
711
+ const task = this.tasks.get(taskArn);
712
+ if (task) {
713
+ tasks.push(task);
714
+ }
715
+ }
716
+ return tasks;
717
+ }
718
+
719
+ updateServiceTasks(serviceName, newTaskDefinition) {
720
+ const service = this.services.get(serviceName);
721
+ if (!service) return;
722
+
723
+ const tasks = this.getTasksForService(serviceName);
724
+ for (const task of tasks) {
725
+ if (task.lastStatus === 'RUNNING') {
726
+ // Para a tarefa antiga e inicia nova
727
+ this.stopTask(task.taskArn);
728
+ this.runTask({
729
+ cluster: service.clusterArn.split('/').pop(),
730
+ taskDefinition: newTaskDefinition,
731
+ serviceName: service.name
732
+ });
733
+ }
734
+ }
735
+ }
736
+
737
+ // ============ Persistence ============
738
+
739
+ persistClusters() {
740
+ const clustersObj = {};
741
+ for (const [name, cluster] of this.clusters.entries()) {
742
+ clustersObj[name] = {
743
+ name: cluster.name,
744
+ arn: cluster.arn,
745
+ status: cluster.status,
746
+ createdAt: cluster.createdAt.toISOString(),
747
+ services: cluster.services,
748
+ tasks: cluster.tasks
749
+ };
750
+ }
751
+ this.store.write('__clusters__', clustersObj);
752
+ }
753
+
754
+ persistServices() {
755
+ const servicesObj = {};
756
+ for (const [name, service] of this.services.entries()) {
757
+ servicesObj[name] = {
758
+ name: service.name,
759
+ arn: service.arn,
760
+ clusterArn: service.clusterArn,
761
+ taskDefinition: service.taskDefinition,
762
+ desiredCount: service.desiredCount,
763
+ runningCount: service.runningCount,
764
+ status: service.status,
765
+ loadBalancers: service.loadBalancers,
766
+ networkConfiguration: service.networkConfiguration,
767
+ schedulingStrategy: service.schedulingStrategy,
768
+ createdAt: service.createdAt.toISOString(),
769
+ tasks: service.tasks
770
+ };
771
+ }
772
+ this.store.write('__services__', servicesObj);
773
+ }
774
+
775
+ persistTasks() {
776
+ const tasksObj = {};
777
+ for (const [arn, task] of this.tasks.entries()) {
778
+ tasksObj[arn] = {
779
+ taskArn: task.taskArn,
780
+ taskDefinitionArn: task.taskDefinitionArn,
781
+ clusterArn: task.clusterArn,
782
+ serviceName: task.serviceName,
783
+ containers: task.containers.map(c => ({
784
+ name: c.name,
785
+ image: c.image,
786
+ containerArn: c.containerArn,
787
+ lastStatus: c.lastStatus,
788
+ desiredStatus: c.desiredStatus,
789
+ portMappings: c.portMappings,
790
+ environment: c.environment,
791
+ startedAt: c.startedAt,
792
+ stoppedAt: c.stoppedAt
793
+ })),
794
+ lastStatus: task.lastStatus,
795
+ desiredStatus: task.desiredStatus,
796
+ startedAt: task.startedAt,
797
+ stoppedAt: task.stoppedAt,
798
+ stopCode: task.stopCode,
799
+ createdAt: task.createdAt.toISOString()
800
+ };
801
+ }
802
+ this.store.write('__tasks__', tasksObj);
803
+ }
804
+
805
+ persistTaskDefinitions() {
806
+ // Task definitions são persistidas individualmente
807
+ // Já salvas no registerTaskDefinition
808
+ }
809
+
810
+ async reset() {
811
+ // Para todas as tarefas em execução
812
+ for (const [taskArn] of this.tasks) {
813
+ await this.stopTask(taskArn);
814
+ }
815
+
816
+ this.clusters.clear();
817
+ this.services.clear();
818
+ this.tasks.clear();
819
+ this.containerProcesses.clear();
820
+
821
+ this.persistClusters();
822
+ this.persistServices();
823
+ this.persistTasks();
824
+
825
+ logger.debug('ECS: Todos os dados resetados');
826
+ }
827
+
828
+ getClustersCount() {
829
+ return this.clusters.size;
830
+ }
831
+
832
+ getServicesCount() {
833
+ return this.services.size;
834
+ }
835
+
836
+ getTasksCount() {
837
+ return this.tasks.size;
838
+ }
839
+
840
+ getRunningContainers() {
841
+ return this.containerProcesses.size;
842
+ }
843
+ }
844
+
845
+ module.exports = ECSSimulator;