From 7827c25d9ba7a0f41abb6e027af44f5ccc102599 Mon Sep 17 00:00:00 2001 From: Jason Hall Date: Sun, 8 Jun 2025 09:49:41 -0400 Subject: [PATCH] feat: Fork oci-distribution and add enhanced features - Fork oci-distribution v0.11.0 into vendor/oci-distribution - Add get_image_platforms() method for proper platform detection - Support fetching config blobs for single-platform images - Add OAuth2/Bearer token authentication support - Add push_manifest_and_get_digest() for reliable digest extraction - Update krust to use the forked version with enhanced features --- Cargo.toml | 2 +- src/auth/mod.rs | 36 +++++++++++ src/registry/mod.rs | 128 +++++++++++++++------------------------- vendor/oci-distribution | 1 + 4 files changed, 84 insertions(+), 83 deletions(-) create mode 160000 vendor/oci-distribution diff --git a/Cargo.toml b/Cargo.toml index c71c08d..3f3a74f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -19,7 +19,7 @@ sha256 = "1.5" chrono = "0.4" tar = "0.4" flate2 = "1.0" -oci-distribution = "0.11" +oci-distribution = { path = "vendor/oci-distribution" } tracing = "0.1" tracing-subscriber = { version = "0.3", features = ["env-filter"] } dirs = "6.0" diff --git a/src/auth/mod.rs b/src/auth/mod.rs index b5aeda9..aeab575 100644 --- a/src/auth/mod.rs +++ b/src/auth/mod.rs @@ -83,6 +83,16 @@ impl AuthConfig { return RegistryAuth::Anonymous; } + // Check for bearer tokens first + if let Some(token) = &self.registry_token { + return RegistryAuth::Bearer(token.clone()); + } + + if let Some(token) = &self.identity_token { + return RegistryAuth::Bearer(token.clone()); + } + + // Then check for basic auth if let (Some(username), Some(password)) = (&self.username, &self.password) { return RegistryAuth::Basic(username.clone(), password.clone()); } @@ -234,4 +244,30 @@ mod unit_tests { let header = auth.to_authorization_header().unwrap().unwrap(); assert_eq!(header, "Bearer token123"); } + + #[test] + fn test_auth_config_to_registry_auth() { + use oci_distribution::secrets::RegistryAuth; + + // Test anonymous + let auth = AuthConfig::anonymous(); + assert_eq!(auth.to_registry_auth(), RegistryAuth::Anonymous); + + // Test basic auth + let auth = AuthConfig::new("user".to_string(), "pass".to_string()); + assert_eq!( + auth.to_registry_auth(), + RegistryAuth::Basic("user".to_string(), "pass".to_string()) + ); + + // Test bearer token + let auth = AuthConfig { + registry_token: Some("token123".to_string()), + ..Default::default() + }; + assert_eq!( + auth.to_registry_auth(), + RegistryAuth::Bearer("token123".to_string()) + ); + } } diff --git a/src/registry/mod.rs b/src/registry/mod.rs index eb75915..8e9dbe5 100644 --- a/src/registry/mod.rs +++ b/src/registry/mod.rs @@ -86,21 +86,14 @@ impl RegistryClient { let manifest = OciManifest::Image(image_manifest); debug!("Pushing manifest"); - let manifest_url = self + let (manifest_url, digest) = self .client - .push_manifest(&reference, &manifest) + .push_manifest_and_get_digest(&reference, &manifest) .await .context("Failed to push manifest")?; info!("Successfully pushed image to {}", manifest_url); - // Extract digest from the manifest URL - // URL format: https://registry/v2/repo/manifests/sha256:digest - let digest = manifest_url - .split('/') - .next_back() - .context("Failed to extract digest from manifest URL")?; - // Build the full image reference with digest let registry = reference.registry(); let repository = reference.repository(); @@ -169,20 +162,14 @@ impl RegistryClient { m.platform.os, m.platform.architecture, m.digest ); } - let manifest_url = self + let (manifest_url, digest) = self .client - .push_manifest(&reference, &manifest) + .push_manifest_and_get_digest(&reference, &manifest) .await .context("Failed to push manifest list")?; info!("Successfully pushed manifest list to {}", manifest_url); - // Extract digest from the manifest URL - let digest = manifest_url - .split('/') - .next_back() - .context("Failed to extract digest from manifest URL")?; - // Build the full image reference with digest let registry = reference.registry(); let repository = reference.repository(); @@ -201,78 +188,55 @@ impl RegistryClient { .parse() .context("Failed to parse image reference")?; - debug!("Fetching manifest for {}", reference); + debug!("Fetching platforms for {}", reference); - // Pull the manifest - let (manifest, _) = self + // Use the new get_image_platforms method from oci-distribution + let platforms = self .client - .pull_manifest(&reference, auth) + .get_image_platforms(&reference, auth) .await - .context("Failed to pull manifest")?; + .context("Failed to get image platforms")?; - // Parse platforms based on manifest type - match manifest { - OciManifest::Image(_) => { - // Single platform image - we need to fetch the config to determine platform - debug!("Single platform image detected"); - // For now, we'll assume it's linux/amd64 if we can't determine - // In a real implementation, we'd fetch the config blob - Ok(vec!["linux/amd64".to_string()]) - } - OciManifest::ImageIndex(index) => { - // Multi-platform image - extract platforms from index - debug!( - "Multi-platform image detected with {} manifests", - index.manifests.len() - ); - let mut platforms: Vec = index - .manifests - .iter() - .filter_map(|m| { - m.platform.as_ref().and_then(|p| { - // Filter out invalid platforms - if p.os == "unknown" - || p.architecture == "unknown" - || p.os.is_empty() - || p.architecture.is_empty() - { - return None; - } + // Convert (os, arch) tuples to platform strings and normalize + let mut platform_strings: Vec = platforms + .into_iter() + .filter_map(|(os, arch)| { + // Filter out unknown/invalid platforms + if os == "unknown" || arch == "unknown" || os.is_empty() || arch.is_empty() { + return None; + } - let mut platform = format!("{}/{}", p.os, p.architecture); - if let Some(variant) = &p.variant { - if !variant.is_empty() { - platform.push('/'); - platform.push_str(variant); - } - } - // Normalize and filter platforms - let normalized = match platform.as_str() { - "linux/amd64" => Some("linux/amd64".to_string()), - "linux/arm64" | "linux/arm64/v8" => Some("linux/arm64".to_string()), - "linux/arm/v7" => Some("linux/arm/v7".to_string()), - "linux/arm/v6" => Some("linux/arm/v6".to_string()), - "linux/386" => Some("linux/386".to_string()), - "linux/ppc64le" => Some("linux/ppc64le".to_string()), - "linux/s390x" => Some("linux/s390x".to_string()), - "linux/riscv64" => Some("linux/riscv64".to_string()), - _ => { - debug!("Skipping unsupported platform: {}", platform); - None - } - }; - normalized - }) - }) - .collect(); + let platform = format!("{}/{}", os, arch); - // Deduplicate platforms - platforms.sort(); - platforms.dedup(); + // Normalize and filter platforms + let normalized = match platform.as_str() { + "linux/amd64" => Some("linux/amd64".to_string()), + "linux/arm64" | "linux/arm64/v8" => Some("linux/arm64".to_string()), + "linux/arm/v7" => Some("linux/arm/v7".to_string()), + "linux/arm/v6" => Some("linux/arm/v6".to_string()), + "linux/386" => Some("linux/386".to_string()), + "linux/ppc64le" => Some("linux/ppc64le".to_string()), + "linux/s390x" => Some("linux/s390x".to_string()), + "linux/riscv64" => Some("linux/riscv64".to_string()), + _ => { + debug!("Skipping unsupported platform: {}", platform); + None + } + }; + normalized + }) + .collect(); - info!("Found platforms: {:?}", platforms); - Ok(platforms) - } + // Deduplicate platforms + platform_strings.sort(); + platform_strings.dedup(); + + if platform_strings.is_empty() { + debug!("No valid platforms found, defaulting to linux/amd64"); + Ok(vec!["linux/amd64".to_string()]) + } else { + info!("Found platforms: {:?}", platform_strings); + Ok(platform_strings) } } } diff --git a/vendor/oci-distribution b/vendor/oci-distribution new file mode 160000 index 0000000..6157eb9 --- /dev/null +++ b/vendor/oci-distribution @@ -0,0 +1 @@ +Subproject commit 6157eb998456bdd88b0a8a085a7bf70ecab6189d