feat(iso): Create generate-iso command (#192)

## Tasks

- [x] Add ctrl-c handler to kill spawned children
- [x] add more args to support all variables
- [x] Add integration test
This commit is contained in:
Gerald Pinder 2024-09-04 18:17:08 -04:00 committed by GitHub
parent 4634f40840
commit e6cce3d542
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
25 changed files with 737 additions and 201 deletions

View file

@ -416,3 +416,97 @@ jobs:
bluebuild template -vv | tee Containerfile
grep -q 'ARG IMAGE_REGISTRY=ghcr.io/blue-build' Containerfile || exit 1
bluebuild build --retry-push -B buildah -I podman -S sigstore --squash --push -vv recipes/recipe.yml recipes/recipe-39.yml
iso-from-image:
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 generate-iso image ghcr.io/blue-build/cli/test:40
iso-from-recipe:
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 generate-iso -vv recipe recipes/recipe.yml

View file

@ -414,3 +414,97 @@ jobs:
bluebuild template -vv | tee Containerfile
grep -q 'ARG IMAGE_REGISTRY=ghcr.io/blue-build' Containerfile || exit 1
bluebuild build --retry-push -B buildah -I podman -S sigstore --squash --push -vv recipes/recipe.yml recipes/recipe-39.yml
iso-from-image:
timeout-minutes: 60
runs-on: ubuntu-latest
permissions:
contents: read
packages: write
id-token: write
needs:
- build
if: github.repository == 'blue-build/cli'
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 generate-iso image ghcr.io/blue-build/cli/test:40
iso-from-recipe:
timeout-minutes: 60
runs-on: ubuntu-latest
permissions:
contents: read
packages: write
id-token: write
needs:
- build
if: github.repository == 'blue-build/cli'
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 generate-iso -vv recipe recipes/recipe.yml

57
Cargo.lock generated
View file

@ -328,6 +328,7 @@ dependencies = [
"indicatif",
"log",
"miette",
"oci-distribution",
"open",
"os_info",
"rayon",
@ -348,7 +349,6 @@ name = "blue-build-process-management"
version = "0.8.14"
dependencies = [
"anyhow",
"blue-build-recipe",
"blue-build-utils",
"chrono",
"clap",
@ -363,6 +363,7 @@ dependencies = [
"miette",
"nix",
"nu-ansi-term",
"oci-distribution",
"once_cell",
"os_pipe",
"rand 0.8.5",
@ -389,6 +390,7 @@ dependencies = [
"indexmap 2.3.0",
"log",
"miette",
"oci-distribution",
"serde",
"serde_json",
"serde_yaml 0.9.34+deprecated",
@ -1626,6 +1628,7 @@ dependencies = [
"hyper 1.4.1",
"hyper-util",
"rustls 0.23.12",
"rustls-native-certs",
"rustls-pki-types",
"tokio",
"tokio-rustls 0.26.0",
@ -2554,6 +2557,12 @@ dependencies = [
"url",
]
[[package]]
name = "openssl-probe"
version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf"
[[package]]
name = "option-ext"
version = "0.2.0"
@ -3320,6 +3329,7 @@ dependencies = [
"pin-project-lite",
"quinn",
"rustls 0.23.12",
"rustls-native-certs",
"rustls-pemfile 2.1.3",
"rustls-pki-types",
"serde",
@ -3514,6 +3524,19 @@ dependencies = [
"zeroize",
]
[[package]]
name = "rustls-native-certs"
version = "0.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a88d6d420651b496bdd98684116959239430022a115c1240e6c3993be0b15fba"
dependencies = [
"openssl-probe",
"rustls-pemfile 2.1.3",
"rustls-pki-types",
"schannel",
"security-framework",
]
[[package]]
name = "rustls-pemfile"
version = "1.0.4"
@ -3608,6 +3631,15 @@ dependencies = [
"winapi-util",
]
[[package]]
name = "schannel"
version = "0.1.23"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fbc91545643bcf3a0bbb6569265615222618bdf33ce4ffbbd13c4bbd4c093534"
dependencies = [
"windows-sys 0.52.0",
]
[[package]]
name = "scopeguard"
version = "1.2.0"
@ -3650,6 +3682,29 @@ dependencies = [
"zeroize",
]
[[package]]
name = "security-framework"
version = "2.11.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02"
dependencies = [
"bitflags 2.6.0",
"core-foundation",
"core-foundation-sys",
"libc",
"security-framework-sys",
]
[[package]]
name = "security-framework-sys"
version = "2.11.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "75da29fe9b9b08fe9d6b22b5b4bcbc75d8db3aa31e639aa56bb62e9d46bfceaf"
dependencies = [
"core-foundation-sys",
"libc",
]
[[package]]
name = "semver"
version = "1.0.23"

View file

@ -16,6 +16,7 @@ colored = "2"
indexmap = { version = "2", features = ["serde"] }
indicatif = { version = "0.17", features = ["improved_unicode"] }
log = "0.4"
oci-distribution = { version = "0.11.0", default-features = false, features = ["rustls-tls", "rustls-tls-native-roots"] }
miette = "7"
rstest = "0.18"
serde = { version = "1", features = ["derive"] }
@ -37,6 +38,7 @@ style = "deny"
nursery = "deny"
pedantic = "deny"
module_name_repetitions = { level = "allow", priority = 1 }
doc_markdown = { level = "allow", priority = 1 }
[package]
name = "blue-build"
@ -74,6 +76,7 @@ colored.workspace = true
indicatif.workspace = true
log.workspace = true
miette = { workspace = true, features = ["fancy"] }
oci-distribution.workspace = true
serde.workspace = true
serde_json.workspace = true
serde_yaml.workspace = true
@ -86,6 +89,7 @@ default = []
stages = ["blue-build-recipe/stages"]
copy = ["blue-build-recipe/copy"]
multi-recipe = ["rayon", "indicatif/rayon"]
iso = []
switch = []
sigstore = ["blue-build-process-management/sigstore"]
login = []

View file

@ -12,7 +12,6 @@ path = "process.rs"
[dependencies]
anyhow = "1"
blue-build-recipe = { version = "=0.8.14", path = "../recipe" }
blue-build-utils = { version = "=0.8.14", path = "../utils" }
expect-exit = "0.5"
indicatif-log-bridge = "0.2"
@ -36,6 +35,7 @@ indicatif.workspace = true
indexmap.workspace = true
log.workspace = true
miette.workspace = true
oci-distribution.workspace = true
serde.workspace = true
serde_json.workspace = true
tempdir.workspace = true

View file

@ -11,12 +11,13 @@ use std::{
sync::{Mutex, RwLock},
};
use blue_build_recipe::Recipe;
use blue_build_utils::constants::IMAGE_VERSION_LABEL;
use clap::Args;
use log::{debug, info, trace};
use miette::{miette, Result};
use oci_distribution::Reference;
use once_cell::sync::Lazy;
use opts::GenerateTagsOpts;
#[cfg(feature = "sigstore")]
use sigstore_driver::SigstoreDriver;
use typed_builder::TypedBuilder;
@ -193,33 +194,31 @@ impl Driver {
///
/// # Panics
/// Panics if the mutex fails to lock.
pub fn get_os_version(recipe: &Recipe) -> Result<u64> {
pub fn get_os_version(oci_ref: &Reference) -> Result<u64> {
#[cfg(test)]
{
use miette::IntoDiagnostic;
let _ = recipe; // silence lint
let _ = oci_ref; // silence lint
if true {
return crate::test::create_test_recipe()
.image_version
.parse()
.into_diagnostic();
return Ok(40);
}
}
trace!("Driver::get_os_version({recipe:#?})");
let image = format!("{}:{}", &recipe.base_image, &recipe.image_version);
trace!("Driver::get_os_version({oci_ref:#?})");
let mut os_version_lock = OS_VERSION.lock().expect("Should lock");
let entry = os_version_lock.get(&image);
let entry = os_version_lock.get(&oci_ref.to_string());
let os_version = match entry {
None => {
info!("Retrieving OS version from {image}. This might take a bit");
info!("Retrieving OS version from {oci_ref}. This might take a bit");
let inspect_opts = GetMetadataOpts::builder()
.image(&*recipe.base_image)
.tag(&*recipe.image_version)
.image(format!(
"{}/{}",
oci_ref.resolve_registry(),
oci_ref.repository()
))
.tag(oci_ref.tag().unwrap_or("latest"))
.build();
let inspection = Self::get_metadata(&inspect_opts)?;
@ -234,13 +233,13 @@ impl Driver {
os_version
}
Some(os_version) => {
debug!("Found cached {os_version} for {image}");
debug!("Found cached {os_version} for {oci_ref}");
*os_version
}
};
if let Entry::Vacant(entry) = os_version_lock.entry(image.clone()) {
trace!("Caching version {os_version} for {image}");
if let Entry::Vacant(entry) = os_version_lock.entry(oci_ref.to_string()) {
trace!("Caching version {os_version} for {oci_ref}");
entry.insert(os_version);
}
drop(os_version_lock);
@ -371,9 +370,9 @@ impl RunDriver for Driver {
macro_rules! impl_ci_driver {
($func:ident($($args:expr),*)) => {
match Self::get_ci_driver() {
CiDriverType::Local => LocalDriver::$func($($args)*),
CiDriverType::Gitlab => GitlabDriver::$func($($args)*),
CiDriverType::Github => GithubDriver::$func($($args)*),
CiDriverType::Local => LocalDriver::$func($($args,)*),
CiDriverType::Gitlab => GitlabDriver::$func($($args,)*),
CiDriverType::Github => GithubDriver::$func($($args,)*),
}
};
}
@ -391,8 +390,8 @@ impl CiDriver for Driver {
impl_ci_driver!(oidc_provider())
}
fn generate_tags(recipe: &Recipe) -> Result<Vec<String>> {
impl_ci_driver!(generate_tags(recipe))
fn generate_tags(opts: &GenerateTagsOpts) -> Result<Vec<String>> {
impl_ci_driver!(generate_tags(opts))
}
fn get_repo_url() -> Result<String> {
@ -403,7 +402,10 @@ impl CiDriver for Driver {
impl_ci_driver!(get_registry())
}
fn generate_image_name(recipe: &Recipe) -> Result<String> {
impl_ci_driver!(generate_image_name(recipe))
fn generate_image_name<S>(name: S) -> Result<Reference>
where
S: AsRef<str>,
{
impl_ci_driver!(generate_image_name(name))
}
}

View file

@ -373,7 +373,7 @@ impl RunDriver for DockerDriver {
}
fn docker_run(opts: &RunOpts, cid_file: &Path) -> Command {
cmd!(
let command = cmd!(
"docker",
"run",
format!("--cidfile={}", cid_file.display()),
@ -397,5 +397,8 @@ fn docker_run(opts: &RunOpts, cid_file: &Path) -> Command {
},
&*opts.image,
for opts.args,
)
);
trace!("{command:?}");
command
}

View file

@ -14,7 +14,7 @@ use blue_build_utils::get_env_var;
#[cfg(test)]
use blue_build_utils::test_utils::get_env_var;
use super::{CiDriver, Driver};
use super::{opts::GenerateTagsOpts, CiDriver, Driver};
mod event;
@ -34,10 +34,11 @@ impl CiDriver for GithubDriver {
Ok(GITHUB_TOKEN_ISSUER_URL.to_string())
}
fn generate_tags(recipe: &blue_build_recipe::Recipe) -> miette::Result<Vec<String>> {
fn generate_tags(opts: &GenerateTagsOpts) -> miette::Result<Vec<String>> {
const PR_EVENT: &str = "pull_request";
let timestamp = blue_build_utils::get_tag_timestamp();
let os_version = Driver::get_os_version(recipe).inspect(|v| trace!("os_version={v}"))?;
let os_version =
Driver::get_os_version(opts.oci_ref).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}"))?;
@ -47,7 +48,7 @@ impl CiDriver for GithubDriver {
let tags = match (
Self::on_default_branch(),
recipe.alt_tags.as_ref(),
opts.alt_tags.as_ref(),
get_env_var(GITHUB_EVENT_NAME).inspect(|v| trace!("{GITHUB_EVENT_NAME}={v}")),
get_env_var(PR_EVENT_NUMBER).inspect(|v| trace!("{PR_EVENT_NUMBER}={v}")),
) {
@ -128,21 +129,21 @@ impl CiDriver for GithubDriver {
#[cfg(test)]
mod test {
use blue_build_recipe::Recipe;
use std::borrow::Cow;
use blue_build_utils::{
constants::{
GITHUB_EVENT_NAME, GITHUB_EVENT_PATH, GITHUB_REF_NAME, GITHUB_SHA, PR_EVENT_NUMBER,
},
string_vec,
cowstr_vec, string_vec,
test_utils::set_env_var,
};
use oci_distribution::Reference;
use rstest::rstest;
use crate::{
drivers::CiDriver,
test::{
create_test_recipe, create_test_recipe_alt_tags, TEST_TAG_1, TEST_TAG_2, TIMESTAMP,
},
drivers::{opts::GenerateTagsOpts, CiDriver},
test::{TEST_TAG_1, TEST_TAG_2, TIMESTAMP},
};
use super::GithubDriver;
@ -216,7 +217,7 @@ mod test {
#[rstest]
#[case::default_branch(
setup_default_branch,
create_test_recipe,
None,
string_vec![
format!("{}-40", &*TIMESTAMP),
"latest",
@ -227,7 +228,7 @@ mod test {
)]
#[case::default_branch_alt_tags(
setup_default_branch,
create_test_recipe_alt_tags,
Some(cowstr_vec![TEST_TAG_1, TEST_TAG_2]),
string_vec![
TEST_TAG_1,
format!("{TEST_TAG_1}-40"),
@ -241,12 +242,12 @@ mod test {
)]
#[case::pr_branch(
setup_pr_branch,
create_test_recipe,
None,
string_vec!["pr-12-40", format!("{COMMIT_SHA}-40")],
)]
#[case::pr_branch_alt_tags(
setup_pr_branch,
create_test_recipe_alt_tags,
Some(cowstr_vec![TEST_TAG_1, TEST_TAG_2]),
string_vec![
format!("pr-12-{TEST_TAG_1}-40"),
format!("{COMMIT_SHA}-{TEST_TAG_1}-40"),
@ -256,12 +257,12 @@ mod test {
)]
#[case::branch(
setup_branch,
create_test_recipe,
None,
string_vec![format!("{COMMIT_SHA}-40"), "br-test-40"],
)]
#[case::branch_alt_tags(
setup_branch,
create_test_recipe_alt_tags,
Some(cowstr_vec![TEST_TAG_1, TEST_TAG_2]),
string_vec![
format!("br-{BR_REF_NAME}-{TEST_TAG_1}-40"),
format!("{COMMIT_SHA}-{TEST_TAG_1}-40"),
@ -271,14 +272,20 @@ mod test {
)]
fn generate_tags(
#[case] setup: impl FnOnce(),
#[case] recipe_fn: impl Fn() -> Recipe<'static>,
#[case] alt_tags: Option<Vec<Cow<'_, str>>>,
#[case] mut expected: Vec<String>,
) {
setup();
expected.sort();
let recipe = recipe_fn();
let oci_ref: Reference = "ghcr.io/ublue-os/silverblue-main".parse().unwrap();
let mut tags = GithubDriver::generate_tags(&recipe).unwrap();
let mut tags = GithubDriver::generate_tags(
&GenerateTagsOpts::builder()
.oci_ref(&oci_ref)
.alt_tags(alt_tags)
.build(),
)
.unwrap();
tags.sort();
assert_eq!(tags, expected);

View file

@ -16,7 +16,7 @@ use blue_build_utils::test_utils::get_env_var;
use crate::drivers::Driver;
use super::CiDriver;
use super::{opts::GenerateTagsOpts, CiDriver};
pub struct GitlabDriver;
@ -43,9 +43,9 @@ impl CiDriver for GitlabDriver {
))
}
fn generate_tags(recipe: &blue_build_recipe::Recipe) -> miette::Result<Vec<String>> {
fn generate_tags(opts: &GenerateTagsOpts) -> miette::Result<Vec<String>> {
const MR_EVENT: &str = "merge_request_event";
let os_version = Driver::get_os_version(recipe)?;
let os_version = Driver::get_os_version(opts.oci_ref)?;
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}"))?;
@ -54,7 +54,7 @@ impl CiDriver for GitlabDriver {
let tags = match (
Self::on_default_branch(),
recipe.alt_tags.as_ref(),
opts.alt_tags.as_ref(),
get_env_var(CI_MERGE_REQUEST_IID).inspect(|v| trace!("{CI_MERGE_REQUEST_IID}={v}")),
get_env_var(CI_PIPELINE_SOURCE).inspect(|v| trace!("{CI_PIPELINE_SOURCE}={v}")),
) {
@ -141,23 +141,23 @@ impl CiDriver for GitlabDriver {
#[cfg(test)]
mod test {
use blue_build_recipe::Recipe;
use std::borrow::Cow;
use blue_build_utils::{
constants::{
CI_COMMIT_REF_NAME, CI_COMMIT_SHORT_SHA, CI_DEFAULT_BRANCH, CI_MERGE_REQUEST_IID,
CI_PIPELINE_SOURCE, CI_PROJECT_NAME, CI_PROJECT_NAMESPACE, CI_REGISTRY, CI_SERVER_HOST,
CI_SERVER_PROTOCOL,
},
string_vec,
cowstr_vec, string_vec,
test_utils::set_env_var,
};
use oci_distribution::Reference;
use rstest::rstest;
use crate::{
drivers::CiDriver,
test::{
create_test_recipe, create_test_recipe_alt_tags, TEST_TAG_1, TEST_TAG_2, TIMESTAMP,
},
drivers::{opts::GenerateTagsOpts, CiDriver},
test::{TEST_TAG_1, TEST_TAG_2, TIMESTAMP},
};
use super::GitlabDriver;
@ -227,7 +227,7 @@ mod test {
#[rstest]
#[case::default_branch(
setup_default_branch,
create_test_recipe,
None,
string_vec![
format!("{}-40", &*TIMESTAMP),
"latest",
@ -238,7 +238,7 @@ mod test {
)]
#[case::default_branch_alt_tags(
setup_default_branch,
create_test_recipe_alt_tags,
Some(cowstr_vec![TEST_TAG_1, TEST_TAG_2]),
string_vec![
TEST_TAG_1,
format!("{TEST_TAG_1}-40"),
@ -252,12 +252,12 @@ mod test {
)]
#[case::pr_branch(
setup_mr_branch,
create_test_recipe,
None,
string_vec!["mr-12-40", format!("{COMMIT_SHA}-40")],
)]
#[case::pr_branch_alt_tags(
setup_mr_branch,
create_test_recipe_alt_tags,
Some(cowstr_vec![TEST_TAG_1, TEST_TAG_2]),
string_vec![
format!("mr-12-{TEST_TAG_1}-40"),
format!("{COMMIT_SHA}-{TEST_TAG_1}-40"),
@ -267,12 +267,12 @@ mod test {
)]
#[case::branch(
setup_branch,
create_test_recipe,
None,
string_vec![format!("{COMMIT_SHA}-40"), "br-test-40"],
)]
#[case::branch_alt_tags(
setup_branch,
create_test_recipe_alt_tags,
Some(cowstr_vec![TEST_TAG_1, TEST_TAG_2]),
string_vec![
format!("br-{BR_REF_NAME}-{TEST_TAG_1}-40"),
format!("{COMMIT_SHA}-{TEST_TAG_1}-40"),
@ -282,14 +282,20 @@ mod test {
)]
fn generate_tags(
#[case] setup: impl FnOnce(),
#[case] recipe_fn: impl Fn() -> Recipe<'static>,
#[case] alt_tags: Option<Vec<Cow<'_, str>>>,
#[case] mut expected: Vec<String>,
) {
setup();
expected.sort();
let recipe = recipe_fn();
let oci_ref: Reference = "ghcr.io/ublue-os/silverblue-main".parse().unwrap();
let mut tags = GitlabDriver::generate_tags(&recipe).unwrap();
let mut tags = GitlabDriver::generate_tags(
&GenerateTagsOpts::builder()
.oci_ref(&oci_ref)
.alt_tags(alt_tags)
.build(),
)
.unwrap();
tags.sort();
assert_eq!(tags, expected);

View file

@ -1,7 +1,9 @@
use blue_build_utils::string_vec;
use log::trace;
use miette::bail;
use miette::{bail, Context, IntoDiagnostic};
use oci_distribution::Reference;
use super::{CiDriver, Driver};
use super::{opts::GenerateTagsOpts, CiDriver, Driver};
pub struct LocalDriver;
@ -21,14 +23,31 @@ impl CiDriver for LocalDriver {
bail!("Keyless not supported");
}
fn generate_tags(recipe: &blue_build_recipe::Recipe) -> miette::Result<Vec<String>> {
trace!("LocalDriver::generate_tags({recipe:?})");
Ok(vec![format!("local-{}", Driver::get_os_version(recipe)?)])
fn generate_tags(opts: &GenerateTagsOpts) -> miette::Result<Vec<String>> {
trace!("LocalDriver::generate_tags({opts:?})");
let os_version = Driver::get_os_version(opts.oci_ref)?;
Ok(opts.alt_tags.as_ref().map_or_else(
|| string_vec![format!("local-{os_version}")],
|alt_tags| {
alt_tags
.iter()
.flat_map(|alt| string_vec![format!("local-{alt}-{os_version}")])
.collect()
},
))
}
fn generate_image_name(recipe: &blue_build_recipe::Recipe) -> miette::Result<String> {
trace!("LocalDriver::generate_image_name({recipe:?})");
Ok(recipe.name.trim().to_lowercase())
fn generate_image_name<S>(name: S) -> miette::Result<Reference>
where
S: AsRef<str>,
{
fn inner(name: &str) -> miette::Result<Reference> {
trace!("LocalDriver::generate_image_name({name})");
name.parse()
.into_diagnostic()
.with_context(|| format!("Unable to parse {name}"))
}
inner(&name.as_ref().trim().to_lowercase())
}
fn get_repo_url() -> miette::Result<String> {

View file

@ -1,11 +1,13 @@
use clap::ValueEnum;
pub use build::*;
pub use ci::*;
pub use inspect::*;
pub use run::*;
pub use signing::*;
mod build;
mod ci;
mod inspect;
mod run;
mod signing;

View file

@ -41,6 +41,8 @@ pub struct PushOpts<'a> {
pub struct BuildTagPushOpts<'a> {
/// The base image name.
///
/// NOTE: This SHOULD NOT contain the tag of the image.
///
/// NOTE: You cannot have this set with `archive_path` set.
#[builder(default, setter(into, strip_option))]
pub image: Option<Cow<'a, str>>,

View file

@ -0,0 +1,12 @@
use std::borrow::Cow;
use oci_distribution::Reference;
use typed_builder::TypedBuilder;
#[derive(Debug, Clone, TypedBuilder)]
pub struct GenerateTagsOpts<'scope> {
pub oci_ref: &'scope Reference,
#[builder(default, setter(into))]
pub alt_tags: Option<Vec<Cow<'scope, str>>>,
}

View file

@ -11,10 +11,10 @@ pub struct RunOpts<'scope> {
pub args: Cow<'scope, [String]>,
#[builder(default, setter(into))]
pub env_vars: Cow<'scope, [RunOptsEnv<'scope>]>,
pub env_vars: Vec<RunOptsEnv<'scope>>,
#[builder(default, setter(into))]
pub volumes: Cow<'scope, [RunOptsVolume<'scope>]>,
pub volumes: Vec<RunOptsVolume<'scope>>,
#[builder(default, setter(strip_option))]
pub uid: Option<u32>,
@ -48,7 +48,7 @@ pub struct RunOptsVolume<'scope> {
macro_rules! run_volumes {
($($host:expr => $container:expr),+ $(,)?) => {
{
[
vec![
$($crate::drivers::opts::RunOptsVolume::builder()
.path_or_vol_name($host)
.container_path($container)
@ -71,7 +71,7 @@ pub struct RunOptsEnv<'scope> {
macro_rules! run_envs {
($($key:expr => $value:expr),+ $(,)?) => {
{
[
vec![
$($crate::drivers::opts::RunOptsEnv::builder()
.key($key)
.value($value)

View file

@ -227,8 +227,12 @@ impl RunDriver for PodmanDriver {
add_cid(&cid);
let status = podman_run(opts, &cid_file)
.status_image_ref_progress(&*opts.image, "Running container")?;
let status = if opts.privileged {
podman_run(opts, &cid_file).status()?
} else {
podman_run(opts, &cid_file)
.status_image_ref_progress(&*opts.image, "Running container")?
};
remove_cid(&cid);
@ -254,7 +258,7 @@ impl RunDriver for PodmanDriver {
}
fn podman_run(opts: &RunOpts, cid_file: &Path) -> Command {
cmd!(
let command = cmd!(
if opts.privileged {
warn!(
"Running 'podman' in privileged mode requires '{}'",
@ -267,7 +271,10 @@ fn podman_run(opts: &RunOpts, cid_file: &Path) -> Command {
if opts.privileged => "podman",
"run",
format!("--cidfile={}", cid_file.display()),
if opts.privileged => "--privileged",
if opts.privileged => [
"--privileged",
"--network=host",
],
if opts.remove => "--rm",
if opts.pull => "--pull=always",
for volume in opts.volumes => [
@ -280,5 +287,8 @@ fn podman_run(opts: &RunOpts, cid_file: &Path) -> Command {
],
&*opts.image,
for opts.args,
)
);
trace!("{command:?}");
command
}

View file

@ -3,10 +3,10 @@ use std::{
process::{ExitStatus, Output},
};
use blue_build_recipe::Recipe;
use blue_build_utils::{constants::COSIGN_PUB_PATH, retry};
use log::{debug, info, trace};
use miette::{bail, miette, Result};
use miette::{bail, miette, Context, IntoDiagnostic, Result};
use oci_distribution::Reference;
use semver::{Version, VersionReq};
use crate::drivers::{functions::get_private_key, types::CiDriverType, Driver};
@ -14,8 +14,9 @@ use crate::drivers::{functions::get_private_key, types::CiDriverType, Driver};
use super::{
image_metadata::ImageMetadata,
opts::{
BuildOpts, BuildTagPushOpts, CheckKeyPairOpts, GenerateKeyPairOpts, GetMetadataOpts,
PushOpts, RunOpts, SignOpts, SignVerifyOpts, TagOpts, VerifyOpts, VerifyType,
BuildOpts, BuildTagPushOpts, CheckKeyPairOpts, GenerateKeyPairOpts, GenerateTagsOpts,
GetMetadataOpts, PushOpts, RunOpts, SignOpts, SignVerifyOpts, TagOpts, VerifyOpts,
VerifyType,
},
};
@ -307,18 +308,27 @@ pub trait CiDriver {
///
/// # Errors
/// Will error if the environment variables aren't set.
fn generate_tags(recipe: &Recipe) -> Result<Vec<String>>;
fn generate_tags(oci_ref: &GenerateTagsOpts) -> Result<Vec<String>>;
/// Generates the image name based on CI.
///
/// # Errors
/// Will error if the environment variables aren't set.
fn generate_image_name(recipe: &Recipe) -> Result<String> {
Ok(format!(
"{}/{}",
Self::get_registry()?,
recipe.name.trim().to_lowercase()
))
fn generate_image_name<S>(name: S) -> Result<Reference>
where
S: AsRef<str>,
{
fn inner(name: &str, registry: &str) -> Result<Reference> {
let image = format!("{registry}/{name}");
image
.parse()
.into_diagnostic()
.with_context(|| format!("Unable to parse image {image}"))
}
inner(
&name.as_ref().trim().to_lowercase(),
&Self::get_registry()?.to_lowercase(),
)
}
/// Get the URL for the repository.

View file

@ -23,45 +23,8 @@ pub(crate) static RT: Lazy<Runtime> = Lazy::new(|| {
pub(crate) mod test {
use std::sync::LazyLock;
use blue_build_recipe::{Module, ModuleExt, Recipe};
use blue_build_utils::cowstr_vec;
use indexmap::IndexMap;
pub const TEST_TAG_1: &str = "test-tag-1";
pub const TEST_TAG_2: &str = "test-tag-2";
pub static TIMESTAMP: LazyLock<String> = LazyLock::new(blue_build_utils::get_tag_timestamp);
pub fn create_test_recipe() -> Recipe<'static> {
Recipe::builder()
.name("test")
.description("This is a test")
.base_image("base-image")
.image_version("40")
.modules_ext(
ModuleExt::builder()
.modules(vec![Module::builder().build()])
.build(),
)
.stages_ext(None)
.extra(IndexMap::new())
.build()
}
pub fn create_test_recipe_alt_tags() -> Recipe<'static> {
Recipe::builder()
.name("test")
.description("This is a test")
.base_image("base-image")
.image_version("40")
.alt_tags(cowstr_vec![TEST_TAG_1, TEST_TAG_2])
.modules_ext(
ModuleExt::builder()
.modules(vec![Module::builder().build()])
.build(),
)
.stages_ext(None)
.extra(IndexMap::new())
.build()
}
}

View file

@ -14,6 +14,7 @@ blue-build-utils = { version = "=0.8.14", path = "../utils" }
colored.workspace = true
log.workspace = true
miette.workspace = true
oci-distribution.workspace = true
indexmap.workspace = true
serde.workspace = true
serde_yaml.workspace = true

View file

@ -1,8 +1,10 @@
use std::{borrow::Cow, fs, path::Path};
use blue_build_utils::cowstr;
use indexmap::IndexMap;
use log::{debug, trace};
use miette::{Context, IntoDiagnostic, Result};
use oci_distribution::Reference;
use serde::{Deserialize, Serialize};
use serde_yaml::Value;
use typed_builder::TypedBuilder;
@ -53,7 +55,7 @@ pub struct Recipe<'a> {
/// Any user input will override the `latest` and timestamp tags.
#[serde(alias = "alt-tags", skip_serializing_if = "Option::is_none")]
#[builder(default, setter(into, strip_option))]
pub alt_tags: Option<Vec<Cow<'a, str>>>,
alt_tags: Option<Vec<Cow<'a, str>>>,
/// The stages extension of the recipe.
///
@ -118,4 +120,22 @@ impl<'a> Recipe<'a> {
Ok(recipe)
}
/// Get a `Reference` object of the `base_image`.
///
/// # Errors
/// Will error if it fails to parse the `base_image`.
pub fn base_image_ref(&self) -> Result<Reference> {
self.base_image
.parse()
.into_diagnostic()
.with_context(|| format!("Unable to parse base image {}", self.base_image))
}
#[must_use]
pub fn alt_tags(&'a self) -> Option<Vec<Cow<'a, str>>> {
self.alt_tags
.as_ref()
.map(|tags| tags.iter().map(|tag| cowstr!(&**tag)).collect())
}
}

View file

@ -27,7 +27,7 @@ pub struct StageRequiredFields<'a> {
/// The shell to use in the stage.
#[builder(default, setter(into, strip_option))]
#[serde(skip_serializing_if = "Option::is_none")]
pub shell: Option<Vec<Cow<'a, str>>>,
pub shell: Option<Cow<'a, [Cow<'a, str>]>>,
/// The modules extension for the stage
#[serde(flatten)]

View file

@ -39,6 +39,9 @@ fn main() {
#[cfg(feature = "login")]
CommandArgs::Login(mut command) => command.run(),
#[cfg(feature = "iso")]
CommandArgs::GenerateIso(mut command) => command.run(),
CommandArgs::BugReport(mut command) => command.run(),
CommandArgs::Completions(mut command) => command.run(),

View file

@ -11,6 +11,8 @@ pub mod bug_report;
pub mod build;
pub mod completions;
pub mod generate;
#[cfg(feature = "iso")]
pub mod generate_iso;
#[cfg(feature = "login")]
pub mod login;
// #[cfg(feature = "init")]
@ -68,6 +70,10 @@ pub enum CommandArgs {
#[clap(visible_alias = "template")]
Generate(generate::GenerateCommand),
/// Generate an ISO for an image or recipe.
#[cfg(feature = "iso")]
GenerateIso(generate_iso::GenerateIsoCommand),
/// Upgrade your current OS with the
/// local image saved at `/etc/bluebuild/`.
///

View file

@ -4,7 +4,7 @@ use std::{
};
use blue_build_process_management::drivers::{
opts::{BuildTagPushOpts, CheckKeyPairOpts, CompressionType},
opts::{BuildTagPushOpts, CheckKeyPairOpts, CompressionType, GenerateTagsOpts, SignVerifyOpts},
BuildDriver, CiDriver, Driver, DriverArgs, SigningDriver,
};
use blue_build_recipe::Recipe;
@ -14,11 +14,13 @@ use blue_build_utils::{
GITIGNORE_PATH, LABELED_ERROR_MESSAGE, NO_LABEL_ERROR_MESSAGE, RECIPE_FILE, RECIPE_PATH,
},
credentials::{Credentials, CredentialsArgs},
string,
};
use clap::Args;
use colored::Colorize;
use log::{debug, info, trace, warn};
use miette::{bail, Context, IntoDiagnostic, Result};
use oci_distribution::Reference;
use typed_builder::TypedBuilder;
use crate::commands::generate::GenerateCommand;
@ -197,7 +199,6 @@ impl BlueBuildCommand for BuildCommand {
impl BuildCommand {
#[cfg(feature = "multi-recipe")]
fn start(&self, recipe_paths: &[PathBuf]) -> Result<()> {
use blue_build_process_management::drivers::opts::SignVerifyOpts;
use rayon::prelude::*;
trace!("BuildCommand::build_image()");
@ -205,54 +206,12 @@ impl BuildCommand {
recipe_paths
.par_iter()
.try_for_each(|recipe_path| -> Result<()> {
let recipe = Recipe::parse(recipe_path)?;
let containerfile = if recipe_paths.len() > 1 {
blue_build_utils::generate_containerfile_path(recipe_path)?
} else {
PathBuf::from(CONTAINER_FILE)
};
let tags = Driver::generate_tags(&recipe)?;
let image_name = self.generate_full_image_name(&recipe)?;
let opts = if let Some(archive_dir) = self.archive.as_ref() {
BuildTagPushOpts::builder()
.containerfile(&containerfile)
.archive_path(format!(
"{}/{}.{ARCHIVE_SUFFIX}",
archive_dir.to_string_lossy().trim_end_matches('/'),
recipe.name.to_lowercase().replace('/', "_"),
))
.squash(self.squash)
.build()
} else {
BuildTagPushOpts::builder()
.image(&image_name)
.containerfile(&containerfile)
.tags(&tags)
.push(self.push)
.retry_push(self.retry_push)
.retry_count(self.retry_count)
.compression(self.compression_format)
.squash(self.squash)
.build()
};
Driver::build_tag_push(&opts)?;
if self.push && !self.no_sign {
let opts = SignVerifyOpts::builder()
.image(&image_name)
.retry_push(self.retry_push)
.retry_count(self.retry_count);
let opts = if let Some(tag) = tags.first() {
opts.tag(tag).build()
} else {
opts.build()
};
Driver::sign_and_verify(&opts)?;
}
Ok(())
self.build(recipe_path, &containerfile)
})?;
info!("Build complete!");
@ -261,18 +220,27 @@ impl BuildCommand {
#[cfg(not(feature = "multi-recipe"))]
fn start(&self, recipe_path: &Path) -> Result<()> {
use blue_build_process_management::drivers::opts::SignVerifyOpts;
trace!("BuildCommand::start()");
self.build(recipe_path, Path::new(CONTAINER_FILE))?;
info!("Build complete!");
Ok(())
}
fn build(&self, recipe_path: &Path, containerfile: &Path) -> Result<()> {
let recipe = Recipe::parse(recipe_path)?;
let containerfile = PathBuf::from(CONTAINER_FILE);
let tags = Driver::generate_tags(&recipe)?;
let image_name = self.generate_full_image_name(&recipe)?;
let tags = Driver::generate_tags(
&GenerateTagsOpts::builder()
.oci_ref(&recipe.base_image_ref()?)
.alt_tags(recipe.alt_tags())
.build(),
)?;
let image_name = self.image_name(&recipe)?;
let opts = if let Some(archive_dir) = self.archive.as_ref() {
BuildTagPushOpts::builder()
.containerfile(&containerfile)
.containerfile(containerfile)
.archive_path(format!(
"{}/{}.{ARCHIVE_SUFFIX}",
archive_dir.to_string_lossy().trim_end_matches('/'),
@ -283,7 +251,7 @@ impl BuildCommand {
} else {
BuildTagPushOpts::builder()
.image(&image_name)
.containerfile(&containerfile)
.containerfile(containerfile)
.tags(&tags)
.push(self.push)
.retry_push(self.retry_push)
@ -308,14 +276,29 @@ impl BuildCommand {
Driver::sign_and_verify(&opts)?;
}
info!("Build complete!");
Ok(())
}
fn image_name(&self, recipe: &Recipe) -> Result<String> {
let image_name = self.generate_full_image_name(recipe)?;
let image_name = if image_name.registry().is_empty() {
string!(image_name.repository())
} else {
format!(
"{}/{}",
image_name.resolve_registry(),
image_name.repository()
)
};
Ok(image_name)
}
/// # Errors
///
/// Will return `Err` if the image name cannot be generated.
fn generate_full_image_name(&self, recipe: &Recipe) -> Result<String> {
fn generate_full_image_name(&self, recipe: &Recipe) -> Result<Reference> {
trace!("BuildCommand::generate_full_image_name({recipe:#?})");
info!("Generating full image name");
@ -324,14 +307,18 @@ impl BuildCommand {
self.registry_namespace.as_ref().map(|s| s.to_lowercase()),
) {
trace!("registry={registry}, registry_path={registry_path}");
format!(
let image = format!(
"{}/{}/{}",
registry.trim().trim_matches('/'),
registry_path.trim().trim_matches('/'),
recipe.name.trim(),
)
);
image
.parse()
.into_diagnostic()
.with_context(|| format!("Unable to parse {image}"))?
} else {
Driver::generate_image_name(recipe)?
Driver::generate_image_name(&recipe.name)?
};
debug!("Using image name '{image_name}'");

View file

@ -93,15 +93,15 @@ impl GenerateCommand {
});
debug!("Deserializing recipe");
let recipe_de = Recipe::parse(&recipe_path)?;
trace!("recipe_de: {recipe_de:#?}");
let recipe = Recipe::parse(&recipe_path)?;
trace!("recipe_de: {recipe:#?}");
if self.display_full_recipe {
if let Some(output) = self.output.as_ref() {
std::fs::write(output, serde_yaml::to_string(&recipe_de).into_diagnostic()?)
std::fs::write(output, serde_yaml::to_string(&recipe).into_diagnostic()?)
.into_diagnostic()?;
} else {
syntax_highlighting::print_ser(&recipe_de, "yml", self.syntax_theme)?;
syntax_highlighting::print_ser(&recipe, "yml", self.syntax_theme)?;
}
return Ok(());
}
@ -109,9 +109,9 @@ impl GenerateCommand {
info!("Templating for recipe at {}", recipe_path.display());
let template = ContainerFileTemplate::builder()
.os_version(Driver::get_os_version(&recipe_de)?)
.os_version(Driver::get_os_version(&recipe.base_image_ref()?)?)
.build_id(Driver::get_build_id())
.recipe(&recipe_de)
.recipe(&recipe)
.recipe_path(recipe_path.as_path())
.registry(Driver::get_registry()?)
.repo(Driver::get_repo_url()?)
@ -142,7 +142,3 @@ impl GenerateCommand {
Ok(())
}
}
// ======================================================== //
// ========================= Helpers ====================== //
// ======================================================== //

View file

@ -0,0 +1,240 @@
use std::{
env, fs,
path::{self, Path, PathBuf},
};
use blue_build_recipe::Recipe;
use blue_build_utils::{constants::ARCHIVE_SUFFIX, string_vec};
use clap::{Args, Subcommand, ValueEnum};
use miette::{bail, Context, IntoDiagnostic, Result};
use oci_distribution::Reference;
use tempdir::TempDir;
use typed_builder::TypedBuilder;
use blue_build_process_management::{
drivers::{opts::RunOpts, Driver, DriverArgs, RunDriver},
run_volumes,
};
use super::{build::BuildCommand, BlueBuildCommand};
#[derive(Clone, Debug, TypedBuilder, Args)]
pub struct GenerateIsoCommand {
#[command(subcommand)]
command: GenIsoSubcommand,
/// The directory to save the resulting ISO file.
#[arg(short, long)]
output_dir: Option<PathBuf>,
/// The variant of the installer to use.
///
/// The Kinoite variant will ask for a user
/// and password before installing the OS.
/// This version is the most stable and is
/// recommended.
///
/// The Silverblue variant will ask for a user
/// and password on first boot after the OS
/// is installed.
///
/// The Server variant is the basic installer
/// and will ask to setup a user at install time.
#[arg(short = 'V', long, default_value = "server")]
variant: GenIsoVariant,
/// The url to the secure boot public key.
///
/// Defaults to one of UBlue's public key.
/// It's recommended to change this if your base
/// image is not from UBlue.
#[arg(
long,
default_value = "https://github.com/ublue-os/bazzite/raw/main/secure_boot.der"
)]
secure_boot_url: String,
/// The enrollment password for the secure boot
/// key.
///
/// Default's to UBlue's enrollment password.
/// It's recommended to change this if your base
/// image is not from UBlue.
#[arg(long, default_value = "universalblue")]
enrollment_password: String,
/// The name of your ISO image file.
#[arg(long)]
iso_name: Option<String>,
#[clap(flatten)]
#[builder(default)]
drivers: DriverArgs,
}
#[derive(Debug, Clone, Subcommand)]
pub enum GenIsoSubcommand {
/// Build an ISO from a remote image.
Image {
/// The image ref to create the iso from.
#[arg()]
image: String,
},
/// Build an ISO from a recipe.
///
/// This will build the image locally first
/// before creating the ISO. This is a long
/// process.
Recipe {
/// The path to the recipe file for your image.
#[arg()]
recipe: PathBuf,
},
}
#[derive(Debug, Default, Clone, Copy, ValueEnum)]
pub enum GenIsoVariant {
#[default]
Kinoite,
Silverblue,
Server,
}
impl std::fmt::Display for GenIsoVariant {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
f,
"{}",
match *self {
Self::Server => "Server",
Self::Silverblue => "Silverblue",
Self::Kinoite => "Kinoite",
}
)
}
}
impl BlueBuildCommand for GenerateIsoCommand {
fn try_run(&mut self) -> Result<()> {
Driver::init(self.drivers);
let image_out_dir = TempDir::new("build_image").into_diagnostic()?;
let output_dir = if let Some(output_dir) = self.output_dir.clone() {
if output_dir.exists() && !output_dir.is_dir() {
bail!("The '--output-dir' arg must be a directory");
}
if !output_dir.exists() {
fs::create_dir(&output_dir).into_diagnostic()?;
}
path::absolute(output_dir).into_diagnostic()?
} else {
env::current_dir().into_diagnostic()?
};
if let GenIsoSubcommand::Recipe { recipe } = &self.command {
#[cfg(feature = "multi-recipe")]
let mut build_command = {
BuildCommand::builder()
.recipe(vec![recipe.clone()])
.archive(image_out_dir.path())
.build()
};
#[cfg(not(feature = "multi-recipe"))]
let mut build_command = {
BuildCommand::builder()
.recipe(recipe.to_path_buf())
.archive(image_out_dir.path())
.build()
};
build_command.try_run()?;
}
let iso_name = self.iso_name.as_ref().map_or("deploy.iso", String::as_str);
let iso_path = output_dir.join(iso_name);
if iso_path.exists() {
fs::remove_file(iso_path).into_diagnostic()?;
}
self.build_iso(iso_name, &output_dir, image_out_dir.path())
}
}
impl GenerateIsoCommand {
fn build_iso(&self, iso_name: &str, output_dir: &Path, image_out_dir: &Path) -> Result<()> {
let mut args = string_vec![
format!("VARIANT={}", self.variant),
format!("ISO_NAME=build/{iso_name}"),
"DNF_CACHE=/cache/dnf",
format!("SECURE_BOOT_KEY_URL={}", self.secure_boot_url),
format!("ENROLLMENT_PASSWORD={}", self.enrollment_password),
];
let mut vols = run_volumes![
output_dir.display().to_string() => "/build-container-installer/build",
"dnf-cache" => "/cache/dnf/",
];
match &self.command {
GenIsoSubcommand::Image { image } => {
let image: Reference = image
.parse()
.into_diagnostic()
.with_context(|| format!("Unable to parse image reference {image}"))?;
let (image_repo, image_name) = {
let registry = image.resolve_registry();
let repo = image.repository();
let image = format!("{registry}/{repo}");
let mut image_parts = image.split('/').collect::<Vec<_>>();
let image_name = image_parts.pop().unwrap(); // Should be at least 2 elements
let image_repo = image_parts.join("/");
(image_repo, image_name.to_string())
};
args.extend([
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)?),
]);
}
GenIsoSubcommand::Recipe { recipe } => {
let recipe = Recipe::parse(recipe)?;
args.extend([
format!(
"IMAGE_SRC=oci-archive:/img_src/{}.{ARCHIVE_SUFFIX}",
recipe.name.replace('/', "_"),
),
format!(
"VERSION={}",
Driver::get_os_version(&recipe.base_image_ref()?)?,
),
]);
vols.extend(run_volumes![
image_out_dir.display().to_string() => "/img_src/",
]);
}
}
// Currently testing local tarball builds
let opts = RunOpts::builder()
.image("ghcr.io/jasonn3/build-container-installer")
.privileged(true)
.remove(true)
.args(&args)
.volumes(vols)
.build();
let status = Driver::run(&opts).into_diagnostic()?;
if !status.success() {
bail!("Failed to create ISO");
}
Ok(())
}
}