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:
parent
4634f40840
commit
e6cce3d542
25 changed files with 737 additions and 201 deletions
94
.github/workflows/build-pr.yml
vendored
94
.github/workflows/build-pr.yml
vendored
|
|
@ -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
|
||||
|
|
|
|||
94
.github/workflows/build.yml
vendored
94
.github/workflows/build.yml
vendored
|
|
@ -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
57
Cargo.lock
generated
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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 = []
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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))
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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> {
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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>>,
|
||||
|
|
|
|||
12
process/drivers/opts/ci.rs
Normal file
12
process/drivers/opts/ci.rs
Normal 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>>>,
|
||||
}
|
||||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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())
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)]
|
||||
|
|
|
|||
|
|
@ -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(),
|
||||
|
|
|
|||
|
|
@ -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/`.
|
||||
///
|
||||
|
|
|
|||
|
|
@ -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}'");
|
||||
|
|
|
|||
|
|
@ -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 ====================== //
|
||||
// ======================================================== //
|
||||
|
|
|
|||
240
src/commands/generate_iso.rs
Normal file
240
src/commands/generate_iso.rs
Normal 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(())
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue