1use std::collections::BTreeMap;
2use std::sync::Arc;
3
4use chrono::{DateTime, Utc};
5use parking_lot::RwLock;
6use serde::{Deserialize, Serialize};
7use serde_json::Value;
8
9pub type SharedEcrState = Arc<RwLock<fakecloud_core::multi_account::MultiAccountState<EcrState>>>;
10
11impl fakecloud_core::multi_account::AccountState for EcrState {
12 fn new_for_account(account_id: &str, region: &str, _endpoint: &str) -> Self {
13 Self::new(account_id, region)
14 }
15}
16
17pub const ECR_SNAPSHOT_SCHEMA_VERSION: u32 = 4;
18
19#[derive(Clone, Debug, Serialize, Deserialize)]
23pub struct EcrSnapshot {
24 pub schema_version: u32,
25 pub accounts: Option<fakecloud_core::multi_account::MultiAccountState<EcrState>>,
26}
27
28#[derive(Clone, Debug, Default, Serialize, Deserialize)]
29pub struct EcrState {
30 pub account_id: String,
31 pub region: String,
32 pub repositories: BTreeMap<String, Repository>,
34 pub registry_policy: Option<String>,
37 pub registry_scanning_configuration: RegistryScanningConfiguration,
41 pub replication_configuration: Option<ReplicationConfiguration>,
43 pub account_settings: BTreeMap<String, String>,
46 #[serde(default)]
49 pub layer_uploads: BTreeMap<String, LayerUpload>,
50 #[serde(default)]
53 pub pull_time_exclusions: BTreeMap<String, PullTimeExclusion>,
54 #[serde(default)]
56 pub pull_through_cache_rules: BTreeMap<String, PullThroughCacheRule>,
57 #[serde(default)]
59 pub repository_creation_templates: BTreeMap<String, RepositoryCreationTemplate>,
60 #[serde(default)]
62 pub signing_configuration: Option<SigningConfiguration>,
63}
64
65impl EcrState {
66 pub fn new(account_id: &str, region: &str) -> Self {
67 Self {
68 account_id: account_id.to_string(),
69 region: region.to_string(),
70 repositories: BTreeMap::new(),
71 registry_policy: None,
72 registry_scanning_configuration: RegistryScanningConfiguration::default(),
73 replication_configuration: None,
74 account_settings: BTreeMap::new(),
75 layer_uploads: BTreeMap::new(),
76 pull_time_exclusions: BTreeMap::new(),
77 pull_through_cache_rules: BTreeMap::new(),
78 repository_creation_templates: BTreeMap::new(),
79 signing_configuration: None,
80 }
81 }
82
83 pub fn reset(&mut self) {
84 self.repositories.clear();
85 self.registry_policy = None;
86 self.registry_scanning_configuration = RegistryScanningConfiguration::default();
87 self.replication_configuration = None;
88 self.account_settings.clear();
89 self.layer_uploads.clear();
90 self.pull_time_exclusions.clear();
91 self.pull_through_cache_rules.clear();
92 self.repository_creation_templates.clear();
93 self.signing_configuration = None;
94 }
95
96 pub fn repository_arn(&self, repository_name: &str) -> String {
97 format!(
98 "arn:aws:ecr:{}:{}:repository/{}",
99 self.region, self.account_id, repository_name
100 )
101 }
102
103 pub fn registry_id(&self) -> &str {
104 &self.account_id
105 }
106}
107
108#[derive(Clone, Debug, Serialize, Deserialize)]
109pub struct Repository {
110 pub repository_name: String,
111 pub repository_arn: String,
112 pub registry_id: String,
113 pub repository_uri: String,
114 pub created_at: DateTime<Utc>,
115 pub image_tag_mutability: String,
116 pub image_scanning_configuration: ImageScanningConfiguration,
117 pub encryption_configuration: EncryptionConfiguration,
118 pub tags: BTreeMap<String, String>,
119 pub policy: Option<String>,
122 pub lifecycle_policy: Option<String>,
124 #[serde(default)]
130 pub lifecycle_policy_preview: Option<String>,
131 #[serde(default)]
136 pub lifecycle_policy_last_evaluated_at: Option<DateTime<Utc>>,
137 #[serde(default)]
139 pub scan_findings: BTreeMap<String, ImageScanFindings>,
140 #[serde(default)]
143 pub images: BTreeMap<String, Image>,
144 #[serde(default)]
147 pub image_tags: BTreeMap<String, String>,
148 #[serde(default)]
152 pub layers: BTreeMap<String, Layer>,
153 #[serde(default)]
156 pub replication_statuses: BTreeMap<String, Vec<ImageReplicationStatus>>,
157}
158
159#[derive(Clone, Debug, Serialize, Deserialize)]
164pub struct ImageReplicationStatus {
165 pub region: String,
166 pub registry_id: String,
167 pub status: String,
168 pub failure_code: Option<String>,
169 #[serde(default)]
170 pub failure_reason: Option<String>,
171}
172
173impl Repository {
174 pub fn new(
175 repository_name: &str,
176 repository_arn: String,
177 registry_id: &str,
178 endpoint: &str,
179 ) -> Self {
180 let host = endpoint
182 .trim_start_matches("http://")
183 .trim_start_matches("https://")
184 .trim_end_matches('/')
185 .to_string();
186 Self {
187 repository_name: repository_name.to_string(),
188 repository_arn,
189 registry_id: registry_id.to_string(),
190 repository_uri: format!("{host}/{repository_name}"),
191 created_at: Utc::now(),
192 image_tag_mutability: "MUTABLE".to_string(),
193 image_scanning_configuration: ImageScanningConfiguration::default(),
194 encryption_configuration: EncryptionConfiguration::default(),
195 tags: BTreeMap::new(),
196 policy: None,
197 lifecycle_policy: None,
198 lifecycle_policy_preview: None,
199 lifecycle_policy_last_evaluated_at: None,
200 scan_findings: BTreeMap::new(),
201 images: BTreeMap::new(),
202 image_tags: BTreeMap::new(),
203 layers: BTreeMap::new(),
204 replication_statuses: BTreeMap::new(),
205 }
206 }
207}
208
209#[derive(Clone, Debug, Serialize, Deserialize)]
210pub struct PullTimeExclusion {
211 pub principal_arn: String,
212 pub registered_at: DateTime<Utc>,
213}
214
215#[derive(Clone, Debug, Serialize, Deserialize)]
216pub struct ImageScanFindings {
217 pub image_digest: String,
218 pub scan_status: String,
219 pub scan_completed_at: Option<DateTime<Utc>>,
220 pub vulnerability_source_updated_at: Option<DateTime<Utc>>,
221 pub finding_severity_counts: BTreeMap<String, i64>,
222 pub findings: Vec<Value>,
223}
224
225#[derive(Clone, Debug, Serialize, Deserialize)]
226pub struct PullThroughCacheRule {
227 pub ecr_repository_prefix: String,
228 pub upstream_registry_url: String,
229 pub upstream_registry: Option<String>,
230 pub credential_arn: Option<String>,
231 pub created_at: DateTime<Utc>,
232 pub updated_at: DateTime<Utc>,
233 pub custom_role_arn: Option<String>,
234}
235
236#[derive(Clone, Debug, Serialize, Deserialize)]
237pub struct RepositoryCreationTemplate {
238 pub prefix: String,
239 pub description: Option<String>,
240 pub image_tag_mutability: String,
241 pub applied_for: Vec<String>,
242 pub resource_tags: Vec<Value>,
243 pub created_at: DateTime<Utc>,
244 pub updated_at: DateTime<Utc>,
245 pub custom_role_arn: Option<String>,
246 pub repository_policy: Option<String>,
247 pub lifecycle_policy: Option<String>,
248 pub encryption_configuration: Option<EncryptionConfiguration>,
249}
250
251#[derive(Clone, Debug, Default, Serialize, Deserialize)]
252pub struct SigningConfiguration {
253 pub rules: Vec<Value>,
257 #[serde(default)]
262 pub trusted_keys: Vec<crate::signing::TrustedKey>,
263}
264
265#[derive(Clone, Debug, Serialize, Deserialize)]
266pub struct Image {
267 pub image_digest: String,
268 pub image_manifest: String,
269 pub image_manifest_media_type: String,
270 pub artifact_media_type: Option<String>,
271 pub image_size_in_bytes: u64,
272 pub image_pushed_at: DateTime<Utc>,
273 pub last_recorded_pull_time: Option<DateTime<Utc>>,
274 #[serde(default = "default_image_status")]
279 pub image_status: String,
280 #[serde(default)]
284 pub last_archived_at: Option<DateTime<Utc>>,
285 #[serde(default)]
289 pub last_activated_at: Option<DateTime<Utc>>,
290 #[serde(default)]
296 pub last_in_use_at: Option<DateTime<Utc>>,
297 #[serde(default)]
303 pub in_use_count: u64,
304}
305
306fn default_image_status() -> String {
307 "ACTIVE".to_string()
308}
309
310#[derive(Clone, Debug, Serialize, Deserialize)]
311pub struct Layer {
312 pub digest: String,
313 pub size: u64,
314 pub blob_b64: String,
320 pub media_type: String,
321 #[serde(default)]
327 pub encrypted_with_kms_key: Option<String>,
328}
329
330#[derive(Clone, Debug, Serialize, Deserialize)]
331pub struct LayerUpload {
332 pub upload_id: String,
333 pub repository_name: String,
334 pub created_at: DateTime<Utc>,
335 #[serde(default)]
343 pub spool_path: String,
344 pub last_byte_received: u64,
345}
346
347#[derive(Clone, Debug, Default, Serialize, Deserialize)]
348pub struct ImageScanningConfiguration {
349 pub scan_on_push: bool,
351}
352
353#[derive(Clone, Debug, Serialize, Deserialize)]
354pub struct EncryptionConfiguration {
355 pub encryption_type: String,
357 pub kms_key: Option<String>,
359}
360
361impl Default for EncryptionConfiguration {
362 fn default() -> Self {
363 Self {
364 encryption_type: "AES256".to_string(),
365 kms_key: None,
366 }
367 }
368}
369
370#[derive(Clone, Debug, Serialize, Deserialize)]
371pub struct RegistryScanningConfiguration {
372 pub scan_type: String,
374 pub rules: Vec<RegistryScanningRule>,
375}
376
377impl Default for RegistryScanningConfiguration {
378 fn default() -> Self {
379 Self {
380 scan_type: "BASIC".to_string(),
381 rules: Vec::new(),
382 }
383 }
384}
385
386#[derive(Clone, Debug, Serialize, Deserialize)]
387pub struct RegistryScanningRule {
388 pub scan_frequency: String,
389 pub repository_filters: Vec<RepositoryFilter>,
390}
391
392#[derive(Clone, Debug, Serialize, Deserialize)]
393pub struct RepositoryFilter {
394 pub filter: String,
395 pub filter_type: String,
396}
397
398#[derive(Clone, Debug, Serialize, Deserialize)]
399pub struct ReplicationConfiguration {
400 pub rules: Vec<ReplicationRule>,
401}
402
403#[derive(Clone, Debug, Serialize, Deserialize)]
404pub struct ReplicationRule {
405 pub destinations: Vec<ReplicationDestination>,
406 pub repository_filters: Vec<RepositoryFilter>,
407}
408
409#[derive(Clone, Debug, Serialize, Deserialize)]
410pub struct ReplicationDestination {
411 pub region: String,
412 pub registry_id: String,
413}