rusticity-core 0.1.6

Core AWS SDK integration for Rusticity
Documentation
use crate::config::AwsConfig;
use anyhow::Result;

#[derive(Clone, Debug)]
pub struct EcrRepository {
    pub name: String,
    pub uri: String,
    pub created_at: String,
    pub tag_immutability: String,
    pub encryption_type: String,
}

#[derive(Clone, Debug)]
pub struct EcrImage {
    pub tag: String,
    pub artifact_type: String,
    pub pushed_at: String,
    pub size_bytes: i64,
    pub uri: String,
    pub digest: String,
    pub last_pull_time: String,
}

pub struct EcrClient {
    config: AwsConfig,
}

impl EcrClient {
    pub fn new(config: AwsConfig) -> Self {
        Self { config }
    }

    pub async fn list_private_repositories(&self) -> Result<Vec<EcrRepository>> {
        let client = self.config.ecr_client();

        let mut repositories = Vec::new();
        let mut next_token: Option<String> = None;

        loop {
            let mut request = client.describe_repositories();
            if let Some(token) = next_token {
                request = request.next_token(token);
            }

            let response = request.send().await?;

            if let Some(repos) = response.repositories {
                for repo in repos {
                    repositories.push(EcrRepository {
                        name: repo.repository_name.unwrap_or_default(),
                        uri: repo.repository_uri.unwrap_or_default(),
                        created_at: repo
                            .created_at
                            .map(|dt| {
                                dt.fmt(aws_smithy_types::date_time::Format::DateTime)
                                    .unwrap_or_default()
                            })
                            .unwrap_or_default(),
                        tag_immutability: repo
                            .image_tag_mutability
                            .map(|m| format!("{:?}", m))
                            .unwrap_or_default(),
                        encryption_type: repo
                            .encryption_configuration
                            .map(|e| {
                                let enc_type = format!("{:?}", e.encryption_type);
                                match enc_type.as_str() {
                                    "Aes256" => "AES256".to_string(),
                                    _ => enc_type,
                                }
                            })
                            .unwrap_or_default(),
                    });
                }
            }

            next_token = response.next_token;
            if next_token.is_none() {
                break;
            }
        }

        Ok(repositories)
    }

    pub async fn list_public_repositories(&self) -> Result<Vec<EcrRepository>> {
        let client = self.config.ecr_public_client();

        let mut repositories = Vec::new();
        let mut next_token: Option<String> = None;

        loop {
            let mut request = client.describe_repositories();
            if let Some(token) = next_token {
                request = request.next_token(token);
            }

            let response = request.send().await?;

            if let Some(repos) = response.repositories {
                for repo in repos {
                    repositories.push(EcrRepository {
                        name: repo.repository_name.unwrap_or_default(),
                        uri: repo.repository_uri.unwrap_or_default(),
                        created_at: repo
                            .created_at
                            .map(|dt| {
                                dt.fmt(aws_smithy_types::date_time::Format::DateTime)
                                    .unwrap_or_default()
                            })
                            .unwrap_or_default(),
                        tag_immutability: String::new(),
                        encryption_type: String::new(),
                    });
                }
            }

            next_token = response.next_token;
            if next_token.is_none() {
                break;
            }
        }

        Ok(repositories)
    }

    pub async fn list_images(
        &self,
        repository_name: &str,
        repository_uri: &str,
    ) -> Result<Vec<EcrImage>> {
        let client = self.config.ecr_client();

        let mut images = Vec::new();
        let mut next_token: Option<String> = None;

        loop {
            let mut request = client.describe_images().repository_name(repository_name);
            if let Some(token) = next_token {
                request = request.next_token(token);
            }

            let response = request.send().await?;

            if let Some(image_details) = response.image_details {
                for image in image_details {
                    let tags = image.image_tags.unwrap_or_default();
                    let tag = tags
                        .first()
                        .cloned()
                        .unwrap_or_else(|| "<untagged>".to_string());

                    let size_bytes = image.image_size_in_bytes.unwrap_or(0);

                    let uri = format!("{}:{}", repository_uri, tag);

                    images.push(EcrImage {
                        tag,
                        artifact_type: image.artifact_media_type.unwrap_or_default(),
                        pushed_at: image
                            .image_pushed_at
                            .map(|dt| {
                                dt.fmt(aws_smithy_types::date_time::Format::DateTime)
                                    .unwrap_or_default()
                            })
                            .unwrap_or_default(),
                        size_bytes,
                        uri,
                        digest: image.image_digest.unwrap_or_default(),
                        last_pull_time: image
                            .last_recorded_pull_time
                            .map(|dt| {
                                dt.fmt(aws_smithy_types::date_time::Format::DateTime)
                                    .unwrap_or_default()
                            })
                            .unwrap_or_default(),
                    });
                }
            }

            next_token = response.next_token;
            if next_token.is_none() {
                break;
            }
        }

        Ok(images)
    }
}

#[cfg(test)]
mod tests {
    #[test]
    fn test_ecr_image_uri_format() {
        let repository_uri = "123456789012.dkr.ecr.us-east-1.amazonaws.com/my-repo";
        let tag = "v1.0.0";
        let expected_uri = "123456789012.dkr.ecr.us-east-1.amazonaws.com/my-repo:v1.0.0";

        let uri = format!("{}:{}", repository_uri, tag);
        assert_eq!(uri, expected_uri);
    }

    #[test]
    fn test_ecr_image_uri_with_untagged() {
        let repository_uri = "123456789012.dkr.ecr.us-east-1.amazonaws.com/my-repo";
        let tag = "<untagged>";
        let expected_uri = "123456789012.dkr.ecr.us-east-1.amazonaws.com/my-repo:<untagged>";

        let uri = format!("{}:{}", repository_uri, tag);
        assert_eq!(uri, expected_uri);
    }

    #[test]
    fn test_encryption_type_aes256_formatting() {
        let enc_type = "Aes256";
        let formatted = match enc_type {
            "Aes256" => "AES256".to_string(),
            _ => enc_type.to_string(),
        };
        assert_eq!(formatted, "AES256");
    }

    #[test]
    fn test_encryption_type_kms_unchanged() {
        let enc_type = "Kms";
        let formatted = match enc_type {
            "Aes256" => "AES256".to_string(),
            _ => enc_type.to_string(),
        };
        assert_eq!(formatted, "Kms");
    }
}