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", "prettytable-rs",
"reqwest", "reqwest",
"rhai", "rhai",
"serde",
"tokio", "tokio",
] ]
@ -1261,6 +1262,7 @@ dependencies = [
"num-traits", "num-traits",
"once_cell", "once_cell",
"rhai_codegen", "rhai_codegen",
"serde",
"smallvec", "smallvec",
"smartstring", "smartstring",
"thin-vec", "thin-vec",
@ -1519,6 +1521,9 @@ name = "smallvec"
version = "1.15.1" version = "1.15.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03"
dependencies = [
"serde",
]
[[package]] [[package]]
name = "smartstring" name = "smartstring"
@ -1527,6 +1532,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3fb72c633efbaa2dd666986505016c32c3044395ceaf881518399d2f4127ee29" checksum = "3fb72c633efbaa2dd666986505016c32c3044395ceaf881518399d2f4127ee29"
dependencies = [ dependencies = [
"autocfg", "autocfg",
"serde",
"static_assertions", "static_assertions",
"version_check", "version_check",
] ]
@ -1662,6 +1668,9 @@ name = "thin-vec"
version = "0.2.14" version = "0.2.14"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "144f754d318415ac792f9d69fc87abbbfc043ce2ef041c60f16ad828f638717d" checksum = "144f754d318415ac792f9d69fc87abbbfc043ce2ef041c60f16ad828f638717d"
dependencies = [
"serde",
]
[[package]] [[package]]
name = "thiserror" name = "thiserror"

View File

@ -6,8 +6,9 @@ edition = "2024"
[dependencies] [dependencies]
hcloud = "0.21.0" hcloud = "0.21.0"
reqwest = "0.12.22" 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"] } tokio = { version = "1.46.1", features = ["full"] }
ping = "0.6.1" ping = "0.6.1"
prettytable-rs = "0.10.0" 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); 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. 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()); print(test_server.show_details());
``` ```
### 3. List SSH Keys ### 4. List SSH Keys
You can also list all the SSH keys in your project. 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()); 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. 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 ```text
Listing all servers... Listing all servers...
@ -155,4 +190,3 @@ Listing details from server with ID 104301883...
+-------------------+----------------------+ +-------------------+----------------------+
| Rescue Enabled | false | | 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 std::sync::mpsc::{Receiver, Sender};
use tokio::runtime::Builder; use tokio::runtime::Builder;
#[derive(Clone)] #[derive(Clone)]
pub enum Request { pub enum Request {
CreateServer(HetznerClient, ServerBuilder),
ListServers(HetznerClient), ListServers(HetznerClient),
GetServerStatus(HetznerClient, i64), GetServerStatus(HetznerClient, i64),
GetServer(HetznerClient, i64), GetServer(HetznerClient, i64),
@ -16,6 +19,7 @@ pub enum Request {
} }
pub enum Response { pub enum Response {
CreateServer(Result<WrappedCreateServerResponse, String>),
ListSshKeys(Result<Vec<WrappedSshKey>, String>), ListSshKeys(Result<Vec<WrappedSshKey>, String>),
ListServers(Result<Vec<WrappedServer>, String>), ListServers(Result<Vec<WrappedServer>, String>),
GetServerStatus(Result<String, String>), GetServerStatus(Result<String, String>),
@ -38,12 +42,20 @@ pub fn run_worker(
while let Ok(request) = command_rx.recv() { while let Ok(request) = command_rx.recv() {
let response = match request { 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) => { Request::ListServers(client) => {
let result = rt.block_on(client.list_servers()).map_err(|e| e.to_string()); let result = rt.block_on(client.list_servers()).map_err(|e| e.to_string());
Response::ListServers(result) Response::ListServers(result)
} }
Request::GetServerStatus(client, server_id) => { 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) Response::GetServerStatus(result)
} }
Request::GetServer(client, server_id) => { Request::GetServer(client, server_id) => {

View File

@ -2,11 +2,14 @@ use hcloud::apis::{
configuration::Configuration, configuration::Configuration,
servers_api::{ servers_api::{
self, DisableRescueModeForServerParams, EnableRescueModeForServerParams, ListServersParams, self, DisableRescueModeForServerParams, EnableRescueModeForServerParams, ListServersParams,
ResetServerParams, ResetServerParams, CreateServerParams,
}, },
ssh_keys_api::{self, ListSshKeysParams}, 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)] #[derive(Debug, Clone)]
pub struct HetznerClient { pub struct HetznerClient {
@ -19,6 +22,77 @@ pub struct WrappedServer(pub Server);
#[derive(Clone)] #[derive(Clone)]
pub struct WrappedSshKey(pub SshKey); 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 { impl HetznerClient {
pub fn new(api_token: &str) -> Self { pub fn new(api_token: &str) -> Self {
let mut configuration = Configuration::new(); let mut configuration = Configuration::new();
@ -32,6 +106,23 @@ impl HetznerClient {
Self { configuration } 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>> { pub async fn list_servers(&self) -> Result<Vec<WrappedServer>, Box<dyn std::error::Error>> {
let mut all_servers = Vec::new(); let mut all_servers = Vec::new();
let mut page = 1; let mut page = 1;

View File

@ -1,10 +1,15 @@
use crate::async_handler::Response;
use crate::async_handler::Request; 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 rhai::{Engine, EvalAltResult};
use std::env; use std::env;
use std::sync::{mpsc::{Receiver, Sender}, Arc, Mutex}; use std::sync::{
use prettytable::{Table, Row, Cell}; Arc, Mutex,
mpsc::{Receiver, Sender},
};
#[derive(Clone)] #[derive(Clone)]
struct ApiBridge { struct ApiBridge {
@ -14,7 +19,10 @@ struct ApiBridge {
impl ApiBridge { impl ApiBridge {
fn new(command_tx: Sender<Request>, reply_rx: Arc<Mutex<Receiver<Response>>>) -> Self { 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>( fn call<T>(
@ -23,7 +31,12 @@ impl ApiBridge {
response_handler: impl FnOnce(Response) -> Result<T, Box<EvalAltResult>>, response_handler: impl FnOnce(Response) -> Result<T, Box<EvalAltResult>>,
) -> Result<T, Box<EvalAltResult>> { ) -> Result<T, Box<EvalAltResult>> {
self.command_tx.send(request).map_err(|e| e.to_string())?; 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) response_handler(response)
} }
} }
@ -37,171 +50,313 @@ pub fn register_hetzner_api(
engine engine
.register_type_with_name::<HetznerClient>("HetznerClient") .register_type_with_name::<HetznerClient>("HetznerClient")
.register_fn("new_hetzner_client", |api_token: &str| -> Result<HetznerClient, Box<EvalAltResult>> { .register_fn(
if api_token.is_empty() { "new_hetzner_client",
return Err("HETZNER_API_TOKEN cannot be empty.".into()); |api_token: &str| -> Result<HetznerClient, Box<EvalAltResult>> {
} if api_token.is_empty() {
Ok(HetznerClient::new(api_token)) return Err("HETZNER_API_TOKEN cannot be empty.".into());
}) }
Ok(HetznerClient::new(api_token))
},
)
.register_fn("list_servers", { .register_fn("list_servers", {
let bridge = api_bridge.clone(); let bridge = api_bridge.clone();
move |client: &mut HetznerClient| { move |client: &mut HetznerClient| {
bridge.call(Request::ListServers(client.clone()), |response| match response { bridge.call(
Response::ListServers(result) => result.map_err(|e| e.into()), Request::ListServers(client.clone()),
_ => Err("Unexpected response".into()), |response| match response {
}) Response::ListServers(result) => result.map_err(|e| e.into()),
_ => Err("Unexpected response".into()),
},
)
} }
}) })
.register_fn("list_ssh_keys", { .register_fn("list_ssh_keys", {
let bridge = api_bridge.clone(); let bridge = api_bridge.clone();
move |client: &mut HetznerClient| { move |client: &mut HetznerClient| {
bridge.call(Request::ListSshKeys(client.clone()), |response| match response { bridge.call(
Response::ListSshKeys(result) => result.map_err(|e| e.into()), Request::ListSshKeys(client.clone()),
_ => Err("Unexpected response".into()), |response| match response {
}) Response::ListSshKeys(result) => result.map_err(|e| e.into()),
_ => Err("Unexpected response".into()),
},
)
} }
}) })
.register_fn("get_server_status", { .register_fn("get_server_status", {
let bridge = api_bridge.clone(); let bridge = api_bridge.clone();
move |client: &mut HetznerClient, server_id: i64| { move |client: &mut HetznerClient, server_id: i64| {
bridge.call(Request::GetServerStatus(client.clone(), server_id), |response| match response { bridge.call(
Response::GetServerStatus(result) => result.map_err(|e| e.into()), Request::GetServerStatus(client.clone(), server_id),
_ => Err("Unexpected response".into()), |response| match response {
}) Response::GetServerStatus(result) => result.map_err(|e| e.into()),
_ => Err("Unexpected response".into()),
},
)
} }
}) })
.register_fn("get_server", { .register_fn("get_server", {
let bridge = api_bridge.clone(); let bridge = api_bridge.clone();
move |client: &mut HetznerClient, server_id: i64| { move |client: &mut HetznerClient, server_id: i64| {
bridge.call(Request::GetServer(client.clone(), server_id), |response| match response { bridge.call(
Response::GetServer(result) => result.map_err(|e| e.into()), Request::GetServer(client.clone(), server_id),
_ => Err("Unexpected response".into()), |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 engine
.register_type_with_name::<WrappedServer>("Server") .register_type_with_name::<WrappedServer>("Server")
.register_get("id", |server: &mut WrappedServer| server.0.id) .register_get("id", |server: &mut WrappedServer| server.0.id)
.register_get("name", |server: &mut WrappedServer| server.0.name.clone()) .register_get("name", |server: &mut WrappedServer| server.0.name.clone())
.register_get("status", |server: &mut WrappedServer| format!("{:?}", server.0.status)) .register_get("status", |server: &mut WrappedServer| {
.register_get("created", |server: &mut WrappedServer| server.0.created.clone()) format!("{:?}", server.0.status)
.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("created", |server: &mut WrappedServer| {
.register_get("included_traffic", |server: &mut WrappedServer| server.0.included_traffic.unwrap_or(0)) server.0.created.clone()
.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("public_ipv4", |server: &mut WrappedServer| {
.register_get("primary_disk_size", |server: &mut WrappedServer| server.0.primary_disk_size) server.0.public_net.ipv4.clone().unwrap().ip
.register_get("rescue_enabled", |server: &mut WrappedServer| server.0.rescue_enabled); })
.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 engine
.register_fn("reboot", { .register_fn("reboot", {
let bridge = api_bridge.clone(); let bridge = api_bridge.clone();
move |client: &mut HetznerClient, server_id: i64| { move |client: &mut HetznerClient, server_id: i64| {
bridge.call(Request::RebootServer(client.clone(), server_id), |response| { bridge.call(
match response { Request::RebootServer(client.clone(), server_id),
|response| match response {
Response::RebootServer(result) => result.map_err(|e| e.into()), Response::RebootServer(result) => result.map_err(|e| e.into()),
_ => Err("Unexpected response".into()), _ => Err("Unexpected response".into()),
} },
}) )
} }
}) })
.register_fn("reset", { .register_fn("reset", {
let bridge = api_bridge.clone(); let bridge = api_bridge.clone();
move |client: &mut HetznerClient, server_id: i64| { move |client: &mut HetznerClient, server_id: i64| {
bridge.call(Request::ResetServer(client.clone(), server_id), |response| { bridge.call(
match response { Request::ResetServer(client.clone(), server_id),
|response| match response {
Response::ResetServer(result) => result.map_err(|e| e.into()), Response::ResetServer(result) => result.map_err(|e| e.into()),
_ => Err("Unexpected response".into()), _ => Err("Unexpected response".into()),
} },
}) )
} }
}) })
.register_fn("enable_rescue_mode", { .register_fn("enable_rescue_mode", {
let bridge = api_bridge.clone(); let bridge = api_bridge.clone();
move |client: &mut HetznerClient, server_id: i64| { move |client: &mut HetznerClient, server_id: i64| {
bridge.call(Request::EnableRescueModeWithAllKeys(client.clone(), server_id), |response| { bridge.call(
match response { Request::EnableRescueModeWithAllKeys(client.clone(), server_id),
|response| match response {
Response::EnableRescueMode(result) => result.map_err(|e| e.into()), Response::EnableRescueMode(result) => result.map_err(|e| e.into()),
_ => Err("Unexpected response".into()), _ => Err("Unexpected response".into()),
} },
}) )
} }
}) })
.register_fn("enable_rescue_mode", { .register_fn("enable_rescue_mode", {
let bridge = api_bridge.clone(); let bridge = api_bridge.clone();
move |client: &mut HetznerClient, server_id: i64, ssh_key: i64| { move |client: &mut HetznerClient, server_id: i64, ssh_key: i64| {
bridge.call(Request::EnableRescueMode(client.clone(), server_id, vec![ssh_key]), |response| { bridge.call(
match response { Request::EnableRescueMode(client.clone(), server_id, vec![ssh_key]),
|response| match response {
Response::EnableRescueMode(result) => result.map_err(|e| e.into()), Response::EnableRescueMode(result) => result.map_err(|e| e.into()),
_ => Err("Unexpected response".into()), _ => Err("Unexpected response".into()),
} },
}) )
} }
}) })
.register_fn("enable_rescue_mode", { .register_fn("enable_rescue_mode", {
let bridge = api_bridge.clone(); let bridge = api_bridge.clone();
move |client: &mut HetznerClient, server_id: i64, ssh_keys: rhai::Array| { 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(); let keys: Vec<i64> = ssh_keys
bridge.call(Request::EnableRescueMode(client.clone(), server_id, keys), |response| { .into_iter()
match response { .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()), Response::EnableRescueMode(result) => result.map_err(|e| e.into()),
_ => Err("Unexpected response".into()), _ => Err("Unexpected response".into()),
} },
}) )
} }
}) })
.register_fn("disable_rescue_mode", { .register_fn("disable_rescue_mode", {
let bridge = api_bridge.clone(); let bridge = api_bridge.clone();
move |client: &mut HetznerClient, server_id: i64| { move |client: &mut HetznerClient, server_id: i64| {
bridge.call(Request::DisableRescueMode(client.clone(), server_id), |response| { bridge.call(
match response { Request::DisableRescueMode(client.clone(), server_id),
|response| match response {
Response::DisableRescueMode(result) => result.map_err(|e| e.into()), Response::DisableRescueMode(result) => result.map_err(|e| e.into()),
_ => Err("Unexpected response".into()), _ => Err("Unexpected response".into()),
} },
}) )
} }
}); });
engine engine
.register_iterator::<Vec<WrappedServer>>() .register_iterator::<Vec<WrappedServer>>()
.register_fn("len", |list: &mut Vec<WrappedServer>| list.len() as i64) .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_indexer_get(|list: &mut Vec<WrappedServer>, index: i64| {
.register_fn("show_table", |servers: &mut Vec<WrappedServer>| -> Result<String, Box<EvalAltResult>> { list[index as usize].clone()
let mut table = Table::new(); })
table.set_titles(Row::new(vec![Cell::new("Server List").style_spec("c")])); .register_fn(
table.add_row(Row::new(vec![ "show_table",
Cell::new("ID"), |servers: &mut Vec<WrappedServer>| -> Result<String, Box<EvalAltResult>> {
Cell::new("Name"), let mut table = Table::new();
Cell::new("Status"), table.set_titles(Row::new(vec![Cell::new("Server List").style_spec("c")]));
Cell::new("Public IPv4"),
]));
for server in servers {
table.add_row(Row::new(vec![ 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.id.to_string()),
Cell::new(&server.0.name), ]));
table.add_row(Row::new(vec![
Cell::new("Status"),
Cell::new(&format!("{:?}", server.0.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()), Cell::new(&server.0.public_net.ipv4.clone().unwrap().ip.to_string()),
])); ]));
} table.add_row(Row::new(vec![
Ok(table.to_string()) Cell::new("Type"),
}) Cell::new(&server.0.server_type.name),
.register_fn("show_details", |server: &mut WrappedServer| -> Result<String, Box<EvalAltResult>> { ]));
let mut table = Table::new(); table.add_row(Row::new(vec![
table.set_titles(Row::new(vec![Cell::new(&server.0.name).style_spec("c")])); Cell::new("Included Traffic"),
table.add_row(Row::new(vec![Cell::new("ID"), Cell::new(&server.0.id.to_string())])); Cell::new(&format!(
table.add_row(Row::new(vec![Cell::new("Status"), Cell::new(&format!("{:?}", server.0.status))])); "{} GB",
table.add_row(Row::new(vec![Cell::new("Created"), Cell::new(&server.0.created)])); server.0.included_traffic.unwrap_or(0) / 1024 / 1024 / 1024
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![
table.add_row(Row::new(vec![Cell::new("Ingoing Traffic"), Cell::new(&format!("{} MB", server.0.ingoing_traffic.unwrap_or(0) / 1024 / 1024))])); Cell::new("Ingoing Traffic"),
table.add_row(Row::new(vec![Cell::new("Outgoing Traffic"), Cell::new(&format!("{} MB", server.0.outgoing_traffic.unwrap_or(0) / 1024 / 1024))])); Cell::new(&format!(
table.add_row(Row::new(vec![Cell::new("Primary Disk Size"), Cell::new(&server.0.primary_disk_size.to_string())])); "{} MB",
table.add_row(Row::new(vec![Cell::new("Rescue Enabled"), Cell::new(&server.0.rescue_enabled.to_string())])); 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 { engine.register_fn("get_env", |key: &str| -> String {
env::var(key).unwrap_or("".to_string()) env::var(key).unwrap_or("".to_string())
@ -211,27 +366,34 @@ pub fn register_hetzner_api(
.register_type_with_name::<WrappedSshKey>("SshKey") .register_type_with_name::<WrappedSshKey>("SshKey")
.register_get("id", |key: &mut WrappedSshKey| key.0.id) .register_get("id", |key: &mut WrappedSshKey| key.0.id)
.register_get("name", |key: &mut WrappedSshKey| key.0.name.clone()) .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 engine
.register_iterator::<Vec<WrappedSshKey>>() .register_iterator::<Vec<WrappedSshKey>>()
.register_fn("len", |list: &mut Vec<WrappedSshKey>| list.len() as i64) .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_indexer_get(|list: &mut Vec<WrappedSshKey>, index: i64| {
.register_fn("show_table", |keys: &mut Vec<WrappedSshKey>| -> Result<String, Box<EvalAltResult>> { list[index as usize].clone()
let mut table = Table::new(); })
table.set_titles(Row::new(vec![Cell::new("SSH Keys").style_spec("c")])); .register_fn(
table.add_row(Row::new(vec![ "show_table",
Cell::new("ID"), |keys: &mut Vec<WrappedSshKey>| -> Result<String, Box<EvalAltResult>> {
Cell::new("Name"), let mut table = Table::new();
Cell::new("Fingerprint"), table.set_titles(Row::new(vec![Cell::new("SSH Keys").style_spec("c")]));
]));
for key in keys {
table.add_row(Row::new(vec![ table.add_row(Row::new(vec![
Cell::new(&key.0.id.to_string()), Cell::new("ID"),
Cell::new(&key.0.name), Cell::new("Name"),
Cell::new(&key.0.fingerprint), Cell::new("Fingerprint"),
])); ]));
} for key in keys {
Ok(table.to_string()) 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())
},
);
}