From 8aa3354490e6eed2e8b06117342279c79717c4c2 Mon Sep 17 00:00:00 2001 From: Yaroslav Bolyukin Date: Sat, 28 Nov 2020 20:49:07 +0000 Subject: [PATCH] refactor: remove nix command wrappers --- --- a/src/cmds/build_systems.rs +++ b/src/cmds/build_systems.rs @@ -1,14 +1,33 @@ +use std::process::Command; + use crate::{ + command::CommandExt, db::{keys::list_hosts, secret::SecretDb, Db, DbData}, - nix::{NixBuild, NixCopy, HOSTS_ATTRIBUTE, SYSTEMS_ATTRIBUTE}, + nix::SYSTEMS_ATTRIBUTE, }; use anyhow::Result; use clap::Clap; -use log::info; +use log::{info, warn}; #[derive(Clap)] -pub struct BuildSystems {} +pub struct BuildSystems { + /// Hosts to skip + #[clap(long, number_of_values = 1)] + skip: Vec, + #[clap(subcommand)] + subcommand: Option, +} +#[derive(Clap)] +enum Subcommand { + /// Switch to built system until reboot + Test, + /// Switch to built system after reboot + Boot, + /// test + boot + Switch, +} + impl BuildSystems { pub fn run(self) -> Result<()> { let db = Db::new(".fleet")?; @@ -16,16 +35,47 @@ let data = SecretDb::open(&db)?.generate_nix_data()?; for host in hosts.iter() { + if self.skip.contains(host) { + warn!("Skipping host {}", host); + continue; + } info!("Building host {}", host); - let path = NixBuild::new(format!( - "{}.{}.config.system.build.toplevel", - SYSTEMS_ATTRIBUTE, host, - )) - .env("SECRET_DATA".into(), data.clone()) - .run()?; - info!("{:?}", path.path()); - NixCopy::new(path.path().to_owned()).to(format!("ssh://root@{}", host))?; - std::thread::sleep_ms(9999999) + let built = tempfile::tempdir()?; + Command::new("nix") + .inherit_stdio() + .arg("build") + .arg(format!( + "{}.{}.config.system.build.toplevel", + SYSTEMS_ATTRIBUTE, host, + )) + .arg("--no-link") + .arg("--out-link") + .arg(built.path()) + .arg("--impure") + .env("SECRET_DATA", data.clone()) + .run()?; + info!("Uploading system closure"); + let full_path = std::fs::canonicalize(built.path())?; + info!("{:?}", full_path); + Command::new("nix") + .inherit_stdio() + .arg("copy") + .arg(full_path) + .arg("--to") + .arg(format!("ssh://root@{}", host)) + .run()?; + match self.subcommand { + Some(Subcommand::Test) => { + info!("Setting system to test") + } + Some(Subcommand::Boot) => { + info!("Setting system to switch on boot") + } + Some(Subcommand::Switch) => { + info!("Switching to configuration") + } + _ => {} + } } Ok(()) } --- a/src/command.rs +++ b/src/command.rs @@ -4,31 +4,46 @@ }; use anyhow::{Context, Result}; -use serde::Deserialize; +use serde::de::DeserializeOwned; -pub struct CommandOutput(pub Vec); -impl CommandOutput { - pub fn into_json<'d, T: Deserialize<'d>>(&'d self) -> Result { - let str = self.as_str().ok(); - Ok(serde_json::from_slice(&self.0).with_context(|| format!("{:?}", str))?) +pub trait CommandExt { + fn run(&mut self) -> Result<()>; + fn run_json(&mut self) -> Result; + fn run_string(&mut self) -> Result; + fn inherit_stdio(&mut self) -> &mut Self; + fn ssh_on(host: impl AsRef, command: impl AsRef) -> Self; +} + +impl CommandExt for Command { + fn inherit_stdio(&mut self) -> &mut Self { + self.stderr(Stdio::inherit()); + self } - pub fn as_str(&self) -> Result<&str> { - Ok(std::str::from_utf8(&self.0)?) + + fn run(&mut self) -> Result<()> { + let out = self.output()?; + if !out.status.success() { + anyhow::bail!("command failed"); + } + Ok(()) } -} -pub fn ssh_command(host: impl AsRef, command: I) -> Result -where - I: IntoIterator, - S: AsRef, -{ - let out = Command::new("ssh") - .stderr(Stdio::inherit()) - .arg(host) - .args(command) - .output()?; - if !out.status.success() { - anyhow::bail!("command failed"); + fn run_json(&mut self) -> Result { + let str = self.run_string()?; + Ok(serde_json::from_str(&str).with_context(|| format!("{:?}", str))?) + } + + fn run_string(&mut self) -> Result { + let out = self.output()?; + if !out.status.success() { + anyhow::bail!("command failed"); + } + Ok(String::from_utf8(out.stdout)?) + } + + fn ssh_on(host: impl AsRef, command: impl AsRef) -> Self { + let mut cmd = Command::new("ssh"); + cmd.arg(host).arg("--").arg(command); + cmd } - Ok(CommandOutput(out.stdout)) } --- a/src/db/keys.rs +++ b/src/db/keys.rs @@ -1,20 +1,21 @@ -use std::collections::BTreeMap; +use std::{collections::BTreeMap, process::Command}; use anyhow::Result; use log::*; -use crate::{ - command::ssh_command, - nix::{NixEval, HOSTS_ATTRIBUTE}, -}; +use crate::{command::CommandExt, nix::HOSTS_ATTRIBUTE}; use serde::{Deserialize, Serialize}; use super::db::DbData; pub fn list_hosts() -> Result> { - Ok(NixEval::new(HOSTS_ATTRIBUTE.into()) - .apply("builtins.attrNames".into()) + Ok(Command::new("nix") + .inherit_stdio() + .arg("eval") + .arg(HOSTS_ATTRIBUTE) + .arg("--apply") + .arg("builtins.attrNames") .run_json()?) } @@ -29,10 +30,9 @@ impl KeyDb { pub fn fetch_key(&mut self, host: &str) -> Result<()> { info!("Fetching key for {}", host); - let key = ssh_command(host, &["cat", "/etc/ssh/ssh_host_ed25519_key.pub"])? - .as_str()? - .trim() - .to_owned(); + let key = Command::ssh_on(host, "cat") + .arg("/etc/ssh/ssh_host_ed25519_key.pub") + .run_string()?; self.host_keys.insert(host.to_owned(), key); Ok(()) } --- a/src/db/secret.rs +++ b/src/db/secret.rs @@ -1,9 +1,10 @@ -use crate::nix::{NixBuild, NixEval, SECRETS_ATTRIBUTE}; +use crate::{command::CommandExt, nix::SECRETS_ATTRIBUTE}; use anyhow::{bail, Result}; use log::info; use serde::{Deserialize, Deserializer, Serialize, Serializer}; use std::{ collections::{BTreeMap, BTreeSet, HashMap}, + process::Command, time::Instant, time::SystemTime, }; @@ -18,14 +19,17 @@ renew_in: Option, } pub fn list_secrets() -> Result> { - NixEval::new(format!("{}", SECRETS_ATTRIBUTE)) - .apply( + Command::new("nix") + .inherit_stdio() + .arg("eval") + .arg(SECRETS_ATTRIBUTE) + .arg("--apply") + .arg( r#" s: (builtins.mapAttrs (n: {owners, expireIn, ...}: { inherit owners expireIn; }) s) - "# - .into(), + "#, ) .run_json() } @@ -122,9 +126,17 @@ let renew_at = data .renew_in .map(|hours| created_at + Duration::hours(hours as i64)); - let built = NixBuild::new(format!("{}.{}.generator", SECRETS_ATTRIBUTE, secret)) - .env("RAGE_KEYS".into(), rage_keys) - .env("IMPURITY_SOURCE".into(), format!("{:?}", Instant::now())) + let built = tempfile::tempdir()?; + Command::new("nix") + .inherit_stdio() + .arg("build") + .arg(format!("{}.{}.generator", SECRETS_ATTRIBUTE, secret)) + .arg("--no-link") + .arg("--out-link") + .arg(built.path()) + .arg("--impure") + .env("RAGE_KEYS", rage_keys) + .env("IMPURITY_SOURCE", format!("{:?}", Instant::now())) .run()?; let path = built.path().to_owned(); let mut secret_data = SecretData { --- a/src/nix.rs +++ b/src/nix.rs @@ -1,172 +1,3 @@ -use std::{ - collections::HashMap, - ffi::OsStr, - path::PathBuf, - process::{Command, Stdio}, -}; - -use anyhow::Result; -use serde::de::DeserializeOwned; - -use crate::command::CommandOutput; - pub const HOSTS_ATTRIBUTE: &str = ".#fleetConfigurations.default.configuredHosts"; pub const SECRETS_ATTRIBUTE: &str = ".#fleetConfigurations.default.configuredSecrets"; pub const SYSTEMS_ATTRIBUTE: &str = ".#fleetConfigurations.default.configuredSystems"; - -pub struct NixCopy { - closure: PathBuf, -} -impl NixCopy { - pub fn new(closure: PathBuf) -> Self { - Self { closure } - } - fn run_internal(&self, f: impl Fn(&mut Command)) -> Result { - let mut cmd = Command::new("nix"); - cmd.stderr(Stdio::inherit()) - .arg("copy") - .arg("--substitute-on-destination") - .arg(&self.closure); - f(&mut cmd); - - let out = cmd.output()?; - if !out.status.success() { - anyhow::bail!("nix copy failed"); - } - Ok(CommandOutput(out.stdout)) - } - pub fn from(&self, from: impl AsRef) -> Result<()> { - let from = from.as_ref(); - self.run_internal(|cmd| { - cmd.arg("--from").arg(from); - })?; - Ok(()) - } - pub fn to(&self, to: impl AsRef) -> Result<()> { - let to = to.as_ref(); - self.run_internal(|cmd| { - cmd.arg("--to").arg(to); - })?; - Ok(()) - } -} - -pub struct NixBuild { - attribute: String, - impure: bool, - env: HashMap, -} - -impl NixBuild { - pub fn new(attribute: String) -> Self { - Self { - attribute, - impure: false, - env: HashMap::new(), - } - } - pub fn env(&mut self, name: String, value: String) -> &mut Self { - self.impure = true; - self.env.insert(name, value); - self - } - pub fn run(&self) -> Result { - let dir = tempfile::tempdir()?; - std::fs::remove_dir(dir.path())?; - let mut cmd = Command::new("nix"); - cmd.stderr(Stdio::inherit()) - .arg("build") - .arg(&self.attribute) - .arg("--no-link") - .arg("--out-link") - .arg(dir.path()); - if self.impure { - cmd.arg("--impure"); - } - if !self.env.is_empty() { - cmd.envs(&self.env); - } - - let out = cmd.output()?; - if !out.status.success() { - anyhow::bail!("nix eval failed"); - } - Ok(dir) - } -} - -#[derive(Default)] -pub struct NixEval { - attribute: String, - impure: bool, - apply: Option, - env: HashMap, -} - -impl NixEval { - pub fn new(attribute: String) -> Self { - Self { - attribute, - ..Default::default() - } - } - pub fn impure(&mut self) -> &mut Self { - self.impure = true; - self - } - /// This is the only and impure way to pass something to flake - /// - https://github.com/NixOS/nix/issues/3949 - /// - https://github.com/NixOS/nixpkgs/issues/101101 - pub fn env(&mut self, name: String, value: String) -> &mut Self { - self.impure = true; - self.env.insert(name, value); - self - } - pub fn apply(&mut self, apply: String) -> &mut Self { - self.apply = Some(apply); - self - } - fn run_internal(&self, f: impl Fn(&mut Command)) -> Result { - let mut cmd = Command::new("nix"); - cmd.stderr(Stdio::inherit()) - .arg("eval") - .arg("--show-trace") - .arg(&self.attribute); - if let Some(apply) = &self.apply { - cmd.arg("--apply").arg(apply); - }; - if self.impure { - cmd.arg("--impure"); - } - if !self.env.is_empty() { - cmd.envs(&self.env); - } - f(&mut cmd); - - let out = cmd.output()?; - if !out.status.success() { - anyhow::bail!("nix eval failed"); - } - Ok(CommandOutput(out.stdout)) - } - pub fn run(&self) -> Result { - Ok(self.run_internal(|_cmd| {})?.as_str()?.to_owned()) - } - pub fn run_json(&self) -> Result { - Ok(serde_json::from_slice( - &self - .run_internal(|cmd| { - cmd.arg("--json"); - })? - .0, - )?) - } - pub fn run_raw(&self) -> Result { - Ok(self - .run_internal(|cmd| { - cmd.arg("--raw"); - })? - .as_str()? - .to_owned()) - } -} -- gitstuff