diff --git a/Earthfile b/Earthfile index 640848b..b0ee791 100644 --- a/Earthfile +++ b/Earthfile @@ -1,13 +1,13 @@ VERSION --global-cache 0.7 IMPORT github.com/earthly/lib/rust AS rust -ARG FEDORA_MAJOR_VERSION=38 - -FROM registry.fedoraproject.org/fedora-toolbox:${FEDORA_MAJOR_VERSION} +ARG --global FEDORA_MAJOR_VERSION=38 ARG --global IMAGE=registry.gitlab.com/wunker-bunker/ublue-cli iso-generator: + FROM registry.fedoraproject.org/fedora-toolbox:${FEDORA_MAJOR_VERSION} + GIT CLONE https://github.com/ublue-os/isogenerator.git /isogenerator WORKDIR /isogenerator ARG PACKAGES=$(cat deps.txt) @@ -15,6 +15,10 @@ iso-generator: SAVE IMAGE --push $IMAGE/iso-generator +cosign: + FROM gcr.io/projectsigstore/cosign + SAVE ARTIFACT /ko-app/cosign + install: FROM rust DO rust+INIT --keep_fingerprints=true @@ -22,14 +26,19 @@ install: COPY --keep-ts . /app WORKDIR /app - DO rust+CARGO --args="build --release" --output="release/[^\./]+" + ARG --required TARGET + DO rust+CARGO --args="build --release --target $TARGET" --output="$TARGET/release/[^\./]+" - SAVE ARTIFACT target/release/ublue + SAVE ARTIFACT target/$TARGET/release/ublue ublue-cli: - BUILD +install + FROM registry.fedoraproject.org/fedora-toolbox:${FEDORA_MAJOR_VERSION} + BUILD +install --TARGET="x86_64-unknown-linux-gnu" - COPY +install/ublue /usr/bin/ublue + RUN dnf install --refresh -y buildah podman skopeo + + COPY +cosign/cosign /usr/bin/cosign + COPY (+install/ublue --TARGET="x86_64-unknown-linux-gnu") /usr/bin/ublue ARG TAG IF [ "$TAG" != "" ] @@ -40,8 +49,33 @@ ublue-cli: IF [ "$LATEST" = "true" ] SAVE IMAGE --push $IMAGE:latest END + ELSE + SAVE IMAGE ublue-cli + END + +ublue-cli-alpine: + FROM alpine + BUILD +install --TARGET="x86_64-unknown-linux-musl" + + RUN apk update && apk add buildah podman skopeo fuse-overlayfs + + COPY +cosign/cosign /usr/bin/cosign + COPY (+install/ublue --TARGET="x86_64-unknown-linux-musl") /usr/bin/ublue + + ARG TAG + IF [ "$TAG" != "" ] + SAVE IMAGE --push $IMAGE:$TAG-alpine + + ARG LATEST=false + + IF [ "$LATEST" = "true" ] + SAVE IMAGE --push $IMAGE:alpine + END + ELSE + SAVE IMAGE ublue-cli:alpine END all: BUILD +ublue-cli + BUILD +ublue-cli-alpine BUILD +iso-generator diff --git a/src/build.rs b/src/build.rs index c979e8a..b8e1763 100644 --- a/src/build.rs +++ b/src/build.rs @@ -5,7 +5,6 @@ use std::{ }; use anyhow::{anyhow, bail, Result}; -use chrono::Local; use clap::Args; use log::{debug, error, info, trace, warn}; use typed_builder::TypedBuilder; @@ -82,7 +81,11 @@ impl BuildCommand { fn build_image(&self) -> Result<()> { trace!("BuildCommand::build_image()"); - ops::check_command_exists("buildah")?; + if let Err(e1) = ops::check_command_exists("buildah") { + ops::check_command_exists("podman").map_err(|e2| { + anyhow!("Need either 'buildah' or 'podman' commands to proceed: {e1}, {e2}") + })?; + } if self.push { ops::check_command_exists("cosign")?; @@ -91,7 +94,7 @@ impl BuildCommand { let recipe: Recipe = serde_yaml::from_str(fs::read_to_string(&self.recipe)?.as_str())?; - let tags = self.generate_tags(&recipe); + let tags = recipe.generate_tags(); let image_name = self.generate_full_image_name(&recipe)?; @@ -105,57 +108,6 @@ impl BuildCommand { Ok(()) } - fn generate_tags(&self, recipe: &Recipe) -> Vec { - debug!("Generating image tags for {}", &recipe.name); - trace!("BuildCommand::generate_tags({recipe:#?})"); - - let mut tags: Vec = Vec::new(); - let image_version = recipe.image_version; - let timestamp = Local::now().format("%Y%m%d").to_string(); - - if env::var("CI").is_ok() { - warn!("Detected running in Gitlab, pulling information from CI variables"); - - if let (Ok(mr_iid), Ok(pipeline_source)) = ( - env::var("CI_MERGE_REQUEST_IID"), - env::var("CI_PIPELINE_SOURCE"), - ) { - trace!("CI_MERGE_REQUEST_IID={mr_iid}, CI_PIPELINE_SOURCE={pipeline_source}"); - if pipeline_source == "merge_request_event" { - debug!("Running in a MR"); - tags.push(format!("{mr_iid}-{image_version}")); - } - } - - if let Ok(commit_sha) = env::var("CI_COMMIT_SHORT_SHA") { - trace!("CI_COMMIT_SHORT_SHA={commit_sha}"); - tags.push(format!("{commit_sha}-{image_version}")); - } - - if let (Ok(commit_branch), Ok(default_branch)) = ( - env::var("CI_COMMIT_REF_NAME"), - env::var("CI_DEFAULT_BRANCH"), - ) { - trace!("CI_COMMIT_REF_NAME={commit_branch}, CI_DEFAULT_BRANCH={default_branch}"); - if default_branch != commit_branch { - debug!("Running on branch {commit_branch}"); - tags.push(format!("br-{commit_branch}-{image_version}")); - } else { - debug!("Running on the default branch"); - tags.push(image_version.to_string()); - tags.push(format!("{image_version}-{timestamp}")); - tags.push(timestamp.to_string()); - } - } - } else { - warn!("Running locally"); - tags.push(format!("{image_version}-local")); - } - info!("Finished generating tags!"); - trace!("Tags: {tags:#?}"); - tags - } - fn login(&self) -> Result<()> { info!("Attempting to login to the registry"); trace!("BuildCommand::login()"); @@ -183,11 +135,12 @@ impl BuildCommand { .arg("-p") .arg(&password) .arg(®istry) - .status()? + .output()? + .status .success() { true => info!("Buildah login success at {registry} for user {username}!"), - false => return Err(anyhow!("Failed to login for buildah!")), + false => bail!("Failed to login for buildah!"), } trace!("cosign login -u {username} -p [MASKED] {registry}"); @@ -198,11 +151,12 @@ impl BuildCommand { .arg("-p") .arg(&password) .arg(®istry) - .status()? + .output()? + .status .success() { true => info!("Cosign login success at {registry} for user {username}!"), - false => return Err(anyhow!("Failed to login for cosign!")), + false => bail!("Failed to login for cosign!"), } Ok(()) @@ -260,12 +214,29 @@ impl BuildCommand { let full_image = format!("{image_name}:{first_tag}"); - trace!("buildah build -t {full_image}"); - let status = Command::new("buildah") - .arg("build") - .arg("-t") - .arg(&full_image) - .status()?; + let status = match ( + ops::check_command_exists("buildah"), + ops::check_command_exists("podman"), + ) { + (Ok(_), _) => { + trace!("buildah build -t {full_image}"); + Command::new("buildah") + .arg("build") + .arg("-t") + .arg(&full_image) + .status()? + } + (Err(_), Ok(_)) => { + trace!("podman build . -t {full_image}"); + Command::new("podman") + .arg("build") + .arg(".") + .arg("-t") + .arg(&full_image) + .status()? + } + (Err(e1), Err(e2)) => bail!("Need either 'buildah' or 'podman' to build: {e1}, {e2}"), + }; if status.success() { info!("Successfully built {image_name}"); @@ -281,12 +252,26 @@ impl BuildCommand { let tag_image = format!("{image_name}:{tag}"); - trace!("buildah tag {full_image} {tag_image}"); - let status = Command::new("buildah") - .arg("tag") - .arg(&full_image) - .arg(&tag_image) - .status()?; + let status = match ( + ops::check_command_exists("buildah"), + ops::check_command_exists("podman"), + ) { + (Ok(_), _) => { + trace!("buildah tag {full_image} {tag_image}"); + Command::new("buildah") + } + (Err(_), Ok(_)) => { + trace!("podman tag {full_image} {tag_image}"); + Command::new("podman") + } + (Err(e1), Err(e2)) => { + bail!("Need either 'buildah' or 'podman' to build: {e1}, {e2}") + } + } + .arg("tag") + .arg(&full_image) + .arg(&tag_image) + .status()?; if status.success() { info!("Successfully tagged {image_name}:{tag}!"); @@ -303,11 +288,25 @@ impl BuildCommand { let tag_image = format!("{image_name}:{tag}"); - trace!("buildah push {tag_image}"); - let status = Command::new("buildah") - .arg("push") - .arg(&tag_image) - .status()?; + let status = match ( + ops::check_command_exists("buildah"), + ops::check_command_exists("podman"), + ) { + (Ok(_), _) => { + trace!("buildah push {tag_image}"); + Command::new("buildah") + } + (Err(_), Ok(_)) => { + trace!("podman push {tag_image}"); + Command::new("podman") + } + (Err(e1), Err(e2)) => { + bail!("Need either 'buildah' or 'podman' to build: {e1}, {e2}") + } + } + .arg("push") + .arg(&tag_image) + .status()?; if status.success() { info!("Successfully pushed {image_name}:{tag}!") @@ -397,6 +396,9 @@ impl BuildCommand { if !status.success() { bail!("Failed to verify image!"); } + } else { + warn!("Unable to determine OIDC host, not signing image"); + warn!("Please ensure your build environment has the variables CI_PROJECT_URL, CI_DEFAULT_BRANCH, CI_COMMIT_REF_NAME, CI_SERVER_PROTOCOL, CI_SERVER_HOST") } } } else { diff --git a/src/module_recipe.rs b/src/module_recipe.rs index 5e7e28c..6242605 100644 --- a/src/module_recipe.rs +++ b/src/module_recipe.rs @@ -1,5 +1,7 @@ -use std::collections::HashMap; +use std::{collections::HashMap, env}; +use chrono::Local; +use log::{debug, info, trace, warn}; use serde::{Deserialize, Serialize}; use serde_yaml::Value; @@ -23,6 +25,59 @@ pub struct Recipe { pub extra: HashMap, } +impl Recipe { + pub fn generate_tags(&self) -> Vec { + debug!("Generating image tags for {}", &self.name); + trace!("BuildCommand::generate_tags({self:#?})"); + + let mut tags: Vec = Vec::new(); + let image_version = self.image_version; + let timestamp = Local::now().format("%Y%m%d").to_string(); + + if env::var("CI").is_ok() { + warn!("Detected running in Gitlab, pulling information from CI variables"); + + if let (Ok(mr_iid), Ok(pipeline_source)) = ( + env::var("CI_MERGE_REQUEST_IID"), + env::var("CI_PIPELINE_SOURCE"), + ) { + trace!("CI_MERGE_REQUEST_IID={mr_iid}, CI_PIPELINE_SOURCE={pipeline_source}"); + if pipeline_source == "merge_request_event" { + debug!("Running in a MR"); + tags.push(format!("{mr_iid}-{image_version}")); + } + } + + if let Ok(commit_sha) = env::var("CI_COMMIT_SHORT_SHA") { + trace!("CI_COMMIT_SHORT_SHA={commit_sha}"); + tags.push(format!("{commit_sha}-{image_version}")); + } + + if let (Ok(commit_branch), Ok(default_branch)) = ( + env::var("CI_COMMIT_REF_NAME"), + env::var("CI_DEFAULT_BRANCH"), + ) { + trace!("CI_COMMIT_REF_NAME={commit_branch}, CI_DEFAULT_BRANCH={default_branch}"); + if default_branch != commit_branch { + debug!("Running on branch {commit_branch}"); + tags.push(format!("br-{commit_branch}-{image_version}")); + } else { + debug!("Running on the default branch"); + tags.push(image_version.to_string()); + tags.push(format!("{image_version}-{timestamp}")); + tags.push(timestamp.to_string()); + } + } + } else { + warn!("Running locally"); + tags.push(format!("{image_version}-local")); + } + info!("Finished generating tags!"); + trace!("Tags: {tags:#?}"); + tags + } +} + #[derive(Serialize, Deserialize, Debug)] pub struct Module { #[serde(rename = "type")] diff --git a/src/ops.rs b/src/ops.rs index 466cce4..cee8427 100644 --- a/src/ops.rs +++ b/src/ops.rs @@ -7,11 +7,11 @@ pub fn check_command_exists(command: &str) -> Result<()> { debug!("Checking if {command} exists"); trace!("check_command_exists({command})"); - trace!("command -v {command}"); - match Command::new("command") - .arg("-v") + trace!("which {command}"); + match Command::new("which") .arg(command) - .status()? + .output()? + .status .success() { true => { diff --git a/src/recipe.rs b/src/recipe.rs deleted file mode 100644 index 5d0588d..0000000 --- a/src/recipe.rs +++ /dev/null @@ -1,66 +0,0 @@ -use serde::{Deserialize, Serialize}; - -#[derive(Serialize, Deserialize, Debug)] -pub struct Recipe { - pub name: String, - - #[serde(alias = "base-image")] - pub base_image: String, - - #[serde(alias = "fedora-version")] - pub fedora_version: u16, - - pub scripts: Option, - - pub rpm: Option, - - #[serde(alias = "usr-dir-overlays")] - pub usr_dir_overlays: Option>, - - pub containerfiles: Option, - - pub firstboot: Option, -} - -impl Recipe { - pub fn process_repos(mut self) -> Self { - if let Some(rpm) = &mut self.rpm { - if let Some(repos) = &rpm.repos { - rpm.repos = Some( - repos - .iter() - .map(|s| { - s.replace("%FEDORA_VERSION%", self.fedora_version.to_string().as_str()) - }) - .collect(), - ); - } - } - self - } -} - -#[derive(Debug, Serialize, Deserialize)] -pub struct Scripts { - pub pre: Option>, - pub post: Option>, -} - -#[derive(Debug, Serialize, Deserialize)] -pub struct Rpm { - pub repos: Option>, - pub install: Option>, - pub remove: Option>, -} - -#[derive(Debug, Serialize, Deserialize)] -pub struct FirstBoot { - pub yafti: bool, - pub flatpaks: Option>, -} - -#[derive(Debug, Serialize, Deserialize)] -pub struct Containerfiles { - pub pre: Option>, - pub post: Option>, -}