feat: allow using env phones
This commit is contained in:
@@ -27,14 +27,37 @@ impl Config {
|
||||
}
|
||||
|
||||
pub fn load() -> Result<Self> {
|
||||
// If all required env vars are set, use them directly (for CI)
|
||||
if let (Ok(token), Ok(git_url)) = (
|
||||
std::env::var("ECTF_TOKEN"),
|
||||
std::env::var("ECTF_GIT_URL"),
|
||||
) {
|
||||
let api_url = std::env::var("ECTF_API_URL")
|
||||
.unwrap_or_else(|_| DEFAULT_API_URL.to_string());
|
||||
return Ok(Self { token, git_url, api_url });
|
||||
}
|
||||
|
||||
let path = Self::path()?;
|
||||
let contents = std::fs::read_to_string(&path).with_context(|| {
|
||||
format!(
|
||||
"Config not found at {}. Run `ectf-tools config` first.",
|
||||
"Config not found at {}. Run `ectf-tools config` first, or set ECTF_TOKEN and ECTF_GIT_URL env vars.",
|
||||
path.display()
|
||||
)
|
||||
})?;
|
||||
serde_yaml::from_str(&contents).context("Failed to parse config file")
|
||||
let mut cfg: Self = serde_yaml::from_str(&contents).context("Failed to parse config file")?;
|
||||
|
||||
// Allow env vars to override individual fields from the config file
|
||||
if let Ok(token) = std::env::var("ECTF_TOKEN") {
|
||||
cfg.token = token;
|
||||
}
|
||||
if let Ok(git_url) = std::env::var("ECTF_GIT_URL") {
|
||||
cfg.git_url = git_url;
|
||||
}
|
||||
if let Ok(api_url) = std::env::var("ECTF_API_URL") {
|
||||
cfg.api_url = api_url;
|
||||
}
|
||||
|
||||
Ok(cfg)
|
||||
}
|
||||
|
||||
pub fn save(&self) -> Result<()> {
|
||||
|
||||
87
src/main.rs
87
src/main.rs
@@ -294,6 +294,9 @@ enum ToolsCmd {
|
||||
/// Skip tests requiring a second HSM
|
||||
#[arg(long)]
|
||||
no_transfer: bool,
|
||||
/// Output results as JSON for CI
|
||||
#[arg(long)]
|
||||
json: bool,
|
||||
},
|
||||
}
|
||||
|
||||
@@ -768,6 +771,7 @@ fn run_tools(port: &str, cmd: ToolsCmd) -> Result<()> {
|
||||
gid,
|
||||
transfer_port,
|
||||
no_transfer,
|
||||
json,
|
||||
} => {
|
||||
validate_pin(&pin)?;
|
||||
let gid = parse_gid(&gid)?;
|
||||
@@ -783,7 +787,7 @@ fn run_tools(port: &str, cmd: ToolsCmd) -> Result<()> {
|
||||
_ => None,
|
||||
};
|
||||
|
||||
return run_test(&mut hsm, hsm2.as_mut(), &pin, gid, no_transfer);
|
||||
return run_test(&mut hsm, hsm2.as_mut(), &pin, gid, no_transfer, json);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -831,28 +835,47 @@ fn run_test(
|
||||
pin: &str,
|
||||
gid: u16,
|
||||
no_transfer: bool,
|
||||
json: bool,
|
||||
) -> Result<()> {
|
||||
struct TestResult {
|
||||
name: &'static str,
|
||||
passed: bool,
|
||||
duration_secs: f64,
|
||||
error: Option<String>,
|
||||
}
|
||||
|
||||
let mut results: Vec<TestResult> = Vec::new();
|
||||
|
||||
macro_rules! run_test {
|
||||
($name:expr, $body:expr) => {{
|
||||
log::info(&format!("Running: {}", $name));
|
||||
if !json {
|
||||
log::info(&format!("Running: {}", $name));
|
||||
}
|
||||
let start = std::time::Instant::now();
|
||||
match (|| -> Result<()> { $body })() {
|
||||
Ok(()) => {
|
||||
let elapsed = start.elapsed();
|
||||
log::success(&format!("{} passed ({:.1}s)", $name, elapsed.as_secs_f64()));
|
||||
results.push(TestResult { name: $name, passed: true });
|
||||
if !json {
|
||||
log::success(&format!("{} passed ({:.1}s)", $name, elapsed.as_secs_f64()));
|
||||
}
|
||||
results.push(TestResult {
|
||||
name: $name,
|
||||
passed: true,
|
||||
duration_secs: elapsed.as_secs_f64(),
|
||||
error: None,
|
||||
});
|
||||
}
|
||||
Err(e) => {
|
||||
let elapsed = start.elapsed();
|
||||
log::error(&format!("{} FAILED ({:.1}s): {e}", $name, elapsed.as_secs_f64()));
|
||||
results.push(TestResult { name: $name, passed: false });
|
||||
if !json {
|
||||
log::error(&format!("{} FAILED ({:.1}s): {e}", $name, elapsed.as_secs_f64()));
|
||||
}
|
||||
results.push(TestResult {
|
||||
name: $name,
|
||||
passed: false,
|
||||
duration_secs: elapsed.as_secs_f64(),
|
||||
error: Some(e.to_string()),
|
||||
});
|
||||
}
|
||||
}
|
||||
}};
|
||||
@@ -1305,27 +1328,57 @@ As we have seen him in the Capitol, Being cross";
|
||||
|
||||
// ── Summary ──
|
||||
|
||||
println!();
|
||||
let total = results.len();
|
||||
let passed = results.iter().filter(|r| r.passed).count();
|
||||
let failed = total - passed;
|
||||
|
||||
for r in &results {
|
||||
if r.passed {
|
||||
log::success(&format!(" PASS {}", r.name));
|
||||
if json {
|
||||
let tests: Vec<serde_json::Value> = results
|
||||
.iter()
|
||||
.map(|r| {
|
||||
let mut obj = serde_json::Map::new();
|
||||
obj.insert("name".into(), serde_json::Value::String(r.name.into()));
|
||||
obj.insert("passed".into(), serde_json::Value::Bool(r.passed));
|
||||
obj.insert(
|
||||
"duration_secs".into(),
|
||||
serde_json::Value::Number(
|
||||
serde_json::Number::from_f64(r.duration_secs).unwrap(),
|
||||
),
|
||||
);
|
||||
if let Some(e) = &r.error {
|
||||
obj.insert("error".into(), serde_json::Value::String(e.clone()));
|
||||
}
|
||||
serde_json::Value::Object(obj)
|
||||
})
|
||||
.collect();
|
||||
let output = serde_json::json!({
|
||||
"total": total,
|
||||
"passed": passed,
|
||||
"failed": failed,
|
||||
"tests": tests,
|
||||
});
|
||||
println!("{}", serde_json::to_string(&output).unwrap());
|
||||
} else {
|
||||
println!();
|
||||
for r in &results {
|
||||
if r.passed {
|
||||
log::success(&format!(" PASS {}", r.name));
|
||||
} else {
|
||||
log::error(&format!(" FAIL {}", r.name));
|
||||
}
|
||||
}
|
||||
println!();
|
||||
if failed == 0 {
|
||||
log::success(&format!("All {total} tests passed"));
|
||||
} else {
|
||||
log::error(&format!(" FAIL {}", r.name));
|
||||
log::error(&format!("{failed}/{total} tests failed"));
|
||||
}
|
||||
}
|
||||
|
||||
println!();
|
||||
if failed == 0 {
|
||||
log::success(&format!("All {total} tests passed"));
|
||||
Ok(())
|
||||
} else {
|
||||
log::error(&format!("{failed}/{total} tests failed"));
|
||||
if failed > 0 {
|
||||
bail!("{failed} test(s) failed");
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// ─── HW commands ───
|
||||
|
||||
Reference in New Issue
Block a user