refactor: Move templates to their own crate (#83)
This PR logically separates out parts of the code to their own crates. This will be useful for future Tauri App development.
This commit is contained in:
parent
ce8f889dc2
commit
910e0434b6
34 changed files with 620 additions and 512 deletions
120
Cargo.lock
generated
120
Cargo.lock
generated
|
|
@ -130,9 +130,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "anyhow"
|
||||
version = "1.0.79"
|
||||
version = "1.0.80"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "080e9890a082662b09c1ad45f567faeeb47f22b5fb23895fbe1e651e718e25ca"
|
||||
checksum = "5ad32ce52e4161730f7098c077cd2ed6229b5804ccf99e5366be1ab72a98b4e1"
|
||||
|
||||
[[package]]
|
||||
name = "askama"
|
||||
|
|
@ -147,7 +147,7 @@ dependencies = [
|
|||
"percent-encoding",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"serde_yaml 0.9.31",
|
||||
"serde_yaml 0.9.32",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
@ -281,31 +281,27 @@ name = "blue-build"
|
|||
version = "0.8.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"askama",
|
||||
"chrono",
|
||||
"blue-build-recipe",
|
||||
"blue-build-template",
|
||||
"blue-build-utils",
|
||||
"clap",
|
||||
"clap-verbosity-flag",
|
||||
"clap_complete",
|
||||
"clap_complete_nushell",
|
||||
"colorized",
|
||||
"derive_builder",
|
||||
"directories",
|
||||
"dunce",
|
||||
"env_logger",
|
||||
"format_serde_error",
|
||||
"futures-util",
|
||||
"fuzzy-matcher",
|
||||
"indexmap 2.2.3",
|
||||
"log",
|
||||
"open",
|
||||
"os_info",
|
||||
"podman-api",
|
||||
"process_control",
|
||||
"requestty",
|
||||
"rusty-hook",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"serde_yaml 0.9.31",
|
||||
"serde_yaml 0.9.32",
|
||||
"shadow-rs",
|
||||
"signal-hook",
|
||||
"signal-hook-tokio",
|
||||
|
|
@ -315,6 +311,51 @@ dependencies = [
|
|||
"urlencoding",
|
||||
"users",
|
||||
"uuid",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "blue-build-recipe"
|
||||
version = "0.8.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"blue-build-utils",
|
||||
"chrono",
|
||||
"format_serde_error",
|
||||
"indexmap 2.2.3",
|
||||
"log",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"serde_yaml 0.9.32",
|
||||
"typed-builder",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "blue-build-template"
|
||||
version = "0.8.0"
|
||||
dependencies = [
|
||||
"askama",
|
||||
"blue-build-recipe",
|
||||
"blue-build-utils",
|
||||
"log",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"serde_yaml 0.9.32",
|
||||
"typed-builder",
|
||||
"uuid",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "blue-build-utils"
|
||||
version = "0.8.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"directories",
|
||||
"format_serde_error",
|
||||
"log",
|
||||
"process_control",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"serde_yaml 0.9.32",
|
||||
"which",
|
||||
]
|
||||
|
||||
|
|
@ -649,21 +690,6 @@ dependencies = [
|
|||
"cfg-if",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crossbeam-channel"
|
||||
version = "0.5.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "176dc175b78f56c0f321911d9c8eb2b77a78a4860b9c19db83835fea1a46649b"
|
||||
dependencies = [
|
||||
"crossbeam-utils",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crossbeam-utils"
|
||||
version = "0.8.19"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "248e3bacc7dc6baa3b21e405ee045c3047101a49145e7e9eca583ab4c2ca5345"
|
||||
|
||||
[[package]]
|
||||
name = "crossterm"
|
||||
version = "0.25.0"
|
||||
|
|
@ -859,37 +885,6 @@ dependencies = [
|
|||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "derive_builder"
|
||||
version = "0.13.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8f59169f400d8087f238c5c0c7db6a28af18681717f3b623227d92f397e938c7"
|
||||
dependencies = [
|
||||
"derive_builder_macro",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "derive_builder_core"
|
||||
version = "0.13.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a4ec317cc3e7ef0928b0ca6e4a634a4d6c001672ae210438cf114a83e56b018d"
|
||||
dependencies = [
|
||||
"darling 0.14.4",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 1.0.109",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "derive_builder_macro"
|
||||
version = "0.13.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "870368c3fb35b8031abb378861d4460f573b92238ec2152c927a21f77e3e0127"
|
||||
dependencies = [
|
||||
"derive_builder_core",
|
||||
"syn 1.0.109",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "digest"
|
||||
version = "0.10.7"
|
||||
|
|
@ -2518,14 +2513,13 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "process_control"
|
||||
version = "4.0.3"
|
||||
version = "4.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "32e056a69288d0a211f4c74c48391c6eb86e714fdcb9dc58a9f34302da9c20bf"
|
||||
checksum = "4d18334c4a4b2770ee894e63cf466d5a9ea449cf29e321101b0b135a747afb6f"
|
||||
dependencies = [
|
||||
"crossbeam-channel",
|
||||
"libc",
|
||||
"signal-hook",
|
||||
"windows-sys 0.48.0",
|
||||
"windows-sys 0.52.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
@ -3073,9 +3067,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "serde_yaml"
|
||||
version = "0.9.31"
|
||||
version = "0.9.32"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "adf8a49373e98a4c5f0ceb5d05aa7c648d75f63774981ed95b7c7443bbd50c6e"
|
||||
checksum = "8fd075d994154d4a774f95b51fb96bdc2832b0ea48425c92546073816cda1f2f"
|
||||
dependencies = [
|
||||
"indexmap 2.2.3",
|
||||
"itoa",
|
||||
|
|
|
|||
82
Cargo.toml
82
Cargo.toml
|
|
@ -1,51 +1,80 @@
|
|||
[package]
|
||||
name = "blue-build"
|
||||
[workspace]
|
||||
members = [ "utils", "recipe","template"]
|
||||
|
||||
[workspace.package]
|
||||
version = "0.8.0"
|
||||
edition = "2021"
|
||||
description = "A CLI tool built for creating Containerfile templates based on the Ublue Community Project"
|
||||
edition = "2021"
|
||||
repository = "https://github.com/blue-build/cli"
|
||||
license = "Apache-2.0"
|
||||
categories = ["command-line-utilities"]
|
||||
|
||||
[workspace.dependencies]
|
||||
anyhow = "1"
|
||||
format_serde_error = "0.3.0"
|
||||
log = "0.4"
|
||||
serde = { version = "1", features = ["derive"] }
|
||||
serde_json = "1"
|
||||
serde_yaml = "0.9.30"
|
||||
typed-builder = "0.18.1"
|
||||
uuid = { version = "1.7.0", features = ["v4"] }
|
||||
|
||||
[workspace.lints.rust]
|
||||
unsafe_code = "forbid"
|
||||
|
||||
[workspace.lints.clippy]
|
||||
correctness = "warn"
|
||||
suspicious = "warn"
|
||||
perf = "warn"
|
||||
style = "warn"
|
||||
nursery = "warn"
|
||||
|
||||
[package]
|
||||
name = "blue-build"
|
||||
build = "build.rs"
|
||||
|
||||
version.workspace = true
|
||||
edition.workspace = true
|
||||
description.workspace = true
|
||||
repository.workspace = true
|
||||
license.workspace = true
|
||||
|
||||
[dependencies]
|
||||
anyhow = "1"
|
||||
askama = { version = "0.12", features = ["serde-json", "serde-yaml"] }
|
||||
chrono = "0.4"
|
||||
blue-build-recipe = { path = "./recipe" }
|
||||
blue-build-template = { path = "./template" }
|
||||
blue-build-utils = { path = "./utils" }
|
||||
clap = { version = "4", features = ["derive", "cargo", "unicode"] }
|
||||
clap-verbosity-flag = "2"
|
||||
clap_complete = "4"
|
||||
clap_complete_nushell = "4"
|
||||
colorized = "1"
|
||||
derive_builder = "0.13"
|
||||
directories = "5"
|
||||
env_logger = "0.11"
|
||||
format_serde_error = "0.3.0"
|
||||
futures-util = { version = "0.3", optional = true }
|
||||
fuzzy-matcher = "0.3"
|
||||
indexmap = { version = "2", features = ["serde"] }
|
||||
log = "0.4"
|
||||
open = "5"
|
||||
# update os module config and tests when upgrading os_info
|
||||
os_info = "3.7"
|
||||
podman-api = { version = "0.10.0", optional = true }
|
||||
process_control = { version = "4.0.3", features = ["crossbeam-channel"] }
|
||||
os_info = "3.7" # update os module config and tests when upgrading os_info
|
||||
requestty = { version = "0.5", features = ["macros", "termion"] }
|
||||
serde = { version = "1", features = ["derive"] }
|
||||
serde_json = "1"
|
||||
serde_yaml = "0.9.30"
|
||||
shadow-rs = { version = "0.26" }
|
||||
urlencoding = "2.1.3"
|
||||
users = "0.11.0"
|
||||
|
||||
# Optional Dependencies
|
||||
futures-util = { version = "0.3", optional = true }
|
||||
podman-api = { version = "0.10.0", optional = true }
|
||||
signal-hook = { version = "0.3.17", optional = true }
|
||||
signal-hook-tokio = { version = "0.3.1", features = [
|
||||
"futures-v0_3",
|
||||
], optional = true }
|
||||
shadow-rs = { version = "0.26" }
|
||||
sigstore = { version = "0.8.0", optional = true }
|
||||
tokio = { version = "1", features = ["full"], optional = true }
|
||||
typed-builder = "0.18.1"
|
||||
urlencoding = "2.1.3"
|
||||
users = "0.11.0"
|
||||
uuid = { version = "1.7.0", features = ["v4"] }
|
||||
which = "6"
|
||||
|
||||
# Workspace dependencies
|
||||
anyhow.workspace = true
|
||||
log.workspace = true
|
||||
serde.workspace = true
|
||||
serde_json.workspace = true
|
||||
serde_yaml.workspace = true
|
||||
typed-builder.workspace = true
|
||||
uuid.workspace = true
|
||||
|
||||
[features]
|
||||
default = []
|
||||
|
|
@ -66,6 +95,9 @@ rusty-hook = "0.11.2"
|
|||
shadow-rs = { version = "0.26.1", default-features = false }
|
||||
dunce = "1.0.4"
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
|
||||
[profile.release]
|
||||
lto = true
|
||||
codegen-units = 1
|
||||
|
|
|
|||
|
|
@ -54,7 +54,7 @@ common:
|
|||
FROM ghcr.io/blue-build/earthly-lib/cargo-builder
|
||||
|
||||
WORKDIR /app
|
||||
COPY --keep-ts --dir src/ templates/ /app
|
||||
COPY --keep-ts --dir src/ template/ recipe/ utils/ /app
|
||||
COPY --keep-ts Cargo.* /app
|
||||
COPY --keep-ts *.md /app
|
||||
COPY --keep-ts LICENSE /app
|
||||
|
|
|
|||
26
recipe/Cargo.toml
Normal file
26
recipe/Cargo.toml
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
[package]
|
||||
name = "blue-build-recipe"
|
||||
version.workspace = true
|
||||
edition.workspace = true
|
||||
description.workspace = true
|
||||
repository.workspace = true
|
||||
license.workspace = true
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
blue-build-utils = { path = "../utils" }
|
||||
chrono = "0.4"
|
||||
indexmap = { version = "2", features = ["serde"] }
|
||||
|
||||
anyhow.workspace = true
|
||||
format_serde_error.workspace = true
|
||||
log.workspace = true
|
||||
serde.workspace = true
|
||||
serde_yaml.workspace = true
|
||||
serde_json.workspace = true
|
||||
typed-builder.workspace = true
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
|
||||
23
recipe/src/image_inspection.rs
Normal file
23
recipe/src/image_inspection.rs
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
use serde::Deserialize;
|
||||
use serde_json::Value;
|
||||
use std::collections::HashMap;
|
||||
|
||||
#[derive(Deserialize, Debug, Clone)]
|
||||
pub struct ImageInspection {
|
||||
#[serde(alias = "Labels")]
|
||||
labels: HashMap<String, Value>,
|
||||
}
|
||||
|
||||
impl ImageInspection {
|
||||
pub fn get_version(&self) -> Option<String> {
|
||||
Some(
|
||||
self.labels
|
||||
.get("org.opencontainers.image.version")?
|
||||
.as_str()
|
||||
.map(std::string::ToString::to_string)?
|
||||
.split('.')
|
||||
.take(1)
|
||||
.collect(),
|
||||
)
|
||||
}
|
||||
}
|
||||
11
recipe/src/lib.rs
Normal file
11
recipe/src/lib.rs
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
pub mod akmods_info;
|
||||
pub mod image_inspection;
|
||||
pub mod module;
|
||||
pub mod module_ext;
|
||||
pub mod recipe;
|
||||
|
||||
pub use akmods_info::*;
|
||||
pub use image_inspection::*;
|
||||
pub use module::*;
|
||||
pub use module_ext::*;
|
||||
pub use recipe::*;
|
||||
133
recipe/src/module.rs
Normal file
133
recipe/src/module.rs
Normal file
|
|
@ -0,0 +1,133 @@
|
|||
use std::{borrow::Cow, process};
|
||||
|
||||
use indexmap::IndexMap;
|
||||
use log::{error, trace};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde_yaml::Value;
|
||||
use typed_builder::TypedBuilder;
|
||||
|
||||
use crate::{AkmodsInfo, ModuleExt};
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, TypedBuilder)]
|
||||
pub struct Module<'a> {
|
||||
#[builder(default, setter(into, strip_option))]
|
||||
#[serde(rename = "type", skip_serializing_if = "Option::is_none")]
|
||||
pub module_type: Option<Cow<'a, str>>,
|
||||
|
||||
#[builder(default, setter(into, strip_option))]
|
||||
#[serde(rename = "from-file", skip_serializing_if = "Option::is_none")]
|
||||
pub from_file: Option<Cow<'a, str>>,
|
||||
|
||||
#[builder(default, setter(into, strip_option))]
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub source: Option<Cow<'a, str>>,
|
||||
|
||||
#[serde(flatten)]
|
||||
#[builder(default, setter(into))]
|
||||
pub config: IndexMap<String, Value>,
|
||||
}
|
||||
|
||||
impl<'a> Module<'a> {
|
||||
#[must_use]
|
||||
pub fn get_modules(modules: &[Self]) -> Vec<Self> {
|
||||
modules
|
||||
.iter()
|
||||
.flat_map(|module| {
|
||||
module.from_file.as_ref().map_or_else(
|
||||
|| vec![module.clone()],
|
||||
|file_name| match ModuleExt::parse_module_from_file(file_name) {
|
||||
Err(e) => {
|
||||
error!("Failed to get module from {file_name}: {e}");
|
||||
vec![]
|
||||
}
|
||||
Ok(module_ext) => Self::get_modules(&module_ext.modules),
|
||||
},
|
||||
)
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn get_module_type_list(&'a self, typ: &str, list_key: &str) -> Option<Vec<String>> {
|
||||
if self.module_type.as_ref()? == typ {
|
||||
Some(
|
||||
self.config
|
||||
.get(list_key)?
|
||||
.as_sequence()?
|
||||
.iter()
|
||||
.filter_map(|t| Some(t.as_str()?.to_owned()))
|
||||
.collect(),
|
||||
)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn get_containerfile_list(&'a self) -> Option<Vec<String>> {
|
||||
self.get_module_type_list("containerfile", "containerfiles")
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn get_containerfile_snippets(&'a self) -> Option<Vec<String>> {
|
||||
self.get_module_type_list("containerfile", "snippets")
|
||||
}
|
||||
|
||||
pub fn print_module_context(&'a self) -> String {
|
||||
serde_json::to_string(self).unwrap_or_else(|e| {
|
||||
error!("Failed to parse module!!!!!: {e}");
|
||||
process::exit(1);
|
||||
})
|
||||
}
|
||||
|
||||
pub fn get_files_list(&'a self) -> Option<Vec<(String, String)>> {
|
||||
Some(
|
||||
self.config
|
||||
.get("files")?
|
||||
.as_sequence()?
|
||||
.iter()
|
||||
.filter_map(|entry| entry.as_mapping())
|
||||
.flatten()
|
||||
.filter_map(|(src, dest)| {
|
||||
Some((
|
||||
format!("./config/files/{}", src.as_str()?),
|
||||
dest.as_str()?.to_string(),
|
||||
))
|
||||
})
|
||||
.collect(),
|
||||
)
|
||||
}
|
||||
|
||||
pub fn generate_akmods_info(&'a self, os_version: &str) -> AkmodsInfo {
|
||||
trace!("generate_akmods_base({self:#?}, {os_version})");
|
||||
|
||||
let base = self
|
||||
.config
|
||||
.get("base")
|
||||
.map(|b| b.as_str().unwrap_or_default());
|
||||
let nvidia_version = self
|
||||
.config
|
||||
.get("nvidia-version")
|
||||
.map(|v| v.as_u64().unwrap_or_default());
|
||||
|
||||
AkmodsInfo::builder()
|
||||
.images(match (base, nvidia_version) {
|
||||
(Some(b), Some(nv)) if !b.is_empty() && nv > 0 => (
|
||||
format!("akmods:{b}-{os_version}"),
|
||||
Some(format!("akmods-nvidia:{b}-{os_version}-{nv}")),
|
||||
),
|
||||
(Some(b), _) if !b.is_empty() => (format!("akmods:{b}-{os_version}"), None),
|
||||
(_, Some(nv)) if nv > 0 => (
|
||||
format!("akmods:main-{os_version}"),
|
||||
Some(format!("akmods-nvidia:main-{os_version}")),
|
||||
),
|
||||
_ => (format!("akmods:main-{os_version}"), None),
|
||||
})
|
||||
.stage_name(format!(
|
||||
"{}{}",
|
||||
base.unwrap_or("main"),
|
||||
nvidia_version.map_or_else(String::default, |nv| format!("-{nv}"))
|
||||
))
|
||||
.build()
|
||||
}
|
||||
}
|
||||
54
recipe/src/module_ext.rs
Normal file
54
recipe/src/module_ext.rs
Normal file
|
|
@ -0,0 +1,54 @@
|
|||
use std::{borrow::Cow, collections::HashSet, fs, path::PathBuf};
|
||||
|
||||
use anyhow::Result;
|
||||
use log::trace;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use typed_builder::TypedBuilder;
|
||||
|
||||
use crate::{AkmodsInfo, Module};
|
||||
|
||||
#[derive(Default, Serialize, Clone, Deserialize, Debug, TypedBuilder)]
|
||||
pub struct ModuleExt<'a> {
|
||||
#[builder(default, setter(into))]
|
||||
pub modules: Cow<'a, [Module<'a>]>,
|
||||
}
|
||||
|
||||
impl ModuleExt<'_> {
|
||||
/// # Parse a module file returning a [`ModuleExt`]
|
||||
///
|
||||
/// # Errors
|
||||
/// Can return an `anyhow` Error if the file cannot be read or deserialized
|
||||
/// into a [`ModuleExt`]
|
||||
pub fn parse_module_from_file(file_name: &str) -> Result<Self> {
|
||||
let file_path = PathBuf::from("config").join(file_name);
|
||||
let file_path = if file_path.is_absolute() {
|
||||
file_path
|
||||
} else {
|
||||
std::env::current_dir()?.join(file_path)
|
||||
};
|
||||
|
||||
let file = fs::read_to_string(file_path)?;
|
||||
|
||||
serde_yaml::from_str::<Self>(&file).map_or_else(
|
||||
|_| -> Result<Self> {
|
||||
let module = serde_yaml::from_str::<Module>(&file)
|
||||
.map_err(blue_build_utils::serde_yaml_err(&file))?;
|
||||
Ok(Self::builder().modules(vec![module]).build())
|
||||
},
|
||||
Ok,
|
||||
)
|
||||
}
|
||||
|
||||
pub fn get_akmods_info_list(&self, os_version: &str) -> Vec<AkmodsInfo> {
|
||||
trace!("get_akmods_image_list({self:#?}, {os_version})");
|
||||
|
||||
let mut seen = HashSet::new();
|
||||
|
||||
self.modules
|
||||
.iter()
|
||||
.filter(|module| module.module_type.as_ref().is_some_and(|t| t == "akmods"))
|
||||
.map(|module| module.generate_akmods_info(os_version))
|
||||
.filter(|image| seen.insert(image.clone()))
|
||||
.collect()
|
||||
}
|
||||
}
|
||||
|
|
@ -1,26 +1,16 @@
|
|||
use std::{
|
||||
borrow::Cow,
|
||||
collections::{HashMap, HashSet},
|
||||
env, fs,
|
||||
path::{Path, PathBuf},
|
||||
process::{self, Command},
|
||||
};
|
||||
use std::{borrow::Cow, env, fs, path::Path, process::Command};
|
||||
|
||||
use anyhow::Result;
|
||||
use blue_build_utils::constants::*;
|
||||
use chrono::Local;
|
||||
use format_serde_error::SerdeError;
|
||||
use indexmap::IndexMap;
|
||||
use log::{debug, error, info, trace, warn};
|
||||
use log::{debug, info, trace, warn};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde_json::Value as JsonValue;
|
||||
use serde_yaml::Value;
|
||||
use typed_builder::TypedBuilder;
|
||||
|
||||
use crate::{
|
||||
akmods_info::AkmodsInfo,
|
||||
constants::*,
|
||||
ops::{self, check_command_exists},
|
||||
};
|
||||
use crate::{ImageInspection, Module, ModuleExt};
|
||||
|
||||
#[derive(Default, Serialize, Clone, Deserialize, Debug, TypedBuilder)]
|
||||
pub struct Recipe<'a> {
|
||||
|
|
@ -146,8 +136,8 @@ impl<'a> Recipe<'a> {
|
|||
|
||||
debug!("Recipe contents: {file}");
|
||||
|
||||
let mut recipe =
|
||||
serde_yaml::from_str::<Recipe>(&file).map_err(ops::serde_yaml_err(&file))?;
|
||||
let mut recipe = serde_yaml::from_str::<Recipe>(&file)
|
||||
.map_err(blue_build_utils::serde_yaml_err(&file))?;
|
||||
|
||||
recipe.modules_ext.modules = Module::get_modules(&recipe.modules_ext.modules).into();
|
||||
|
||||
|
|
@ -157,7 +147,7 @@ impl<'a> Recipe<'a> {
|
|||
pub fn get_os_version(&self) -> String {
|
||||
trace!("Recipe::get_os_version()");
|
||||
|
||||
if check_command_exists("skopeo").is_err() {
|
||||
if blue_build_utils::check_command_exists("skopeo").is_err() {
|
||||
warn!("The 'skopeo' command doesn't exist, falling back to version defined in recipe");
|
||||
return self.image_version.to_string();
|
||||
}
|
||||
|
|
@ -205,193 +195,3 @@ impl<'a> Recipe<'a> {
|
|||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default, Serialize, Clone, Deserialize, Debug, TypedBuilder)]
|
||||
pub struct ModuleExt<'a> {
|
||||
#[builder(default, setter(into))]
|
||||
pub modules: Cow<'a, [Module<'a>]>,
|
||||
}
|
||||
|
||||
impl ModuleExt<'_> {
|
||||
/// # Parse a module file returning a [`ModuleExt`]
|
||||
///
|
||||
/// # Errors
|
||||
/// Can return an `anyhow` Error if the file cannot be read or deserialized
|
||||
/// into a [`ModuleExt`]
|
||||
pub fn parse_module_from_file(file_name: &str) -> Result<Self> {
|
||||
let file_path = PathBuf::from("config").join(file_name);
|
||||
let file_path = if file_path.is_absolute() {
|
||||
file_path
|
||||
} else {
|
||||
std::env::current_dir()?.join(file_path)
|
||||
};
|
||||
|
||||
let file = fs::read_to_string(file_path)?;
|
||||
|
||||
serde_yaml::from_str::<Self>(&file).map_or_else(
|
||||
|_| -> Result<Self> {
|
||||
let module =
|
||||
serde_yaml::from_str::<Module>(&file).map_err(ops::serde_yaml_err(&file))?;
|
||||
Ok(Self::builder().modules(vec![module]).build())
|
||||
},
|
||||
Ok,
|
||||
)
|
||||
}
|
||||
|
||||
pub fn get_akmods_info_list(&self, os_version: &str) -> Vec<AkmodsInfo> {
|
||||
trace!("get_akmods_image_list({self:#?}, {os_version})");
|
||||
|
||||
let mut seen = HashSet::new();
|
||||
|
||||
self.modules
|
||||
.iter()
|
||||
.filter(|module| module.module_type.as_ref().is_some_and(|t| t == "akmods"))
|
||||
.map(|module| module.generate_akmods_info(os_version))
|
||||
.filter(|image| seen.insert(image.clone()))
|
||||
.collect()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, TypedBuilder)]
|
||||
pub struct Module<'a> {
|
||||
#[builder(default, setter(into, strip_option))]
|
||||
#[serde(rename = "type", skip_serializing_if = "Option::is_none")]
|
||||
pub module_type: Option<Cow<'a, str>>,
|
||||
|
||||
#[builder(default, setter(into, strip_option))]
|
||||
#[serde(rename = "from-file", skip_serializing_if = "Option::is_none")]
|
||||
pub from_file: Option<Cow<'a, str>>,
|
||||
|
||||
#[builder(default, setter(into, strip_option))]
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub source: Option<Cow<'a, str>>,
|
||||
|
||||
#[serde(flatten)]
|
||||
#[builder(default, setter(into))]
|
||||
pub config: IndexMap<String, Value>,
|
||||
}
|
||||
|
||||
impl<'a> Module<'a> {
|
||||
#[must_use]
|
||||
pub fn get_modules(modules: &[Self]) -> Vec<Self> {
|
||||
modules
|
||||
.iter()
|
||||
.flat_map(|module| {
|
||||
module.from_file.as_ref().map_or_else(
|
||||
|| vec![module.clone()],
|
||||
|file_name| match ModuleExt::parse_module_from_file(file_name) {
|
||||
Err(e) => {
|
||||
error!("Failed to get module from {file_name}: {e}");
|
||||
vec![]
|
||||
}
|
||||
Ok(module_ext) => Self::get_modules(&module_ext.modules),
|
||||
},
|
||||
)
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn get_module_type_list(&'a self, typ: &str, list_key: &str) -> Option<Vec<String>> {
|
||||
if self.module_type.as_ref()? == typ {
|
||||
Some(
|
||||
self.config
|
||||
.get(list_key)?
|
||||
.as_sequence()?
|
||||
.iter()
|
||||
.filter_map(|t| Some(t.as_str()?.to_owned()))
|
||||
.collect(),
|
||||
)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn get_containerfile_list(&'a self) -> Option<Vec<String>> {
|
||||
self.get_module_type_list("containerfile", "containerfiles")
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn get_containerfile_snippets(&'a self) -> Option<Vec<String>> {
|
||||
self.get_module_type_list("containerfile", "snippets")
|
||||
}
|
||||
|
||||
pub fn print_module_context(&'a self) -> String {
|
||||
serde_json::to_string(self).unwrap_or_else(|e| {
|
||||
error!("Failed to parse module!!!!!: {e}");
|
||||
process::exit(1);
|
||||
})
|
||||
}
|
||||
|
||||
pub fn get_files_list(&'a self) -> Option<Vec<(String, String)>> {
|
||||
Some(
|
||||
self.config
|
||||
.get("files")?
|
||||
.as_sequence()?
|
||||
.iter()
|
||||
.filter_map(|entry| entry.as_mapping())
|
||||
.flatten()
|
||||
.filter_map(|(src, dest)| {
|
||||
Some((
|
||||
format!("./config/files/{}", src.as_str()?),
|
||||
dest.as_str()?.to_string(),
|
||||
))
|
||||
})
|
||||
.collect(),
|
||||
)
|
||||
}
|
||||
|
||||
pub fn generate_akmods_info(&'a self, os_version: &str) -> AkmodsInfo {
|
||||
trace!("generate_akmods_base({self:#?}, {os_version})");
|
||||
|
||||
let base = self
|
||||
.config
|
||||
.get("base")
|
||||
.map(|b| b.as_str().unwrap_or_default());
|
||||
let nvidia_version = self
|
||||
.config
|
||||
.get("nvidia-version")
|
||||
.map(|v| v.as_u64().unwrap_or_default());
|
||||
|
||||
AkmodsInfo::builder()
|
||||
.images(match (base, nvidia_version) {
|
||||
(Some(b), Some(nv)) if !b.is_empty() && nv > 0 => (
|
||||
format!("akmods:{b}-{os_version}"),
|
||||
Some(format!("akmods-nvidia:{b}-{os_version}-{nv}")),
|
||||
),
|
||||
(Some(b), _) if !b.is_empty() => (format!("akmods:{b}-{os_version}"), None),
|
||||
(_, Some(nv)) if nv > 0 => (
|
||||
format!("akmods:main-{os_version}"),
|
||||
Some(format!("akmods-nvidia:main-{os_version}")),
|
||||
),
|
||||
_ => (format!("akmods:main-{os_version}"), None),
|
||||
})
|
||||
.stage_name(format!(
|
||||
"{}{}",
|
||||
base.unwrap_or("main"),
|
||||
nvidia_version.map_or_else(String::default, |nv| format!("-{nv}"))
|
||||
))
|
||||
.build()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Debug, Clone)]
|
||||
struct ImageInspection {
|
||||
#[serde(alias = "Labels")]
|
||||
labels: HashMap<String, JsonValue>,
|
||||
}
|
||||
|
||||
impl ImageInspection {
|
||||
pub fn get_version(&self) -> Option<String> {
|
||||
Some(
|
||||
self.labels
|
||||
.get("org.opencontainers.image.version")?
|
||||
.as_str()
|
||||
.map(std::string::ToString::to_string)?
|
||||
.split('.')
|
||||
.take(1)
|
||||
.collect(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -3,6 +3,8 @@ use log::error;
|
|||
use clap::{command, crate_authors, Parser, Subcommand};
|
||||
use clap_verbosity_flag::{InfoLevel, Verbosity};
|
||||
|
||||
use crate::shadow;
|
||||
|
||||
pub mod bug_report;
|
||||
pub mod build;
|
||||
pub mod completions;
|
||||
|
|
@ -10,7 +12,6 @@ pub mod completions;
|
|||
pub mod init;
|
||||
pub mod local;
|
||||
pub mod template;
|
||||
pub mod utils;
|
||||
|
||||
pub trait BlueBuildCommand {
|
||||
/// Runs the command and returns a result
|
||||
|
|
@ -29,8 +30,6 @@ pub trait BlueBuildCommand {
|
|||
}
|
||||
}
|
||||
|
||||
shadow_rs::shadow!(shadow);
|
||||
|
||||
#[derive(Parser, Debug)]
|
||||
#[clap(
|
||||
name = "BlueBuild",
|
||||
|
|
|
|||
|
|
@ -1,17 +1,17 @@
|
|||
use askama::Template;
|
||||
use blue_build_recipe::Recipe;
|
||||
use blue_build_template::{GithubIssueTemplate, Template};
|
||||
use blue_build_utils::constants::*;
|
||||
use clap::Args;
|
||||
use clap_complete::Shell;
|
||||
use fuzzy_matcher::{skim::SkimMatcherV2, FuzzyMatcher};
|
||||
use log::{debug, error, trace};
|
||||
use requestty::question::{completions, Completions};
|
||||
use std::borrow::Cow;
|
||||
use std::time::Duration;
|
||||
use typed_builder::TypedBuilder;
|
||||
|
||||
use super::utils::exec_cmd;
|
||||
use super::BlueBuildCommand;
|
||||
|
||||
use crate::{constants::*, module_recipe::Recipe, shadow};
|
||||
use crate::shadow;
|
||||
|
||||
#[derive(Default, Debug, Clone, TypedBuilder, Args)]
|
||||
pub struct BugReportRecipe {
|
||||
|
|
@ -272,7 +272,7 @@ fn get_shell_version(shell: &str) -> String {
|
|||
error!("Powershell is not supported.");
|
||||
None
|
||||
}
|
||||
_ => exec_cmd(shell, &["--version"], time_limit),
|
||||
_ => blue_build_utils::exec_cmd(shell, &["--version"], time_limit),
|
||||
}
|
||||
.map_or_else(
|
||||
|| UNKNOWN_VERSION.to_string(),
|
||||
|
|
@ -284,52 +284,6 @@ fn get_shell_version(shell: &str) -> String {
|
|||
// Git
|
||||
// ============================================================================= //
|
||||
|
||||
#[derive(Debug, Clone, Template, TypedBuilder)]
|
||||
#[template(path = "github_issue.j2", escape = "md")]
|
||||
struct GithubIssueTemplate<'a> {
|
||||
#[builder(setter(into))]
|
||||
bb_version: Cow<'a, str>,
|
||||
|
||||
#[builder(setter(into))]
|
||||
build_rust_channel: Cow<'a, str>,
|
||||
|
||||
#[builder(setter(into))]
|
||||
build_time: Cow<'a, str>,
|
||||
|
||||
#[builder(setter(into))]
|
||||
git_commit_hash: Cow<'a, str>,
|
||||
|
||||
#[builder(setter(into))]
|
||||
os_name: Cow<'a, str>,
|
||||
|
||||
#[builder(setter(into))]
|
||||
os_version: Cow<'a, str>,
|
||||
|
||||
#[builder(setter(into))]
|
||||
pkg_branch_tag: Cow<'a, str>,
|
||||
|
||||
#[builder(setter(into))]
|
||||
recipe: Cow<'a, str>,
|
||||
|
||||
#[builder(setter(into))]
|
||||
rust_channel: Cow<'a, str>,
|
||||
|
||||
#[builder(setter(into))]
|
||||
rust_version: Cow<'a, str>,
|
||||
|
||||
#[builder(setter(into))]
|
||||
shell_name: Cow<'a, str>,
|
||||
|
||||
#[builder(setter(into))]
|
||||
shell_version: Cow<'a, str>,
|
||||
|
||||
#[builder(setter(into))]
|
||||
terminal_name: Cow<'a, str>,
|
||||
|
||||
#[builder(setter(into))]
|
||||
terminal_version: Cow<'a, str>,
|
||||
}
|
||||
|
||||
fn get_pkg_branch_tag() -> String {
|
||||
format!("{} ({})", shadow::BRANCH, shadow::LAST_TAG)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,6 +8,8 @@ use std::{
|
|||
};
|
||||
|
||||
use anyhow::{anyhow, bail, Result};
|
||||
use blue_build_recipe::Recipe;
|
||||
use blue_build_utils::constants::*;
|
||||
use clap::Args;
|
||||
use colorized::{Color, Colors};
|
||||
use log::{debug, info, trace, warn};
|
||||
|
|
@ -35,12 +37,7 @@ use tokio::{
|
|||
sync::oneshot::{self, Sender},
|
||||
};
|
||||
|
||||
use crate::{
|
||||
commands::template::TemplateCommand,
|
||||
constants::{self, *},
|
||||
module_recipe::Recipe,
|
||||
ops,
|
||||
};
|
||||
use crate::commands::template::TemplateCommand;
|
||||
|
||||
use super::BlueBuildCommand;
|
||||
|
||||
|
|
@ -181,19 +178,19 @@ impl BlueBuildCommand for BuildCommand {
|
|||
// -> If it does => *Ask* to add to .gitignore and remove from git
|
||||
// -> If it doesn't => *Ask* to continue and override the file
|
||||
|
||||
let container_file_path = Path::new(constants::CONTAINER_FILE);
|
||||
let container_file_path = Path::new(CONTAINER_FILE);
|
||||
|
||||
if !self.force && container_file_path.exists() {
|
||||
let gitignore = fs::read_to_string(constants::GITIGNORE_PATH)?;
|
||||
let gitignore = fs::read_to_string(GITIGNORE_PATH)?;
|
||||
|
||||
let is_ignored = gitignore
|
||||
.lines()
|
||||
.any(|line: &str| line.contains(constants::CONTAINER_FILE));
|
||||
.any(|line: &str| line.contains(CONTAINER_FILE));
|
||||
|
||||
if !is_ignored {
|
||||
let containerfile = fs::read_to_string(container_file_path)?;
|
||||
let has_label = containerfile.lines().any(|line| {
|
||||
let label = format!("LABEL {}", constants::BUILD_ID_LABEL);
|
||||
let label = format!("LABEL {}", BUILD_ID_LABEL);
|
||||
line.to_string().trim().starts_with(&label)
|
||||
});
|
||||
|
||||
|
|
@ -211,9 +208,9 @@ impl BlueBuildCommand for BuildCommand {
|
|||
|
||||
if let Ok(answer) = requestty::prompt_one(question) {
|
||||
if answer.as_bool().unwrap_or(false) {
|
||||
ops::append_to_file(
|
||||
constants::GITIGNORE_PATH,
|
||||
&format!("/{}", constants::CONTAINER_FILE),
|
||||
blue_build_utils::append_to_file(
|
||||
GITIGNORE_PATH,
|
||||
&format!("/{}", CONTAINER_FILE),
|
||||
)?;
|
||||
}
|
||||
}
|
||||
|
|
@ -231,15 +228,15 @@ impl BlueBuildCommand for BuildCommand {
|
|||
.unwrap_or_else(|| PathBuf::from(RECIPE_PATH));
|
||||
|
||||
#[cfg(not(feature = "podman-api"))]
|
||||
if let Err(e1) = ops::check_command_exists("buildah") {
|
||||
ops::check_command_exists("podman").map_err(|e2| {
|
||||
if let Err(e1) = blue_build_utils::check_command_exists("buildah") {
|
||||
blue_build_utils::check_command_exists("podman").map_err(|e2| {
|
||||
anyhow!("Need either 'buildah' or 'podman' commands to proceed: {e1}, {e2}")
|
||||
})?;
|
||||
}
|
||||
|
||||
if self.push {
|
||||
ops::check_command_exists("cosign")?;
|
||||
ops::check_command_exists("skopeo")?;
|
||||
blue_build_utils::check_command_exists("cosign")?;
|
||||
blue_build_utils::check_command_exists("skopeo")?;
|
||||
check_cosign_files()?;
|
||||
}
|
||||
|
||||
|
|
@ -420,8 +417,8 @@ impl BuildCommand {
|
|||
|
||||
info!("Logging into the registry, {registry}");
|
||||
let login_output = match (
|
||||
ops::check_command_exists("buildah"),
|
||||
ops::check_command_exists("podman"),
|
||||
blue_build_utils::check_command_exists("buildah"),
|
||||
blue_build_utils::check_command_exists("podman"),
|
||||
) {
|
||||
(Ok(()), _) => {
|
||||
trace!("buildah login -u {username} -p [MASKED] {registry}");
|
||||
|
|
@ -550,8 +547,8 @@ impl BuildCommand {
|
|||
|
||||
info!("Building image {full_image}");
|
||||
let status = match (
|
||||
ops::check_command_exists("buildah"),
|
||||
ops::check_command_exists("podman"),
|
||||
blue_build_utils::check_command_exists("buildah"),
|
||||
blue_build_utils::check_command_exists("podman"),
|
||||
) {
|
||||
(Ok(()), _) => {
|
||||
trace!("buildah build -t {full_image}");
|
||||
|
|
@ -588,7 +585,7 @@ impl BuildCommand {
|
|||
let retry_count = if retry { self.retry_count } else { 0 };
|
||||
|
||||
// Push images with retries (1s delay between retries)
|
||||
ops::retry(retry_count, 1000, || push_images(tags, image_name))?;
|
||||
blue_build_utils::retry(retry_count, 1000, || push_images(tags, image_name))?;
|
||||
sign_images(image_name, tags.first().map(String::as_str))?;
|
||||
}
|
||||
|
||||
|
|
@ -963,8 +960,8 @@ fn tag_images(tags: &[String], image_name: &str, full_image: &str) -> Result<()>
|
|||
let tag_image = format!("{image_name}:{tag}");
|
||||
|
||||
let status = match (
|
||||
ops::check_command_exists("buildah"),
|
||||
ops::check_command_exists("podman"),
|
||||
blue_build_utils::check_command_exists("buildah"),
|
||||
blue_build_utils::check_command_exists("podman"),
|
||||
) {
|
||||
(Ok(()), _) => {
|
||||
trace!("buildah tag {full_image} {tag_image}");
|
||||
|
|
@ -1001,8 +998,8 @@ fn push_images(tags: &[String], image_name: &str) -> Result<()> {
|
|||
let tag_image = format!("{image_name}:{tag}");
|
||||
|
||||
let status = match (
|
||||
ops::check_command_exists("buildah"),
|
||||
ops::check_command_exists("podman"),
|
||||
blue_build_utils::check_command_exists("buildah"),
|
||||
blue_build_utils::check_command_exists("podman"),
|
||||
) {
|
||||
(Ok(()), _) => {
|
||||
trace!("buildah push {tag_image}");
|
||||
|
|
|
|||
|
|
@ -4,10 +4,9 @@ use std::{
|
|||
};
|
||||
|
||||
use anyhow::{bail, Result};
|
||||
use blue_build_utils::constants::*;
|
||||
use log::trace;
|
||||
|
||||
use crate::{constants::*, ops};
|
||||
|
||||
#[cfg(feature = "podman-api")]
|
||||
#[derive(Debug, Clone, Default)]
|
||||
pub enum BuildStrategy {
|
||||
|
|
@ -29,8 +28,8 @@ impl BuildStrategy {
|
|||
PathBuf::from(RUN_PODMAN_SOCK),
|
||||
PathBuf::from(VAR_RUN_PODMAN_PODMAN_SOCK),
|
||||
PathBuf::from(VAR_RUN_PODMAN_SOCK),
|
||||
ops::check_command_exists("buildah"),
|
||||
ops::check_command_exists("podman"),
|
||||
blue_build_utils::check_command_exists("buildah"),
|
||||
blue_build_utils::check_command_exists("podman"),
|
||||
) {
|
||||
(Ok(xdg_runtime), _, _, _, _, _)
|
||||
if Path::new(&format!("{xdg_runtime}/podman/podman.sock")).exists() =>
|
||||
|
|
|
|||
|
|
@ -5,17 +5,14 @@ use std::{
|
|||
};
|
||||
|
||||
use anyhow::{bail, Result};
|
||||
use blue_build_recipe::Recipe;
|
||||
use blue_build_utils::constants::*;
|
||||
use clap::Args;
|
||||
use log::{debug, info, trace};
|
||||
use typed_builder::TypedBuilder;
|
||||
use users::{Users, UsersCache};
|
||||
|
||||
use crate::{
|
||||
commands::build::BuildCommand,
|
||||
constants::{ARCHIVE_SUFFIX, LOCAL_BUILD},
|
||||
module_recipe::Recipe,
|
||||
ops,
|
||||
};
|
||||
use crate::commands::build::BuildCommand;
|
||||
|
||||
use super::BlueBuildCommand;
|
||||
|
||||
|
|
@ -148,7 +145,7 @@ impl BlueBuildCommand for RebaseCommand {
|
|||
fn check_can_run() -> Result<()> {
|
||||
trace!("check_can_run()");
|
||||
|
||||
ops::check_command_exists("rpm-ostree")?;
|
||||
blue_build_utils::check_command_exists("rpm-ostree")?;
|
||||
|
||||
let cache = UsersCache::new();
|
||||
if cache.get_current_uid() != 0 {
|
||||
|
|
|
|||
|
|
@ -1,57 +1,16 @@
|
|||
use std::{
|
||||
env, fs,
|
||||
path::{Path, PathBuf},
|
||||
process,
|
||||
};
|
||||
use std::path::PathBuf;
|
||||
|
||||
use anyhow::{anyhow, Result};
|
||||
use askama::Template;
|
||||
use blue_build_recipe::Recipe;
|
||||
use blue_build_template::{ContainerFileTemplate, Template};
|
||||
use blue_build_utils::constants::*;
|
||||
use clap::Args;
|
||||
use log::{debug, error, info, trace};
|
||||
use log::{debug, info, trace};
|
||||
use typed_builder::TypedBuilder;
|
||||
use uuid::Uuid;
|
||||
|
||||
use crate::{constants::*, module_recipe::Recipe};
|
||||
|
||||
use super::BlueBuildCommand;
|
||||
|
||||
#[derive(Debug, Clone, Template, TypedBuilder)]
|
||||
#[template(path = "Containerfile.j2", escape = "none")]
|
||||
pub struct ContainerFileTemplate<'a> {
|
||||
recipe: &'a Recipe<'a>,
|
||||
|
||||
#[builder(setter(into))]
|
||||
recipe_path: &'a Path,
|
||||
|
||||
#[builder(setter(into))]
|
||||
build_id: Uuid,
|
||||
|
||||
#[builder(default)]
|
||||
export_script: ExportsTemplate,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Default, Template)]
|
||||
#[template(path = "export.sh", escape = "none")]
|
||||
pub struct ExportsTemplate;
|
||||
|
||||
impl ExportsTemplate {
|
||||
fn print_script(&self) -> String {
|
||||
trace!("print_script({self})");
|
||||
|
||||
format!(
|
||||
"\"{}\"",
|
||||
self.render()
|
||||
.unwrap_or_else(|e| {
|
||||
error!("Failed to render export.sh script: {e}");
|
||||
process::exit(1);
|
||||
})
|
||||
.replace('\n', "\\n")
|
||||
.replace('\"', "\\\"")
|
||||
.replace('$', "\\$")
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Args, TypedBuilder)]
|
||||
pub struct TemplateCommand {
|
||||
/// The recipe file to create a template from
|
||||
|
|
@ -127,48 +86,3 @@ impl TemplateCommand {
|
|||
// ======================================================== //
|
||||
// ========================= Helpers ====================== //
|
||||
// ======================================================== //
|
||||
|
||||
fn has_cosign_file() -> bool {
|
||||
trace!("has_cosign_file()");
|
||||
std::env::current_dir()
|
||||
.map(|p| p.join(COSIGN_PATH).exists())
|
||||
.unwrap_or(false)
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
fn print_containerfile(containerfile: &str) -> String {
|
||||
trace!("print_containerfile({containerfile})");
|
||||
debug!("Loading containerfile contents for {containerfile}");
|
||||
|
||||
let path = format!("config/containerfiles/{containerfile}/Containerfile");
|
||||
|
||||
let file = fs::read_to_string(&path).unwrap_or_else(|e| {
|
||||
error!("Failed to read file {path}: {e}");
|
||||
process::exit(1);
|
||||
});
|
||||
|
||||
debug!("Containerfile contents {path}:\n{file}");
|
||||
|
||||
file
|
||||
}
|
||||
|
||||
fn get_github_repo_owner() -> Option<String> {
|
||||
Some(env::var(GITHUB_REPOSITORY_OWNER).ok()?.to_lowercase())
|
||||
}
|
||||
|
||||
fn get_gitlab_registry_path() -> Option<String> {
|
||||
Some(
|
||||
format!(
|
||||
"{}/{}/{}",
|
||||
env::var(CI_REGISTRY).ok()?,
|
||||
env::var(CI_PROJECT_NAMESPACE).ok()?,
|
||||
env::var(CI_PROJECT_NAME).ok()?,
|
||||
)
|
||||
.to_lowercase(),
|
||||
)
|
||||
}
|
||||
|
||||
fn modules_exists() -> bool {
|
||||
let mod_path = Path::new("modules");
|
||||
mod_path.exists() && mod_path.is_dir()
|
||||
}
|
||||
|
|
|
|||
13
src/lib.rs
13
src/lib.rs
|
|
@ -1,19 +1,6 @@
|
|||
//! The root library for blue-build.
|
||||
#![warn(
|
||||
clippy::correctness,
|
||||
clippy::suspicious,
|
||||
clippy::perf,
|
||||
clippy::style,
|
||||
clippy::nursery
|
||||
)]
|
||||
#![doc = include_str!("../README.md")]
|
||||
#![forbid(unsafe_code)]
|
||||
#![allow(clippy::module_name_repetitions)]
|
||||
|
||||
shadow_rs::shadow!(shadow);
|
||||
|
||||
pub mod akmods_info;
|
||||
pub mod commands;
|
||||
pub mod constants;
|
||||
pub mod module_recipe;
|
||||
mod ops;
|
||||
|
|
|
|||
25
template/Cargo.toml
Normal file
25
template/Cargo.toml
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
[package]
|
||||
name = "blue-build-template"
|
||||
version.workspace = true
|
||||
edition.workspace = true
|
||||
description.workspace = true
|
||||
repository.workspace = true
|
||||
license.workspace = true
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
askama = { version = "0.12", features = ["serde-json", "serde-yaml"] }
|
||||
blue-build-recipe = { path = "../recipe" }
|
||||
blue-build-utils = { path = "../utils" }
|
||||
|
||||
log.workspace = true
|
||||
serde.workspace = true
|
||||
serde_yaml.workspace = true
|
||||
serde_json.workspace = true
|
||||
typed-builder.workspace = true
|
||||
uuid.workspace = true
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
|
||||
137
template/src/lib.rs
Normal file
137
template/src/lib.rs
Normal file
|
|
@ -0,0 +1,137 @@
|
|||
use std::{borrow::Cow, env, fs, path::Path, process};
|
||||
|
||||
use blue_build_recipe::Recipe;
|
||||
use blue_build_utils::constants::*;
|
||||
use log::{debug, error, trace};
|
||||
use typed_builder::TypedBuilder;
|
||||
use uuid::Uuid;
|
||||
|
||||
pub use askama::Template;
|
||||
|
||||
#[derive(Debug, Clone, Template, TypedBuilder)]
|
||||
#[template(path = "Containerfile.j2", escape = "none")]
|
||||
pub struct ContainerFileTemplate<'a> {
|
||||
recipe: &'a Recipe<'a>,
|
||||
|
||||
#[builder(setter(into))]
|
||||
recipe_path: &'a Path,
|
||||
|
||||
#[builder(setter(into))]
|
||||
build_id: Uuid,
|
||||
|
||||
#[builder(default)]
|
||||
export_script: ExportsTemplate,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Default, Template)]
|
||||
#[template(path = "export.sh", escape = "none")]
|
||||
pub struct ExportsTemplate;
|
||||
|
||||
impl ExportsTemplate {
|
||||
fn print_script(&self) -> String {
|
||||
trace!("print_script({self})");
|
||||
|
||||
format!(
|
||||
"\"{}\"",
|
||||
self.render()
|
||||
.unwrap_or_else(|e| {
|
||||
error!("Failed to render export.sh script: {e}");
|
||||
process::exit(1);
|
||||
})
|
||||
.replace('\n', "\\n")
|
||||
.replace('\"', "\\\"")
|
||||
.replace('$', "\\$")
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Template, TypedBuilder)]
|
||||
#[template(path = "github_issue.j2", escape = "md")]
|
||||
pub struct GithubIssueTemplate<'a> {
|
||||
#[builder(setter(into))]
|
||||
bb_version: Cow<'a, str>,
|
||||
|
||||
#[builder(setter(into))]
|
||||
build_rust_channel: Cow<'a, str>,
|
||||
|
||||
#[builder(setter(into))]
|
||||
build_time: Cow<'a, str>,
|
||||
|
||||
#[builder(setter(into))]
|
||||
git_commit_hash: Cow<'a, str>,
|
||||
|
||||
#[builder(setter(into))]
|
||||
os_name: Cow<'a, str>,
|
||||
|
||||
#[builder(setter(into))]
|
||||
os_version: Cow<'a, str>,
|
||||
|
||||
#[builder(setter(into))]
|
||||
pkg_branch_tag: Cow<'a, str>,
|
||||
|
||||
#[builder(setter(into))]
|
||||
recipe: Cow<'a, str>,
|
||||
|
||||
#[builder(setter(into))]
|
||||
rust_channel: Cow<'a, str>,
|
||||
|
||||
#[builder(setter(into))]
|
||||
rust_version: Cow<'a, str>,
|
||||
|
||||
#[builder(setter(into))]
|
||||
shell_name: Cow<'a, str>,
|
||||
|
||||
#[builder(setter(into))]
|
||||
shell_version: Cow<'a, str>,
|
||||
|
||||
#[builder(setter(into))]
|
||||
terminal_name: Cow<'a, str>,
|
||||
|
||||
#[builder(setter(into))]
|
||||
terminal_version: Cow<'a, str>,
|
||||
}
|
||||
|
||||
fn has_cosign_file() -> bool {
|
||||
trace!("has_cosign_file()");
|
||||
std::env::current_dir()
|
||||
.map(|p| p.join(COSIGN_PATH).exists())
|
||||
.unwrap_or(false)
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
fn print_containerfile(containerfile: &str) -> String {
|
||||
trace!("print_containerfile({containerfile})");
|
||||
debug!("Loading containerfile contents for {containerfile}");
|
||||
|
||||
let path = format!("config/containerfiles/{containerfile}/Containerfile");
|
||||
|
||||
let file = fs::read_to_string(&path).unwrap_or_else(|e| {
|
||||
error!("Failed to read file {path}: {e}");
|
||||
process::exit(1);
|
||||
});
|
||||
|
||||
debug!("Containerfile contents {path}:\n{file}");
|
||||
|
||||
file
|
||||
}
|
||||
|
||||
fn get_github_repo_owner() -> Option<String> {
|
||||
Some(env::var(GITHUB_REPOSITORY_OWNER).ok()?.to_lowercase())
|
||||
}
|
||||
|
||||
fn get_gitlab_registry_path() -> Option<String> {
|
||||
Some(
|
||||
format!(
|
||||
"{}/{}/{}",
|
||||
env::var(CI_REGISTRY).ok()?,
|
||||
env::var(CI_PROJECT_NAMESPACE).ok()?,
|
||||
env::var(CI_PROJECT_NAME).ok()?,
|
||||
)
|
||||
.to_lowercase(),
|
||||
)
|
||||
}
|
||||
|
||||
fn modules_exists() -> bool {
|
||||
let mod_path = Path::new("modules");
|
||||
mod_path.exists() && mod_path.is_dir()
|
||||
}
|
||||
|
|
@ -23,7 +23,7 @@ RUN printf {{ export_script.print_script() }} >> /exports.sh && chmod +x /export
|
|||
|
||||
FROM {{ recipe.base_image }}:{{ recipe.image_version }}
|
||||
|
||||
LABEL {{ crate::constants::BUILD_ID_LABEL }}="{{ build_id }}"
|
||||
LABEL {{ blue_build_utils::constants::BUILD_ID_LABEL }}="{{ build_id }}"
|
||||
LABEL org.opencontainers.image.title="{{ recipe.name }}"
|
||||
LABEL org.opencontainers.image.description="{{ recipe.description }}"
|
||||
LABEL io.artifacthub.package.readme-url=https://raw.githubusercontent.com/blue-build/cli/main/README.md
|
||||
25
utils/Cargo.toml
Normal file
25
utils/Cargo.toml
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
[package]
|
||||
name = "blue-build-utils"
|
||||
version.workspace = true
|
||||
edition.workspace = true
|
||||
description.workspace = true
|
||||
repository.workspace = true
|
||||
license.workspace = true
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
directories = "5"
|
||||
process_control = { version = "4.0.3", features = ["crossbeam-channel"] }
|
||||
which = "6"
|
||||
|
||||
anyhow.workspace = true
|
||||
format_serde_error.workspace = true
|
||||
log.workspace = true
|
||||
serde.workspace = true
|
||||
serde_yaml.workspace = true
|
||||
serde_json.workspace = true
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
|
||||
|
|
@ -1,19 +1,12 @@
|
|||
use std::{
|
||||
ffi::OsStr,
|
||||
fmt::Debug,
|
||||
io::{Error, ErrorKind, Result},
|
||||
process::{Command, Stdio},
|
||||
time::{Duration, Instant},
|
||||
};
|
||||
|
||||
use process_control::{ChildExt, Control};
|
||||
use std::ffi::OsStr;
|
||||
use std::fmt::Debug;
|
||||
use std::io::{Error, ErrorKind, Result};
|
||||
use std::path::PathBuf;
|
||||
use std::process::{Command, Stdio};
|
||||
use std::time::{Duration, Instant};
|
||||
|
||||
#[must_use]
|
||||
pub fn home_dir() -> Option<PathBuf> {
|
||||
directories::BaseDirs::new().map(|base_dirs| base_dirs.home_dir().to_path_buf())
|
||||
}
|
||||
|
||||
// ================================================================================================= //
|
||||
// CommandOutput
|
||||
// ================================================================================================= //
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct CommandOutput {
|
||||
|
|
@ -27,7 +20,7 @@ pub struct CommandOutput {
|
|||
/// #
|
||||
/// # Errors
|
||||
///
|
||||
pub fn create_command<T: AsRef<OsStr>>(binary_name: T) -> Result<Command> {
|
||||
fn create_command<T: AsRef<OsStr>>(binary_name: T) -> Result<Command> {
|
||||
let binary_name = binary_name.as_ref();
|
||||
log::trace!("Creating Command for binary {:?}", binary_name);
|
||||
|
||||
|
|
@ -42,7 +35,6 @@ pub fn create_command<T: AsRef<OsStr>>(binary_name: T) -> Result<Command> {
|
|||
}
|
||||
};
|
||||
|
||||
#[allow(clippy::disallowed_methods)]
|
||||
let mut cmd = Command::new(full_path);
|
||||
cmd.stderr(Stdio::piped())
|
||||
.stdout(Stdio::piped())
|
||||
|
|
@ -71,7 +63,7 @@ fn internal_exec_cmd<T: AsRef<OsStr> + Debug, U: AsRef<OsStr> + Debug>(
|
|||
exec_timeout(&mut cmd, time_limit)
|
||||
}
|
||||
|
||||
pub fn exec_timeout(cmd: &mut Command, time_limit: Duration) -> Option<CommandOutput> {
|
||||
fn exec_timeout(cmd: &mut Command, time_limit: Duration) -> Option<CommandOutput> {
|
||||
let start = Instant::now();
|
||||
let process = match cmd.spawn() {
|
||||
Ok(process) => process,
|
||||
|
|
@ -1,9 +1,13 @@
|
|||
use std::{io::Write, process::Command};
|
||||
pub mod command_output;
|
||||
pub mod constants;
|
||||
|
||||
use std::{io::Write, path::PathBuf, process::Command, thread, time::Duration};
|
||||
|
||||
use anyhow::{anyhow, Result};
|
||||
use format_serde_error::SerdeError;
|
||||
use log::{debug, trace};
|
||||
use std::{thread, time::Duration};
|
||||
|
||||
pub use command_output::*;
|
||||
|
||||
pub fn check_command_exists(command: &str) -> Result<()> {
|
||||
trace!("check_command_exists({command})");
|
||||
|
|
@ -68,3 +72,8 @@ where
|
|||
};
|
||||
}
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn home_dir() -> Option<PathBuf> {
|
||||
directories::BaseDirs::new().map(|base_dirs| base_dirs.home_dir().to_path_buf())
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue