Rust · 2603 bytes Raw Blame History
1 use anyhow::{Context, Result};
2 use clap::{Parser, Subcommand};
3 use garcard_ipc::{Command, Response, socket_path};
4 use std::io::{BufRead, BufReader, Write};
5 use std::os::unix::net::UnixStream;
6
7 #[derive(Parser, Debug)]
8 #[command(name = "garcardctl", about = "Control and inspect garcard daemon")]
9 struct Cli {
10 #[command(subcommand)]
11 command: Commands,
12 }
13
14 #[derive(Subcommand, Debug)]
15 enum Commands {
16 Ping,
17 Status,
18 Diagnose,
19 Version,
20 AuthSummary,
21 TempList,
22 TempRevoke { authorization_id: String },
23 TempRevokeAll,
24 Quit,
25 }
26
27 fn main() -> Result<()> {
28 let cli = Cli::parse();
29 let command = to_protocol_command(cli.command);
30 let response = send_command(&command)?;
31
32 if response.success {
33 if let Some(data) = response.data {
34 println!("{}", serde_json::to_string_pretty(&data)?);
35 } else {
36 println!("OK");
37 }
38 return Ok(());
39 }
40
41 let message = response
42 .error
43 .unwrap_or_else(|| "unknown daemon error".to_string());
44 eprintln!("Error: {}", message);
45 std::process::exit(1);
46 }
47
48 fn to_protocol_command(command: Commands) -> Command {
49 match command {
50 Commands::Ping => Command::Ping,
51 Commands::Status => Command::Status,
52 Commands::Diagnose => Command::Diagnose,
53 Commands::Version => Command::Version,
54 Commands::AuthSummary => Command::AuthSummary,
55 Commands::TempList => Command::TempList,
56 Commands::TempRevoke { authorization_id } => Command::TempRevoke { authorization_id },
57 Commands::TempRevokeAll => Command::TempRevokeAll,
58 Commands::Quit => Command::Quit,
59 }
60 }
61
62 fn send_command(command: &Command) -> Result<Response> {
63 let socket = socket_path();
64 let mut stream = UnixStream::connect(&socket).with_context(|| {
65 format!(
66 "failed to connect to garcard daemon at {} (is it running?)",
67 socket.display()
68 )
69 })?;
70
71 let request = serde_json::to_string(command).context("failed to serialize command")?;
72 writeln!(stream, "{}", request).context("failed to send command")?;
73 stream.flush().context("failed to flush command")?;
74
75 let mut reader = BufReader::new(stream);
76 let mut line = String::new();
77 let bytes = reader
78 .read_line(&mut line)
79 .context("failed to read daemon response")?;
80 if bytes == 0 {
81 anyhow::bail!("daemon closed socket before responding");
82 }
83
84 let response: Response =
85 serde_json::from_str(line.trim()).context("failed to decode daemon response")?;
86 Ok(response)
87 }
88