From ee900729b4988c1d940367368ea7823ccf956b26 Mon Sep 17 00:00:00 2001 From: marshmallow Date: Sun, 26 Oct 2025 16:15:43 +1100 Subject: [PATCH] update `show` command to be nicer --- CHANGELOG.md | 1 + Cargo.lock | 1 + Cargo.toml | 1 + wire/cli/Cargo.toml | 2 +- wire/cli/src/main.rs | 2 +- wire/lib/Cargo.toml | 1 + wire/lib/src/hive/mod.rs | 71 +++++++++++++++++++++++++++++++++ wire/lib/src/hive/node.rs | 14 +++++++ wire/lib/src/hive/steps/keys.rs | 29 +++++++++++--- 9 files changed, 115 insertions(+), 7 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 81ce09c..adff736 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -39,6 +39,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 the .drv is required - A node which is going to be applied locally will now never `push` or `cleanup`. +- `show` subcommand looks nicer now. ### Documented diff --git a/Cargo.lock b/Cargo.lock index 2127ee1..2cb7cfe 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -952,6 +952,7 @@ dependencies = [ "nix 0.30.1", "nix-compat", "num_enum", + "owo-colors", "portable-pty", "proc-macro2", "prost", diff --git a/Cargo.toml b/Cargo.toml index 2ac871f..2482693 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -38,3 +38,4 @@ nix-compat = { git = "https://git.snix.dev/snix/snix.git", features = [ # "hints", # ] } serde_json = { version = "1.0.145" } +owo-colors = { version = "4.2.3", features = ["supports-colors"] } diff --git a/wire/cli/Cargo.toml b/wire/cli/Cargo.toml index 7a4038a..d6bf108 100644 --- a/wire/cli/Cargo.toml +++ b/wire/cli/Cargo.toml @@ -27,4 +27,4 @@ itertools = "0.14.0" dhat = "0.3.2" clap_complete = "4.5.59" nix-compat = { workspace = true } -owo-colors = { version = "4.2.3", features = ["supports-colors"] } +owo-colors = { workspace = true } diff --git a/wire/cli/src/main.rs b/wire/cli/src/main.rs index aec32a7..0206fc7 100644 --- a/wire/cli/src/main.rs +++ b/wire/cli/src/main.rs @@ -62,7 +62,7 @@ async fn main() -> Result<()> { serde_json::to_string(&hive).into_diagnostic()? } else { warn!("use --json to output something scripting suitable"); - format!("{hive:#?}") + format!("{hive}") } }), cli::Commands::Completions { shell } => { diff --git a/wire/lib/Cargo.toml b/wire/lib/Cargo.toml index a06fd61..cde1265 100644 --- a/wire/lib/Cargo.toml +++ b/wire/lib/Cargo.toml @@ -33,6 +33,7 @@ strip-ansi-escapes = "0.2.1" aho-corasick = "1.1.3" num_enum = "0.7.5" gjson = "0.8.1" +owo-colors = { workspace = true } [dev-dependencies] tempdir = "0.3" diff --git a/wire/lib/src/hive/mod.rs b/wire/lib/src/hive/mod.rs index 004acd6..3adeb57 100644 --- a/wire/lib/src/hive/mod.rs +++ b/wire/lib/src/hive/mod.rs @@ -1,13 +1,16 @@ // SPDX-License-Identifier: AGPL-3.0-or-later // Copyright 2024-2025 wire Contributors +use itertools::Itertools; use nix_compat::flakeref::FlakeRef; use node::{Name, Node}; +use owo_colors::{OwoColorize, Stream}; use serde::de::Error; use serde::{Deserialize, Deserializer, Serialize}; use std::collections::HashMap; use std::collections::hash_map::OccupiedEntry; use std::ffi::OsStr; +use std::fmt::Display; use std::fs; use std::path::PathBuf; use std::str::FromStr; @@ -82,6 +85,74 @@ impl Hive { } } +impl Display for Hive { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + for (name, node) in &self.nodes { + writeln!( + f, + "Node {} {}:\n", + name.bold(), + format!("({})", node.host_platform) + .italic() + .if_supports_color(Stream::Stdout, |x| x.dimmed()), + )?; + + if !node.tags.is_empty() { + write!(f, " > {}", "Tags:".bold())?; + writeln!(f, " {:?}", node.tags)?; + } + + write!(f, " > {}", "Connection:".bold())?; + writeln!(f, " {{{}}}", node.target)?; + + write!(f, " > {} {}{}", "Build remotely".bold(), "`deployment.buildOnTarget`".if_supports_color(Stream::Stdout, |x| x.dimmed()).italic(), ":".bold())?; + writeln!(f, " {}", node.build_remotely)?; + + write!(f, " > {} {}{}", "Local apply allowed".bold(), "`deployment.allowLocalDeployment`".if_supports_color(Stream::Stdout, |x| x.dimmed()).italic(), ":".bold())?; + writeln!(f, " {}", node.allow_local_deployment)?; + + if !node.keys.is_empty() { + write!(f, " > {}", "Keys:".bold())?; + writeln!(f, " {} key(s)", node.keys.len())?; + + for key in &node.keys { + writeln!(f, " > {key}")?; + } + } + + writeln!(f)?; + } + + let total_keys = self + .nodes + .values() + .flat_map(|node| node.keys.iter()) + .collect::>(); + let distinct_keys = self + .nodes + .values() + .flat_map(|node| node.keys.iter()) + .unique() + .collect::>() + .len(); + + write!(f, "{}", "Summary:".bold())?; + writeln!( + f, + " {} total node(s), totalling {} keys ({distinct_keys} distinct).", + self.nodes.len(), + total_keys.len() + )?; + writeln!( + f, + "{}", + "Note: Listed connections are tried from Left to Right".italic(), + )?; + + Ok(()) + } +} + #[derive(Debug, PartialEq, Eq)] pub enum HiveLocation { HiveNix(PathBuf), diff --git a/wire/lib/src/hive/node.rs b/wire/lib/src/hive/node.rs index 6d04d33..a6d2a4e 100644 --- a/wire/lib/src/hive/node.rs +++ b/wire/lib/src/hive/node.rs @@ -170,6 +170,20 @@ impl Target { } } +impl Display for Target { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let hosts = itertools::Itertools::join( + &mut self + .hosts + .iter() + .map(|host| format!("{}@{host}:{}", self.user, self.port)), + ", ", + ); + + write!(f, "{hosts}") + } +} + #[derive(Serialize, Deserialize, Clone, Debug, Eq, PartialEq, Hash)] pub struct Node { #[serde(rename = "target")] diff --git a/wire/lib/src/hive/steps/keys.rs b/wire/lib/src/hive/steps/keys.rs index d5ec4b1..ed9d77f 100644 --- a/wire/lib/src/hive/steps/keys.rs +++ b/wire/lib/src/hive/steps/keys.rs @@ -5,6 +5,7 @@ use base64::Engine; use base64::prelude::BASE64_STANDARD; use futures::future::join_all; use itertools::{Itertools, Position}; +use owo_colors::OwoColorize; use prost::Message; use prost::bytes::BytesMut; use serde::{Deserialize, Serialize}; @@ -12,7 +13,7 @@ use sha2::{Digest, Sha256}; use std::env; use std::fmt::Display; use std::io::Cursor; -use std::path::PathBuf; +use std::path::{PathBuf}; use std::pin::Pin; use std::process::Stdio; use std::str::from_utf8; @@ -62,6 +63,27 @@ pub struct Key { pub environment: im::HashMap, } +impl Display for Key { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!( + f, + "{} {} {}:{} {}", + match self.source { + Source::String(_) => "Literal", + Source::Path(_) => "Path", + Source::Command(_) => "Command", + }.if_supports_color(owo_colors::Stream::Stdout, |x| x.dimmed()), + [self.dest_dir.clone(), self.name.clone()] + .iter() + .collect::() + .display(), + self.user, + self.group, + self.permissions, + ) + } +} + fn get_u32_permission(key: &Key) -> Result { u32::from_str_radix(&key.permissions, 8).map_err(KeyError::ParseKeyPermissions) } @@ -114,10 +136,7 @@ async fn process_key(key: &Key) -> Result<(key_agent::keys::KeySpec, Vec), K let destination: PathBuf = [key.dest_dir.clone(), key.name.clone()].iter().collect(); - debug!( - "Staging push to {}", - destination.clone().into_os_string().into_string().unwrap() - ); + debug!("Staging push to {}", destination.clone().display()); Ok(( key_agent::keys::KeySpec { -- 2.51.2