diff --git a/process/drivers.rs b/process/drivers.rs index b825320..74bd505 100644 --- a/process/drivers.rs +++ b/process/drivers.rs @@ -188,9 +188,9 @@ impl Driver { pub fn get_os_version( /// The OCI image reference. oci_ref: &Reference, + /// The platform of the image to pull the version info from. - #[builder(default)] - platform: Platform, + platform: Option, ) -> Result { trace!("Driver::get_os_version({oci_ref:#?})"); @@ -208,7 +208,7 @@ impl Driver { let os_version = Self::get_metadata( &GetMetadataOpts::builder() .image(oci_ref) - .platform(platform) + .maybe_platform(platform) .build(), ) .and_then(|inspection| { diff --git a/process/drivers/buildah_driver.rs b/process/drivers/buildah_driver.rs index 85a8ef4..9fc8fe9 100644 --- a/process/drivers/buildah_driver.rs +++ b/process/drivers/buildah_driver.rs @@ -8,7 +8,7 @@ use miette::{Context, IntoDiagnostic, Result, bail, miette}; use serde::Deserialize; use tempfile::TempDir; -use crate::{drivers::types::Platform, logging::CommandLogging}; +use crate::logging::CommandLogging; use super::{ BuildDriver, DriverVersion, @@ -60,9 +60,9 @@ impl BuildDriver for BuildahDriver { "build", for opts.secrets.args(&temp_dir)?, if opts.secrets.ssh() => "--ssh", - if !matches!(opts.platform, Platform::Native) => [ + if let Some(platform) = opts.platform => [ "--platform", - opts.platform.to_string(), + platform.to_string(), ], "--pull=true", format!("--layers={}", !opts.squash), diff --git a/process/drivers/docker_driver.rs b/process/drivers/docker_driver.rs index 8f802de..cdfff40 100644 --- a/process/drivers/docker_driver.rs +++ b/process/drivers/docker_driver.rs @@ -30,7 +30,7 @@ use crate::{ RunOptsVolume, TagOpts, }, traits::{BuildDriver, DriverVersion, InspectDriver, RunDriver}, - types::{ContainerId, ImageMetadata, ImageRef, Platform}, + types::{ContainerId, ImageMetadata, ImageRef}, }, logging::CommandLogging, signal_handler::{ContainerRuntime, ContainerSignalId, add_cid, remove_cid}, @@ -207,9 +207,9 @@ impl BuildDriver for DockerDriver { let c = cmd!( "docker", "build", - if !matches!(opts.platform, Platform::Native) => [ + if let Some(platform) = opts.platform => [ "--platform", - opts.platform.to_string(), + platform.to_string(), ], "-t", opts.image.to_string(), @@ -456,9 +456,9 @@ fn build_tag_push_cmd( }).collect() }), "--pull", - if !matches!(opts.platform, Platform::Native) => [ + if let Some(platform) = opts.platform => [ "--platform", - opts.platform.to_string(), + platform.to_string(), ], "-f", &*opts.containerfile, @@ -507,7 +507,7 @@ impl InspectDriver for DockerDriver { #[cached( result = true, key = "String", - convert = r#"{ format!("{}-{}", opts.image, opts.platform)}"#, + convert = r#"{ format!("{}-{:?}", opts.image, opts.platform)}"#, sync_writes = "by_key" )] fn get_metadata_cache(opts: &GetMetadataOpts) -> Result { diff --git a/process/drivers/docker_driver/metadata.rs b/process/drivers/docker_driver/metadata.rs index 5fe5faf..1dfa30b 100644 --- a/process/drivers/docker_driver/metadata.rs +++ b/process/drivers/docker_driver/metadata.rs @@ -3,7 +3,7 @@ use std::collections::HashMap; use miette::{Report, bail}; use serde::Deserialize; -use crate::drivers::types::{ImageMetadata, Platform}; +use crate::drivers::types::{ImageMetadata, Platform, PlatformInfo}; #[derive(Deserialize, Debug, Clone)] pub struct Metadata { @@ -48,10 +48,10 @@ pub struct Config { labels: HashMap, } -impl TryFrom<(Metadata, Platform)> for ImageMetadata { +impl TryFrom<(Metadata, Option)> for ImageMetadata { type Error = Report; - fn try_from((metadata, platform): (Metadata, Platform)) -> Result { + fn try_from((metadata, platform): (Metadata, Option)) -> Result { match metadata.image { MetadataImage::Single(image) => Ok(Self { labels: image.config.labels, @@ -59,7 +59,10 @@ impl TryFrom<(Metadata, Platform)> for ImageMetadata { }), MetadataImage::Multi(mut platforms) => { let Some(image) = platforms.remove(&platform.to_string()) else { - bail!("Image information does not exist for {platform}"); + bail!( + "Image information does not exist for {}", + platform.to_string() + ); }; let Some(manifest) = metadata .manifest @@ -67,7 +70,7 @@ impl TryFrom<(Metadata, Platform)> for ImageMetadata { .into_iter() .find(|manifest| manifest.platform.architecture == platform.arch()) else { - bail!("Manifest does not exist for {platform}"); + bail!("Manifest does not exist for {}", platform.to_string()); }; Ok(Self { labels: image.config.labels, diff --git a/process/drivers/github_driver.rs b/process/drivers/github_driver.rs index 80789b3..9a1508e 100644 --- a/process/drivers/github_driver.rs +++ b/process/drivers/github_driver.rs @@ -41,7 +41,7 @@ impl CiDriver for GithubDriver { let timestamp = blue_build_utils::get_tag_timestamp(); let os_version = Driver::get_os_version() .oci_ref(opts.oci_ref) - .platform(opts.platform) + .maybe_platform(opts.platform) .call() .inspect(|v| trace!("os_version={v}"))?; let ref_name = get_env_var(GITHUB_REF_NAME) diff --git a/process/drivers/gitlab_driver.rs b/process/drivers/gitlab_driver.rs index af3b257..d0fb9e2 100644 --- a/process/drivers/gitlab_driver.rs +++ b/process/drivers/gitlab_driver.rs @@ -49,7 +49,7 @@ impl CiDriver for GitlabDriver { const MR_EVENT: &str = "merge_request_event"; let os_version = Driver::get_os_version() .oci_ref(opts.oci_ref) - .platform(opts.platform) + .maybe_platform(opts.platform) .call()?; let timestamp = blue_build_utils::get_tag_timestamp(); let short_sha = diff --git a/process/drivers/local_driver.rs b/process/drivers/local_driver.rs index 2c3d11f..273783a 100644 --- a/process/drivers/local_driver.rs +++ b/process/drivers/local_driver.rs @@ -26,7 +26,7 @@ impl CiDriver for LocalDriver { trace!("LocalDriver::generate_tags({opts:?})"); let os_version = Driver::get_os_version() .oci_ref(opts.oci_ref) - .platform(opts.platform) + .maybe_platform(opts.platform) .call()?; let timestamp = blue_build_utils::get_tag_timestamp(); let short_sha = commit_sha(); diff --git a/process/drivers/opts/build.rs b/process/drivers/opts/build.rs index 4db19a5..2abb958 100644 --- a/process/drivers/opts/build.rs +++ b/process/drivers/opts/build.rs @@ -20,8 +20,7 @@ pub struct BuildOpts<'scope> { #[builder(into)] pub containerfile: Cow<'scope, Path>, - #[builder(default)] - pub platform: Platform, + pub platform: Option, #[builder(default)] pub host_network: bool, @@ -102,8 +101,7 @@ pub struct BuildTagPushOpts<'scope> { pub squash: bool, /// The platform to build the image on. - #[builder(default)] - pub platform: Platform, + pub platform: Option, /// Runs the build with elevated privileges #[builder(default)] diff --git a/process/drivers/opts/ci.rs b/process/drivers/opts/ci.rs index d828376..12dcdf3 100644 --- a/process/drivers/opts/ci.rs +++ b/process/drivers/opts/ci.rs @@ -12,8 +12,7 @@ pub struct GenerateTagsOpts<'scope> { #[builder(into)] pub alt_tags: Option>>, - #[builder(default)] - pub platform: Platform, + pub platform: Option, } #[derive(Debug, Clone, Builder)] diff --git a/process/drivers/opts/inspect.rs b/process/drivers/opts/inspect.rs index dd2f090..1f34ab9 100644 --- a/process/drivers/opts/inspect.rs +++ b/process/drivers/opts/inspect.rs @@ -9,6 +9,5 @@ pub struct GetMetadataOpts<'scope> { #[builder(into)] pub image: &'scope Reference, - #[builder(default)] - pub platform: Platform, + pub platform: Option, } diff --git a/process/drivers/opts/rechunk.rs b/process/drivers/opts/rechunk.rs index dccd355..1b2c5ed 100644 --- a/process/drivers/opts/rechunk.rs +++ b/process/drivers/opts/rechunk.rs @@ -16,8 +16,7 @@ pub struct RechunkOpts<'scope> { #[builder(into)] pub containerfile: Cow<'scope, Path>, - #[builder(default)] - pub platform: Platform, + pub platform: Option, pub version: Cow<'scope, str>, pub name: Cow<'scope, str>, pub description: Cow<'scope, str>, diff --git a/process/drivers/opts/signing.rs b/process/drivers/opts/signing.rs index d4379ab..44a859a 100644 --- a/process/drivers/opts/signing.rs +++ b/process/drivers/opts/signing.rs @@ -114,6 +114,5 @@ pub struct SignVerifyOpts<'scope> { #[builder(default = 1)] pub retry_count: u8, - #[builder(default)] - pub platform: Platform, + pub platform: Option, } diff --git a/process/drivers/podman_driver.rs b/process/drivers/podman_driver.rs index c6087b2..6918176 100644 --- a/process/drivers/podman_driver.rs +++ b/process/drivers/podman_driver.rs @@ -28,7 +28,7 @@ use crate::{ drivers::{ BuildDriver, DriverVersion, InspectDriver, RunDriver, opts::{BuildOpts, GetMetadataOpts, PushOpts, RunOpts, RunOptsEnv, RunOptsVolume, TagOpts}, - types::{ImageMetadata, Platform}, + types::ImageMetadata, }, logging::{CommandLogging, Logger}, signal_handler::{ContainerRuntime, ContainerSignalId, add_cid, remove_cid}, @@ -158,9 +158,9 @@ impl BuildDriver for PodmanDriver { "podman", ], "build", - if !matches!(opts.platform, Platform::Native) => [ + if let Some(platform) = opts.platform => [ "--platform", - opts.platform.to_string(), + platform.to_string(), ], if let Some(cache_from) = opts.cache_from.as_ref() => [ "--cache-from", @@ -347,7 +347,7 @@ impl InspectDriver for PodmanDriver { #[cached( result = true, key = "String", - convert = r#"{ format!("{}-{}", opts.image, opts.platform)}"#, + convert = r#"{ format!("{}-{:?}", opts.image, opts.platform)}"#, sync_writes = "by_key" )] fn get_metadata_cache(opts: &GetMetadataOpts) -> Result { @@ -369,9 +369,9 @@ fn get_metadata_cache(opts: &GetMetadataOpts) -> Result { let c = cmd!( "podman", "pull", - if !matches!(opts.platform, Platform::Native) => [ + if let Some(platform) = opts.platform => [ "--platform", - opts.platform.to_string(), + platform.to_string(), ], &image_str, ); diff --git a/process/drivers/skopeo_driver.rs b/process/drivers/skopeo_driver.rs index 6602242..3496858 100644 --- a/process/drivers/skopeo_driver.rs +++ b/process/drivers/skopeo_driver.rs @@ -23,7 +23,7 @@ impl InspectDriver for SkopeoDriver { #[cached( result = true, key = "String", - convert = r#"{ format!("{}-{}", opts.image, opts.platform)}"#, + convert = r#"{ format!("{}-{:?}", opts.image, opts.platform)}"#, sync_writes = "by_key" )] fn get_metadata_cache(opts: &GetMetadataOpts) -> Result { @@ -40,9 +40,13 @@ fn get_metadata_cache(opts: &GetMetadataOpts) -> Result { let mut command = cmd!( "skopeo", - if !matches!(opts.platform, Platform::Native) => [ + if let Some(platform) = opts.platform => [ "--override-arch", - opts.platform.arch(), + platform.arch(), + ], + if let Some(variant) = opts.platform.as_ref().and_then(Platform::variant) => [ + "--override-variant", + variant, ], "inspect", format!("docker://{image_str}"), diff --git a/process/drivers/traits.rs b/process/drivers/traits.rs index babd607..fda27b4 100644 --- a/process/drivers/traits.rs +++ b/process/drivers/traits.rs @@ -124,7 +124,7 @@ pub trait BuildDriver: PrivateDriver { let build_opts = BuildOpts::builder() .image(&opts.image) .containerfile(opts.containerfile.as_ref()) - .platform(opts.platform) + .maybe_platform(opts.platform) .squash(opts.squash) .maybe_cache_from(opts.cache_from) .maybe_cache_to(opts.cache_to) @@ -286,7 +286,7 @@ pub trait RechunkDriver: RunDriver + BuildDriver + ContainerMountDriver { &BuildOpts::builder() .image(raw_image) .containerfile(&*opts.containerfile) - .platform(opts.platform) + .maybe_platform(opts.platform) .privileged(true) .squash(true) .host_network(true) @@ -562,7 +562,7 @@ pub trait SigningDriver: PrivateDriver { let image_digest = Driver::get_metadata( &GetMetadataOpts::builder() .image(opts.image) - .platform(opts.platform) + .maybe_platform(opts.platform) .build(), )? .digest; diff --git a/process/drivers/types.rs b/process/drivers/types.rs index bcbd3a6..d457484 100644 --- a/process/drivers/types.rs +++ b/process/drivers/types.rs @@ -8,6 +8,7 @@ use blue_build_utils::{ constants::{GITHUB_ACTIONS, GITLAB_CI, IMAGE_VERSION_LABEL}, get_env_var, semver::Version, + string, }; use clap::ValueEnum; use log::{trace, warn}; @@ -20,6 +21,10 @@ use crate::drivers::{ podman_driver::PodmanDriver, }; +mod private { + pub trait Private {} +} + pub(super) trait DetermineDriver { fn determine_driver(&mut self) -> T; } @@ -178,30 +183,86 @@ impl DetermineDriver for Option { } } -#[derive(Debug, Default, Clone, Copy, ValueEnum, PartialEq, Eq, Hash)] +#[derive(Debug, Clone, Copy, ValueEnum, PartialEq, Eq, Hash)] pub enum Platform { - #[default] - #[value(name = "native")] - Native, #[value(name = "linux/amd64")] LinuxAmd64, + #[value(name = "linux/amd64/v2")] + LinuxAmd64V2, + #[value(name = "linux/arm64")] LinuxArm64, + + #[value(name = "linux/arm")] + LinuxArm, + + #[value(name = "linux/arm/v6")] + LinuxArmV6, + + #[value(name = "linux/arm/v7")] + LinuxArmV7, + + #[value(name = "linux/386")] + Linux386, + + #[value(name = "linux/loong64")] + LinuxLoong64, + + #[value(name = "linux/mips")] + LinuxMips, + + #[value(name = "linux/mipsle")] + LinuxMipsle, + + #[value(name = "linux/mips64")] + LinuxMips64, + + #[value(name = "linux/mips64le")] + LinuxMips64le, + + #[value(name = "linux/ppc64")] + LinuxPpc64, + + #[value(name = "linux/ppc64le")] + LinuxPpc64le, + + #[value(name = "linux/riscv64")] + LinuxRiscv64, + + #[value(name = "linux/s390x")] + LinuxS390x, } impl Platform { /// The architecture of the platform. #[must_use] - pub fn arch(&self) -> &str { + pub const fn arch(&self) -> &str { match *self { - Self::Native => match std::env::consts::ARCH { - "x86_64" => "amd64", - "aarch64" => "arm64", - arch => unimplemented!("Arch {arch} is unsupported"), - }, - Self::LinuxAmd64 => "amd64", + Self::LinuxAmd64 | Self::LinuxAmd64V2 => "amd64", Self::LinuxArm64 => "arm64", + Self::LinuxArm | Self::LinuxArmV6 | Self::LinuxArmV7 => "arm", + Self::Linux386 => "386", + Self::LinuxLoong64 => "loong64", + Self::LinuxMips => "mips", + Self::LinuxMipsle => "mipsle", + Self::LinuxMips64 => "mips64", + Self::LinuxMips64le => "mips64le", + Self::LinuxPpc64 => "ppc64", + Self::LinuxPpc64le => "ppc64le", + Self::LinuxRiscv64 => "riscv64", + Self::LinuxS390x => "s390x", + } + } + + /// The variant of the platform. + #[must_use] + pub const fn variant(&self) -> Option<&str> { + match *self { + Self::LinuxAmd64V2 => Some("v2"), + Self::LinuxArmV6 => Some("v6"), + Self::LinuxArmV7 => Some("v7"), + _ => None, } } } @@ -212,18 +273,65 @@ impl std::fmt::Display for Platform { f, "{}", match *self { - Self::Native => match std::env::consts::ARCH { - "x86_64" => "linux/amd64", - "aarch64" => "linux/arm64", - arch => unimplemented!("Arch {arch} is unsupported"), - }, Self::LinuxAmd64 => "linux/amd64", + Self::LinuxAmd64V2 => "linux/amd64/v2", Self::LinuxArm64 => "linux/arm64", + Self::LinuxArm => "linux/arm", + Self::LinuxArmV6 => "linux/arm/v6", + Self::LinuxArmV7 => "linux/arm/v7", + Self::Linux386 => "linux/386", + Self::LinuxLoong64 => "linux/loong64", + Self::LinuxMips => "linux/mips", + Self::LinuxMipsle => "linux/mipsle", + Self::LinuxMips64 => "linux/mips64", + Self::LinuxMips64le => "linux/mips64le", + Self::LinuxPpc64 => "linux/ppc64", + Self::LinuxPpc64le => "linux/ppc64le", + Self::LinuxRiscv64 => "linux/riscv64", + Self::LinuxS390x => "linux/s390x", } ) } } +impl private::Private for Option {} + +pub trait PlatformInfo: private::Private { + /// The string representation of the platform. + /// + /// If `None`, then the native architecture will be used. + fn to_string(&self) -> String; + + /// The string representation of the architecture. + /// + /// If `None`, then the native architecture will be used. + fn arch(&self) -> &str; +} + +impl PlatformInfo for Option { + fn to_string(&self) -> String { + self.map_or_else( + || match std::env::consts::ARCH { + "x86_64" => string!("linux/amd64"), + "aarch64" => string!("linux/arm64"), + arch => unimplemented!("Arch {arch} is unsupported"), + }, + |platform| format!("{platform}"), + ) + } + + fn arch(&self) -> &str { + self.as_ref().map_or_else( + || match std::env::consts::ARCH { + "x86_64" => "amd64", + "aarch64" => "arm64", + arch => unimplemented!("Arch {arch} is unsupported"), + }, + Platform::arch, + ) + } +} + #[derive(Deserialize, Debug, Clone)] #[serde(rename_all = "PascalCase")] pub struct ImageMetadata { diff --git a/src/commands/build.rs b/src/commands/build.rs index 2df0ca4..41463bf 100644 --- a/src/commands/build.rs +++ b/src/commands/build.rs @@ -57,9 +57,8 @@ pub struct BuildCommand { /// than your hardware will require installing /// qemu. Build times will be much greater when /// building for a non-native architecture. - #[arg(long, default_value = "native")] - #[builder(default)] - platform: Platform, + #[arg(long)] + platform: Option, /// The compression format the images /// will be pushed in. @@ -198,7 +197,7 @@ impl BlueBuildCommand for BuildCommand { PathBuf::from(CONTAINER_FILE) })) .skip_validation(self.skip_validation) - .platform(self.platform) + .maybe_platform(self.platform) .recipe(recipe) .drivers(self.drivers) .build() @@ -249,7 +248,7 @@ impl BuildCommand { &GenerateTagsOpts::builder() .oci_ref(&recipe.base_image_ref()?) .maybe_alt_tags(recipe.alt_tags.as_ref().map(CowCollecter::collect_cow_vec)) - .platform(self.platform) + .maybe_platform(self.platform) .build(), )?; let image_name = self.image_name(&recipe)?; @@ -283,7 +282,7 @@ impl BuildCommand { BuildTagPushOpts::builder() .image(&image) .containerfile(containerfile) - .platform(self.platform) + .maybe_platform(self.platform) .tags(tags.collect_cow_vec()) .push(self.push) .retry_push(self.retry_push) @@ -298,7 +297,7 @@ impl BuildCommand { |archive_dir| { BuildTagPushOpts::builder() .containerfile(containerfile) - .platform(self.platform) + .maybe_platform(self.platform) .image(PathBuf::from(format!( "{}/{}.{ARCHIVE_SUFFIX}", archive_dir.to_string_lossy().trim_end_matches('/'), @@ -319,7 +318,7 @@ impl BuildCommand { .image(&image) .retry_push(self.retry_push) .retry_count(self.retry_count) - .platform(self.platform) + .maybe_platform(self.platform) .build(), )?; } @@ -346,14 +345,14 @@ impl BuildCommand { &RechunkOpts::builder() .image(image_name) .containerfile(containerfile) - .platform(self.platform) + .maybe_platform(self.platform) .tags(tags.collect_cow_vec()) .push(self.push) .version(format!( "{version}.", version = Driver::get_os_version() .oci_ref(&recipe.base_image_ref()?) - .platform(self.platform) + .maybe_platform(self.platform) .call()?, )) .retry_push(self.retry_push) @@ -363,7 +362,7 @@ impl BuildCommand { Driver::get_metadata( &GetMetadataOpts::builder() .image(&base_image) - .platform(self.platform) + .maybe_platform(self.platform) .build(), )? .digest, diff --git a/src/commands/generate.rs b/src/commands/generate.rs index 565ab19..bfe5566 100644 --- a/src/commands/generate.rs +++ b/src/commands/generate.rs @@ -73,8 +73,7 @@ pub struct GenerateCommand { /// Inspect the image for a specific platform /// when retrieving the version. #[arg(long, default_value = "native")] - #[builder(default)] - platform: Platform, + platform: Option, /// Skips validation of the recipe file. #[arg(long, env = BB_SKIP_VALIDATION)] @@ -148,7 +147,7 @@ impl GenerateCommand { .os_version( Driver::get_os_version() .oci_ref(&recipe.base_image_ref()?) - .platform(self.platform) + .maybe_platform(self.platform) .call()?, ) .build_id(Driver::get_build_id()) @@ -161,7 +160,7 @@ impl GenerateCommand { Driver::get_metadata( &GetMetadataOpts::builder() .image(&base_image) - .platform(self.platform) + .maybe_platform(self.platform) .build(), )? .digest, @@ -186,14 +185,14 @@ impl GenerateCommand { #[cached( result = true, - key = "Platform", + key = "Option", convert = r#"{ platform }"#, sync_writes = "by_key" )] -fn determine_scripts_tag(platform: Platform) -> Result { +fn determine_scripts_tag(platform: Option) -> Result { trace!("determine_scripts_tag({platform:?})"); - let opts = GetMetadataOpts::builder().platform(platform); + let opts = GetMetadataOpts::builder().maybe_platform(platform); format!("{BUILD_SCRIPTS_IMAGE_REF}:{}", shadow::COMMIT_HASH) .parse() .into_diagnostic()