implemented list servers + ping + reboot + example shown in example.rhai
This commit is contained in:
commit
bf3bcba164
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
|||||||
|
/target
|
2280
Cargo.lock
generated
Normal file
2280
Cargo.lock
generated
Normal file
File diff suppressed because it is too large
Load Diff
12
Cargo.toml
Normal file
12
Cargo.toml
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
[package]
|
||||||
|
name = "hetzner_rhai"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2024"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
hcloud = "0.21.0"
|
||||||
|
reqwest = "0.12.22"
|
||||||
|
rhai = { version = "1.22.2", features = ["sync"] }
|
||||||
|
tokio = { version = "1.46.1", features = ["full"] }
|
||||||
|
|
||||||
|
ping = "0.6.1"
|
37
example.rhai
Normal file
37
example.rhai
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
let client = new_hetzner_client(HETZNER_API_TOKEN);
|
||||||
|
|
||||||
|
try {
|
||||||
|
print("Listing servers...");
|
||||||
|
let servers = client.list_servers();
|
||||||
|
|
||||||
|
if servers.len() == 0 {
|
||||||
|
print("No servers found.");
|
||||||
|
} else {
|
||||||
|
for server in servers {
|
||||||
|
print(`Server: ${server.name} (${server.id}), Status: ${server.status}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
let first_server = servers[0];
|
||||||
|
print(`Getting details for server: ${first_server.name}`);
|
||||||
|
let detailed_server = client.get_server(first_server.id);
|
||||||
|
print(detailed_server.show_details());
|
||||||
|
print(`Pinging server ${detailed_server.name}...`);
|
||||||
|
let is_online = detailed_server.ping();
|
||||||
|
if is_online {
|
||||||
|
print("Server is online.");
|
||||||
|
} else {
|
||||||
|
print("Server is offline.");
|
||||||
|
}
|
||||||
|
|
||||||
|
// To reboot the server, uncomment the following lines:
|
||||||
|
// print("\nAttempting to reboot the server...");
|
||||||
|
// try {
|
||||||
|
// first_server.reboot(client);
|
||||||
|
// print("Reboot command sent successfully.");
|
||||||
|
// } catch(e) {
|
||||||
|
// print(`Error during reboot: ${e}`);
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
print(`An error occurred: ${e}`);
|
||||||
|
}
|
60
src/async_handler.rs
Normal file
60
src/async_handler.rs
Normal file
@ -0,0 +1,60 @@
|
|||||||
|
use crate::hetzner_api::{HetznerClient, WrappedServer};
|
||||||
|
use crate::ping::ping_server;
|
||||||
|
use std::net::IpAddr;
|
||||||
|
use std::sync::mpsc::{Receiver, Sender};
|
||||||
|
use tokio::runtime::Builder;
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub enum Request {
|
||||||
|
ListServers(HetznerClient),
|
||||||
|
GetServerStatus(HetznerClient, i64),
|
||||||
|
GetServer(HetznerClient, i64),
|
||||||
|
RebootServer(HetznerClient, i64),
|
||||||
|
PingServer(IpAddr),
|
||||||
|
}
|
||||||
|
|
||||||
|
pub enum Response {
|
||||||
|
ListServers(Result<Vec<WrappedServer>, String>),
|
||||||
|
GetServerStatus(Result<String, String>),
|
||||||
|
GetServer(Result<WrappedServer, String>),
|
||||||
|
RebootServer(Result<(), String>),
|
||||||
|
PingServer(Result<bool, String>),
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn run_worker(
|
||||||
|
command_rx: Receiver<Request>,
|
||||||
|
reply_tx: Sender<Response>,
|
||||||
|
) {
|
||||||
|
std::thread::spawn(move || {
|
||||||
|
let rt = Builder::new_current_thread()
|
||||||
|
.enable_all()
|
||||||
|
.build()
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
while let Ok(request) = command_rx.recv() {
|
||||||
|
let response = match request {
|
||||||
|
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());
|
||||||
|
Response::GetServerStatus(result)
|
||||||
|
}
|
||||||
|
Request::GetServer(client, server_id) => {
|
||||||
|
let result = rt.block_on(client.get_server(server_id)).map_err(|e| e.to_string());
|
||||||
|
Response::GetServer(result)
|
||||||
|
}
|
||||||
|
Request::RebootServer(client, server_id) => {
|
||||||
|
let result = rt.block_on(client.reboot_server(server_id)).map_err(|e| e.to_string());
|
||||||
|
Response::RebootServer(result)
|
||||||
|
}
|
||||||
|
Request::PingServer(ip) => {
|
||||||
|
let result = ping_server(ip).map_err(|e| e.to_string());
|
||||||
|
Response::PingServer(result)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
reply_tx.send(response).expect("Failed to send response");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
0
src/error.rs
Normal file
0
src/error.rs
Normal file
72
src/hetzner_api.rs
Normal file
72
src/hetzner_api.rs
Normal file
@ -0,0 +1,72 @@
|
|||||||
|
use hcloud::apis::{configuration::Configuration, servers_api};
|
||||||
|
use hcloud::models::Server;
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct HetznerClient {
|
||||||
|
pub configuration: Configuration,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct WrappedServer(pub Server);
|
||||||
|
|
||||||
|
impl HetznerClient {
|
||||||
|
pub fn new(api_token: &str) -> Self {
|
||||||
|
let mut configuration = Configuration::new();
|
||||||
|
configuration.bearer_access_token = Some(api_token.to_string());
|
||||||
|
let client = reqwest::Client::builder()
|
||||||
|
.timeout(std::time::Duration::from_secs(5))
|
||||||
|
.build()
|
||||||
|
.unwrap();
|
||||||
|
configuration.client = client;
|
||||||
|
|
||||||
|
|
||||||
|
Self { configuration }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn list_servers(&self) -> Result<Vec<WrappedServer>, Box<dyn std::error::Error>> {
|
||||||
|
let servers = servers_api::list_servers(&self.configuration, Default::default())
|
||||||
|
.await?
|
||||||
|
.servers
|
||||||
|
.into_iter()
|
||||||
|
.map(WrappedServer)
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
Ok(servers)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn get_server_status(&self, server_id: i64) -> Result<String, Box<dyn std::error::Error>> {
|
||||||
|
let params = servers_api::GetServerParams {
|
||||||
|
id: server_id,
|
||||||
|
};
|
||||||
|
let server_response = servers_api::get_server(&self.configuration, params)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
if let Some(server) = server_response.server {
|
||||||
|
Ok(format!("{:?}", server.status))
|
||||||
|
} else {
|
||||||
|
Err(Box::from(format!("Server with id {} not found", server_id)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn get_server(&self, server_id: i64) -> Result<WrappedServer, Box<dyn std::error::Error>> {
|
||||||
|
let params = servers_api::GetServerParams {
|
||||||
|
id: server_id,
|
||||||
|
};
|
||||||
|
let server_response = servers_api::get_server(&self.configuration, params)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
if let Some(server) = server_response.server {
|
||||||
|
Ok(WrappedServer(*server))
|
||||||
|
} else {
|
||||||
|
Err(Box::from(format!("Server with id {} not found", server_id)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn reboot_server(&self, server_id: i64) -> Result<(), Box<dyn std::error::Error>> {
|
||||||
|
let params = servers_api::SoftRebootServerParams {
|
||||||
|
id: server_id,
|
||||||
|
};
|
||||||
|
servers_api::soft_reboot_server(&self.configuration, params).await?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
50
src/main.rs
Normal file
50
src/main.rs
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
mod error;
|
||||||
|
mod hetzner_api;
|
||||||
|
mod rhai_api;
|
||||||
|
mod async_handler;
|
||||||
|
mod ping;
|
||||||
|
|
||||||
|
use crate::rhai_api::register_hetzner_api;
|
||||||
|
use rhai::{Engine, Scope};
|
||||||
|
use std::env;
|
||||||
|
|
||||||
|
use std::thread;
|
||||||
|
use std::sync::{Arc, Mutex, mpsc};
|
||||||
|
|
||||||
|
use rhai::EvalAltResult;
|
||||||
|
|
||||||
|
fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
|
||||||
|
let (command_tx, command_rx) = mpsc::channel::<async_handler::Request>();
|
||||||
|
let (reply_tx, reply_rx) = mpsc::channel::<async_handler::Response>();
|
||||||
|
async_handler::run_worker(command_rx, reply_tx);
|
||||||
|
|
||||||
|
let rhai_thread = thread::spawn(move || -> Result<(), Box<EvalAltResult>> {
|
||||||
|
let reply_rx = Arc::new(Mutex::new(reply_rx));
|
||||||
|
let mut engine = Engine::new();
|
||||||
|
|
||||||
|
register_hetzner_api(&mut engine, command_tx, reply_rx);
|
||||||
|
|
||||||
|
let mut scope = Scope::new();
|
||||||
|
scope.push(
|
||||||
|
"HETZNER_API_TOKEN",
|
||||||
|
env::var("HETZNER_API_TOKEN").unwrap_or_else(|_| {
|
||||||
|
let args: Vec<String> = env::args().collect();
|
||||||
|
args.get(1).cloned().unwrap_or_default()
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
let script = std::env::args().nth(2);
|
||||||
|
if let Some(s) = script {
|
||||||
|
engine.run_with_scope(&mut scope, &s)?;
|
||||||
|
} else {
|
||||||
|
engine.run_file_with_scope(&mut scope, "example.rhai".into())?;
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
});
|
||||||
|
|
||||||
|
if let Err(err) = rhai_thread.join().unwrap() {
|
||||||
|
eprintln!("Error in Rhai script: {}", *err);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
16
src/ping.rs
Normal file
16
src/ping.rs
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
use std::net::IpAddr;
|
||||||
|
use std::time::Duration;
|
||||||
|
|
||||||
|
pub fn ping_server(ip: IpAddr) -> Result<bool, Box<dyn std::error::Error>> {
|
||||||
|
match ping::new(ip)
|
||||||
|
.socket_type(ping::SocketType::DGRAM)
|
||||||
|
.timeout(Duration::from_secs(2))
|
||||||
|
.send()
|
||||||
|
{
|
||||||
|
Ok(_) => Ok(true),
|
||||||
|
Err(e) => {
|
||||||
|
eprintln!("Ping error: {}", e);
|
||||||
|
Ok(false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
178
src/rhai_api.rs
Normal file
178
src/rhai_api.rs
Normal file
@ -0,0 +1,178 @@
|
|||||||
|
use crate::async_handler::Response;
|
||||||
|
use crate::async_handler::Request;
|
||||||
|
use crate::hetzner_api::{HetznerClient, WrappedServer};
|
||||||
|
use rhai::{Engine, EvalAltResult};
|
||||||
|
use rhai::EvalContext;
|
||||||
|
use std::sync::{Arc, Mutex, mpsc::{Sender, Receiver}};
|
||||||
|
|
||||||
|
pub fn register_hetzner_api(
|
||||||
|
engine: &mut Engine,
|
||||||
|
command_tx: Sender<Request>,
|
||||||
|
reply_rx: Arc<Mutex<Receiver<Response>>>,
|
||||||
|
) {
|
||||||
|
let list_servers_tx = command_tx.clone();
|
||||||
|
let list_servers_rx = reply_rx.clone();
|
||||||
|
let get_server_status_tx = command_tx.clone();
|
||||||
|
let get_server_status_rx = reply_rx.clone();
|
||||||
|
let get_server_tx = command_tx.clone();
|
||||||
|
let get_server_rx = reply_rx.clone();
|
||||||
|
let reboot_server_tx = command_tx.clone();
|
||||||
|
let reboot_server_rx = reply_rx.clone();
|
||||||
|
let ping_server_tx = command_tx.clone();
|
||||||
|
let ping_server_rx = reply_rx.clone();
|
||||||
|
|
||||||
|
engine
|
||||||
|
.register_type_with_name::<HetznerClient>("HetznerClient")
|
||||||
|
.register_fn("new_hetzner_client", HetznerClient::new)
|
||||||
|
.register_fn(
|
||||||
|
"list_servers",
|
||||||
|
move |client: &mut HetznerClient| -> Result<Vec<WrappedServer>, Box<EvalAltResult>> {
|
||||||
|
list_servers_tx.send(Request::ListServers(client.clone()))
|
||||||
|
.map_err(|e| e.to_string())?;
|
||||||
|
|
||||||
|
let response = list_servers_rx.lock().unwrap().recv()
|
||||||
|
.map_err(|e| e.to_string())?;
|
||||||
|
|
||||||
|
match response {
|
||||||
|
Response::ListServers(result) => result.map_err(|e| e.into()),
|
||||||
|
_ => Err("Unexpected response".into()),
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.register_fn(
|
||||||
|
"get_server_status",
|
||||||
|
move |client: &mut HetznerClient,
|
||||||
|
server_id: i64|
|
||||||
|
-> Result<String, Box<EvalAltResult>> {
|
||||||
|
get_server_status_tx.send(Request::GetServerStatus(client.clone(), server_id))
|
||||||
|
.map_err(|e| e.to_string())?;
|
||||||
|
|
||||||
|
let response = get_server_status_rx.lock().unwrap().recv()
|
||||||
|
.map_err(|e| e.to_string())?;
|
||||||
|
|
||||||
|
match response {
|
||||||
|
Response::GetServerStatus(result) => result.map_err(|e| e.into()),
|
||||||
|
_ => Err("Unexpected response".into()),
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.register_fn(
|
||||||
|
"get_server",
|
||||||
|
move |client: &mut HetznerClient,
|
||||||
|
server_id: i64|
|
||||||
|
-> Result<WrappedServer, Box<EvalAltResult>> {
|
||||||
|
get_server_tx.send(Request::GetServer(client.clone(), server_id))
|
||||||
|
.map_err(|e| e.to_string())?;
|
||||||
|
|
||||||
|
let response = get_server_rx.lock().unwrap().recv()
|
||||||
|
.map_err(|e| e.to_string())?;
|
||||||
|
|
||||||
|
match response {
|
||||||
|
Response::GetServer(result) => result.map_err(|e| e.into()),
|
||||||
|
_ => Err("Unexpected response".into()),
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.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_fn(
|
||||||
|
"reboot",
|
||||||
|
move |server: &mut WrappedServer,
|
||||||
|
client: HetznerClient|
|
||||||
|
-> Result<(), Box<EvalAltResult>> {
|
||||||
|
reboot_server_tx
|
||||||
|
.send(Request::RebootServer(client, server.0.id))
|
||||||
|
.map_err(|e| e.to_string())?;
|
||||||
|
|
||||||
|
let response = reboot_server_rx
|
||||||
|
.lock()
|
||||||
|
.unwrap()
|
||||||
|
.recv()
|
||||||
|
.map_err(|e| e.to_string())?;
|
||||||
|
|
||||||
|
match response {
|
||||||
|
Response::RebootServer(result) => result.map_err(|e| e.into()),
|
||||||
|
_ => Err("Unexpected response".into()),
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.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(
|
||||||
|
"ping",
|
||||||
|
move |server: &mut WrappedServer| -> Result<bool, Box<EvalAltResult>> {
|
||||||
|
ping_server_tx
|
||||||
|
.send(Request::PingServer(
|
||||||
|
server
|
||||||
|
.0
|
||||||
|
.public_net
|
||||||
|
.ipv4
|
||||||
|
.clone()
|
||||||
|
.unwrap()
|
||||||
|
.ip
|
||||||
|
.parse()
|
||||||
|
.unwrap(),
|
||||||
|
))
|
||||||
|
.map_err(|e| e.to_string())?;
|
||||||
|
|
||||||
|
let response = ping_server_rx
|
||||||
|
.lock()
|
||||||
|
.unwrap()
|
||||||
|
.recv()
|
||||||
|
.map_err(|e| e.to_string())?;
|
||||||
|
|
||||||
|
match response {
|
||||||
|
Response::PingServer(result) => result.map_err(|e| e.into()),
|
||||||
|
_ => Err("Unexpected response".into()),
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.register_fn(
|
||||||
|
"show_details",
|
||||||
|
|server: &mut WrappedServer| -> Result<String, Box<EvalAltResult>> {
|
||||||
|
let mut details = String::new();
|
||||||
|
details.push_str(&format!(" ID: {}\n", server.0.id));
|
||||||
|
details.push_str(&format!(" Status: {:?}\n", server.0.status));
|
||||||
|
details.push_str(&format!(" Created: {}\n", server.0.created));
|
||||||
|
details.push_str(&format!(" IPv4: {}\n", server.0.public_net.ipv4.clone().unwrap().ip));
|
||||||
|
details.push_str(&format!(" Type: {}\n", server.0.server_type.clone().name));
|
||||||
|
details.push_str(&format!(" Included Traffic: {} GB\n", server.0.included_traffic.unwrap_or(0) / 1024 / 1024 / 1024));
|
||||||
|
details.push_str(&format!(" Ingoing Traffic: {} MB\n", server.0.ingoing_traffic.unwrap_or(0) / 1024 / 1024));
|
||||||
|
details.push_str(&format!(" Outgoing Traffic: {} MB\n", server.0.outgoing_traffic.unwrap_or(0) / 1024 / 1024));
|
||||||
|
details.push_str(&format!(" Primary Disk Size: {} GB\n", server.0.primary_disk_size));
|
||||||
|
details.push_str(&format!(" Rescue Enabled: {}\n", server.0.rescue_enabled));
|
||||||
|
Ok(details)
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user