integrated hetzner client in repo + showcase of using scope for 'cleaner' scripts
This commit is contained in:
		
							
								
								
									
										10
									
								
								Cargo.toml
									
									
									
									
									
								
							
							
						
						
									
										10
									
								
								Cargo.toml
									
									
									
									
									
								
							| @@ -26,6 +26,7 @@ members = [ | ||||
|     "packages/system/virt", | ||||
|     "rhai", | ||||
|     "herodo", | ||||
|     "packages/clients/hetznerclient", | ||||
| ] | ||||
| resolver = "2" | ||||
|  | ||||
| @@ -47,7 +48,7 @@ log = "0.4" | ||||
| once_cell = "1.18.0" | ||||
| rand = "0.8.5" | ||||
| regex = "1.8.1" | ||||
| reqwest = { version = "0.12.15", features = ["json"] } | ||||
| reqwest = { version = "0.12.15", features = ["json", "blocking"] } | ||||
| rhai = { version = "1.12.0", features = ["sync"] } | ||||
| serde = { version = "1.0", features = ["derive"] } | ||||
| serde_json = "1.0" | ||||
| @@ -102,6 +103,7 @@ sal-git = { path = "packages/system/git" } | ||||
| sal-kubernetes = { path = "packages/system/kubernetes" } | ||||
| sal-redisclient = { path = "packages/clients/redisclient" } | ||||
| sal-mycelium = { path = "packages/clients/myceliumclient" } | ||||
| sal-hetzner = { path = "packages/clients/hetznerclient" } | ||||
| sal-text = { path = "packages/core/text" } | ||||
| sal-os = { path = "packages/system/os" } | ||||
| sal-net = { path = "packages/core/net" } | ||||
| @@ -122,6 +124,7 @@ sal-git = { workspace = true, optional = true } | ||||
| sal-kubernetes = { workspace = true, optional = true } | ||||
| sal-redisclient = { workspace = true, optional = true } | ||||
| sal-mycelium = { workspace = true, optional = true } | ||||
| sal-hetzner = { workspace = true, optional = true } | ||||
| sal-text = { workspace = true, optional = true } | ||||
| sal-os = { workspace = true, optional = true } | ||||
| sal-net = { workspace = true, optional = true } | ||||
| @@ -141,6 +144,7 @@ git = ["dep:sal-git"] | ||||
| kubernetes = ["dep:sal-kubernetes"] | ||||
| redisclient = ["dep:sal-redisclient"] | ||||
| mycelium = ["dep:sal-mycelium"] | ||||
| hetzner = ["dep:sal-hetzner"] | ||||
| text = ["dep:sal-text"] | ||||
| os = ["dep:sal-os"] | ||||
| net = ["dep:sal-net"] | ||||
| @@ -154,7 +158,7 @@ rhai = ["dep:sal-rhai"] | ||||
|  | ||||
| # Convenience feature groups | ||||
| core = ["os", "process", "text", "net"] | ||||
| clients = ["redisclient", "postgresclient", "zinit_client", "mycelium"] | ||||
| clients = ["redisclient", "postgresclient", "zinit_client", "mycelium", "hetzner"] | ||||
| infrastructure = ["git", "vault", "kubernetes", "virt"] | ||||
| scripting = ["rhai"] | ||||
| all = [ | ||||
| @@ -162,6 +166,7 @@ all = [ | ||||
|     "kubernetes", | ||||
|     "redisclient", | ||||
|     "mycelium", | ||||
|     "hetzner", | ||||
|     "text", | ||||
|     "os", | ||||
|     "net", | ||||
| @@ -188,4 +193,3 @@ required-features = ["kubernetes"] | ||||
| name = "generic_cluster" | ||||
| path = "examples/kubernetes/clusters/generic.rs" | ||||
| required-features = ["kubernetes"] | ||||
|  | ||||
|   | ||||
| @@ -3,7 +3,7 @@ | ||||
| //! This library loads the Rhai engine, registers all SAL modules, | ||||
| //! and executes Rhai scripts from a specified directory in sorted order. | ||||
|  | ||||
| use rhai::Engine; | ||||
| use rhai::{Engine, Scope}; | ||||
| use std::error::Error; | ||||
| use std::fs; | ||||
| use std::path::{Path, PathBuf}; | ||||
| @@ -29,6 +29,17 @@ pub fn run(script_path: &str) -> Result<(), Box<dyn Error>> { | ||||
|  | ||||
|     // Create a new Rhai engine | ||||
|     let mut engine = Engine::new(); | ||||
|      | ||||
|     // TODO: if we create a scope here we could clean up all the different functionsand types regsitered wit the engine | ||||
|     // We should generalize the way we add things to the scope for each module sepeartely | ||||
|     let mut scope = Scope::new(); | ||||
|     // TODO: this should be done for the other clients as well (but not here of course, in each module) | ||||
|     let hetzner_client = sal::hetzner::api::Client::new(sal::hetzner::config::Config::from_env().unwrap()); | ||||
|     scope.push("hetzner", hetzner_client); | ||||
|     // This makes it easy to call e.g. `hetzner.get_server()` or `mycelium.get_connected_peers()` | ||||
|     // --> without the need of manually created a client for each one first | ||||
|     // --> could be conditionally compiled to only use those who we need (we only push the things to the scope that we actually need to run the script) | ||||
|  | ||||
|  | ||||
|     // Register println function for output | ||||
|     engine.register_fn("println", |s: &str| println!("{}", s)); | ||||
| @@ -78,19 +89,20 @@ pub fn run(script_path: &str) -> Result<(), Box<dyn Error>> { | ||||
|         let script = fs::read_to_string(&script_file)?; | ||||
|  | ||||
|         // Execute the script | ||||
|         match engine.eval::<rhai::Dynamic>(&script) { | ||||
|             Ok(result) => { | ||||
|                 println!("Script executed successfully"); | ||||
|                 if !result.is_unit() { | ||||
|                     println!("Result: {}", result); | ||||
|                 } | ||||
|             } | ||||
|             Err(err) => { | ||||
|                 eprintln!("Error executing script: {}", err); | ||||
|                 // Exit with error code when a script fails | ||||
|                 process::exit(1); | ||||
|             } | ||||
|         } | ||||
|         // match engine.eval::<rhai::Dynamic>(&script) { | ||||
|         //     Ok(result) => { | ||||
|         //         println!("Script executed successfully"); | ||||
|         //         if !result.is_unit() { | ||||
|         //             println!("Result: {}", result); | ||||
|         //         } | ||||
|         //     } | ||||
|         //     Err(err) => { | ||||
|         //         eprintln!("Error executing script: {}", err); | ||||
|         //         // Exit with error code when a script fails | ||||
|         //         process::exit(1); | ||||
|         //     } | ||||
|         // } | ||||
|         engine.run_with_scope(&mut scope, &script)?; | ||||
|     } | ||||
|  | ||||
|     println!("\nAll scripts executed successfully!"); | ||||
|   | ||||
							
								
								
									
										12
									
								
								packages/clients/hetznerclient/Cargo.toml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										12
									
								
								packages/clients/hetznerclient/Cargo.toml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,12 @@ | ||||
| [package] | ||||
| name = "sal-hetzner" | ||||
| version = "0.1.0" | ||||
| edition = "2024" | ||||
|  | ||||
| [dependencies] | ||||
| prettytable = "0.10.0" | ||||
| reqwest.workspace = true | ||||
| rhai = { workspace = true, features = ["serde"] } | ||||
| serde = { workspace = true, features = ["derive"] } | ||||
| serde_json.workspace = true | ||||
| thiserror.workspace = true | ||||
							
								
								
									
										54
									
								
								packages/clients/hetznerclient/src/api/error.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										54
									
								
								packages/clients/hetznerclient/src/api/error.rs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,54 @@ | ||||
| use std::fmt; | ||||
|  | ||||
| use serde::Deserialize; | ||||
| use thiserror::Error; | ||||
|  | ||||
| #[derive(Debug, Error)] | ||||
| pub enum AppError { | ||||
|     #[error("Request failed: {0}")] | ||||
|     RequestError(#[from] reqwest::Error), | ||||
|     #[error("API error: {0}")] | ||||
|     ApiError(ApiError), | ||||
|     #[error("Deserialization Error: {0:?}")] | ||||
|     SerdeJsonError(#[from] serde_json::Error), | ||||
| } | ||||
|  | ||||
| #[derive(Debug, Deserialize)] | ||||
| pub struct ApiError { | ||||
|     pub status: u16, | ||||
|     pub message: String, | ||||
| } | ||||
|  | ||||
| impl From<reqwest::blocking::Response> for ApiError { | ||||
|     fn from(value: reqwest::blocking::Response) -> Self { | ||||
|         ApiError { | ||||
|             status: value.status().into(), | ||||
|             message: value.text().unwrap_or("The API call returned an error.".to_string()), | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
| impl fmt::Display for ApiError { | ||||
|     fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { | ||||
|         #[derive(Deserialize)] | ||||
|         struct HetznerApiError { | ||||
|             code: String, | ||||
|             message: String, | ||||
|         } | ||||
|  | ||||
|         #[derive(Deserialize)] | ||||
|         struct HetznerApiErrorWrapper { | ||||
|             error: HetznerApiError, | ||||
|         } | ||||
|  | ||||
|         if let Ok(wrapper) = serde_json::from_str::<HetznerApiErrorWrapper>(&self.message) { | ||||
|             write!( | ||||
|                 f, | ||||
|                 "Status: {}, Code: {}, Message: {}", | ||||
|                 self.status, wrapper.error.code, wrapper.error.message | ||||
|             ) | ||||
|         } else { | ||||
|             write!(f, "Status: {}: {}", self.status, self.message) | ||||
|         } | ||||
|     } | ||||
| } | ||||
							
								
								
									
										513
									
								
								packages/clients/hetznerclient/src/api/mod.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										513
									
								
								packages/clients/hetznerclient/src/api/mod.rs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,513 @@ | ||||
| pub mod error; | ||||
| pub mod models; | ||||
|  | ||||
| use self::models::{ | ||||
|     Boot, Rescue, Server, SshKey, ServerAddonProduct, ServerAddonProductWrapper, | ||||
|     AuctionServerProduct, AuctionServerProductWrapper, AuctionTransaction, | ||||
|     AuctionTransactionWrapper, BootWrapper, Cancellation, CancellationWrapper, | ||||
|     OrderServerBuilder, OrderServerProduct, OrderServerProductWrapper, RescueWrapped, | ||||
|     ServerWrapper, SshKeyWrapper, Transaction, TransactionWrapper, | ||||
|     ServerAddonTransaction, ServerAddonTransactionWrapper, | ||||
|     OrderServerAddonBuilder, | ||||
| }; | ||||
| use crate::api::error::ApiError; | ||||
| use crate::config::Config; | ||||
| use error::AppError; | ||||
| use reqwest::blocking::Client as HttpClient; | ||||
| use serde_json::json; | ||||
|  | ||||
| #[derive(Clone)] | ||||
| pub struct Client { | ||||
|     http_client: HttpClient, | ||||
|     config: Config, | ||||
| } | ||||
|  | ||||
| impl Client { | ||||
|     pub fn new(config: Config) -> Self { | ||||
|         Self { | ||||
|             http_client: HttpClient::new(), | ||||
|             config, | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     fn handle_response<T>(&self, response: reqwest::blocking::Response) -> Result<T, AppError> | ||||
|     where | ||||
|         T: serde::de::DeserializeOwned, | ||||
|     { | ||||
|         let status = response.status(); | ||||
|         let body = response.text()?; | ||||
|  | ||||
|         if status.is_success() { | ||||
|             serde_json::from_str::<T>(&body).map_err(Into::into) | ||||
|         } else { | ||||
|             Err(AppError::ApiError(ApiError { | ||||
|                 status: status.as_u16(), | ||||
|                 message: body, | ||||
|             })) | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     pub fn get_server(&self, server_number: i32) -> Result<Server, AppError> { | ||||
|         let response = self | ||||
|             .http_client | ||||
|             .get(format!("{}/server/{}", self.config.api_url, server_number)) | ||||
|             .basic_auth(&self.config.username, Some(&self.config.password)) | ||||
|             .send()?; | ||||
|  | ||||
|         let wrapped: ServerWrapper = self.handle_response(response)?; | ||||
|         Ok(wrapped.server) | ||||
|     } | ||||
|  | ||||
|     pub fn get_servers(&self) -> Result<Vec<Server>, AppError> { | ||||
|         let response = self | ||||
|             .http_client | ||||
|             .get(format!("{}/server", self.config.api_url)) | ||||
|             .basic_auth(&self.config.username, Some(&self.config.password)) | ||||
|             .send()?; | ||||
|  | ||||
|         let wrapped: Vec<ServerWrapper> = self.handle_response(response)?; | ||||
|         let servers = wrapped.into_iter().map(|sw| sw.server).collect(); | ||||
|         Ok(servers) | ||||
|     } | ||||
|  | ||||
|     pub fn update_server_name(&self, server_number: i32, name: &str) -> Result<Server, AppError> { | ||||
|         let params = [("server_name", name)]; | ||||
|         let response = self | ||||
|             .http_client | ||||
|             .post(format!("{}/server/{}", self.config.api_url, server_number)) | ||||
|             .basic_auth(&self.config.username, Some(&self.config.password)) | ||||
|             .form(¶ms) | ||||
|             .send()?; | ||||
|  | ||||
|         let wrapped: ServerWrapper = self.handle_response(response)?; | ||||
|         Ok(wrapped.server) | ||||
|     } | ||||
|  | ||||
|     pub fn get_cancellation_data(&self, server_number: i32) -> Result<Cancellation, AppError> { | ||||
|         let response = self | ||||
|             .http_client | ||||
|             .get(format!( | ||||
|                 "{}/server/{}/cancellation", | ||||
|                 self.config.api_url, server_number | ||||
|             )) | ||||
|             .basic_auth(&self.config.username, Some(&self.config.password)) | ||||
|             .send()?; | ||||
|  | ||||
|         let wrapped: CancellationWrapper = self.handle_response(response)?; | ||||
|         Ok(wrapped.cancellation) | ||||
|     } | ||||
|  | ||||
|     pub fn cancel_server( | ||||
|         &self, | ||||
|         server_number: i32, | ||||
|         cancellation_date: &str, | ||||
|     ) -> Result<Cancellation, AppError> { | ||||
|         let params = [("cancellation_date", cancellation_date)]; | ||||
|         let response = self | ||||
|             .http_client | ||||
|             .post(format!( | ||||
|                 "{}/server/{}/cancellation", | ||||
|                 self.config.api_url, server_number | ||||
|             )) | ||||
|             .basic_auth(&self.config.username, Some(&self.config.password)) | ||||
|             .form(¶ms) | ||||
|             .send()?; | ||||
|  | ||||
|         let wrapped: CancellationWrapper = self.handle_response(response)?; | ||||
|         Ok(wrapped.cancellation) | ||||
|     } | ||||
|  | ||||
|     pub fn withdraw_cancellation(&self, server_number: i32) -> Result<(), AppError> { | ||||
|         self.http_client | ||||
|             .delete(format!( | ||||
|                 "{}/server/{}/cancellation", | ||||
|                 self.config.api_url, server_number | ||||
|             )) | ||||
|             .basic_auth(&self.config.username, Some(&self.config.password)) | ||||
|             .send()?; | ||||
|  | ||||
|         Ok(()) | ||||
|     } | ||||
|  | ||||
|     pub fn get_ssh_keys(&self) -> Result<Vec<SshKey>, AppError> { | ||||
|         let response = self | ||||
|             .http_client | ||||
|             .get(format!("{}/key", self.config.api_url)) | ||||
|             .basic_auth(&self.config.username, Some(&self.config.password)) | ||||
|             .send()?; | ||||
|  | ||||
|         let wrapped: Vec<SshKeyWrapper> = self.handle_response(response)?; | ||||
|         let keys = wrapped.into_iter().map(|sk| sk.key).collect(); | ||||
|         Ok(keys) | ||||
|     } | ||||
|  | ||||
|     pub fn get_ssh_key(&self, fingerprint: &str) -> Result<SshKey, AppError> { | ||||
|         let response = self | ||||
|             .http_client | ||||
|             .get(format!("{}/key/{}", self.config.api_url, fingerprint)) | ||||
|             .basic_auth(&self.config.username, Some(&self.config.password)) | ||||
|             .send()?; | ||||
|  | ||||
|         let wrapped: SshKeyWrapper = self.handle_response(response)?; | ||||
|         Ok(wrapped.key) | ||||
|     } | ||||
|  | ||||
|     pub fn add_ssh_key(&self, name: &str, data: &str) -> Result<SshKey, AppError> { | ||||
|         let params = [("name", name), ("data", data)]; | ||||
|         let response = self | ||||
|             .http_client | ||||
|             .post(format!("{}/key", self.config.api_url)) | ||||
|             .basic_auth(&self.config.username, Some(&self.config.password)) | ||||
|             .form(¶ms) | ||||
|             .send()?; | ||||
|  | ||||
|         let wrapped: SshKeyWrapper = self.handle_response(response)?; | ||||
|         Ok(wrapped.key) | ||||
|     } | ||||
|  | ||||
|     pub fn update_ssh_key_name(&self, fingerprint: &str, name: &str) -> Result<SshKey, AppError> { | ||||
|         let params = [("name", name)]; | ||||
|         let response = self | ||||
|             .http_client | ||||
|             .post(format!("{}/key/{}", self.config.api_url, fingerprint)) | ||||
|             .basic_auth(&self.config.username, Some(&self.config.password)) | ||||
|             .form(¶ms) | ||||
|             .send()?; | ||||
|  | ||||
|         let wrapped: SshKeyWrapper = self.handle_response(response)?; | ||||
|         Ok(wrapped.key) | ||||
|     } | ||||
|  | ||||
|     pub fn delete_ssh_key(&self, fingerprint: &str) -> Result<(), AppError> { | ||||
|         self.http_client | ||||
|             .delete(format!("{}/key/{}", self.config.api_url, fingerprint)) | ||||
|             .basic_auth(&self.config.username, Some(&self.config.password)) | ||||
|             .send()?; | ||||
|  | ||||
|         Ok(()) | ||||
|     } | ||||
|     pub fn get_boot_configuration(&self, server_number: i32) -> Result<Boot, AppError> { | ||||
|         let response = self | ||||
|             .http_client | ||||
|             .get(format!("{}/boot/{}", self.config.api_url, server_number)) | ||||
|             .basic_auth(&self.config.username, Some(&self.config.password)) | ||||
|             .send()?; | ||||
|  | ||||
|         let wrapped: BootWrapper = self.handle_response(response)?; | ||||
|         Ok(wrapped.boot) | ||||
|     } | ||||
|  | ||||
|     pub fn get_rescue_boot_configuration(&self, server_number: i32) -> Result<Rescue, AppError> { | ||||
|         let response = self | ||||
|             .http_client | ||||
|             .get(format!( | ||||
|                 "{}/boot/{}/rescue", | ||||
|                 self.config.api_url, server_number | ||||
|             )) | ||||
|             .basic_auth(&self.config.username, Some(&self.config.password)) | ||||
|             .send()?; | ||||
|  | ||||
|         let wrapped: RescueWrapped = self.handle_response(response)?; | ||||
|         Ok(wrapped.rescue) | ||||
|     } | ||||
|  | ||||
|     pub fn enable_rescue_mode( | ||||
|         &self, | ||||
|         server_number: i32, | ||||
|         os: &str, | ||||
|         authorized_keys: Option<&[String]>, | ||||
|     ) -> Result<Rescue, AppError> { | ||||
|         let mut params = vec![("os", os)]; | ||||
|         if let Some(keys) = authorized_keys { | ||||
|             for key in keys { | ||||
|                 params.push(("authorized_key[]", key)); | ||||
|             } | ||||
|         } | ||||
|         let response = self | ||||
|             .http_client | ||||
|             .post(format!( | ||||
|                 "{}/boot/{}/rescue", | ||||
|                 self.config.api_url, server_number | ||||
|             )) | ||||
|             .basic_auth(&self.config.username, Some(&self.config.password)) | ||||
|             .form(¶ms) | ||||
|             .send()?; | ||||
|  | ||||
|         let wrapped: RescueWrapped = self.handle_response(response)?; | ||||
|         Ok(wrapped.rescue) | ||||
|     } | ||||
|  | ||||
|     pub fn disable_rescue_mode(&self, server_number: i32) -> Result<Rescue, AppError> { | ||||
|         let response = self | ||||
|             .http_client | ||||
|             .delete(format!( | ||||
|                 "{}/boot/{}/rescue", | ||||
|                 self.config.api_url, server_number | ||||
|             )) | ||||
|             .basic_auth(&self.config.username, Some(&self.config.password)) | ||||
|             .send()?; | ||||
|  | ||||
|         let wrapped: RescueWrapped = self.handle_response(response)?; | ||||
|         Ok(wrapped.rescue) | ||||
|     } | ||||
|  | ||||
|     pub fn get_server_products( | ||||
|         &self, | ||||
|     ) -> Result<Vec<OrderServerProduct>, AppError> { | ||||
|         let response = self | ||||
|             .http_client | ||||
|             .get(format!("{}/order/server/product", &self.config.api_url)) | ||||
|             .basic_auth(&self.config.username, Some(&self.config.password)) | ||||
|             .send()?; | ||||
|  | ||||
|         let wrapped: Vec<OrderServerProductWrapper> = self.handle_response(response)?; | ||||
|         let products = wrapped.into_iter().map(|sop| sop.product).collect(); | ||||
|         Ok(products) | ||||
|     } | ||||
|  | ||||
|     pub fn get_server_product_by_id( | ||||
|         &self, | ||||
|         product_id: &str, | ||||
|     ) -> Result<OrderServerProduct, AppError> { | ||||
|         let response = self | ||||
|             .http_client | ||||
|             .get(format!( | ||||
|                 "{}/order/server/product/{}", | ||||
|                 &self.config.api_url, product_id | ||||
|             )) | ||||
|             .basic_auth(&self.config.username, Some(&self.config.password)) | ||||
|             .send()?; | ||||
|  | ||||
|         let wrapped: OrderServerProductWrapper = self.handle_response(response)?; | ||||
|         Ok(wrapped.product) | ||||
|     } | ||||
|     pub fn order_server(&self, order: OrderServerBuilder) -> Result<Transaction, AppError> { | ||||
|         let mut params = json!({ | ||||
|             "product_id": order.product_id, | ||||
|             "dist": order.dist, | ||||
|             "location": order.location, | ||||
|             "authorized_key": order.authorized_keys.unwrap_or_default(), | ||||
|         }); | ||||
|  | ||||
|         if let Some(addons) = order.addons { | ||||
|             params["addon"] = json!(addons); | ||||
|         } | ||||
|  | ||||
|         if let Some(test) = order.test { | ||||
|             if test { | ||||
|                 params["test"] = json!(test); | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         let response = self | ||||
|             .http_client | ||||
|             .post(format!("{}/order/server/transaction", &self.config.api_url)) | ||||
|             .basic_auth(&self.config.username, Some(&self.config.password)) | ||||
|             .json(¶ms) | ||||
|             .send()?; | ||||
|  | ||||
|         let wrapped: TransactionWrapper = self.handle_response(response)?; | ||||
|         Ok(wrapped.transaction) | ||||
|     } | ||||
|  | ||||
|     pub fn get_transaction_by_id(&self, transaction_id: &str) -> Result<Transaction, AppError> { | ||||
|         let response = self | ||||
|             .http_client | ||||
|             .get(format!( | ||||
|                 "{}/order/server/transaction/{}", | ||||
|                 &self.config.api_url, transaction_id | ||||
|             )) | ||||
|             .basic_auth(&self.config.username, Some(&self.config.password)) | ||||
|             .send()?; | ||||
|  | ||||
|         let wrapped: TransactionWrapper = self.handle_response(response)?; | ||||
|         Ok(wrapped.transaction) | ||||
|     } | ||||
|     pub fn get_transactions(&self) -> Result<Vec<Transaction>, AppError> { | ||||
|         let response = self | ||||
|             .http_client | ||||
|             .get(format!("{}/order/server/transaction", &self.config.api_url)) | ||||
|             .basic_auth(&self.config.username, Some(&self.config.password)) | ||||
|             .send()?; | ||||
|  | ||||
|         let wrapped: Vec<TransactionWrapper> = self.handle_response(response)?; | ||||
|         let transactions = wrapped.into_iter().map(|t| t.transaction).collect(); | ||||
|         Ok(transactions) | ||||
|     } | ||||
|     pub fn get_auction_server_products(&self) -> Result<Vec<AuctionServerProduct>, AppError> { | ||||
|         let response = self | ||||
|             .http_client | ||||
|             .get(format!( | ||||
|                 "{}/order/server_market/product", | ||||
|                 &self.config.api_url | ||||
|             )) | ||||
|             .basic_auth(&self.config.username, Some(&self.config.password)) | ||||
|             .send()?; | ||||
|  | ||||
|         let wrapped: Vec<AuctionServerProductWrapper> = self.handle_response(response)?; | ||||
|         let products = wrapped.into_iter().map(|asp| asp.product).collect(); | ||||
|         Ok(products) | ||||
|     } | ||||
|     pub fn get_auction_server_product_by_id(&self, product_id: &str) -> Result<AuctionServerProduct, AppError> { | ||||
|         let response = self | ||||
|             .http_client | ||||
|             .get(format!("{}/order/server_market/product/{}", &self.config.api_url, product_id)) | ||||
|             .basic_auth(&self.config.username, Some(&self.config.password)) | ||||
|             .send()?; | ||||
|  | ||||
|         let wrapped: AuctionServerProductWrapper = self.handle_response(response)?; | ||||
|         Ok(wrapped.product) | ||||
|     } | ||||
|     pub fn get_auction_transactions(&self) -> Result<Vec<AuctionTransaction>, AppError> { | ||||
|         let response = self | ||||
|             .http_client | ||||
|             .get(format!("{}/order/server_market/transaction", &self.config.api_url)) | ||||
|             .basic_auth(&self.config.username, Some(&self.config.password)) | ||||
|             .send()?; | ||||
|  | ||||
|         let wrapped: Vec<AuctionTransactionWrapper> = self.handle_response(response)?; | ||||
|         let transactions = wrapped.into_iter().map(|t| t.transaction).collect(); | ||||
|         Ok(transactions) | ||||
|     } | ||||
|  | ||||
|     pub fn get_auction_transaction_by_id(&self, transaction_id: &str) -> Result<AuctionTransaction, AppError> { | ||||
|         let response = self | ||||
|             .http_client | ||||
|             .get(format!("{}/order/server_market/transaction/{}", &self.config.api_url, transaction_id)) | ||||
|             .basic_auth(&self.config.username, Some(&self.config.password)) | ||||
|             .send()?; | ||||
|  | ||||
|         let wrapped: AuctionTransactionWrapper = self.handle_response(response)?; | ||||
|         Ok(wrapped.transaction) | ||||
|     } | ||||
|  | ||||
|     pub fn get_server_addon_products( | ||||
|         &self, | ||||
|         server_number: i64, | ||||
|     ) -> Result<Vec<ServerAddonProduct>, AppError> { | ||||
|         let response = self | ||||
|             .http_client | ||||
|             .get(format!( | ||||
|                 "{}/order/server_addon/{}/product", | ||||
|                 &self.config.api_url, server_number | ||||
|             )) | ||||
|             .basic_auth(&self.config.username, Some(&self.config.password)) | ||||
|             .send()?; | ||||
|  | ||||
|         let wrapped: Vec<ServerAddonProductWrapper> = self.handle_response(response)?; | ||||
|         let products = wrapped.into_iter().map(|sap| sap.product).collect(); | ||||
|         Ok(products) | ||||
|     } | ||||
|  | ||||
|     pub fn order_auction_server( | ||||
|         &self, | ||||
|         product_id: i64, | ||||
|         authorized_keys: Vec<String>, | ||||
|         dist: Option<String>, | ||||
|         arch: Option<String>, | ||||
|         lang: Option<String>, | ||||
|         comment: Option<String>, | ||||
|         addons: Option<Vec<String>>, | ||||
|         test: Option<bool>, | ||||
|     ) -> Result<AuctionTransaction, AppError> { | ||||
|         let mut params: Vec<(&str, String)> = Vec::new(); | ||||
|  | ||||
|         params.push(("product_id", product_id.to_string())); | ||||
|  | ||||
|         for key in &authorized_keys { | ||||
|             params.push(("authorized_key[]", key.clone())); | ||||
|         } | ||||
|  | ||||
|         if let Some(dist) = dist { | ||||
|             params.push(("dist", dist)); | ||||
|         } | ||||
|         if let Some(arch) = arch { | ||||
|             params.push(("@deprecated arch", arch)); | ||||
|         } | ||||
|         if let Some(lang) = lang { | ||||
|             params.push(("lang", lang)); | ||||
|         } | ||||
|         if let Some(comment) = comment { | ||||
|             params.push(("comment", comment)); | ||||
|         } | ||||
|         if let Some(addons) = addons { | ||||
|             for addon in addons { | ||||
|                 params.push(("addon[]", addon)); | ||||
|             } | ||||
|         } | ||||
|         if let Some(test) = test { | ||||
|             params.push(("test", test.to_string())); | ||||
|         } | ||||
|  | ||||
|         let response = self | ||||
|             .http_client | ||||
|             .post(format!("{}/order/server_market/transaction", &self.config.api_url)) | ||||
|             .basic_auth(&self.config.username, Some(&self.config.password)) | ||||
|             .form(¶ms) | ||||
|             .send()?; | ||||
|  | ||||
|         let wrapped: AuctionTransactionWrapper = self.handle_response(response)?; | ||||
|         Ok(wrapped.transaction) | ||||
|     } | ||||
|  | ||||
|     pub fn get_server_addon_transactions(&self) -> Result<Vec<ServerAddonTransaction>, AppError> { | ||||
|         let response = self | ||||
|             .http_client | ||||
|             .get(format!("{}/order/server_addon/transaction", &self.config.api_url)) | ||||
|             .basic_auth(&self.config.username, Some(&self.config.password)) | ||||
|             .send()?; | ||||
|  | ||||
|         let wrapped: Vec<ServerAddonTransactionWrapper> = self.handle_response(response)?; | ||||
|         let transactions = wrapped.into_iter().map(|satw| satw.transaction).collect(); | ||||
|         Ok(transactions) | ||||
|     } | ||||
|  | ||||
|     pub fn get_server_addon_transaction_by_id( | ||||
|         &self, | ||||
|         transaction_id: &str, | ||||
|     ) -> Result<ServerAddonTransaction, AppError> { | ||||
|         let response = self | ||||
|             .http_client | ||||
|             .get(format!( | ||||
|                 "{}/order/server_addon/transaction/{}", | ||||
|                 &self.config.api_url, transaction_id | ||||
|             )) | ||||
|             .basic_auth(&self.config.username, Some(&self.config.password)) | ||||
|             .send()?; | ||||
|  | ||||
|         let wrapped: ServerAddonTransactionWrapper = self.handle_response(response)?; | ||||
|         Ok(wrapped.transaction) | ||||
|     } | ||||
|  | ||||
|     pub fn order_server_addon( | ||||
|         &self, | ||||
|         order: OrderServerAddonBuilder, | ||||
|     ) -> Result<ServerAddonTransaction, AppError> { | ||||
|         let mut params = json!({ | ||||
|             "server_number": order.server_number, | ||||
|             "product_id": order.product_id, | ||||
|         }); | ||||
|  | ||||
|         if let Some(reason) = order.reason { | ||||
|             params["reason"] = json!(reason); | ||||
|         } | ||||
|         if let Some(gateway) = order.gateway { | ||||
|             params["gateway"] = json!(gateway); | ||||
|         } | ||||
|         if let Some(test) = order.test { | ||||
|             if test { | ||||
|                 params["test"] = json!(test); | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         let response = self | ||||
|             .http_client | ||||
|             .post(format!("{}/order/server_addon/transaction", &self.config.api_url)) | ||||
|             .basic_auth(&self.config.username, Some(&self.config.password)) | ||||
|             .form(¶ms) | ||||
|             .send()?; | ||||
|  | ||||
|         let wrapped: ServerAddonTransactionWrapper = self.handle_response(response)?; | ||||
|         Ok(wrapped.transaction) | ||||
|     } | ||||
| } | ||||
							
								
								
									
										1894
									
								
								packages/clients/hetznerclient/src/api/models.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1894
									
								
								packages/clients/hetznerclient/src/api/models.rs
									
									
									
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
							
								
								
									
										25
									
								
								packages/clients/hetznerclient/src/config.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										25
									
								
								packages/clients/hetznerclient/src/config.rs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,25 @@ | ||||
| use std::env; | ||||
|  | ||||
| #[derive(Clone)] | ||||
| pub struct Config { | ||||
|     pub username: String, | ||||
|     pub password: String, | ||||
|     pub api_url: String, | ||||
| } | ||||
|  | ||||
| impl Config { | ||||
|     pub fn from_env() -> Result<Self, String> { | ||||
|         let username = env::var("HETZNER_USERNAME") | ||||
|             .map_err(|_| "HETZNER_USERNAME environment variable not set".to_string())?; | ||||
|         let password = env::var("HETZNER_PASSWORD") | ||||
|             .map_err(|_| "HETZNER_PASSWORD environment variable not set".to_string())?; | ||||
|         let api_url = env::var("HETZNER_API_URL") | ||||
|             .unwrap_or_else(|_| "https://robot-ws.your-server.de".to_string()); | ||||
|  | ||||
|         Ok(Config { | ||||
|             username, | ||||
|             password, | ||||
|             api_url, | ||||
|         }) | ||||
|     } | ||||
| } | ||||
							
								
								
									
										3
									
								
								packages/clients/hetznerclient/src/lib.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										3
									
								
								packages/clients/hetznerclient/src/lib.rs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,3 @@ | ||||
| pub mod api; | ||||
| pub mod config; | ||||
| pub mod rhai; | ||||
							
								
								
									
										63
									
								
								packages/clients/hetznerclient/src/rhai/boot.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										63
									
								
								packages/clients/hetznerclient/src/rhai/boot.rs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,63 @@ | ||||
| use crate::api::{ | ||||
|     models::{Boot, Rescue}, | ||||
|     Client, | ||||
| }; | ||||
| use rhai::{plugin::*, Engine}; | ||||
|  | ||||
| pub fn register(engine: &mut Engine) { | ||||
|     let boot_module = exported_module!(boot_api); | ||||
|     engine.register_global_module(boot_module.into()); | ||||
| } | ||||
|  | ||||
| #[export_module] | ||||
| pub mod boot_api { | ||||
|     use super::*; | ||||
|     use rhai::EvalAltResult; | ||||
|  | ||||
|     #[rhai_fn(name = "get_boot_configuration", return_raw)] | ||||
|     pub fn get_boot_configuration( | ||||
|         client: &mut Client, | ||||
|         server_number: i64, | ||||
|     ) -> Result<Boot, Box<EvalAltResult>> { | ||||
|         client | ||||
|             .get_boot_configuration(server_number as i32) | ||||
|             .map_err(|e| e.to_string().into()) | ||||
|     } | ||||
|  | ||||
|     #[rhai_fn(name = "get_rescue_boot_configuration", return_raw)] | ||||
|     pub fn get_rescue_boot_configuration( | ||||
|         client: &mut Client, | ||||
|         server_number: i64, | ||||
|     ) -> Result<Rescue, Box<EvalAltResult>> { | ||||
|         client | ||||
|             .get_rescue_boot_configuration(server_number as i32) | ||||
|             .map_err(|e| e.to_string().into()) | ||||
|     } | ||||
|  | ||||
|     #[rhai_fn(name = "enable_rescue_mode", return_raw)] | ||||
|     pub fn enable_rescue_mode( | ||||
|         client: &mut Client, | ||||
|         server_number: i64, | ||||
|         os: &str, | ||||
|         authorized_keys: rhai::Array, | ||||
|     ) -> Result<Rescue, Box<EvalAltResult>> { | ||||
|         let keys: Vec<String> = authorized_keys | ||||
|             .into_iter() | ||||
|             .map(|k| k.into_string().unwrap()) | ||||
|             .collect(); | ||||
|  | ||||
|         client | ||||
|             .enable_rescue_mode(server_number as i32, os, Some(&keys)) | ||||
|             .map_err(|e| e.to_string().into()) | ||||
|     } | ||||
|  | ||||
|     #[rhai_fn(name = "disable_rescue_mode", return_raw)] | ||||
|     pub fn disable_rescue_mode( | ||||
|         client: &mut Client, | ||||
|         server_number: i64, | ||||
|     ) -> Result<Rescue, Box<EvalAltResult>> { | ||||
|         client | ||||
|             .disable_rescue_mode(server_number as i32) | ||||
|             .map_err(|e| e.to_string().into()) | ||||
|     } | ||||
| } | ||||
							
								
								
									
										54
									
								
								packages/clients/hetznerclient/src/rhai/mod.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										54
									
								
								packages/clients/hetznerclient/src/rhai/mod.rs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,54 @@ | ||||
| use rhai::{Engine, EvalAltResult}; | ||||
|  | ||||
| use crate::api::models::{ | ||||
|     AuctionServerProduct, AuctionTransaction, AuctionTransactionProduct, AuthorizedKey, Boot, | ||||
|     Cancellation, Cpanel, HostKey, Linux, OrderAuctionServerBuilder, OrderServerAddonBuilder, | ||||
|     OrderServerBuilder, OrderServerProduct, Plesk, Rescue, Server, ServerAddonProduct, | ||||
|     ServerAddonResource, ServerAddonTransaction, SshKey, Transaction, TransactionProduct, Vnc, | ||||
|     Windows, | ||||
| }; | ||||
|  | ||||
| pub mod boot; | ||||
| pub mod printing; | ||||
| pub mod server; | ||||
| pub mod server_ordering; | ||||
| pub mod ssh_keys; | ||||
|  | ||||
| // here just register the hetzner module | ||||
| pub fn register_hetzner_module(engine: &mut Engine) -> Result<(), Box<EvalAltResult>> { | ||||
|     // TODO:register types | ||||
|     engine.build_type::<Server>(); | ||||
|     engine.build_type::<SshKey>(); | ||||
|     engine.build_type::<Boot>(); | ||||
|     engine.build_type::<Rescue>(); | ||||
|     engine.build_type::<Linux>(); | ||||
|     engine.build_type::<Vnc>(); | ||||
|     engine.build_type::<Windows>(); | ||||
|     engine.build_type::<Plesk>(); | ||||
|     engine.build_type::<Cpanel>(); | ||||
|     engine.build_type::<Cancellation>(); | ||||
|     engine.build_type::<OrderServerProduct>(); | ||||
|     engine.build_type::<Transaction>(); | ||||
|     engine.build_type::<AuthorizedKey>(); | ||||
|     engine.build_type::<TransactionProduct>(); | ||||
|     engine.build_type::<HostKey>(); | ||||
|     engine.build_type::<AuctionServerProduct>(); | ||||
|     engine.build_type::<AuctionTransaction>(); | ||||
|     engine.build_type::<AuctionTransactionProduct>(); | ||||
|     engine.build_type::<OrderAuctionServerBuilder>(); | ||||
|     engine.build_type::<OrderServerBuilder>(); | ||||
|     engine.build_type::<ServerAddonProduct>(); | ||||
|     engine.build_type::<ServerAddonTransaction>(); | ||||
|     engine.build_type::<ServerAddonResource>(); | ||||
|     engine.build_type::<OrderServerAddonBuilder>(); | ||||
|  | ||||
|     server::register(engine); | ||||
|     ssh_keys::register(engine); | ||||
|     boot::register(engine); | ||||
|     server_ordering::register(engine); | ||||
|  | ||||
|     // TODO: push hetzner to scope as value client: | ||||
|     // scope.push("hetzner", client); | ||||
|  | ||||
|     Ok(()) | ||||
| } | ||||
							
								
								
									
										43
									
								
								packages/clients/hetznerclient/src/rhai/printing/mod.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										43
									
								
								packages/clients/hetznerclient/src/rhai/printing/mod.rs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,43 @@ | ||||
| use rhai::{Array, Engine}; | ||||
| use crate::{api::models::{OrderServerProduct, AuctionServerProduct, AuctionTransaction, ServerAddonProduct, ServerAddonTransaction, Server, SshKey}}; | ||||
|  | ||||
| mod servers_table; | ||||
| mod ssh_keys_table; | ||||
| mod server_ordering_table; | ||||
|  | ||||
| // This will be called when we print(...) or pretty_print() an Array (with Dynamic values) | ||||
| pub fn pretty_print_dispatch(array: Array) { | ||||
|     if array.is_empty() { | ||||
|         println!("<empty table>"); | ||||
|         return; | ||||
|     } | ||||
|  | ||||
|     let first = &array[0]; | ||||
|  | ||||
|     if first.is::<Server>() { | ||||
|         println!("Yeah first is server!"); | ||||
|         servers_table::pretty_print_servers(array); | ||||
|     } else if first.is::<SshKey>() { | ||||
|         ssh_keys_table::pretty_print_ssh_keys(array); | ||||
|     } | ||||
|     else if first.is::<OrderServerProduct>() { | ||||
|         server_ordering_table::pretty_print_server_products(array); | ||||
|     } else if first.is::<AuctionServerProduct>() { | ||||
|         server_ordering_table::pretty_print_auction_server_products(array); | ||||
|     } else if first.is::<AuctionTransaction>() { | ||||
|         server_ordering_table::pretty_print_auction_transactions(array); | ||||
|     } else if first.is::<ServerAddonProduct>() { | ||||
|         server_ordering_table::pretty_print_server_addon_products(array); | ||||
|     } else if first.is::<ServerAddonTransaction>() { | ||||
|         server_ordering_table::pretty_print_server_addon_transactions(array); | ||||
|     } else { | ||||
|         // Generic fallback for other types | ||||
|         for item in array { | ||||
|             println!("{}", item.to_string()); | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
| pub fn register(engine: &mut Engine) { | ||||
|     engine.register_fn("pretty_print", pretty_print_dispatch); | ||||
| } | ||||
| @@ -0,0 +1,293 @@ | ||||
| use prettytable::{row, Table}; | ||||
| use crate::api::models::{OrderServerProduct, ServerAddonProduct, ServerAddonTransaction, ServerAddonResource}; | ||||
|  | ||||
| pub fn pretty_print_server_products(products: rhai::Array) { | ||||
|     let mut table = Table::new(); | ||||
|     table.add_row(row![b => | ||||
|         "ID", | ||||
|         "Name", | ||||
|         "Description", | ||||
|         "Traffic", | ||||
|         "Location", | ||||
|         "Price (Net)", | ||||
|         "Price (Gross)", | ||||
|     ]); | ||||
|  | ||||
|     for product_dyn in products { | ||||
|         if let Some(product) = product_dyn.try_cast::<OrderServerProduct>() { | ||||
|             let mut price_net = "N/A".to_string(); | ||||
|             let mut price_gross = "N/A".to_string(); | ||||
|  | ||||
|             if let Some(first_price) = product.prices.first() { | ||||
|                 price_net = first_price.price.net.clone(); | ||||
|                 price_gross = first_price.price.gross.clone(); | ||||
|             } | ||||
|  | ||||
|             table.add_row(row![ | ||||
|                 product.id, | ||||
|                 product.name, | ||||
|                 product.description.join(", "), | ||||
|                 product.traffic, | ||||
|                 product.location.join(", "), | ||||
|                 price_net, | ||||
|                 price_gross, | ||||
|             ]); | ||||
|         } | ||||
|     } | ||||
|     table.printstd(); | ||||
| } | ||||
|  | ||||
| pub fn pretty_print_auction_server_products(products: rhai::Array) { | ||||
|     let mut table = Table::new(); | ||||
|     table.add_row(row![b => | ||||
|         "ID", | ||||
|         "Name", | ||||
|         "Description", | ||||
|         "Traffic", | ||||
|         "Distributions", | ||||
|         "Architectures", | ||||
|         "Languages", | ||||
|         "CPU", | ||||
|         "CPU Benchmark", | ||||
|         "Memory Size (GB)", | ||||
|         "HDD Size (GB)", | ||||
|         "HDD Text", | ||||
|         "HDD Count", | ||||
|         "Datacenter", | ||||
|         "Network Speed", | ||||
|         "Price (Net)", | ||||
|         "Price (Hourly Net)", | ||||
|         "Price (Setup Net)", | ||||
|         "Price (VAT)", | ||||
|         "Price (Hourly VAT)", | ||||
|         "Price (Setup VAT)", | ||||
|         "Fixed Price", | ||||
|         "Next Reduce (seconds)", | ||||
|         "Next Reduce Date", | ||||
|         "Orderable Addons", | ||||
|     ]); | ||||
|  | ||||
|     for product_dyn in products { | ||||
|         if let Some(product) = product_dyn.try_cast::<crate::api::models::AuctionServerProduct>() { | ||||
|             let mut addons_table = Table::new(); | ||||
|             addons_table.add_row(row![b => "ID", "Name", "Min", "Max", "Prices"]); | ||||
|             for addon in &product.orderable_addons { | ||||
|                 let mut addon_prices_table = Table::new(); | ||||
|                 addon_prices_table.add_row(row![b => "Location", "Net", "Gross", "Hourly Net", "Hourly Gross", "Setup Net", "Setup Gross"]); | ||||
|                 for price in &addon.prices { | ||||
|                     addon_prices_table.add_row(row![ | ||||
|                         price.location, | ||||
|                         price.price.net, | ||||
|                         price.price.gross, | ||||
|                         price.price.hourly_net, | ||||
|                         price.price.hourly_gross, | ||||
|                         price.price_setup.net, | ||||
|                         price.price_setup.gross | ||||
|                     ]); | ||||
|                 } | ||||
|                 addons_table.add_row(row![ | ||||
|                     addon.id, | ||||
|                     addon.name, | ||||
|                     addon.min, | ||||
|                     addon.max, | ||||
|                     addon_prices_table | ||||
|                 ]); | ||||
|             } | ||||
|  | ||||
|             table.add_row(row![ | ||||
|                 product.id, | ||||
|                 product.name, | ||||
|                 product.description.join(", "), | ||||
|                 product.traffic, | ||||
|                 product.dist.join(", "), | ||||
|                 product.arch.as_deref().unwrap_or_default().join(", "), | ||||
|                 product.lang.join(", "), | ||||
|                 product.cpu, | ||||
|                 product.cpu_benchmark, | ||||
|                 product.memory_size, | ||||
|                 product.hdd_size, | ||||
|                 product.hdd_text, | ||||
|                 product.hdd_count, | ||||
|                 product.datacenter, | ||||
|                 product.network_speed, | ||||
|                 product.price, | ||||
|                 product.price_hourly.as_deref().unwrap_or("N/A"), | ||||
|                 product.price_setup, | ||||
|                 product.price_with_vat, | ||||
|                 product.price_hourly_with_vat.as_deref().unwrap_or("N/A"), | ||||
|                 product.price_setup_with_vat, | ||||
|                 product.fixed_price, | ||||
|                 product.next_reduce, | ||||
|                 product.next_reduce_date, | ||||
|                 addons_table, | ||||
|             ]); | ||||
|         } | ||||
|     } | ||||
|     table.printstd(); | ||||
| } | ||||
|  | ||||
| pub fn pretty_print_server_addon_products(products: rhai::Array) { | ||||
|     let mut table = Table::new(); | ||||
|     table.add_row(row![b => | ||||
|         "ID", | ||||
|         "Name", | ||||
|         "Type", | ||||
|         "Location", | ||||
|         "Price (Net)", | ||||
|         "Price (Gross)", | ||||
|         "Hourly Net", | ||||
|         "Hourly Gross", | ||||
|         "Setup Net", | ||||
|         "Setup Gross", | ||||
|     ]); | ||||
|  | ||||
|     for product_dyn in products { | ||||
|         if let Some(product) = product_dyn.try_cast::<ServerAddonProduct>() { | ||||
|             table.add_row(row![ | ||||
|                 product.id, | ||||
|                 product.name, | ||||
|                 product.product_type, | ||||
|                 product.price.location, | ||||
|                 product.price.price.net, | ||||
|                 product.price.price.gross, | ||||
|                 product.price.price.hourly_net, | ||||
|                 product.price.price.hourly_gross, | ||||
|                 product.price.price_setup.net, | ||||
|                 product.price.price_setup.gross, | ||||
|             ]); | ||||
|         } | ||||
|     } | ||||
|     table.printstd(); | ||||
| } | ||||
|  | ||||
| pub fn pretty_print_auction_transactions(transactions: rhai::Array) { | ||||
|     let mut table = Table::new(); | ||||
|     table.add_row(row![b => | ||||
|         "ID", | ||||
|         "Date", | ||||
|         "Status", | ||||
|         "Server Number", | ||||
|         "Server IP", | ||||
|         "Comment", | ||||
|         "Product ID", | ||||
|         "Product Name", | ||||
|         "Product Traffic", | ||||
|         "Product Distributions", | ||||
|         "Product Architectures", | ||||
|         "Product Languages", | ||||
|         "Product CPU", | ||||
|         "Product CPU Benchmark", | ||||
|         "Product Memory Size (GB)", | ||||
|         "Product HDD Size (GB)", | ||||
|         "Product HDD Text", | ||||
|         "Product HDD Count", | ||||
|         "Product Datacenter", | ||||
|         "Product Network Speed", | ||||
|         "Product Fixed Price", | ||||
|         "Product Next Reduce (seconds)", | ||||
|         "Product Next Reduce Date", | ||||
|         "Addons", | ||||
|     ]); | ||||
|  | ||||
|     for transaction_dyn in transactions { | ||||
|         if let Some(transaction) = transaction_dyn.try_cast::<crate::api::models::AuctionTransaction>() { | ||||
|             let _authorized_keys_table = { | ||||
|                 let mut table = Table::new(); | ||||
|                 table.add_row(row![b => "Name", "Fingerprint", "Type", "Size"]); | ||||
|                 for key in &transaction.authorized_key { | ||||
|                     table.add_row(row![ | ||||
|                         key.key.name.as_deref().unwrap_or("N/A"), | ||||
|                         key.key.fingerprint.as_deref().unwrap_or("N/A"), | ||||
|                         key.key.key_type.as_deref().unwrap_or("N/A"), | ||||
|                         key.key.size.map_or("N/A".to_string(), |s| s.to_string()) | ||||
|                     ]); | ||||
|                 } | ||||
|                 table | ||||
|             }; | ||||
|  | ||||
|             let _host_keys_table = { | ||||
|                 let mut table = Table::new(); | ||||
|                 table.add_row(row![b => "Fingerprint", "Type", "Size"]); | ||||
|                 for key in &transaction.host_key { | ||||
|                     table.add_row(row![ | ||||
|                         key.key.fingerprint.as_deref().unwrap_or("N/A"), | ||||
|                         key.key.key_type.as_deref().unwrap_or("N/A"), | ||||
|                         key.key.size.map_or("N/A".to_string(), |s| s.to_string()) | ||||
|                     ]); | ||||
|                 } | ||||
|                 table | ||||
|             }; | ||||
|  | ||||
|             table.add_row(row![ | ||||
|                 transaction.id, | ||||
|                 transaction.date, | ||||
|                 transaction.status, | ||||
|                 transaction.server_number.map_or("N/A".to_string(), |id| id.to_string()), | ||||
|                 transaction.server_ip.as_deref().unwrap_or("N/A"), | ||||
|                 transaction.comment.as_deref().unwrap_or("N/A"), | ||||
|                 transaction.product.id, | ||||
|                 transaction.product.name, | ||||
|                 transaction.product.traffic, | ||||
|                 transaction.product.dist, | ||||
|                 transaction.product.arch.as_deref().unwrap_or("N/A"), | ||||
|                 transaction.product.lang, | ||||
|                 transaction.product.cpu, | ||||
|                 transaction.product.cpu_benchmark, | ||||
|                 transaction.product.memory_size, | ||||
|                 transaction.product.hdd_size, | ||||
|                 transaction.product.hdd_text, | ||||
|                 transaction.product.hdd_count, | ||||
|                 transaction.product.datacenter, | ||||
|                 transaction.product.network_speed, | ||||
|                 transaction.product.fixed_price.unwrap_or_default().to_string(), | ||||
|                 transaction | ||||
|                     .product | ||||
|                     .next_reduce | ||||
|                     .map_or("N/A".to_string(), |r| r.to_string()), | ||||
|                 transaction | ||||
|                     .product | ||||
|                     .next_reduce_date | ||||
|                     .as_deref() | ||||
|                     .unwrap_or("N/A"), | ||||
|                 transaction.addons.join(", "), | ||||
|             ]); | ||||
|         } | ||||
|     } | ||||
|     table.printstd(); | ||||
| } | ||||
|  | ||||
| pub fn pretty_print_server_addon_transactions(transactions: rhai::Array) { | ||||
|     let mut table = Table::new(); | ||||
|     table.add_row(row![b => | ||||
|         "ID", | ||||
|         "Date", | ||||
|         "Status", | ||||
|         "Server Number", | ||||
|         "Product ID", | ||||
|         "Product Name", | ||||
|         "Product Price", | ||||
|         "Resources", | ||||
|     ]); | ||||
|  | ||||
|     for transaction_dyn in transactions { | ||||
|         if let Some(transaction) = transaction_dyn.try_cast::<ServerAddonTransaction>() { | ||||
|             let mut resources_table = Table::new(); | ||||
|             resources_table.add_row(row![b => "Type", "ID"]); | ||||
|             for resource in &transaction.resources { | ||||
|                 resources_table.add_row(row![resource.resource_type, resource.id]); | ||||
|             } | ||||
|  | ||||
|             table.add_row(row![ | ||||
|                 transaction.id, | ||||
|                 transaction.date, | ||||
|                 transaction.status, | ||||
|                 transaction.server_number, | ||||
|                 transaction.product.id, | ||||
|                 transaction.product.name, | ||||
|                 transaction.product.price.to_string(), | ||||
|                 resources_table, | ||||
|             ]); | ||||
|         } | ||||
|     } | ||||
|     table.printstd(); | ||||
| } | ||||
| @@ -0,0 +1,30 @@ | ||||
| use prettytable::{row, Table}; | ||||
| use rhai::Array; | ||||
|  | ||||
| use super::Server; | ||||
|  | ||||
| pub fn pretty_print_servers(servers: Array) { | ||||
|     let mut table = Table::new(); | ||||
|     table.add_row(row![b => | ||||
|         "Number", | ||||
|         "Name", | ||||
|         "IP", | ||||
|         "Product", | ||||
|         "DC", | ||||
|         "Status" | ||||
|     ]); | ||||
|  | ||||
|     for server_dyn in servers { | ||||
|         if let Some(server) = server_dyn.try_cast::<Server>() { | ||||
|             table.add_row(row![ | ||||
|                 server.server_number.to_string(), | ||||
|                 server.server_name, | ||||
|                 server.server_ip.unwrap_or("N/A".to_string()), | ||||
|                 server.product, | ||||
|                 server.dc, | ||||
|                 server.status | ||||
|             ]); | ||||
|         } | ||||
|     } | ||||
|     table.printstd(); | ||||
| } | ||||
| @@ -0,0 +1,26 @@ | ||||
| use prettytable::{row, Table}; | ||||
| use super::SshKey; | ||||
|  | ||||
| pub fn pretty_print_ssh_keys(keys: rhai::Array) { | ||||
|     let mut table = Table::new(); | ||||
|     table.add_row(row![b => | ||||
|         "Name", | ||||
|         "Fingerprint", | ||||
|         "Type", | ||||
|         "Size", | ||||
|         "Created At" | ||||
|     ]); | ||||
|  | ||||
|     for key_dyn in keys { | ||||
|         if let Some(key) = key_dyn.try_cast::<SshKey>() { | ||||
|             table.add_row(row![ | ||||
|                 key.name, | ||||
|                 key.fingerprint, | ||||
|                 key.key_type, | ||||
|                 key.size.to_string(), | ||||
|                 key.created_at | ||||
|             ]); | ||||
|         } | ||||
|     } | ||||
|     table.printstd(); | ||||
| } | ||||
							
								
								
									
										76
									
								
								packages/clients/hetznerclient/src/rhai/server.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										76
									
								
								packages/clients/hetznerclient/src/rhai/server.rs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,76 @@ | ||||
| use crate::api::{Client, models::Server}; | ||||
| use rhai::{Array, Dynamic, plugin::*}; | ||||
|  | ||||
| pub fn register(engine: &mut Engine) { | ||||
|     let server_module = exported_module!(server_api); | ||||
|     engine.register_global_module(server_module.into()); | ||||
| } | ||||
|  | ||||
| #[export_module] | ||||
| pub mod server_api { | ||||
|     use crate::api::models::Cancellation; | ||||
|  | ||||
|     use super::*; | ||||
|     use rhai::EvalAltResult; | ||||
|  | ||||
|     #[rhai_fn(name = "get_server", return_raw)] | ||||
|     pub fn get_server( | ||||
|         client: &mut Client, | ||||
|         server_number: i64, | ||||
|     ) -> Result<Server, Box<EvalAltResult>> { | ||||
|         client | ||||
|             .get_server(server_number as i32) | ||||
|             .map_err(|e| e.to_string().into()) | ||||
|     } | ||||
|  | ||||
|     #[rhai_fn(name = "get_servers", return_raw)] | ||||
|     pub fn get_servers(client: &mut Client) -> Result<Array, Box<EvalAltResult>> { | ||||
|         let servers = client | ||||
|             .get_servers() | ||||
|             .map_err(|e| Into::<Box<EvalAltResult>>::into(e.to_string()))?; | ||||
|         println!("number of SERVERS we got: {:#?}", servers.len()); | ||||
|         Ok(servers.into_iter().map(Dynamic::from).collect()) | ||||
|     } | ||||
|  | ||||
|     #[rhai_fn(name = "update_server_name", return_raw)] | ||||
|     pub fn update_server_name( | ||||
|         client: &mut Client, | ||||
|         server_number: i64, | ||||
|         name: &str, | ||||
|     ) -> Result<Server, Box<EvalAltResult>> { | ||||
|         client | ||||
|             .update_server_name(server_number as i32, name) | ||||
|             .map_err(|e| e.to_string().into()) | ||||
|     } | ||||
|  | ||||
|     #[rhai_fn(name = "get_cancellation_data", return_raw)] | ||||
|     pub fn get_cancellation_data( | ||||
|         client: &mut Client, | ||||
|         server_number: i64, | ||||
|     ) -> Result<Cancellation, Box<EvalAltResult>> { | ||||
|         client | ||||
|             .get_cancellation_data(server_number as i32) | ||||
|             .map_err(|e| e.to_string().into()) | ||||
|     } | ||||
|  | ||||
|     #[rhai_fn(name = "cancel_server", return_raw)] | ||||
|     pub fn cancel_server( | ||||
|         client: &mut Client, | ||||
|         server_number: i64, | ||||
|         cancellation_date: &str, | ||||
|     ) -> Result<Cancellation, Box<EvalAltResult>> { | ||||
|         client | ||||
|             .cancel_server(server_number as i32, cancellation_date) | ||||
|             .map_err(|e| e.to_string().into()) | ||||
|     } | ||||
|  | ||||
|     #[rhai_fn(name = "withdraw_cancellation", return_raw)] | ||||
|     pub fn withdraw_cancellation( | ||||
|         client: &mut Client, | ||||
|         server_number: i64, | ||||
|     ) -> Result<(), Box<EvalAltResult>> { | ||||
|         client | ||||
|             .withdraw_cancellation(server_number as i32) | ||||
|             .map_err(|e| e.to_string().into()) | ||||
|     } | ||||
| } | ||||
							
								
								
									
										170
									
								
								packages/clients/hetznerclient/src/rhai/server_ordering.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										170
									
								
								packages/clients/hetznerclient/src/rhai/server_ordering.rs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,170 @@ | ||||
| use crate::api::{ | ||||
|     Client, | ||||
|     models::{ | ||||
|         AuctionServerProduct, AuctionTransaction, OrderAuctionServerBuilder, OrderServerBuilder, | ||||
|         OrderServerProduct, ServerAddonProduct, ServerAddonTransaction, Transaction, | ||||
|     }, | ||||
| }; | ||||
| use rhai::{Array, Dynamic, plugin::*}; | ||||
|  | ||||
| pub fn register(engine: &mut Engine) { | ||||
|     let server_order_module = exported_module!(server_order_api); | ||||
|     engine.register_global_module(server_order_module.into()); | ||||
| } | ||||
|  | ||||
| #[export_module] | ||||
| pub mod server_order_api { | ||||
|     use crate::api::models::OrderServerAddonBuilder; | ||||
|  | ||||
|     #[rhai_fn(name = "get_server_products", return_raw)] | ||||
|     pub fn get_server_ordering_product_overview( | ||||
|         client: &mut Client, | ||||
|     ) -> Result<Array, Box<EvalAltResult>> { | ||||
|         let overview_servers = client | ||||
|             .get_server_products() | ||||
|             .map_err(|e| Into::<Box<EvalAltResult>>::into(e.to_string()))?; | ||||
|         Ok(overview_servers.into_iter().map(Dynamic::from).collect()) | ||||
|     } | ||||
|  | ||||
|     #[rhai_fn(name = "get_server_product_by_id", return_raw)] | ||||
|     pub fn get_server_ordering_product_by_id( | ||||
|         client: &mut Client, | ||||
|         product_id: &str, | ||||
|     ) -> Result<OrderServerProduct, Box<EvalAltResult>> { | ||||
|         let product = client | ||||
|             .get_server_product_by_id(product_id) | ||||
|             .map_err(|e| Into::<Box<EvalAltResult>>::into(e.to_string()))?; | ||||
|         Ok(product) | ||||
|     } | ||||
|  | ||||
|     #[rhai_fn(name = "order_server", return_raw)] | ||||
|     pub fn order_server( | ||||
|         client: &mut Client, | ||||
|         order: OrderServerBuilder, | ||||
|     ) -> Result<Transaction, Box<EvalAltResult>> { | ||||
|         let transaction = client | ||||
|             .order_server(order) | ||||
|             .map_err(|e| Into::<Box<EvalAltResult>>::into(e.to_string()))?; | ||||
|         Ok(transaction) | ||||
|     } | ||||
|  | ||||
|     #[rhai_fn(name = "get_transaction_by_id", return_raw)] | ||||
|     pub fn get_transaction_by_id( | ||||
|         client: &mut Client, | ||||
|         transaction_id: &str, | ||||
|     ) -> Result<Transaction, Box<EvalAltResult>> { | ||||
|         let transaction = client | ||||
|             .get_transaction_by_id(transaction_id) | ||||
|             .map_err(|e| Into::<Box<EvalAltResult>>::into(e.to_string()))?; | ||||
|         Ok(transaction) | ||||
|     } | ||||
|  | ||||
|     #[rhai_fn(name = "get_transactions", return_raw)] | ||||
|     pub fn get_transactions(client: &mut Client) -> Result<Array, Box<EvalAltResult>> { | ||||
|         let transactions = client | ||||
|             .get_transactions() | ||||
|             .map_err(|e| Into::<Box<EvalAltResult>>::into(e.to_string()))?; | ||||
|         Ok(transactions.into_iter().map(Dynamic::from).collect()) | ||||
|     } | ||||
|  | ||||
|     #[rhai_fn(name = "get_auction_server_products", return_raw)] | ||||
|     pub fn get_auction_server_products(client: &mut Client) -> Result<Array, Box<EvalAltResult>> { | ||||
|         let products = client | ||||
|             .get_auction_server_products() | ||||
|             .map_err(|e| Into::<Box<EvalAltResult>>::into(e.to_string()))?; | ||||
|         Ok(products.into_iter().map(Dynamic::from).collect()) | ||||
|     } | ||||
|  | ||||
|     #[rhai_fn(name = "get_auction_server_product_by_id", return_raw)] | ||||
|     pub fn get_auction_server_product_by_id( | ||||
|         client: &mut Client, | ||||
|         product_id: &str, | ||||
|     ) -> Result<AuctionServerProduct, Box<EvalAltResult>> { | ||||
|         let product = client | ||||
|             .get_auction_server_product_by_id(product_id) | ||||
|             .map_err(|e| Into::<Box<EvalAltResult>>::into(e.to_string()))?; | ||||
|         Ok(product) | ||||
|     } | ||||
|  | ||||
|     #[rhai_fn(name = "get_auction_transactions", return_raw)] | ||||
|     pub fn get_auction_transactions(client: &mut Client) -> Result<Array, Box<EvalAltResult>> { | ||||
|         let transactions = client | ||||
|             .get_auction_transactions() | ||||
|             .map_err(|e| Into::<Box<EvalAltResult>>::into(e.to_string()))?; | ||||
|         Ok(transactions.into_iter().map(Dynamic::from).collect()) | ||||
|     } | ||||
|  | ||||
|     #[rhai_fn(name = "get_auction_transaction_by_id", return_raw)] | ||||
|     pub fn get_auction_transaction_by_id( | ||||
|         client: &mut Client, | ||||
|         transaction_id: &str, | ||||
|     ) -> Result<AuctionTransaction, Box<EvalAltResult>> { | ||||
|         let transaction = client | ||||
|             .get_auction_transaction_by_id(transaction_id) | ||||
|             .map_err(|e| Into::<Box<EvalAltResult>>::into(e.to_string()))?; | ||||
|         Ok(transaction) | ||||
|     } | ||||
|  | ||||
|     #[rhai_fn(name = "get_server_addon_products", return_raw)] | ||||
|     pub fn get_server_addon_products( | ||||
|         client: &mut Client, | ||||
|         server_number: i64, | ||||
|     ) -> Result<Array, Box<EvalAltResult>> { | ||||
|         let products = client | ||||
|             .get_server_addon_products(server_number) | ||||
|             .map_err(|e| Into::<Box<EvalAltResult>>::into(e.to_string()))?; | ||||
|         Ok(products.into_iter().map(Dynamic::from).collect()) | ||||
|     } | ||||
|  | ||||
|     #[rhai_fn(name = "get_server_addon_transactions", return_raw)] | ||||
|     pub fn get_server_addon_transactions( | ||||
|         client: &mut Client, | ||||
|     ) -> Result<Array, Box<EvalAltResult>> { | ||||
|         let transactions = client | ||||
|             .get_server_addon_transactions() | ||||
|             .map_err(|e| Into::<Box<EvalAltResult>>::into(e.to_string()))?; | ||||
|         Ok(transactions.into_iter().map(Dynamic::from).collect()) | ||||
|     } | ||||
|  | ||||
|     #[rhai_fn(name = "get_server_addon_transaction_by_id", return_raw)] | ||||
|     pub fn get_server_addon_transaction_by_id( | ||||
|         client: &mut Client, | ||||
|         transaction_id: &str, | ||||
|     ) -> Result<ServerAddonTransaction, Box<EvalAltResult>> { | ||||
|         let transaction = client | ||||
|             .get_server_addon_transaction_by_id(transaction_id) | ||||
|             .map_err(|e| Into::<Box<EvalAltResult>>::into(e.to_string()))?; | ||||
|         Ok(transaction) | ||||
|     } | ||||
|  | ||||
|     #[rhai_fn(name = "order_auction_server", return_raw)] | ||||
|     pub fn order_auction_server( | ||||
|         client: &mut Client, | ||||
|         order: OrderAuctionServerBuilder, | ||||
|     ) -> Result<AuctionTransaction, Box<EvalAltResult>> { | ||||
|         println!("Builder struct being used to order server: {:#?}", order); | ||||
|         let transaction = client.order_auction_server( | ||||
|             order.product_id, | ||||
|             order.authorized_keys.unwrap_or(vec![]), | ||||
|             order.dist, | ||||
|             None, | ||||
|             order.lang, | ||||
|             order.comment, | ||||
|             order.addon, | ||||
|             order.test, | ||||
|         ).map_err(|e| Into::<Box<EvalAltResult>>::into(e.to_string()))?; | ||||
|         Ok(transaction) | ||||
|     } | ||||
|  | ||||
|     #[rhai_fn(name = "order_server_addon", return_raw)] | ||||
|     pub fn order_server_addon( | ||||
|         client: &mut Client, | ||||
|         order: OrderServerAddonBuilder, | ||||
|     ) -> Result<ServerAddonTransaction, Box<EvalAltResult>> { | ||||
|         println!("Builder struct being used to order server addon: {:#?}", order); | ||||
|         let transaction = client | ||||
|             .order_server_addon(order) | ||||
|             .map_err(|e| Into::<Box<EvalAltResult>>::into(e.to_string()))?; | ||||
|         Ok(transaction) | ||||
|     } | ||||
| } | ||||
							
								
								
									
										89
									
								
								packages/clients/hetznerclient/src/rhai/ssh_keys.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										89
									
								
								packages/clients/hetznerclient/src/rhai/ssh_keys.rs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,89 @@ | ||||
| use crate::api::{Client, models::SshKey}; | ||||
| use prettytable::{Table, row}; | ||||
| use rhai::{Array, Dynamic, Engine, plugin::*}; | ||||
|  | ||||
| pub fn register(engine: &mut Engine) { | ||||
|     let ssh_keys_module = exported_module!(ssh_keys_api); | ||||
|     engine.register_global_module(ssh_keys_module.into()); | ||||
| } | ||||
|  | ||||
| #[export_module] | ||||
| pub mod ssh_keys_api { | ||||
|     use super::*; | ||||
|     use rhai::EvalAltResult; | ||||
|  | ||||
|     #[rhai_fn(name = "get_ssh_keys", return_raw)] | ||||
|     pub fn get_ssh_keys(client: &mut Client) -> Result<Array, Box<EvalAltResult>> { | ||||
|         let ssh_keys = client | ||||
|             .get_ssh_keys() | ||||
|             .map_err(|e| Into::<Box<EvalAltResult>>::into(e.to_string()))?; | ||||
|         Ok(ssh_keys.into_iter().map(Dynamic::from).collect()) | ||||
|     } | ||||
|  | ||||
|     #[rhai_fn(name = "get_ssh_key", return_raw)] | ||||
|     pub fn get_ssh_key( | ||||
|         client: &mut Client, | ||||
|         fingerprint: &str, | ||||
|     ) -> Result<SshKey, Box<EvalAltResult>> { | ||||
|         client | ||||
|             .get_ssh_key(fingerprint) | ||||
|             .map_err(|e| e.to_string().into()) | ||||
|     } | ||||
|  | ||||
|     #[rhai_fn(name = "add_ssh_key", return_raw)] | ||||
|     pub fn add_ssh_key( | ||||
|         client: &mut Client, | ||||
|         name: &str, | ||||
|         data: &str, | ||||
|     ) -> Result<SshKey, Box<EvalAltResult>> { | ||||
|         client | ||||
|             .add_ssh_key(name, data) | ||||
|             .map_err(|e| e.to_string().into()) | ||||
|     } | ||||
|  | ||||
|     #[rhai_fn(name = "update_ssh_key_name", return_raw)] | ||||
|     pub fn update_ssh_key_name( | ||||
|         client: &mut Client, | ||||
|         fingerprint: &str, | ||||
|         name: &str, | ||||
|     ) -> Result<SshKey, Box<EvalAltResult>> { | ||||
|         client | ||||
|             .update_ssh_key_name(fingerprint, name) | ||||
|             .map_err(|e| e.to_string().into()) | ||||
|     } | ||||
|  | ||||
|     #[rhai_fn(name = "delete_ssh_key", return_raw)] | ||||
|     pub fn delete_ssh_key( | ||||
|         client: &mut Client, | ||||
|         fingerprint: &str, | ||||
|     ) -> Result<(), Box<EvalAltResult>> { | ||||
|         client | ||||
|             .delete_ssh_key(fingerprint) | ||||
|             .map_err(|e| e.to_string().into()) | ||||
|     } | ||||
|  | ||||
|     #[rhai_fn(name = "pretty_print")] | ||||
|     pub fn pretty_print_ssh_keys(keys: Array) { | ||||
|         let mut table = Table::new(); | ||||
|         table.add_row(row![b => | ||||
|             "Name", | ||||
|             "Fingerprint", | ||||
|             "Type", | ||||
|             "Size", | ||||
|             "Created At" | ||||
|         ]); | ||||
|  | ||||
|         for key_dyn in keys { | ||||
|             if let Some(key) = key_dyn.try_cast::<SshKey>() { | ||||
|                 table.add_row(row![ | ||||
|                     key.name, | ||||
|                     key.fingerprint, | ||||
|                     key.key_type, | ||||
|                     key.size.to_string(), | ||||
|                     key.created_at | ||||
|                 ]); | ||||
|             } | ||||
|         } | ||||
|         table.printstd(); | ||||
|     } | ||||
| } | ||||
| @@ -26,6 +26,7 @@ sal-redisclient = { workspace = true } | ||||
| sal-postgresclient = { workspace = true } | ||||
| sal-virt = { workspace = true } | ||||
| sal-mycelium = { workspace = true } | ||||
| sal-hetzner = { workspace = true } | ||||
| sal-text = { workspace = true } | ||||
| sal-net = { workspace = true } | ||||
| sal-zinit-client = { workspace = true } | ||||
|   | ||||
| @@ -90,6 +90,9 @@ pub use sal_zinit_client::rhai::register_zinit_module; | ||||
| // Re-export mycelium module | ||||
| pub use sal_mycelium::rhai::register_mycelium_module; | ||||
|  | ||||
| // Re-export hetzner module | ||||
| pub use sal_hetzner::rhai::register_hetzner_module; | ||||
|  | ||||
| // Re-export text module | ||||
| pub use sal_text::rhai::register_text_module; | ||||
|  | ||||
| @@ -151,6 +154,9 @@ pub fn register(engine: &mut Engine) -> Result<(), Box<rhai::EvalAltResult>> { | ||||
|     // Register Mycelium module functions | ||||
|     sal_mycelium::rhai::register_mycelium_module(engine)?; | ||||
|  | ||||
|     // Register Hetzner module functions | ||||
|     sal_hetzner::rhai::register_hetzner_module(engine)?; | ||||
|  | ||||
|     // Register Text module functions | ||||
|     sal_text::rhai::register_text_module(engine)?; | ||||
|  | ||||
|   | ||||
| @@ -46,6 +46,9 @@ pub use sal_kubernetes as kubernetes; | ||||
| #[cfg(feature = "mycelium")] | ||||
| pub use sal_mycelium as mycelium; | ||||
|  | ||||
| #[cfg(feature = "hetzner")] | ||||
| pub use sal_hetzner as hetzner; | ||||
|  | ||||
| #[cfg(feature = "net")] | ||||
| pub use sal_net as net; | ||||
|  | ||||
|   | ||||
		Reference in New Issue
	
	Block a user