use crate::api::populate_send_request_output;
use crate::{exec_command, send_request};
use extism_pdk::*;
use serde::de::DeserializeOwned;
use std::path::PathBuf;
use std::sync::OnceLock;
use std::vec;
use warpgate_api::{
AnyResult, ExecCommandInput, ExecCommandOutput, HostEnvironment, HostOS, Id, SendRequestInput,
SendRequestOutput, TestEnvironment, anyhow,
};
#[host_fn]
extern "ExtismHost" {
fn exec_command(input: Json<ExecCommandInput>) -> Json<ExecCommandOutput>;
fn send_request(input: Json<SendRequestInput>) -> Json<SendRequestOutput>;
fn get_env_var(key: String) -> String;
fn set_env_var(name: String, value: String);
}
pub fn fetch(input: SendRequestInput) -> AnyResult<SendRequestOutput> {
let url = input.url.clone();
let response = send_request!(input, input);
let status = response.status;
if !(200..300).contains(&status) {
let body = response.text()?;
debug!(
"Response body for <url>{}</url>: <muted>{}</muted>",
url, body
);
return Err(anyhow!(
"Failed to request <url>{url}</url> <mutedlight>({})</mutedlight>",
status
));
}
if response.body.is_empty() {
return Err(anyhow!("Invalid response from <url>{url}</url>, no body"));
}
Ok(response)
}
pub fn fetch_bytes<U>(url: U) -> AnyResult<Vec<u8>>
where
U: AsRef<str>,
{
Ok(fetch(SendRequestInput::new(url))?.body)
}
pub fn fetch_json<U, R>(url: U) -> AnyResult<R>
where
U: AsRef<str>,
R: DeserializeOwned,
{
fetch(SendRequestInput::new(url))?.json()
}
pub fn fetch_text<U>(url: U) -> AnyResult<String>
where
U: AsRef<str>,
{
fetch(SendRequestInput::new(url))?.text()
}
pub fn exec(input: ExecCommandInput) -> AnyResult<ExecCommandOutput> {
Ok(exec_command!(input, input))
}
pub fn exec_captured<C, I, A>(command: C, args: I) -> AnyResult<ExecCommandOutput>
where
C: AsRef<str>,
I: IntoIterator<Item = A>,
A: AsRef<str>,
{
exec(ExecCommandInput::pipe(command, args))
}
pub fn exec_streamed<C, I, A>(command: C, args: I) -> AnyResult<ExecCommandOutput>
where
C: AsRef<str>,
I: IntoIterator<Item = A>,
A: AsRef<str>,
{
exec(ExecCommandInput::inherit(command, args))
}
pub fn load_git_tags<U>(url: U) -> AnyResult<Vec<String>>
where
U: AsRef<str>,
{
let url = url.as_ref();
debug!("Loading Git tags from remote <url>{}</url>", url);
let mut tags: Vec<String> = vec![];
let output = exec_captured(
"git",
["ls-remote", "--tags", "--sort", "version:refname", url],
)?;
if output.exit_code != 0 {
debug!("Failed to load Git tags");
return Ok(tags);
}
for line in output.stdout.split('\n') {
if line.ends_with("^{}") {
continue;
}
let parts = line.split('\t').collect::<Vec<_>>();
if parts.len() < 2 {
continue;
}
if let Some(tag) = parts[1].strip_prefix("refs/tags/") {
tags.push(tag.to_owned());
}
}
debug!("Loaded {} Git tags", tags.len());
Ok(tags)
}
pub fn command_exists(env: &HostEnvironment, command: &str) -> bool {
debug!(
"Checking if command <shell>{}</shell> exists on the host",
command
);
let result = if env.os == HostOS::Windows {
exec_captured(
"powershell",
["-Command", format!("Get-Command {command}").as_str()],
)
} else {
exec_captured("which", [command])
};
if result.is_ok_and(|res| res.exit_code == 0) {
debug!("Command does exist");
return true;
}
debug!("Command does NOT exist");
false
}
pub fn get_host_env_var<K>(key: K) -> AnyResult<Option<String>>
where
K: AsRef<str>,
{
let inner = unsafe { get_env_var(key.as_ref().into())? };
Ok(if inner.is_empty() { None } else { Some(inner) })
}
pub fn set_host_env_var<K, V>(key: K, value: V) -> AnyResult<()>
where
K: AsRef<str>,
V: AsRef<str>,
{
unsafe { set_env_var(key.as_ref().into(), value.as_ref().into())? };
Ok(())
}
pub fn add_host_paths<I, P>(paths: I) -> AnyResult<()>
where
I: IntoIterator<Item = P>,
P: AsRef<str>,
{
let paths = paths
.into_iter()
.map(|p| p.as_ref().to_owned())
.collect::<Vec<_>>();
set_host_env_var("PATH", paths.join(":"))
}
pub fn get_plugin_id() -> AnyResult<Id> {
Ok(Id::raw(
config::get("plugin_id")?.expect("Missing plugin ID!"),
))
}
pub fn get_host_environment() -> AnyResult<&'static HostEnvironment> {
static HOST_ENVIRONMENT: OnceLock<HostEnvironment> = OnceLock::new();
if HOST_ENVIRONMENT.get().is_none() {
let config = config::get("host_environment")?.expect("Missing host environment!");
let _ = HOST_ENVIRONMENT.set(json::from_str(&config)?);
}
Ok(HOST_ENVIRONMENT.get_or_init(HostEnvironment::default))
}
pub fn get_test_environment() -> AnyResult<Option<&'static TestEnvironment>> {
static TEST_ENVIRONMENT: OnceLock<Option<TestEnvironment>> = OnceLock::new();
if TEST_ENVIRONMENT.get().is_none() {
if let Some(config) = config::get("test_environment")? {
let _ = TEST_ENVIRONMENT.set(json::from_str(&config)?);
}
}
Ok(TEST_ENVIRONMENT.get_or_init(|| None).as_ref())
}
pub fn get_host_to_guest_paths() -> AnyResult<&'static Vec<(PathBuf, PathBuf)>> {
static PATHS_LIST: OnceLock<Vec<(PathBuf, PathBuf)>> = OnceLock::new();
if PATHS_LIST.get().is_none() {
if let Some(config) = config::get("virtual_paths")? {
let _ = PATHS_LIST.set(json::from_str(&config)?);
}
}
Ok(PATHS_LIST.get_or_init(Vec::new))
}