Skip to main content

gitee_cli_rs/cmd/
ssh_key.rs

1use std::io::Write;
2
3use super::{confirm, Ctx};
4use crate::cli::SshKeyCmd;
5use crate::error::{GiteeError, Result};
6use crate::out;
7
8pub fn execute(ctx: &Ctx, cmd: SshKeyCmd) -> Result<()> {
9    match cmd {
10        SshKeyCmd::List { limit } => {
11            let items = ctx.client.users().keys(limit.limit)?;
12            let mut out = std::io::stdout().lock();
13            ctx.out
14                .render(&mut out, &items, |w| out::ssh_key_table(w, &items))?;
15        }
16        SshKeyCmd::Add { pubkey_file, title } => {
17            let contents = std::fs::read_to_string(&pubkey_file)
18                .map_err(|e| GiteeError::Usage(format!("read {pubkey_file}: {e}")))?;
19            let key = contents.trim();
20            if key.is_empty() {
21                return Err(GiteeError::Usage("public key file is empty".into()));
22            }
23            let title = title.unwrap_or_else(|| default_key_title(key));
24            let created = ctx.client.users().add_key(key, &title)?;
25            let items = [created];
26            let mut out = std::io::stdout().lock();
27            ctx.out
28                .render(&mut out, &items, |w| out::ssh_key_table(w, &items))?;
29        }
30        SshKeyCmd::Delete { id, yes } => {
31            confirm(&format!("Delete SSH key {id}"), yes)?;
32            ctx.client.users().delete_key(id)?;
33            writeln!(std::io::stdout().lock(), "Deleted SSH key {id}")?;
34        }
35    }
36    Ok(())
37}
38
39fn default_key_title(key: &str) -> String {
40    // ssh pubkey line: <type> <blob> [comment...]
41    let mut parts = key.split_whitespace();
42    let _ty = parts.next();
43    let _blob = parts.next();
44    let comment: Vec<&str> = parts.collect();
45    if !comment.is_empty() {
46        return comment.join(" ");
47    }
48    let host = hostname();
49    let date = today();
50    format!("{host}-{date}")
51}
52
53fn hostname() -> String {
54    std::process::Command::new("hostname")
55        .output()
56        .ok()
57        .and_then(|o| String::from_utf8(o.stdout).ok())
58        .map(|s| s.trim().to_string())
59        .filter(|s| !s.is_empty())
60        .unwrap_or_else(|| "host".into())
61}
62
63fn today() -> String {
64    // Avoid extra chrono dep: local YYYY-MM-DD via date(1).
65    std::process::Command::new("date")
66        .arg("+%Y-%m-%d")
67        .output()
68        .ok()
69        .and_then(|o| String::from_utf8(o.stdout).ok())
70        .map(|s| s.trim().to_string())
71        .filter(|s| !s.is_empty())
72        .unwrap_or_else(|| "unknown-date".into())
73}
74
75#[cfg(test)]
76mod title_tests {
77    use super::default_key_title;
78
79    #[test]
80    fn uses_pubkey_comment_when_present() {
81        let title = default_key_title("ssh-ed25519 AAAAAcel comment@box");
82        assert_eq!(title, "comment@box");
83        let title = default_key_title("ssh-ed25519 AAAAAcel my laptop key");
84        assert_eq!(title, "my laptop key");
85    }
86}