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