1use crate::api::populate_send_request_output;
2use crate::{exec_command, send_request};
3use extism_pdk::*;
4use serde::de::DeserializeOwned;
5use std::ffi::OsStr;
6use std::path::PathBuf;
7use std::vec;
8use warpgate_api::{
9 AnyResult, ExecCommandInput, ExecCommandOutput, HostEnvironment, HostOS, SendRequestInput,
10 SendRequestOutput, TestEnvironment, VirtualPath, anyhow,
11};
12
13#[host_fn]
14extern "ExtismHost" {
15 fn exec_command(input: Json<ExecCommandInput>) -> Json<ExecCommandOutput>;
16 fn from_virtual_path(input: String) -> String;
17 fn get_env_var(key: String) -> String;
18 fn send_request(input: Json<SendRequestInput>) -> Json<SendRequestOutput>;
19 fn set_env_var(name: String, value: String);
20 fn to_virtual_path(input: String) -> Json<VirtualPath>;
21}
22
23pub fn fetch(input: SendRequestInput) -> AnyResult<SendRequestOutput> {
25 let url = input.url.clone();
26 let response = send_request!(input, input);
27 let status = response.status;
28
29 if status != 200 {
30 let body = response.text()?;
31
32 debug!(
33 "Response body for <url>{}</url>: <muted>{}</muted>",
34 url, body
35 );
36
37 return Err(anyhow!(
38 "Failed to request <url>{url}</url> <mutedlight>({})</mutedlight>",
39 status
40 ));
41 }
42
43 if response.body.is_empty() {
44 return Err(anyhow!("Invalid response from <url>{url}</url>, no body"));
45 }
46
47 Ok(response)
48}
49
50pub fn fetch_bytes<U>(url: U) -> AnyResult<Vec<u8>>
52where
53 U: AsRef<str>,
54{
55 Ok(fetch(SendRequestInput::new(url))?.body)
56}
57
58pub fn fetch_json<U, R>(url: U) -> AnyResult<R>
60where
61 U: AsRef<str>,
62 R: DeserializeOwned,
63{
64 fetch(SendRequestInput::new(url))?.json()
65}
66
67pub fn fetch_text<U>(url: U) -> AnyResult<String>
69where
70 U: AsRef<str>,
71{
72 fetch(SendRequestInput::new(url))?.text()
73}
74
75pub fn exec(input: ExecCommandInput) -> AnyResult<ExecCommandOutput> {
77 Ok(exec_command!(input, input))
78}
79
80pub fn exec_captured<C, I, A>(command: C, args: I) -> AnyResult<ExecCommandOutput>
82where
83 C: AsRef<str>,
84 I: IntoIterator<Item = A>,
85 A: AsRef<str>,
86{
87 exec(ExecCommandInput::pipe(command, args))
88}
89
90pub fn exec_streamed<C, I, A>(command: C, args: I) -> AnyResult<ExecCommandOutput>
92where
93 C: AsRef<str>,
94 I: IntoIterator<Item = A>,
95 A: AsRef<str>,
96{
97 exec(ExecCommandInput::inherit(command, args))
98}
99
100pub fn load_git_tags<U>(url: U) -> AnyResult<Vec<String>>
103where
104 U: AsRef<str>,
105{
106 let url = url.as_ref();
107
108 debug!("Loading Git tags from remote <url>{}</url>", url);
109
110 let mut tags: Vec<String> = vec![];
111 let output = exec_captured(
112 "git",
113 ["ls-remote", "--tags", "--sort", "version:refname", url],
114 )?;
115
116 if output.exit_code != 0 {
117 debug!("Failed to load Git tags");
118
119 return Ok(tags);
120 }
121
122 for line in output.stdout.split('\n') {
123 if line.ends_with("^{}") {
125 continue;
126 }
127
128 let parts = line.split('\t').collect::<Vec<_>>();
129
130 if parts.len() < 2 {
131 continue;
132 }
133
134 if let Some(tag) = parts[1].strip_prefix("refs/tags/") {
135 tags.push(tag.to_owned());
136 }
137 }
138
139 debug!("Loaded {} Git tags", tags.len());
140
141 Ok(tags)
142}
143
144pub fn command_exists(env: &HostEnvironment, command: &str) -> bool {
146 debug!(
147 "Checking if command <shell>{}</shell> exists on the host",
148 command
149 );
150
151 let result = if env.os == HostOS::Windows {
152 exec_captured(
153 "powershell",
154 ["-Command", format!("Get-Command {command}").as_str()],
155 )
156 } else {
157 exec_captured("which", [command])
158 };
159
160 if result.is_ok_and(|res| res.exit_code == 0) {
161 debug!("Command does exist");
162
163 return true;
164 }
165
166 debug!("Command does NOT exist");
167
168 false
169}
170
171pub fn get_host_env_var<K>(key: K) -> AnyResult<Option<String>>
173where
174 K: AsRef<str>,
175{
176 let inner = unsafe { get_env_var(key.as_ref().into())? };
177
178 Ok(if inner.is_empty() { None } else { Some(inner) })
179}
180
181pub fn set_host_env_var<K, V>(key: K, value: V) -> AnyResult<()>
183where
184 K: AsRef<str>,
185 V: AsRef<str>,
186{
187 unsafe { set_env_var(key.as_ref().into(), value.as_ref().into())? };
188
189 Ok(())
190}
191
192pub fn add_host_paths<I, P>(paths: I) -> AnyResult<()>
194where
195 I: IntoIterator<Item = P>,
196 P: AsRef<str>,
197{
198 let paths = paths
199 .into_iter()
200 .map(|p| p.as_ref().to_owned())
201 .collect::<Vec<_>>();
202
203 set_host_env_var("PATH", paths.join(":"))
204}
205
206pub fn into_real_path<P>(path: P) -> AnyResult<PathBuf>
209where
210 P: AsRef<OsStr>,
211{
212 Ok(PathBuf::from(unsafe {
213 from_virtual_path(path.as_ref().to_string_lossy().into())?
214 }))
215}
216
217pub fn into_virtual_path<P>(path: P) -> AnyResult<VirtualPath>
220where
221 P: AsRef<OsStr>,
222{
223 let data = unsafe { to_virtual_path(path.as_ref().to_string_lossy().into())? };
224
225 Ok(data.0)
226}
227
228pub fn get_plugin_id() -> AnyResult<String> {
230 Ok(config::get("plugin_id")?.expect("Missing plugin ID!"))
231}
232
233pub fn get_host_environment() -> AnyResult<HostEnvironment> {
235 let config = config::get("host_environment")?.expect("Missing host environment!");
236 let config: HostEnvironment = json::from_str(&config)?;
237
238 Ok(config)
239}
240
241pub fn get_test_environment() -> AnyResult<Option<TestEnvironment>> {
243 if let Some(config) = config::get("test_environment")? {
244 return Ok(json::from_str(&config)?);
245 }
246
247 Ok(None)
248}