diff --git a/.github/workflows/build-pr.yml b/.github/workflows/build-pr.yml index 6982b669..d840d4c7 100644 --- a/.github/workflows/build-pr.yml +++ b/.github/workflows/build-pr.yml @@ -258,6 +258,58 @@ jobs: grep -q 'ARG IMAGE_REGISTRY=ghcr.io/blue-build' Containerfile || exit 1 bluebuild build --retry-push -B docker -I docker -S sigstore --push -vv recipes/recipe.yml recipes/recipe-39.yml + arm64-build: + timeout-minutes: 60 + runs-on: ubuntu-latest + permissions: + contents: read + packages: write + id-token: write + needs: + - build + if: needs.build.outputs.push == 'true' + + steps: + - name: Maximize build space + uses: ublue-os/remove-unwanted-software@v6 + + - uses: sigstore/cosign-installer@v3.3.0 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + with: + install: true + + - uses: actions-rust-lang/setup-rust-toolchain@v1 + + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + ref: ${{github.event.pull_request.head.ref}} + repository: ${{github.event.pull_request.head.repo.full_name}} + + - name: Install bluebuild + run: | + cargo install --path . --debug --all-features + + - name: Expose GitHub Runtime + uses: crazy-max/ghaction-github-runtime@v3 + + - name: Run Build + env: + GH_TOKEN: ${{ github.token }} + GH_PR_EVENT_NUMBER: ${{ github.event.number }} + COSIGN_PRIVATE_KEY: ${{ secrets.TEST_SIGNING_SECRET }} + BB_BUILDKIT_CACHE_GHA: true + run: | + cd integration-tests/test-repo + bluebuild build \ + --retry-push \ + --platform linux/arm64 \ + --push \ + -vv \ + recipes/recipe-arm64.yml + docker-build-external-login: timeout-minutes: 60 runs-on: ubuntu-latest diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index b0fe04ba..86d64bef 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -256,6 +256,58 @@ jobs: grep -q 'ARG IMAGE_REGISTRY=ghcr.io/blue-build' Containerfile || exit 1 bluebuild build --retry-push -B docker -I docker -S sigstore --push -vv recipes/recipe.yml recipes/recipe-39.yml + arm64-build: + timeout-minutes: 60 + runs-on: ubuntu-latest + permissions: + contents: read + packages: write + id-token: write + if: github.repository == 'blue-build/cli' + needs: + - build + + steps: + - name: Maximize build space + uses: ublue-os/remove-unwanted-software@v6 + + - uses: sigstore/cosign-installer@v3.3.0 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + with: + install: true + + - uses: actions-rust-lang/setup-rust-toolchain@v1 + + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + ref: ${{github.event.pull_request.head.ref}} + repository: ${{github.event.pull_request.head.repo.full_name}} + + - name: Install bluebuild + run: | + cargo install --path . --debug --all-features + + - name: Expose GitHub Runtime + uses: crazy-max/ghaction-github-runtime@v3 + + - name: Run Build + env: + GH_TOKEN: ${{ github.token }} + GH_PR_EVENT_NUMBER: ${{ github.event.number }} + COSIGN_PRIVATE_KEY: ${{ secrets.TEST_SIGNING_SECRET }} + BB_BUILDKIT_CACHE_GHA: true + run: | + cd integration-tests/test-repo + bluebuild build \ + --retry-push \ + --platform linux/arm64 \ + --push \ + -vv \ + recipes/recipe-arm64.yml + docker-build-external-login: timeout-minutes: 60 runs-on: ubuntu-latest diff --git a/integration-tests/test-repo/recipes/recipe-arm64.yml b/integration-tests/test-repo/recipes/recipe-arm64.yml new file mode 100644 index 00000000..5e802d38 --- /dev/null +++ b/integration-tests/test-repo/recipes/recipe-arm64.yml @@ -0,0 +1,38 @@ +name: cli/test +description: This is my personal OS image. +base-image: quay.io/fedora/fedora-silverblue +image-version: 40 +alt_tags: + - arm64 +stages: +modules: + - from-file: akmods.yml + - from-file: flatpaks.yml + + - type: files + files: + - usr: /usr + + - type: script + scripts: + - example.sh + + - type: rpm-ostree + repos: + - https://copr.fedorainfracloud.org/coprs/atim/starship/repo/fedora-%OS_VERSION%/atim-starship-fedora-%OS_VERSION%.repo + install: + - micro + - starship + remove: + - firefox + - firefox-langpacks + + - type: signing + + - type: test-module + + - type: containerfile + containerfiles: + - labels + snippets: + - RUN echo "This is a snippet" && ostree container commit diff --git a/process/drivers.rs b/process/drivers.rs index 6b75a6ce..31174160 100644 --- a/process/drivers.rs +++ b/process/drivers.rs @@ -12,7 +12,7 @@ use std::{ time::Duration, }; -use bon::Builder; +use bon::{bon, Builder}; use cached::proc_macro::cached; use clap::Args; use colored::Colorize; @@ -24,6 +24,7 @@ use once_cell::sync::Lazy; use opts::{GenerateImageNameOpts, GenerateTagsOpts}; #[cfg(feature = "sigstore")] use sigstore_driver::SigstoreDriver; +use types::Platform; use uuid::Uuid; use crate::logging::Logger; @@ -152,6 +153,7 @@ macro_rules! impl_driver_init { pub struct Driver; +#[bon] impl Driver { /// Initializes the Strategy with user provided credentials. /// @@ -192,7 +194,14 @@ impl Driver { /// /// # Panics /// Panics if the mutex fails to lock. - pub fn get_os_version(oci_ref: &Reference) -> Result { + #[builder] + 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, + ) -> Result { #[cfg(test)] { let _ = oci_ref; // silence lint @@ -203,7 +212,7 @@ impl Driver { } trace!("Driver::get_os_version({oci_ref:#?})"); - get_version(oci_ref) + get_version(oci_ref, platform) } fn get_build_driver() -> BuildDriverType { @@ -230,10 +239,10 @@ impl Driver { #[cached( result = true, key = "String", - convert = "{ oci_ref.to_string() }", + convert = r#"{ format!("{oci_ref}-{platform}") }"#, sync_writes = true )] -fn get_version(oci_ref: &Reference) -> Result { +fn get_version(oci_ref: &Reference, platform: Platform) -> Result { info!("Retrieving OS version from {oci_ref}. This might take a bit"); let inspect_opts = GetMetadataOpts::builder() .image(format!( @@ -242,6 +251,7 @@ fn get_version(oci_ref: &Reference) -> Result { oci_ref.repository() )) .tag(oci_ref.tag().unwrap_or("latest")) + .platform(platform) .build(); let os_version = Driver::get_metadata(&inspect_opts) .and_then(|inspection| { diff --git a/process/drivers/buildah_driver.rs b/process/drivers/buildah_driver.rs index 773ebda5..2dc77c95 100644 --- a/process/drivers/buildah_driver.rs +++ b/process/drivers/buildah_driver.rs @@ -6,7 +6,7 @@ use miette::{bail, miette, IntoDiagnostic, Result}; use semver::Version; use serde::Deserialize; -use crate::logging::CommandLogging; +use crate::{drivers::types::Platform, logging::CommandLogging}; use super::{ opts::{BuildOpts, PushOpts, TagOpts}, @@ -50,6 +50,10 @@ impl BuildDriver for BuildahDriver { let command = cmd!( "buildah", "build", + if !matches!(opts.platform, Platform::Native) => [ + "--platform", + opts.platform.to_string(), + ], "--pull=true", format!("--layers={}", !opts.squash), "-f", diff --git a/process/drivers/docker_driver.rs b/process/drivers/docker_driver.rs index 0f1055b7..675740d9 100644 --- a/process/drivers/docker_driver.rs +++ b/process/drivers/docker_driver.rs @@ -12,6 +12,7 @@ use blue_build_utils::{ constants::{BB_BUILDKIT_CACHE_GHA, CONTAINER_FILE, DOCKER_HOST, SKOPEO_IMAGE}, credentials::Credentials, string_vec, + traits::IntoCollector, }; use colored::Colorize; use indicatif::{ProgressBar, ProgressStyle}; @@ -26,6 +27,7 @@ use crate::{ drivers::{ image_metadata::ImageMetadata, opts::{RunOptsEnv, RunOptsVolume}, + types::Platform, }, logging::{CommandLogging, Logger}, signal_handler::{add_cid, remove_cid, ContainerId, ContainerRuntime}, @@ -130,6 +132,10 @@ impl BuildDriver for DockerDriver { let status = cmd!( "docker", "build", + if !matches!(opts.platform, Platform::Native) => [ + "--platform", + opts.platform.to_string(), + ], "-t", &*opts.image, "-f", @@ -235,6 +241,10 @@ impl BuildDriver for DockerDriver { }, "build", "--pull", + if !matches!(opts.platform, Platform::Native) => [ + "--platform", + opts.platform.to_string(), + ], "-f", &*opts.containerfile, // https://github.com/moby/buildkit?tab=readme-ov-file#github-actions-cache-experimental @@ -322,10 +332,16 @@ impl InspectDriver for DockerDriver { ); progress.enable_steady_tick(Duration::from_millis(100)); + let mut args = Vec::new(); + if !matches!(opts.platform, Platform::Native) { + args.extend(["--override-arch", opts.platform.arch()]); + } + args.extend(["inspect", &url]); + let output = Self::run_output( &RunOpts::builder() .image(SKOPEO_IMAGE) - .args(bon::vec!["inspect", &url]) + .args(args.collect_into_vec()) .remove(true) .build(), ) diff --git a/process/drivers/github_driver.rs b/process/drivers/github_driver.rs index ae27f97f..563317d6 100644 --- a/process/drivers/github_driver.rs +++ b/process/drivers/github_driver.rs @@ -37,8 +37,11 @@ impl CiDriver for GithubDriver { fn generate_tags(opts: &GenerateTagsOpts) -> miette::Result> { const PR_EVENT: &str = "pull_request"; let timestamp = blue_build_utils::get_tag_timestamp(); - let os_version = - Driver::get_os_version(opts.oci_ref).inspect(|v| trace!("os_version={v}"))?; + let os_version = Driver::get_os_version() + .oci_ref(opts.oci_ref) + .platform(opts.platform) + .call() + .inspect(|v| trace!("os_version={v}"))?; let ref_name = get_env_var(GITHUB_REF_NAME).inspect(|v| trace!("{GITHUB_REF_NAME}={v}"))?; let short_sha = { let mut short_sha = get_env_var(GITHUB_SHA).inspect(|v| trace!("{GITHUB_SHA}={v}"))?; @@ -144,7 +147,7 @@ mod test { use rstest::rstest; use crate::{ - drivers::{opts::GenerateTagsOpts, CiDriver}, + drivers::{opts::GenerateTagsOpts, types::Platform, CiDriver}, test::{TEST_TAG_1, TEST_TAG_2, TIMESTAMP}, }; @@ -285,6 +288,7 @@ mod test { &GenerateTagsOpts::builder() .oci_ref(&oci_ref) .maybe_alt_tags(alt_tags) + .platform(Platform::LinuxAmd64) .build(), ) .unwrap(); diff --git a/process/drivers/gitlab_driver.rs b/process/drivers/gitlab_driver.rs index 7cc52841..bbac949e 100644 --- a/process/drivers/gitlab_driver.rs +++ b/process/drivers/gitlab_driver.rs @@ -45,7 +45,10 @@ impl CiDriver for GitlabDriver { fn generate_tags(opts: &GenerateTagsOpts) -> miette::Result> { const MR_EVENT: &str = "merge_request_event"; - let os_version = Driver::get_os_version(opts.oci_ref)?; + let os_version = Driver::get_os_version() + .oci_ref(opts.oci_ref) + .platform(opts.platform) + .call()?; let timestamp = blue_build_utils::get_tag_timestamp(); let short_sha = get_env_var(CI_COMMIT_SHORT_SHA).inspect(|v| trace!("{CI_COMMIT_SHORT_SHA}={v}"))?; @@ -293,6 +296,7 @@ mod test { &GenerateTagsOpts::builder() .oci_ref(&oci_ref) .maybe_alt_tags(alt_tags) + .platform(crate::drivers::types::Platform::LinuxAmd64) .build(), ) .unwrap(); diff --git a/process/drivers/local_driver.rs b/process/drivers/local_driver.rs index acd2b21b..955a6e15 100644 --- a/process/drivers/local_driver.rs +++ b/process/drivers/local_driver.rs @@ -24,7 +24,10 @@ impl CiDriver for LocalDriver { fn generate_tags(opts: &GenerateTagsOpts) -> miette::Result> { trace!("LocalDriver::generate_tags({opts:?})"); - let os_version = Driver::get_os_version(opts.oci_ref)?; + let os_version = Driver::get_os_version() + .oci_ref(opts.oci_ref) + .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 2eff5230..0810f69a 100644 --- a/process/drivers/opts/build.rs +++ b/process/drivers/opts/build.rs @@ -2,6 +2,8 @@ use std::{borrow::Cow, path::Path}; use bon::Builder; +use crate::drivers::types::Platform; + use super::CompressionType; /// Options for building @@ -15,14 +17,15 @@ pub struct BuildOpts<'scope> { #[builder(into)] pub containerfile: Cow<'scope, Path>, + + #[builder(default)] + pub platform: Platform, } #[derive(Debug, Clone, Builder)] +#[builder(on(Cow<'_, str>, into))] pub struct TagOpts<'scope> { - #[builder(into)] pub src_image: Cow<'scope, str>, - - #[builder(into)] pub dest_image: Cow<'scope, str>, } @@ -80,4 +83,8 @@ pub struct BuildTagPushOpts<'scope> { /// Run all steps in a single layer. #[builder(default)] pub squash: bool, + + /// The platform to build the image on. + #[builder(default)] + pub platform: Platform, } diff --git a/process/drivers/opts/ci.rs b/process/drivers/opts/ci.rs index 2a64dc9f..d828376e 100644 --- a/process/drivers/opts/ci.rs +++ b/process/drivers/opts/ci.rs @@ -3,12 +3,17 @@ use std::borrow::Cow; use bon::Builder; use oci_distribution::Reference; +use crate::drivers::types::Platform; + #[derive(Debug, Clone, Builder)] pub struct GenerateTagsOpts<'scope> { pub oci_ref: &'scope Reference, #[builder(into)] pub alt_tags: Option>>, + + #[builder(default)] + pub platform: Platform, } #[derive(Debug, Clone, Builder)] diff --git a/process/drivers/opts/inspect.rs b/process/drivers/opts/inspect.rs index 95b0036e..0f75422d 100644 --- a/process/drivers/opts/inspect.rs +++ b/process/drivers/opts/inspect.rs @@ -2,6 +2,8 @@ use std::borrow::Cow; use bon::Builder; +use crate::drivers::types::Platform; + #[derive(Debug, Clone, Builder)] pub struct GetMetadataOpts<'scope> { #[builder(into)] @@ -9,4 +11,7 @@ pub struct GetMetadataOpts<'scope> { #[builder(into)] pub tag: Option>, + + #[builder(default)] + pub platform: Platform, } diff --git a/process/drivers/opts/signing.rs b/process/drivers/opts/signing.rs index 8a43e37f..e9e31715 100644 --- a/process/drivers/opts/signing.rs +++ b/process/drivers/opts/signing.rs @@ -8,6 +8,8 @@ use bon::Builder; use miette::{IntoDiagnostic, Result}; use zeroize::{Zeroize, Zeroizing}; +use crate::drivers::types::Platform; + pub enum PrivateKey { Env(String), Path(PathBuf), @@ -112,4 +114,7 @@ pub struct SignVerifyOpts<'scope> { /// Defaults to 1. #[builder(default = 1)] pub retry_count: u8, + + #[builder(default)] + pub platform: Platform, } diff --git a/process/drivers/podman_driver.rs b/process/drivers/podman_driver.rs index 651bb5aa..685d4526 100644 --- a/process/drivers/podman_driver.rs +++ b/process/drivers/podman_driver.rs @@ -5,7 +5,9 @@ use std::{ time::Duration, }; -use blue_build_utils::{cmd, constants::SKOPEO_IMAGE, credentials::Credentials}; +use blue_build_utils::{ + cmd, constants::SKOPEO_IMAGE, credentials::Credentials, traits::IntoCollector, +}; use colored::Colorize; use indicatif::{ProgressBar, ProgressStyle}; use log::{debug, error, info, trace, warn}; @@ -18,6 +20,7 @@ use crate::{ drivers::{ image_metadata::ImageMetadata, opts::{RunOptsEnv, RunOptsVolume}, + types::Platform, }, logging::{CommandLogging, Logger}, signal_handler::{add_cid, remove_cid, ContainerId, ContainerRuntime}, @@ -72,6 +75,10 @@ impl BuildDriver for PodmanDriver { let command = cmd!( "podman", "build", + if !matches!(opts.platform, Platform::Native) => [ + "--platform", + opts.platform.to_string(), + ], "--pull=true", format!("--layers={}", !opts.squash), "-f", @@ -198,10 +205,16 @@ impl InspectDriver for PodmanDriver { ); progress.enable_steady_tick(Duration::from_millis(100)); + let mut args = Vec::new(); + if !matches!(opts.platform, Platform::Native) { + args.extend(["--override-arch", opts.platform.arch()]); + } + args.extend(["inspect", &url]); + let output = Self::run_output( &RunOpts::builder() .image(SKOPEO_IMAGE) - .args(bon::vec!["inspect", &url]) + .args(args.collect_into_vec()) .remove(true) .build(), ) diff --git a/process/drivers/skopeo_driver.rs b/process/drivers/skopeo_driver.rs index d0e1c1c7..b54351ce 100644 --- a/process/drivers/skopeo_driver.rs +++ b/process/drivers/skopeo_driver.rs @@ -6,7 +6,7 @@ use indicatif::{ProgressBar, ProgressStyle}; use log::{debug, trace}; use miette::{bail, IntoDiagnostic, Result}; -use crate::logging::Logger; +use crate::{drivers::types::Platform, logging::Logger}; use super::{image_metadata::ImageMetadata, opts::GetMetadataOpts, InspectDriver}; @@ -29,11 +29,19 @@ impl InspectDriver for SkopeoDriver { ); progress.enable_steady_tick(Duration::from_millis(100)); - trace!("skopeo inspect {url}"); - let output = cmd!("skopeo", "inspect", &url) - .stderr(Stdio::inherit()) - .output() - .into_diagnostic()?; + let mut command = cmd!( + "skopeo", + if !matches!(opts.platform, Platform::Native) => [ + "--override-arch", + opts.platform.arch(), + ], + "inspect", + &url, + stderr = Stdio::inherit(), + ); + trace!("{command:?}"); + + let output = command.output().into_diagnostic()?; progress.finish_and_clear(); Logger::multi_progress().remove(&progress); diff --git a/process/drivers/traits.rs b/process/drivers/traits.rs index 6075fe83..49dde33b 100644 --- a/process/drivers/traits.rs +++ b/process/drivers/traits.rs @@ -90,6 +90,7 @@ pub trait BuildDriver { let build_opts = BuildOpts::builder() .image(&full_image) .containerfile(opts.containerfile.as_ref()) + .platform(opts.platform) .squash(opts.squash) .build(); @@ -212,10 +213,12 @@ pub trait SigningDriver { .map_or_else(|| PathBuf::from("."), |d| d.to_path_buf()); let image_name: &str = opts.image.as_ref(); - let inspect_opts = GetMetadataOpts::builder().image(image_name); + let inspect_opts = GetMetadataOpts::builder() + .image(image_name) + .platform(opts.platform); let inspect_opts = if let Some(ref tag) = opts.tag { - inspect_opts.tag(tag.as_ref() as &str).build() + inspect_opts.tag(&**tag).build() } else { inspect_opts.build() }; diff --git a/process/drivers/types.rs b/process/drivers/types.rs index 45b177c7..8ae5fd84 100644 --- a/process/drivers/types.rs +++ b/process/drivers/types.rs @@ -167,3 +167,41 @@ impl DetermineDriver for Option { ) } } + +#[derive(Debug, Default, Clone, Copy, ValueEnum)] +pub enum Platform { + #[default] + #[value(name = "native")] + Native, + #[value(name = "linux/amd64")] + LinuxAmd64, + + #[value(name = "linux/arm64")] + LinuxArm64, +} + +impl Platform { + /// The architecture of the platform. + #[must_use] + pub const fn arch(&self) -> &str { + match *self { + Self::Native => "native", + Self::LinuxAmd64 => "amd64", + Self::LinuxArm64 => "arm64", + } + } +} + +impl std::fmt::Display for Platform { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!( + f, + "{}", + match *self { + Self::Native => "native", + Self::LinuxAmd64 => "linux/amd64", + Self::LinuxArm64 => "linux/arm64", + } + ) + } +} diff --git a/scripts/exports.sh b/scripts/exports.sh index 47d6336d..c0c9569d 100644 --- a/scripts/exports.sh +++ b/scripts/exports.sh @@ -46,6 +46,7 @@ color_string() { # Parse OS version and export it export OS_VERSION=$(grep -Po "(?<=VERSION_ID=)\d+" /usr/lib/os-release) +export OS_ARCH=$(uname -m) # Export functions for use in sub-shells or sourced scripts export -f get_yaml_array diff --git a/src/commands/build.rs b/src/commands/build.rs index 8dd96b0f..4ee7ea4b 100644 --- a/src/commands/build.rs +++ b/src/commands/build.rs @@ -9,6 +9,7 @@ use blue_build_process_management::{ BuildTagPushOpts, CheckKeyPairOpts, CompressionType, GenerateImageNameOpts, GenerateTagsOpts, SignVerifyOpts, }, + types::Platform, BuildDriver, CiDriver, Driver, DriverArgs, SigningDriver, }, logging::{color_str, gen_random_ansi_color}, @@ -58,6 +59,16 @@ pub struct BuildCommand { #[builder(default)] push: bool, + /// Build for a specific platform. + /// + /// NOTE: Building for a different architecture + /// 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, + /// The compression format the images /// will be pushed in. #[arg(short, long, default_value_t = CompressionType::Gzip)] @@ -260,7 +271,8 @@ impl BuildCommand { let tags = Driver::generate_tags( &GenerateTagsOpts::builder() .oci_ref(&recipe.base_image_ref()?) - .maybe_alt_tags(recipe.alt_tags.as_ref().map(CowCollecter::to_cow_vec)) + .maybe_alt_tags(recipe.alt_tags.as_ref().map(CowCollecter::collect_cow_vec)) + .platform(self.platform) .build(), )?; let image_name = self.image_name(&recipe)?; @@ -268,6 +280,7 @@ impl BuildCommand { let opts = if let Some(archive_dir) = self.archive.as_ref() { BuildTagPushOpts::builder() .containerfile(containerfile) + .platform(self.platform) .archive_path(format!( "{}/{}.{ARCHIVE_SUFFIX}", archive_dir.to_string_lossy().trim_end_matches('/'), @@ -279,7 +292,8 @@ impl BuildCommand { BuildTagPushOpts::builder() .image(&image_name) .containerfile(containerfile) - .tags(tags.to_cow_vec()) + .platform(self.platform) + .tags(tags.collect_cow_vec()) .push(self.push) .retry_push(self.retry_push) .retry_count(self.retry_count) @@ -297,6 +311,7 @@ impl BuildCommand { .retry_push(self.retry_push) .retry_count(self.retry_count) .maybe_tag(tags.first()) + .platform(self.platform) .build(), )?; } diff --git a/src/commands/generate.rs b/src/commands/generate.rs index 24308e89..e4adf398 100644 --- a/src/commands/generate.rs +++ b/src/commands/generate.rs @@ -3,7 +3,7 @@ use std::{ path::{Path, PathBuf}, }; -use blue_build_process_management::drivers::{CiDriver, Driver, DriverArgs}; +use blue_build_process_management::drivers::{types::Platform, CiDriver, Driver, DriverArgs}; use blue_build_recipe::Recipe; use blue_build_template::{ContainerFileTemplate, Template}; use blue_build_utils::{ @@ -63,6 +63,12 @@ pub struct GenerateCommand { #[arg(short = 't', long)] syntax_theme: Option, + /// Inspect the image for a specific platform + /// when retrieving the version. + #[arg(long, default_value = "native")] + #[builder(default)] + platform: Platform, + #[clap(flatten)] #[builder(default)] drivers: DriverArgs, @@ -115,7 +121,12 @@ impl GenerateCommand { info!("Templating for recipe at {}", recipe_path.display()); let template = ContainerFileTemplate::builder() - .os_version(Driver::get_os_version(&recipe.base_image_ref()?)?) + .os_version( + Driver::get_os_version() + .oci_ref(&recipe.base_image_ref()?) + .platform(self.platform) + .call()?, + ) .build_id(Driver::get_build_id()) .recipe(&recipe) .recipe_path(recipe_path.as_path()) diff --git a/src/commands/generate_iso.rs b/src/commands/generate_iso.rs index b493ed83..60b024f5 100644 --- a/src/commands/generate_iso.rs +++ b/src/commands/generate_iso.rs @@ -203,7 +203,10 @@ impl GenerateIsoCommand { format!("IMAGE_NAME={image_name}",), format!("IMAGE_REPO={image_repo}"), format!("IMAGE_TAG={}", image.tag().unwrap_or("latest")), - format!("VERSION={}", Driver::get_os_version(&image)?), + format!( + "VERSION={}", + Driver::get_os_version().oci_ref(&image).call()? + ), ]); } GenIsoSubcommand::Recipe { recipe } => { @@ -216,7 +219,9 @@ impl GenerateIsoCommand { ), format!( "VERSION={}", - Driver::get_os_version(&recipe.base_image_ref()?)?, + Driver::get_os_version() + .oci_ref(&recipe.base_image_ref()?) + .call()?, ), ]); vols.extend(run_volumes![ @@ -230,7 +235,7 @@ impl GenerateIsoCommand { .image("ghcr.io/jasonn3/build-container-installer") .privileged(true) .remove(true) - .args(args.to_cow_vec()) + .args(args.collect_cow_vec()) .volumes(vols) .build(); diff --git a/utils/src/traits.rs b/utils/src/traits.rs index fafa0b87..a2ca544e 100644 --- a/utils/src/traits.rs +++ b/utils/src/traits.rs @@ -1,26 +1,8 @@ -use std::{ - borrow::Cow, - ffi::{OsStr, OsString}, - path::{Path, PathBuf}, -}; +use std::{borrow::Cow, ffi::OsStr, path::Path}; -trait PrivateTrait {} +trait PrivateTrait: IntoIterator {} -macro_rules! impl_private_trait { - ($lt:lifetime, $type:ty) => { - impl<$lt, T> PrivateTrait<$type> for T where T: AsRef<[&$lt $type]> {} - }; - ($type:ty) => { - impl PrivateTrait<$type> for T where T: AsRef<[$type]> {} - }; -} - -impl_private_trait!(String); -impl_private_trait!('a, str); -impl_private_trait!(PathBuf); -impl_private_trait!('a, Path); -impl_private_trait!(OsString); -impl_private_trait!('a, OsStr); +impl PrivateTrait for T where T: IntoIterator {} #[allow(private_bounds)] pub trait CowCollecter<'a, IN, OUT>: PrivateTrait @@ -28,35 +10,91 @@ where IN: ToOwned + ?Sized, OUT: ToOwned + ?Sized, { - fn to_cow_vec(&'a self) -> Vec>; + fn collect_cow_vec(&'a self) -> Vec>; +} + +impl<'a, T, R> CowCollecter<'a, R, R> for T +where + T: AsRef<[R]> + IntoIterator, + R: ToOwned, +{ + fn collect_cow_vec(&'a self) -> Vec> { + self.as_ref().iter().map(Cow::Borrowed).collect() + } } macro_rules! impl_cow_collector { ($type:ty) => { - impl<'a, T> CowCollecter<'a, $type, $type> for T + impl<'a, T, R> CowCollecter<'a, R, $type> for T where - T: AsRef<[&'a $type]>, + T: AsRef<[R]> + IntoIterator, + R: AsRef<$type> + ToOwned + 'a, { - fn to_cow_vec(&'a self) -> Vec> { - self.as_ref().iter().map(|v| Cow::Borrowed(*v)).collect() + fn collect_cow_vec(&'a self) -> Vec> { + self.as_ref() + .iter() + .map(|v| v.as_ref()) + .map(Cow::from) + .collect() } } }; - ($in:ty, $out:ty) => { - impl<'a, T> CowCollecter<'a, $in, $out> for T +} + +impl_cow_collector!(str); +impl_cow_collector!(Path); +impl_cow_collector!(OsStr); + +#[allow(private_bounds)] +pub trait AsRefCollector<'a, IN, OUT>: PrivateTrait +where + IN: ?Sized, + OUT: ?Sized, +{ + fn collect_as_ref_vec(&'a self) -> Vec<&'a OUT>; +} + +impl<'a, T, R> AsRefCollector<'a, R, R> for T +where + T: AsRef<[R]> + IntoIterator, +{ + fn collect_as_ref_vec(&'a self) -> Vec<&'a R> { + self.as_ref().iter().collect() + } +} + +macro_rules! impl_asref_collector { + ($type:ty) => { + impl<'a, T, R> AsRefCollector<'a, R, $type> for T where - T: AsRef<[$in]>, + T: AsRef<[R]> + IntoIterator, + R: AsRef<$type> + 'a, { - fn to_cow_vec(&'a self) -> Vec> { - self.as_ref().iter().map(Cow::from).collect() + fn collect_as_ref_vec(&'a self) -> Vec<&'a $type> { + self.as_ref().iter().map(AsRef::as_ref).collect() } } }; } -impl_cow_collector!(String, str); -impl_cow_collector!(str); -impl_cow_collector!(PathBuf, Path); -impl_cow_collector!(Path); -impl_cow_collector!(OsString, OsStr); -impl_cow_collector!(OsStr); +impl_asref_collector!(str); +impl_asref_collector!(Path); +impl_asref_collector!(OsStr); + +#[allow(private_bounds)] +pub trait IntoCollector: PrivateTrait +where + IN: Into, +{ + fn collect_into_vec(self) -> Vec; +} + +impl IntoCollector for T +where + T: IntoIterator, + U: Into, +{ + fn collect_into_vec(self) -> Vec { + self.into_iter().map(Into::into).collect() + } +}