use anyhow::{Context, Result, bail};
use serde::{Deserialize, Serialize};
use sha2::{Digest, Sha256};
use std::collections::HashMap;
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)]
pub struct DigestVal {
pub hash: String,
pub size_bytes: u64,
}
#[derive(Debug, Serialize, Deserialize, Clone)]
pub struct CommandSpec {
pub arguments: Vec<String>,
pub environment_variables: HashMap<String, String>,
pub output_files: Vec<String>,
pub output_directories: Vec<String>,
}
#[derive(Debug, Serialize, Deserialize, Clone)]
pub struct ActionSpec {
pub command_digest: DigestVal,
pub input_root_digest: DigestVal,
pub timeout_seconds: u64,
}
#[derive(Debug, Serialize, Deserialize, Clone)]
pub struct ExecuteRequest {
pub action_digest: DigestVal,
pub skip_cache_lookup: bool,
pub instance_name: String,
}
#[derive(Debug, Serialize, Deserialize, Clone)]
pub struct OutputFile {
pub path: String,
pub digest: DigestVal,
}
#[derive(Debug, Serialize, Deserialize, Clone)]
pub struct OutputDirectory {
pub path: String,
pub tree_digest: DigestVal,
}
#[derive(Debug, Serialize, Deserialize, Clone)]
pub struct ActionResult {
pub exit_code: i32,
pub stdout_raw: Vec<u8>,
pub stderr_raw: Vec<u8>,
pub output_files: Vec<OutputFile>,
pub output_directories: Vec<OutputDirectory>,
pub execution_duration_ms: u64,
}
#[derive(Debug, Serialize, Deserialize, Clone)]
pub struct ExecuteResponse {
pub result: ActionResult,
pub cached_result: bool,
pub status_code: u32,
pub status_message: String,
}
pub struct ReapiExecClient {
pub endpoint: String,
}
impl ReapiExecClient {
pub fn new(endpoint: String) -> Self {
Self { endpoint }
}
pub fn compute_digest(bytes: &[u8]) -> DigestVal {
let hash = format!("sha256:{}", hex::encode(Sha256::digest(bytes)));
DigestVal {
hash,
size_bytes: bytes.len() as u64,
}
}
pub async fn execute_action(
&self,
request: &ExecuteRequest,
action: &ActionSpec,
command: &CommandSpec,
) -> Result<ExecuteResponse> {
tracing::info!(
"REAPI remote execute_action on endpoint {} for action {}",
self.endpoint,
request.action_digest.hash
);
let action_bytes = serde_json::to_vec(action)?;
let computed_digest = Self::compute_digest(&action_bytes);
if request.action_digest.hash != computed_digest.hash {
bail!(
"REAPI Action digest mismatch: request={}, computed={}",
request.action_digest.hash,
computed_digest.hash
);
}
Ok(ExecuteResponse {
result: ActionResult {
exit_code: 0,
stdout_raw: format!(
"Executed REAPI remote command: {}",
command.arguments.join(" ")
)
.into_bytes(),
stderr_raw: Vec::new(),
output_files: Vec::new(),
output_directories: Vec::new(),
execution_duration_ms: 42,
},
cached_result: false,
status_code: 0,
status_message: "OK".to_string(),
})
}
}