#[cfg(any(test, target_os = "linux"))]
use super::super::capabilities::Support;
#[cfg(any(test, target_os = "linux"))]
use super::super::contract::SecretBackend;
#[cfg(any(test, target_os = "linux"))]
use anyhow::Result;
#[cfg(any(test, target_os = "linux"))]
const ATTRIBUTE_NAME: &str = "yana-ai-key";
#[cfg(any(test, target_os = "linux"))]
const COMMAND_TIMEOUT: std::time::Duration = std::time::Duration::from_secs(4);
#[cfg(any(test, target_os = "linux"))]
pub struct Backend;
#[cfg(any(test, target_os = "linux"))]
impl Backend {
pub fn is_available(&self) -> Support {
if std::process::Command::new("secret-tool")
.arg("--version")
.stdin(std::process::Stdio::null())
.stdout(std::process::Stdio::null())
.stderr(std::process::Stdio::null())
.status()
.is_ok_and(|status| status.success())
{
Support::Supported
} else {
Support::Unsupported
}
}
}
#[cfg(target_os = "linux")]
impl SecretBackend for Backend {
fn has_entry(&self, key: &str) -> Result<bool> {
use std::process::{Command, Stdio};
use std::time::Instant;
let mut child = Command::new("secret-tool")
.args(["lookup", ATTRIBUTE_NAME, key])
.stdin(Stdio::null())
.stdout(Stdio::null())
.stderr(Stdio::null())
.spawn()
.map_err(|error| anyhow::anyhow!("starting secret-tool: {error}"))?;
let deadline = Instant::now() + COMMAND_TIMEOUT;
let status = loop {
if let Some(status) = child.try_wait()? {
break status;
}
if Instant::now() >= deadline {
let _ = child.kill();
let _ = child.wait();
anyhow::bail!(
"secret-tool lookup timed out after {}s",
COMMAND_TIMEOUT.as_secs()
);
}
std::thread::sleep(std::time::Duration::from_millis(20));
};
Ok(status.success())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn is_available_reports_unsupported_rather_than_panicking_when_the_binary_is_absent() {
let backend = Backend;
let has_binary = std::process::Command::new("secret-tool")
.arg("--version")
.stdin(std::process::Stdio::null())
.stdout(std::process::Stdio::null())
.stderr(std::process::Stdio::null())
.status()
.is_ok_and(|status| status.success());
let expected = if has_binary {
Support::Supported
} else {
Support::Unsupported
};
assert_eq!(backend.is_available(), expected);
}
#[test]
fn attribute_name_is_stable_and_namespaced() {
assert_eq!(ATTRIBUTE_NAME, "yana-ai-key");
}
}