Skip to main content

rustack_cloudfront_core/
store.rs

1//! In-memory store for CloudFront resources.
2//!
3//! Each resource kind is a `DashMap` keyed by ID. Tags are keyed by ARN so a
4//! single `TagResource` / `UntagResource` implementation works across every
5//! taggable resource kind.
6
7use std::{hash::Hash, sync::Arc};
8
9use dashmap::DashMap;
10use rustack_cloudfront_model::{
11    CachePolicy, CloudFrontFunction, CloudFrontOriginAccessIdentity, Distribution,
12    FieldLevelEncryption, FieldLevelEncryptionProfile, Invalidation, KeyGroup, KeyValueStore,
13    MonitoringSubscription, OriginAccessControl, OriginRequestPolicy, PublicKey, RealtimeLogConfig,
14    ResponseHeadersPolicy, TagSet,
15};
16use serde::{Deserialize, Serialize};
17
18/// In-memory store for all CloudFront resource kinds.
19#[derive(Debug, Default)]
20pub struct CloudFrontStore {
21    /// Distributions, keyed by distribution ID.
22    pub distributions: DashMap<String, Distribution>,
23    /// Invalidations, keyed by `(distribution_id, invalidation_id)`.
24    pub invalidations: DashMap<(String, String), Invalidation>,
25    /// Origin access controls.
26    pub origin_access_controls: DashMap<String, OriginAccessControl>,
27    /// Origin access identities (legacy).
28    pub origin_access_identities: DashMap<String, CloudFrontOriginAccessIdentity>,
29    /// Cache policies (managed + customer).
30    pub cache_policies: DashMap<String, CachePolicy>,
31    /// Origin request policies (managed + customer).
32    pub origin_request_policies: DashMap<String, OriginRequestPolicy>,
33    /// Response headers policies (managed + customer).
34    pub response_headers_policies: DashMap<String, ResponseHeadersPolicy>,
35    /// Key groups.
36    pub key_groups: DashMap<String, KeyGroup>,
37    /// Public keys.
38    pub public_keys: DashMap<String, PublicKey>,
39    /// CloudFront Functions, keyed by function name.
40    pub functions: DashMap<String, CloudFrontFunction>,
41    /// Field-level encryption configs.
42    pub fle_configs: DashMap<String, FieldLevelEncryption>,
43    /// Field-level encryption profiles.
44    pub fle_profiles: DashMap<String, FieldLevelEncryptionProfile>,
45    /// Monitoring subscriptions, keyed by distribution ID.
46    pub monitoring_subscriptions: DashMap<String, MonitoringSubscription>,
47    /// Key-value stores.
48    pub key_value_stores: DashMap<String, KeyValueStore>,
49    /// Realtime log configs, keyed by name.
50    pub realtime_log_configs: DashMap<String, RealtimeLogConfig>,
51    /// Tag sets, keyed by resource ARN.
52    pub tags: DashMap<String, TagSet>,
53}
54
55impl CloudFrontStore {
56    /// Create a new empty store wrapped in `Arc`.
57    #[must_use]
58    pub fn new() -> Arc<Self> {
59        Arc::new(Self::default())
60    }
61
62    /// Export all CloudFront resources in deterministic order.
63    #[must_use]
64    pub fn export_snapshot(&self) -> CloudFrontStoreSnapshot {
65        CloudFrontStoreSnapshot {
66            distributions: sorted_values(&self.distributions, |value| value.id.clone()),
67            invalidations: sorted_values(&self.invalidations, |value| {
68                (value.distribution_id.clone(), value.id.clone())
69            }),
70            origin_access_controls: sorted_values(&self.origin_access_controls, |value| {
71                value.id.clone()
72            }),
73            origin_access_identities: sorted_values(&self.origin_access_identities, |value| {
74                value.id.clone()
75            }),
76            cache_policies: sorted_values(&self.cache_policies, |value| value.id.clone()),
77            origin_request_policies: sorted_values(&self.origin_request_policies, |value| {
78                value.id.clone()
79            }),
80            response_headers_policies: sorted_values(&self.response_headers_policies, |value| {
81                value.id.clone()
82            }),
83            key_groups: sorted_values(&self.key_groups, |value| value.id.clone()),
84            public_keys: sorted_values(&self.public_keys, |value| value.id.clone()),
85            functions: sorted_values(&self.functions, |value| value.name.clone()),
86            fle_configs: sorted_values(&self.fle_configs, |value| value.id.clone()),
87            fle_profiles: sorted_values(&self.fle_profiles, |value| value.id.clone()),
88            monitoring_subscriptions: sorted_values(&self.monitoring_subscriptions, |value| {
89                value.distribution_id.clone()
90            }),
91            key_value_stores: sorted_values(&self.key_value_stores, |value| value.name.clone()),
92            realtime_log_configs: sorted_values(&self.realtime_log_configs, |value| {
93                value.name.clone()
94            }),
95            tags: sorted_key_values(&self.tags),
96        }
97    }
98
99    /// Replace all CloudFront resources with snapshot contents.
100    pub fn import_snapshot(&self, snapshot: CloudFrontStoreSnapshot) {
101        self.distributions.clear();
102        self.invalidations.clear();
103        self.origin_access_controls.clear();
104        self.origin_access_identities.clear();
105        self.cache_policies.clear();
106        self.origin_request_policies.clear();
107        self.response_headers_policies.clear();
108        self.key_groups.clear();
109        self.public_keys.clear();
110        self.functions.clear();
111        self.fle_configs.clear();
112        self.fle_profiles.clear();
113        self.monitoring_subscriptions.clear();
114        self.key_value_stores.clear();
115        self.realtime_log_configs.clear();
116        self.tags.clear();
117
118        for value in snapshot.distributions {
119            self.distributions.insert(value.id.clone(), value);
120        }
121        for value in snapshot.invalidations {
122            self.invalidations
123                .insert((value.distribution_id.clone(), value.id.clone()), value);
124        }
125        for value in snapshot.origin_access_controls {
126            self.origin_access_controls.insert(value.id.clone(), value);
127        }
128        for value in snapshot.origin_access_identities {
129            self.origin_access_identities
130                .insert(value.id.clone(), value);
131        }
132        for value in snapshot.cache_policies {
133            self.cache_policies.insert(value.id.clone(), value);
134        }
135        for value in snapshot.origin_request_policies {
136            self.origin_request_policies.insert(value.id.clone(), value);
137        }
138        for value in snapshot.response_headers_policies {
139            self.response_headers_policies
140                .insert(value.id.clone(), value);
141        }
142        for value in snapshot.key_groups {
143            self.key_groups.insert(value.id.clone(), value);
144        }
145        for value in snapshot.public_keys {
146            self.public_keys.insert(value.id.clone(), value);
147        }
148        for value in snapshot.functions {
149            self.functions.insert(value.name.clone(), value);
150        }
151        for value in snapshot.fle_configs {
152            self.fle_configs.insert(value.id.clone(), value);
153        }
154        for value in snapshot.fle_profiles {
155            self.fle_profiles.insert(value.id.clone(), value);
156        }
157        for value in snapshot.monitoring_subscriptions {
158            self.monitoring_subscriptions
159                .insert(value.distribution_id.clone(), value);
160        }
161        for value in snapshot.key_value_stores {
162            self.key_value_stores.insert(value.name.clone(), value);
163        }
164        for value in snapshot.realtime_log_configs {
165            self.realtime_log_configs.insert(value.name.clone(), value);
166        }
167        for (arn, tags) in snapshot.tags {
168            self.tags.insert(arn, tags);
169        }
170    }
171}
172
173/// Serializable CloudFront store snapshot.
174#[derive(Debug, Clone, Default, Serialize, Deserialize)]
175#[serde(rename_all = "camelCase")]
176pub struct CloudFrontStoreSnapshot {
177    /// Distributions.
178    pub distributions: Vec<Distribution>,
179    /// Invalidations.
180    pub invalidations: Vec<Invalidation>,
181    /// Origin access controls.
182    pub origin_access_controls: Vec<OriginAccessControl>,
183    /// Legacy origin access identities.
184    pub origin_access_identities: Vec<CloudFrontOriginAccessIdentity>,
185    /// Cache policies.
186    pub cache_policies: Vec<CachePolicy>,
187    /// Origin request policies.
188    pub origin_request_policies: Vec<OriginRequestPolicy>,
189    /// Response headers policies.
190    pub response_headers_policies: Vec<ResponseHeadersPolicy>,
191    /// Key groups.
192    pub key_groups: Vec<KeyGroup>,
193    /// Public keys.
194    pub public_keys: Vec<PublicKey>,
195    /// CloudFront Functions.
196    pub functions: Vec<CloudFrontFunction>,
197    /// Field-level encryption configs.
198    pub fle_configs: Vec<FieldLevelEncryption>,
199    /// Field-level encryption profiles.
200    pub fle_profiles: Vec<FieldLevelEncryptionProfile>,
201    /// Monitoring subscriptions.
202    pub monitoring_subscriptions: Vec<MonitoringSubscription>,
203    /// Key-value stores.
204    pub key_value_stores: Vec<KeyValueStore>,
205    /// Realtime log configs.
206    pub realtime_log_configs: Vec<RealtimeLogConfig>,
207    /// Resource tags keyed by ARN.
208    pub tags: Vec<(String, TagSet)>,
209}
210
211fn sorted_values<K, T, F, O>(map: &DashMap<K, T>, key_fn: F) -> Vec<T>
212where
213    K: Eq + Hash,
214    T: Clone,
215    F: Fn(&T) -> O,
216    O: Ord,
217{
218    let mut values: Vec<T> = map.iter().map(|entry| entry.value().clone()).collect();
219    values.sort_by_key(key_fn);
220    values
221}
222
223fn sorted_key_values<T>(map: &DashMap<String, T>) -> Vec<(String, T)>
224where
225    T: Clone,
226{
227    let mut values: Vec<(String, T)> = map
228        .iter()
229        .map(|entry| (entry.key().clone(), entry.value().clone()))
230        .collect();
231    values.sort_by(|left, right| left.0.cmp(&right.0));
232    values
233}