use std::fmt;
use std::path::{Path, PathBuf};
use thiserror::Error;
pub const ORACLE_KUBECONFIG_ENV: &str = "ENGENHO_ORACLE_KUBECONFIG";
pub const ORACLE_KUBECONFIG_FALLBACK: &str = ".kube/engenho-local-tunnel.yaml";
#[derive(Debug, Clone, Copy, PartialEq, Eq, Error)]
#[error(
"no oracle kubeconfig to try: {ORACLE_KUBECONFIG_ENV} is unset or empty and HOME is unset, \
so the $HOME/{ORACLE_KUBECONFIG_FALLBACK} fallback cannot be formed. Set \
{ORACLE_KUBECONFIG_ENV} to a live cluster's kubeconfig."
)]
pub struct OracleUnresolved;
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct OracleKubeconfig(PathBuf);
impl OracleKubeconfig {
pub fn resolve(env_value: Option<&str>, home: Option<&Path>) -> Result<Self, OracleUnresolved> {
if let Some(configured) = env_value.filter(|v| !v.is_empty()) {
return Ok(Self(PathBuf::from(configured)));
}
home.map(|h| Self(h.join(ORACLE_KUBECONFIG_FALLBACK)))
.ok_or(OracleUnresolved)
}
pub fn from_env() -> Result<Self, OracleUnresolved> {
let configured = std::env::var(ORACLE_KUBECONFIG_ENV).ok();
let home = std::env::var("HOME").ok();
Self::resolve(configured.as_deref(), home.as_deref().map(Path::new))
}
#[must_use]
pub fn path(&self) -> &Path {
&self.0
}
#[must_use]
pub fn unreachable(&self) -> OracleUnreachable {
OracleUnreachable {
kubeconfig: self.clone(),
}
}
}
impl fmt::Display for OracleKubeconfig {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.0.display())
}
}
#[derive(Debug, Clone, PartialEq, Eq, Error)]
#[error(
"Verdict::ReferenceUnreachable — the oracle at {kubeconfig} did not answer GET /api/v1. A \
live differential run cannot proceed. Point {ORACLE_KUBECONFIG_ENV} at a live cluster's \
kubeconfig (a kind cluster pinned to the target Kubernetes version works) and run \
`cargo nextest run --profile oracle`."
)]
pub struct OracleUnreachable {
kubeconfig: OracleKubeconfig,
}
impl OracleUnreachable {
#[must_use]
pub fn kubeconfig(&self) -> &OracleKubeconfig {
&self.kubeconfig
}
}
#[cfg(test)]
mod tests {
use super::*;
const SECOND_LOADER: &[&str] = &["from_kubeconfig(", "tls-server-name"];
#[test]
fn a_configured_kubeconfig_wins_over_the_home_fallback() {
let resolved = OracleKubeconfig::resolve(
Some("/tmp/kind-oracle.yaml"),
Some(Path::new("/home/operator")),
)
.expect("a configured value resolves");
assert_eq!(resolved.path(), Path::new("/tmp/kind-oracle.yaml"));
}
#[test]
fn an_empty_configured_value_counts_as_unset() {
let resolved = OracleKubeconfig::resolve(Some(""), Some(Path::new("/home/operator")))
.expect("the home fallback resolves");
assert_eq!(
resolved.path(),
Path::new("/home/operator").join(ORACLE_KUBECONFIG_FALLBACK)
);
}
#[test]
fn nothing_configured_and_no_home_is_a_typed_absence_not_a_guessed_path() {
assert_eq!(
OracleKubeconfig::resolve(None, None),
Err(OracleUnresolved),
"with no env value and no HOME there is no path to try, and inventing one \
would report a configuration gap as a dead cluster"
);
}
#[test]
fn the_unreachable_hint_names_the_env_knob_and_never_the_dead_tunnel() {
let kubeconfig = OracleKubeconfig::resolve(Some("/tmp/kind-oracle.yaml"), None)
.expect("a configured value resolves");
let hint = kubeconfig.unreachable().to_string();
assert!(
hint.contains(ORACLE_KUBECONFIG_ENV),
"the hint must name the knob that repoints the oracle; got: {hint}"
);
assert!(
hint.contains("/tmp/kind-oracle.yaml"),
"the hint must name the kubeconfig it actually tried; got: {hint}"
);
for dead in ["tls-server-name", "tunnel", "192.168.64.10"] {
assert!(
!hint.contains(dead),
"the hint still sends the operator to the decommissioned k3s VM \
({dead}); got: {hint}"
);
}
}
#[test]
fn no_integration_binary_loads_the_oracle_itself() {
let tests_dir = Path::new(env!("CARGO_MANIFEST_DIR")).join("tests");
assert!(
tests_dir.join("common").join("mod.rs").is_file(),
"the shared loader is not at {}/common/mod.rs — this gate is scanning \
the wrong tree",
tests_dir.display()
);
assert!(
tests_dir.join("m0_core_crud_parity.rs").is_file(),
"the binary this gate exists for is not at {}/m0_core_crud_parity.rs \
— this gate is scanning the wrong tree",
tests_dir.display()
);
let mut offenders: Vec<String> = Vec::new();
let entries = std::fs::read_dir(&tests_dir).expect("engenho-diff/tests is readable");
for entry in entries {
let path = entry.expect("a readable directory entry").path();
if path.extension().and_then(std::ffi::OsStr::to_str) != Some("rs") {
continue;
}
let source = std::fs::read_to_string(&path).expect("a readable test source");
let name = path
.file_name()
.and_then(std::ffi::OsStr::to_str)
.unwrap_or("<unnamed>")
.to_owned();
for needle in SECOND_LOADER {
if source.contains(needle) {
offenders.push(format!("{name} contains {needle:?}"));
}
}
}
assert!(
offenders.is_empty(),
"a second oracle loader lives outside tests/common/mod.rs, and its copy of \
the hint will drift out of sync silently:\n {}\n\nUse \
`common::load_oracle()`; if it does not return what the binary needs, add \
that to common/mod.rs.",
offenders.join("\n ")
);
}
}