Skip to main content

rusticity_core/
ecr.rs

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