use std::path::Path;
use std::process::Command;
use std::time::{SystemTime, UNIX_EPOCH};
use anyhow::{bail, Context, Result};
use serde::{Deserialize, Serialize};
pub const ATTESTATION_PATH: &str = "e2e-attestation.json";
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct Attestation {
pub command: String,
pub ran_at: u64,
pub exit_code: i32,
pub commit: String,
}
pub fn attest(repo: &Path, command: &str) -> Result<Attestation> {
let commit = git_capture(repo, &["rev-parse", "HEAD"])
.context("resolving HEAD — `e2e attest` must run inside a git repo with a commit")?;
let status = Command::new("sh")
.arg("-c")
.arg(command)
.current_dir(repo)
.status()
.with_context(|| format!("running e2e command `{command}`"))?;
let exit_code = status.code().unwrap_or(-1);
let ran_at = SystemTime::now()
.duration_since(UNIX_EPOCH)
.map(|d| d.as_secs())
.unwrap_or(0);
let attestation = Attestation {
command: command.to_string(),
ran_at,
exit_code,
commit: commit.clone(),
};
let path = repo.join(ATTESTATION_PATH);
let json = serde_json::to_string_pretty(&attestation).context("serializing the attestation")?;
std::fs::write(&path, format!("{json}\n"))
.with_context(|| format!("writing {}", path.display()))?;
git_run(repo, &["add", ATTESTATION_PATH])?;
let short = &commit[..commit.len().min(7)];
let message = format!("e2e attestation for {short}");
git_run(
repo,
&[
"-c",
"commit.gpgsign=false",
"commit",
"-q",
"-m",
message.as_str(),
],
)?;
Ok(attestation)
}
fn git_capture(repo: &Path, args: &[&str]) -> Result<String> {
let out = Command::new("git")
.args(args)
.current_dir(repo)
.output()
.with_context(|| format!("running `git {}`", args.join(" ")))?;
if !out.status.success() {
bail!(
"`git {}` failed: {}",
args.join(" "),
String::from_utf8_lossy(&out.stderr).trim()
);
}
Ok(String::from_utf8(out.stdout)?.trim().to_string())
}
fn git_run(repo: &Path, args: &[&str]) -> Result<()> {
let status = Command::new("git")
.args(args)
.current_dir(repo)
.status()
.with_context(|| format!("running `git {}`", args.join(" ")))?;
if !status.success() {
bail!("`git {}` failed", args.join(" "));
}
Ok(())
}