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

difftreelog

feat info subcommand

Yaroslav Bolyukin2021-10-01parent: #5fd92ba.patch.diff
in: trunk

4 files changed

addedcmds/fleet/src/cmds/info.rsdiffbeforeafterboth
--- /dev/null
+++ b/cmds/fleet/src/cmds/info.rs
@@ -0,0 +1,81 @@
+use std::collections::BTreeSet;
+
+use crate::host::Config;
+use anyhow::{ensure, Result};
+use structopt::StructOpt;
+
+#[derive(StructOpt)]
+pub struct Info {
+	#[structopt(long)]
+	json: bool,
+	#[structopt(subcommand)]
+	cmd: InfoCmd,
+}
+
+#[derive(StructOpt)]
+pub enum InfoCmd {
+	/// List hosts
+	ListHosts {
+		#[structopt(long)]
+		tagged: Vec<String>,
+	},
+	/// List ips
+	HostIps {
+		host: String,
+		#[structopt(long)]
+		external: bool,
+		#[structopt(long)]
+		internal: bool,
+	},
+}
+
+impl Info {
+	pub fn run(self, config: &Config) -> Result<()> {
+		let mut data = Vec::new();
+		match self.cmd {
+			InfoCmd::ListHosts { ref tagged } => {
+				'host: for host in config.list_hosts()? {
+					if !tagged.is_empty() {
+						let tags: Vec<String> = config.config_attr(&host, "tags")?;
+						for tag in tagged {
+							if !tags.contains(&tag) {
+								continue 'host;
+							}
+						}
+					}
+					data.push(host);
+				}
+			}
+			InfoCmd::HostIps {
+				host,
+				external,
+				internal,
+			} => {
+				ensure!(
+					external || internal,
+					"at leas one of --external or --internal must be set"
+				);
+				let mut out = <BTreeSet<String>>::new();
+				if external {
+					out.extend(config.config_attr::<Vec<String>>(&host, "network.externalIps")?);
+				}
+				if internal {
+					out.extend(config.config_attr::<Vec<String>>(&host, "network.internalIps")?);
+				}
+				for ip in out {
+					data.push(ip);
+				}
+			}
+		}
+
+		if self.json {
+			let v = serde_json::to_string_pretty(&data)?;
+			print!("{}", v);
+		} else {
+			for v in data {
+				println!("{}", v);
+			}
+		}
+		Ok(())
+	}
+}
modifiedcmds/fleet/src/cmds/mod.rsdiffbeforeafterboth
--- a/cmds/fleet/src/cmds/mod.rs
+++ b/cmds/fleet/src/cmds/mod.rs
@@ -1,2 +1,3 @@
 pub mod build_systems;
 pub mod secrets;
+pub mod info;
modifiedcmds/fleet/src/host.rsdiffbeforeafterboth
before · cmds/fleet/src/host.rs
1use std::{2	cell::{Ref, RefCell, RefMut},3	env::current_dir,4	ffi::{OsStr, OsString},5	ops::Deref,6	path::PathBuf,7	process::Command,8	sync::Arc,9};1011use anyhow::Result;12use structopt::clap::ArgGroup;13use structopt::StructOpt;1415use crate::{command::CommandExt, fleetdata::FleetData};1617pub struct FleetConfigInternals {18	pub directory: PathBuf,19	pub opts: FleetOpts,20	pub data: RefCell<FleetData>,21}2223#[derive(Clone)]24pub struct Config(Arc<FleetConfigInternals>);2526impl Deref for Config {27	type Target = FleetConfigInternals;2829	fn deref(&self) -> &Self::Target {30		&self.031	}32}3334impl Config {35	pub fn should_skip(&self, host: &str) -> bool {36		if !self.opts.skip.is_empty() {37			self.opts.skip.iter().any(|h| h as &str == host)38		} else if !self.opts.only.is_empty() {39			!self.opts.only.iter().any(|h| h as &str == host)40		} else {41			false42		}43	}44	pub fn is_local(&self, host: &str) -> bool {45		self.opts.localhost.as_ref().map(|s| s as &str) == Some(host)46	}4748	pub fn command_on(&self, host: &str, program: impl AsRef<OsStr>, sudo: bool) -> Command {49		if self.is_local(host) {50			if sudo {51				let mut cmd = Command::new("sudo");52				cmd.arg(program);53				cmd54			} else {55				Command::new(program)56			}57		} else {58			let mut cmd = Command::new("ssh");59			cmd.arg(host).arg("--");60			if sudo {61				cmd.arg("sudo");62			}63			cmd.arg(program);64			cmd65		}66	}6768	pub fn full_attr_name(&self, attr_name: &str) -> OsString {69		let mut str = self.directory.as_os_str().to_owned();70		str.push("#");71		str.push(attr_name);72		str73	}7475	pub fn list_hosts(&self) -> Result<Vec<String>> {76		Command::new("nix")77			.arg("eval")78			.arg(self.full_attr_name("fleetConfigurations.default.configuredHosts"))79			.args(&["--apply", "builtins.attrNames", "--json", "--show-trace"])80			.inherit_stdio()81			.run_json()82	}8384	pub fn data(&self) -> Ref<FleetData> {85		self.data.borrow()86	}87	pub fn data_mut(&self) -> RefMut<FleetData> {88		self.data.borrow_mut()89	}9091	pub fn save(&self) -> Result<()> {92		let mut fleet_data_path = self.directory.clone();93		fleet_data_path.push("fleet.nix");94		let data = nixlike::serialize(&self.data() as &FleetData)?;95		std::fs::write(96			fleet_data_path,97			format!(98				"# This file contains fleet state and shouldn't be edited by hand\n\n{}\n",99				data100			),101		)?;102		Ok(())103	}104}105106#[derive(StructOpt, Clone)]107#[structopt(group = ArgGroup::with_name("target_hosts"))]108pub struct FleetOpts {109	/// All hosts except those would be skipped110	#[structopt(long, number_of_values = 1, group = "target_hosts")]111	only: Vec<String>,112113	/// Hosts to skip114	#[structopt(long, number_of_values = 1, group = "target_hosts")]115	skip: Vec<String>,116117	/// Host, which should be threaten as current machine118	#[structopt(long)]119	pub localhost: Option<String>,120}121122impl FleetOpts {123	pub fn build(mut self) -> Result<Config> {124		if self.localhost.is_none() {125			self.localhost126				.replace(hostname::get().unwrap().to_str().unwrap().to_owned());127		}128		let directory = current_dir()?;129130		let mut fleet_data_path = directory.clone();131		fleet_data_path.push("fleet.nix");132		let bytes = std::fs::read_to_string(fleet_data_path)?;133		let data = nixlike::parse_str(&bytes)?;134135		Ok(Config(Arc::new(FleetConfigInternals {136			opts: self,137			directory,138			data,139		})))140	}141}
after · cmds/fleet/src/host.rs
1use std::{2	cell::{Ref, RefCell, RefMut},3	env::current_dir,4	ffi::{OsStr, OsString},5	ops::Deref,6	path::PathBuf,7	process::Command,8	sync::Arc,9};1011use anyhow::Result;12use serde::de::DeserializeOwned;13use structopt::clap::ArgGroup;14use structopt::StructOpt;1516use crate::{command::CommandExt, fleetdata::FleetData};1718pub struct FleetConfigInternals {19	pub directory: PathBuf,20	pub opts: FleetOpts,21	pub data: RefCell<FleetData>,22}2324#[derive(Clone)]25pub struct Config(Arc<FleetConfigInternals>);2627impl Deref for Config {28	type Target = FleetConfigInternals;2930	fn deref(&self) -> &Self::Target {31		&self.032	}33}3435impl Config {36	pub fn should_skip(&self, host: &str) -> bool {37		if !self.opts.skip.is_empty() {38			self.opts.skip.iter().any(|h| h as &str == host)39		} else if !self.opts.only.is_empty() {40			!self.opts.only.iter().any(|h| h as &str == host)41		} else {42			false43		}44	}45	pub fn is_local(&self, host: &str) -> bool {46		self.opts.localhost.as_ref().map(|s| s as &str) == Some(host)47	}4849	pub fn command_on(&self, host: &str, program: impl AsRef<OsStr>, sudo: bool) -> Command {50		if self.is_local(host) {51			if sudo {52				let mut cmd = Command::new("sudo");53				cmd.arg(program);54				cmd55			} else {56				Command::new(program)57			}58		} else {59			let mut cmd = Command::new("ssh");60			cmd.arg(host).arg("--");61			if sudo {62				cmd.arg("sudo");63			}64			cmd.arg(program);65			cmd66		}67	}6869	pub fn full_attr_name(&self, attr_name: &str) -> OsString {70		let mut str = self.directory.as_os_str().to_owned();71		str.push("#");72		str.push(attr_name);73		str74	}7576	pub fn list_hosts(&self) -> Result<Vec<String>> {77		Command::new("nix")78			.arg("eval")79			.arg(self.full_attr_name("fleetConfigurations.default.configuredHosts"))80			.args(&["--apply", "builtins.attrNames", "--json", "--show-trace"])81			.inherit_stdio()82			.run_json()83	}84	pub fn config_attr<T: DeserializeOwned>(&self, host: &str, attr: &str) -> Result<T> {85		Command::new("nix")86			.arg("eval")87			.arg(self.full_attr_name(&format!(88				"fleetConfigurations.default.configuredSystems.{}.config.{}",89				host, attr90			)))91			.args(&["--json", "--show-trace"])92			.inherit_stdio()93			.run_json()94	}9596	pub fn data(&self) -> Ref<FleetData> {97		self.data.borrow()98	}99	pub fn data_mut(&self) -> RefMut<FleetData> {100		self.data.borrow_mut()101	}102103	pub fn save(&self) -> Result<()> {104		let mut fleet_data_path = self.directory.clone();105		fleet_data_path.push("fleet.nix");106		let data = nixlike::serialize(&self.data() as &FleetData)?;107		std::fs::write(108			fleet_data_path,109			format!(110				"# This file contains fleet state and shouldn't be edited by hand\n\n{}\n",111				data112			),113		)?;114		Ok(())115	}116}117118#[derive(StructOpt, Clone)]119#[structopt(group = ArgGroup::with_name("target_hosts"))]120pub struct FleetOpts {121	/// All hosts except those would be skipped122	#[structopt(long, number_of_values = 1, group = "target_hosts")]123	only: Vec<String>,124125	/// Hosts to skip126	#[structopt(long, number_of_values = 1, group = "target_hosts")]127	skip: Vec<String>,128129	/// Host, which should be threaten as current machine130	#[structopt(long)]131	pub localhost: Option<String>,132}133134impl FleetOpts {135	pub fn build(mut self) -> Result<Config> {136		if self.localhost.is_none() {137			self.localhost138				.replace(hostname::get().unwrap().to_str().unwrap().to_owned());139		}140		let directory = current_dir()?;141142		let mut fleet_data_path = directory.clone();143		fleet_data_path.push("fleet.nix");144		let bytes = std::fs::read_to_string(fleet_data_path)?;145		let data = nixlike::parse_str(&bytes)?;146147		Ok(Config(Arc::new(FleetConfigInternals {148			opts: self,149			directory,150			data,151		})))152	}153}
modifiedcmds/fleet/src/main.rsdiffbeforeafterboth
--- a/cmds/fleet/src/main.rs
+++ b/cmds/fleet/src/main.rs
@@ -11,7 +11,7 @@
 use structopt::clap::AppSettings::*;
 use structopt::StructOpt;
 
-use cmds::{build_systems::BuildSystems, secrets::Secrets};
+use cmds::{build_systems::BuildSystems, info::Info, secrets::Secrets};
 use host::{Config, FleetOpts};
 
 #[derive(StructOpt)]
@@ -20,6 +20,8 @@
 	BuildSystems(BuildSystems),
 	/// Secret management
 	Secrets(Secrets),
+	/// Config parsing
+	Info(Info),
 }
 
 #[derive(StructOpt)]
@@ -40,6 +42,7 @@
 	match command {
 		Opts::BuildSystems(c) => c.run(config)?,
 		Opts::Secrets(s) => s.run(config)?,
+		Opts::Info(i) => i.run(config)?,
 	};
 	Ok(())
 }