refactor: Swtich to using bon for builder pattern

This commit is contained in:
Gerald Pinder 2024-09-21 13:15:45 -04:00
parent aed7e275f2
commit 0c52cf6a54
36 changed files with 326 additions and 314 deletions

53
Cargo.lock generated
View file

@ -320,6 +320,7 @@ dependencies = [
"blue-build-recipe",
"blue-build-template",
"blue-build-utils",
"bon",
"clap",
"clap-verbosity-flag",
"clap_complete",
@ -339,7 +340,6 @@ dependencies = [
"serde_yaml 0.9.34+deprecated",
"shadow-rs",
"tempdir",
"typed-builder",
"urlencoding",
"users",
]
@ -350,6 +350,7 @@ version = "0.8.17"
dependencies = [
"anyhow",
"blue-build-utils",
"bon",
"chrono",
"clap",
"colored",
@ -375,7 +376,6 @@ dependencies = [
"sigstore",
"tempdir",
"tokio",
"typed-builder",
"users",
"uuid",
"zeroize",
@ -386,6 +386,7 @@ name = "blue-build-recipe"
version = "0.8.17"
dependencies = [
"blue-build-utils",
"bon",
"colored",
"indexmap 2.3.0",
"log",
@ -394,7 +395,6 @@ dependencies = [
"serde",
"serde_json",
"serde_yaml 0.9.34+deprecated",
"typed-builder",
]
[[package]]
@ -403,10 +403,10 @@ version = "0.8.17"
dependencies = [
"blue-build-recipe",
"blue-build-utils",
"bon",
"colored",
"log",
"rinja",
"typed-builder",
"uuid",
]
@ -417,6 +417,7 @@ dependencies = [
"atty",
"base64 0.22.1",
"blake2",
"bon",
"chrono",
"clap",
"directories",
@ -430,10 +431,32 @@ dependencies = [
"serde_json",
"serde_yaml 0.9.34+deprecated",
"syntect",
"typed-builder",
"which",
]
[[package]]
name = "bon"
version = "2.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "97493a391b4b18ee918675fb8663e53646fd09321c58b46afa04e8ce2499c869"
dependencies = [
"bon-macros",
"rustversion",
]
[[package]]
name = "bon-macros"
version = "2.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2a2af3eac944c12cdf4423eab70d310da0a8e5851a18ffb192c0a5e3f7ae1663"
dependencies = [
"darling",
"ident_case",
"proc-macro2",
"quote",
"syn 2.0.72",
]
[[package]]
name = "bstr"
version = "1.10.0"
@ -4573,26 +4596,6 @@ version = "0.2.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b"
[[package]]
name = "typed-builder"
version = "0.18.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "77739c880e00693faef3d65ea3aad725f196da38b22fdc7ea6ded6e1ce4d3add"
dependencies = [
"typed-builder-macro",
]
[[package]]
name = "typed-builder-macro"
version = "0.18.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1f718dfaf347dcb5b983bfc87608144b0bad87970aebcbea5ce44d2a30c08e63"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.72",
]
[[package]]
name = "typed-path"
version = "0.9.1"

View file

@ -10,6 +10,7 @@ categories = ["command-line-utilities"]
version = "0.8.17"
[workspace.dependencies]
bon = "2"
chrono = "0.4"
clap = "4"
colored = "2"
@ -23,7 +24,6 @@ serde = { version = "1", features = ["derive"] }
serde_json = "1"
serde_yaml = "0.9"
tempdir = "0.3"
typed-builder = "0.18"
users = "0.11"
uuid = { version = "1", features = ["v4"] }
@ -81,7 +81,7 @@ serde.workspace = true
serde_json.workspace = true
serde_yaml.workspace = true
tempdir.workspace = true
typed-builder.workspace = true
bon.workspace = true
users.workspace = true
[features]

View file

@ -39,7 +39,7 @@ oci-distribution.workspace = true
serde.workspace = true
serde_json.workspace = true
tempdir.workspace = true
typed-builder.workspace = true
bon.workspace = true
users.workspace = true
uuid.workspace = true

View file

@ -13,6 +13,7 @@ use std::{
};
use blue_build_utils::constants::IMAGE_VERSION_LABEL;
use bon::Builder;
use clap::Args;
use log::{debug, info, trace};
use miette::{miette, Result};
@ -21,7 +22,6 @@ use once_cell::sync::Lazy;
use opts::{GenerateImageNameOpts, GenerateTagsOpts};
#[cfg(feature = "sigstore")]
use sigstore_driver::SigstoreDriver;
use typed_builder::TypedBuilder;
use uuid::Uuid;
use self::{
@ -82,29 +82,25 @@ static OS_VERSION: Lazy<Mutex<HashMap<String, u64>>> = Lazy::new(|| Mutex::new(H
///
/// If the args are left uninitialized, the program will determine
/// the best one available.
#[derive(Default, Clone, Copy, Debug, TypedBuilder, Args)]
#[derive(Default, Clone, Copy, Debug, Builder, Args)]
pub struct DriverArgs {
/// Select which driver to use to build
/// your image.
#[builder(default)]
#[arg(short = 'B', long)]
build_driver: Option<BuildDriverType>,
/// Select which driver to use to inspect
/// images.
#[builder(default)]
#[arg(short = 'I', long)]
inspect_driver: Option<InspectDriverType>,
/// Select which driver to use to sign
/// images.
#[builder(default)]
#[arg(short = 'S', long)]
signing_driver: Option<SigningDriverType>,
/// Select which driver to use to run
/// containers.
#[builder(default)]
#[arg(short = 'R', long)]
run_driver: Option<RunDriverType>,
}

View file

@ -22,7 +22,10 @@ use serde::Deserialize;
use tempdir::TempDir;
use crate::{
drivers::image_metadata::ImageMetadata,
drivers::{
image_metadata::ImageMetadata,
opts::{RunOptsEnv, RunOptsVolume},
},
logging::{CommandLogging, Logger},
signal_handler::{add_cid, remove_cid, ContainerId, ContainerRuntime},
};
@ -321,7 +324,7 @@ impl InspectDriver for DockerDriver {
let output = Self::run_output(
&RunOpts::builder()
.image(SKOPEO_IMAGE)
.args(string_vec!["inspect", url.clone()])
.args(bon::vec!["inspect", &url])
.remove(true)
.build(),
)
@ -379,13 +382,13 @@ fn docker_run(opts: &RunOpts, cid_file: &Path) -> Command {
if opts.privileged => "--privileged",
if opts.remove => "--rm",
if opts.pull => "--pull=always",
for volume in opts.volumes => [
for RunOptsVolume { path_or_vol_name, container_path } in opts.volumes.iter() => [
"--volume",
format!("{}:{}", volume.path_or_vol_name, volume.container_path),
format!("{path_or_vol_name}:{container_path}"),
],
for env in opts.env_vars => [
for RunOptsEnv { key, value } in opts.env_vars.iter() => [
"--env",
format!("{}={}", env.key, env.value),
format!("{key}={value}"),
],
|command| {
match (opts.uid, opts.gid) {
@ -395,7 +398,7 @@ fn docker_run(opts: &RunOpts, cid_file: &Path) -> Command {
}
},
&*opts.image,
for opts.args,
for arg in opts.args.iter() => &**arg,
);
trace!("{command:?}");

View file

@ -137,7 +137,7 @@ mod test {
constants::{
GITHUB_EVENT_NAME, GITHUB_EVENT_PATH, GITHUB_REF_NAME, GITHUB_SHA, PR_EVENT_NUMBER,
},
cowstr_vec, string_vec,
string_vec,
test_utils::set_env_var,
};
use oci_distribution::Reference;
@ -230,7 +230,7 @@ mod test {
)]
#[case::default_branch_alt_tags(
setup_default_branch,
Some(cowstr_vec![TEST_TAG_1, TEST_TAG_2]),
Some(bon::vec![TEST_TAG_1, TEST_TAG_2]),
string_vec![
TEST_TAG_1,
format!("{TEST_TAG_1}-40"),
@ -249,7 +249,7 @@ mod test {
)]
#[case::pr_branch_alt_tags(
setup_pr_branch,
Some(cowstr_vec![TEST_TAG_1, TEST_TAG_2]),
Some(bon::vec![TEST_TAG_1, TEST_TAG_2]),
string_vec![
format!("pr-12-{TEST_TAG_1}-40"),
format!("{COMMIT_SHA}-{TEST_TAG_1}-40"),
@ -264,7 +264,7 @@ mod test {
)]
#[case::branch_alt_tags(
setup_branch,
Some(cowstr_vec![TEST_TAG_1, TEST_TAG_2]),
Some(bon::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"),
@ -284,7 +284,7 @@ mod test {
let mut tags = GithubDriver::generate_tags(
&GenerateTagsOpts::builder()
.oci_ref(&oci_ref)
.alt_tags(alt_tags)
.maybe_alt_tags(alt_tags)
.build(),
)
.unwrap();

View file

@ -149,7 +149,7 @@ mod test {
CI_PIPELINE_SOURCE, CI_PROJECT_NAME, CI_PROJECT_NAMESPACE, CI_REGISTRY, CI_SERVER_HOST,
CI_SERVER_PROTOCOL,
},
cowstr_vec, string_vec,
string_vec,
test_utils::set_env_var,
};
use oci_distribution::Reference;
@ -238,7 +238,7 @@ mod test {
)]
#[case::default_branch_alt_tags(
setup_default_branch,
Some(cowstr_vec![TEST_TAG_1, TEST_TAG_2]),
Some(bon::vec![TEST_TAG_1, TEST_TAG_2]),
string_vec![
TEST_TAG_1,
format!("{TEST_TAG_1}-40"),
@ -257,7 +257,7 @@ mod test {
)]
#[case::pr_branch_alt_tags(
setup_mr_branch,
Some(cowstr_vec![TEST_TAG_1, TEST_TAG_2]),
Some(bon::vec![TEST_TAG_1, TEST_TAG_2]),
string_vec![
format!("mr-12-{TEST_TAG_1}-40"),
format!("{COMMIT_SHA}-{TEST_TAG_1}-40"),
@ -272,7 +272,7 @@ mod test {
)]
#[case::branch_alt_tags(
setup_branch,
Some(cowstr_vec![TEST_TAG_1, TEST_TAG_2]),
Some(bon::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"),
@ -292,7 +292,7 @@ mod test {
let mut tags = GitlabDriver::generate_tags(
&GenerateTagsOpts::builder()
.oci_ref(&oci_ref)
.alt_tags(alt_tags)
.maybe_alt_tags(alt_tags)
.build(),
)
.unwrap();

View file

@ -1,65 +1,63 @@
use std::{borrow::Cow, path::Path};
use typed_builder::TypedBuilder;
use bon::Builder;
use super::CompressionType;
/// Options for building
#[derive(Debug, Clone, TypedBuilder)]
pub struct BuildOpts<'a> {
#[builder(setter(into))]
pub image: Cow<'a, str>,
#[derive(Debug, Clone, Builder)]
pub struct BuildOpts<'scope> {
#[builder(into)]
pub image: Cow<'scope, str>,
#[builder(default)]
pub squash: bool,
#[builder(setter(into))]
pub containerfile: Cow<'a, Path>,
#[builder(into)]
pub containerfile: Cow<'scope, Path>,
}
#[derive(Debug, Clone, TypedBuilder)]
pub struct TagOpts<'a> {
#[builder(setter(into))]
pub src_image: Cow<'a, str>,
#[derive(Debug, Clone, Builder)]
pub struct TagOpts<'scope> {
#[builder(into)]
pub src_image: Cow<'scope, str>,
#[builder(setter(into))]
pub dest_image: Cow<'a, str>,
#[builder(into)]
pub dest_image: Cow<'scope, str>,
}
#[derive(Debug, Clone, TypedBuilder)]
pub struct PushOpts<'a> {
#[builder(setter(into))]
pub image: Cow<'a, str>,
#[builder(default, setter(strip_option))]
#[derive(Debug, Clone, Builder)]
pub struct PushOpts<'scope> {
#[builder(into)]
pub image: Cow<'scope, str>,
pub compression_type: Option<CompressionType>,
}
/// Options for building, tagging, and pusing images.
#[allow(clippy::struct_excessive_bools)]
#[derive(Debug, Clone, TypedBuilder)]
pub struct BuildTagPushOpts<'a> {
#[derive(Debug, Clone, Builder)]
pub struct BuildTagPushOpts<'scope> {
/// 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>>,
#[builder(into)]
pub image: Option<Cow<'scope, str>>,
/// The path to the archive file.
///
/// NOTE: You cannot have this set with image set.
#[builder(default, setter(into, strip_option))]
pub archive_path: Option<Cow<'a, str>>,
#[builder(into)]
pub archive_path: Option<Cow<'scope, str>>,
/// The path to the Containerfile to build.
#[builder(setter(into))]
pub containerfile: Cow<'a, Path>,
#[builder(into)]
pub containerfile: Cow<'scope, Path>,
/// The list of tags for the image being built.
#[builder(default, setter(into))]
pub tags: Cow<'a, [String]>,
#[builder(default, into)]
pub tags: Vec<Cow<'scope, str>>,
/// Enable pushing the image.
#[builder(default)]

View file

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

View file

@ -1,12 +1,12 @@
use std::borrow::Cow;
use typed_builder::TypedBuilder;
use bon::Builder;
#[derive(Debug, Clone, TypedBuilder)]
pub struct GetMetadataOpts<'a> {
#[builder(setter(into))]
pub image: Cow<'a, str>,
#[derive(Debug, Clone, Builder)]
pub struct GetMetadataOpts<'scope> {
#[builder(into)]
pub image: Cow<'scope, str>,
#[builder(default, setter(into, strip_option))]
pub tag: Option<Cow<'a, str>>,
#[builder(into)]
pub tag: Option<Cow<'scope, str>>,
}

View file

@ -1,30 +1,23 @@
use std::borrow::Cow;
use typed_builder::TypedBuilder;
use bon::Builder;
#[derive(Debug, Clone, TypedBuilder)]
#[derive(Debug, Clone, Builder)]
pub struct RunOpts<'scope> {
#[builder(setter(into))]
#[builder(into)]
pub image: Cow<'scope, str>,
#[builder(default, setter(into))]
pub args: Cow<'scope, [String]>,
#[builder(default, into)]
pub args: Vec<Cow<'scope, str>>,
#[builder(default, setter(into))]
#[builder(default, into)]
pub env_vars: Vec<RunOptsEnv<'scope>>,
#[builder(default, setter(into))]
#[builder(default, into)]
pub volumes: Vec<RunOptsVolume<'scope>>,
#[builder(default, setter(strip_option))]
pub uid: Option<u32>,
#[builder(default, setter(strip_option))]
pub gid: Option<u32>,
#[builder(default, setter(into))]
pub workdir: Cow<'scope, str>,
#[builder(default)]
pub privileged: bool,
@ -35,12 +28,12 @@ pub struct RunOpts<'scope> {
pub remove: bool,
}
#[derive(Debug, Clone, TypedBuilder)]
#[derive(Debug, Clone, Builder)]
pub struct RunOptsVolume<'scope> {
#[builder(setter(into))]
#[builder(into)]
pub path_or_vol_name: Cow<'scope, str>,
#[builder(setter(into))]
#[builder(into)]
pub container_path: Cow<'scope, str>,
}
@ -48,7 +41,7 @@ pub struct RunOptsVolume<'scope> {
macro_rules! run_volumes {
($($host:expr => $container:expr),+ $(,)?) => {
{
vec![
::bon::vec![
$($crate::drivers::opts::RunOptsVolume::builder()
.path_or_vol_name($host)
.container_path($container)
@ -58,12 +51,12 @@ macro_rules! run_volumes {
};
}
#[derive(Debug, Clone, TypedBuilder)]
#[derive(Debug, Clone, Builder)]
pub struct RunOptsEnv<'scope> {
#[builder(setter(into))]
#[builder(into)]
pub key: Cow<'scope, str>,
#[builder(setter(into))]
#[builder(into)]
pub value: Cow<'scope, str>,
}
@ -71,7 +64,7 @@ pub struct RunOptsEnv<'scope> {
macro_rules! run_envs {
($($key:expr => $value:expr),+ $(,)?) => {
{
vec![
::bon::vec![
$($crate::drivers::opts::RunOptsEnv::builder()
.key($key)
.value($value)

View file

@ -4,8 +4,8 @@ use std::{
path::{Path, PathBuf},
};
use bon::Builder;
use miette::{IntoDiagnostic, Result};
use typed_builder::TypedBuilder;
use zeroize::{Zeroize, Zeroizing};
pub enum PrivateKey {
@ -13,6 +13,18 @@ pub enum PrivateKey {
Path(PathBuf),
}
impl std::fmt::Display for PrivateKey {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str(
match *self {
Self::Env(ref env) => format!("env://{env}"),
Self::Path(ref path) => format!("{}", path.display()),
}
.as_str(),
)
}
}
pub trait PrivateKeyContents<T>
where
T: Zeroize,
@ -40,39 +52,27 @@ impl PrivateKeyContents<String> for PrivateKey {
}
}
impl std::fmt::Display for PrivateKey {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str(
match *self {
Self::Env(ref env) => format!("env://{env}"),
Self::Path(ref path) => format!("{}", path.display()),
}
.as_str(),
)
}
}
#[derive(Debug, Clone, TypedBuilder)]
#[derive(Debug, Clone, Builder)]
pub struct GenerateKeyPairOpts<'scope> {
#[builder(setter(into, strip_option))]
#[builder(into)]
pub dir: Option<Cow<'scope, Path>>,
}
#[derive(Debug, Clone, TypedBuilder)]
#[derive(Debug, Clone, Builder)]
pub struct CheckKeyPairOpts<'scope> {
#[builder(setter(into, strip_option))]
#[builder(into)]
pub dir: Option<Cow<'scope, Path>>,
}
#[derive(Debug, Clone, TypedBuilder)]
#[derive(Debug, Clone, Builder)]
pub struct SignOpts<'scope> {
#[builder(setter(into))]
#[builder(into)]
pub image: Cow<'scope, str>,
#[builder(default, setter(into, strip_option))]
#[builder(into)]
pub key: Option<Cow<'scope, str>>,
#[builder(default, setter(into, strip_option))]
#[builder(into)]
pub dir: Option<Cow<'scope, Path>>,
}
@ -85,22 +85,22 @@ pub enum VerifyType<'scope> {
},
}
#[derive(Debug, Clone, TypedBuilder)]
#[derive(Debug, Clone, Builder)]
pub struct VerifyOpts<'scope> {
#[builder(setter(into))]
#[builder(into)]
pub image: Cow<'scope, str>,
pub verify_type: VerifyType<'scope>,
}
#[derive(Debug, Clone, TypedBuilder)]
#[derive(Debug, Clone, Builder)]
pub struct SignVerifyOpts<'scope> {
#[builder(setter(into))]
#[builder(into)]
pub image: Cow<'scope, str>,
#[builder(default, setter(into, strip_option))]
#[builder(into)]
pub tag: Option<Cow<'scope, str>>,
#[builder(default, setter(into, strip_option))]
#[builder(into)]
pub dir: Option<Cow<'scope, Path>>,
/// Enable retry logic for pushing.

View file

@ -5,7 +5,7 @@ use std::{
time::Duration,
};
use blue_build_utils::{cmd, constants::SKOPEO_IMAGE, credentials::Credentials, string_vec};
use blue_build_utils::{cmd, constants::SKOPEO_IMAGE, credentials::Credentials};
use colored::Colorize;
use indicatif::{ProgressBar, ProgressStyle};
use log::{debug, error, info, trace, warn};
@ -15,7 +15,10 @@ use serde::Deserialize;
use tempdir::TempDir;
use crate::{
drivers::image_metadata::ImageMetadata,
drivers::{
image_metadata::ImageMetadata,
opts::{RunOptsEnv, RunOptsVolume},
},
logging::{CommandLogging, Logger},
signal_handler::{add_cid, remove_cid, ContainerId, ContainerRuntime},
};
@ -198,7 +201,7 @@ impl InspectDriver for PodmanDriver {
let output = Self::run_output(
&RunOpts::builder()
.image(SKOPEO_IMAGE)
.args(string_vec!["inspect", url.clone()])
.args(bon::vec!["inspect", &url])
.remove(true)
.build(),
)
@ -277,16 +280,16 @@ fn podman_run(opts: &RunOpts, cid_file: &Path) -> Command {
],
if opts.remove => "--rm",
if opts.pull => "--pull=always",
for volume in opts.volumes => [
for RunOptsVolume { path_or_vol_name, container_path } in opts.volumes.iter() => [
"--volume",
format!("{}:{}", volume.path_or_vol_name, volume.container_path),
format!("{path_or_vol_name}:{container_path}"),
],
for env in opts.env_vars => [
for RunOptsEnv { key, value } in opts.env_vars.iter() => [
"--env",
format!("{}={}", env.key, env.value),
format!("{key}={value}"),
],
&*opts.image,
for opts.args,
for arg in opts.args.iter() => &**arg,
);
trace!("{command:?}");

View file

@ -105,7 +105,7 @@ pub trait BuildDriver {
let mut image_list = Vec::with_capacity(opts.tags.len());
for tag in opts.tags.as_ref() {
for tag in &opts.tags {
debug!("Tagging {} with {tag}", &full_image);
let tagged_image = format!("{image}:{tag}");

View file

@ -9,6 +9,7 @@ use std::{
time::Duration,
};
use bon::Builder;
use chrono::Local;
use colored::{control::ShouldColorize, ColoredString, Colorize};
use indicatif::{MultiProgress, ProgressBar};
@ -31,7 +32,6 @@ use log4rs::{
use nu_ansi_term::Color;
use once_cell::sync::Lazy;
use rand::Rng;
use typed_builder::TypedBuilder;
use crate::signal_handler::{add_pid, remove_pid};
@ -258,9 +258,9 @@ impl CommandLogging for Command {
}
}
#[derive(Debug, TypedBuilder)]
#[derive(Debug, Builder)]
struct CustomPatternEncoder {
#[builder(default, setter(into))]
#[builder(default, into)]
filter_modules: Vec<(String, LevelFilter)>,
}

View file

@ -19,7 +19,7 @@ indexmap.workspace = true
serde.workspace = true
serde_yaml.workspace = true
serde_json.workspace = true
typed-builder.workspace = true
bon.workspace = true
[features]
default = []

View file

@ -1,7 +1,10 @@
use typed_builder::TypedBuilder;
use bon::Builder;
#[derive(Debug, Clone, TypedBuilder, PartialEq, Eq, Hash)]
#[derive(Debug, Clone, Builder, PartialEq, Eq, Hash)]
pub struct AkmodsInfo {
#[builder(into)]
pub images: (String, String, Option<String>),
#[builder(into)]
pub stage_name: String,
}

View file

@ -1,23 +1,23 @@
use std::{borrow::Cow, path::PathBuf};
use blue_build_utils::syntax_highlighting::highlight_ser;
use bon::Builder;
use colored::Colorize;
use indexmap::IndexMap;
use log::{trace, warn};
use miette::{bail, Result};
use serde::{Deserialize, Serialize};
use serde_yaml::Value;
use typed_builder::TypedBuilder;
use crate::{AkmodsInfo, ModuleExt};
#[derive(Serialize, Deserialize, Debug, Clone, TypedBuilder, Default)]
#[derive(Serialize, Deserialize, Debug, Clone, Builder, Default)]
pub struct ModuleRequiredFields<'a> {
#[builder(default, setter(into))]
#[builder(into)]
#[serde(rename = "type")]
pub module_type: Cow<'a, str>,
#[builder(default, setter(into, strip_option))]
#[builder(into)]
#[serde(skip_serializing_if = "Option::is_none")]
pub source: Option<Cow<'a, str>>,
@ -26,7 +26,7 @@ pub struct ModuleRequiredFields<'a> {
pub no_cache: bool,
#[serde(flatten)]
#[builder(default, setter(into))]
#[builder(default, into)]
pub config: IndexMap<String, Value>,
}
@ -154,13 +154,12 @@ impl<'a> ModuleRequiredFields<'a> {
}
}
#[derive(Serialize, Deserialize, Debug, Clone, TypedBuilder, Default)]
#[derive(Serialize, Deserialize, Debug, Clone, Builder, Default)]
pub struct Module<'a> {
#[builder(default, setter(strip_option))]
#[serde(flatten, skip_serializing_if = "Option::is_none")]
pub required_fields: Option<ModuleRequiredFields<'a>>,
#[builder(default, setter(into, strip_option))]
#[builder(into)]
#[serde(rename = "from-file", skip_serializing_if = "Option::is_none")]
pub from_file: Option<Cow<'a, str>>,
}

View file

@ -1,17 +1,17 @@
use std::{borrow::Cow, collections::HashSet, fs, path::Path};
use std::{collections::HashSet, fs, path::Path};
use blue_build_utils::constants::{CONFIG_PATH, RECIPE_PATH};
use bon::Builder;
use log::{trace, warn};
use miette::{Context, IntoDiagnostic, Result};
use serde::{Deserialize, Serialize};
use typed_builder::TypedBuilder;
use crate::{AkmodsInfo, Module};
#[derive(Default, Serialize, Clone, Deserialize, Debug, TypedBuilder)]
#[derive(Default, Serialize, Clone, Deserialize, Debug, Builder)]
pub struct ModuleExt<'a> {
#[builder(default, setter(into))]
pub modules: Cow<'a, [Module<'a>]>,
#[builder(default)]
pub modules: Vec<Module<'a>>,
}
impl ModuleExt<'_> {

View file

@ -1,13 +1,12 @@
use std::{borrow::Cow, fs, path::Path};
use blue_build_utils::cowstr;
use bon::Builder;
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;
use crate::{Module, ModuleExt, StagesExt};
@ -17,33 +16,33 @@ use crate::{Module, ModuleExt, StagesExt};
/// This will contain information on the image and its
/// base image to assist with building the Containerfile
/// and tagging the image appropriately.
#[derive(Default, Serialize, Clone, Deserialize, Debug, TypedBuilder)]
#[derive(Default, Serialize, Clone, Deserialize, Debug, Builder)]
pub struct Recipe<'a> {
/// The name of the user's image.
///
/// This will be set on the `org.opencontainers.image.title` label.
#[builder(setter(into))]
#[builder(into)]
pub name: Cow<'a, str>,
/// The description of the user's image.
///
/// This will be set on the `org.opencontainers.image.description` label.
#[builder(setter(into))]
#[builder(into)]
pub description: Cow<'a, str>,
/// The base image from which to build the user's image.
#[serde(alias = "base-image")]
#[builder(setter(into))]
#[builder(into)]
pub base_image: Cow<'a, str>,
/// The version/tag of the base image.
#[serde(alias = "image-version")]
#[builder(setter(into))]
#[builder(into)]
pub image_version: Cow<'a, str>,
/// The version of `bluebuild` to install in the image
#[serde(alias = "blue-build-tag", skip_serializing_if = "Option::is_none")]
#[builder(default, setter(into, strip_option))]
#[builder(into)]
pub blue_build_tag: Option<Cow<'a, str>>,
/// Alternate tags to the `latest` tag to add to the image.
@ -54,8 +53,8 @@ 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))]
alt_tags: Option<Vec<Cow<'a, str>>>,
#[builder(into)]
pub alt_tags: Option<Vec<String>>,
/// The stages extension of the recipe.
///
@ -75,7 +74,7 @@ pub struct Recipe<'a> {
/// done in case we serialize the data to a yaml file
/// so that we retain any unused information.
#[serde(flatten)]
#[builder(setter(into))]
#[builder(into)]
pub extra: IndexMap<String, Value>,
}
@ -106,11 +105,11 @@ impl<'a> Recipe<'a> {
.map_err(blue_build_utils::serde_yaml_err(&file))
.into_diagnostic()?;
recipe.modules_ext.modules = Module::get_modules(&recipe.modules_ext.modules, None)?.into();
recipe.modules_ext.modules = Module::get_modules(&recipe.modules_ext.modules, None)?;
#[cfg(feature = "stages")]
if let Some(ref mut stages_ext) = recipe.stages_ext {
stages_ext.stages = crate::Stage::get_stages(&stages_ext.stages, None)?.into();
stages_ext.stages = crate::Stage::get_stages(&stages_ext.stages, None)?;
}
#[cfg(not(feature = "stages"))]
@ -132,11 +131,4 @@ impl<'a> Recipe<'a> {
.into_diagnostic()
.with_context(|| format!("Unable to parse base image {base_image}"))
}
#[must_use]
pub fn alt_tags(&'a self) -> Option<Vec<Cow<'a, str>>> {
self.alt_tags
.as_ref()
.map(|tags| tags.iter().map(|tag| cowstr!(&**tag)).collect())
}
}

View file

@ -1,33 +1,33 @@
use std::{borrow::Cow, path::PathBuf};
use blue_build_utils::syntax_highlighting::highlight_ser;
use bon::Builder;
use colored::Colorize;
use miette::{bail, Result};
use serde::{Deserialize, Serialize};
use typed_builder::TypedBuilder;
use crate::{Module, ModuleExt, StagesExt};
/// Contains the required fields for a stage.
#[derive(Serialize, Deserialize, Debug, Clone, TypedBuilder)]
#[derive(Serialize, Deserialize, Debug, Clone, Builder)]
pub struct StageRequiredFields<'a> {
/// The name of the stage.
///
/// This can then be referenced in the `copy`
/// module using the `from:` property.
#[builder(setter(into))]
#[builder(into)]
pub name: Cow<'a, str>,
/// The base image of the stage.
///
/// This is set directly in a `FROM` instruction.
#[builder(setter(into))]
#[builder(into)]
pub from: Cow<'a, str>,
/// The shell to use in the stage.
#[builder(default, setter(into, strip_option))]
#[builder(into)]
#[serde(skip_serializing_if = "Option::is_none")]
pub shell: Option<Cow<'a, [Cow<'a, str>]>>,
pub shell: Option<Vec<String>>,
/// The modules extension for the stage
#[serde(flatten)]
@ -38,10 +38,9 @@ pub struct StageRequiredFields<'a> {
///
/// A stage has its own list of modules to run which
/// allows the user to reuse the modules thats provided to the main build.
#[derive(Serialize, Deserialize, Debug, Clone, TypedBuilder)]
#[derive(Serialize, Deserialize, Debug, Clone, Builder)]
pub struct Stage<'a> {
/// The requied fields for a stage.
#[builder(default, setter(strip_option))]
#[serde(flatten, skip_serializing_if = "Option::is_none")]
pub required_fields: Option<StageRequiredFields<'a>>,
@ -82,7 +81,7 @@ pub struct Stage<'a> {
/// snippets:
/// - echo "Hello World!"
/// ```
#[builder(default, setter(into, strip_option))]
#[builder(into)]
#[serde(rename = "from-file", skip_serializing_if = "Option::is_none")]
pub from_file: Option<Cow<'a, str>>,
}

View file

@ -1,17 +1,17 @@
use std::{borrow::Cow, fs, path::Path};
use std::{fs, path::Path};
use blue_build_utils::constants::{CONFIG_PATH, RECIPE_PATH};
use bon::Builder;
use log::warn;
use miette::{Context, IntoDiagnostic, Result};
use serde::{Deserialize, Serialize};
use typed_builder::TypedBuilder;
use crate::{Module, Stage};
#[derive(Default, Serialize, Clone, Deserialize, Debug, TypedBuilder)]
#[derive(Default, Serialize, Clone, Deserialize, Debug, Builder)]
pub struct StagesExt<'a> {
#[builder(default, setter(into))]
pub stages: Cow<'a, [Stage<'a>]>,
#[builder(default)]
pub stages: Vec<Stage<'a>>,
}
impl<'a> StagesExt<'a> {
@ -41,8 +41,7 @@ impl<'a> StagesExt<'a> {
.map_err(blue_build_utils::serde_yaml_err(&file))
.into_diagnostic()?;
if let Some(ref mut rf) = stage.required_fields {
rf.modules_ext.modules =
Module::get_modules(&rf.modules_ext.modules, None)?.into();
rf.modules_ext.modules = Module::get_modules(&rf.modules_ext.modules, None)?;
}
Ok(Self::builder().stages(vec![stage]).build())
},
@ -52,10 +51,10 @@ impl<'a> StagesExt<'a> {
for stage in &mut stages {
if let Some(ref mut rf) = stage.required_fields {
rf.modules_ext.modules =
Module::get_modules(&rf.modules_ext.modules, None)?.into();
Module::get_modules(&rf.modules_ext.modules, None)?;
}
}
stages_ext.stages = stages.into();
stages_ext.stages = stages;
Ok(stages_ext)
},
)

View file

@ -4,6 +4,7 @@ use blue_build_utils::constants::{
BUG_REPORT_WARNING_MESSAGE, GITHUB_CHAR_LIMIT, LC_TERMINAL, LC_TERMINAL_VERSION, TERM_PROGRAM,
TERM_PROGRAM_VERSION, UNKNOWN_SHELL, UNKNOWN_TERMINAL, UNKNOWN_VERSION,
};
use bon::Builder;
use clap::Args;
use clap_complete::Shell;
use colored::Colorize;
@ -12,23 +13,21 @@ use log::{debug, error, trace};
use miette::{IntoDiagnostic, Result};
use requestty::question::{completions, Completions};
use std::time::Duration;
use typed_builder::TypedBuilder;
use super::BlueBuildCommand;
use crate::shadow;
#[derive(Default, Debug, Clone, TypedBuilder, Args)]
#[derive(Default, Debug, Clone, Builder, Args)]
pub struct BugReportRecipe {
recipe_dir: Option<String>,
recipe_path: Option<String>,
}
#[derive(Debug, Clone, Args, TypedBuilder)]
#[derive(Debug, Clone, Args, Builder)]
pub struct BugReportCommand {
/// Path to the recipe file
#[arg(short, long)]
#[builder(default)]
recipe_path: Option<String>,
}

View file

@ -22,30 +22,31 @@ use blue_build_utils::{
cowstr,
credentials::{Credentials, CredentialsArgs},
string,
traits::CowCollecter,
};
use bon::Builder;
use clap::Args;
use colored::Colorize;
use log::{info, trace, warn};
use miette::{bail, Context, IntoDiagnostic, Result};
use typed_builder::TypedBuilder;
use crate::commands::generate::GenerateCommand;
use super::BlueBuildCommand;
#[allow(clippy::struct_excessive_bools)]
#[derive(Debug, Clone, Args, TypedBuilder)]
#[derive(Debug, Clone, Args, Builder)]
pub struct BuildCommand {
/// The recipe file to build an image
#[arg()]
#[cfg(feature = "multi-recipe")]
#[builder(default, setter(into, strip_option))]
#[builder(into)]
recipe: Option<Vec<PathBuf>>,
/// The recipe file to build an image
#[arg()]
#[cfg(not(feature = "multi-recipe"))]
#[builder(default, setter(into, strip_option))]
#[builder(into)]
recipe: Option<PathBuf>,
/// Push the image with all the tags.
@ -85,14 +86,13 @@ pub struct BuildCommand {
/// Archives the built image into a tarfile
/// in the specified directory.
#[arg(short, long)]
#[builder(default, setter(into, strip_option))]
#[builder(into)]
archive: Option<PathBuf>,
/// The url path to your base
/// project images.
#[arg(long, env = BB_REGISTRY_NAMESPACE)]
#[builder(default, setter(into, strip_option))]
#[arg(visible_alias("registry-path"))]
#[arg(long, env = BB_REGISTRY_NAMESPACE, visible_alias("registry-path"))]
#[builder(into)]
registry_namespace: Option<String>,
/// Do not sign the image on push.
@ -260,7 +260,7 @@ impl BuildCommand {
let tags = Driver::generate_tags(
&GenerateTagsOpts::builder()
.oci_ref(&recipe.base_image_ref()?)
.alt_tags(recipe.alt_tags())
.maybe_alt_tags(recipe.alt_tags.as_ref().map(CowCollecter::to_cow_vec))
.build(),
)?;
let image_name = self.image_name(&recipe)?;
@ -279,7 +279,7 @@ impl BuildCommand {
BuildTagPushOpts::builder()
.image(&image_name)
.containerfile(containerfile)
.tags(&tags)
.tags(tags.to_cow_vec())
.push(self.push)
.retry_push(self.retry_push)
.retry_count(self.retry_count)
@ -291,16 +291,14 @@ impl BuildCommand {
let images = 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)?;
Driver::sign_and_verify(
&SignVerifyOpts::builder()
.image(&image_name)
.retry_push(self.retry_push)
.retry_count(self.retry_count)
.maybe_tag(tags.first())
.build(),
)?;
}
Ok(images)
@ -310,8 +308,8 @@ impl BuildCommand {
let image_name = Driver::generate_image_name(
GenerateImageNameOpts::builder()
.name(recipe.name.trim())
.registry(self.credentials.registry.as_ref().map(|r| cowstr!(r)))
.registry_namespace(self.registry_namespace.as_ref().map(|r| cowstr!(r)))
.maybe_registry(self.credentials.registry.as_ref().map(|r| cowstr!(r)))
.maybe_registry_namespace(self.registry_namespace.as_ref().map(|r| cowstr!(r)))
.build(),
)?;

View file

@ -10,25 +10,25 @@ use blue_build_utils::{
constants::{CONFIG_PATH, RECIPE_FILE, RECIPE_PATH},
syntax_highlighting::{self, DefaultThemes},
};
use bon::Builder;
use clap::{crate_version, Args};
use log::{debug, info, trace, warn};
use miette::{IntoDiagnostic, Result};
use typed_builder::TypedBuilder;
use crate::shadow;
use super::BlueBuildCommand;
#[derive(Debug, Clone, Args, TypedBuilder)]
#[derive(Debug, Clone, Args, Builder)]
pub struct GenerateCommand {
/// The recipe file to create a template from
#[arg()]
#[builder(default, setter(into, strip_option))]
#[builder(into)]
recipe: Option<PathBuf>,
/// File to output to instead of STDOUT
#[arg(short, long)]
#[builder(default, setter(into, strip_option))]
#[builder(into)]
output: Option<PathBuf>,
/// The registry domain the image will be published to.
@ -36,7 +36,7 @@ pub struct GenerateCommand {
/// This is used for modules that need to know where
/// the image is being published (i.e. the signing module).
#[arg(long)]
#[builder(default, setter(into, strip_option))]
#[builder(into)]
registry: Option<String>,
/// The registry namespace the image will be published to.
@ -44,7 +44,7 @@ pub struct GenerateCommand {
/// This is used for modules that need to know where
/// the image is being published (i.e. the signing module).
#[arg(long)]
#[builder(default, setter(into, strip_option))]
#[builder(into)]
registry_namespace: Option<String>,
/// Instead of creating a Containerfile, display
@ -61,7 +61,6 @@ pub struct GenerateCommand {
///
/// The default is `mocha-dark`.
#[arg(short = 't', long)]
#[builder(default, setter(strip_option))]
syntax_theme: Option<DefaultThemes>,
#[clap(flatten)]

View file

@ -4,12 +4,12 @@ use std::{
};
use blue_build_recipe::Recipe;
use blue_build_utils::{constants::ARCHIVE_SUFFIX, string_vec};
use blue_build_utils::{constants::ARCHIVE_SUFFIX, string_vec, traits::CowCollecter};
use bon::Builder;
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},
@ -18,13 +18,14 @@ use blue_build_process_management::{
use super::{build::BuildCommand, BlueBuildCommand};
#[derive(Clone, Debug, TypedBuilder, Args)]
#[derive(Clone, Debug, Builder, Args)]
pub struct GenerateIsoCommand {
#[command(subcommand)]
command: GenIsoSubcommand,
/// The directory to save the resulting ISO file.
#[arg(short, long)]
#[builder(into)]
output_dir: Option<PathBuf>,
/// The variant of the installer to use.
@ -52,6 +53,7 @@ pub struct GenerateIsoCommand {
long,
default_value = "https://github.com/ublue-os/bazzite/raw/main/secure_boot.der"
)]
#[builder(into)]
secure_boot_url: String,
/// The enrollment password for the secure boot
@ -61,10 +63,12 @@ pub struct GenerateIsoCommand {
/// It's recommended to change this if your base
/// image is not from UBlue.
#[arg(long, default_value = "universalblue")]
#[builder(into)]
enrollment_password: String,
/// The name of your ISO image file.
#[arg(long)]
#[builder(into)]
iso_name: Option<String>,
#[clap(flatten)]
@ -226,7 +230,7 @@ impl GenerateIsoCommand {
.image("ghcr.io/jasonn3/build-container-installer")
.privileged(true)
.remove(true)
.args(&args)
.args(args.to_cow_vec())
.volumes(vols)
.build();

View file

@ -9,17 +9,17 @@ use blue_build_utils::{
cmd,
constants::{ARCHIVE_SUFFIX, LOCAL_BUILD},
};
use bon::Builder;
use clap::Args;
use log::{debug, info, trace};
use miette::{bail, IntoDiagnostic, Result};
use typed_builder::TypedBuilder;
use users::{Users, UsersCache};
use crate::commands::build::BuildCommand;
use super::BlueBuildCommand;
#[derive(Default, Clone, Debug, TypedBuilder, Args)]
#[derive(Default, Clone, Debug, Builder, Args)]
pub struct LocalCommonArgs {
/// The recipe file to build an image.
#[arg()]
@ -45,7 +45,7 @@ pub struct LocalCommonArgs {
drivers: DriverArgs,
}
#[derive(Default, Clone, Debug, TypedBuilder, Args)]
#[derive(Default, Clone, Debug, Builder, Args)]
pub struct UpgradeCommand {
#[clap(flatten)]
common: LocalCommonArgs,
@ -103,7 +103,7 @@ impl BlueBuildCommand for UpgradeCommand {
}
}
#[derive(Default, Clone, Debug, TypedBuilder, Args)]
#[derive(Default, Clone, Debug, Builder, Args)]
pub struct RebaseCommand {
#[clap(flatten)]
common: LocalCommonArgs,

View file

@ -5,11 +5,10 @@ use blue_build_utils::credentials::{Credentials, CredentialsArgs};
use clap::Args;
use miette::{bail, IntoDiagnostic, Result};
use requestty::questions;
use typed_builder::TypedBuilder;
use super::BlueBuildCommand;
#[derive(Debug, Clone, Args, TypedBuilder)]
#[derive(Debug, Clone, Args)]
pub struct LoginCommand {
/// The server to login to.
server: String,
@ -31,7 +30,6 @@ pub struct LoginCommand {
username: Option<String>,
#[clap(flatten)]
#[builder(default)]
drivers: DriverArgs,
}

View file

@ -12,19 +12,19 @@ use blue_build_utils::{
cmd,
constants::{ARCHIVE_SUFFIX, LOCAL_BUILD, OCI_ARCHIVE, OSTREE_UNVERIFIED_IMAGE},
};
use bon::Builder;
use clap::Args;
use colored::Colorize;
use indicatif::ProgressBar;
use log::{debug, trace, warn};
use miette::{bail, IntoDiagnostic, Result};
use tempdir::TempDir;
use typed_builder::TypedBuilder;
use crate::{commands::build::BuildCommand, rpm_ostree_status::RpmOstreeStatus};
use super::BlueBuildCommand;
#[derive(Default, Clone, Debug, TypedBuilder, Args)]
#[derive(Default, Clone, Debug, Builder, Args)]
pub struct SwitchCommand {
/// The recipe file to build an image.
#[arg()]

View file

@ -15,7 +15,7 @@ blue-build-utils = { version = "=0.8.17", path = "../utils" }
log.workspace = true
colored.workspace = true
typed-builder.workspace = true
bon.workspace = true
uuid.workspace = true
[lints]

View file

@ -4,92 +4,57 @@ use blue_build_recipe::Recipe;
use blue_build_utils::constants::{
CONFIG_PATH, CONTAINERFILES_PATH, CONTAINER_FILE, COSIGN_PUB_PATH, FILES_PATH,
};
use bon::Builder;
use colored::control::ShouldColorize;
use log::{debug, error, trace, warn};
use typed_builder::TypedBuilder;
use uuid::Uuid;
pub use rinja::Template;
#[derive(Debug, Clone, Template, TypedBuilder)]
#[derive(Debug, Clone, Template, Builder)]
#[template(path = "Containerfile.j2", escape = "none", whitespace = "minimize")]
#[builder(on(Cow<'_, str>, into))]
pub struct ContainerFileTemplate<'a> {
#[builder(into)]
recipe: &'a Recipe<'a>,
#[builder(setter(into))]
recipe_path: &'a Path,
#[builder(into)]
recipe_path: Cow<'a, Path>,
#[builder(setter(into))]
#[builder(into)]
build_id: Uuid,
os_version: u64,
#[builder(setter(into))]
registry: Cow<'a, str>,
#[builder(setter(into))]
exports_tag: Cow<'a, str>,
#[builder(setter(into))]
repo: Cow<'a, str>,
}
#[derive(Debug, Clone, Template, TypedBuilder)]
#[derive(Debug, Clone, Template, Builder)]
#[template(path = "github_issue.j2", escape = "md")]
#[builder(on(Cow<'_, str>, into))]
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>,
}
#[derive(Debug, Clone, Template, TypedBuilder)]
#[derive(Debug, Clone, Template, Builder)]
#[template(path = "init/README.j2", escape = "md")]
#[builder(on(Cow<'_, str>, into))]
pub struct InitReadmeTemplate<'a> {
#[builder(setter(into))]
repo_name: Cow<'a, str>,
#[builder(setter(into))]
registry: Cow<'a, str>,
#[builder(setter(into))]
image_name: Cow<'a, str>,
}

View file

@ -26,7 +26,7 @@ miette.workspace = true
serde.workspace = true
serde_json.workspace = true
serde_yaml.workspace = true
typed-builder.workspace = true
bon.workspace = true
[build-dependencies]
syntect = "5"

View file

@ -3,10 +3,10 @@ use std::{
sync::{LazyLock, Mutex},
};
use bon::Builder;
use clap::Args;
use docker_credential::DockerCredential;
use log::trace;
use typed_builder::TypedBuilder;
use crate::{
constants::{
@ -94,7 +94,7 @@ static ENV_CREDENTIALS: LazyLock<Option<Credentials>> = LazyLock::new(|| {
});
/// The credentials for logging into image registries.
#[derive(Debug, Default, Clone, TypedBuilder)]
#[derive(Debug, Default, Clone, Builder)]
pub struct Credentials {
pub registry: String,
pub username: String,
@ -132,22 +132,20 @@ impl Credentials {
}
}
#[derive(Debug, Default, Clone, TypedBuilder, Args)]
#[derive(Debug, Default, Clone, Builder, Args)]
#[builder(on(String, into))]
pub struct CredentialsArgs {
/// The registry's domain name.
#[arg(long, env = BB_REGISTRY)]
#[builder(default, setter(into, strip_option))]
pub registry: Option<String>,
/// The username to login to the
/// container registry.
#[arg(short = 'U', long, env = BB_USERNAME, hide_env_values = true)]
#[builder(default, setter(into, strip_option))]
pub username: Option<String>,
/// The password to login to the
/// container registry.
#[arg(short = 'P', long, env = BB_PASSWORD, hide_env_values = true)]
#[builder(default, setter(into, strip_option))]
pub password: Option<String>,
}

View file

@ -5,6 +5,7 @@ mod macros;
pub mod syntax_highlighting;
#[cfg(feature = "test")]
pub mod test_utils;
pub mod traits;
use std::{
os::unix::ffi::OsStrExt,

View file

@ -27,23 +27,23 @@ macro_rules! cmd {
(@ $command:ident $(,)?) => { };
(@ $command:ident, for $for_expr:expr $(, $($tail:tt)*)?) => {
{
for arg in $for_expr.iter() {
for arg in $for_expr {
$crate::cmd!($command, arg);
}
$($crate::cmd!(@ $command, $($tail)*);)*
}
};
(@ $command:ident, for $iter:ident in $for_expr:expr => [ $($arg:expr),* $(,)? ] $(, $($tail:tt)*)?) => {
(@ $command:ident, for $iter:pat in $for_expr:expr => [ $($arg:expr),* $(,)? ] $(, $($tail:tt)*)?) => {
{
for $iter in $for_expr.iter() {
for $iter in $for_expr {
$($crate::cmd!(@ $command, $arg);)*
}
$($crate::cmd!(@ $command, $($tail)*);)*
}
};
(@ $command:ident, for $iter:ident in $for_expr:expr => $arg:expr $(, $($tail:tt)*)?) => {
(@ $command:ident, for $iter:pat in $for_expr:expr => $arg:expr $(, $($tail:tt)*)?) => {
{
for $iter in $for_expr.iter() {
for $iter in $for_expr {
$crate::cmd!(@ $command, $arg);
}
$($crate::cmd!(@ $command, $($tail)*);)*

62
utils/src/traits.rs Normal file
View file

@ -0,0 +1,62 @@
use std::{
borrow::Cow,
ffi::{OsStr, OsString},
path::{Path, PathBuf},
};
trait PrivateTrait<T: ToOwned + ?Sized> {}
macro_rules! impl_private_trait {
($lt:lifetime, $type:ty) => {
impl<$lt, T> PrivateTrait<$type> for T where T: AsRef<[&$lt $type]> {}
};
($type:ty) => {
impl<T> PrivateTrait<$type> for T where T: AsRef<[$type]> {}
};
}
impl_private_trait!(String);
impl_private_trait!('a, str);
impl_private_trait!(PathBuf);
impl_private_trait!('a, Path);
impl_private_trait!(OsString);
impl_private_trait!('a, OsStr);
#[allow(private_bounds)]
pub trait CowCollecter<'a, IN, OUT>: PrivateTrait<IN>
where
IN: ToOwned + ?Sized,
OUT: ToOwned + ?Sized,
{
fn to_cow_vec(&'a self) -> Vec<Cow<'a, OUT>>;
}
macro_rules! impl_cow_collector {
($type:ty) => {
impl<'a, T> CowCollecter<'a, $type, $type> for T
where
T: AsRef<[&'a $type]>,
{
fn to_cow_vec(&'a self) -> Vec<Cow<'a, $type>> {
self.as_ref().iter().map(|v| Cow::Borrowed(*v)).collect()
}
}
};
($in:ty, $out:ty) => {
impl<'a, T> CowCollecter<'a, $in, $out> for T
where
T: AsRef<[$in]>,
{
fn to_cow_vec(&'a self) -> Vec<Cow<'a, $out>> {
self.as_ref().iter().map(Cow::from).collect()
}
}
};
}
impl_cow_collector!(String, str);
impl_cow_collector!(str);
impl_cow_collector!(PathBuf, Path);
impl_cow_collector!(Path);
impl_cow_collector!(OsString, OsStr);
impl_cow_collector!(OsStr);