blue_build_utils/
lib.rs

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