@gugananuvem/aws-local-simulator 1.0.35 → 1.0.39
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +662 -658
- package/package.json +10 -3
- package/src/config/default-config.js +71 -0
- package/src/config/env-loader.js +4 -2
- package/src/middleware/authorization.js +271 -0
- package/src/middleware/cloudtrail-logger.js +333 -0
- package/src/middleware/error-handler.js +138 -0
- package/src/middleware/index.js +79 -0
- package/src/middleware/policy-enforcement.js +407 -0
- package/src/server.js +74 -4
- package/src/services/acm/index.js +66 -0
- package/src/services/acm/models/certificate.js +202 -0
- package/src/services/acm/server.js +224 -0
- package/src/services/acm/simulator.js +293 -0
- package/src/services/alb/index.js +104 -0
- package/src/services/alb/models/loadBalancer.js +88 -0
- package/src/services/alb/models/targetGroup.js +128 -0
- package/src/services/alb/server.js +160 -0
- package/src/services/alb/simulator.js +262 -0
- package/src/services/apigateway/index.js +22 -0
- package/src/services/apigateway/server.js +247 -35
- package/src/services/apigateway/simulator.js +780 -26
- package/src/services/asg/index.js +100 -0
- package/src/services/asg/models/autoScalingGroup.js +110 -0
- package/src/services/asg/models/launchTemplate.js +41 -0
- package/src/services/asg/models/scalingPolicy.js +51 -0
- package/src/services/asg/server.js +236 -0
- package/src/services/asg/simulator.js +410 -0
- package/src/services/aurora/cluster-manager.js +731 -0
- package/src/services/aurora/data-api.js +514 -0
- package/src/services/aurora/db-engine.js +270 -0
- package/src/services/aurora/index.js +159 -0
- package/src/services/aurora/rds-api.js +189 -0
- package/src/services/aurora/server.js +213 -0
- package/src/services/aurora/sql-translator.js +334 -0
- package/src/services/cloudformation/index.js +2 -2
- package/src/services/cloudformation/server.js +1 -0
- package/src/services/cloudfront/cache-engine.js +225 -0
- package/src/services/cloudfront/geo-restrictions.js +377 -0
- package/src/services/cloudfront/index.js +220 -0
- package/src/services/cloudfront/models/behavior.js +158 -0
- package/src/services/cloudfront/models/distribution.js +284 -0
- package/src/services/cloudfront/models/index.js +99 -0
- package/src/services/cloudfront/models/invalidation.js +109 -0
- package/src/services/cloudfront/models/origin.js +155 -0
- package/src/services/cloudfront/origin-router.js +552 -0
- package/src/services/cloudfront/origin-validator.js +398 -0
- package/src/services/cloudfront/server.js +512 -0
- package/src/services/cloudfront/signed-url-generator.js +377 -0
- package/src/services/cloudfront/simulator.js +425 -0
- package/src/services/cloudfront/store.js +326 -0
- package/src/services/cloudshell/env-builder.js +27 -0
- package/src/services/cloudshell/index.js +48 -0
- package/src/services/cloudshell/routes.js +60 -0
- package/src/services/cloudshell/session-manager.js +226 -0
- package/src/services/cloudshell/static/.gitkeep +0 -0
- package/src/services/cloudshell/static/index.html +406 -0
- package/src/services/cloudshell/websocket-handler.js +107 -0
- package/src/services/cloudtrail/index.js +2 -3
- package/src/services/cloudtrail/server.js +68 -0
- package/src/services/cognito/server.js +4 -1
- package/src/services/dynamodb/server.js +4 -1
- package/src/services/dynamodb/simulator.js +2 -1
- package/src/services/ec2/index.js +101 -0
- package/src/services/ec2/instanceServer.js +311 -0
- package/src/services/ec2/models/ami.js +39 -0
- package/src/services/ec2/models/instance.js +98 -0
- package/src/services/ec2/models/networkInterface.js +74 -0
- package/src/services/ec2/models/securityGroup.js +71 -0
- package/src/services/ec2/server.js +350 -0
- package/src/services/ec2/simulator.js +436 -0
- package/src/services/eventbridge/server.js +36 -0
- package/src/services/eventbridge/simulator.js +230 -45
- package/src/services/iam/index.js +65 -0
- package/src/services/iam/middleware.js +99 -0
- package/src/services/iam/server.js +331 -0
- package/src/services/iam/simulator.js +1432 -0
- package/src/services/index.js +28 -0
- package/src/services/kinesis-analytics/index.js +105 -0
- package/src/services/kinesis-analytics/models/application.js +325 -0
- package/src/services/kinesis-analytics/models/index.js +12 -0
- package/src/services/kinesis-analytics/server.js +250 -0
- package/src/services/kinesis-analytics/simulator.js +407 -0
- package/src/services/kinesis-analytics/store.js +176 -0
- package/src/services/kinesis-data-streams/index.js +106 -0
- package/src/services/kinesis-data-streams/models/index.js +12 -0
- package/src/services/kinesis-data-streams/models/record.js +70 -0
- package/src/services/kinesis-data-streams/models/stream.js +280 -0
- package/src/services/kinesis-data-streams/server.js +255 -0
- package/src/services/kinesis-data-streams/simulator.js +514 -0
- package/src/services/kinesis-data-streams/store.js +163 -0
- package/src/services/kinesis-firehose/index.js +106 -0
- package/src/services/kinesis-firehose/models/delivery-stream.js +265 -0
- package/src/services/kinesis-firehose/models/index.js +10 -0
- package/src/services/kinesis-firehose/server.js +215 -0
- package/src/services/kinesis-firehose/simulator.js +388 -0
- package/src/services/kinesis-firehose/store.js +163 -0
- package/src/services/kms/server.js +4 -1
- package/src/services/lambda/index.js +26 -0
- package/src/services/lambda/server.js +12 -1
- package/src/services/lambda/simulator.js +4 -0
- package/src/services/opensearch/index.js +105 -0
- package/src/services/opensearch/models/document.js +115 -0
- package/src/services/opensearch/models/index.js +73 -0
- package/src/services/opensearch/server.js +310 -0
- package/src/services/opensearch/simulator.js +605 -0
- package/src/services/opensearch/store.js +162 -0
- package/src/services/organizations/control-tower-simulator.js +321 -0
- package/src/services/organizations/control-tower-store.js +182 -0
- package/src/services/organizations/iam-integration.js +413 -0
- package/src/services/organizations/index.js +172 -0
- package/src/services/organizations/models/index.js +13 -0
- package/src/services/organizations/models/invitation.js +48 -0
- package/src/services/organizations/models/organization.js +566 -0
- package/src/services/organizations/models/role.js +244 -0
- package/src/services/organizations/models/scp.js +131 -0
- package/src/services/organizations/server.js +736 -0
- package/src/services/organizations/simulator.js +593 -0
- package/src/services/organizations/store.js +235 -0
- package/src/services/organizations/vpc-integration.js +453 -0
- package/src/services/route53/index.js +67 -0
- package/src/services/route53/models/healthCheck.js +101 -0
- package/src/services/route53/models/hostedZone.js +37 -0
- package/src/services/route53/models/resourceRecord.js +106 -0
- package/src/services/route53/server.js +841 -0
- package/src/services/route53/simulator.js +350 -0
- package/src/services/s3/index.js +62 -0
- package/src/services/s3/presigned-url-generator.js +337 -0
- package/src/services/s3/server.js +231 -0
- package/src/services/s3/simulator.js +551 -4
- package/src/services/sns/index.js +31 -0
- package/src/services/sns/server.js +9 -0
- package/src/services/sqs/index.js +38 -0
- package/src/services/sqs/server.js +57 -4
- package/src/services/sqs/simulator.js +423 -33
- package/src/services/sts/simulator.js +209 -17
- package/src/services/vpc/README.md +459 -0
- package/src/services/vpc/ec2ResponseFormatter.js +431 -0
- package/src/services/vpc/index.js +90 -0
- package/src/services/vpc/models/internetGateway.js +71 -0
- package/src/services/vpc/models/routeTable.js +160 -0
- package/src/services/vpc/models/subnet.js +48 -0
- package/src/services/vpc/models/vpc.js +37 -0
- package/src/services/vpc/models/vpcEndpoint.js +100 -0
- package/src/services/vpc/models/vpcGateway.js +134 -0
- package/src/services/vpc/server.js +836 -0
- package/src/services/vpc/simulator.js +613 -0
- package/src/utils/policy-cache.js +215 -0
- package/aws-config +0 -154
|
@@ -0,0 +1,388 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Kinesis Data Firehose Simulator
|
|
3
|
+
* Simulador de Kinesis Data Firehose com suporte a delivery streams e buffering
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
const { DeliveryStream } = require('./models/delivery-stream');
|
|
7
|
+
const LocalStore = require('../../utils/local-store');
|
|
8
|
+
const logger = require('../../utils/logger');
|
|
9
|
+
const path = require('path');
|
|
10
|
+
const { CloudTrailAudit } = require('../../utils/cloudtrail-audit');
|
|
11
|
+
const { v4: uuidv4 } = require('uuid');
|
|
12
|
+
|
|
13
|
+
class KinesisFirehoseSimulator {
|
|
14
|
+
constructor(config) {
|
|
15
|
+
this.config = config;
|
|
16
|
+
const dataDir = process.env.AWS_LOCAL_SIMULATOR_DATA_DIR || config.dataDir || './.aws-local-simulator-data';
|
|
17
|
+
this.dataDir = path.join(dataDir, 'kinesis-firehose');
|
|
18
|
+
|
|
19
|
+
this.store = new LocalStore(this.dataDir);
|
|
20
|
+
this.deliveryStreams = new Map();
|
|
21
|
+
this.audit = new CloudTrailAudit('firehose.amazonaws.com');
|
|
22
|
+
|
|
23
|
+
// Mutexes por stream para operações thread-safe
|
|
24
|
+
this._streamLocks = new Map();
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Executa operação com lock exclusivo por stream
|
|
29
|
+
*/
|
|
30
|
+
_withStreamLock(streamName, fn) {
|
|
31
|
+
const prev = this._streamLocks.get(streamName) || Promise.resolve();
|
|
32
|
+
const next = prev.then(() => fn());
|
|
33
|
+
this._streamLocks.set(streamName, next.catch(() => {}));
|
|
34
|
+
return next;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
async initialize() {
|
|
38
|
+
logger.debug('Inicializando Kinesis Data Firehose Simulator...');
|
|
39
|
+
this.loadDeliveryStreams();
|
|
40
|
+
|
|
41
|
+
// Cria delivery streams da configuração
|
|
42
|
+
if (this.config['kinesis-firehose']?.deliveryStreams) {
|
|
43
|
+
for (const streamConfig of this.config['kinesis-firehose'].deliveryStreams) {
|
|
44
|
+
if (!this.deliveryStreams.has(streamConfig.name)) {
|
|
45
|
+
this.createDeliveryStream(streamConfig.name, streamConfig);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
logger.debug(`✅ Kinesis Data Firehose Simulator inicializado com ${this.deliveryStreams.size} delivery streams`);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Carrega delivery streams persistidos
|
|
55
|
+
*/
|
|
56
|
+
loadDeliveryStreams() {
|
|
57
|
+
const metadata = this.store.read('__delivery_streams__');
|
|
58
|
+
if (!metadata) return;
|
|
59
|
+
|
|
60
|
+
for (const [streamName, streamData] of Object.entries(metadata)) {
|
|
61
|
+
const stream = new DeliveryStream(streamName, streamData);
|
|
62
|
+
this.deliveryStreams.set(streamName, stream);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
logger.debug(`Carregados ${this.deliveryStreams.size} delivery streams`);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Persiste metadados de delivery streams
|
|
70
|
+
*/
|
|
71
|
+
persistDeliveryStreams() {
|
|
72
|
+
const metadata = {};
|
|
73
|
+
|
|
74
|
+
for (const [streamName, stream] of this.deliveryStreams) {
|
|
75
|
+
metadata[streamName] = stream.toJSON();
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
this.store.write('__delivery_streams__', metadata);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Cria um novo delivery stream
|
|
83
|
+
*/
|
|
84
|
+
createDeliveryStream(deliveryStreamName, config = {}) {
|
|
85
|
+
if (this.deliveryStreams.has(deliveryStreamName)) {
|
|
86
|
+
throw new Error(`DeliveryStream '${deliveryStreamName}' already exists`);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
const stream = new DeliveryStream(deliveryStreamName, config);
|
|
90
|
+
|
|
91
|
+
// Adiciona destinos (S3, HTTP, Redshift)
|
|
92
|
+
if (config.s3DestinationConfiguration) {
|
|
93
|
+
stream.addDestination({
|
|
94
|
+
type: 's3',
|
|
95
|
+
destinationId: uuidv4(),
|
|
96
|
+
arn: config.s3DestinationConfiguration.RoleArn,
|
|
97
|
+
bucketArn: config.s3DestinationConfiguration.BucketARN,
|
|
98
|
+
prefix: config.s3DestinationConfiguration.Prefix || '',
|
|
99
|
+
errorOutputPrefix: config.s3DestinationConfiguration.ErrorOutputPrefix || '',
|
|
100
|
+
bufferingHints: {
|
|
101
|
+
sizeInMBs: config.s3DestinationConfiguration.BufferingHints?.SizeInMBs || 128,
|
|
102
|
+
intervalInSeconds: config.s3DestinationConfiguration.BufferingHints?.IntervalInSeconds || 60
|
|
103
|
+
},
|
|
104
|
+
compressionFormat: config.s3DestinationConfiguration.CompressionFormat || 'UNCOMPRESSED',
|
|
105
|
+
destinationStatus: 'ACTIVE'
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
if (config.httpEndpointDestinationConfiguration) {
|
|
110
|
+
stream.addDestination({
|
|
111
|
+
type: 'http',
|
|
112
|
+
destinationId: uuidv4(),
|
|
113
|
+
url: config.httpEndpointDestinationConfiguration.EndpointConfiguration?.Url,
|
|
114
|
+
name: config.httpEndpointDestinationConfiguration.EndpointConfiguration?.Name,
|
|
115
|
+
roleArn: config.httpEndpointDestinationConfiguration.RoleARN,
|
|
116
|
+
bufferingHints: {
|
|
117
|
+
sizeInMBs: config.httpEndpointDestinationConfiguration.BufferingHints?.SizeInMBs || 128,
|
|
118
|
+
intervalInSeconds: config.httpEndpointDestinationConfiguration.BufferingHints?.IntervalInSeconds || 60
|
|
119
|
+
},
|
|
120
|
+
processingConfiguration: config.httpEndpointDestinationConfiguration.ProcessingConfiguration || null,
|
|
121
|
+
destinationStatus: 'ACTIVE'
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
if (config.redshiftDestinationConfiguration) {
|
|
126
|
+
stream.addDestination({
|
|
127
|
+
type: 'redshift',
|
|
128
|
+
destinationId: uuidv4(),
|
|
129
|
+
clusterArn: config.redshiftDestinationConfiguration.ClusterARN,
|
|
130
|
+
roleArn: config.redshiftDestinationConfiguration.RoleARN,
|
|
131
|
+
username: config.redshiftDestinationConfiguration.Username,
|
|
132
|
+
databaseName: config.redshiftDestinationConfiguration.DatabaseName,
|
|
133
|
+
tableName: config.redshiftDestinationConfiguration.TableName,
|
|
134
|
+
s3DestinationDescription: config.redshiftDestinationConfiguration.S3DestinationConfiguration,
|
|
135
|
+
destinationStatus: 'ACTIVE'
|
|
136
|
+
});
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
this.deliveryStreams.set(deliveryStreamName, stream);
|
|
140
|
+
this.persistDeliveryStreams();
|
|
141
|
+
|
|
142
|
+
this.audit.record({
|
|
143
|
+
eventName: 'CreateDeliveryStream',
|
|
144
|
+
readOnly: false,
|
|
145
|
+
resources: [{
|
|
146
|
+
ARN: stream.deliveryStreamArn,
|
|
147
|
+
type: 'AWS::Firehose::DeliveryStream'
|
|
148
|
+
}],
|
|
149
|
+
requestParameters: { deliveryStreamName }
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
logger.info(`✅ Delivery Stream criado: ${deliveryStreamName}`);
|
|
153
|
+
|
|
154
|
+
return {
|
|
155
|
+
DeliveryStreamArn: stream.deliveryStreamArn
|
|
156
|
+
};
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
/**
|
|
160
|
+
* Descreve um delivery stream
|
|
161
|
+
*/
|
|
162
|
+
describeDeliveryStream(deliveryStreamName) {
|
|
163
|
+
const stream = this.deliveryStreams.get(deliveryStreamName);
|
|
164
|
+
if (!stream) {
|
|
165
|
+
throw new Error(`DeliveryStream '${deliveryStreamName}' not found`);
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
return stream.describe();
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
/**
|
|
172
|
+
* Lista delivery streams
|
|
173
|
+
*/
|
|
174
|
+
listDeliveryStreams(params = {}) {
|
|
175
|
+
const { Limit = 100, ExclusiveStartDeliveryStreamName } = params;
|
|
176
|
+
const streamNames = Array.from(this.deliveryStreams.keys());
|
|
177
|
+
|
|
178
|
+
let startIndex = 0;
|
|
179
|
+
if (ExclusiveStartDeliveryStreamName) {
|
|
180
|
+
const index = streamNames.indexOf(ExclusiveStartDeliveryStreamName);
|
|
181
|
+
if (index !== -1) startIndex = index + 1;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
const result = streamNames.slice(startIndex, startIndex + Limit);
|
|
185
|
+
|
|
186
|
+
return {
|
|
187
|
+
DeliveryStreamNames: result,
|
|
188
|
+
HasMoreDeliveryStreams: result.length === Limit
|
|
189
|
+
};
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
/**
|
|
193
|
+
* Deleta um delivery stream
|
|
194
|
+
*/
|
|
195
|
+
deleteDeliveryStream(deliveryStreamName) {
|
|
196
|
+
const stream = this.deliveryStreams.get(deliveryStreamName);
|
|
197
|
+
if (!stream) {
|
|
198
|
+
throw new Error(`DeliveryStream '${deliveryStreamName}' not found`);
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
this.deliveryStreams.delete(deliveryStreamName);
|
|
202
|
+
this.persistDeliveryStreams();
|
|
203
|
+
|
|
204
|
+
this.audit.record({
|
|
205
|
+
eventName: 'DeleteDeliveryStream',
|
|
206
|
+
readOnly: false,
|
|
207
|
+
resources: [{
|
|
208
|
+
ARN: stream.deliveryStreamArn,
|
|
209
|
+
type: 'AWS::Firehose::DeliveryStream'
|
|
210
|
+
}],
|
|
211
|
+
requestParameters: { deliveryStreamName }
|
|
212
|
+
});
|
|
213
|
+
|
|
214
|
+
logger.info(`✅ Delivery Stream deletado: ${deliveryStreamName}`);
|
|
215
|
+
|
|
216
|
+
return {};
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
/**
|
|
220
|
+
* Coloca um record no delivery stream (PutRecord)
|
|
221
|
+
*/
|
|
222
|
+
async putRecord(deliveryStreamName, record) {
|
|
223
|
+
const stream = this.deliveryStreams.get(deliveryStreamName);
|
|
224
|
+
if (!stream) {
|
|
225
|
+
throw new Error(`DeliveryStream '${deliveryStreamName}' not found`);
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
return this._withStreamLock(deliveryStreamName, () => {
|
|
229
|
+
const addedRecord = stream.addRecordToBuffer(record.Data);
|
|
230
|
+
|
|
231
|
+
this.persistDeliveryStreams();
|
|
232
|
+
|
|
233
|
+
this.audit.record({
|
|
234
|
+
eventName: 'PutRecord',
|
|
235
|
+
readOnly: false,
|
|
236
|
+
isDataEvent: true,
|
|
237
|
+
resources: [{
|
|
238
|
+
ARN: stream.deliveryStreamArn,
|
|
239
|
+
type: 'AWS::Firehose::DeliveryStream'
|
|
240
|
+
}],
|
|
241
|
+
requestParameters: { deliveryStreamName }
|
|
242
|
+
});
|
|
243
|
+
|
|
244
|
+
return {
|
|
245
|
+
RecordId: addedRecord.recordId,
|
|
246
|
+
Encrypted: false
|
|
247
|
+
};
|
|
248
|
+
});
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
/**
|
|
252
|
+
* Coloca múltiplos records no delivery stream (PutRecordBatch)
|
|
253
|
+
*/
|
|
254
|
+
async putRecordBatch(deliveryStreamName, records) {
|
|
255
|
+
const stream = this.deliveryStreams.get(deliveryStreamName);
|
|
256
|
+
if (!stream) {
|
|
257
|
+
throw new Error(`DeliveryStream '${deliveryStreamName}' not found`);
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
return this._withStreamLock(deliveryStreamName, () => {
|
|
261
|
+
const results = [];
|
|
262
|
+
let failedCount = 0;
|
|
263
|
+
|
|
264
|
+
for (const record of records) {
|
|
265
|
+
try {
|
|
266
|
+
const addedRecord = stream.addRecordToBuffer(record.Data);
|
|
267
|
+
results.push({
|
|
268
|
+
RecordId: addedRecord.recordId,
|
|
269
|
+
ErrorCode: null,
|
|
270
|
+
ErrorMessage: null
|
|
271
|
+
});
|
|
272
|
+
} catch (error) {
|
|
273
|
+
failedCount++;
|
|
274
|
+
results.push({
|
|
275
|
+
RecordId: null,
|
|
276
|
+
ErrorCode: 'InternalFailure',
|
|
277
|
+
ErrorMessage: error.message
|
|
278
|
+
});
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
this.persistDeliveryStreams();
|
|
283
|
+
|
|
284
|
+
this.audit.record({
|
|
285
|
+
eventName: 'PutRecordBatch',
|
|
286
|
+
readOnly: false,
|
|
287
|
+
isDataEvent: true,
|
|
288
|
+
resources: [{
|
|
289
|
+
ARN: stream.deliveryStreamArn,
|
|
290
|
+
type: 'AWS::Firehose::DeliveryStream'
|
|
291
|
+
}],
|
|
292
|
+
requestParameters: { deliveryStreamName, recordCount: records.length }
|
|
293
|
+
});
|
|
294
|
+
|
|
295
|
+
return {
|
|
296
|
+
FailedPutCount: failedCount,
|
|
297
|
+
Encrypted: false,
|
|
298
|
+
Records: results
|
|
299
|
+
};
|
|
300
|
+
});
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
/**
|
|
304
|
+
* Obtém informações de destino
|
|
305
|
+
*/
|
|
306
|
+
describeDestination(deliveryStreamName, destinationId) {
|
|
307
|
+
const stream = this.deliveryStreams.get(deliveryStreamName);
|
|
308
|
+
if (!stream) {
|
|
309
|
+
throw new Error(`DeliveryStream '${deliveryStreamName}' not found`);
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
const destination = stream.destinations.find(d => d.destinationId === destinationId);
|
|
313
|
+
if (!destination) {
|
|
314
|
+
throw new Error(`Destination '${destinationId}' not found`);
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
return {
|
|
318
|
+
DestinationDescription: destination
|
|
319
|
+
};
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
/**
|
|
323
|
+
* Atualiza delivery stream
|
|
324
|
+
*/
|
|
325
|
+
updateDeliveryStream(deliveryStreamName, config) {
|
|
326
|
+
const stream = this.deliveryStreams.get(deliveryStreamName);
|
|
327
|
+
if (!stream) {
|
|
328
|
+
throw new Error(`DeliveryStream '${deliveryStreamName}' not found`);
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
// Atualiza configurações
|
|
332
|
+
if (config.deliveryStreamStatus) {
|
|
333
|
+
stream.deliveryStreamStatus = config.deliveryStreamStatus;
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
if (config.s3DestinationUpdate) {
|
|
337
|
+
const dest = stream.destinations[0];
|
|
338
|
+
if (dest && dest.type === 's3') {
|
|
339
|
+
Object.assign(dest, {
|
|
340
|
+
bufferingHints: config.s3DestinationUpdate.BufferingHints || dest.bufferingHints,
|
|
341
|
+
prefix: config.s3DestinationUpdate.Prefix || dest.prefix
|
|
342
|
+
});
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
stream.lastUpdateTimestamp = new Date().toISOString();
|
|
347
|
+
this.persistDeliveryStreams();
|
|
348
|
+
|
|
349
|
+
logger.info(`✅ Delivery Stream atualizado: ${deliveryStreamName}`);
|
|
350
|
+
|
|
351
|
+
return {};
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
/**
|
|
355
|
+
* Obtém estatísticas do simulator
|
|
356
|
+
*/
|
|
357
|
+
getStats() {
|
|
358
|
+
const stats = {
|
|
359
|
+
deliveryStreamsCount: this.deliveryStreams.size,
|
|
360
|
+
totalRecords: 0,
|
|
361
|
+
totalDeliveredRecords: 0,
|
|
362
|
+
totalFailedRecords: 0,
|
|
363
|
+
deliveryStreams: []
|
|
364
|
+
};
|
|
365
|
+
|
|
366
|
+
for (const [streamName, stream] of this.deliveryStreams) {
|
|
367
|
+
const streamStats = stream.toJSON();
|
|
368
|
+
stats.totalRecords += streamStats.recordCount;
|
|
369
|
+
stats.totalDeliveredRecords += streamStats.deliveredRecordCount;
|
|
370
|
+
stats.totalFailedRecords += streamStats.failedDeliveryRecordCount;
|
|
371
|
+
stats.deliveryStreams.push(streamStats);
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
return stats;
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
/**
|
|
378
|
+
* Reset - limpa todos os dados
|
|
379
|
+
*/
|
|
380
|
+
async reset() {
|
|
381
|
+
this.deliveryStreams.clear();
|
|
382
|
+
this.store.write('__delivery_streams__', {});
|
|
383
|
+
|
|
384
|
+
logger.info('✅ Kinesis Data Firehose Simulator resetado');
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
module.exports = KinesisFirehoseSimulator;
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Kinesis Data Firehose Store - Persistência de delivery streams em JSON
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
const path = require('path');
|
|
6
|
+
const fs = require('fs');
|
|
7
|
+
const logger = require('../../utils/logger');
|
|
8
|
+
|
|
9
|
+
class KinesisFirehoseStore {
|
|
10
|
+
constructor(dataDir) {
|
|
11
|
+
this.dataDir = dataDir;
|
|
12
|
+
this.streamsDir = path.join(dataDir, 'kinesis-firehose');
|
|
13
|
+
this.metadataFile = path.join(this.streamsDir, '__delivery_streams__.json');
|
|
14
|
+
|
|
15
|
+
if (!fs.existsSync(this.streamsDir)) {
|
|
16
|
+
fs.mkdirSync(this.streamsDir, { recursive: true });
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Carrega metadados de todos os delivery streams
|
|
22
|
+
*/
|
|
23
|
+
loadMetadata() {
|
|
24
|
+
if (fs.existsSync(this.metadataFile)) {
|
|
25
|
+
try {
|
|
26
|
+
const data = fs.readFileSync(this.metadataFile, 'utf8');
|
|
27
|
+
return JSON.parse(data);
|
|
28
|
+
} catch (error) {
|
|
29
|
+
logger.warn(`Erro ao ler ${this.metadataFile}: ${error.message}`);
|
|
30
|
+
return {};
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
return {};
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Salva metadados de todos os delivery streams
|
|
38
|
+
*/
|
|
39
|
+
saveMetadata(metadata) {
|
|
40
|
+
try {
|
|
41
|
+
fs.writeFileSync(this.metadataFile, JSON.stringify(metadata, null, 2), 'utf8');
|
|
42
|
+
logger.debug(`Metadados de delivery streams salvos em ${this.metadataFile}`);
|
|
43
|
+
} catch (error) {
|
|
44
|
+
logger.error(`Erro ao salvar ${this.metadataFile}: ${error.message}`);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Obtém caminho do arquivo de delivery stream
|
|
50
|
+
*/
|
|
51
|
+
getDeliveryStreamPath(deliveryStreamName) {
|
|
52
|
+
return path.join(this.streamsDir, `${deliveryStreamName}.json`);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Carrega records de um delivery stream
|
|
57
|
+
*/
|
|
58
|
+
loadDeliveryStreamRecords(deliveryStreamName) {
|
|
59
|
+
const streamPath = this.getDeliveryStreamPath(deliveryStreamName);
|
|
60
|
+
|
|
61
|
+
if (!fs.existsSync(streamPath)) {
|
|
62
|
+
return [];
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
try {
|
|
66
|
+
const data = fs.readFileSync(streamPath, 'utf8');
|
|
67
|
+
return JSON.parse(data);
|
|
68
|
+
} catch (error) {
|
|
69
|
+
logger.warn(`Erro ao ler delivery stream ${deliveryStreamName}: ${error.message}`);
|
|
70
|
+
return [];
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Salva records de um delivery stream
|
|
76
|
+
*/
|
|
77
|
+
saveDeliveryStreamRecords(deliveryStreamName, records) {
|
|
78
|
+
try {
|
|
79
|
+
const streamPath = this.getDeliveryStreamPath(deliveryStreamName);
|
|
80
|
+
fs.writeFileSync(streamPath, JSON.stringify(records, null, 2), 'utf8');
|
|
81
|
+
logger.debug(`${records.length} records salvos no delivery stream ${deliveryStreamName}`);
|
|
82
|
+
} catch (error) {
|
|
83
|
+
logger.error(`Erro ao salvar delivery stream ${deliveryStreamName}: ${error.message}`);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Adiciona record ao delivery stream
|
|
89
|
+
*/
|
|
90
|
+
addRecord(deliveryStreamName, record) {
|
|
91
|
+
const records = this.loadDeliveryStreamRecords(deliveryStreamName);
|
|
92
|
+
records.push(record);
|
|
93
|
+
this.saveDeliveryStreamRecords(deliveryStreamName, records);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Adiciona múltiplos records
|
|
98
|
+
*/
|
|
99
|
+
addRecords(deliveryStreamName, newRecords) {
|
|
100
|
+
const records = this.loadDeliveryStreamRecords(deliveryStreamName);
|
|
101
|
+
records.push(...newRecords);
|
|
102
|
+
this.saveDeliveryStreamRecords(deliveryStreamName, records);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Remove record do delivery stream
|
|
107
|
+
*/
|
|
108
|
+
removeRecord(deliveryStreamName, recordId) {
|
|
109
|
+
const records = this.loadDeliveryStreamRecords(deliveryStreamName);
|
|
110
|
+
const filtered = records.filter(r => r.recordId !== recordId);
|
|
111
|
+
|
|
112
|
+
if (filtered.length < records.length) {
|
|
113
|
+
this.saveDeliveryStreamRecords(deliveryStreamName, filtered);
|
|
114
|
+
return true;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
return false;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Deleta delivery stream completamente
|
|
122
|
+
*/
|
|
123
|
+
deleteDeliveryStream(deliveryStreamName) {
|
|
124
|
+
try {
|
|
125
|
+
const streamPath = this.getDeliveryStreamPath(deliveryStreamName);
|
|
126
|
+
if (fs.existsSync(streamPath)) {
|
|
127
|
+
fs.unlinkSync(streamPath);
|
|
128
|
+
logger.debug(`Delivery Stream ${deliveryStreamName} deletado`);
|
|
129
|
+
}
|
|
130
|
+
} catch (error) {
|
|
131
|
+
logger.error(`Erro ao deletar delivery stream ${deliveryStreamName}: ${error.message}`);
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* Calcula tamanho total do delivery stream em bytes
|
|
137
|
+
*/
|
|
138
|
+
getDeliveryStreamSize(deliveryStreamName) {
|
|
139
|
+
const records = this.loadDeliveryStreamRecords(deliveryStreamName);
|
|
140
|
+
return records.reduce((sum, record) => sum + Buffer.byteLength(JSON.stringify(record.data)), 0);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* Limpa todos os delivery streams
|
|
145
|
+
*/
|
|
146
|
+
clear() {
|
|
147
|
+
try {
|
|
148
|
+
if (fs.existsSync(this.streamsDir)) {
|
|
149
|
+
const files = fs.readdirSync(this.streamsDir);
|
|
150
|
+
files.forEach(file => {
|
|
151
|
+
if (file.endsWith('.json')) {
|
|
152
|
+
fs.unlinkSync(path.join(this.streamsDir, file));
|
|
153
|
+
}
|
|
154
|
+
});
|
|
155
|
+
logger.debug('Todos os delivery streams foram limpos');
|
|
156
|
+
}
|
|
157
|
+
} catch (error) {
|
|
158
|
+
logger.error(`Erro ao limpar delivery streams: ${error.message}`);
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
module.exports = KinesisFirehoseStore;
|
|
@@ -1,10 +1,13 @@
|
|
|
1
|
+
const { setupServiceMiddleware } = require('../../middleware');
|
|
1
2
|
'use strict';
|
|
2
3
|
|
|
3
4
|
const express = require('express');
|
|
4
5
|
const cors = require('cors');
|
|
5
6
|
|
|
6
7
|
class KMSServer {
|
|
7
|
-
constructor(simulator, logger, config) {
|
|
8
|
+
constructor(simulator, logger, config) {
|
|
9
|
+
this.organizationsService = null;
|
|
10
|
+
this.stsService = null;
|
|
8
11
|
this.simulator = simulator;
|
|
9
12
|
this.logger = logger;
|
|
10
13
|
this.config = config;
|
|
@@ -39,6 +39,32 @@ class LambdaService {
|
|
|
39
39
|
|
|
40
40
|
const cw = server.getService('cloudwatch');
|
|
41
41
|
if (cw?.simulator) this.simulator.cloudwatchSimulator = cw.simulator;
|
|
42
|
+
|
|
43
|
+
// IAM enforcement middleware (only when IAM service is active)
|
|
44
|
+
const iamService = server.getService('iam');
|
|
45
|
+
if (iamService && iamService.getSimulator()) {
|
|
46
|
+
const { createIAMEnforcementMiddleware } = require('../iam/middleware');
|
|
47
|
+
const iamSimulator = iamService.getSimulator();
|
|
48
|
+
this.server.iamMiddleware = createIAMEnforcementMiddleware({
|
|
49
|
+
iamSimulator,
|
|
50
|
+
servicePrefix: 'lambda',
|
|
51
|
+
extractAction: (req) => {
|
|
52
|
+
if (req.path.includes('/invocations')) return 'InvokeFunction';
|
|
53
|
+
if (req.method === 'POST') return 'CreateFunction';
|
|
54
|
+
if (req.method === 'GET') return 'ListFunctions';
|
|
55
|
+
if (req.method === 'DELETE') return 'DeleteFunction';
|
|
56
|
+
if (req.method === 'PUT') return 'UpdateFunctionConfiguration';
|
|
57
|
+
return 'Unknown';
|
|
58
|
+
},
|
|
59
|
+
extractResource: (req) => {
|
|
60
|
+
const functionName = req.params?.functionName || req.path.split('/functions/')[1]?.split('/')[0] || 'unknown';
|
|
61
|
+
return `arn:aws:lambda:us-east-1:123456789012:function:${functionName}`;
|
|
62
|
+
},
|
|
63
|
+
formatError: (res, code, message) => {
|
|
64
|
+
res.status(403).json({ Message: message, Type: code });
|
|
65
|
+
}
|
|
66
|
+
});
|
|
67
|
+
}
|
|
42
68
|
}
|
|
43
69
|
|
|
44
70
|
async start() {
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
const { setupServiceMiddleware } = require('../../middleware');
|
|
1
2
|
/**
|
|
2
3
|
* Lambda Server - Servidor HTTP para Lambda (API Gateway style)
|
|
3
4
|
*/
|
|
@@ -8,7 +9,9 @@ const LambdaSimulator = require('./simulator');
|
|
|
8
9
|
const logger = require('../../utils/logger');
|
|
9
10
|
|
|
10
11
|
class LambdaServer {
|
|
11
|
-
constructor(port, config) {
|
|
12
|
+
constructor(port, config) {
|
|
13
|
+
this.organizationsService = null;
|
|
14
|
+
this.stsService = null;
|
|
12
15
|
this.port = port;
|
|
13
16
|
this.config = config;
|
|
14
17
|
this.app = express();
|
|
@@ -42,6 +45,14 @@ class LambdaServer {
|
|
|
42
45
|
}
|
|
43
46
|
|
|
44
47
|
setupRoutes() {
|
|
48
|
+
// IAM enforcement (conditionally applied)
|
|
49
|
+
this.app.use((req, res, next) => {
|
|
50
|
+
if (this.iamMiddleware && !req.path.startsWith('/__admin') && !req.path.startsWith('/health')) {
|
|
51
|
+
return this.iamMiddleware(req, res, next);
|
|
52
|
+
}
|
|
53
|
+
next();
|
|
54
|
+
});
|
|
55
|
+
|
|
45
56
|
// Health check
|
|
46
57
|
this.app.get('/health', (req, res) => {
|
|
47
58
|
res.json({ status: 'healthy', lambdas: this.simulator.getLambdasCount() });
|
|
@@ -141,6 +141,10 @@ class LambdaSimulator {
|
|
|
141
141
|
return { StatusCode: result.statusCode || 200, Payload: result };
|
|
142
142
|
}
|
|
143
143
|
|
|
144
|
+
async invokeFunction(functionName, event) {
|
|
145
|
+
return this.invoke(functionName, event, "Event");
|
|
146
|
+
}
|
|
147
|
+
|
|
144
148
|
async executeHandler(lambda, functionName, event) {
|
|
145
149
|
const requestId = Math.random().toString(36).substring(2, 10) + Math.random().toString(36).substring(2, 10);
|
|
146
150
|
const capturedLogs = [];
|