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)]
129 pub lifecycle_policy_last_evaluated_at: Option<DateTime<Utc>>,
130 #[serde(default)]
132 pub scan_findings: BTreeMap<String, ImageScanFindings>,
133 #[serde(default)]
136 pub images: BTreeMap<String, Image>,
137 #[serde(default)]
140 pub image_tags: BTreeMap<String, String>,
141 #[serde(default)]
145 pub layers: BTreeMap<String, Layer>,
146 #[serde(default)]
149 pub replication_statuses: BTreeMap<String, Vec<ImageReplicationStatus>>,
150}
151
152#[derive(Clone, Debug, Serialize, Deserialize)]
157pub struct ImageReplicationStatus {
158 pub region: String,
159 pub registry_id: String,
160 pub status: String,
161 pub failure_code: Option<String>,
162 #[serde(default)]
163 pub failure_reason: Option<String>,
164}
165
166impl Repository {
167 pub fn new(
168 repository_name: &str,
169 repository_arn: String,
170 registry_id: &str,
171 endpoint: &str,
172 ) -> Self {
173 let host = endpoint
175 .trim_start_matches("http://")
176 .trim_start_matches("https://")
177 .trim_end_matches('/')
178 .to_string();
179 Self {
180 repository_name: repository_name.to_string(),
181 repository_arn,
182 registry_id: registry_id.to_string(),
183 repository_uri: format!("{host}/{repository_name}"),
184 created_at: Utc::now(),
185 image_tag_mutability: "MUTABLE".to_string(),
186 image_scanning_configuration: ImageScanningConfiguration::default(),
187 encryption_configuration: EncryptionConfiguration::default(),
188 tags: BTreeMap::new(),
189 policy: None,
190 lifecycle_policy: None,
191 lifecycle_policy_last_evaluated_at: None,
192 scan_findings: BTreeMap::new(),
193 images: BTreeMap::new(),
194 image_tags: BTreeMap::new(),
195 layers: BTreeMap::new(),
196 replication_statuses: BTreeMap::new(),
197 }
198 }
199}
200
201#[derive(Clone, Debug, Serialize, Deserialize)]
202pub struct PullTimeExclusion {
203 pub principal_arn: String,
204 pub registered_at: DateTime<Utc>,
205}
206
207#[derive(Clone, Debug, Serialize, Deserialize)]
208pub struct ImageScanFindings {
209 pub image_digest: String,
210 pub scan_status: String,
211 pub scan_completed_at: Option<DateTime<Utc>>,
212 pub vulnerability_source_updated_at: Option<DateTime<Utc>>,
213 pub finding_severity_counts: BTreeMap<String, i64>,
214 pub findings: Vec<Value>,
215}
216
217#[derive(Clone, Debug, Serialize, Deserialize)]
218pub struct PullThroughCacheRule {
219 pub ecr_repository_prefix: String,
220 pub upstream_registry_url: String,
221 pub upstream_registry: Option<String>,
222 pub credential_arn: Option<String>,
223 pub created_at: DateTime<Utc>,
224 pub updated_at: DateTime<Utc>,
225 pub custom_role_arn: Option<String>,
226}
227
228#[derive(Clone, Debug, Serialize, Deserialize)]
229pub struct RepositoryCreationTemplate {
230 pub prefix: String,
231 pub description: Option<String>,
232 pub image_tag_mutability: String,
233 pub applied_for: Vec<String>,
234 pub resource_tags: Vec<Value>,
235 pub created_at: DateTime<Utc>,
236 pub updated_at: DateTime<Utc>,
237 pub custom_role_arn: Option<String>,
238 pub repository_policy: Option<String>,
239 pub lifecycle_policy: Option<String>,
240 pub encryption_configuration: Option<EncryptionConfiguration>,
241}
242
243#[derive(Clone, Debug, Default, Serialize, Deserialize)]
244pub struct SigningConfiguration {
245 pub rules: Vec<Value>,
249 #[serde(default)]
254 pub trusted_keys: Vec<crate::signing::TrustedKey>,
255}
256
257#[derive(Clone, Debug, Serialize, Deserialize)]
258pub struct Image {
259 pub image_digest: String,
260 pub image_manifest: String,
261 pub image_manifest_media_type: String,
262 pub artifact_media_type: Option<String>,
263 pub image_size_in_bytes: u64,
264 pub image_pushed_at: DateTime<Utc>,
265 pub last_recorded_pull_time: Option<DateTime<Utc>>,
266 #[serde(default = "default_image_status")]
271 pub image_status: String,
272 #[serde(default)]
276 pub last_archived_at: Option<DateTime<Utc>>,
277 #[serde(default)]
281 pub last_activated_at: Option<DateTime<Utc>>,
282 #[serde(default)]
288 pub last_in_use_at: Option<DateTime<Utc>>,
289 #[serde(default)]
295 pub in_use_count: u64,
296}
297
298fn default_image_status() -> String {
299 "ACTIVE".to_string()
300}
301
302#[derive(Clone, Debug, Serialize, Deserialize)]
303pub struct Layer {
304 pub digest: String,
305 pub size: u64,
306 pub blob_b64: String,
312 pub media_type: String,
313 #[serde(default)]
319 pub encrypted_with_kms_key: Option<String>,
320}
321
322#[derive(Clone, Debug, Serialize, Deserialize)]
323pub struct LayerUpload {
324 pub upload_id: String,
325 pub repository_name: String,
326 pub created_at: DateTime<Utc>,
327 #[serde(default)]
335 pub spool_path: String,
336 pub last_byte_received: u64,
337}
338
339#[derive(Clone, Debug, Default, Serialize, Deserialize)]
340pub struct ImageScanningConfiguration {
341 pub scan_on_push: bool,
343}
344
345#[derive(Clone, Debug, Serialize, Deserialize)]
346pub struct EncryptionConfiguration {
347 pub encryption_type: String,
349 pub kms_key: Option<String>,
351}
352
353impl Default for EncryptionConfiguration {
354 fn default() -> Self {
355 Self {
356 encryption_type: "AES256".to_string(),
357 kms_key: None,
358 }
359 }
360}
361
362#[derive(Clone, Debug, Serialize, Deserialize)]
363pub struct RegistryScanningConfiguration {
364 pub scan_type: String,
366 pub rules: Vec<RegistryScanningRule>,
367}
368
369impl Default for RegistryScanningConfiguration {
370 fn default() -> Self {
371 Self {
372 scan_type: "BASIC".to_string(),
373 rules: Vec::new(),
374 }
375 }
376}
377
378#[derive(Clone, Debug, Serialize, Deserialize)]
379pub struct RegistryScanningRule {
380 pub scan_frequency: String,
381 pub repository_filters: Vec<RepositoryFilter>,
382}
383
384#[derive(Clone, Debug, Serialize, Deserialize)]
385pub struct RepositoryFilter {
386 pub filter: String,
387 pub filter_type: String,
388}
389
390#[derive(Clone, Debug, Serialize, Deserialize)]
391pub struct ReplicationConfiguration {
392 pub rules: Vec<ReplicationRule>,
393}
394
395#[derive(Clone, Debug, Serialize, Deserialize)]
396pub struct ReplicationRule {
397 pub destinations: Vec<ReplicationDestination>,
398 pub repository_filters: Vec<RepositoryFilter>,
399}
400
401#[derive(Clone, Debug, Serialize, Deserialize)]
402pub struct ReplicationDestination {
403 pub region: String,
404 pub registry_id: String,
405}