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:
Gerald Pinder 2024-02-25 15:45:33 -05:00 committed by GitHub
parent ce8f889dc2
commit 910e0434b6
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
34 changed files with 620 additions and 512 deletions

128
utils/src/command_output.rs Normal file
View file

@ -0,0 +1,128 @@
use std::{
ffi::OsStr,
fmt::Debug,
io::{Error, ErrorKind, Result},
process::{Command, Stdio},
time::{Duration, Instant},
};
use process_control::{ChildExt, Control};
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct CommandOutput {
pub stdout: String,
pub stderr: String,
}
/// # Attempt to resolve `binary_name` from and creates a new `Command` pointing at it
/// # This allows executing cmd files on Windows and prevents running executable from cwd on Windows
/// # This function also initializes std{err,out,in} to protect against processes changing the console mode
/// #
/// # Errors
///
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);
let full_path = match which::which(binary_name) {
Ok(full_path) => {
log::trace!("Using {:?} as {:?}", full_path, binary_name);
full_path
}
Err(error) => {
log::trace!("Unable to find {:?} in PATH, {:?}", binary_name, error);
return Err(Error::new(ErrorKind::NotFound, error));
}
};
let mut cmd = Command::new(full_path);
cmd.stderr(Stdio::piped())
.stdout(Stdio::piped())
.stdin(Stdio::null());
Ok(cmd)
}
/// Execute a command and return the output on stdout and stderr if successful
pub fn exec_cmd<T: AsRef<OsStr> + Debug, U: AsRef<OsStr> + Debug>(
cmd: T,
args: &[U],
time_limit: Duration,
) -> Option<CommandOutput> {
log::trace!("Executing command {:?} with args {:?}", cmd, args);
internal_exec_cmd(cmd, args, time_limit)
}
fn internal_exec_cmd<T: AsRef<OsStr> + Debug, U: AsRef<OsStr> + Debug>(
cmd: T,
args: &[U],
time_limit: Duration,
) -> Option<CommandOutput> {
let mut cmd = create_command(cmd).ok()?;
cmd.args(args);
exec_timeout(&mut cmd, time_limit)
}
fn exec_timeout(cmd: &mut Command, time_limit: Duration) -> Option<CommandOutput> {
let start = Instant::now();
let process = match cmd.spawn() {
Ok(process) => process,
Err(error) => {
log::trace!("Unable to run {:?}, {:?}", cmd.get_program(), error);
return None;
}
};
match process
.controlled_with_output()
.time_limit(time_limit)
.terminate_for_timeout()
.wait()
{
Ok(Some(output)) => {
let stdout_string = match String::from_utf8(output.stdout) {
Ok(stdout) => stdout,
Err(error) => {
log::warn!("Unable to decode stdout: {:?}", error);
return None;
}
};
let stderr_string = match String::from_utf8(output.stderr) {
Ok(stderr) => stderr,
Err(error) => {
log::warn!("Unable to decode stderr: {:?}", error);
return None;
}
};
log::trace!(
"stdout: {:?}, stderr: {:?}, exit code: \"{:?}\", took {:?}",
stdout_string,
stderr_string,
output.status.code(),
start.elapsed()
);
if !output.status.success() {
return None;
}
Some(CommandOutput {
stdout: stdout_string,
stderr: stderr_string,
})
}
Ok(None) => {
log::warn!("Executing command {:?} timed out.", cmd.get_program());
log::warn!("You can set command_timeout in your config to a higher value to allow longer-running commands to keep executing.");
None
}
Err(error) => {
log::trace!(
"Executing command {:?} failed by: {:?}",
cmd.get_program(),
error
);
None
}
}
}

68
utils/src/constants.rs Normal file
View file

@ -0,0 +1,68 @@
// Paths
pub const ARCHIVE_SUFFIX: &str = "tar.gz";
pub const COSIGN_PATH: &str = "./cosign.pub";
pub const GITIGNORE_PATH: &str = ".gitignore";
pub const LOCAL_BUILD: &str = "/etc/bluebuild";
pub const CONTAINER_FILE: &str = "Containerfile";
pub const MODULES_PATH: &str = "./config/modules";
pub const RECIPE_PATH: &str = "./config/recipe.yml";
pub const RUN_PODMAN_SOCK: &str = "/run/podman/podman.sock";
pub const VAR_RUN_PODMAN_PODMAN_SOCK: &str = "/var/run/podman/podman.sock";
pub const VAR_RUN_PODMAN_SOCK: &str = "/var/run/podman.sock";
// Labels
pub const BUILD_ID_LABEL: &str = "org.blue-build.build-id";
// Cosign vars
pub const COSIGN_PRIVATE_KEY: &str = "COSIGN_PRIVATE_KEY";
pub const GITHUB_TOKEN_ISSUER_URL: &str = "https://token.actions.githubusercontent.com";
pub const SIGSTORE_ID_TOKEN: &str = "SIGSTORE_ID_TOKEN";
// GitHub CI vars
pub const GITHUB_ACTIONS: &str = "GITHUB_ACTIONS";
pub const GITHUB_ACTOR: &str = "GITHUB_ACTOR";
pub const GITHUB_EVENT_NAME: &str = "GITHUB_EVENT_NAME";
pub const GITHUB_REF_NAME: &str = "GITHUB_REF_NAME";
pub const GITHUB_REPOSITORY_OWNER: &str = "GITHUB_REPOSITORY_OWNER";
pub const GITHUB_SHA: &str = "GITHUB_SHA";
pub const GITHUB_TOKEN: &str = "GH_TOKEN";
pub const GITHUB_WORKFLOW_REF: &str = "GITHUB_WORKFLOW_REF";
pub const PR_EVENT_NUMBER: &str = "GH_PR_EVENT_NUMBER";
// GitLab CI vars
pub const CI_COMMIT_REF_NAME: &str = "CI_COMMIT_REF_NAME";
pub const CI_COMMIT_SHORT_SHA: &str = "CI_COMMIT_SHORT_SHA";
pub const CI_DEFAULT_BRANCH: &str = "CI_DEFAULT_BRANCH";
pub const CI_MERGE_REQUEST_IID: &str = "CI_MERGE_REQUEST_IID";
pub const CI_PIPELINE_SOURCE: &str = "CI_PIPELINE_SOURCE";
pub const CI_PROJECT_NAME: &str = "CI_PROJECT_NAME";
pub const CI_PROJECT_NAMESPACE: &str = "CI_PROJECT_NAMESPACE";
pub const CI_PROJECT_URL: &str = "CI_PROJECT_URL";
pub const CI_SERVER_HOST: &str = "CI_SERVER_HOST";
pub const CI_SERVER_PROTOCOL: &str = "CI_SERVER_PROTOCOL";
pub const CI_REGISTRY: &str = "CI_REGISTRY";
pub const CI_REGISTRY_PASSWORD: &str = "CI_REGISTRY_PASSWORD";
pub const CI_REGISTRY_USER: &str = "CI_REGISTRY_USER";
// Terminal vars
pub const TERM_PROGRAM: &str = "TERM_PROGRAM";
pub const LC_TERMINAL: &str = "LC_TERMINAL";
pub const TERM_PROGRAM_VERSION: &str = "TERM_PROGRAM_VERSION";
pub const LC_TERMINAL_VERSION: &str = "LC_TERMINAL_VERSION";
pub const XDG_RUNTIME_DIR: &str = "XDG_RUNTIME_DIR";
// Misc
pub const UNKNOWN_SHELL: &str = "<unknown shell>";
pub const UNKNOWN_VERSION: &str = "<unknown version>";
pub const UNKNOWN_TERMINAL: &str = "<unknown terminal>";
pub const GITHUB_CHAR_LIMIT: usize = 8100; // Magic number accepted by Github
// Messages
pub const LABELED_ERROR_MESSAGE: &str =
"It looks you have a BlueBuild-generated Containerfile that is not .gitignored. \
Do you want to remove it from git and add it to .gitignore? (This will still continue the build)";
pub const NO_LABEL_ERROR_MESSAGE: &str =
"It looks you have a Containerfile that has not been generated by BlueBuild. \
Running `build` will override your Containerfile and add an entry to the .gitignore. \
Do you want to continue?";

79
utils/src/lib.rs Normal file
View file

@ -0,0 +1,79 @@
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};
pub use command_output::*;
pub fn check_command_exists(command: &str) -> Result<()> {
trace!("check_command_exists({command})");
debug!("Checking if {command} exists");
trace!("which {command}");
if Command::new("which")
.arg(command)
.output()?
.status
.success()
{
debug!("Command {command} does exist");
Ok(())
} else {
Err(anyhow!(
"Command {command} doesn't exist and is required to build the image"
))
}
}
pub fn append_to_file(file_path: &str, content: &str) -> Result<()> {
trace!("append_to_file({file_path}, {content})");
debug!("Appending {content} to {file_path}");
let mut file = std::fs::OpenOptions::new()
.append(true)
.create(true)
.open(file_path)?;
writeln!(file, "\n{content}")?;
Ok(())
}
pub fn serde_yaml_err(contents: &str) -> impl Fn(serde_yaml::Error) -> SerdeError + '_ {
|err: serde_yaml::Error| {
let location = err.location();
let location = location.as_ref();
SerdeError::new(
contents.to_string(),
(
err.into(),
location.map_or(0, serde_yaml::Location::line).into(),
location.map_or(0, serde_yaml::Location::column).into(),
),
)
}
}
pub fn retry<V, F>(mut attempts: u8, delay: u64, f: F) -> anyhow::Result<V>
where
F: Fn() -> anyhow::Result<V>,
{
loop {
match f() {
Ok(v) => return Ok(v),
Err(e) if attempts == 1 => return Err(e),
_ => {
attempts -= 1;
thread::sleep(Duration::from_secs(delay));
}
};
}
}
#[must_use]
pub fn home_dir() -> Option<PathBuf> {
directories::BaseDirs::new().map(|base_dirs| base_dirs.home_dir().to_path_buf())
}