use crate::env::Env;
use azure_core::{
credentials::AccessToken,
error::{Error, ErrorKind, Result},
};
use std::{
ffi::{OsStr, OsString},
fmt, io,
process::Output,
sync::Arc,
};
mod standard;
#[cfg(feature = "tokio")]
mod tokio;
#[allow(unused)]
pub use standard::StdExecutor;
#[cfg(feature = "tokio")]
pub use tokio::TokioExecutor;
pub fn new_executor() -> Arc<dyn Executor> {
#[cfg(not(feature = "tokio"))]
{
Arc::new(StdExecutor)
}
#[cfg(feature = "tokio")]
{
Arc::new(TokioExecutor)
}
}
#[async_trait::async_trait]
pub trait Executor: Send + Sync + fmt::Debug {
async fn run(&self, program: &OsStr, args: &[&OsStr]) -> io::Result<Output>;
}
pub(crate) async fn shell_exec<T: OutputProcessor>(
executor: Arc<dyn Executor>,
#[cfg_attr(not(windows), allow(unused_variables))] env: &Env,
command: &OsStr,
) -> Result<AccessToken> {
let (workdir, program, c_switch) = {
#[cfg(windows)]
{
let system_root = env.var_os("SYSTEMROOT").map_err(|_| {
Error::with_message(
ErrorKind::Credential,
"SYSTEMROOT environment variable not set",
)
})?;
(system_root, OsStr::new("cmd"), OsStr::new("/C"))
}
#[cfg(not(windows))]
{
(
OsString::from("/bin"),
OsStr::new("/bin/sh"),
OsStr::new("-c"),
)
}
};
let mut command_string = OsString::from("cd ");
command_string.push(workdir);
command_string.push(" && ");
command_string.push(command);
let args = &[c_switch, &command_string];
let status = executor.run(program, args).await;
match status {
Ok(output) if output.status.success() => {
T::deserialize_token(&String::from_utf8_lossy(&output.stdout))
}
Ok(output) => {
let stderr = String::from_utf8_lossy(&output.stderr);
let message = if let Some(error_message) = T::get_error_message(&stderr) {
error_message
} else if output.status.code() == Some(127) || stderr.contains("' is not recognized") {
format!("{} not found on PATH", T::tool_name())
} else {
stderr.to_string()
};
Err(Error::with_message(ErrorKind::Credential, message))
}
Err(e) if e.kind() == std::io::ErrorKind::NotFound => {
let message = format!("{program:?} wasn't found on PATH");
Err(Error::with_error(ErrorKind::Credential, e, message))
}
Err(e) => {
let message = format!("{} error: {e}", e.kind());
Err(Error::with_error(ErrorKind::Credential, e, message))
}
}
}
pub(crate) trait OutputProcessor: Send + Sized + Sync + 'static {
fn deserialize_token(stdout: &str) -> Result<AccessToken>;
fn get_error_message(stderr: &str) -> Option<String>;
fn tool_name() -> &'static str;
}