use std::time::Duration;
use tracing::{debug, warn};
const REQUEST_TIMEOUT: Duration = Duration::from_secs(5);
const CONNECT_TIMEOUT: Duration = Duration::from_millis(750);
#[derive(Debug)]
pub(super) enum DeleteOutcome {
Removed,
Rejected(reqwest::StatusCode),
Transport(String),
}
pub(super) struct DestructiveIndexDelete {
base: String,
client: reqwest::Client,
}
impl DestructiveIndexDelete {
pub(super) fn acquire() -> Option<Self> {
if trusty_common::running_under_test_harness() {
warn!(
"refusing a destructive trusty-search index DELETE: this is a test process \
(#4743). Set {} to override.",
trusty_common::test_harness::ALLOW_PRODUCTION_ENV
);
return None;
}
let Some(base) = trusty_common::resolve_daemon_base_url("trusty-search") else {
debug!("trusty-search daemon not discoverable; skipping index removal (#2033)");
return None;
};
match reqwest::Client::builder()
.timeout(REQUEST_TIMEOUT)
.connect_timeout(CONNECT_TIMEOUT)
.build()
{
Ok(client) => Some(Self { base, client }),
Err(e) => {
warn!("trusty-search index-delete client build failed: {e}");
None
}
}
}
fn delete_url(&self, index_id: &str) -> String {
format!("{}/indexes/{index_id}?delete_data=true", self.base)
}
pub(super) async fn delete(&self, index_id: &str) -> DeleteOutcome {
match self.client.delete(self.delete_url(index_id)).send().await {
Ok(resp) if resp.status().is_success() => DeleteOutcome::Removed,
Ok(resp) => DeleteOutcome::Rejected(resp.status()),
Err(e) => DeleteOutcome::Transport(e.to_string()),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn acquire_is_refused_under_a_test_harness() {
assert!(
DestructiveIndexDelete::acquire().is_none(),
"a cargo test process must never acquire the destructive-delete capability (#4743)"
);
}
#[serial_test::serial]
#[test]
fn acquire_succeeds_when_production_state_is_explicitly_allowed() {
let dir = crate::test_support::hermetic_temp_dir();
std::fs::create_dir_all(dir.path().join("trusty-search")).unwrap();
std::fs::write(
dir.path().join("trusty-search").join("http_addr"),
"127.0.0.1:59999",
)
.unwrap();
unsafe {
std::env::set_var(trusty_common::DATA_DIR_OVERRIDE_ENV, dir.path());
std::env::set_var(trusty_common::test_harness::ALLOW_PRODUCTION_ENV, "1");
}
let acquired = DestructiveIndexDelete::acquire();
let url = acquired.as_ref().map(|d| d.delete_url("some-index"));
unsafe {
std::env::remove_var(trusty_common::test_harness::ALLOW_PRODUCTION_ENV);
std::env::remove_var(trusty_common::DATA_DIR_OVERRIDE_ENV);
}
assert_eq!(
url.as_deref(),
Some("http://127.0.0.1:59999/indexes/some-index?delete_data=true"),
"the explicit production opt-in must still yield a working capability"
);
}
#[serial_test::serial]
#[test]
fn delete_url_opts_into_data_deletion() {
let cap = DestructiveIndexDelete {
base: "http://127.0.0.1:7878".into(),
client: reqwest::Client::new(),
};
assert_eq!(
cap.delete_url("my-index"),
"http://127.0.0.1:7878/indexes/my-index?delete_data=true"
);
}
}