git.delta.rocks / fleet / refs/commits / 8aa3354490e6

difftreelog

refactor remove nix command wrappers

Yaroslav Bolyukin2020-11-28parent: #d866c97.patch.diff

5 files changed

modifiedsrc/cmds/build_systems.rsdiffbeforeafterboth
--- 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<String>,
+	#[clap(subcommand)]
+	subcommand: Option<Subcommand>,
+}
 
+#[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(())
 	}
modifiedsrc/command.rsdiffbeforeafterboth
--- 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<u8>);
-impl CommandOutput {
-	pub fn into_json<'d, T: Deserialize<'d>>(&'d self) -> Result<T> {
-		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<T: DeserializeOwned>(&mut self) -> Result<T>;
+	fn run_string(&mut self) -> Result<String>;
+	fn inherit_stdio(&mut self) -> &mut Self;
+	fn ssh_on(host: impl AsRef<OsStr>, command: impl AsRef<OsStr>) -> 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<I, S>(host: impl AsRef<OsStr>, command: I) -> Result<CommandOutput>
-where
-	I: IntoIterator<Item = S>,
-	S: AsRef<OsStr>,
-{
-	let out = Command::new("ssh")
-		.stderr(Stdio::inherit())
-		.arg(host)
-		.args(command)
-		.output()?;
-	if !out.status.success() {
-		anyhow::bail!("command failed");
+	fn run_json<T: DeserializeOwned>(&mut self) -> Result<T> {
+		let str = self.run_string()?;
+		Ok(serde_json::from_str(&str).with_context(|| format!("{:?}", str))?)
+	}
+
+	fn run_string(&mut self) -> Result<String> {
+		let out = self.output()?;
+		if !out.status.success() {
+			anyhow::bail!("command failed");
+		}
+		Ok(String::from_utf8(out.stdout)?)
+	}
+
+	fn ssh_on(host: impl AsRef<OsStr>, command: impl AsRef<OsStr>) -> Self {
+		let mut cmd = Command::new("ssh");
+		cmd.arg(host).arg("--").arg(command);
+		cmd
 	}
-	Ok(CommandOutput(out.stdout))
 }
modifiedsrc/db/keys.rsdiffbeforeafterboth
--- 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<Vec<String>> {
-	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(())
 	}
modifiedsrc/db/secret.rsdiffbeforeafterboth
--- 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<u64>,
 }
 pub fn list_secrets() -> Result<HashMap<String, SecretListData>> {
-	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 {
modifiedsrc/nix.rsdiffbeforeafterboth
before · src/nix.rs
1use std::{2	collections::HashMap,3	ffi::OsStr,4	path::PathBuf,5	process::{Command, Stdio},6};78use anyhow::Result;9use serde::de::DeserializeOwned;1011use crate::command::CommandOutput;1213pub const HOSTS_ATTRIBUTE: &str = ".#fleetConfigurations.default.configuredHosts";14pub const SECRETS_ATTRIBUTE: &str = ".#fleetConfigurations.default.configuredSecrets";15pub const SYSTEMS_ATTRIBUTE: &str = ".#fleetConfigurations.default.configuredSystems";1617pub struct NixCopy {18	closure: PathBuf,19}20impl NixCopy {21	pub fn new(closure: PathBuf) -> Self {22		Self { closure }23	}24	fn run_internal(&self, f: impl Fn(&mut Command)) -> Result<CommandOutput> {25		let mut cmd = Command::new("nix");26		cmd.stderr(Stdio::inherit())27			.arg("copy")28			.arg("--substitute-on-destination")29			.arg(&self.closure);30		f(&mut cmd);3132		let out = cmd.output()?;33		if !out.status.success() {34			anyhow::bail!("nix copy failed");35		}36		Ok(CommandOutput(out.stdout))37	}38	pub fn from(&self, from: impl AsRef<OsStr>) -> Result<()> {39		let from = from.as_ref();40		self.run_internal(|cmd| {41			cmd.arg("--from").arg(from);42		})?;43		Ok(())44	}45	pub fn to(&self, to: impl AsRef<OsStr>) -> Result<()> {46		let to = to.as_ref();47		self.run_internal(|cmd| {48			cmd.arg("--to").arg(to);49		})?;50		Ok(())51	}52}5354pub struct NixBuild {55	attribute: String,56	impure: bool,57	env: HashMap<String, String>,58}5960impl NixBuild {61	pub fn new(attribute: String) -> Self {62		Self {63			attribute,64			impure: false,65			env: HashMap::new(),66		}67	}68	pub fn env(&mut self, name: String, value: String) -> &mut Self {69		self.impure = true;70		self.env.insert(name, value);71		self72	}73	pub fn run(&self) -> Result<tempfile::TempDir> {74		let dir = tempfile::tempdir()?;75		std::fs::remove_dir(dir.path())?;76		let mut cmd = Command::new("nix");77		cmd.stderr(Stdio::inherit())78			.arg("build")79			.arg(&self.attribute)80			.arg("--no-link")81			.arg("--out-link")82			.arg(dir.path());83		if self.impure {84			cmd.arg("--impure");85		}86		if !self.env.is_empty() {87			cmd.envs(&self.env);88		}8990		let out = cmd.output()?;91		if !out.status.success() {92			anyhow::bail!("nix eval failed");93		}94		Ok(dir)95	}96}9798#[derive(Default)]99pub struct NixEval {100	attribute: String,101	impure: bool,102	apply: Option<String>,103	env: HashMap<String, String>,104}105106impl NixEval {107	pub fn new(attribute: String) -> Self {108		Self {109			attribute,110			..Default::default()111		}112	}113	pub fn impure(&mut self) -> &mut Self {114		self.impure = true;115		self116	}117	/// This is the only and impure way to pass something to flake118	/// - https://github.com/NixOS/nix/issues/3949119	/// - https://github.com/NixOS/nixpkgs/issues/101101120	pub fn env(&mut self, name: String, value: String) -> &mut Self {121		self.impure = true;122		self.env.insert(name, value);123		self124	}125	pub fn apply(&mut self, apply: String) -> &mut Self {126		self.apply = Some(apply);127		self128	}129	fn run_internal(&self, f: impl Fn(&mut Command)) -> Result<CommandOutput> {130		let mut cmd = Command::new("nix");131		cmd.stderr(Stdio::inherit())132			.arg("eval")133			.arg("--show-trace")134			.arg(&self.attribute);135		if let Some(apply) = &self.apply {136			cmd.arg("--apply").arg(apply);137		};138		if self.impure {139			cmd.arg("--impure");140		}141		if !self.env.is_empty() {142			cmd.envs(&self.env);143		}144		f(&mut cmd);145146		let out = cmd.output()?;147		if !out.status.success() {148			anyhow::bail!("nix eval failed");149		}150		Ok(CommandOutput(out.stdout))151	}152	pub fn run(&self) -> Result<String> {153		Ok(self.run_internal(|_cmd| {})?.as_str()?.to_owned())154	}155	pub fn run_json<T: DeserializeOwned>(&self) -> Result<T> {156		Ok(serde_json::from_slice(157			&self158				.run_internal(|cmd| {159					cmd.arg("--json");160				})?161				.0,162		)?)163	}164	pub fn run_raw(&self) -> Result<String> {165		Ok(self166			.run_internal(|cmd| {167				cmd.arg("--raw");168			})?169			.as_str()?170			.to_owned())171	}172}