@gugananuvem/aws-local-simulator 1.0.22 → 1.0.26
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 +45 -0
- package/aws-config +154 -0
- package/package.json +30 -84
- package/src/config/default-config.js +15 -4
- package/src/server.js +257 -21
- package/src/services/apigateway/server.js +37 -0
- package/src/services/apigateway/simulator.js +148 -4
- package/src/services/cognito/server.js +8 -14
- package/src/services/cognito/simulator.js +63 -11
- package/src/services/dynamodb/simulator.js +159 -49
- package/src/services/kms/server.js +15 -1
- package/src/services/kms/simulator.js +48 -28
- package/src/services/lambda/server.js +24 -0
- package/src/services/lambda/simulator.js +136 -12
- package/src/services/parameter-store/simulator.js +1 -1
- package/src/services/s3/server.js +21 -0
- package/src/services/s3/simulator.js +4 -1
- package/src/services/secret-manager/server.js +2 -1
- package/src/services/secret-manager/simulator.js +21 -10
- package/src/services/sns/server.js +32 -5
- package/src/services/sqs/server.js +11 -0
- package/src/services/sqs/simulator.js +74 -6
package/src/server.js
CHANGED
|
@@ -5,6 +5,8 @@
|
|
|
5
5
|
const path = require("path");
|
|
6
6
|
const fs = require("fs");
|
|
7
7
|
const mkdirp = require("mkdirp");
|
|
8
|
+
const express = require("express");
|
|
9
|
+
const cors = require("cors");
|
|
8
10
|
const logger = require("./utils/logger");
|
|
9
11
|
|
|
10
12
|
// Importa serviços
|
|
@@ -34,6 +36,10 @@ class Server {
|
|
|
34
36
|
this.services = [];
|
|
35
37
|
this.servicesMap = new Map();
|
|
36
38
|
this.running = false;
|
|
39
|
+
this.managementApp = express();
|
|
40
|
+
this.managementApp.use(cors());
|
|
41
|
+
this.managementApp.use(express.json());
|
|
42
|
+
this.managementServer = null;
|
|
37
43
|
this.setupDataDir();
|
|
38
44
|
this.setupLogLevel();
|
|
39
45
|
}
|
|
@@ -63,6 +69,8 @@ class Server {
|
|
|
63
69
|
try {
|
|
64
70
|
await this.initializeServices();
|
|
65
71
|
await this.startServices();
|
|
72
|
+
this.setupManagementRoutes();
|
|
73
|
+
await this.startManagementServer();
|
|
66
74
|
|
|
67
75
|
this.running = true;
|
|
68
76
|
this.printStatus();
|
|
@@ -72,28 +80,150 @@ class Server {
|
|
|
72
80
|
}
|
|
73
81
|
}
|
|
74
82
|
|
|
75
|
-
|
|
76
|
-
const
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
83
|
+
setupManagementRoutes() {
|
|
84
|
+
const app = this.managementApp;
|
|
85
|
+
|
|
86
|
+
// GET /__admin/services — returns all services status
|
|
87
|
+
app.get("/__admin/services", (req, res) => {
|
|
88
|
+
const registry = this.buildServiceRegistry();
|
|
89
|
+
const services = registry.map((def) => {
|
|
90
|
+
const svc = this.servicesMap.get(def.name);
|
|
91
|
+
if (svc) {
|
|
92
|
+
const status = typeof svc.getStatus === "function"
|
|
93
|
+
? svc.getStatus()
|
|
94
|
+
: { name: def.name, running: true, port: svc.port };
|
|
95
|
+
// Compute canDisable: no running service should depend on this one
|
|
96
|
+
const canDisable = !registry.some(
|
|
97
|
+
(other) =>
|
|
98
|
+
other.depends.includes(def.name) &&
|
|
99
|
+
this.servicesMap.has(other.name)
|
|
100
|
+
);
|
|
101
|
+
return {
|
|
102
|
+
name: def.name,
|
|
103
|
+
running: true,
|
|
104
|
+
enabled: true,
|
|
105
|
+
port: status.port || this.config.ports?.[def.name],
|
|
106
|
+
endpoint: status.endpoint || `http://localhost:${status.port || this.config.ports?.[def.name]}`,
|
|
107
|
+
dependencies: def.depends,
|
|
108
|
+
category: def.category,
|
|
109
|
+
canDisable,
|
|
110
|
+
...status,
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
return {
|
|
114
|
+
name: def.name,
|
|
115
|
+
running: false,
|
|
116
|
+
enabled: false,
|
|
117
|
+
port: this.config.ports?.[def.name],
|
|
118
|
+
endpoint: `http://localhost:${this.config.ports?.[def.name]}`,
|
|
119
|
+
dependencies: def.depends,
|
|
120
|
+
canDisable: true,
|
|
121
|
+
};
|
|
122
|
+
});
|
|
123
|
+
res.json({ services });
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
// GET /__admin/services/:name — returns single service status
|
|
127
|
+
app.get("/__admin/services/:name", (req, res) => {
|
|
128
|
+
const { name } = req.params;
|
|
129
|
+
const registry = this.buildServiceRegistry();
|
|
130
|
+
const def = registry.find((d) => d.name === name);
|
|
131
|
+
if (!def) {
|
|
132
|
+
return res.status(404).json({ error: `Unknown service: ${name}` });
|
|
133
|
+
}
|
|
134
|
+
const svc = this.servicesMap.get(name);
|
|
135
|
+
const canDisable = !registry.some(
|
|
136
|
+
(other) =>
|
|
137
|
+
other.depends.includes(name) &&
|
|
138
|
+
this.servicesMap.has(other.name)
|
|
139
|
+
);
|
|
140
|
+
if (svc) {
|
|
141
|
+
const status = typeof svc.getStatus === "function"
|
|
142
|
+
? svc.getStatus()
|
|
143
|
+
: { name, running: true, port: svc.port };
|
|
144
|
+
return res.json({
|
|
145
|
+
name,
|
|
146
|
+
running: true,
|
|
147
|
+
enabled: true,
|
|
148
|
+
port: status.port || this.config.ports?.[name],
|
|
149
|
+
endpoint: status.endpoint || `http://localhost:${status.port || this.config.ports?.[name]}`,
|
|
150
|
+
dependencies: def.depends,
|
|
151
|
+
canDisable,
|
|
152
|
+
...status,
|
|
153
|
+
});
|
|
154
|
+
}
|
|
155
|
+
return res.json({
|
|
156
|
+
name,
|
|
157
|
+
running: false,
|
|
158
|
+
enabled: false,
|
|
159
|
+
port: this.config.ports?.[name],
|
|
160
|
+
endpoint: `http://localhost:${this.config.ports?.[name]}`,
|
|
161
|
+
dependencies: def.depends,
|
|
162
|
+
canDisable: true,
|
|
163
|
+
});
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
// POST /__admin/services/:name/enable — enables a service
|
|
167
|
+
app.post("/__admin/services/:name/enable", async (req, res) => {
|
|
168
|
+
const result = await this.enableService(req.params.name);
|
|
169
|
+
res.status(result.success ? 200 : 400).json(result);
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
// POST /__admin/services/:name/disable — disables a service
|
|
173
|
+
app.post("/__admin/services/:name/disable", async (req, res) => {
|
|
174
|
+
const result = await this.disableService(req.params.name);
|
|
175
|
+
res.status(result.success ? 200 : 400).json(result);
|
|
176
|
+
});
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
startManagementServer() {
|
|
180
|
+
return new Promise((resolve, reject) => {
|
|
181
|
+
const port = this.config.adminPort || 9999;
|
|
182
|
+
this.managementServer = this.managementApp.listen(port, () => {
|
|
183
|
+
logger.info(`🔧 Management API rodando em http://localhost:${port}`);
|
|
184
|
+
resolve();
|
|
185
|
+
});
|
|
186
|
+
this.managementServer.on("error", reject);
|
|
187
|
+
});
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
buildServiceRegistry() {
|
|
191
|
+
return [
|
|
192
|
+
//Armazenamento & BD
|
|
193
|
+
{ name: "dynamodb", class: DynamoDBService, depends: [], category: 'Armazenamento & Banco de Dados' },
|
|
194
|
+
{ name: "s3", class: S3Service, depends: [], category: 'Armazenamento & Banco de Dados' },
|
|
195
|
+
{ name: "athena", class: AthenaService, depends: [], category: 'Armazenamento & Banco de Dados' },
|
|
196
|
+
|
|
197
|
+
//Computação
|
|
198
|
+
{ name: "lambda", class: LambdaService, depends: [], category: 'Computação' },
|
|
199
|
+
/* { name: "ecs", class: ECSService, depends: [] , category:'Computação'},*/
|
|
200
|
+
|
|
201
|
+
{ name: "cognito", class: CognitoService, depends: ["lambda"], category: 'Segurança & Identidade' },
|
|
202
|
+
{ name: "sts", class: STSService, depends: [], category: 'Segurança & Identidade' },
|
|
203
|
+
{ name: "kms", class: KMSService, depends: [], category: 'Segurança & Identidade' },
|
|
204
|
+
{ name: "secret-manager", class: SecretManagerService, depends: [], category: 'Segurança & Identidade' },
|
|
205
|
+
{ name: "parameter-store", class: ParameterStoreService, depends: [], category: 'Segurança & Identidade' },
|
|
206
|
+
|
|
207
|
+
//Mensageria
|
|
208
|
+
{ name: "sqs", class: SQSService, depends: ["lambda"], category: 'Mensageria & Integração' },
|
|
209
|
+
{ name: "sns", class: SNSService, depends: [], category: 'Mensageria & Integração' },
|
|
210
|
+
{ name: "eventbridge", class: EventBridgeService, depends: [], category: 'Mensageria & Integração' },
|
|
211
|
+
|
|
212
|
+
//Networking
|
|
213
|
+
{ name: "apigateway", class: APIGatewayService, depends: ["lambda", "cognito"], category: 'Networking' },
|
|
214
|
+
|
|
215
|
+
//Observabilidade & Conformidade
|
|
216
|
+
{ name: "cloudwatch", class: CloudWatchService, depends: [], category: 'Observabilidade & Conformidade' },
|
|
217
|
+
{ name: "cloudtrail", class: CloudTrailService, depends: [], category: 'Observabilidade & Conformidade' },
|
|
218
|
+
{ name: "xray", class: XRayService, depends: [], category: 'Observabilidade & Conformidade' },
|
|
219
|
+
{ name: "config", class: ConfigService, depends: [], category: 'Observabilidade & Conformidade' },
|
|
220
|
+
{ name: "cloudformation", class: CloudFormationService, depends: [], category: 'Observabilidade & Conformidade' },
|
|
221
|
+
|
|
96
222
|
];
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
async initializeServices() {
|
|
226
|
+
const serviceOrder = this.buildServiceRegistry();
|
|
97
227
|
|
|
98
228
|
for (const serviceDef of serviceOrder) {
|
|
99
229
|
if (this.config.services[serviceDef.name]) {
|
|
@@ -143,6 +273,11 @@ class Server {
|
|
|
143
273
|
const stopPromises = [...this.services].reverse().map((service) => service.stop());
|
|
144
274
|
await Promise.all(stopPromises);
|
|
145
275
|
|
|
276
|
+
if (this.managementServer) {
|
|
277
|
+
await new Promise((resolve) => this.managementServer.close(resolve));
|
|
278
|
+
this.managementServer = null;
|
|
279
|
+
}
|
|
280
|
+
|
|
146
281
|
this.running = false;
|
|
147
282
|
logger.success("✅ Todos os serviços foram parados");
|
|
148
283
|
} catch (error) {
|
|
@@ -177,6 +312,107 @@ class Server {
|
|
|
177
312
|
return this.servicesMap.get(name);
|
|
178
313
|
}
|
|
179
314
|
|
|
315
|
+
async enableService(name) {
|
|
316
|
+
// Check if already running
|
|
317
|
+
if (this.servicesMap.has(name)) {
|
|
318
|
+
return { success: false, error: "Service already running" };
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
// Look up in registry
|
|
322
|
+
const registry = this.buildServiceRegistry();
|
|
323
|
+
const serviceDef = registry.find((s) => s.name === name);
|
|
324
|
+
if (!serviceDef) {
|
|
325
|
+
return { success: false, error: "Unknown service" };
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
// Validate all dependencies are running
|
|
329
|
+
for (const dep of serviceDef.depends) {
|
|
330
|
+
if (!this.servicesMap.has(dep)) {
|
|
331
|
+
return { success: false, error: `Dependency not running: ${dep}` };
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
try {
|
|
336
|
+
const service = new serviceDef.class(this.config);
|
|
337
|
+
await service.initialize();
|
|
338
|
+
await service.start();
|
|
339
|
+
|
|
340
|
+
this.services.push(service);
|
|
341
|
+
this.servicesMap.set(name, service);
|
|
342
|
+
|
|
343
|
+
if (typeof service.injectDependencies === "function") {
|
|
344
|
+
service.injectDependencies(this);
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
// Build ServiceStatus for the response
|
|
348
|
+
const canDisable = !registry.some(
|
|
349
|
+
(other) =>
|
|
350
|
+
other.depends.includes(name) && this.servicesMap.has(other.name)
|
|
351
|
+
);
|
|
352
|
+
const rawStatus =
|
|
353
|
+
typeof service.getStatus === "function"
|
|
354
|
+
? service.getStatus()
|
|
355
|
+
: { name, running: true, port: service.port };
|
|
356
|
+
|
|
357
|
+
const serviceStatus = {
|
|
358
|
+
name,
|
|
359
|
+
running: true,
|
|
360
|
+
enabled: true,
|
|
361
|
+
port: rawStatus.port || this.config.ports?.[name],
|
|
362
|
+
endpoint:
|
|
363
|
+
rawStatus.endpoint ||
|
|
364
|
+
`http://localhost:${rawStatus.port || this.config.ports?.[name]}`,
|
|
365
|
+
dependencies: serviceDef.depends,
|
|
366
|
+
canDisable,
|
|
367
|
+
...rawStatus,
|
|
368
|
+
};
|
|
369
|
+
|
|
370
|
+
return { success: true, service: serviceStatus };
|
|
371
|
+
} catch (error) {
|
|
372
|
+
return { success: false, error: error.message };
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
async disableService(name) {
|
|
377
|
+
// Check if running
|
|
378
|
+
const service = this.servicesMap.get(name);
|
|
379
|
+
if (!service) {
|
|
380
|
+
return { success: false, error: "Service not running" };
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
// Check reverse dependencies
|
|
384
|
+
const registry = this.buildServiceRegistry();
|
|
385
|
+
for (const other of registry) {
|
|
386
|
+
if (other.depends.includes(name) && this.servicesMap.has(other.name)) {
|
|
387
|
+
return {
|
|
388
|
+
success: false,
|
|
389
|
+
error: `Cannot disable: ${other.name} depends on it`,
|
|
390
|
+
};
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
try {
|
|
395
|
+
await service.stop();
|
|
396
|
+
this.services = this.services.filter((s) => s !== service);
|
|
397
|
+
this.servicesMap.delete(name);
|
|
398
|
+
|
|
399
|
+
return {
|
|
400
|
+
success: true,
|
|
401
|
+
service: {
|
|
402
|
+
name,
|
|
403
|
+
running: false,
|
|
404
|
+
enabled: false,
|
|
405
|
+
port: this.config.ports?.[name],
|
|
406
|
+
endpoint: `http://localhost:${this.config.ports?.[name]}`,
|
|
407
|
+
dependencies: registry.find((d) => d.name === name)?.depends || [],
|
|
408
|
+
canDisable: true,
|
|
409
|
+
},
|
|
410
|
+
};
|
|
411
|
+
} catch (error) {
|
|
412
|
+
return { success: false, error: error.message };
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
|
|
180
416
|
printStatus() {
|
|
181
417
|
logger.info("\n" + "=".repeat(60));
|
|
182
418
|
logger.info("📊 Status dos Serviços:");
|
|
@@ -209,6 +209,18 @@ class APIGatewayServer {
|
|
|
209
209
|
}
|
|
210
210
|
});
|
|
211
211
|
|
|
212
|
+
this.app.patch('/restapis/:apiId', (req, res) => {
|
|
213
|
+
try {
|
|
214
|
+
const result = this.simulator.updateRestApi({
|
|
215
|
+
...req.body,
|
|
216
|
+
restApiId: req.params.apiId
|
|
217
|
+
});
|
|
218
|
+
res.json(result);
|
|
219
|
+
} catch (error) {
|
|
220
|
+
res.status(400).json({ error: error.message });
|
|
221
|
+
}
|
|
222
|
+
});
|
|
223
|
+
|
|
212
224
|
this.app.delete('/restapis/:apiId', (req, res) => {
|
|
213
225
|
try {
|
|
214
226
|
this.simulator.deleteRestApi({ restApiId: req.params.apiId });
|
|
@@ -509,6 +521,31 @@ class APIGatewayServer {
|
|
|
509
521
|
}
|
|
510
522
|
});
|
|
511
523
|
|
|
524
|
+
this.app.post('/__admin/apis/:apiId/endpoints', (req, res) => {
|
|
525
|
+
try {
|
|
526
|
+
const result = this.simulator.putEndpoint({
|
|
527
|
+
...req.body,
|
|
528
|
+
restApiId: req.params.apiId
|
|
529
|
+
});
|
|
530
|
+
res.json(result);
|
|
531
|
+
} catch (error) {
|
|
532
|
+
res.status(400).json({ error: error.message });
|
|
533
|
+
}
|
|
534
|
+
});
|
|
535
|
+
|
|
536
|
+
this.app.delete('/__admin/apis/:apiId/endpoints', (req, res) => {
|
|
537
|
+
try {
|
|
538
|
+
const result = this.simulator.deleteEndpoint({
|
|
539
|
+
restApiId: req.params.apiId,
|
|
540
|
+
path: req.query.path,
|
|
541
|
+
method: req.query.method
|
|
542
|
+
});
|
|
543
|
+
res.json(result);
|
|
544
|
+
} catch (error) {
|
|
545
|
+
res.status(400).json({ error: error.message });
|
|
546
|
+
}
|
|
547
|
+
});
|
|
548
|
+
|
|
512
549
|
this.app.get('/__admin/apikeys', (req, res) => {
|
|
513
550
|
const keys = Array.from(this.simulator.apiKeys.values()).map(k => ({
|
|
514
551
|
id: k.id,
|
|
@@ -33,6 +33,7 @@ class APIGatewaySimulator {
|
|
|
33
33
|
async initialize() {
|
|
34
34
|
logger.debug('Inicializando API Gateway Simulator...');
|
|
35
35
|
this.loadAPIs();
|
|
36
|
+
this._loadStaticAPIs();
|
|
36
37
|
this.loadWebSocketAPIs();
|
|
37
38
|
this.loadDeployments();
|
|
38
39
|
this.loadStages();
|
|
@@ -44,9 +45,62 @@ class APIGatewaySimulator {
|
|
|
44
45
|
this.loadApiKeys();
|
|
45
46
|
this.loadDomainNames();
|
|
46
47
|
|
|
47
|
-
logger.debug(`✅ API Gateway Simulator inicializado com ${this.apis.size} APIs
|
|
48
|
+
logger.debug(`✅ API Gateway Simulator inicializado com ${this.apis.size} APIs (${Array.from(this.apis.values()).filter(a => a.isStatic).length} estáticas)`);
|
|
48
49
|
}
|
|
49
50
|
|
|
51
|
+
_loadStaticAPIs() {
|
|
52
|
+
const staticApis = this.config.apigateway?.apis || [];
|
|
53
|
+
staticApis.forEach((apiConfig, index) => {
|
|
54
|
+
const apiId = `static_${index}`;
|
|
55
|
+
|
|
56
|
+
const api = {
|
|
57
|
+
id: apiId,
|
|
58
|
+
name: apiConfig.name,
|
|
59
|
+
description: apiConfig.description || 'Configured in aws-local-simulator.json',
|
|
60
|
+
version: 'config',
|
|
61
|
+
createdDate: new Date().toISOString(),
|
|
62
|
+
isStatic: true,
|
|
63
|
+
apiKeySource: 'HEADER',
|
|
64
|
+
endpointConfiguration: { types: ['REGIONAL'] },
|
|
65
|
+
resources: new Map(),
|
|
66
|
+
stages: new Map(),
|
|
67
|
+
deployments: new Map(),
|
|
68
|
+
models: new Map(),
|
|
69
|
+
authorizers: new Map()
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
// Adiciona recursos a partir dos endpoints configurados
|
|
73
|
+
(apiConfig.endpoints || []).forEach((ep, epIndex) => {
|
|
74
|
+
const resId = `res_${apiId}_${epIndex}`;
|
|
75
|
+
api.resources.set(resId, {
|
|
76
|
+
id: resId,
|
|
77
|
+
path: ep.path,
|
|
78
|
+
pathPart: ep.path.split('/').pop() || '/',
|
|
79
|
+
resourceMethods: new Map([[ep.method, {
|
|
80
|
+
httpMethod: ep.method,
|
|
81
|
+
authorizationType: ep.authorizerRequired ? 'COGNITO_USER_POOLS' : 'NONE',
|
|
82
|
+
apiKeyRequired: false,
|
|
83
|
+
integration: {
|
|
84
|
+
type: ep.integrationType === 'lambda' ? 'AWS_PROXY' : 'HTTP',
|
|
85
|
+
uri: ep.lambdaName,
|
|
86
|
+
integrationHttpMethod: 'POST'
|
|
87
|
+
}
|
|
88
|
+
}]])
|
|
89
|
+
});
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
// Adiciona um stage padrão
|
|
93
|
+
api.stages.set('local', {
|
|
94
|
+
stageName: 'local',
|
|
95
|
+
createdDate: new Date().toISOString(),
|
|
96
|
+
deploymentId: 'static-deploy'
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
this.apis.set(apiId, api);
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
|
|
50
104
|
// ============ REST API Operations ============
|
|
51
105
|
|
|
52
106
|
createRestApi(params) {
|
|
@@ -104,11 +158,15 @@ class APIGatewaySimulator {
|
|
|
104
158
|
description: api.description,
|
|
105
159
|
version: api.version,
|
|
106
160
|
createdDate: api.createdDate,
|
|
107
|
-
apiKeySource: api.apiKeySource
|
|
161
|
+
apiKeySource: api.apiKeySource,
|
|
162
|
+
isStatic: api.isStatic || false,
|
|
163
|
+
resourceCount: api.resources.size,
|
|
164
|
+
stageCount: api.stages.size
|
|
108
165
|
}))
|
|
109
166
|
};
|
|
110
167
|
}
|
|
111
168
|
|
|
169
|
+
|
|
112
170
|
getRestApi(params) {
|
|
113
171
|
const { restApiId } = params;
|
|
114
172
|
const api = this.apis.get(restApiId);
|
|
@@ -129,6 +187,27 @@ class APIGatewaySimulator {
|
|
|
129
187
|
};
|
|
130
188
|
}
|
|
131
189
|
|
|
190
|
+
updateRestApi(params) {
|
|
191
|
+
const { restApiId, name, description } = params;
|
|
192
|
+
const api = this.apis.get(restApiId);
|
|
193
|
+
|
|
194
|
+
if (!api) {
|
|
195
|
+
throw new Error(`API ${restApiId} not found`);
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
if (name !== undefined) api.name = name;
|
|
199
|
+
if (description !== undefined) api.description = description;
|
|
200
|
+
|
|
201
|
+
if (api.isStatic) {
|
|
202
|
+
// Once edited, the API is no longer static and will be persisted
|
|
203
|
+
api.isStatic = false;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
this.persistAPIs();
|
|
207
|
+
|
|
208
|
+
return this.getRestApi({ restApiId });
|
|
209
|
+
}
|
|
210
|
+
|
|
132
211
|
deleteRestApi(params) {
|
|
133
212
|
const { restApiId } = params;
|
|
134
213
|
|
|
@@ -142,8 +221,67 @@ class APIGatewaySimulator {
|
|
|
142
221
|
return {};
|
|
143
222
|
}
|
|
144
223
|
|
|
224
|
+
// ============ Simplified Dashboard Endpoint Operations ============
|
|
225
|
+
|
|
226
|
+
putEndpoint(params) {
|
|
227
|
+
const { restApiId, path, method, integrationType, lambdaName, authorizerRequired } = params;
|
|
228
|
+
const api = this.apis.get(restApiId);
|
|
229
|
+
if (!api) throw new Error(`API ${restApiId} not found`);
|
|
230
|
+
|
|
231
|
+
if (api.isStatic) api.isStatic = false;
|
|
232
|
+
|
|
233
|
+
// Ensure resource exists
|
|
234
|
+
let resource = Array.from(api.resources.values()).find(r => r.path === path);
|
|
235
|
+
if (!resource) {
|
|
236
|
+
const resourceId = `res_${Date.now()}`;
|
|
237
|
+
resource = {
|
|
238
|
+
id: resourceId,
|
|
239
|
+
path: path,
|
|
240
|
+
pathPart: path.split('/').pop() || '/',
|
|
241
|
+
parentId: null, // Simplified
|
|
242
|
+
resourceMethods: new Map()
|
|
243
|
+
};
|
|
244
|
+
api.resources.set(resourceId, resource);
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
// Put method
|
|
248
|
+
resource.resourceMethods.set(method.toUpperCase(), {
|
|
249
|
+
httpMethod: method.toUpperCase(),
|
|
250
|
+
authorizationType: authorizerRequired ? 'COGNITO_USER_POOLS' : 'NONE',
|
|
251
|
+
apiKeyRequired: false,
|
|
252
|
+
integration: {
|
|
253
|
+
type: integrationType === 'lambda' ? 'AWS_PROXY' : 'HTTP',
|
|
254
|
+
uri: lambdaName,
|
|
255
|
+
integrationHttpMethod: 'POST'
|
|
256
|
+
}
|
|
257
|
+
});
|
|
258
|
+
|
|
259
|
+
this.persistAPIs();
|
|
260
|
+
return { resourceId: resource.id, path, method };
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
deleteEndpoint(params) {
|
|
264
|
+
const { restApiId, path, method } = params;
|
|
265
|
+
const api = this.apis.get(restApiId);
|
|
266
|
+
if (!api) throw new Error(`API ${restApiId} not found`);
|
|
267
|
+
|
|
268
|
+
if (api.isStatic) api.isStatic = false;
|
|
269
|
+
|
|
270
|
+
const resource = Array.from(api.resources.values()).find(r => r.path === path);
|
|
271
|
+
if (resource) {
|
|
272
|
+
resource.resourceMethods.delete(method.toUpperCase());
|
|
273
|
+
// If no methods left and not root, we could delete the resource, but keeping it is fine.
|
|
274
|
+
if (resource.resourceMethods.size === 0 && resource.path !== '/') {
|
|
275
|
+
api.resources.delete(resource.id);
|
|
276
|
+
}
|
|
277
|
+
this.persistAPIs();
|
|
278
|
+
}
|
|
279
|
+
return {};
|
|
280
|
+
}
|
|
281
|
+
|
|
145
282
|
// ============ Resource Operations ============
|
|
146
283
|
|
|
284
|
+
|
|
147
285
|
createResource(params) {
|
|
148
286
|
const { restApiId, parentId, pathPart } = params;
|
|
149
287
|
const api = this.apis.get(restApiId);
|
|
@@ -1054,7 +1192,10 @@ class APIGatewaySimulator {
|
|
|
1054
1192
|
if (saved) {
|
|
1055
1193
|
for (const [id, data] of Object.entries(saved)) {
|
|
1056
1194
|
// Reconstitui Maps
|
|
1057
|
-
data.resources = new Map(Object.entries(data.resources || {}))
|
|
1195
|
+
data.resources = new Map(Object.entries(data.resources || {}).map(([rid, r]) => {
|
|
1196
|
+
r.resourceMethods = new Map(Object.entries(r.resourceMethods || {}));
|
|
1197
|
+
return [rid, r];
|
|
1198
|
+
}));
|
|
1058
1199
|
data.stages = new Map(Object.entries(data.stages || {}));
|
|
1059
1200
|
data.deployments = new Map(Object.entries(data.deployments || {}));
|
|
1060
1201
|
data.models = new Map(Object.entries(data.models || {}));
|
|
@@ -1064,6 +1205,7 @@ class APIGatewaySimulator {
|
|
|
1064
1205
|
}
|
|
1065
1206
|
}
|
|
1066
1207
|
|
|
1208
|
+
|
|
1067
1209
|
loadWebSocketAPIs() {
|
|
1068
1210
|
const saved = this.store.read('__websocket_apis__');
|
|
1069
1211
|
if (saved) {
|
|
@@ -1157,9 +1299,10 @@ class APIGatewaySimulator {
|
|
|
1157
1299
|
persistAPIs() {
|
|
1158
1300
|
const apisObj = {};
|
|
1159
1301
|
for (const [id, api] of this.apis.entries()) {
|
|
1302
|
+
if (api.isStatic) continue;
|
|
1160
1303
|
apisObj[id] = {
|
|
1161
1304
|
...api,
|
|
1162
|
-
resources: Object.fromEntries(api.resources),
|
|
1305
|
+
resources: Object.fromEntries(Array.from(api.resources.entries()).map(([rid, r]) => [rid, { ...r, resourceMethods: Object.fromEntries(r.resourceMethods) }])),
|
|
1163
1306
|
stages: Object.fromEntries(api.stages),
|
|
1164
1307
|
deployments: Object.fromEntries(api.deployments),
|
|
1165
1308
|
models: Object.fromEntries(api.models),
|
|
@@ -1169,6 +1312,7 @@ class APIGatewaySimulator {
|
|
|
1169
1312
|
this.store.write('__apis__', apisObj);
|
|
1170
1313
|
}
|
|
1171
1314
|
|
|
1315
|
+
|
|
1172
1316
|
persistResources(apiId) {
|
|
1173
1317
|
const api = this.apis.get(apiId);
|
|
1174
1318
|
if (api) {
|
|
@@ -18,19 +18,10 @@ class CognitoServer {
|
|
|
18
18
|
|
|
19
19
|
setupMiddlewares() {
|
|
20
20
|
this.app.use(cors());
|
|
21
|
-
this.app.use(express.
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
req.body = JSON.parse(req.body.toString('utf8'));
|
|
26
|
-
} catch (e) {
|
|
27
|
-
req.body = {};
|
|
28
|
-
}
|
|
29
|
-
} else if (!req.body) {
|
|
30
|
-
req.body = {};
|
|
31
|
-
}
|
|
32
|
-
next();
|
|
33
|
-
});
|
|
21
|
+
this.app.use(express.json({
|
|
22
|
+
limit: '10mb',
|
|
23
|
+
type: ['application/json', 'application/x-amz-json-1.1']
|
|
24
|
+
}));
|
|
34
25
|
|
|
35
26
|
if (logger.currentLogLevel === 'verboso') {
|
|
36
27
|
this.app.use((req, res, next) => {
|
|
@@ -73,7 +64,8 @@ class CognitoServer {
|
|
|
73
64
|
|
|
74
65
|
try {
|
|
75
66
|
const result = await this.handleRequest(target, req.body || {});
|
|
76
|
-
res.json
|
|
67
|
+
res.setHeader('Content-Type', 'application/x-amz-json-1.1');
|
|
68
|
+
res.send(JSON.stringify(result));
|
|
77
69
|
} catch (error) {
|
|
78
70
|
logger.error('Cognito Error:', error.message);
|
|
79
71
|
res.status(400).json({
|
|
@@ -108,6 +100,8 @@ class CognitoServer {
|
|
|
108
100
|
return this.simulator.describeUserPool(params);
|
|
109
101
|
case 'DeleteUserPool':
|
|
110
102
|
return this.simulator.deleteUserPool(params);
|
|
103
|
+
case 'UpdateUserPool':
|
|
104
|
+
return this.simulator.updateUserPool(params);
|
|
111
105
|
|
|
112
106
|
case 'ListUsers':
|
|
113
107
|
return this.simulator.listUsers(params);
|