mirror of
https://github.com/imjasonh/krust
synced 2026-07-14 09:25:51 +00:00
feat: Add multi-arch support
- Support building for multiple platforms with --platform flag - Accept comma-separated platforms or multiple --platform flags - Default to building for linux/amd64 and linux/arm64 - Add ARM runner (ubuntu-24.04-arm) to CI matrix - Create manifest list for multi-arch images (TODO: push to registry) - Update integration tests to handle multi-platform builds - Add cross-compilation setup instructions to README - Add .cargo/config.toml for local development Breaking changes: - Default behavior now builds for multiple platforms (amd64+arm64) - Use --platform linux/amd64 to build for single platform
This commit is contained in:
parent
11083dc527
commit
6153452303
8 changed files with 260 additions and 37 deletions
|
|
@ -25,9 +25,10 @@ pub enum Commands {
|
|||
#[arg(short, long, env = "KRUST_IMAGE")]
|
||||
image: Option<String>,
|
||||
|
||||
/// Target platform (e.g., linux/amd64, linux/arm64)
|
||||
#[arg(long, default_value = "linux/amd64")]
|
||||
platform: String,
|
||||
/// Target platforms (e.g., linux/amd64, linux/arm64)
|
||||
/// Can be specified multiple times or as a comma-separated list
|
||||
#[arg(long, value_delimiter = ',')]
|
||||
platform: Option<Vec<String>>,
|
||||
|
||||
/// Skip pushing the image to the registry after building
|
||||
#[arg(long)]
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ pub mod builder;
|
|||
pub mod cli;
|
||||
pub mod config;
|
||||
pub mod image;
|
||||
pub mod manifest;
|
||||
pub mod registry;
|
||||
|
||||
pub use anyhow::Result;
|
||||
|
|
|
|||
120
src/main.rs
120
src/main.rs
|
|
@ -5,6 +5,7 @@ use krust::{
|
|||
cli::{Cli, Commands},
|
||||
config::Config,
|
||||
image::ImageBuilder,
|
||||
manifest::{ImageIndex, ManifestDescriptor, Platform},
|
||||
registry::RegistryClient,
|
||||
};
|
||||
use std::path::{Path, PathBuf};
|
||||
|
|
@ -57,33 +58,100 @@ async fn main() -> Result<()> {
|
|||
format!("{}/{}:latest", repo, project_name)
|
||||
};
|
||||
|
||||
// Build the Rust binary
|
||||
let target = get_rust_target_triple(&platform)?;
|
||||
let builder = RustBuilder::new(&project_path, &target).with_cargo_args(cargo_args);
|
||||
|
||||
let binary_path = builder.build()?;
|
||||
|
||||
// Build container image
|
||||
let image_builder = ImageBuilder::new(binary_path, base_image, platform.clone());
|
||||
|
||||
let (config_data, layer_data, manifest) = image_builder.build()?;
|
||||
|
||||
// Push by default unless --no-push is specified
|
||||
if !no_push {
|
||||
info!("Pushing image to registry...");
|
||||
let auth = oci_distribution::secrets::RegistryAuth::Anonymous;
|
||||
let mut registry_client = RegistryClient::new(auth)?;
|
||||
|
||||
let layers = vec![(layer_data, manifest.layers[0].media_type.clone())];
|
||||
|
||||
let digest_ref = registry_client
|
||||
.push_image(&image_ref, config_data, layers)
|
||||
.await?;
|
||||
|
||||
// Print only the digest reference to stdout
|
||||
println!("{}", digest_ref);
|
||||
// Determine platforms to build for
|
||||
let platforms = if let Some(platforms) = platform {
|
||||
platforms
|
||||
} else {
|
||||
info!("Successfully built image: {}", image_ref);
|
||||
// Default to common platforms
|
||||
vec!["linux/amd64".to_string(), "linux/arm64".to_string()]
|
||||
};
|
||||
|
||||
// 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)?;
|
||||
|
||||
for platform_str in &platforms {
|
||||
info!("Building for platform: {}", platform_str);
|
||||
|
||||
// Build the Rust binary for this platform
|
||||
let target = get_rust_target_triple(platform_str)?;
|
||||
let builder =
|
||||
RustBuilder::new(&project_path, &target).with_cargo_args(cargo_args.clone());
|
||||
|
||||
let binary_path = builder.build()?;
|
||||
|
||||
// Build container image for this platform
|
||||
let image_builder =
|
||||
ImageBuilder::new(binary_path, base_image.clone(), platform_str.clone());
|
||||
|
||||
let (config_data, layer_data, manifest) = image_builder.build()?;
|
||||
|
||||
// Push platform-specific image if not --no-push
|
||||
if !no_push {
|
||||
info!("Pushing image for platform: {}", platform_str);
|
||||
|
||||
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()
|
||||
} else {
|
||||
// For multi-platform, use platform-specific tag
|
||||
format!("{}-{}", image_ref, platform_str.replace('/', "-"))
|
||||
};
|
||||
|
||||
let digest_ref = registry_client
|
||||
.push_image(&push_ref, config_data, layers)
|
||||
.await?;
|
||||
|
||||
// Store digest for single platform builds
|
||||
if platforms.len() == 1 {
|
||||
single_platform_digest = Some(digest_ref.clone());
|
||||
}
|
||||
|
||||
// Parse platform string
|
||||
let parts: Vec<&str> = platform_str.split('/').collect();
|
||||
let (os, arch) = if parts.len() >= 2 {
|
||||
(parts[0].to_string(), parts[1].to_string())
|
||||
} else {
|
||||
return Err(anyhow::anyhow!("Invalid platform format: {}", platform_str));
|
||||
};
|
||||
|
||||
// Add to manifest list
|
||||
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(),
|
||||
platform: Platform {
|
||||
architecture: arch,
|
||||
os,
|
||||
variant: None,
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Create and push manifest list if not --no-push
|
||||
if !no_push && platforms.len() > 1 {
|
||||
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);
|
||||
}
|
||||
} else {
|
||||
info!(
|
||||
"Successfully built image for {} platform(s)",
|
||||
platforms.len()
|
||||
);
|
||||
info!("Skipping push (--no-push specified)");
|
||||
}
|
||||
}
|
||||
|
|
|
|||
40
src/manifest.rs
Normal file
40
src/manifest.rs
Normal file
|
|
@ -0,0 +1,40 @@
|
|||
use serde::{Deserialize, Serialize};
|
||||
|
||||
/// OCI Image Index (manifest list) for multi-arch support
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct ImageIndex {
|
||||
#[serde(rename = "schemaVersion")]
|
||||
pub schema_version: i32,
|
||||
#[serde(rename = "mediaType")]
|
||||
pub media_type: String,
|
||||
pub manifests: Vec<ManifestDescriptor>,
|
||||
}
|
||||
|
||||
/// Descriptor for a platform-specific manifest in the index
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct ManifestDescriptor {
|
||||
#[serde(rename = "mediaType")]
|
||||
pub media_type: String,
|
||||
pub size: i64,
|
||||
pub digest: String,
|
||||
pub platform: Platform,
|
||||
}
|
||||
|
||||
/// Platform information for a manifest
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct Platform {
|
||||
pub architecture: String,
|
||||
pub os: String,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub variant: Option<String>,
|
||||
}
|
||||
|
||||
impl ImageIndex {
|
||||
pub fn new(manifests: Vec<ManifestDescriptor>) -> Self {
|
||||
Self {
|
||||
schema_version: 2,
|
||||
media_type: "application/vnd.oci.image.index.v1+json".to_string(),
|
||||
manifests,
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue