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 log::{trace, warn};
30use miette::{Context, IntoDiagnostic, Result, miette};
31use uuid::Uuid;
32
33use crate::constants::CONTAINER_FILE;
34
35pub use command_output::*;
36
37pub static BUILD_ID: std::sync::LazyLock<Uuid> = std::sync::LazyLock::new(Uuid::new_v4);
39
40pub fn check_command_exists(command: &str) -> Result<()> {
45 trace!("check_command_exists({command})");
46
47 trace!("which {command}");
48 if which::which(command).is_ok() {
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
58pub fn retry<V, F>(mut retries: u8, delay_secs: u64, mut f: F) -> miette::Result<V>
63where
64 F: FnMut() -> miette::Result<V> + Send,
65{
66 loop {
67 match f() {
68 Ok(v) => return Ok(v),
69 Err(e) if retries == 0 => return Err(e),
70 Err(e) => {
71 retries -= 1;
72 warn!("Failed operation, will retry {retries} more time(s). Error:\n{e:?}");
73 thread::sleep(Duration::from_secs(delay_secs));
74 }
75 }
76 }
77}
78
79pub async fn retry_async<V, F>(mut retries: u8, delay_secs: u64, mut f: F) -> miette::Result<V>
84where
85 F: AsyncFnMut() -> miette::Result<V>,
86{
87 loop {
88 match f().await {
89 Ok(v) => return Ok(v),
90 Err(e) if retries == 0 => return Err(e),
91 Err(e) => {
92 retries -= 1;
93 warn!("Failed operation, will retry {retries} more time(s). Error:\n{e:?}");
94 thread::sleep(Duration::from_secs(delay_secs));
95 }
96 }
97 }
98}
99
100#[must_use]
101pub fn home_dir() -> Option<PathBuf> {
102 directories::BaseDirs::new().map(|base_dirs| base_dirs.home_dir().to_path_buf())
103}
104
105pub fn generate_containerfile_path<T: AsRef<Path>>(path: T) -> Result<PathBuf> {
111 const HASH_SIZE: usize = 8;
112 let mut buf = [0u8; HASH_SIZE];
113
114 let mut hasher = Blake2bVar::new(HASH_SIZE).into_diagnostic()?;
115 hasher.update(path.as_ref().as_os_str().as_bytes());
116 hasher.finalize_variable(&mut buf).into_diagnostic()?;
117
118 Ok(PathBuf::from(format!(
119 "{CONTAINER_FILE}.{}",
120 BASE64_URL_SAFE_NO_PAD.encode(buf)
121 )))
122}
123
124#[must_use]
125pub fn get_tag_timestamp() -> String {
126 Local::now().format("%Y%m%d").to_string()
127}
128
129pub fn get_env_var(key: &str) -> Result<String> {
134 std::env::var(key)
135 .into_diagnostic()
136 .with_context(|| format!("Failed to get {key}'"))
137}
138
139#[must_use]
141pub fn has_env_var(key: &str) -> bool {
142 get_env_var(key).is_ok_and(|v| v.is_empty().not())
143}
144
145#[once]
149#[must_use]
150pub fn running_as_root() -> bool {
151 nix::unistd::Uid::effective().is_root()
152}
153
154#[must_use]
155pub fn current_timestamp() -> String {
156 Utc::now().to_rfc3339()
157}