From 841901368fd2337e9d7b4a08a3f94b51986c58a3 Mon Sep 17 00:00:00 2001 From: Maxime Van Hees Date: Tue, 15 Jul 2025 17:03:44 +0200 Subject: [PATCH] added functionality to list SSH key IDs --- src/async_handler.rs | 8 +++- src/hetzner_api.rs | 108 ++++++++++++++++++++++++++++++------------- src/rhai_api.rs | 39 +++++++++++++++- test.rhai | 22 +++++---- 4 files changed, 135 insertions(+), 42 deletions(-) diff --git a/src/async_handler.rs b/src/async_handler.rs index f6c32aa..e7b20c1 100644 --- a/src/async_handler.rs +++ b/src/async_handler.rs @@ -1,4 +1,4 @@ -use crate::hetzner_api::{HetznerClient, WrappedServer}; +use crate::hetzner_api::{HetznerClient, WrappedServer, WrappedSshKey}; use std::sync::mpsc::{Receiver, Sender}; use tokio::runtime::Builder; @@ -11,9 +11,11 @@ pub enum Request { ResetServer(HetznerClient, i64), EnableRescueMode(HetznerClient, i64, Vec), DisableRescueMode(HetznerClient, i64), + ListSshKeys(HetznerClient), } pub enum Response { + ListSshKeys(Result, String>), ListServers(Result, String>), GetServerStatus(Result), GetServer(Result), @@ -63,6 +65,10 @@ pub fn run_worker( let result = rt.block_on(client.disable_rescue_mode_for_server(server_id)).map_err(|e| e.to_string()); Response::DisableRescueMode(result) } + Request::ListSshKeys(client) => { + let result = rt.block_on(client.list_ssh_keys()).map_err(|e| e.to_string()); + Response::ListSshKeys(result) + } }; reply_tx.send(response).expect("Failed to send response"); } diff --git a/src/hetzner_api.rs b/src/hetzner_api.rs index efc8a17..2546708 100644 --- a/src/hetzner_api.rs +++ b/src/hetzner_api.rs @@ -1,5 +1,12 @@ -use hcloud::apis::{configuration::Configuration, servers_api::{self, ListServersParams, ResetServerParams, EnableRescueModeForServerParams, DisableRescueModeForServerParams}}; -use hcloud::models::{Server, EnableRescueModeForServerRequest}; +use hcloud::apis::{ + configuration::Configuration, + servers_api::{ + self, DisableRescueModeForServerParams, EnableRescueModeForServerParams, ListServersParams, + ResetServerParams, + }, + ssh_keys_api::{self, ListSshKeysParams}, +}; +use hcloud::models::{EnableRescueModeForServerRequest, Server, SshKey}; #[derive(Debug, Clone)] pub struct HetznerClient { @@ -9,6 +16,9 @@ pub struct HetznerClient { #[derive(Clone)] pub struct WrappedServer(pub Server); +#[derive(Clone)] +pub struct WrappedSshKey(pub SshKey); + impl HetznerClient { pub fn new(api_token: &str) -> Self { let mut configuration = Configuration::new(); @@ -19,7 +29,6 @@ impl HetznerClient { .unwrap(); configuration.client = client; - Self { configuration } } @@ -36,11 +45,12 @@ impl HetznerClient { }; let response = servers_api::list_servers(&self.configuration, params).await?; - let mut servers: Vec = response.servers.into_iter().map(WrappedServer).collect(); + let mut servers: Vec = + response.servers.into_iter().map(WrappedServer).collect(); let is_empty = servers.is_empty(); all_servers.append(&mut servers); - + if is_empty || response.meta.pagination.next_page.is_none() { break; } @@ -51,12 +61,12 @@ impl HetznerClient { Ok(all_servers) } - pub async fn get_server_status(&self, server_id: i64) -> Result> { - let params = servers_api::GetServerParams { - id: server_id, - }; - let server_response = servers_api::get_server(&self.configuration, params) - .await?; + pub async fn get_server_status( + &self, + server_id: i64, + ) -> Result> { + let params = servers_api::GetServerParams { id: server_id }; + let server_response = servers_api::get_server(&self.configuration, params).await?; if let Some(server) = server_response.server { Ok(format!("{:?}", server.status)) @@ -65,12 +75,12 @@ impl HetznerClient { } } - pub async fn get_server(&self, server_id: i64) -> Result> { - let params = servers_api::GetServerParams { - id: server_id, - }; - let server_response = servers_api::get_server(&self.configuration, params) - .await?; + pub async fn get_server( + &self, + server_id: i64, + ) -> Result> { + let params = servers_api::GetServerParams { id: server_id }; + let server_response = servers_api::get_server(&self.configuration, params).await?; if let Some(server) = server_response.server { Ok(WrappedServer(*server)) @@ -80,37 +90,73 @@ impl HetznerClient { } pub async fn reboot_server(&self, server_id: i64) -> Result<(), Box> { - let params = servers_api::SoftRebootServerParams { - id: server_id, - }; + let params = servers_api::SoftRebootServerParams { id: server_id }; servers_api::soft_reboot_server(&self.configuration, params).await?; Ok(()) } pub async fn reset_server(&self, server_id: i64) -> Result<(), Box> { - let params = ResetServerParams { - id: server_id, - }; + let params = ResetServerParams { id: server_id }; servers_api::reset_server(&self.configuration, params).await?; Ok(()) } - pub async fn enable_rescue_mode_for_server(&self, server_id: i64, ssh_keys: Vec) -> Result> { + pub async fn enable_rescue_mode_for_server( + &self, + server_id: i64, + ssh_keys: Vec, + ) -> Result> { let params = EnableRescueModeForServerParams { id: server_id, enable_rescue_mode_for_server_request: Some(EnableRescueModeForServerRequest { - ssh_keys: if ssh_keys.is_empty() { None } else { Some(ssh_keys) }, + ssh_keys: if ssh_keys.is_empty() { + None + } else { + Some(ssh_keys) + }, r#type: Some(hcloud::models::enable_rescue_mode_for_server_request::Type::Linux64), }), }; - let response = servers_api::enable_rescue_mode_for_server(&self.configuration, params).await?; - Ok(response.root_password.expect("Unable to fetch root_password from enabling rescue mode for server response")) + let response = + servers_api::enable_rescue_mode_for_server(&self.configuration, params).await?; + Ok(response + .root_password + .expect("Unable to fetch root_password from enabling rescue mode for server response")) } - pub async fn disable_rescue_mode_for_server(&self, server_id: i64) -> Result<(), Box> { - let params = DisableRescueModeForServerParams { - id: server_id, - }; + pub async fn disable_rescue_mode_for_server( + &self, + server_id: i64, + ) -> Result<(), Box> { + let params = DisableRescueModeForServerParams { id: server_id }; servers_api::disable_rescue_mode_for_server(&self.configuration, params).await?; Ok(()) } + pub async fn list_ssh_keys(&self) -> Result, Box> { + let mut all_keys = Vec::new(); + let mut page = 1; + let per_page = 50; + + loop { + let params = ListSshKeysParams { + page: Some(page), + per_page: Some(per_page), + ..Default::default() + }; + + let response = ssh_keys_api::list_ssh_keys(&self.configuration, params).await?; + let mut keys: Vec = + response.ssh_keys.into_iter().map(WrappedSshKey).collect(); + let is_empty = keys.is_empty(); + + all_keys.append(&mut keys); + + if is_empty || response.meta.pagination.next_page.is_none() { + break; + } + + page += 1; + } + + Ok(all_keys) + } } diff --git a/src/rhai_api.rs b/src/rhai_api.rs index 2558ef3..011de3b 100644 --- a/src/rhai_api.rs +++ b/src/rhai_api.rs @@ -1,6 +1,6 @@ use crate::async_handler::Response; use crate::async_handler::Request; -use crate::hetzner_api::{HetznerClient, WrappedServer}; +use crate::hetzner_api::{HetznerClient, WrappedServer, WrappedSshKey}; use rhai::{Engine, EvalAltResult}; use std::env; use std::sync::{mpsc::{Receiver, Sender}, Arc, Mutex}; @@ -52,6 +52,15 @@ pub fn register_hetzner_api( }) } }) + .register_fn("list_ssh_keys", { + let bridge = api_bridge.clone(); + move |client: &mut HetznerClient| { + bridge.call(Request::ListSshKeys(client.clone()), |response| match response { + Response::ListSshKeys(result) => result.map_err(|e| e.into()), + _ => Err("Unexpected response".into()), + }) + } + }) .register_fn("get_server_status", { let bridge = api_bridge.clone(); move |client: &mut HetznerClient, server_id: i64| { @@ -197,4 +206,32 @@ pub fn register_hetzner_api( engine.register_fn("get_env", |key: &str| -> String { env::var(key).unwrap_or("".to_string()) }); + + engine + .register_type_with_name::("SshKey") + .register_get("id", |key: &mut WrappedSshKey| key.0.id) + .register_get("name", |key: &mut WrappedSshKey| key.0.name.clone()) + .register_get("fingerprint", |key: &mut WrappedSshKey| key.0.fingerprint.clone()); + + engine + .register_iterator::>() + .register_fn("len", |list: &mut Vec| list.len() as i64) + .register_indexer_get(|list: &mut Vec, index: i64| list[index as usize].clone()) + .register_fn("show_table", |keys: &mut Vec| -> Result> { + let mut table = Table::new(); + table.set_titles(Row::new(vec![Cell::new("SSH Keys").style_spec("c")])); + table.add_row(Row::new(vec![ + Cell::new("ID"), + Cell::new("Name"), + Cell::new("Fingerprint"), + ])); + for key in keys { + table.add_row(Row::new(vec![ + Cell::new(&key.0.id.to_string()), + Cell::new(&key.0.name), + Cell::new(&key.0.fingerprint), + ])); + } + Ok(table.to_string()) + }); } \ No newline at end of file diff --git a/test.rhai b/test.rhai index 76d9667..39a46f0 100644 --- a/test.rhai +++ b/test.rhai @@ -5,6 +5,10 @@ let client = new_hetzner_client(HETZNER_API_TOKEN); print("Listing all servers..."); let servers = client.list_servers(); print(servers.show_table()); +// List all SSH keys and print in table +print("Listing all SSH keys..."); +let ssh_keys = client.list_ssh_keys(); +print(ssh_keys.show_table()); // Get server through ID and print details in table print("Listing details from server with ID 104301883..."); @@ -12,19 +16,19 @@ let test_server = client.get_server(104301883); print(test_server.show_details()); // Enable rescue mode flag on server -print(`Enabling rescue mode on server with ID: ${test_server.id}`); -let root_password = client.enable_rescue_mode(test_server.id, 1337); -print(`Root password is: ${root_password}`); +// print(`Enabling rescue mode on server with ID: ${test_server.id}`); +// let root_password = client.enable_rescue_mode(test_server.id, 1337); +// print(`Root password is: ${root_password}`); // Enable rescue mode with multiple keys from array -let ssh_keys = [123, 456, 789]; +let ssh_keys = [100324992, 100325001]; let root_password = client.enable_rescue_mode(test_server.id, ssh_keys); -// read SSH key from env var -let ssh_key_from_env = get_env("SSH_KEY_ID"); -if ssh_key_from_env != "" { - client.enable_rescue_mode(test_server.id, ssh_key_from_env.parse_int()); -} +// // read SSH key from env var and use it for rescue mode +// let ssh_key_from_env = get_env("SSH_KEY_ID"); +// if ssh_key_from_env != "" { +// client.enable_rescue_mode(test_server.id, ssh_key_from_env.parse_int()); +// } // Disable rescue mode flag on server print(`Disabling rescue mode on server with ID: ${test_server.id}`);