Skip to main content

fakecloud_logs/
state.rs

1use std::collections::BTreeMap;
2use std::sync::Arc;
3
4use parking_lot::RwLock;
5
6pub type SharedLogsState = Arc<RwLock<fakecloud_core::multi_account::MultiAccountState<LogsState>>>;
7
8impl fakecloud_core::multi_account::AccountState for LogsState {
9    fn new_for_account(account_id: &str, region: &str, _endpoint: &str) -> Self {
10        Self::new(account_id, region)
11    }
12}
13
14/// JSON object keys must be strings, so serialize
15/// `HashMap<(String,String), AccountPolicy>` as a list of
16/// `[policy_name, policy_type, policy]` tuples.
17mod account_policy_map_serde {
18    use super::AccountPolicy;
19    use serde::{Deserialize, Deserializer, Serialize, Serializer};
20    use std::collections::BTreeMap;
21
22    pub fn serialize<S: Serializer>(
23        map: &BTreeMap<(String, String), AccountPolicy>,
24        s: S,
25    ) -> Result<S::Ok, S::Error> {
26        let entries: Vec<(&String, &String, &AccountPolicy)> = map
27            .iter()
28            .map(|((name, kind), p)| (name, kind, p))
29            .collect();
30        entries.serialize(s)
31    }
32
33    pub fn deserialize<'de, D: Deserializer<'de>>(
34        d: D,
35    ) -> Result<BTreeMap<(String, String), AccountPolicy>, D::Error> {
36        let entries: Vec<(String, String, AccountPolicy)> = Vec::deserialize(d)?;
37        Ok(entries
38            .into_iter()
39            .map(|(name, kind, p)| ((name, kind), p))
40            .collect())
41    }
42}
43
44#[derive(Clone, serde::Serialize, serde::Deserialize)]
45pub struct LogsState {
46    pub account_id: String,
47    pub region: String,
48    pub log_groups: BTreeMap<String, LogGroup>,
49    pub metric_filters: Vec<MetricFilter>,
50    pub resource_policies: BTreeMap<String, ResourcePolicy>,
51    pub destinations: BTreeMap<String, Destination>,
52    pub queries: BTreeMap<String, QueryInfo>,
53    pub export_tasks: Vec<ExportTask>,
54    pub delivery_destinations: BTreeMap<String, DeliveryDestination>,
55    pub delivery_sources: BTreeMap<String, DeliverySource>,
56    pub deliveries: BTreeMap<String, Delivery>,
57    pub query_definitions: BTreeMap<String, QueryDefinition>,
58    /// Account policies keyed by (policy_name, policy_type)
59    #[serde(with = "account_policy_map_serde")]
60    pub account_policies: BTreeMap<(String, String), AccountPolicy>,
61    /// Anomaly detectors keyed by detector ARN
62    pub anomaly_detectors: BTreeMap<String, AnomalyDetector>,
63    /// Import tasks keyed by import ID
64    pub import_tasks: BTreeMap<String, ImportTask>,
65    /// Integrations keyed by integration name
66    pub integrations: BTreeMap<String, Integration>,
67    /// Lookup tables keyed by ARN
68    pub lookup_tables: BTreeMap<String, LookupTable>,
69    /// Scheduled queries keyed by identifier (ARN)
70    pub scheduled_queries: BTreeMap<String, ScheduledQuery>,
71    /// S3 table integration sources keyed by integration ARN -> list of source identifiers
72    pub s3_table_sources: BTreeMap<String, Vec<String>>,
73    /// Bearer token authentication flag per log group
74    pub bearer_token_auth: BTreeMap<String, bool>,
75    /// Internal export storage: keyed by "bucket/prefix/..." path, value is exported data.
76    /// Used by CreateExportTask and delivery pipeline when direct S3 access is unavailable.
77    pub export_storage: BTreeMap<String, Vec<u8>>,
78    /// Detected log anomalies keyed by anomaly id. Populated via the
79    /// `/_fakecloud/logs/anomalies/inject` admin endpoint and surfaced
80    /// through ListAnomalies / UpdateAnomaly.
81    #[serde(default)]
82    pub anomalies: BTreeMap<String, LogAnomaly>,
83    /// Syslog configurations keyed by log group name. Each enables syslog
84    /// ingestion into the log group through a VPC endpoint, surfaced through
85    /// PutSyslogConfiguration / ListSyslogConfigurations / DeleteSyslogConfiguration.
86    #[serde(default)]
87    pub syslog_configurations: BTreeMap<String, SyslogConfiguration>,
88}
89
90#[derive(Clone, serde::Serialize, serde::Deserialize)]
91pub struct SyslogConfiguration {
92    pub log_group_arn: String,
93    pub source_type: String,
94    pub vpc_endpoint_id: Option<String>,
95    pub created_at: i64,
96}
97
98#[derive(Clone, serde::Serialize, serde::Deserialize)]
99pub struct LogAnomaly {
100    pub anomaly_id: String,
101    pub anomaly_detector_arn: String,
102    pub log_group_arn_list: Vec<String>,
103    pub pattern_id: String,
104    pub pattern_string: String,
105    pub first_seen: i64,
106    pub last_seen: i64,
107    pub priority: String,
108    pub state: String,
109    pub suppressed: bool,
110}
111
112impl LogsState {
113    pub fn new(account_id: &str, region: &str) -> Self {
114        Self {
115            account_id: account_id.to_string(),
116            region: region.to_string(),
117            log_groups: BTreeMap::new(),
118            metric_filters: Vec::new(),
119            resource_policies: BTreeMap::new(),
120            destinations: BTreeMap::new(),
121            queries: BTreeMap::new(),
122            export_tasks: Vec::new(),
123            delivery_destinations: BTreeMap::new(),
124            delivery_sources: BTreeMap::new(),
125            deliveries: BTreeMap::new(),
126            query_definitions: BTreeMap::new(),
127            account_policies: BTreeMap::new(),
128            anomaly_detectors: BTreeMap::new(),
129            import_tasks: BTreeMap::new(),
130            integrations: BTreeMap::new(),
131            lookup_tables: BTreeMap::new(),
132            scheduled_queries: BTreeMap::new(),
133            s3_table_sources: BTreeMap::new(),
134            bearer_token_auth: BTreeMap::new(),
135            export_storage: BTreeMap::new(),
136            anomalies: BTreeMap::new(),
137            syslog_configurations: BTreeMap::new(),
138        }
139    }
140
141    pub fn reset(&mut self) {
142        self.log_groups.clear();
143        self.metric_filters.clear();
144        self.resource_policies.clear();
145        self.destinations.clear();
146        self.queries.clear();
147        self.export_tasks.clear();
148        self.delivery_destinations.clear();
149        self.delivery_sources.clear();
150        self.deliveries.clear();
151        self.query_definitions.clear();
152        self.account_policies.clear();
153        self.anomaly_detectors.clear();
154        self.import_tasks.clear();
155        self.integrations.clear();
156        self.lookup_tables.clear();
157        self.scheduled_queries.clear();
158        self.s3_table_sources.clear();
159        self.bearer_token_auth.clear();
160        self.export_storage.clear();
161        self.anomalies.clear();
162        self.syslog_configurations.clear();
163    }
164}
165
166#[derive(Clone, serde::Serialize, serde::Deserialize)]
167pub struct LogGroup {
168    pub name: String,
169    pub arn: String,
170    pub creation_time: i64,
171    pub retention_in_days: Option<i32>,
172    pub kms_key_id: Option<String>,
173    pub tags: BTreeMap<String, String>,
174    pub log_streams: BTreeMap<String, LogStream>,
175    pub stored_bytes: i64,
176    pub subscription_filters: Vec<SubscriptionFilter>,
177    pub data_protection_policy: Option<DataProtectionPolicy>,
178    pub index_policies: Vec<IndexPolicy>,
179    pub transformer: Option<Transformer>,
180    pub deletion_protection: bool,
181    /// `STANDARD` (default), `INFREQUENT_ACCESS`, or `DELIVERY`. Set at
182    /// creation time via `CreateLogGroup`'s `logGroupClass` parameter.
183    /// Tracked here so `DescribeLogGroups` round-trips it correctly.
184    pub log_group_class: Option<String>,
185}
186
187#[derive(Clone, serde::Serialize, serde::Deserialize)]
188pub struct LogStream {
189    pub name: String,
190    pub arn: String,
191    pub creation_time: i64,
192    pub first_event_timestamp: Option<i64>,
193    pub last_event_timestamp: Option<i64>,
194    pub last_ingestion_time: Option<i64>,
195    pub upload_sequence_token: String,
196    pub events: Vec<LogEvent>,
197}
198
199#[derive(Clone, serde::Serialize, serde::Deserialize)]
200pub struct LogEvent {
201    pub timestamp: i64,
202    pub message: String,
203    pub ingestion_time: i64,
204}
205
206#[derive(Clone, serde::Serialize, serde::Deserialize)]
207pub struct SubscriptionFilter {
208    pub filter_name: String,
209    pub log_group_name: String,
210    pub filter_pattern: String,
211    pub destination_arn: String,
212    pub role_arn: Option<String>,
213    pub distribution: String,
214    pub creation_time: i64,
215}
216
217#[derive(Clone, serde::Serialize, serde::Deserialize)]
218pub struct MetricFilter {
219    pub filter_name: String,
220    pub filter_pattern: String,
221    pub log_group_name: String,
222    pub metric_transformations: Vec<MetricTransformation>,
223    pub creation_time: i64,
224}
225
226#[derive(Clone, serde::Serialize, serde::Deserialize)]
227pub struct MetricTransformation {
228    pub metric_name: String,
229    pub metric_namespace: String,
230    pub metric_value: String,
231    pub default_value: Option<f64>,
232    /// CloudWatch unit for the published metric. AWS always reports it on
233    /// DescribeMetricFilters, defaulting to `None` when unset, and the
234    /// Terraform `aws_cloudwatch_log_metric_filter` resource asserts on it.
235    #[serde(default)]
236    pub unit: Option<String>,
237}
238
239#[derive(Clone, serde::Serialize, serde::Deserialize)]
240pub struct ResourcePolicy {
241    pub policy_name: String,
242    pub policy_document: String,
243    pub last_updated_time: i64,
244}
245
246#[derive(Clone, serde::Serialize, serde::Deserialize)]
247pub struct Destination {
248    pub destination_name: String,
249    pub target_arn: String,
250    pub role_arn: String,
251    pub arn: String,
252    pub access_policy: Option<String>,
253    pub creation_time: i64,
254    pub tags: BTreeMap<String, String>,
255}
256
257#[derive(Clone, serde::Serialize, serde::Deserialize)]
258pub struct QueryInfo {
259    pub query_id: String,
260    pub log_group_name: String,
261    /// Every log group / identifier referenced by this query, used by
262    /// `ListLogGroupsForQuery`. Always includes `log_group_name` plus any
263    /// names from `logGroupNames` / identifiers from `logGroupIdentifiers`
264    /// passed at start time.
265    #[serde(default)]
266    pub log_group_identifiers: Vec<String>,
267    pub query_string: String,
268    pub start_time: i64,
269    pub end_time: i64,
270    pub status: String,
271    pub create_time: i64,
272}
273
274#[derive(Clone, serde::Serialize, serde::Deserialize)]
275pub struct ExportTask {
276    pub task_id: String,
277    pub task_name: Option<String>,
278    pub log_group_name: String,
279    pub log_stream_name_prefix: Option<String>,
280    pub from_time: i64,
281    pub to_time: i64,
282    pub destination: String,
283    pub destination_prefix: String,
284    pub status_code: String,
285    pub status_message: String,
286    #[serde(default)]
287    pub creation_time: i64,
288    #[serde(default)]
289    pub completion_time: Option<i64>,
290}
291
292#[derive(Clone, serde::Serialize, serde::Deserialize)]
293pub struct DeliveryDestination {
294    pub name: String,
295    pub arn: String,
296    pub output_format: Option<String>,
297    pub delivery_destination_configuration: BTreeMap<String, String>,
298    /// `CWL`/`S3`/`FH`/`XRAY` — derived from the destination resource ARN when
299    /// the caller does not specify it. AWS always reports it on read.
300    #[serde(default)]
301    pub delivery_destination_type: String,
302    pub tags: BTreeMap<String, String>,
303    pub delivery_destination_policy: Option<String>,
304}
305
306#[derive(Clone, serde::Serialize, serde::Deserialize)]
307pub struct DeliverySource {
308    pub name: String,
309    pub arn: String,
310    pub resource_arns: Vec<String>,
311    pub service: String,
312    pub log_type: String,
313    pub tags: BTreeMap<String, String>,
314    #[serde(default)]
315    pub created_at: i64,
316}
317
318#[derive(Clone, serde::Serialize, serde::Deserialize)]
319pub struct Delivery {
320    pub id: String,
321    pub delivery_source_name: String,
322    pub delivery_destination_arn: String,
323    pub delivery_destination_type: String,
324    pub arn: String,
325    pub tags: BTreeMap<String, String>,
326    #[serde(default)]
327    pub field_delimiter: Option<String>,
328    #[serde(default)]
329    pub record_fields: Vec<String>,
330    #[serde(default)]
331    pub s3_delivery_configuration: Option<serde_json::Value>,
332    #[serde(default)]
333    pub created_at: i64,
334}
335
336#[derive(Clone, serde::Serialize, serde::Deserialize)]
337pub struct QueryDefinition {
338    pub query_definition_id: String,
339    pub name: String,
340    pub query_string: String,
341    pub log_group_names: Vec<String>,
342    pub last_modified: i64,
343}
344
345#[derive(Clone, serde::Serialize, serde::Deserialize)]
346pub struct AccountPolicy {
347    pub policy_name: String,
348    pub policy_type: String,
349    pub policy_document: String,
350    pub scope: Option<String>,
351    pub selection_criteria: Option<String>,
352    pub account_id: String,
353    pub last_updated_time: i64,
354}
355
356#[derive(Clone, serde::Serialize, serde::Deserialize)]
357pub struct DataProtectionPolicy {
358    pub policy_document: String,
359    pub last_updated_time: i64,
360}
361
362#[derive(Clone, serde::Serialize, serde::Deserialize)]
363pub struct IndexPolicy {
364    pub policy_name: String,
365    pub policy_document: String,
366    pub last_updated_time: i64,
367}
368
369#[derive(Clone, serde::Serialize, serde::Deserialize)]
370pub struct Transformer {
371    pub transformer_config: serde_json::Value,
372    pub creation_time: i64,
373    pub last_modified_time: i64,
374}
375
376#[derive(Clone, serde::Serialize, serde::Deserialize)]
377pub struct AnomalyDetector {
378    pub detector_name: String,
379    pub arn: String,
380    pub log_group_arn_list: Vec<String>,
381    pub evaluation_frequency: Option<String>,
382    pub filter_pattern: Option<String>,
383    pub anomaly_visibility_time: Option<i64>,
384    pub creation_time: i64,
385    pub last_modified_time: i64,
386    pub enabled: bool,
387    #[serde(default)]
388    pub tags: BTreeMap<String, String>,
389}
390
391#[derive(Clone, serde::Serialize, serde::Deserialize)]
392pub struct ImportTask {
393    pub import_id: String,
394    pub import_source_arn: String,
395    pub import_role_arn: String,
396    pub log_group_name: Option<String>,
397    pub status: String,
398    pub creation_time: i64,
399}
400
401#[derive(Clone, serde::Serialize, serde::Deserialize)]
402pub struct Integration {
403    pub integration_name: String,
404    pub integration_type: String,
405    pub resource_config: serde_json::Value,
406    pub status: String,
407    pub creation_time: i64,
408}
409
410#[derive(Clone, serde::Serialize, serde::Deserialize)]
411pub struct LookupTable {
412    pub lookup_table_name: String,
413    pub arn: String,
414    pub table_body: String,
415    pub creation_time: i64,
416    pub last_modified_time: i64,
417}
418
419#[derive(Clone, serde::Serialize, serde::Deserialize)]
420pub struct ScheduledQuery {
421    pub name: String,
422    pub arn: String,
423    pub query_string: String,
424    pub query_language: String,
425    pub schedule_expression: String,
426    pub execution_role_arn: String,
427    pub status: String,
428    pub creation_time: i64,
429    pub last_modified_time: i64,
430}
431
432/// On-disk snapshot envelope for CloudWatch Logs state. Versioned so
433/// format changes fail loudly on upgrade.
434#[derive(Clone, serde::Serialize, serde::Deserialize)]
435pub struct LogsSnapshot {
436    pub schema_version: u32,
437    #[serde(default)]
438    pub accounts: Option<fakecloud_core::multi_account::MultiAccountState<LogsState>>,
439    #[serde(default)]
440    pub state: Option<LogsState>,
441}
442
443pub const LOGS_SNAPSHOT_SCHEMA_VERSION: u32 = 2;
444
445#[cfg(test)]
446mod tests {
447    use super::*;
448
449    #[test]
450    fn new_initializes_empty() {
451        let state = LogsState::new("123456789012", "us-east-1");
452        assert_eq!(state.account_id, "123456789012");
453        assert_eq!(state.region, "us-east-1");
454        assert!(state.log_groups.is_empty());
455        assert!(state.queries.is_empty());
456    }
457
458    #[test]
459    fn reset_clears_state() {
460        let mut state = LogsState::new("123456789012", "us-east-1");
461        state.bearer_token_auth.insert("g".to_string(), true);
462        state.reset();
463        assert!(state.bearer_token_auth.is_empty());
464    }
465}