1use std::{2 env::current_dir,3 ffi::{OsStr, OsString},4 ops::Deref,5 path::PathBuf,6 process::Command,7 sync::Arc,8};910use anyhow::Result;11use clap::Clap;1213use crate::command::CommandExt;1415pub struct FleetConfigInternals {16 pub directory: PathBuf,17 pub opts: FleetOpts,18}1920#[derive(Clone)]21pub struct FleetConfig(Arc<FleetConfigInternals>);2223impl Deref for FleetConfig {24 type Target = FleetConfigInternals;2526 fn deref(&self) -> &Self::Target {27 &self.028 }29}3031impl FleetConfig {32 pub fn data_dir(&self) -> PathBuf {33 let mut out = self.directory.clone();34 out.push(".fleet");35 out36 }3738 pub fn full_attr_name(&self, attr_name: &str) -> OsString {39 let mut str = self.directory.as_os_str().to_owned();40 str.push("#");41 str.push(attr_name);42 str43 }4445 pub fn list_host_names(&self) -> Result<Vec<String>> {46 Ok(Command::new("nix")47 .arg("eval")48 .arg(self.full_attr_name("fleetConfigurations.default.configuredHosts"))49 .args(&["--apply", "builtins.attrNames", "--json"])50 .inherit_stdio()51 .run_json()?)52 }5354 pub fn list_hosts(&self) -> Result<Vec<Host>> {55 Ok(self56 .list_host_names()?57 .into_iter()58 .map(|hostname| Host {59 fleet_config: self.clone(),60 hostname,61 })62 .collect())63 }64}6566pub struct Host {67 pub fleet_config: FleetConfig,6869 pub hostname: String,70}7172impl Host {73 pub fn skip(&self) -> bool {74 self.fleet_config.0.opts.should_skip(&self.hostname)75 }76 pub fn is_local(&self) -> bool {77 self.fleet_config.0.opts.is_local(&self.hostname)78 }79 pub fn command_on(&self, cmd: impl AsRef<OsStr>, sudo: bool) -> Command {80 if !self.is_local() {81 let mut out = Command::new("ssh");82 out.arg(&self.hostname).arg("--");83 if sudo {84 out.arg("sudo");85 }86 out.arg(cmd);87 out88 } else if sudo {89 let mut out = Command::new("sudo");90 out.arg(cmd);91 out92 } else {93 Command::new(cmd)94 }95 }96}9798#[derive(Clap, Clone)]99#[clap(group = clap::ArgGroup::new("target_hosts"))]100pub struct FleetOpts {101 102 #[clap(long, number_of_values = 1, group = "target_hosts")]103 only: Vec<String>,104105 106 #[clap(long, number_of_values = 1, group = "target_hosts")]107 skip: Vec<String>,108109 110 #[clap(long)]111 pub localhost: Option<String>,112}113114impl FleetOpts {115 pub fn should_skip(&self, host: &str) -> bool {116 if self.skip.len() > 0 {117 self.skip.iter().find(|h| h as &str == host).is_some()118 } else if self.only.len() > 0 {119 self.only.iter().find(|h| h as &str == host).is_none()120 } else {121 false122 }123 }124 pub fn is_local(&self, host: &str) -> bool {125 self.localhost.as_ref().map(|s| &s as &str) == Some(host)126 }127 pub fn build(mut self) -> Result<FleetConfig> {128 if self.localhost.is_none() {129 self.localhost130 .replace(hostname::get().unwrap().to_str().unwrap().to_owned());131 }132 let directory = current_dir()?;133 Ok(FleetConfig(Arc::new(FleetConfigInternals {134 opts: self,135 directory,136 })))137 }138}