Skip to main content

rusticity_core/
ecr.rs

1pub mod detail;
2pub mod scan;
3use crate::config::AwsConfig;
4use anyhow::Result;
5
6#[derive(Clone, Debug)]
7pub struct EcrRepository {
8    pub name: String,
9    pub uri: String,
10    pub created_at: String,
11    pub tag_immutability: String,
12    pub encryption_type: String,
13}
14
15#[derive(Clone, Debug)]
16pub struct EcrImage {
17    pub tag: String,
18    pub artifact_type: String,
19    pub pushed_at: String,
20    pub size_bytes: i64,
21    pub uri: String,
22    pub digest: String,
23    pub last_pull_time: String,
24}
25
26pub struct EcrClient {
27    config: AwsConfig,
28}
29
30impl EcrClient {
31    pub fn new(config: AwsConfig) -> Self {
32        Self { config }
33    }
34
35    pub async fn list_private_repositories(&self) -> Result<Vec<EcrRepository>> {
36        let client = self.config.ecr_client();
37
38        let mut repositories = Vec::new();
39        let mut next_token: Option<String> = None;
40
41        loop {
42            let mut request = client.describe_repositories();
43            if let Some(token) = next_token {
44                request = request.next_token(token);
45            }
46
47            let response = request.send().await?;
48
49            if let Some(repos) = response.repositories {
50                for repo in repos {
51                    repositories.push(EcrRepository {
52                        name: repo.repository_name.unwrap_or_default(),
53                        uri: repo.repository_uri.unwrap_or_default(),
54                        created_at: repo
55                            .created_at
56                            .map(|dt| {
57                                dt.fmt(aws_smithy_types::date_time::Format::DateTime)
58                                    .unwrap_or_default()
59                            })
60                            .unwrap_or_default(),
61                        tag_immutability: repo
62                            .image_tag_mutability
63                            .map(|m| format!("{:?}", m))
64                            .unwrap_or_default(),
65                        encryption_type: repo
66                            .encryption_configuration
67                            .map(|e| {
68                                let enc_type = format!("{:?}", e.encryption_type);
69                                match enc_type.as_str() {
70                                    "Aes256" => "AES256".to_string(),
71                                    _ => enc_type,
72                                }
73                            })
74                            .unwrap_or_default(),
75                    });
76                }
77            }
78
79            next_token = response.next_token;
80            if next_token.is_none() {
81                break;
82            }
83        }
84
85        Ok(repositories)
86    }
87
88    pub async fn list_public_repositories(&self) -> Result<Vec<EcrRepository>> {
89        let client = self.config.ecr_public_client();
90
91        let mut repositories = Vec::new();
92        let mut next_token: Option<String> = None;
93
94        loop {
95            let mut request = client.describe_repositories();
96            if let Some(token) = next_token {
97                request = request.next_token(token);
98            }
99
100            let response = request.send().await?;
101
102            if let Some(repos) = response.repositories {
103                for repo in repos {
104                    repositories.push(EcrRepository {
105                        name: repo.repository_name.unwrap_or_default(),
106                        uri: repo.repository_uri.unwrap_or_default(),
107                        created_at: repo
108                            .created_at
109                            .map(|dt| {
110                                dt.fmt(aws_smithy_types::date_time::Format::DateTime)
111                                    .unwrap_or_default()
112                            })
113                            .unwrap_or_default(),
114                        tag_immutability: String::new(),
115                        encryption_type: String::new(),
116                    });
117                }
118            }
119
120            next_token = response.next_token;
121            if next_token.is_none() {
122                break;
123            }
124        }
125
126        Ok(repositories)
127    }
128
129    pub async fn list_images(
130        &self,
131        repository_name: &str,
132        repository_uri: &str,
133    ) -> Result<Vec<EcrImage>> {
134        let client = self.config.ecr_client();
135
136        let mut images = Vec::new();
137        let mut next_token: Option<String> = None;
138
139        loop {
140            let mut request = client.describe_images().repository_name(repository_name);
141            if let Some(token) = next_token {
142                request = request.next_token(token);
143            }
144
145            let response = request.send().await?;
146
147            if let Some(image_details) = response.image_details {
148                for image in image_details {
149                    let tags = image.image_tags.unwrap_or_default();
150                    let tag = tags
151                        .first()
152                        .cloned()
153                        .unwrap_or_else(|| "<untagged>".to_string());
154
155                    let size_bytes = image.image_size_in_bytes.unwrap_or(0);
156
157                    let uri = format!("{}:{}", repository_uri, tag);
158
159                    images.push(EcrImage {
160                        tag,
161                        artifact_type: image.artifact_media_type.unwrap_or_default(),
162                        pushed_at: image
163                            .image_pushed_at
164                            .map(|dt| {
165                                dt.fmt(aws_smithy_types::date_time::Format::DateTime)
166                                    .unwrap_or_default()
167                            })
168                            .unwrap_or_default(),
169                        size_bytes,
170                        uri,
171                        digest: image.image_digest.unwrap_or_default(),
172                        last_pull_time: image
173                            .last_recorded_pull_time
174                            .map(|dt| {
175                                dt.fmt(aws_smithy_types::date_time::Format::DateTime)
176                                    .unwrap_or_default()
177                            })
178                            .unwrap_or_default(),
179                    });
180                }
181            }
182
183            next_token = response.next_token;
184            if next_token.is_none() {
185                break;
186            }
187        }
188
189        Ok(images)
190    }
191}
192
193#[cfg(test)]
194mod tests {
195    #[test]
196    fn test_ecr_image_uri_format() {
197        let repository_uri = "123456789012.dkr.ecr.us-east-1.amazonaws.com/my-repo";
198        let tag = "v1.0.0";
199        let expected_uri = "123456789012.dkr.ecr.us-east-1.amazonaws.com/my-repo:v1.0.0";
200
201        let uri = format!("{}:{}", repository_uri, tag);
202        assert_eq!(uri, expected_uri);
203    }
204
205    #[test]
206    fn test_ecr_image_uri_with_untagged() {
207        let repository_uri = "123456789012.dkr.ecr.us-east-1.amazonaws.com/my-repo";
208        let tag = "<untagged>";
209        let expected_uri = "123456789012.dkr.ecr.us-east-1.amazonaws.com/my-repo:<untagged>";
210
211        let uri = format!("{}:{}", repository_uri, tag);
212        assert_eq!(uri, expected_uri);
213    }
214
215    #[test]
216    fn test_encryption_type_aes256_formatting() {
217        let enc_type = "Aes256";
218        let formatted = match enc_type {
219            "Aes256" => "AES256".to_string(),
220            _ => enc_type.to_string(),
221        };
222        assert_eq!(formatted, "AES256");
223    }
224
225    #[test]
226    fn test_encryption_type_kms_unchanged() {
227        let enc_type = "Kms";
228        let formatted = match enc_type {
229            "Aes256" => "AES256".to_string(),
230            _ => enc_type.to_string(),
231        };
232        assert_eq!(formatted, "Kms");
233    }
234}