@gugananuvem/aws-local-simulator 1.0.11 → 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 (65) hide show
  1. package/README.md +349 -72
  2. package/package.json +12 -2
  3. package/src/config/config-loader.js +2 -0
  4. package/src/config/default-config.js +3 -0
  5. package/src/index.js +18 -2
  6. package/src/server.js +37 -31
  7. package/src/services/apigateway/index.js +10 -3
  8. package/src/services/apigateway/server.js +73 -0
  9. package/src/services/apigateway/simulator.js +13 -3
  10. package/src/services/athena/index.js +75 -0
  11. package/src/services/athena/server.js +101 -0
  12. package/src/services/athena/simulador.js +998 -0
  13. package/src/services/athena/simulator.js +346 -0
  14. package/src/services/cloudformation/index.js +106 -0
  15. package/src/services/cloudformation/server.js +417 -0
  16. package/src/services/cloudformation/simulador.js +1045 -0
  17. package/src/services/cloudtrail/index.js +84 -0
  18. package/src/services/cloudtrail/server.js +235 -0
  19. package/src/services/cloudtrail/simulador.js +719 -0
  20. package/src/services/cloudwatch/index.js +84 -0
  21. package/src/services/cloudwatch/server.js +366 -0
  22. package/src/services/cloudwatch/simulador.js +1173 -0
  23. package/src/services/cognito/index.js +5 -0
  24. package/src/services/cognito/server.js +54 -3
  25. package/src/services/cognito/simulator.js +273 -2
  26. package/src/services/config/index.js +96 -0
  27. package/src/services/config/server.js +215 -0
  28. package/src/services/config/simulador.js +1260 -0
  29. package/src/services/dynamodb/index.js +7 -3
  30. package/src/services/dynamodb/server.js +4 -2
  31. package/src/services/dynamodb/simulator.js +39 -29
  32. package/src/services/eventbridge/index.js +55 -51
  33. package/src/services/eventbridge/server.js +209 -0
  34. package/src/services/eventbridge/simulator.js +684 -0
  35. package/src/services/index.js +30 -4
  36. package/src/services/kms/index.js +75 -0
  37. package/src/services/kms/server.js +67 -0
  38. package/src/services/kms/simulator.js +324 -0
  39. package/src/services/lambda/handler-loader.js +13 -2
  40. package/src/services/lambda/index.js +7 -1
  41. package/src/services/lambda/server.js +32 -39
  42. package/src/services/lambda/simulator.js +78 -181
  43. package/src/services/parameter-store/index.js +80 -0
  44. package/src/services/parameter-store/server.js +50 -0
  45. package/src/services/parameter-store/simulator.js +201 -0
  46. package/src/services/s3/index.js +7 -3
  47. package/src/services/s3/server.js +20 -13
  48. package/src/services/s3/simulator.js +163 -407
  49. package/src/services/secret-manager/index.js +80 -0
  50. package/src/services/secret-manager/server.js +50 -0
  51. package/src/services/secret-manager/simulator.js +171 -0
  52. package/src/services/sns/index.js +55 -42
  53. package/src/services/sns/server.js +580 -0
  54. package/src/services/sns/simulator.js +1482 -0
  55. package/src/services/sqs/index.js +2 -4
  56. package/src/services/sqs/server.js +92 -18
  57. package/src/services/sqs/simulator.js +79 -298
  58. package/src/services/sts/index.js +37 -0
  59. package/src/services/sts/server.js +142 -0
  60. package/src/services/sts/simulator.js +69 -0
  61. package/src/services/xray/index.js +83 -0
  62. package/src/services/xray/server.js +308 -0
  63. package/src/services/xray/simulador.js +994 -0
  64. package/src/utils/cloudtrail-audit.js +129 -0
  65. package/src/utils/local-store.js +18 -2
package/src/server.js CHANGED
@@ -12,11 +12,21 @@ const DynamoDBService = require("./services/dynamodb");
12
12
  const S3Service = require("./services/s3");
13
13
  const SQSService = require("./services/sqs");
14
14
  const LambdaService = require("./services/lambda");
15
- const SNSService = require("./services/sns");
16
- const EventBridgeService = require("./services/eventbridge");
15
+ const { SNSService } = require("./services/sns");
16
+ const { EventBridgeService } = require("./services/eventbridge");
17
17
  const CognitoService = require("./services/cognito");
18
18
  const APIGatewayService = require("./services/apigateway");
19
19
  const ECSService = require("./services/ecs");
20
+ const STSService = require("./services/sts");
21
+ const { CloudWatchService } = require("./services/cloudwatch");
22
+ const CloudTrailService = require("./services/cloudtrail");
23
+ const { KMSService } = require("./services/kms");
24
+ const CloudFormationService = require("./services/cloudformation");
25
+ const { XRayService } = require("./services/xray");
26
+ const { SecretManagerService } = require("./services/secret-manager");
27
+ const { ParameterStoreService } = require("./services/parameter-store");
28
+ const { ConfigService } = require("./services/config");
29
+ const { AthenaService } = require("./services/athena");
20
30
 
21
31
  class Server {
22
32
  constructor(config) {
@@ -63,23 +73,31 @@ class Server {
63
73
  }
64
74
 
65
75
  async initializeServices() {
66
- // Ordem de inicialização importante
67
76
  const serviceOrder = [
68
- { name: "lambda", class: LambdaService, depends: [] },
69
- { name: "dynamodb", class: DynamoDBService, depends: [] },
70
- { name: "s3", class: S3Service, depends: [] },
71
- { name: "sqs", class: SQSService, depends: ["lambda"] },
72
- { name: "sns", class: SNSService, depends: [] },
73
- { name: "eventbridge", class: EventBridgeService, depends: [] },
74
- { name: "cognito", class: CognitoService, depends: [] },
75
- { name: "ecs", class: ECSService, depends: [] },
76
- { name: "apigateway", class: APIGatewayService, depends: [] },
77
+ { name: "sts", class: STSService, depends: [] },
78
+ { name: "lambda", class: LambdaService, depends: [] },
79
+ { name: "dynamodb", class: DynamoDBService, depends: [] },
80
+ { name: "s3", class: S3Service, depends: [] },
81
+ { name: "sqs", class: SQSService, depends: ["lambda"] },
82
+ { name: "sns", class: SNSService, depends: [] },
83
+ { name: "eventbridge", class: EventBridgeService, depends: [] },
84
+ { name: "cognito", class: CognitoService, depends: [] },
85
+ { name: "ecs", class: ECSService, depends: [] },
86
+ { name: "apigateway", class: APIGatewayService, depends: ["lambda"] },
87
+ { name: "kms", class: KMSService, depends: [] },
88
+ { name: "cloudwatch", class: CloudWatchService, depends: [] },
89
+ { name: "cloudtrail", class: CloudTrailService, depends: [] },
90
+ { name: "cloudformation", class: CloudFormationService, depends: [] },
91
+ { name: "xray", class: XRayService, depends: [] },
92
+ { name: "secret-manager", class: SecretManagerService, depends: [] },
93
+ { name: "parameter-store",class: ParameterStoreService, depends: [] },
94
+ { name: "config", class: ConfigService, depends: [] },
95
+ { name: "athena", class: AthenaService, depends: [] },
77
96
  ];
78
97
 
79
98
  for (const serviceDef of serviceOrder) {
80
99
  if (this.config.services[serviceDef.name]) {
81
100
  try {
82
- // Resolve dependências
83
101
  const dependencies = {};
84
102
  for (const dep of serviceDef.depends) {
85
103
  dependencies[dep] = this.servicesMap.get(dep);
@@ -94,29 +112,17 @@ class Server {
94
112
  logger.success(`✅ ${serviceDef.name.toUpperCase()} Service inicializado`);
95
113
  } catch (error) {
96
114
  logger.error(`❌ Erro ao inicializar ${serviceDef.name}:`, error);
97
- if (serviceDef.name === "lambda") {
98
- throw error; // Lambda é essencial
99
- }
115
+ if (serviceDef.name === "lambda") throw error;
100
116
  }
101
117
  }
102
118
  }
103
119
 
104
- /*
105
- // Cognito Service (opcional)
106
- if (this.config.services.cognito) {
107
- const cognitoService = new CognitoService(this.config);
108
- await cognitoService.initialize();
109
- this.services.push(cognitoService);
110
- this.servicesMap.set("cognito", cognitoService);
111
- logger.success("✅ Cognito Service inicializado");
120
+ // Injeta dependências cross-service nos serviços que suportam
121
+ for (const service of this.services) {
122
+ if (typeof service.injectDependencies === "function") {
123
+ service.injectDependencies(this);
124
+ }
112
125
  }
113
- if (this.config.services.apigateway) {
114
- const apigatewayService = new APIGatewayService(this.config);
115
- await apigatewayService.initialize();
116
- this.services.push(apigatewayService);
117
- this.servicesMap.set("apigateway", apigatewayService);
118
- logger.success("✅ API Gateway Service inicializado");
119
- }*/
120
126
  }
121
127
 
122
128
  async startServices() {
@@ -7,30 +7,37 @@ const APIGatewayServer = require('./server');
7
7
  const APIGatewaySimulator = require('./simulator');
8
8
 
9
9
  class APIGatewayService {
10
- constructor(config) {
10
+ constructor(config, dependencies = {}) {
11
11
  this.config = config;
12
12
  this.name = 'apigateway';
13
13
  this.port = config.ports.apigateway || 4567;
14
14
  this.server = null;
15
15
  this.simulator = null;
16
16
  this.isRunning = false;
17
+ this.lambdaService = dependencies.lambda || null;
17
18
  }
18
19
 
19
20
  async initialize() {
20
21
  const logger = require('../../utils/logger');
21
22
  logger.debug(`Inicializando API Gateway Service na porta ${this.port}...`);
22
-
23
+
23
24
  this.simulator = new APIGatewaySimulator(this.config);
24
25
  await this.simulator.initialize();
25
-
26
+
26
27
  this.server = new APIGatewayServer(this.port, this.config);
27
28
  this.server.simulator = this.simulator;
29
+ this.server.lambdaService = this.lambdaService;
28
30
 
29
31
  await this.server.initialize();
30
32
 
31
33
  logger.debug('API Gateway Service inicializado');
32
34
  }
33
35
 
36
+ injectDependencies(server) {
37
+ const ct = server.getService('cloudtrail');
38
+ if (ct?.simulator) this.simulator.audit.setTrail(ct.simulator);
39
+ }
40
+
34
41
  async start() {
35
42
  if (this.isRunning) return;
36
43
  await this.server.start();
@@ -19,6 +19,26 @@ class APIGatewayServer {
19
19
  setupMiddlewares() {
20
20
  this.app.use(express.json({ limit: '10mb' }));
21
21
  this.app.use(express.urlencoded({ extended: true, limit: '10mb' }));
22
+ // Parse bodies with AWS content types (e.g. application/x-amz-json-1.1)
23
+ this.app.use((req, res, next) => {
24
+ const ct = req.headers['content-type'] || '';
25
+ if ((req.body && JSON.stringify(req.body) == "{}") && ct.includes('application/x-amz-json')) {
26
+ let data = "";
27
+ req.on("data", (chunk) => {
28
+ data += chunk;
29
+ });
30
+ req.on("end", () => {
31
+ try {
32
+ req.body = JSON.parse(data);
33
+ } catch (error) {
34
+ req.body = {};
35
+ }
36
+ next();
37
+ });
38
+ } else {
39
+ next();
40
+ }
41
+ });
22
42
  this.app.use(cors());
23
43
 
24
44
  if (logger.currentLogLevel === 'verboso') {
@@ -35,10 +55,63 @@ class APIGatewayServer {
35
55
 
36
56
  async initialize() {
37
57
  this.setupRoutes();
58
+ this.setupConfigRoutes();
38
59
  this.setupProxyRoutes();
39
60
  logger.debug('API Gateway Server inicializado');
40
61
  }
41
62
 
63
+ setupConfigRoutes() {
64
+ // Register routes from aws-local-simulator.json config directly
65
+ const apis = this.config.apigateway?.apis || [];
66
+ for (const api of apis) {
67
+ for (const endpoint of (api.endpoints || [])) {
68
+ const { path, method, lambdaName, integrationType } = endpoint;
69
+ if (!path || !method) continue;
70
+
71
+ const expressPath = path.replace(/\{([^}]+)\}/g, ':$1');
72
+ const httpMethod = method.toLowerCase();
73
+
74
+ logger.debug(`📡 Registrando rota: ${method} ${path} -> ${lambdaName}`);
75
+
76
+ this.app[httpMethod](expressPath, async (req, res) => {
77
+ try {
78
+ const lambdaService = this.lambdaService;
79
+ if (!lambdaService) {
80
+ return res.status(500).json({ error: 'Lambda service not available' });
81
+ }
82
+
83
+ const event = {
84
+ httpMethod: req.method,
85
+ path: req.path,
86
+ headers: req.headers,
87
+ queryStringParameters: Object.keys(req.query).length ? req.query : null,
88
+ pathParameters: Object.keys(req.params).length ? req.params : null,
89
+ body: req.body ? JSON.stringify(req.body) : null,
90
+ isBase64Encoded: false,
91
+ requestContext: {
92
+ path: req.path,
93
+ stage: 'local',
94
+ requestId: Math.random().toString(36).substring(7),
95
+ identity: { sourceIp: req.ip }
96
+ }
97
+ };
98
+
99
+ const result = await lambdaService.simulator.invoke(lambdaName, event);
100
+ const payload = result.Payload || {};
101
+ const statusCode = payload.statusCode || 200;
102
+ const headers = payload.headers || { 'Content-Type': 'application/json' };
103
+ const body = payload.body;
104
+
105
+ res.status(statusCode).set(headers).send(body);
106
+ } catch (err) {
107
+ logger.error(`Lambda invoke error (${lambdaName}):`, err);
108
+ res.status(500).json({ error: err.message });
109
+ }
110
+ });
111
+ }
112
+ }
113
+ }
114
+
42
115
  setupRoutes() {
43
116
  // Health check
44
117
  this.app.get('/health', (req, res) => {
@@ -9,14 +9,15 @@ const logger = require('../../utils/logger');
9
9
  const LocalStore = require('../../utils/local-store');
10
10
  const path = require('path');
11
11
  const { URLPattern } = require('urlpattern-polyfill');
12
+ const { CloudTrailAudit } = require('../../utils/cloudtrail-audit');
12
13
 
13
14
  class APIGatewaySimulator {
14
15
  constructor(config) {
15
16
  this.config = config;
16
17
  this.dataDir = path.join(process.env.AWS_LOCAL_SIMULATOR_DATA_DIR, 'apigateway');
17
18
  this.store = new LocalStore(this.dataDir);
18
- this.apis = new Map(); // REST/HTTP APIs
19
- this.websocketApis = new Map(); // WebSocket APIs
19
+ this.apis = new Map();
20
+ this.websocketApis = new Map();
20
21
  this.deployments = new Map();
21
22
  this.stages = new Map();
22
23
  this.resources = new Map();
@@ -26,6 +27,7 @@ class APIGatewaySimulator {
26
27
  this.usagePlans = new Map();
27
28
  this.apiKeys = new Map();
28
29
  this.domainNames = new Map();
30
+ this.audit = new CloudTrailAudit('execute-api.amazonaws.com');
29
31
  }
30
32
 
31
33
  async initialize() {
@@ -734,7 +736,15 @@ class APIGatewaySimulator {
734
736
  response.headers = { ...response.headers, ...integrationResponse.responseParameters };
735
737
  response.body = this.applyResponseTemplate(integrationResponse, response.body);
736
738
  }
737
-
739
+
740
+ this.audit.record({
741
+ eventName: 'Invoke',
742
+ readOnly: method === 'GET' || method === 'HEAD',
743
+ isDataEvent: true,
744
+ resources: [{ ARN: `arn:aws:execute-api:local:000000000000:${apiId}/${stageName}/${method}${path}`, type: 'AWS::APIGateway::Stage' }],
745
+ requestParameters: { apiId, stageName, method, path },
746
+ });
747
+
738
748
  return response;
739
749
  }
740
750
 
@@ -0,0 +1,75 @@
1
+ 'use strict';
2
+
3
+ const http = require('http');
4
+ const path = require('path');
5
+ const { AthenaSimulator } = require('./simulator');
6
+ const { createAthenaServer } = require('./server');
7
+ const LocalStore = require('../../utils/local-store');
8
+
9
+ class AthenaService {
10
+ constructor(config) {
11
+ this.config = config;
12
+ this.logger = require('../../utils/logger');
13
+ this.name = 'athena';
14
+ this.port = config?.ports?.athena || 4599;
15
+ this.store = null;
16
+ this.simulator = null;
17
+ this._server = null;
18
+ this.isRunning = false;
19
+ }
20
+
21
+ async initialize() {
22
+ this.logger.debug(`Inicializando Athena Service na porta ${this.port}...`);
23
+ const dataDir = process.env.AWS_LOCAL_SIMULATOR_DATA_DIR;
24
+ this.store = new LocalStore(path.join(dataDir, 'athena'));
25
+ this.simulator = new AthenaSimulator(this.config, this.store, this.logger);
26
+ await this.simulator.initialize();
27
+ this.app = createAthenaServer(this.simulator, this.logger);
28
+ this.logger.debug('Athena Service inicializado');
29
+ }
30
+
31
+ injectDependencies(server) {
32
+ const ct = server.getService('cloudtrail');
33
+ if (ct?.simulator) this.simulator.audit.setTrail(ct.simulator);
34
+ }
35
+
36
+ async start() {
37
+ if (this.isRunning) return;
38
+ return new Promise((resolve, reject) => {
39
+ this._server = http.createServer(this.app);
40
+ this._server.listen(this.port, () => {
41
+ this.isRunning = true;
42
+ this.logger.info(`🔍 Athena rodando em http://localhost:${this.port}`);
43
+ resolve();
44
+ });
45
+ this._server.on('error', reject);
46
+ });
47
+ }
48
+
49
+ async stop() {
50
+ if (!this.isRunning || !this._server) return;
51
+ return new Promise((resolve) => {
52
+ this._server.close(() => {
53
+ this.isRunning = false;
54
+ resolve();
55
+ });
56
+ });
57
+ }
58
+
59
+ async reset() {
60
+ await this.simulator.reset();
61
+ }
62
+
63
+ getStatus() {
64
+ return {
65
+ running: this.isRunning,
66
+ port: this.port,
67
+ endpoint: `http://localhost:${this.port}`,
68
+ ...this.simulator?.getStats(),
69
+ };
70
+ }
71
+
72
+ getSimulator() { return this.simulator; }
73
+ }
74
+
75
+ module.exports = { AthenaService };
@@ -0,0 +1,101 @@
1
+ 'use strict';
2
+
3
+ const express = require('express');
4
+ const cors = require('cors');
5
+
6
+ /**
7
+ * Athena Server - protocolo JSON via x-amz-target
8
+ */
9
+ function createAthenaServer(simulator, logger) {
10
+ const app = express();
11
+
12
+ app.use(cors());
13
+ app.use(express.json({ limit: '10mb', type: () => true }));
14
+
15
+ app.use((req, _res, next) => {
16
+ const target = req.headers['x-amz-target'] || '';
17
+ if (target) logger.debug(`[Athena] ${req.method} ${req.path} target=${target}`);
18
+ next();
19
+ });
20
+
21
+ // ── Rota principal ───────────────────────────────────────────────
22
+ app.post('/', async (req, res) => {
23
+ const target = (req.headers['x-amz-target'] || '').replace('AmazonAthena.', '');
24
+ const body = req.body || {};
25
+
26
+ try {
27
+ let result;
28
+
29
+ switch (target) {
30
+ case 'StartQueryExecution':
31
+ result = await simulator.startQueryExecution(body);
32
+ break;
33
+ case 'GetQueryExecution':
34
+ result = simulator.getQueryExecution(body);
35
+ break;
36
+ case 'GetQueryResults':
37
+ result = simulator.getQueryResults(body);
38
+ break;
39
+ case 'StopQueryExecution':
40
+ result = await simulator.stopQueryExecution(body);
41
+ break;
42
+ case 'ListQueryExecutions':
43
+ result = simulator.listQueryExecutions(body);
44
+ break;
45
+ case 'CreateNamedQuery':
46
+ result = await simulator.createNamedQuery(body);
47
+ break;
48
+ case 'GetNamedQuery':
49
+ result = simulator.getNamedQuery(body);
50
+ break;
51
+ case 'ListNamedQueries':
52
+ result = simulator.listNamedQueries(body);
53
+ break;
54
+ case 'DeleteNamedQuery':
55
+ result = await simulator.deleteNamedQuery(body);
56
+ break;
57
+ case 'CreateWorkGroup':
58
+ result = await simulator.createWorkGroup(body);
59
+ break;
60
+ case 'GetWorkGroup':
61
+ result = simulator.getWorkGroup(body);
62
+ break;
63
+ case 'ListWorkGroups':
64
+ result = simulator.listWorkGroups(body);
65
+ break;
66
+ case 'DeleteWorkGroup':
67
+ result = await simulator.deleteWorkGroup(body);
68
+ break;
69
+ default:
70
+ return res.status(400).json({ __type: 'InvalidRequestException', message: `Unknown action: ${target}` });
71
+ }
72
+
73
+ res.json(result || {});
74
+ } catch (err) {
75
+ logger.error(`[Athena] Error in ${target}: ${err.message}`);
76
+ res.status(err.statusCode || 500).json({ __type: err.code || 'InternalServerError', message: err.message });
77
+ }
78
+ });
79
+
80
+ // ── Admin ────────────────────────────────────────────────────────
81
+ app.get('/__admin/health', (_req, res) => {
82
+ res.json({ status: 'healthy', service: 'athena', ...simulator.getStats() });
83
+ });
84
+
85
+ app.get('/__admin/executions', (_req, res) => {
86
+ res.json({ executions: Array.from(simulator.queryExecutions.values()).map(({ _results, ...e }) => e) });
87
+ });
88
+
89
+ app.get('/__admin/workgroups', (_req, res) => {
90
+ res.json({ workGroups: Array.from(simulator.workGroups.values()) });
91
+ });
92
+
93
+ app.post('/__admin/reset', async (_req, res) => {
94
+ await simulator.reset();
95
+ res.json({ message: 'Athena data reset complete' });
96
+ });
97
+
98
+ return app;
99
+ }
100
+
101
+ module.exports = { createAthenaServer };