git.delta.rocks / jrsonnet / refs/commits / d5e1b5f02261

difftreelog

source

cmds/fleet/src/host.rs4.8 KiBsourcehistory
1use std::{2	cell::{Ref, RefCell, RefMut},3	env::current_dir,4	ffi::{OsStr, OsString},5	io::Write,6	ops::Deref,7	path::PathBuf,8	sync::Arc,9};1011use anyhow::Result;12use clap::{ArgGroup, Parser};13use serde::de::DeserializeOwned;14use tempfile::{NamedTempFile, TempDir};15use tokio::process::Command;1617use crate::{18	command::CommandExt,19	fleetdata::{dummy_flake, FleetData},20};2122pub struct FleetConfigInternals {23	pub local_system: String,24	pub directory: PathBuf,25	pub opts: FleetOpts,26	pub data: RefCell<FleetData>,27	pub nix_args: Vec<OsString>,28}2930#[derive(Clone)]31pub struct Config(Arc<FleetConfigInternals>);3233impl Deref for Config {34	type Target = FleetConfigInternals;3536	fn deref(&self) -> &Self::Target {37		&self.038	}39}4041impl Config {42	pub fn should_skip(&self, host: &str) -> bool {43		if !self.opts.skip.is_empty() {44			self.opts.skip.iter().any(|h| h as &str == host)45		} else if !self.opts.only.is_empty() {46			!self.opts.only.iter().any(|h| h as &str == host)47		} else {48			false49		}50	}51	pub fn is_local(&self, host: &str) -> bool {52		self.opts.localhost.as_ref().map(|s| s as &str) == Some(host)53	}5455	pub fn command_on(&self, host: &str, program: impl AsRef<OsStr>, sudo: bool) -> Command {56		if self.is_local(host) {57			if sudo {58				let mut cmd = Command::new("sudo");59				cmd.arg(program);60				cmd61			} else {62				Command::new(program)63			}64		} else {65			let mut cmd = Command::new("ssh");66			cmd.arg(host).arg("--");67			if sudo {68				cmd.arg("sudo");69			}70			cmd.arg(program);71			cmd72		}73	}7475	pub fn configuration_attr_name(&self, name: &str) -> OsString {76		let mut str = self.directory.as_os_str().to_owned();77		str.push("#");78		str.push(&format!(79			"fleetConfigurations.default.{}.{}",80			self.local_system, name81		));82		str83	}8485	pub async fn list_hosts(&self) -> Result<Vec<String>> {86		Command::new("nix")87			.arg("eval")88			.arg(self.configuration_attr_name("configuredHosts"))89			.args(["--apply", "builtins.attrNames", "--json", "--show-trace"])90			.args(&self.nix_args)91			.run_nix_json()92			.await93	}94	pub async fn shared_config_attr<T: DeserializeOwned>(&self, attr: &str) -> Result<T> {95		Command::new("nix")96			.arg("eval")97			.arg(self.configuration_attr_name(&format!("configUnchecked.{}", attr)))98			.args(["--json", "--show-trace"])99			.args(&self.nix_args)100			.run_nix_json()101			.await102	}103	pub async fn shared_config_attr_names(&self, attr: &str) -> Result<Vec<String>> {104		Command::new("nix")105			.arg("eval")106			.arg(self.configuration_attr_name(&format!("configUnchecked.{}", attr)))107			.args(["--apply", "builtins.attrNames"])108			.args(["--json", "--show-trace"])109			.args(&self.nix_args)110			.run_nix_json()111			.await112	}113	pub async fn config_attr<T: DeserializeOwned>(&self, host: &str, attr: &str) -> Result<T> {114		Command::new("nix")115			.arg("eval")116			.arg(117				self.configuration_attr_name(&format!(118					"configuredSystems.{}.config.{}",119					host, attr120				)),121			)122			.args(["--json", "--show-trace"])123			.args(&self.nix_args)124			.run_nix_json()125			.await126	}127128	pub fn data(&self) -> Ref<FleetData> {129		self.data.borrow()130	}131	pub fn data_mut(&self) -> RefMut<FleetData> {132		self.data.borrow_mut()133	}134135	pub fn save(&self) -> Result<()> {136		let mut tempfile = NamedTempFile::new_in(self.directory.clone())?;137		let data = nixlike::serialize(&self.data() as &FleetData)?;138		tempfile.write_all(139			format!(140				"# This file contains fleet state and shouldn't be edited by hand\n\n{}\n",141				data142			)143			.as_bytes(),144		)?;145		let mut fleet_data_path = self.directory.clone();146		fleet_data_path.push("fleet.nix");147		tempfile.persist(fleet_data_path)?;148		Ok(())149	}150}151152#[derive(Parser, Clone)]153#[clap(group = ArgGroup::new("target_hosts"))]154pub struct FleetOpts {155	/// All hosts except those would be skipped156	#[clap(long, number_of_values = 1, group = "target_hosts")]157	only: Vec<String>,158159	/// Hosts to skip160	#[clap(long, number_of_values = 1, group = "target_hosts")]161	skip: Vec<String>,162163	/// Host, which should be threaten as current machine164	#[clap(long)]165	pub localhost: Option<String>,166167	// TODO: unhardcode x86_64-linux168	/// Override detected system for host, to perform builds via169	/// binfmt-declared qemu instead of trying to crosscompile170	#[clap(long, default_value = "x86_64-linux")]171	pub local_system: String,172}173174impl FleetOpts {175	pub async fn build(mut self, nix_args: Vec<OsString>) -> Result<Config> {176		let local_system = self.local_system.clone();177		if self.localhost.is_none() {178			self.localhost179				.replace(hostname::get().unwrap().to_str().unwrap().to_owned());180		}181		let directory = current_dir()?;182183		let mut fleet_data_path = directory.clone();184		fleet_data_path.push("fleet.nix");185		let bytes = std::fs::read_to_string(fleet_data_path)?;186		let data = nixlike::parse_str(&bytes)?;187188		Ok(Config(Arc::new(FleetConfigInternals {189			opts: self,190			directory,191			data,192			local_system,193			nix_args,194		})))195	}196}