mirror of
https://github.com/imjasonh/krust
synced 2026-07-14 17:35:55 +00:00
Fix manifest list implementation
- Always push OCI image indexes (manifest lists) even for single platform builds - Fix manifest size calculation to use actual pushed manifest size - Update platform-specific image tagging to use consistent format - Fix ARM64 linker configuration to use musl toolchain - Update README to document that manifest lists are always created - Fix test to use native platform for Docker compatibility This ensures krust provides a consistent interface regardless of the number of platforms being built, making it easier for downstream tools to consume images. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
parent
d4b7b216df
commit
fe8b27a144
5 changed files with 136 additions and 39 deletions
64
src/main.rs
64
src/main.rs
|
|
@ -5,7 +5,7 @@ use krust::{
|
|||
cli::{Cli, Commands},
|
||||
config::Config,
|
||||
image::ImageBuilder,
|
||||
manifest::{ImageIndex, ManifestDescriptor, Platform},
|
||||
manifest::{ManifestDescriptor, Platform},
|
||||
registry::RegistryClient,
|
||||
};
|
||||
use std::path::{Path, PathBuf};
|
||||
|
|
@ -68,7 +68,6 @@ async fn main() -> Result<()> {
|
|||
|
||||
// Build for each platform
|
||||
let mut manifest_descriptors = Vec::new();
|
||||
let mut single_platform_digest = None;
|
||||
let auth = oci_distribution::secrets::RegistryAuth::Anonymous;
|
||||
let mut registry_client = RegistryClient::new(auth)?;
|
||||
|
||||
|
|
@ -97,22 +96,24 @@ async fn main() -> Result<()> {
|
|||
|
||||
let layers = vec![(layer_data, manifest.layers[0].media_type.clone())];
|
||||
|
||||
// For single platform, push to the main tag
|
||||
let push_ref = if platforms.len() == 1 {
|
||||
image_ref.clone()
|
||||
// For manifest lists to work properly, we need to push to a consistent location
|
||||
// We'll use the base image ref with a unique tag for each platform
|
||||
let (base_ref, _) = if let Some(pos) = image_ref.rfind(':') {
|
||||
(
|
||||
image_ref[..pos].to_string(),
|
||||
image_ref[pos + 1..].to_string(),
|
||||
)
|
||||
} else {
|
||||
// For multi-platform, use platform-specific tag
|
||||
format!("{}-{}", image_ref, platform_str.replace('/', "-"))
|
||||
(image_ref.to_string(), "latest".to_string())
|
||||
};
|
||||
|
||||
let digest_ref = registry_client
|
||||
.push_image(&push_ref, config_data, layers)
|
||||
.await?;
|
||||
// Create a unique tag for this platform to avoid conflicts
|
||||
let platform_tag = format!("platform-{}", platform_str.replace('/', "-"));
|
||||
let platform_ref = format!("{}:{}", base_ref, platform_tag);
|
||||
|
||||
// Store digest for single platform builds
|
||||
if platforms.len() == 1 {
|
||||
single_platform_digest = Some(digest_ref.clone());
|
||||
}
|
||||
let (digest_ref, manifest_size) = registry_client
|
||||
.push_image(&platform_ref, config_data, layers)
|
||||
.await?;
|
||||
|
||||
// Parse platform string
|
||||
let parts: Vec<&str> = platform_str.split('/').collect();
|
||||
|
|
@ -122,11 +123,20 @@ async fn main() -> Result<()> {
|
|||
return Err(anyhow::anyhow!("Invalid platform format: {}", platform_str));
|
||||
};
|
||||
|
||||
// Extract just the digest from the full reference
|
||||
let digest = digest_ref.split('@').next_back().unwrap_or("").to_string();
|
||||
|
||||
info!("Pushed platform image to: {}", digest_ref);
|
||||
|
||||
// Add to manifest list
|
||||
info!(
|
||||
"Adding manifest to list - platform: {}/{}, digest: {}, size: {}",
|
||||
os, arch, digest, manifest_size
|
||||
);
|
||||
manifest_descriptors.push(ManifestDescriptor {
|
||||
media_type: manifest.media_type.clone(),
|
||||
size: serde_json::to_vec(&manifest)?.len() as i64,
|
||||
digest: digest_ref.split('@').next_back().unwrap_or("").to_string(),
|
||||
media_type: "application/vnd.oci.image.manifest.v1+json".to_string(),
|
||||
size: manifest_size as i64,
|
||||
digest,
|
||||
platform: Platform {
|
||||
architecture: arch,
|
||||
os,
|
||||
|
|
@ -136,20 +146,16 @@ async fn main() -> Result<()> {
|
|||
}
|
||||
}
|
||||
|
||||
// Create and push manifest list if not --no-push
|
||||
if !no_push && platforms.len() > 1 {
|
||||
// Always push manifest list if not --no-push (even for single platform)
|
||||
if !no_push {
|
||||
info!("Creating and pushing manifest list...");
|
||||
let index = ImageIndex::new(manifest_descriptors);
|
||||
let _index_data = serde_json::to_vec(&index)?;
|
||||
|
||||
// TODO: Push manifest list to registry
|
||||
// For now, just print the multi-arch image reference
|
||||
println!("{}", image_ref);
|
||||
} else if !no_push && platforms.len() == 1 {
|
||||
// Single platform, print the digest reference
|
||||
if let Some(digest_ref) = single_platform_digest {
|
||||
println!("{}", digest_ref);
|
||||
}
|
||||
let manifest_list_ref = registry_client
|
||||
.push_manifest_list(&image_ref, manifest_descriptors)
|
||||
.await?;
|
||||
|
||||
// Output the manifest list reference
|
||||
println!("{}", manifest_list_ref);
|
||||
} else {
|
||||
info!(
|
||||
"Successfully built image for {} platform(s)",
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ use anyhow::{Context, Result};
|
|||
use oci_distribution::manifest::{OciDescriptor, OciImageManifest, OciManifest};
|
||||
use oci_distribution::secrets::RegistryAuth;
|
||||
use oci_distribution::{Client, Reference};
|
||||
use std::str::FromStr;
|
||||
use tracing::{debug, info};
|
||||
|
||||
#[cfg(test)]
|
||||
|
|
@ -24,7 +25,7 @@ impl RegistryClient {
|
|||
image_ref: &str,
|
||||
config_data: Vec<u8>,
|
||||
layers: Vec<(Vec<u8>, String)>,
|
||||
) -> Result<String> {
|
||||
) -> Result<(String, usize)> {
|
||||
let reference: Reference = image_ref
|
||||
.parse()
|
||||
.context("Failed to parse image reference")?;
|
||||
|
|
@ -100,7 +101,82 @@ impl RegistryClient {
|
|||
let repository = reference.repository();
|
||||
let digest_ref = format!("{}/{}@{}", registry, repository, digest);
|
||||
|
||||
Ok(digest_ref)
|
||||
// Return both the digest ref and the actual manifest size
|
||||
let manifest_size = serde_json::to_vec(&manifest)?.len();
|
||||
Ok((digest_ref, manifest_size))
|
||||
}
|
||||
|
||||
pub async fn push_manifest_list(
|
||||
&mut self,
|
||||
image_ref: &str,
|
||||
manifest_descriptors: Vec<crate::manifest::ManifestDescriptor>,
|
||||
) -> Result<String> {
|
||||
let reference = Reference::from_str(image_ref)
|
||||
.context(format!("Failed to parse image reference: {}", image_ref))?;
|
||||
|
||||
// Create the image index
|
||||
let index = crate::manifest::ImageIndex::new(manifest_descriptors);
|
||||
|
||||
// Convert to OCI index
|
||||
let oci_manifests: Vec<oci_distribution::manifest::ImageIndexEntry> = index
|
||||
.manifests
|
||||
.iter()
|
||||
.map(|m| oci_distribution::manifest::ImageIndexEntry {
|
||||
media_type: m.media_type.clone(),
|
||||
digest: m.digest.clone(),
|
||||
size: m.size,
|
||||
platform: Some(oci_distribution::manifest::Platform {
|
||||
architecture: m.platform.architecture.clone(),
|
||||
os: m.platform.os.clone(),
|
||||
os_version: None,
|
||||
os_features: None,
|
||||
variant: m.platform.variant.clone(),
|
||||
features: None,
|
||||
}),
|
||||
annotations: None,
|
||||
})
|
||||
.collect();
|
||||
|
||||
let oci_index = oci_distribution::manifest::OciImageIndex {
|
||||
schema_version: 2,
|
||||
media_type: Some("application/vnd.oci.image.index.v1+json".to_string()),
|
||||
manifests: oci_manifests,
|
||||
annotations: None,
|
||||
};
|
||||
|
||||
// Wrap in OciManifest enum
|
||||
let manifest = oci_distribution::manifest::OciManifest::ImageIndex(oci_index);
|
||||
|
||||
debug!(
|
||||
"Pushing manifest list with {} manifests",
|
||||
index.manifests.len()
|
||||
);
|
||||
for m in &index.manifests {
|
||||
debug!(
|
||||
" - Platform: {}/{}, digest: {}",
|
||||
m.platform.os, m.platform.architecture, m.digest
|
||||
);
|
||||
}
|
||||
let manifest_url = self
|
||||
.client
|
||||
.push_manifest(&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();
|
||||
let image_ref = format!("{}/{}@{}", registry, repository, digest);
|
||||
|
||||
Ok(image_ref)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue