added functionality to create servers + example rhai script to demonstrate it

This commit is contained in:
Maxime Van Hees 2025-07-16 15:13:34 +02:00
parent 34e810b611
commit 6f12a1bf09
7 changed files with 453 additions and 119 deletions

9
Cargo.lock generated
View File

@ -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"

View File

@ -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"

View File

@ -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 |
+-------------------+----------------------+
```

25
create_server.rhai Normal file
View File

@ -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)");
}

View File

@ -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<WrappedCreateServerResponse, String>),
ListSshKeys(Result<Vec<WrappedSshKey>, String>),
ListServers(Result<Vec<WrappedServer>, String>),
GetServerStatus(Result<String, String>),
@ -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) => {

View File

@ -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<String>,
pub datacenter: Option<String>,
pub user_data: Option<String>,
pub start_after_create: Option<bool>,
pub ssh_keys: Option<Vec<i64>>,
}
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<i64>) -> 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<WrappedCreateServerResponse, Box<dyn std::error::Error>> {
if builder.ssh_keys.is_none() {
let all_keys = self.list_ssh_keys().await?;
let key_ids: Vec<i64> = 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<Vec<WrappedServer>, Box<dyn std::error::Error>> {
let mut all_servers = Vec::new();
let mut page = 1;

View File

@ -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<Request>, reply_rx: Arc<Mutex<Receiver<Response>>>) -> Self {
Self { command_tx, reply_rx }
Self {
command_tx,
reply_rx,
}
}
fn call<T>(
@ -23,7 +31,12 @@ impl ApiBridge {
response_handler: impl FnOnce(Response) -> Result<T, Box<EvalAltResult>>,
) -> Result<T, Box<EvalAltResult>> {
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>("HetznerClient")
.register_fn("new_hetzner_client", |api_token: &str| -> Result<HetznerClient, Box<EvalAltResult>> {
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<HetznerClient, Box<EvalAltResult>> {
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>("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::<WrappedCreateServerResponse>("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::<WrappedServer>("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<i64> = 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<i64> = 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::<Vec<WrappedServer>>()
.register_fn("len", |list: &mut Vec<WrappedServer>| list.len() as i64)
.register_indexer_get(|list: &mut Vec<WrappedServer>, index: i64| list[index as usize].clone())
.register_fn("show_table", |servers: &mut Vec<WrappedServer>| -> Result<String, Box<EvalAltResult>> {
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<WrappedServer>, index: i64| {
list[index as usize].clone()
})
.register_fn(
"show_table",
|servers: &mut Vec<WrappedServer>| -> Result<String, Box<EvalAltResult>> {
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<String, Box<EvalAltResult>> {
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<String, Box<EvalAltResult>> {
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::<WrappedSshKey>("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::<Vec<WrappedSshKey>>()
.register_fn("len", |list: &mut Vec<WrappedSshKey>| list.len() as i64)
.register_indexer_get(|list: &mut Vec<WrappedSshKey>, index: i64| list[index as usize].clone())
.register_fn("show_table", |keys: &mut Vec<WrappedSshKey>| -> Result<String, Box<EvalAltResult>> {
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<WrappedSshKey>, index: i64| {
list[index as usize].clone()
})
.register_fn(
"show_table",
|keys: &mut Vec<WrappedSshKey>| -> Result<String, Box<EvalAltResult>> {
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())
});
}
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())
},
);
}