blue_build_utils/
lib.rs

1pub mod command_output;
2pub mod constants;
3pub mod credentials;
4mod macros;
5pub mod semver;
6pub mod syntax_highlighting;
7#[cfg(feature = "test")]
8pub mod test_utils;
9pub mod traits;
10
11use std::{
12    ops::{AsyncFnMut, Not},
13    os::unix::ffi::OsStrExt,
14    path::{Path, PathBuf},
15    thread,
16    time::Duration,
17};
18
19use base64::prelude::*;
20use blake2::{
21    Blake2bVar,
22    digest::{Update, VariableOutput},
23};
24use cached::proc_macro::once;
25use chrono::Local;
26use comlexr::cmd;
27use format_serde_error::SerdeError;
28use log::{trace, warn};
29use miette::{Context, IntoDiagnostic, Result, miette};
30
31use crate::constants::CONTAINER_FILE;
32
33pub use command_output::*;
34
35/// Checks for the existance of a given command.
36///
37/// # Errors
38/// Will error if the command doesn't exist.
39pub fn check_command_exists(command: &str) -> Result<()> {
40    trace!("check_command_exists({command})");
41
42    trace!("which {command}");
43    if cmd!("which", command)
44        .output()
45        .into_diagnostic()?
46        .status
47        .success()
48    {
49        trace!("Command {command} does exist");
50        Ok(())
51    } else {
52        Err(miette!(
53            "Command {command} doesn't exist and is required to build the image"
54        ))
55    }
56}
57
58/// Creates a serde error for displaying the file
59/// and where the error occurred.
60pub fn serde_yaml_err(contents: &str) -> impl Fn(serde_yaml::Error) -> SerdeError + '_ {
61    |err: serde_yaml::Error| {
62        let location = err.location();
63        let location = location.as_ref();
64        SerdeError::new(
65            contents.to_string(),
66            (
67                err.into(),
68                location.map_or(0, serde_yaml::Location::line).into(),
69                location.map_or(0, serde_yaml::Location::column).into(),
70            ),
71        )
72    }
73}
74
75/// Performs a retry on a given closure with a given nubmer of attempts and delay.
76///
77/// # Errors
78/// Will error when retries have been expended.
79pub fn retry<V, F>(mut retries: u8, delay_secs: u64, mut f: F) -> miette::Result<V>
80where
81    F: FnMut() -> miette::Result<V> + Send,
82{
83    loop {
84        match f() {
85            Ok(v) => return Ok(v),
86            Err(e) if retries == 0 => return Err(e),
87            Err(e) => {
88                retries -= 1;
89                warn!("Failed operation, will retry {retries} more time(s). Error:\n{e:?}");
90                thread::sleep(Duration::from_secs(delay_secs));
91            }
92        }
93    }
94}
95
96/// Performs a retry on a given closure with a given nubmer of attempts and delay.
97///
98/// # Errors
99/// Will error when retries have been expended.
100pub async fn retry_async<V, F>(mut retries: u8, delay_secs: u64, mut f: F) -> miette::Result<V>
101where
102    F: AsyncFnMut() -> miette::Result<V>,
103{
104    loop {
105        match f().await {
106            Ok(v) => return Ok(v),
107            Err(e) if retries == 0 => return Err(e),
108            Err(e) => {
109                retries -= 1;
110                warn!("Failed operation, will retry {retries} more time(s). Error:\n{e:?}");
111                thread::sleep(Duration::from_secs(delay_secs));
112            }
113        }
114    }
115}
116
117#[must_use]
118pub fn home_dir() -> Option<PathBuf> {
119    directories::BaseDirs::new().map(|base_dirs| base_dirs.home_dir().to_path_buf())
120}
121
122/// Generates a 1-1 related Containerfile to a recipe.
123/// The file is in the format of `Containerfile.{path_hash}`.
124///
125/// # Errors
126/// Will error if unable to create a hash of the
127pub fn generate_containerfile_path<T: AsRef<Path>>(path: T) -> Result<PathBuf> {
128    const HASH_SIZE: usize = 8;
129    let mut buf = [0u8; HASH_SIZE];
130
131    let mut hasher = Blake2bVar::new(HASH_SIZE).into_diagnostic()?;
132    hasher.update(path.as_ref().as_os_str().as_bytes());
133    hasher.finalize_variable(&mut buf).into_diagnostic()?;
134
135    Ok(PathBuf::from(format!(
136        "{CONTAINER_FILE}.{}",
137        BASE64_URL_SAFE_NO_PAD.encode(buf)
138    )))
139}
140
141#[must_use]
142pub fn get_tag_timestamp() -> String {
143    Local::now().format("%Y%m%d").to_string()
144}
145
146/// Get's the env var wrapping it with a miette error
147///
148/// # Errors
149/// Will error if the env var doesn't exist.
150pub fn get_env_var(key: &str) -> Result<String> {
151    std::env::var(key)
152        .into_diagnostic()
153        .with_context(|| format!("Failed to get {key}'"))
154}
155
156/// Checks if an environment variable is set and isn't empty.
157#[must_use]
158pub fn has_env_var(key: &str) -> bool {
159    get_env_var(key).is_ok_and(|v| v.is_empty().not())
160}
161
162/// Checks if the process is running as root.
163///
164/// This call is cached to reduce syscalls.
165#[once]
166#[must_use]
167pub fn running_as_root() -> bool {
168    nix::unistd::Uid::effective().is_root()
169}