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
1use std::process::Command;
2
1use 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};
811
9#[derive(Clap)]12#[derive(Clap)]
10pub struct BuildSystems {}13pub struct BuildSystems {
14 /// Hosts to skip
15 #[clap(long, number_of_values = 1)]
16 skip: Vec<String>,
17 #[clap(subcommand)]
18 subcommand: Option<Subcommand>,
19}
20
21#[derive(Clap)]
22enum Subcommand {
23 /// Switch to built system until reboot
24 Test,
25 /// Switch to built system after reboot
26 Boot,
27 /// test + boot
28 Switch,
29}
1130
12impl 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()?;
1736
18 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 }
modifiedsrc/command.rsdiffbeforeafterboth
4};4};
55
6use anyhow::{Context, Result};6use anyhow::{Context, Result};
7use serde::Deserialize;7use serde::de::DeserializeOwned;
88
9pub 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}16
1917impl 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 self
23 S: AsRef<OsStr>,21 }
24{22
23 fn run(&mut self) -> Result<()> {
24 let out = self.output()?;
25 if !out.status.success() {
26 anyhow::bail!("command failed");
27 }
28 Ok(())
29 }
30
31 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)35
29 .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 }
43
44 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 cmd
48 }
49}
3550
modifiedsrc/db/keys.rsdiffbeforeafterboth
1use std::collections::BTreeMap;1use std::{collections::BTreeMap, process::Command};
22
3use anyhow::Result;3use anyhow::Result;
4use log::*;4use log::*;
55
6use crate::{6use crate::{command::CommandExt, nix::HOSTS_ATTRIBUTE};
7 command::ssh_command,
8 nix::{NixEval, HOSTS_ATTRIBUTE},
9};
107
11use serde::{Deserialize, Serialize};8use serde::{Deserialize, Serialize};
129
13use super::db::DbData;10use super::db::DbData;
1411
15pub 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}
2021
29impl 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 }
modifiedsrc/db/secret.rsdiffbeforeafterboth
1use 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 = data
123 .renew_in127 .renew_in
124 .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 {
modifiedsrc/nix.rsdiffbeforeafterboth
1use std::{
2 collections::HashMap,
3 ffi::OsStr,
4 path::PathBuf,
5 process::{Command, Stdio},
6};
7
8use anyhow::Result;
9use serde::de::DeserializeOwned;
10
11use crate::command::CommandOutput;
12
13pub 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";
16
17pub 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);
31
32 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}
53
54pub struct NixBuild {
55 attribute: String,
56 impure: bool,
57 env: HashMap<String, String>,
58}
59
60impl 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 self
72 }
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 }
89
90 let out = cmd.output()?;
91 if !out.status.success() {
92 anyhow::bail!("nix eval failed");
93 }
94 Ok(dir)
95 }
96}
97
98#[derive(Default)]
99pub struct NixEval {
100 attribute: String,
101 impure: bool,
102 apply: Option<String>,
103 env: HashMap<String, String>,
104}
105
106impl 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 self
116 }
117 /// This is the only and impure way to pass something to flake
118 /// - https://github.com/NixOS/nix/issues/3949
119 /// - https://github.com/NixOS/nixpkgs/issues/101101
120 pub fn env(&mut self, name: String, value: String) -> &mut Self {
121 self.impure = true;
122 self.env.insert(name, value);
123 self
124 }
125 pub fn apply(&mut self, apply: String) -> &mut Self {
126 self.apply = Some(apply);
127 self
128 }
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);
145
146 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 &self
158 .run_internal(|cmd| {
159 cmd.arg("--json");
160 })?
161 .0,
162 )?)
163 }
164 pub fn run_raw(&self) -> Result<String> {
165 Ok(self
166 .run_internal(|cmd| {
167 cmd.arg("--raw");
168 })?
169 .as_str()?
170 .to_owned())
171 }
172}
1734