@gugananuvem/aws-local-simulator 1.0.34 → 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 +11 -4
- 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/handler-loader.js +50 -5
- package/src/services/lambda/index.js +26 -0
- package/src/services/lambda/server.js +18 -12
- package/src/services/lambda/simulator.js +73 -17
- 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;
|
|
@@ -146,9 +146,25 @@ class HandlerLoader {
|
|
|
146
146
|
* Recarrega um handler (útil para hot reload)
|
|
147
147
|
*/
|
|
148
148
|
static async reload(handlerPath, type = 'auto') {
|
|
149
|
-
//
|
|
150
|
-
|
|
151
|
-
|
|
149
|
+
// Resolve path: try cwd first, then data dir (same logic as load())
|
|
150
|
+
let fullPath = path.resolve(process.cwd(), handlerPath);
|
|
151
|
+
|
|
152
|
+
if (!fs.existsSync(fullPath)) {
|
|
153
|
+
const dataDir = process.env.AWS_LOCAL_SIMULATOR_DATA_DIR;
|
|
154
|
+
if (dataDir) {
|
|
155
|
+
const dataPath = path.resolve(dataDir, 'lambda', handlerPath.replace(/^\.\//, ''));
|
|
156
|
+
if (fs.existsSync(dataPath)) {
|
|
157
|
+
fullPath = dataPath;
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
if (!fs.existsSync(fullPath)) {
|
|
163
|
+
throw new Error(`Handler não encontrado: ${fullPath}`);
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
// Limpa cache do require
|
|
167
|
+
delete require.cache[fullPath];
|
|
152
168
|
|
|
153
169
|
// Recarrega
|
|
154
170
|
return this.load(handlerPath, type);
|
|
@@ -158,7 +174,20 @@ class HandlerLoader {
|
|
|
158
174
|
* Valida se um caminho de handler é válido
|
|
159
175
|
*/
|
|
160
176
|
static isValid(handlerPath) {
|
|
161
|
-
|
|
177
|
+
// Tenta resolver de cwd primeiro
|
|
178
|
+
let fullPath = path.resolve(process.cwd(), handlerPath);
|
|
179
|
+
|
|
180
|
+
if (!fs.existsSync(fullPath)) {
|
|
181
|
+
// Tenta resolver do data dir
|
|
182
|
+
const dataDir = process.env.AWS_LOCAL_SIMULATOR_DATA_DIR;
|
|
183
|
+
if (dataDir) {
|
|
184
|
+
const dataPath = path.resolve(dataDir, 'lambda', handlerPath.replace(/^\.\//, ''));
|
|
185
|
+
if (fs.existsSync(dataPath)) {
|
|
186
|
+
return true;
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
|
|
162
191
|
return fs.existsSync(fullPath);
|
|
163
192
|
}
|
|
164
193
|
|
|
@@ -166,7 +195,23 @@ class HandlerLoader {
|
|
|
166
195
|
* Retorna informações sobre o handler
|
|
167
196
|
*/
|
|
168
197
|
static async getInfo(handlerPath) {
|
|
169
|
-
|
|
198
|
+
// Resolve path: try cwd first, then data dir (same logic as load())
|
|
199
|
+
let fullPath = path.resolve(process.cwd(), handlerPath);
|
|
200
|
+
|
|
201
|
+
if (!fs.existsSync(fullPath)) {
|
|
202
|
+
const dataDir = process.env.AWS_LOCAL_SIMULATOR_DATA_DIR;
|
|
203
|
+
if (dataDir) {
|
|
204
|
+
const dataPath = path.resolve(dataDir, 'lambda', handlerPath.replace(/^\.\//, ''));
|
|
205
|
+
if (fs.existsSync(dataPath)) {
|
|
206
|
+
fullPath = dataPath;
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
if (!fs.existsSync(fullPath)) {
|
|
212
|
+
throw new Error(`Handler não encontrado: ${fullPath}`);
|
|
213
|
+
}
|
|
214
|
+
|
|
170
215
|
const type = this.detectType(fullPath);
|
|
171
216
|
const stats = fs.statSync(fullPath);
|
|
172
217
|
|
|
@@ -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() {
|