@gugananuvem/aws-local-simulator 1.0.12 → 1.0.14

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 (57) hide show
  1. package/README.md +235 -11
  2. package/package.json +12 -2
  3. package/src/config/default-config.js +1 -0
  4. package/src/index.js +18 -2
  5. package/src/server.js +36 -32
  6. package/src/services/apigateway/index.js +5 -0
  7. package/src/services/apigateway/server.js +20 -0
  8. package/src/services/apigateway/simulator.js +13 -3
  9. package/src/services/athena/index.js +75 -0
  10. package/src/services/athena/server.js +101 -0
  11. package/src/services/athena/simulador.js +998 -0
  12. package/src/services/athena/simulator.js +346 -0
  13. package/src/services/cloudformation/index.js +106 -0
  14. package/src/services/cloudformation/server.js +417 -0
  15. package/src/services/cloudformation/simulador.js +1045 -0
  16. package/src/services/cloudtrail/index.js +84 -0
  17. package/src/services/cloudtrail/server.js +235 -0
  18. package/src/services/cloudtrail/simulador.js +719 -0
  19. package/src/services/cloudwatch/index.js +84 -0
  20. package/src/services/cloudwatch/server.js +366 -0
  21. package/src/services/cloudwatch/simulador.js +1173 -0
  22. package/src/services/cognito/index.js +5 -0
  23. package/src/services/cognito/simulator.js +4 -0
  24. package/src/services/config/index.js +96 -0
  25. package/src/services/config/server.js +215 -0
  26. package/src/services/config/simulador.js +1260 -0
  27. package/src/services/dynamodb/index.js +7 -3
  28. package/src/services/dynamodb/server.js +4 -2
  29. package/src/services/dynamodb/simulator.js +39 -29
  30. package/src/services/eventbridge/index.js +55 -51
  31. package/src/services/eventbridge/server.js +209 -0
  32. package/src/services/eventbridge/simulator.js +684 -0
  33. package/src/services/index.js +30 -4
  34. package/src/services/kms/index.js +75 -0
  35. package/src/services/kms/server.js +67 -0
  36. package/src/services/kms/simulator.js +324 -0
  37. package/src/services/lambda/index.js +5 -0
  38. package/src/services/lambda/simulator.js +48 -38
  39. package/src/services/parameter-store/index.js +80 -0
  40. package/src/services/parameter-store/server.js +50 -0
  41. package/src/services/parameter-store/simulator.js +201 -0
  42. package/src/services/s3/index.js +7 -3
  43. package/src/services/s3/server.js +20 -13
  44. package/src/services/s3/simulator.js +163 -407
  45. package/src/services/secret-manager/index.js +80 -0
  46. package/src/services/secret-manager/server.js +50 -0
  47. package/src/services/secret-manager/simulator.js +171 -0
  48. package/src/services/sns/index.js +55 -42
  49. package/src/services/sns/server.js +580 -0
  50. package/src/services/sns/simulator.js +1482 -0
  51. package/src/services/sqs/index.js +2 -4
  52. package/src/services/sqs/server.js +4 -2
  53. package/src/services/xray/index.js +83 -0
  54. package/src/services/xray/server.js +308 -0
  55. package/src/services/xray/simulador.js +994 -0
  56. package/src/utils/cloudtrail-audit.js +129 -0
  57. package/src/utils/local-store.js +18 -2
@@ -21,10 +21,9 @@ class DynamoDBService {
21
21
  const logger = require('../../utils/logger');
22
22
  logger.debug(`Inicializando DynamoDB Service na porta ${this.port}...`);
23
23
 
24
- // Cria o simulador
25
24
  this.simulator = new DynamoDBSimulator(this.config);
26
-
27
- // Cria o servidor HTTP
25
+ await this.simulator.initialize();
26
+
28
27
  this.server = new DynamoDBServer(this.port, this.config);
29
28
  this.server.simulator = this.simulator;
30
29
 
@@ -33,6 +32,11 @@ class DynamoDBService {
33
32
  logger.debug('DynamoDB Service inicializado');
34
33
  }
35
34
 
35
+ injectDependencies(server) {
36
+ const ct = server.getService('cloudtrail');
37
+ if (ct?.simulator) this.simulator.audit.setTrail(ct.simulator);
38
+ }
39
+
36
40
  async start() {
37
41
  if (this.isRunning) return;
38
42
  await this.server.start();
@@ -35,8 +35,10 @@ class DynamoDBServer {
35
35
  }
36
36
 
37
37
  async initialize() {
38
- this.simulator = new DynamoDBSimulator(this.config);
39
- await this.simulator.initialize();
38
+ if (!this.simulator) {
39
+ this.simulator = new DynamoDBSimulator(this.config);
40
+ await this.simulator.initialize();
41
+ }
40
42
  this.setupRoutes();
41
43
  }
42
44
 
@@ -6,6 +6,7 @@ const LocalStore = require("../../utils/local-store");
6
6
  const logger = require("../../utils/logger");
7
7
  const crypto = require("crypto");
8
8
  const path = require("path");
9
+ const { CloudTrailAudit } = require("../../utils/cloudtrail-audit");
9
10
 
10
11
  class DynamoDBSimulator {
11
12
  constructor(config) {
@@ -19,6 +20,7 @@ class DynamoDBSimulator {
19
20
  this.dataDir = path.join(dataDir, "dynamodb");
20
21
  this.store = new LocalStore(this.dataDir);
21
22
  this.tables = new Map();
23
+ this.audit = new CloudTrailAudit("dynamodb.amazonaws.com");
22
24
  }
23
25
  async initialize() {
24
26
  logger.debug("Inicializando DynamoDB Simulator...");
@@ -74,10 +76,13 @@ class DynamoDBSimulator {
74
76
  this.tables.set(TableName, table);
75
77
  this.persistTables();
76
78
 
77
- // Inicializa arquivo de dados
78
- this.store.write(TableName, []);
79
+ // Inicializa arquivo de dados apenas se não existir (preserva dados entre reinicializações)
80
+ if (!this.store.exists(TableName)) {
81
+ this.store.write(TableName, []);
82
+ }
79
83
 
80
84
  logger.debug(`✅ Tabela criada: ${TableName}`);
85
+ this.audit.record({ eventName: "CreateTable", readOnly: false, resources: [{ ARN: `arn:aws:dynamodb:local:000000000000:table/${TableName}`, type: "AWS::DynamoDB::Table" }], requestParameters: { tableName: TableName } });
81
86
 
82
87
  return {
83
88
  TableDescription: {
@@ -101,34 +106,39 @@ class DynamoDBSimulator {
101
106
 
102
107
  logger.verboso(`DynamoDB Action: ${action}`, params);
103
108
 
104
- switch (action) {
105
- case "CreateTable":
106
- return this.createTable(params);
107
- case "DescribeTable":
108
- return this.describeTable(params.TableName);
109
- case "ListTables":
110
- return this.listTables(params);
111
- case "DeleteTable":
112
- return this.deleteTable(params);
113
- case "PutItem":
114
- return this.putItem(params);
115
- case "GetItem":
116
- return this.getItem(params);
117
- case "UpdateItem":
118
- return this.updateItem(params);
119
- case "DeleteItem":
120
- return this.deleteItem(params);
121
- case "BatchWriteItem":
122
- return this.batchWriteItem(params);
123
- case "BatchGetItem":
124
- return this.batchGetItem(params);
125
- case "Query":
126
- return this.query(params);
127
- case "Scan":
128
- return this.scan(params);
129
- default:
130
- throw new Error(`Unsupported action: ${action}`);
109
+ const readActions = new Set(["GetItem", "BatchGetItem", "Query", "Scan", "DescribeTable", "ListTables"]);
110
+ const dataActions = new Set(["PutItem", "GetItem", "UpdateItem", "DeleteItem", "BatchWriteItem", "BatchGetItem", "Query", "Scan"]);
111
+
112
+ const result = (() => {
113
+ switch (action) {
114
+ case "CreateTable": return this.createTable(params);
115
+ case "DescribeTable": return this.describeTable(params.TableName);
116
+ case "ListTables": return this.listTables(params);
117
+ case "DeleteTable": return this.deleteTable(params);
118
+ case "PutItem": return this.putItem(params);
119
+ case "GetItem": return this.getItem(params);
120
+ case "UpdateItem": return this.updateItem(params);
121
+ case "DeleteItem": return this.deleteItem(params);
122
+ case "BatchWriteItem": return this.batchWriteItem(params);
123
+ case "BatchGetItem": return this.batchGetItem(params);
124
+ case "Query": return this.query(params);
125
+ case "Scan": return this.scan(params);
126
+ default: throw new Error(`Unsupported action: ${action}`);
127
+ }
128
+ })();
129
+
130
+ const tableName = params.TableName;
131
+ if (tableName) {
132
+ this.audit.record({
133
+ eventName: action,
134
+ readOnly: readActions.has(action),
135
+ isDataEvent: dataActions.has(action),
136
+ resources: [{ ARN: `arn:aws:dynamodb:local:000000000000:table/${tableName}`, type: "AWS::DynamoDB::Table" }],
137
+ requestParameters: { tableName },
138
+ });
131
139
  }
140
+
141
+ return result;
132
142
  }
133
143
 
134
144
  describeTable(tableName) {
@@ -1,45 +1,76 @@
1
+ 'use strict';
2
+
1
3
  /**
2
- * EventBridge Service - Ponto de entrada (Stub para implementação futura)
4
+ * @fileoverview EventBridge Service entry point
5
+ * Porta padrão: 4010
3
6
  */
4
7
 
5
- const logger = require('../../utils/logger');
8
+ const http = require('http');
9
+ const path = require('path');
10
+ const { EventBridgeSimulator } = require('./simulator');
11
+ const { createEventBridgeServer } = require('./server');
12
+ const LocalStore = require('../../utils/local-store');
6
13
 
7
14
  class EventBridgeService {
8
15
  constructor(config) {
9
16
  this.config = config;
17
+ this.logger = require('../../utils/logger');
10
18
  this.name = 'eventbridge';
11
- this.port = config.ports.eventbridge;
19
+ this.port = config?.ports?.eventbridge || config?.services?.eventbridge?.port || 4010;
20
+ this.store = null;
21
+ this.simulator = null;
22
+ this.app = null;
23
+ this.server = null;
12
24
  this.isRunning = false;
13
- this.buses = new Map();
14
- this.events = [];
15
25
  }
16
26
 
17
27
  async initialize() {
18
- logger.debug(`Inicializando EventBridge Service na porta ${this.port}...`);
19
- logger.warn('⚠️ EventBridge Service ainda não está completamente implementado');
20
-
21
- // TODO: Implementar EventBridge simulator
22
- this.buses = new Map();
23
- this.events = [];
28
+ this.logger.debug(`Inicializando EventBridge Service na porta ${this.port}...`);
29
+ const dataDir = process.env.AWS_LOCAL_SIMULATOR_DATA_DIR;
30
+ this.store = new LocalStore(path.join(dataDir, 'eventbridge'));
31
+ this.simulator = new EventBridgeSimulator(this.config, this.store, this.logger);
32
+ this.app = createEventBridgeServer(this.simulator, this.config, this.logger);
33
+ this.logger.debug('EventBridge Service inicializado');
34
+ }
35
+
36
+ injectDependencies(server) {
37
+ if (!server) return;
38
+ const lambda = server.getService('lambda');
39
+ if (lambda) this.simulator.setLambdaService(lambda);
40
+ const sqs = server.getService('sqs');
41
+ if (sqs) this.simulator.setSqsService(sqs);
42
+ const sns = server.getService('sns');
43
+ if (sns) this.simulator.setSnsService(sns);
24
44
  }
25
45
 
26
46
  async start() {
27
47
  if (this.isRunning) return;
28
-
29
- // TODO: Iniciar servidor HTTP para EventBridge
30
- this.isRunning = true;
31
- logger.info(`🎯 EventBridge Service stub rodando (porta ${this.port}) - Implementação em breve`);
48
+ await this.store.ensureDir();
49
+ await this.simulator.load();
50
+ return new Promise((resolve, reject) => {
51
+ this.server = http.createServer(this.app);
52
+ this.server.on('error', reject);
53
+ this.server.listen(this.port, () => {
54
+ this.isRunning = true;
55
+ this.logger.debug(`EventBridge rodando na porta ${this.port}`);
56
+ resolve();
57
+ });
58
+ });
32
59
  }
33
60
 
34
61
  async stop() {
35
- if (!this.isRunning) return;
36
- this.isRunning = false;
62
+ if (!this.isRunning || !this.server) return;
63
+ return new Promise((resolve, reject) => {
64
+ this.server.close((err) => {
65
+ if (err) return reject(err);
66
+ this.isRunning = false;
67
+ resolve();
68
+ });
69
+ });
37
70
  }
38
71
 
39
72
  async reset() {
40
- this.buses.clear();
41
- this.events = [];
42
- logger.debug('EventBridge: Todos os dados resetados');
73
+ await this.simulator.reset();
43
74
  }
44
75
 
45
76
  getStatus() {
@@ -47,39 +78,12 @@ class EventBridgeService {
47
78
  running: this.isRunning,
48
79
  port: this.port,
49
80
  endpoint: `http://localhost:${this.port}`,
50
- implemented: false,
51
- busesCount: this.buses.size,
52
- eventsCount: this.events.length
81
+ buses: this.simulator?.buses.size || 0,
82
+ rules: this.simulator?.rules.size || 0,
53
83
  };
54
84
  }
55
85
 
56
- // Métodos stub para compatibilidade
57
- async createEventBus(name) {
58
- if (!this.buses.has(name)) {
59
- this.buses.set(name, {
60
- name,
61
- arn: `arn:aws:events:local:000000000000:event-bus/${name}`,
62
- createdAt: new Date().toISOString()
63
- });
64
- }
65
- return this.buses.get(name);
66
- }
67
-
68
- async putEvents(entries) {
69
- const results = [];
70
- for (const entry of entries) {
71
- const eventId = Math.random().toString(36).substring(7);
72
- this.events.push({
73
- ...entry,
74
- eventId,
75
- time: new Date().toISOString(),
76
- receivedAt: new Date().toISOString()
77
- });
78
- results.push({ EventId: eventId });
79
- logger.verboso(`EventBridge: Event ${eventId} published to ${entry.EventBusName || 'default'}`);
80
- }
81
- return { Entries: results, FailedEntryCount: 0 };
82
- }
86
+ getSimulator() { return this.simulator; }
83
87
  }
84
88
 
85
- module.exports = EventBridgeService;
89
+ module.exports = { EventBridgeService };
@@ -0,0 +1,209 @@
1
+ /**
2
+ * @fileoverview EventBridge HTTP Server
3
+ * Express server compatível com AWS EventBridge REST API (JSON)
4
+ */
5
+
6
+ 'use strict';
7
+
8
+ const express = require('express');
9
+ const cors = require('cors');
10
+
11
+ /**
12
+ * Create EventBridge Express application
13
+ * @param {Object} simulator - EventBridgeSimulator instance
14
+ * @param {Object} config - Service configuration
15
+ * @param {Object} logger - Logger instance
16
+ * @returns {express.Application}
17
+ */
18
+ function createEventBridgeServer(simulator, config, logger) {
19
+ const app = express();
20
+
21
+ if (config.cors?.enabled) {
22
+ app.use(cors({ origin: config.cors.origin || '*' }));
23
+ }
24
+
25
+ app.use(express.json({ limit: '10mb' }));
26
+
27
+ app.use((req, _res, next) => {
28
+ logger.debug('EventBridge', `${req.method} ${req.path}`);
29
+ next();
30
+ });
31
+
32
+ // ==================== Event Buses ====================
33
+
34
+ app.post('/event-buses', async (req, res) => {
35
+ try {
36
+ const result = await simulator.createEventBus(req.body);
37
+ res.status(201).json(result);
38
+ } catch (err) { sendError(res, err); }
39
+ });
40
+
41
+ app.delete('/event-buses/:name', async (req, res) => {
42
+ try {
43
+ await simulator.deleteEventBus({ Name: req.params.name });
44
+ res.status(200).json({});
45
+ } catch (err) { sendError(res, err); }
46
+ });
47
+
48
+ app.get('/event-buses', (_req, res) => {
49
+ try {
50
+ const result = simulator.listEventBuses();
51
+ res.json(result);
52
+ } catch (err) { sendError(res, err); }
53
+ });
54
+
55
+ app.get('/event-buses/:name', (req, res) => {
56
+ try {
57
+ const result = simulator.describeEventBus({ Name: req.params.name });
58
+ res.json(result);
59
+ } catch (err) { sendError(res, err); }
60
+ });
61
+
62
+ // ==================== Rules ====================
63
+
64
+ app.put('/rules', async (req, res) => {
65
+ try {
66
+ const result = await simulator.putRule(req.body);
67
+ res.json(result);
68
+ } catch (err) { sendError(res, err); }
69
+ });
70
+
71
+ app.delete('/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('/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
+ res.json(result);
86
+ } catch (err) { sendError(res, err); }
87
+ });
88
+
89
+ app.get('/rules/:name', (req, res) => {
90
+ try {
91
+ const result = simulator.describeRule({
92
+ Name: req.params.name,
93
+ EventBusName: req.query.EventBusName || 'default'
94
+ });
95
+ res.json(result);
96
+ } catch (err) { sendError(res, err); }
97
+ });
98
+
99
+ app.patch('/rules/:name/enable', async (req, res) => {
100
+ try {
101
+ await simulator.enableRule({ Name: req.params.name, EventBusName: req.query.EventBusName || 'default' });
102
+ res.json({ message: 'Rule enabled' });
103
+ } catch (err) { sendError(res, err); }
104
+ });
105
+
106
+ app.patch('/rules/:name/disable', async (req, res) => {
107
+ try {
108
+ await simulator.disableRule({ Name: req.params.name, EventBusName: req.query.EventBusName || 'default' });
109
+ res.json({ message: 'Rule disabled' });
110
+ } catch (err) { sendError(res, err); }
111
+ });
112
+
113
+ // ==================== Targets ====================
114
+
115
+ app.put('/rules/:name/targets', async (req, res) => {
116
+ try {
117
+ const result = await simulator.putTargets({
118
+ Rule: req.params.name,
119
+ EventBusName: req.body.EventBusName || 'default',
120
+ Targets: req.body.Targets
121
+ });
122
+ res.json(result);
123
+ } catch (err) { sendError(res, err); }
124
+ });
125
+
126
+ app.delete('/rules/:name/targets', async (req, res) => {
127
+ try {
128
+ const result = await simulator.removeTargets({
129
+ Rule: req.params.name,
130
+ EventBusName: req.query.EventBusName || 'default',
131
+ Ids: req.body.Ids || []
132
+ });
133
+ res.json(result);
134
+ } catch (err) { sendError(res, err); }
135
+ });
136
+
137
+ app.get('/rules/:name/targets', (req, res) => {
138
+ try {
139
+ const result = simulator.listTargetsByRule({
140
+ Rule: req.params.name,
141
+ EventBusName: req.query.EventBusName || 'default'
142
+ });
143
+ res.json(result);
144
+ } catch (err) { sendError(res, err); }
145
+ });
146
+
147
+ // ==================== PutEvents ====================
148
+
149
+ app.post('/events', async (req, res) => {
150
+ try {
151
+ const result = await simulator.putEvents(req.body);
152
+ res.json(result);
153
+ } catch (err) { sendError(res, err); }
154
+ });
155
+
156
+ // ==================== Admin ====================
157
+
158
+ app.get('/__admin/health', (_req, res) => {
159
+ res.json({
160
+ status: 'healthy',
161
+ service: 'eventbridge',
162
+ buses: simulator.buses.size,
163
+ rules: simulator.rules.size,
164
+ recentEvents: simulator.eventArchive.length,
165
+ timestamp: new Date().toISOString()
166
+ });
167
+ });
168
+
169
+ app.get('/__admin/buses', (_req, res) => {
170
+ res.json({ buses: Array.from(simulator.buses.values()) });
171
+ });
172
+
173
+ app.get('/__admin/rules', (_req, res) => {
174
+ res.json({ rules: Array.from(simulator.rules.values()) });
175
+ });
176
+
177
+ app.get('/__admin/events', (req, res) => {
178
+ const limit = parseInt(req.query.limit) || 50;
179
+ const events = simulator.eventArchive.slice(-limit);
180
+ res.json({ events, total: simulator.eventArchive.length });
181
+ });
182
+
183
+ app.post('/__admin/reset', async (_req, res) => {
184
+ await simulator.reset();
185
+ res.json({ message: 'EventBridge data reset' });
186
+ });
187
+
188
+ app.use((_req, res) => res.status(404).json({ message: 'Not Found' }));
189
+
190
+ return app;
191
+ }
192
+
193
+ /**
194
+ * Send error response
195
+ * @param {express.Response} res
196
+ * @param {Error} err
197
+ */
198
+ function sendError(res, err) {
199
+ const statusMap = {
200
+ ValidationException: 400,
201
+ ResourceNotFoundException: 404,
202
+ ResourceAlreadyExistsException: 409,
203
+ InvalidEventPatternException: 400
204
+ };
205
+ const status = statusMap[err.code] || statusMap[err.__type] || 500;
206
+ res.status(status).json({ message: err.message, __type: err.__type || err.code });
207
+ }
208
+
209
+ module.exports = { createEventBridgeServer };