From 6f12a1bf0930e65e65160f3dbb5aca8ab4729dfc Mon Sep 17 00:00:00 2001 From: Maxime Van Hees Date: Wed, 16 Jul 2025 15:13:34 +0200 Subject: [PATCH] added functionality to create servers + example rhai script to demonstrate it --- Cargo.lock | 9 + Cargo.toml | 3 +- README.md | 44 ++++- create_server.rhai | 25 +++ src/async_handler.rs | 16 +- src/hetzner_api.rs | 95 ++++++++++- src/rhai_api.rs | 380 ++++++++++++++++++++++++++++++------------- 7 files changed, 453 insertions(+), 119 deletions(-) create mode 100644 create_server.rhai diff --git a/Cargo.lock b/Cargo.lock index 249fd98..c264294 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -466,6 +466,7 @@ dependencies = [ "prettytable-rs", "reqwest", "rhai", + "serde", "tokio", ] @@ -1261,6 +1262,7 @@ dependencies = [ "num-traits", "once_cell", "rhai_codegen", + "serde", "smallvec", "smartstring", "thin-vec", @@ -1519,6 +1521,9 @@ name = "smallvec" version = "1.15.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" +dependencies = [ + "serde", +] [[package]] name = "smartstring" @@ -1527,6 +1532,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3fb72c633efbaa2dd666986505016c32c3044395ceaf881518399d2f4127ee29" dependencies = [ "autocfg", + "serde", "static_assertions", "version_check", ] @@ -1662,6 +1668,9 @@ name = "thin-vec" version = "0.2.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "144f754d318415ac792f9d69fc87abbbfc043ce2ef041c60f16ad828f638717d" +dependencies = [ + "serde", +] [[package]] name = "thiserror" diff --git a/Cargo.toml b/Cargo.toml index 0db9a89..d6ba18c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -6,8 +6,9 @@ edition = "2024" [dependencies] hcloud = "0.21.0" reqwest = "0.12.22" -rhai = { version = "1.22.2", features = ["sync"] } +rhai = { version = "1.22.2", features = ["sync", "serde"] } tokio = { version = "1.46.1", features = ["full"] } ping = "0.6.1" prettytable-rs = "0.10.0" +serde = "1.0.219" diff --git a/README.md b/README.md index c76522b..06c8f70 100644 --- a/README.md +++ b/README.md @@ -39,7 +39,42 @@ All API interactions start by creating a client instance. let client = new_hetzner_client(HETZNER_API_TOKEN); ``` -### 2. List Servers & Display Details +### 2. Create a Server + +You can create a new server using the `ServerBuilder` pattern. This provides a flexible way to configure the server before creation. + +```rust +// Create a server builder with required parameters +let server_builder = new_server_builder("my-new-server", "cpx11", "ubuntu-20.04"); + +// Chain optional parameters +let server_builder = server_builder + .with_location("fsn1") + .with_datacenter("fsn1-dc14") + .with_start_after_create(true) + .with_user_data("#cloud-config\nruncmd:\n - [ ls, -l, / ]"); + +// Specify SSH key IDs. If this is omitted, all SSH keys in your project will be used. +let server_builder = server_builder.with_ssh_keys([12345, 67890]); + +// Create the server +let response = client.create_server(server_builder); + +print(`Server creation initiated for: ${response.name}`); +``` + +The following options are available on the `ServerBuilder`: + +| Method | Description | +| :--- | :--- | +| `with_location(string)` | Sets the server location (e.g., "fsn1"). | +| `with_datacenter(string)`| Sets the datacenter (e.g., "fsn1-dc14"). | +| `with_user_data(string)` | Provides user data for cloud-init. | +| `with_start_after_create(bool)` | Specifies whether the server should start after creation. | +| `with_ssh_keys(array)` | An array of SSH Key IDs to attach to the server. | + + +### 3. List Servers & Display Details You can fetch all your servers and display their details in a formatted table. @@ -54,7 +89,7 @@ let test_server = client.get_server(104301883); print(test_server.show_details()); ``` -### 3. List SSH Keys +### 4. List SSH Keys You can also list all the SSH keys in your project. @@ -65,7 +100,7 @@ let ssh_keys = client.list_ssh_keys(); print(ssh_keys.show_table()); ``` -### 4. Manage Server State +### 5. Manage Server State Perform actions like enabling rescue mode, disabling it, or rebooting the server. @@ -106,7 +141,7 @@ if ssh_key_from_env != "" { } ``` -### 5. Example Output (from `test.rhai` script) +### 6. Example Output (from `test.rhai` script) ```text Listing all servers... @@ -155,4 +190,3 @@ Listing details from server with ID 104301883... +-------------------+----------------------+ | Rescue Enabled | false | +-------------------+----------------------+ -``` diff --git a/create_server.rhai b/create_server.rhai new file mode 100644 index 0000000..1fd4a92 --- /dev/null +++ b/create_server.rhai @@ -0,0 +1,25 @@ +let client = new_hetzner_client(get_env("HETZNER_API_TOKEN")); + +let server_builder = new_server_builder("my-new-test-server", "cx22", "ubuntu-24.04"); + +// Example of using the new optional parameters +let server_builder = server_builder + .with_location("fsn1"); + // .with_datacenter("fsn1-dc14") + // .with_start_after_create(true) + // .with_user_data("#cloud-config\nruncmd:\n - [ ls, -l, / ]"); + +// Example of specifying SSH key IDs. +// If you don't call with_ssh_keys, all keys will be added by default. +// let server_builder = server_builder.with_ssh_keys([12345, 67890]); + +let response = client.create_server(server_builder); + +print(`Server creation initiated for: ${response.name}`); +print(` ID: ${response.id}`); +print(` Status: ${response.status}`); +if response.root_password != "" { + print(` Root Password: ${response.root_password}`); +} else { + print(" Root Password: Not set (SSH key id was provided)"); +} \ No newline at end of file diff --git a/src/async_handler.rs b/src/async_handler.rs index ad195d4..43cdb35 100644 --- a/src/async_handler.rs +++ b/src/async_handler.rs @@ -1,9 +1,12 @@ -use crate::hetzner_api::{HetznerClient, WrappedServer, WrappedSshKey}; +use crate::hetzner_api::{ + HetznerClient, ServerBuilder, WrappedCreateServerResponse, WrappedServer, WrappedSshKey, +}; use std::sync::mpsc::{Receiver, Sender}; use tokio::runtime::Builder; #[derive(Clone)] pub enum Request { + CreateServer(HetznerClient, ServerBuilder), ListServers(HetznerClient), GetServerStatus(HetznerClient, i64), GetServer(HetznerClient, i64), @@ -16,6 +19,7 @@ pub enum Request { } pub enum Response { + CreateServer(Result), ListSshKeys(Result, String>), ListServers(Result, String>), GetServerStatus(Result), @@ -38,12 +42,20 @@ pub fn run_worker( while let Ok(request) = command_rx.recv() { let response = match request { + Request::CreateServer(client, builder) => { + let result = rt + .block_on(client.create_server(builder)) + .map_err(|e| e.to_string()); + Response::CreateServer(result) + } Request::ListServers(client) => { let result = rt.block_on(client.list_servers()).map_err(|e| e.to_string()); Response::ListServers(result) } Request::GetServerStatus(client, server_id) => { - let result = rt.block_on(client.get_server_status(server_id)).map_err(|e| e.to_string()); + let result = rt + .block_on(client.get_server_status(server_id)) + .map_err(|e| e.to_string()); Response::GetServerStatus(result) } Request::GetServer(client, server_id) => { diff --git a/src/hetzner_api.rs b/src/hetzner_api.rs index 2546708..e75bdfe 100644 --- a/src/hetzner_api.rs +++ b/src/hetzner_api.rs @@ -2,11 +2,14 @@ use hcloud::apis::{ configuration::Configuration, servers_api::{ self, DisableRescueModeForServerParams, EnableRescueModeForServerParams, ListServersParams, - ResetServerParams, + ResetServerParams, CreateServerParams, }, ssh_keys_api::{self, ListSshKeysParams}, }; -use hcloud::models::{EnableRescueModeForServerRequest, Server, SshKey}; +use hcloud::models::{ + CreateServerRequest, CreateServerResponse, EnableRescueModeForServerRequest, Server, SshKey, +}; +use serde::{Deserialize, Serialize}; #[derive(Debug, Clone)] pub struct HetznerClient { @@ -19,6 +22,77 @@ pub struct WrappedServer(pub Server); #[derive(Clone)] pub struct WrappedSshKey(pub SshKey); +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct ServerBuilder { + pub name: String, + pub server_type: String, + pub image: String, + pub location: Option, + pub datacenter: Option, + pub user_data: Option, + pub start_after_create: Option, + pub ssh_keys: Option>, +} + +impl ServerBuilder { + pub fn new(name: String, server_type: String, image: String) -> Self { + Self { + name, + server_type, + image, + location: None, + datacenter: None, + user_data: None, + start_after_create: None, + ssh_keys: None, + } + } + + pub fn with_location(mut self, location: String) -> Self { + self.location = Some(location); + self + } + + pub fn with_datacenter(mut self, datacenter: String) -> Self { + self.datacenter = Some(datacenter); + self + } + + pub fn with_user_data(mut self, user_data: String) -> Self { + self.user_data = Some(user_data); + self + } + + pub fn with_start_after_create(mut self, start_after_create: bool) -> Self { + self.start_after_create = Some(start_after_create); + self + } + + pub fn with_ssh_keys(mut self, ssh_keys: Vec) -> Self { + self.ssh_keys = Some(ssh_keys); + self + } + + pub fn build(self) -> CreateServerRequest { + CreateServerRequest { + name: self.name, + server_type: self.server_type, + image: self.image, + location: self.location, + datacenter: self.datacenter, + user_data: self.user_data, + start_after_create: self.start_after_create, + ssh_keys: self + .ssh_keys + .map(|keys| keys.into_iter().map(|id| id.to_string()).collect()), + ..Default::default() + } + } +} + +#[derive(Clone)] +pub struct WrappedCreateServerResponse(pub CreateServerResponse); + impl HetznerClient { pub fn new(api_token: &str) -> Self { let mut configuration = Configuration::new(); @@ -32,6 +106,23 @@ impl HetznerClient { Self { configuration } } + pub async fn create_server( + &self, + mut builder: ServerBuilder, + ) -> Result> { + if builder.ssh_keys.is_none() { + let all_keys = self.list_ssh_keys().await?; + let key_ids: Vec = all_keys.into_iter().map(|k| k.0.id).collect(); + builder.ssh_keys = Some(key_ids); + } + + let params = CreateServerParams { + create_server_request: Some(builder.build()), + }; + let response = servers_api::create_server(&self.configuration, params).await?; + Ok(WrappedCreateServerResponse(response)) + } + pub async fn list_servers(&self) -> Result, Box> { let mut all_servers = Vec::new(); let mut page = 1; diff --git a/src/rhai_api.rs b/src/rhai_api.rs index d0eda48..47009cd 100644 --- a/src/rhai_api.rs +++ b/src/rhai_api.rs @@ -1,10 +1,15 @@ -use crate::async_handler::Response; use crate::async_handler::Request; -use crate::hetzner_api::{HetznerClient, WrappedServer, WrappedSshKey}; +use crate::async_handler::Response; +use crate::hetzner_api::{ + HetznerClient, ServerBuilder, WrappedCreateServerResponse, WrappedServer, WrappedSshKey, +}; +use prettytable::{Cell, Row, Table}; use rhai::{Engine, EvalAltResult}; use std::env; -use std::sync::{mpsc::{Receiver, Sender}, Arc, Mutex}; -use prettytable::{Table, Row, Cell}; +use std::sync::{ + Arc, Mutex, + mpsc::{Receiver, Sender}, +}; #[derive(Clone)] struct ApiBridge { @@ -14,7 +19,10 @@ struct ApiBridge { impl ApiBridge { fn new(command_tx: Sender, reply_rx: Arc>>) -> Self { - Self { command_tx, reply_rx } + Self { + command_tx, + reply_rx, + } } fn call( @@ -23,7 +31,12 @@ impl ApiBridge { response_handler: impl FnOnce(Response) -> Result>, ) -> Result> { self.command_tx.send(request).map_err(|e| e.to_string())?; - let response = self.reply_rx.lock().unwrap().recv().map_err(|e| e.to_string())?; + let response = self + .reply_rx + .lock() + .unwrap() + .recv() + .map_err(|e| e.to_string())?; response_handler(response) } } @@ -37,171 +50,313 @@ pub fn register_hetzner_api( engine .register_type_with_name::("HetznerClient") - .register_fn("new_hetzner_client", |api_token: &str| -> Result> { - if api_token.is_empty() { - return Err("HETZNER_API_TOKEN cannot be empty.".into()); - } - Ok(HetznerClient::new(api_token)) - }) + .register_fn( + "new_hetzner_client", + |api_token: &str| -> Result> { + if api_token.is_empty() { + return Err("HETZNER_API_TOKEN cannot be empty.".into()); + } + Ok(HetznerClient::new(api_token)) + }, + ) .register_fn("list_servers", { let bridge = api_bridge.clone(); move |client: &mut HetznerClient| { - bridge.call(Request::ListServers(client.clone()), |response| match response { - Response::ListServers(result) => result.map_err(|e| e.into()), - _ => Err("Unexpected response".into()), - }) + bridge.call( + Request::ListServers(client.clone()), + |response| match response { + Response::ListServers(result) => result.map_err(|e| e.into()), + _ => Err("Unexpected response".into()), + }, + ) } }) .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()), - }) + 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| { - bridge.call(Request::GetServerStatus(client.clone(), server_id), |response| match response { - Response::GetServerStatus(result) => result.map_err(|e| e.into()), - _ => Err("Unexpected response".into()), - }) + bridge.call( + Request::GetServerStatus(client.clone(), server_id), + |response| match response { + Response::GetServerStatus(result) => result.map_err(|e| e.into()), + _ => Err("Unexpected response".into()), + }, + ) } }) .register_fn("get_server", { let bridge = api_bridge.clone(); move |client: &mut HetznerClient, server_id: i64| { - bridge.call(Request::GetServer(client.clone(), server_id), |response| match response { - Response::GetServer(result) => result.map_err(|e| e.into()), - _ => Err("Unexpected response".into()), + bridge.call( + Request::GetServer(client.clone(), server_id), + |response| match response { + Response::GetServer(result) => result.map_err(|e| e.into()), + _ => Err("Unexpected response".into()), + }, + ) + } + }) + .register_fn("create_server", { + let bridge = api_bridge.clone(); + move |client: &mut HetznerClient, builder: ServerBuilder| { + bridge.call(Request::CreateServer(client.clone(), builder), |response| { + match response { + Response::CreateServer(result) => result.map_err(|e| e.into()), + _ => Err("Unexpected response".into()), + } }) } }); + engine + .register_type_with_name::("ServerBuilder") + .register_fn( + "new_server_builder", + |name: String, server_type: String, image: String| { + ServerBuilder::new(name, server_type, image) + }, + ) + .register_fn("with_location", ServerBuilder::with_location) + .register_fn("with_datacenter", ServerBuilder::with_datacenter) + .register_fn("with_user_data", ServerBuilder::with_user_data) + .register_fn( + "with_start_after_create", + ServerBuilder::with_start_after_create, + ) + .register_fn( + "with_ssh_keys", + |builder: ServerBuilder, ssh_keys: rhai::Array| { + builder.with_ssh_keys( + ssh_keys + .into_iter() + .map(|k| k.as_int().unwrap()) + .collect(), + ) + }, + ); + + engine + .register_type_with_name::("CreateServerResponse") + .register_get("id", |resp: &mut WrappedCreateServerResponse| { + resp.0.server.id + }) + .register_get("name", |resp: &mut WrappedCreateServerResponse| { + resp.0.server.name.clone() + }) + .register_get("status", |resp: &mut WrappedCreateServerResponse| { + format!("{:?}", resp.0.server.status) + }) + .register_get("root_password", |resp: &mut WrappedCreateServerResponse| { + resp.0.root_password.clone().unwrap_or_default() + }); + engine .register_type_with_name::("Server") .register_get("id", |server: &mut WrappedServer| server.0.id) .register_get("name", |server: &mut WrappedServer| server.0.name.clone()) - .register_get("status", |server: &mut WrappedServer| format!("{:?}", server.0.status)) - .register_get("created", |server: &mut WrappedServer| server.0.created.clone()) - .register_get("public_ipv4", |server: &mut WrappedServer| server.0.public_net.ipv4.clone().unwrap().ip) - .register_get("server_type", |server: &mut WrappedServer| server.0.server_type.clone().name) - .register_get("included_traffic", |server: &mut WrappedServer| server.0.included_traffic.unwrap_or(0)) - .register_get("ingoing_traffic", |server: &mut WrappedServer| server.0.ingoing_traffic.unwrap_or(0)) - .register_get("outgoing_traffic", |server: &mut WrappedServer| server.0.outgoing_traffic.unwrap_or(0)) - .register_get("primary_disk_size", |server: &mut WrappedServer| server.0.primary_disk_size) - .register_get("rescue_enabled", |server: &mut WrappedServer| server.0.rescue_enabled); + .register_get("status", |server: &mut WrappedServer| { + format!("{:?}", server.0.status) + }) + .register_get("created", |server: &mut WrappedServer| { + server.0.created.clone() + }) + .register_get("public_ipv4", |server: &mut WrappedServer| { + server.0.public_net.ipv4.clone().unwrap().ip + }) + .register_get("server_type", |server: &mut WrappedServer| { + server.0.server_type.clone().name + }) + .register_get("included_traffic", |server: &mut WrappedServer| { + server.0.included_traffic.unwrap_or(0) + }) + .register_get("ingoing_traffic", |server: &mut WrappedServer| { + server.0.ingoing_traffic.unwrap_or(0) + }) + .register_get("outgoing_traffic", |server: &mut WrappedServer| { + server.0.outgoing_traffic.unwrap_or(0) + }) + .register_get("primary_disk_size", |server: &mut WrappedServer| { + server.0.primary_disk_size + }) + .register_get("rescue_enabled", |server: &mut WrappedServer| { + server.0.rescue_enabled + }); engine .register_fn("reboot", { let bridge = api_bridge.clone(); move |client: &mut HetznerClient, server_id: i64| { - bridge.call(Request::RebootServer(client.clone(), server_id), |response| { - match response { + bridge.call( + Request::RebootServer(client.clone(), server_id), + |response| match response { Response::RebootServer(result) => result.map_err(|e| e.into()), _ => Err("Unexpected response".into()), - } - }) + }, + ) } }) .register_fn("reset", { let bridge = api_bridge.clone(); move |client: &mut HetznerClient, server_id: i64| { - bridge.call(Request::ResetServer(client.clone(), server_id), |response| { - match response { + bridge.call( + Request::ResetServer(client.clone(), server_id), + |response| match response { Response::ResetServer(result) => result.map_err(|e| e.into()), _ => Err("Unexpected response".into()), - } - }) + }, + ) } }) .register_fn("enable_rescue_mode", { let bridge = api_bridge.clone(); move |client: &mut HetznerClient, server_id: i64| { - bridge.call(Request::EnableRescueModeWithAllKeys(client.clone(), server_id), |response| { - match response { + bridge.call( + Request::EnableRescueModeWithAllKeys(client.clone(), server_id), + |response| match response { Response::EnableRescueMode(result) => result.map_err(|e| e.into()), _ => Err("Unexpected response".into()), - } - }) + }, + ) } }) .register_fn("enable_rescue_mode", { let bridge = api_bridge.clone(); move |client: &mut HetznerClient, server_id: i64, ssh_key: i64| { - bridge.call(Request::EnableRescueMode(client.clone(), server_id, vec![ssh_key]), |response| { - match response { + bridge.call( + Request::EnableRescueMode(client.clone(), server_id, vec![ssh_key]), + |response| match response { Response::EnableRescueMode(result) => result.map_err(|e| e.into()), _ => Err("Unexpected response".into()), - } - }) + }, + ) } }) .register_fn("enable_rescue_mode", { let bridge = api_bridge.clone(); move |client: &mut HetznerClient, server_id: i64, ssh_keys: rhai::Array| { - let keys: Vec = ssh_keys.into_iter().map(|k| k.as_int().unwrap_or(0)).collect(); - bridge.call(Request::EnableRescueMode(client.clone(), server_id, keys), |response| { - match response { + let keys: Vec = ssh_keys + .into_iter() + .map(|k| k.as_int().unwrap_or(0)) + .collect(); + bridge.call( + Request::EnableRescueMode(client.clone(), server_id, keys), + |response| match response { Response::EnableRescueMode(result) => result.map_err(|e| e.into()), _ => Err("Unexpected response".into()), - } - }) + }, + ) } }) .register_fn("disable_rescue_mode", { let bridge = api_bridge.clone(); move |client: &mut HetznerClient, server_id: i64| { - bridge.call(Request::DisableRescueMode(client.clone(), server_id), |response| { - match response { + bridge.call( + Request::DisableRescueMode(client.clone(), server_id), + |response| match response { Response::DisableRescueMode(result) => result.map_err(|e| e.into()), _ => Err("Unexpected response".into()), - } - }) + }, + ) } }); 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", |servers: &mut Vec| -> Result> { - let mut table = Table::new(); - table.set_titles(Row::new(vec![Cell::new("Server List").style_spec("c")])); - table.add_row(Row::new(vec![ - Cell::new("ID"), - Cell::new("Name"), - Cell::new("Status"), - Cell::new("Public IPv4"), - ])); - for server in servers { + .register_indexer_get(|list: &mut Vec, index: i64| { + list[index as usize].clone() + }) + .register_fn( + "show_table", + |servers: &mut Vec| -> Result> { + let mut table = Table::new(); + table.set_titles(Row::new(vec![Cell::new("Server List").style_spec("c")])); table.add_row(Row::new(vec![ + Cell::new("ID"), + Cell::new("Name"), + Cell::new("Status"), + Cell::new("Public IPv4"), + ])); + for server in servers { + table.add_row(Row::new(vec![ + Cell::new(&server.0.id.to_string()), + Cell::new(&server.0.name), + Cell::new(&format!("{:?}", server.0.status)), + Cell::new(&server.0.public_net.ipv4.clone().unwrap().ip.to_string()), + ])); + } + Ok(table.to_string()) + }, + ) + .register_fn( + "show_details", + |server: &mut WrappedServer| -> Result> { + let mut table = Table::new(); + table.set_titles(Row::new(vec![Cell::new(&server.0.name).style_spec("c")])); + table.add_row(Row::new(vec![ + Cell::new("ID"), Cell::new(&server.0.id.to_string()), - Cell::new(&server.0.name), + ])); + table.add_row(Row::new(vec![ + Cell::new("Status"), Cell::new(&format!("{:?}", server.0.status)), + ])); + table.add_row(Row::new(vec![ + Cell::new("Created"), + Cell::new(&server.0.created), + ])); + table.add_row(Row::new(vec![ + Cell::new("IPv4"), Cell::new(&server.0.public_net.ipv4.clone().unwrap().ip.to_string()), ])); - } - Ok(table.to_string()) - }) - .register_fn("show_details", |server: &mut WrappedServer| -> Result> { - let mut table = Table::new(); - table.set_titles(Row::new(vec![Cell::new(&server.0.name).style_spec("c")])); - table.add_row(Row::new(vec![Cell::new("ID"), Cell::new(&server.0.id.to_string())])); - table.add_row(Row::new(vec![Cell::new("Status"), Cell::new(&format!("{:?}", server.0.status))])); - table.add_row(Row::new(vec![Cell::new("Created"), Cell::new(&server.0.created)])); - table.add_row(Row::new(vec![Cell::new("IPv4"), Cell::new(&server.0.public_net.ipv4.clone().unwrap().ip.to_string())])); - table.add_row(Row::new(vec![Cell::new("Type"), Cell::new(&server.0.server_type.name)])); - table.add_row(Row::new(vec![Cell::new("Included Traffic"), Cell::new(&format!("{} GB", server.0.included_traffic.unwrap_or(0) / 1024 / 1024 / 1024))])); - table.add_row(Row::new(vec![Cell::new("Ingoing Traffic"), Cell::new(&format!("{} MB", server.0.ingoing_traffic.unwrap_or(0) / 1024 / 1024))])); - table.add_row(Row::new(vec![Cell::new("Outgoing Traffic"), Cell::new(&format!("{} MB", server.0.outgoing_traffic.unwrap_or(0) / 1024 / 1024))])); - table.add_row(Row::new(vec![Cell::new("Primary Disk Size"), Cell::new(&server.0.primary_disk_size.to_string())])); - table.add_row(Row::new(vec![Cell::new("Rescue Enabled"), Cell::new(&server.0.rescue_enabled.to_string())])); + table.add_row(Row::new(vec![ + Cell::new("Type"), + Cell::new(&server.0.server_type.name), + ])); + table.add_row(Row::new(vec![ + Cell::new("Included Traffic"), + Cell::new(&format!( + "{} GB", + server.0.included_traffic.unwrap_or(0) / 1024 / 1024 / 1024 + )), + ])); + table.add_row(Row::new(vec![ + Cell::new("Ingoing Traffic"), + Cell::new(&format!( + "{} MB", + server.0.ingoing_traffic.unwrap_or(0) / 1024 / 1024 + )), + ])); + table.add_row(Row::new(vec![ + Cell::new("Outgoing Traffic"), + Cell::new(&format!( + "{} MB", + server.0.outgoing_traffic.unwrap_or(0) / 1024 / 1024 + )), + ])); + table.add_row(Row::new(vec![ + Cell::new("Primary Disk Size"), + Cell::new(&server.0.primary_disk_size.to_string()), + ])); + table.add_row(Row::new(vec![ + Cell::new("Rescue Enabled"), + Cell::new(&server.0.rescue_enabled.to_string()), + ])); - Ok(table.to_string()) - }); + Ok(table.to_string()) + }, + ); engine.register_fn("get_env", |key: &str| -> String { env::var(key).unwrap_or("".to_string()) @@ -211,27 +366,34 @@ pub fn register_hetzner_api( .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()); + .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 { + .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(&key.0.id.to_string()), - Cell::new(&key.0.name), - Cell::new(&key.0.fingerprint), + Cell::new("ID"), + Cell::new("Name"), + Cell::new("Fingerprint"), ])); - } - Ok(table.to_string()) - }); -} \ No newline at end of file + 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()) + }, + ); +}