difftreelog
refactor remove nix command wrappers
5 files changed
src/cmds/build_systems.rsdiffbeforeafterboth1use std::process::Command;21use crate::{3use crate::{4 command::CommandExt,2 db::{keys::list_hosts, secret::SecretDb, Db, DbData},5 db::{keys::list_hosts, secret::SecretDb, Db, DbData},3 nix::{NixBuild, NixCopy, HOSTS_ATTRIBUTE, SYSTEMS_ATTRIBUTE},6 nix::SYSTEMS_ATTRIBUTE,4};7};5use anyhow::Result;8use anyhow::Result;6use clap::Clap;9use clap::Clap;7use log::info;10use log::{info, warn};8119#[derive(Clap)]12#[derive(Clap)]10pub struct BuildSystems {}13pub struct BuildSystems {14 /// Hosts to skip15 #[clap(long, number_of_values = 1)]16 skip: Vec<String>,17 #[clap(subcommand)]18 subcommand: Option<Subcommand>,19}2021#[derive(Clap)]22enum Subcommand {23 /// Switch to built system until reboot24 Test,25 /// Switch to built system after reboot26 Boot,27 /// test + boot28 Switch,29}113012impl BuildSystems {31impl BuildSystems {13 pub fn run(self) -> Result<()> {32 pub fn run(self) -> Result<()> {16 let data = SecretDb::open(&db)?.generate_nix_data()?;35 let data = SecretDb::open(&db)?.generate_nix_data()?;173618 for host in hosts.iter() {37 for host in hosts.iter() {38 if self.skip.contains(host) {39 warn!("Skipping host {}", host);40 continue;41 }19 info!("Building host {}", host);42 info!("Building host {}", host);20 let path = NixBuild::new(format!(43 let built = tempfile::tempdir()?;44 Command::new("nix")45 .inherit_stdio()46 .arg("build")47 .arg(format!(21 "{}.{}.config.system.build.toplevel",48 "{}.{}.config.system.build.toplevel",22 SYSTEMS_ATTRIBUTE, host,49 SYSTEMS_ATTRIBUTE, host,23 ))50 ))51 .arg("--no-link")52 .arg("--out-link")53 .arg(built.path())54 .arg("--impure")24 .env("SECRET_DATA".into(), data.clone())55 .env("SECRET_DATA", data.clone())25 .run()?;56 .run()?;57 info!("Uploading system closure");58 let full_path = std::fs::canonicalize(built.path())?;26 info!("{:?}", path.path());59 info!("{:?}", full_path);27 NixCopy::new(path.path().to_owned()).to(format!("ssh://root@{}", host))?;60 Command::new("nix")61 .inherit_stdio()62 .arg("copy")63 .arg(full_path)64 .arg("--to")65 .arg(format!("ssh://root@{}", host))66 .run()?;67 match self.subcommand {28 std::thread::sleep_ms(9999999)68 Some(Subcommand::Test) => {69 info!("Setting system to test")70 }71 Some(Subcommand::Boot) => {72 info!("Setting system to switch on boot")73 }74 Some(Subcommand::Switch) => {75 info!("Switching to configuration")76 }77 _ => {}78 }29 }79 }30 Ok(())80 Ok(())31 }81 }src/command.rsdiffbeforeafterboth4};4};556use anyhow::{Context, Result};6use anyhow::{Context, Result};7use serde::Deserialize;7use serde::de::DeserializeOwned;889pub struct CommandOutput(pub Vec<u8>);9pub trait CommandExt {10impl CommandOutput {10 fn run(&mut self) -> Result<()>;11 pub fn into_json<'d, T: Deserialize<'d>>(&'d self) -> Result<T> {11 fn run_json<T: DeserializeOwned>(&mut self) -> Result<T>;12 let str = self.as_str().ok();13 Ok(serde_json::from_slice(&self.0).with_context(|| format!("{:?}", str))?)14 }15 pub fn as_str(&self) -> Result<&str> {12 fn run_string(&mut self) -> Result<String>;13 fn inherit_stdio(&mut self) -> &mut Self;16 Ok(std::str::from_utf8(&self.0)?)14 fn ssh_on(host: impl AsRef<OsStr>, command: impl AsRef<OsStr>) -> Self;17 }15}18}161917impl CommandExt for Command {20pub fn ssh_command<I, S>(host: impl AsRef<OsStr>, command: I) -> Result<CommandOutput>18 fn inherit_stdio(&mut self) -> &mut Self {21where19 self.stderr(Stdio::inherit());22 I: IntoIterator<Item = S>,20 self23 S: AsRef<OsStr>,21 }24{2223 fn run(&mut self) -> Result<()> {24 let out = self.output()?;25 if !out.status.success() {26 anyhow::bail!("command failed");27 }28 Ok(())29 }3031 fn run_json<T: DeserializeOwned>(&mut self) -> Result<T> {25 let out = Command::new("ssh")32 let str = self.run_string()?;26 .stderr(Stdio::inherit())33 Ok(serde_json::from_str(&str).with_context(|| format!("{:?}", str))?)27 .arg(host)34 }28 .args(command)3529 .output()?;36 fn run_string(&mut self) -> Result<String> {37 let out = self.output()?;30 if !out.status.success() {38 if !out.status.success() {31 anyhow::bail!("command failed");39 anyhow::bail!("command failed");32 }40 }33 Ok(CommandOutput(out.stdout))41 Ok(String::from_utf8(out.stdout)?)34}42 }4344 fn ssh_on(host: impl AsRef<OsStr>, command: impl AsRef<OsStr>) -> Self {45 let mut cmd = Command::new("ssh");46 cmd.arg(host).arg("--").arg(command);47 cmd48 }49}3550src/db/keys.rsdiffbeforeafterboth1use std::collections::BTreeMap;1use std::{collections::BTreeMap, process::Command};223use anyhow::Result;3use anyhow::Result;4use log::*;4use log::*;556use crate::{6use crate::{command::CommandExt, nix::HOSTS_ATTRIBUTE};7 command::ssh_command,8 nix::{NixEval, HOSTS_ATTRIBUTE},9};10711use serde::{Deserialize, Serialize};8use serde::{Deserialize, Serialize};12913use super::db::DbData;10use super::db::DbData;141115pub fn list_hosts() -> Result<Vec<String>> {12pub fn list_hosts() -> Result<Vec<String>> {16 Ok(NixEval::new(HOSTS_ATTRIBUTE.into())13 Ok(Command::new("nix")14 .inherit_stdio()15 .arg("eval")16 .arg(HOSTS_ATTRIBUTE)17 .arg("--apply")17 .apply("builtins.attrNames".into())18 .arg("builtins.attrNames")18 .run_json()?)19 .run_json()?)19}20}202129impl KeyDb {30impl KeyDb {30 pub fn fetch_key(&mut self, host: &str) -> Result<()> {31 pub fn fetch_key(&mut self, host: &str) -> Result<()> {31 info!("Fetching key for {}", host);32 info!("Fetching key for {}", host);32 let key = ssh_command(host, &["cat", "/etc/ssh/ssh_host_ed25519_key.pub"])?33 let key = Command::ssh_on(host, "cat")33 .as_str()?34 .arg("/etc/ssh/ssh_host_ed25519_key.pub")34 .trim()35 .to_owned();35 .run_string()?;36 self.host_keys.insert(host.to_owned(), key);36 self.host_keys.insert(host.to_owned(), key);37 Ok(())37 Ok(())38 }38 }src/db/secret.rsdiffbeforeafterboth1use crate::nix::{NixBuild, NixEval, SECRETS_ATTRIBUTE};1use crate::{command::CommandExt, nix::SECRETS_ATTRIBUTE};2use anyhow::{bail, Result};2use anyhow::{bail, Result};3use log::info;3use log::info;4use serde::{Deserialize, Deserializer, Serialize, Serializer};4use serde::{Deserialize, Deserializer, Serialize, Serializer};5use std::{5use std::{6 collections::{BTreeMap, BTreeSet, HashMap},6 collections::{BTreeMap, BTreeSet, HashMap},7 process::Command,7 time::Instant,8 time::Instant,8 time::SystemTime,9 time::SystemTime,9};10};18 renew_in: Option<u64>,19 renew_in: Option<u64>,19}20}20pub fn list_secrets() -> Result<HashMap<String, SecretListData>> {21pub fn list_secrets() -> Result<HashMap<String, SecretListData>> {21 NixEval::new(format!("{}", SECRETS_ATTRIBUTE))22 Command::new("nix")23 .inherit_stdio()24 .arg("eval")25 .arg(SECRETS_ATTRIBUTE)26 .arg("--apply")22 .apply(27 .arg(23 r#"28 r#"24 s: (builtins.mapAttrs (n: {owners, expireIn, ...}: {29 s: (builtins.mapAttrs (n: {owners, expireIn, ...}: {25 inherit owners expireIn;30 inherit owners expireIn;26 }) s)31 }) s)27 "#32 "#,28 .into(),29 )33 )30 .run_json()34 .run_json()31}35}122 let renew_at = data126 let renew_at = data123 .renew_in127 .renew_in124 .map(|hours| created_at + Duration::hours(hours as i64));128 .map(|hours| created_at + Duration::hours(hours as i64));125 let built = NixBuild::new(format!("{}.{}.generator", SECRETS_ATTRIBUTE, secret))129 let built = tempfile::tempdir()?;130 Command::new("nix")131 .inherit_stdio()132 .arg("build")133 .arg(format!("{}.{}.generator", SECRETS_ATTRIBUTE, secret))134 .arg("--no-link")135 .arg("--out-link")136 .arg(built.path())137 .arg("--impure")126 .env("RAGE_KEYS".into(), rage_keys)138 .env("RAGE_KEYS", rage_keys)127 .env("IMPURITY_SOURCE".into(), format!("{:?}", Instant::now()))139 .env("IMPURITY_SOURCE", format!("{:?}", Instant::now()))128 .run()?;140 .run()?;129 let path = built.path().to_owned();141 let path = built.path().to_owned();130 let mut secret_data = SecretData {142 let mut secret_data = SecretData {src/nix.rsdiffbeforeafterboth1use 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";1pub const HOSTS_ATTRIBUTE: &str = ".#fleetConfigurations.default.configuredHosts";14pub const SECRETS_ATTRIBUTE: &str = ".#fleetConfigurations.default.configuredSecrets";2pub const SECRETS_ATTRIBUTE: &str = ".#fleetConfigurations.default.configuredSecrets";15pub const SYSTEMS_ATTRIBUTE: &str = ".#fleetConfigurations.default.configuredSystems";3pub 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}1734