difftreelog
feat info subcommand
in: trunk
4 files changed
cmds/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(())
+ }
+}
cmds/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;
cmds/fleet/src/host.rsdiffbeforeafterboth1use 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}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}cmds/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(())
}