diff --git a/src/nix/host/generic.rs b/src/nix/host/generic.rs new file mode 100644 index 0000000..5b1075a --- /dev/null +++ b/src/nix/host/generic.rs @@ -0,0 +1,182 @@ +use std::collections::HashMap; +use std::process::Stdio; + +use async_trait::async_trait; +use serde::{Deserialize, Serialize}; +use tokio::io::{AsyncBufReadExt, AsyncWriteExt, BufReader, BufWriter}; +use tokio::process::Command; + +use super::{CopyDirection, CopyOptions, Host}; +use crate::error::{ColmenaError, ColmenaResult}; +use crate::job::JobHandle; +use crate::nix::{self, Profile, StorePath}; + +#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] +pub struct TransportId(String); + +#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] +pub struct Transport { + id: TransportId, + name: String, + long_name: String, + description: String, +} + +#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] +pub struct GoalId(String); + +#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] +pub struct Goal { + id: GoalId, + name: String, + long_name: String, + description: String, +} + +/// A request to the activation program +#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] +pub enum Request { + /// Asks for activation program's capabilities + Capabilities, + + /// Copy closure to/from host + CopyClosure { + transport: TransportId, + path: StorePath, + to_host: bool, + options: HashMap, + }, + + /// Deploys the profile to host + Deploy { + transport: TransportId, + goal: GoalId, + toplevel: StorePath, + options: HashMap, + }, + + /// Realizes the derivation + Realize { + transport: TransportId, + path: StorePath, + options: HashMap, + }, + + /// Uploads keys to host + UploadKeys { transport: TransportId }, +} + +#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] +pub enum CapabilityResponse { + V1 { + supported_transports: Vec, + supported_goals: Vec, + }, +} + +/// A response from the activation program +#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] +pub enum Response { + Capabilities(CapabilityResponse), + Progress { phase: String }, + NewStorePath { store_path: StorePath }, +} + +#[derive(Debug)] +pub struct GenericHost { + activation_program: StorePath, + transport: TransportId, +} + +#[async_trait] +impl Host for GenericHost { + async fn copy_closure( + &mut self, + closure: &StorePath, + direction: CopyDirection, + options: CopyOptions, + ) -> ColmenaResult<()> { + self.call(Request::CopyClosure { + transport: self.transport.clone(), + path: closure.clone(), + to_host: direction == CopyDirection::ToRemote, + options: HashMap::from([( + "include_outputs".to_owned(), + if options.include_outputs { + "true".to_owned() + } else { + "false".to_owned() + }, + )]), + }) + .await + } + + async fn realize_remote(&mut self, derivation: &StorePath) -> ColmenaResult> { + self.call(Request::Realize { + transport: self.transport.clone(), + path: derivation.clone(), + options: HashMap::new(), + }) + .await?; + + Ok(Vec::new()) + } + + fn set_job(&mut self, bar: Option) {} + + async fn deploy( + &mut self, + profile: &Profile, + goal: nix::Goal, + copy_options: CopyOptions, + activation_program: Option<&StorePath>, + ) -> ColmenaResult<()> { + self.call(Request::Deploy { + transport: self.transport.clone(), + goal: GoalId(goal.to_string()), + toplevel: profile.as_store_path().clone(), + options: HashMap::new(), + }) + .await + } + + async fn get_current_system_profile(&mut self) -> ColmenaResult { + Err(ColmenaError::Unsupported) + } + + async fn get_main_system_profile(&mut self) -> ColmenaResult { + Err(ColmenaError::Unsupported) + } +} + +impl GenericHost { + async fn call(&mut self, request: Request) -> ColmenaResult<()> { + let mut command = Command::new(self.activation_program.as_path()) + .stdin(Stdio::piped()) + .stdout(Stdio::piped()) + .stderr(Stdio::piped()) + .spawn()?; + + let json = serde_json::to_string(&request).unwrap(); + + let mut stdin = BufWriter::new(command.stdin.take().unwrap()); + let mut stdout = BufReader::new(command.stdout.take().unwrap()).lines(); + + stdin.write_all(json.as_bytes()).await?; + + tokio::spawn(async move { + let _status = command + .wait() + .await + .expect("child process encountered an error"); + }); + + while let Some(line) = stdout.next_line().await? { + let response: Response = serde_json::from_str(line.as_str()).unwrap(); + println!("{:?}", response); + } + + Ok(()) + } +} diff --git a/src/nix/host/mod.rs b/src/nix/host/mod.rs index 531c26c..69c9b90 100644 --- a/src/nix/host/mod.rs +++ b/src/nix/host/mod.rs @@ -14,7 +14,9 @@ pub use local::Local; mod key_uploader; -#[derive(Copy, Clone, Debug)] +mod generic; + +#[derive(Copy, Clone, Debug, PartialEq, Eq)] pub enum CopyDirection { ToRemote, FromRemote,