first commit: added functionality for server listing, ssh key management and boot configuration management
This commit is contained in:
		
							
								
								
									
										12
									
								
								src/api/error.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										12
									
								
								src/api/error.rs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,12 @@
 | 
			
		||||
use crate::api::models::ApiError;
 | 
			
		||||
use thiserror::Error;
 | 
			
		||||
 | 
			
		||||
#[derive(Debug, Error)]
 | 
			
		||||
pub enum AppError {
 | 
			
		||||
    #[error("Request failed: {0}")]
 | 
			
		||||
    RequestError(#[from] reqwest::Error),
 | 
			
		||||
    #[error("API error: {0:?}")]
 | 
			
		||||
    ApiError(ApiError),
 | 
			
		||||
    #[error("Deserialization failed: {0}\nResponse body: {1}")]
 | 
			
		||||
    DeserializationError(String, String),
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										204
									
								
								src/api/mod.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										204
									
								
								src/api/mod.rs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,204 @@
 | 
			
		||||
pub mod error;
 | 
			
		||||
pub mod models;
 | 
			
		||||
 | 
			
		||||
use self::models::{
 | 
			
		||||
    Boot, BootWrapper, ErrorResponse, Rescue, RescueWrapper, Server, ServerWrapper, SshKey,
 | 
			
		||||
    SshKeyWrapper,
 | 
			
		||||
};
 | 
			
		||||
use crate::config::Config;
 | 
			
		||||
use error::AppError;
 | 
			
		||||
use reqwest::blocking::Client as HttpClient;
 | 
			
		||||
use reqwest::StatusCode;
 | 
			
		||||
 | 
			
		||||
#[derive(Clone)]
 | 
			
		||||
pub struct Client {
 | 
			
		||||
    http_client: HttpClient,
 | 
			
		||||
    config: Config,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl Client {
 | 
			
		||||
    pub fn new(config: Config) -> Self {
 | 
			
		||||
        Self {
 | 
			
		||||
            http_client: HttpClient::new(),
 | 
			
		||||
            config,
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fn handle_response<T>(&self, response: reqwest::blocking::Response) -> Result<T, AppError>
 | 
			
		||||
    where
 | 
			
		||||
        T: serde::de::DeserializeOwned,
 | 
			
		||||
    {
 | 
			
		||||
        match response.status() {
 | 
			
		||||
            StatusCode::OK => {
 | 
			
		||||
                // Read the body as text first, then try to deserialize from that
 | 
			
		||||
                let text = response.text().unwrap_or_else(|_| "<failed to read body>".to_string());
 | 
			
		||||
                let result = serde_json::from_str(&text);
 | 
			
		||||
                match result {
 | 
			
		||||
                    Ok(val) => Ok(val),
 | 
			
		||||
                    Err(e) => {
 | 
			
		||||
                        let deser_err = format!("{:?}", e);
 | 
			
		||||
                        Err(AppError::DeserializationError(deser_err, text))
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
            },
 | 
			
		||||
            _status => {
 | 
			
		||||
                let error_response: ErrorResponse = response.json()?;
 | 
			
		||||
                Err(AppError::ApiError(error_response.error))
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    pub fn get_server(&self, server_number: i32) -> Result<Server, AppError> {
 | 
			
		||||
        let response = self
 | 
			
		||||
            .http_client
 | 
			
		||||
            .get(format!(
 | 
			
		||||
                "{}/server/{}",
 | 
			
		||||
                self.config.api_url, server_number
 | 
			
		||||
            ))
 | 
			
		||||
            .basic_auth(&self.config.username, Some(&self.config.password))
 | 
			
		||||
            .send()?;
 | 
			
		||||
 | 
			
		||||
        let server_wrapper: ServerWrapper = self.handle_response(response)?;
 | 
			
		||||
        Ok(server_wrapper.server)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    pub fn get_servers(&self) -> Result<Vec<Server>, AppError> {
 | 
			
		||||
        let response = self
 | 
			
		||||
            .http_client
 | 
			
		||||
            .get(format!("{}/server", self.config.api_url))
 | 
			
		||||
            .basic_auth(&self.config.username, Some(&self.config.password))
 | 
			
		||||
            .send()?;
 | 
			
		||||
 | 
			
		||||
        let server_wrappers: Vec<ServerWrapper> = self.handle_response(response)?;
 | 
			
		||||
        let servers = server_wrappers.into_iter().map(|sw| sw.server).collect();
 | 
			
		||||
        Ok(servers)
 | 
			
		||||
    }
 | 
			
		||||
    pub fn get_ssh_keys(&self) -> Result<Vec<SshKey>, AppError> {
 | 
			
		||||
        let response = self
 | 
			
		||||
            .http_client
 | 
			
		||||
            .get(format!("{}/key", self.config.api_url))
 | 
			
		||||
            .basic_auth(&self.config.username, Some(&self.config.password))
 | 
			
		||||
            .send()?;
 | 
			
		||||
            
 | 
			
		||||
        let ssh_key_wrappers: Vec<SshKeyWrapper> = self.handle_response(response)?;
 | 
			
		||||
        let ssh_keys = ssh_key_wrappers.into_iter().map(|sw| sw.key).collect();
 | 
			
		||||
        Ok(ssh_keys)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    pub fn get_ssh_key(&self, fingerprint: &str) -> Result<SshKey, AppError> {
 | 
			
		||||
        let response = self
 | 
			
		||||
            .http_client
 | 
			
		||||
            .get(format!("{}/key/{}", self.config.api_url, fingerprint))
 | 
			
		||||
            .basic_auth(&self.config.username, Some(&self.config.password))
 | 
			
		||||
            .send()?;
 | 
			
		||||
        
 | 
			
		||||
        let ssh_key_wrapper: SshKeyWrapper = self.handle_response(response)?;
 | 
			
		||||
        Ok(ssh_key_wrapper.key)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    pub fn add_ssh_key(&self, name: &str, data: &str) -> Result<SshKey, AppError> {
 | 
			
		||||
        let params = [("name", name), ("data", data)];
 | 
			
		||||
        let response = self
 | 
			
		||||
            .http_client
 | 
			
		||||
            .post(format!("{}/key", self.config.api_url))
 | 
			
		||||
            .basic_auth(&self.config.username, Some(&self.config.password))
 | 
			
		||||
            .form(¶ms)
 | 
			
		||||
            .send()?;
 | 
			
		||||
            
 | 
			
		||||
        let ssh_key_wrapper: SshKeyWrapper = self.handle_response(response)?;
 | 
			
		||||
        Ok(ssh_key_wrapper.key)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    pub fn update_ssh_key_name(
 | 
			
		||||
        &self,
 | 
			
		||||
        fingerprint: &str,
 | 
			
		||||
        name: &str,
 | 
			
		||||
    ) -> Result<SshKey, AppError> {
 | 
			
		||||
        let params = [("name", name)];
 | 
			
		||||
        let response = self
 | 
			
		||||
            .http_client
 | 
			
		||||
            .post(format!("{}/key/{}", self.config.api_url, fingerprint))
 | 
			
		||||
            .basic_auth(&self.config.username, Some(&self.config.password))
 | 
			
		||||
            .form(¶ms)
 | 
			
		||||
            .send()?;
 | 
			
		||||
            
 | 
			
		||||
        let ssh_key_wrapper: SshKeyWrapper = self.handle_response(response)?;
 | 
			
		||||
        Ok(ssh_key_wrapper.key)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    pub fn delete_ssh_key(&self, fingerprint: &str) -> Result<(), AppError> {
 | 
			
		||||
        self.http_client
 | 
			
		||||
            .delete(format!("{}/key/{}", self.config.api_url, fingerprint))
 | 
			
		||||
            .basic_auth(&self.config.username, Some(&self.config.password))
 | 
			
		||||
            .send()?;
 | 
			
		||||
 | 
			
		||||
        Ok(())
 | 
			
		||||
    }
 | 
			
		||||
    pub fn get_boot_configuration(&self, server_number: i32) -> Result<Boot, AppError> {
 | 
			
		||||
        let response = self
 | 
			
		||||
            .http_client
 | 
			
		||||
            .get(format!("{}/boot/{}", self.config.api_url, server_number))
 | 
			
		||||
            .basic_auth(&self.config.username, Some(&self.config.password))
 | 
			
		||||
            .send()?;
 | 
			
		||||
            
 | 
			
		||||
        let boot_wrapper: BootWrapper = self.handle_response(response)?;
 | 
			
		||||
        Ok(boot_wrapper.boot)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    pub fn get_rescue_boot_configuration(
 | 
			
		||||
        &self,
 | 
			
		||||
        server_number: i32,
 | 
			
		||||
    ) -> Result<Rescue, AppError> {
 | 
			
		||||
        let response = self
 | 
			
		||||
            .http_client
 | 
			
		||||
            .get(format!(
 | 
			
		||||
                "{}/boot/{}/rescue",
 | 
			
		||||
                self.config.api_url, server_number
 | 
			
		||||
            ))
 | 
			
		||||
            .basic_auth(&self.config.username, Some(&self.config.password))
 | 
			
		||||
            .send()?;
 | 
			
		||||
 | 
			
		||||
        let rescue_wrapper: RescueWrapper = self.handle_response(response)?;
 | 
			
		||||
        Ok(rescue_wrapper.rescue)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    pub fn enable_rescue_mode(
 | 
			
		||||
        &self,
 | 
			
		||||
        server_number: i32,
 | 
			
		||||
        os: &str,
 | 
			
		||||
        authorized_keys: Option<&[String]>,
 | 
			
		||||
    ) -> Result<Rescue, AppError> {
 | 
			
		||||
        let mut params = vec![("os", os)];
 | 
			
		||||
        if let Some(keys) = authorized_keys {
 | 
			
		||||
            for key in keys {
 | 
			
		||||
                params.push(("authorized_key[]", key));
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        let response = self
 | 
			
		||||
            .http_client
 | 
			
		||||
            .post(format!(
 | 
			
		||||
                "{}/boot/{}/rescue",
 | 
			
		||||
                self.config.api_url, server_number
 | 
			
		||||
            ))
 | 
			
		||||
            .basic_auth(&self.config.username, Some(&self.config.password))
 | 
			
		||||
            .form(¶ms)
 | 
			
		||||
            .send()?;
 | 
			
		||||
            
 | 
			
		||||
        let rescue_wrapper: RescueWrapper = self.handle_response(response)?;
 | 
			
		||||
        Ok(rescue_wrapper.rescue)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    pub fn disable_rescue_mode(&self, server_number: i32) -> Result<Rescue, AppError> {
 | 
			
		||||
        let response = self
 | 
			
		||||
            .http_client
 | 
			
		||||
            .delete(format!(
 | 
			
		||||
                "{}/boot/{}/rescue",
 | 
			
		||||
                self.config.api_url, server_number
 | 
			
		||||
            ))
 | 
			
		||||
            .basic_auth(&self.config.username, Some(&self.config.password))
 | 
			
		||||
            .send()?;
 | 
			
		||||
            
 | 
			
		||||
        let rescue_wrapper: RescueWrapper = self.handle_response(response)?;
 | 
			
		||||
        Ok(rescue_wrapper.rescue)
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										382
									
								
								src/api/models.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										382
									
								
								src/api/models.rs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,382 @@
 | 
			
		||||
use rhai::{CustomType, TypeBuilder};
 | 
			
		||||
use serde::{Deserialize, Deserializer};
 | 
			
		||||
use serde_json::Value;
 | 
			
		||||
 | 
			
		||||
#[derive(Debug, Deserialize, Clone)]
 | 
			
		||||
pub struct ServerWrapper {
 | 
			
		||||
    pub server: Server,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(Debug, Deserialize, Clone, CustomType)]
 | 
			
		||||
#[rhai_type(extra = Self::build_rhai_type)]
 | 
			
		||||
pub struct Server {
 | 
			
		||||
    pub server_ip: String,
 | 
			
		||||
    pub server_ipv6_net: String,
 | 
			
		||||
    pub server_number: i32,
 | 
			
		||||
    pub server_name: String,
 | 
			
		||||
    pub product: String,
 | 
			
		||||
    pub dc: String,
 | 
			
		||||
    pub traffic: String,
 | 
			
		||||
    pub status: String,
 | 
			
		||||
    pub cancelled: bool,
 | 
			
		||||
    pub paid_until: String,
 | 
			
		||||
    pub ip: Vec<String>,
 | 
			
		||||
    pub subnet: Option<Vec<Subnet>>,
 | 
			
		||||
    pub reset: Option<bool>,
 | 
			
		||||
    pub rescue: Option<bool>,
 | 
			
		||||
    pub vnc: Option<bool>,
 | 
			
		||||
    pub windows: Option<bool>,
 | 
			
		||||
    pub plesk: Option<bool>,
 | 
			
		||||
    pub cpanel: Option<bool>,
 | 
			
		||||
    pub wol: Option<bool>,
 | 
			
		||||
    pub hot_swap: Option<bool>,
 | 
			
		||||
    pub linked_storagebox: Option<i32>,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl Server {
 | 
			
		||||
    fn build_rhai_type(builder: &mut TypeBuilder<Self>) {
 | 
			
		||||
        builder
 | 
			
		||||
            .with_name("Server")
 | 
			
		||||
            .with_get("server_ip", |s: &mut Server| s.server_ip.clone())
 | 
			
		||||
            .with_get("server_ipv6_net", |s: &mut Server| s.server_ipv6_net.clone())
 | 
			
		||||
            .with_get("server_number", |s: &mut Server| s.server_number)
 | 
			
		||||
            .with_get("server_name", |s: &mut Server| s.server_name.clone())
 | 
			
		||||
            .with_get("product", |s: &mut Server| s.product.clone())
 | 
			
		||||
            .with_get("dc", |s: &mut Server| s.dc.clone())
 | 
			
		||||
            .with_get("traffic", |s: &mut Server| s.traffic.clone())
 | 
			
		||||
            .with_get("status", |s: &mut Server| s.status.clone())
 | 
			
		||||
            .with_get("cancelled", |s: &mut Server| s.cancelled)
 | 
			
		||||
            .with_get("paid_until", |s: &mut Server| s.paid_until.clone());
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
#[derive(Debug, Deserialize, Clone, CustomType)]
 | 
			
		||||
#[rhai_type(extra = Self::build_rhai_type)]
 | 
			
		||||
pub struct Subnet {
 | 
			
		||||
    pub ip: String,
 | 
			
		||||
    pub mask: String,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl Subnet {
 | 
			
		||||
    fn build_rhai_type(builder: &mut TypeBuilder<Self>) {
 | 
			
		||||
        builder
 | 
			
		||||
            .with_name("Subnet")
 | 
			
		||||
            .with_get("ip", |s: &mut Subnet| s.ip.clone())
 | 
			
		||||
            .with_get("mask", |s: &mut Subnet| s.mask.clone());
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
#[derive(Debug, Deserialize, Clone)]
 | 
			
		||||
pub struct SshKeyWrapper {
 | 
			
		||||
    pub key: SshKey,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(Debug, Deserialize, Clone, CustomType)]
 | 
			
		||||
#[rhai_type(extra = Self::build_rhai_type)]
 | 
			
		||||
pub struct SshKey {
 | 
			
		||||
    pub name: String,
 | 
			
		||||
    pub fingerprint: String,
 | 
			
		||||
    #[serde(rename = "type")]
 | 
			
		||||
    pub key_type: String,
 | 
			
		||||
    pub size: i32,
 | 
			
		||||
    pub data: String,
 | 
			
		||||
    pub created_at: String,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl SshKey {
 | 
			
		||||
    fn build_rhai_type(builder: &mut TypeBuilder<Self>) {
 | 
			
		||||
        builder
 | 
			
		||||
            .with_name("SshKey")
 | 
			
		||||
            .with_get("name", |s: &mut SshKey| s.name.clone())
 | 
			
		||||
            .with_get("fingerprint", |s: &mut SshKey| s.fingerprint.clone())
 | 
			
		||||
            .with_get("key_type", |s: &mut SshKey| s.key_type.clone())
 | 
			
		||||
            .with_get("size", |s: &mut SshKey| s.size)
 | 
			
		||||
            .with_get("data", |s: &mut SshKey| s.data.clone())
 | 
			
		||||
            .with_get("created_at", |s: &mut SshKey| s.created_at.clone());
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
#[derive(Debug, Deserialize, Clone)]
 | 
			
		||||
pub struct BootWrapper {
 | 
			
		||||
    pub boot: Boot,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(Debug, Deserialize, Clone, CustomType)]
 | 
			
		||||
#[rhai_type(extra = Self::build_rhai_type)]
 | 
			
		||||
pub struct Boot {
 | 
			
		||||
    pub rescue: Rescue,
 | 
			
		||||
    pub linux: Linux,
 | 
			
		||||
    pub vnc: Vnc,
 | 
			
		||||
    pub windows: Option<Windows>,
 | 
			
		||||
    pub plesk: Option<Plesk>,
 | 
			
		||||
    pub cpanel: Option<Cpanel>,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl Boot {
 | 
			
		||||
    fn build_rhai_type(builder: &mut TypeBuilder<Self>) {
 | 
			
		||||
        builder
 | 
			
		||||
            .with_name("Boot")
 | 
			
		||||
            .with_get("rescue", |b: &mut Boot| b.rescue.clone())
 | 
			
		||||
            .with_get("linux", |b: &mut Boot| b.linux.clone())
 | 
			
		||||
            .with_get("vnc", |b: &mut Boot| b.vnc.clone())
 | 
			
		||||
            .with_get("windows", |b: &mut Boot| b.windows.clone())
 | 
			
		||||
            .with_get("plesk", |b: &mut Boot| b.plesk.clone())
 | 
			
		||||
            .with_get("cpanel", |b: &mut Boot| b.cpanel.clone());
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(Debug, Deserialize, Clone)]
 | 
			
		||||
pub struct RescueWrapper {
 | 
			
		||||
    pub rescue: Rescue,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(Debug, Deserialize, Clone, CustomType)]
 | 
			
		||||
#[rhai_type(extra = Self::build_rhai_type)]
 | 
			
		||||
pub struct Rescue {
 | 
			
		||||
    pub server_ip: String,
 | 
			
		||||
    pub server_ipv6_net: String,
 | 
			
		||||
    pub server_number: i32,
 | 
			
		||||
    #[serde(deserialize_with = "string_or_seq_string")]
 | 
			
		||||
    pub os: Vec<String>,
 | 
			
		||||
    pub active: bool,
 | 
			
		||||
    pub password: Option<String>,
 | 
			
		||||
    pub authorized_key: Vec<String>,
 | 
			
		||||
    pub host_key: Vec<String>,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl Rescue {
 | 
			
		||||
    fn build_rhai_type(builder: &mut TypeBuilder<Self>) {
 | 
			
		||||
        builder
 | 
			
		||||
            .with_name("Rescue")
 | 
			
		||||
            .with_get("server_ip", |r: &mut Rescue| r.server_ip.clone())
 | 
			
		||||
            .with_get("server_ipv6_net", |r: &mut Rescue| {
 | 
			
		||||
                r.server_ipv6_net.clone()
 | 
			
		||||
            })
 | 
			
		||||
            .with_get("server_number", |r: &mut Rescue| r.server_number)
 | 
			
		||||
            .with_get("os", |r: &mut Rescue| r.os.clone())
 | 
			
		||||
            .with_get("active", |r: &mut Rescue| r.active)
 | 
			
		||||
            .with_get("password", |r: &mut Rescue| r.password.clone())
 | 
			
		||||
            .with_get("authorized_key", |r: &mut Rescue| {
 | 
			
		||||
                r.authorized_key.clone()
 | 
			
		||||
            })
 | 
			
		||||
            .with_get("host_key", |r: &mut Rescue| r.host_key.clone());
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(Debug, Deserialize, Clone, CustomType)]
 | 
			
		||||
#[rhai_type(extra = Self::build_rhai_type)]
 | 
			
		||||
pub struct Linux {
 | 
			
		||||
    pub server_ip: String,
 | 
			
		||||
    pub server_ipv6_net: String,
 | 
			
		||||
    pub server_number: i32,
 | 
			
		||||
    #[serde(deserialize_with = "string_or_seq_string")]
 | 
			
		||||
    pub dist: Vec<String>,
 | 
			
		||||
    #[serde(deserialize_with = "string_or_seq_string")]
 | 
			
		||||
    pub lang: Vec<String>,
 | 
			
		||||
    pub active: bool,
 | 
			
		||||
    pub password: Option<String>,
 | 
			
		||||
    pub authorized_key: Vec<String>,
 | 
			
		||||
    pub host_key: Vec<String>,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl Linux {
 | 
			
		||||
    fn build_rhai_type(builder: &mut TypeBuilder<Self>) {
 | 
			
		||||
        builder
 | 
			
		||||
            .with_name("Linux")
 | 
			
		||||
            .with_get("server_ip", |l: &mut Linux| l.server_ip.clone())
 | 
			
		||||
            .with_get("server_ipv6_net", |l: &mut Linux| {
 | 
			
		||||
                l.server_ipv6_net.clone()
 | 
			
		||||
            })
 | 
			
		||||
            .with_get("server_number", |l: &mut Linux| l.server_number)
 | 
			
		||||
            .with_get("dist", |l: &mut Linux| l.dist.clone())
 | 
			
		||||
            .with_get("lang", |l: &mut Linux| l.lang.clone())
 | 
			
		||||
            .with_get("active", |l: &mut Linux| l.active)
 | 
			
		||||
            .with_get("password", |l: &mut Linux| l.password.clone())
 | 
			
		||||
            .with_get("authorized_key", |l: &mut Linux| {
 | 
			
		||||
                l.authorized_key.clone()
 | 
			
		||||
            })
 | 
			
		||||
            .with_get("host_key", |l: &mut Linux| l.host_key.clone());
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(Debug, Deserialize, Clone, CustomType)]
 | 
			
		||||
#[rhai_type(extra = Self::build_rhai_type)]
 | 
			
		||||
pub struct Vnc {
 | 
			
		||||
    pub server_ip: String,
 | 
			
		||||
    pub server_ipv6_net: String,
 | 
			
		||||
    pub server_number: i32,
 | 
			
		||||
    #[serde(deserialize_with = "string_or_seq_string")]
 | 
			
		||||
    pub dist: Vec<String>,
 | 
			
		||||
    #[serde(deserialize_with = "string_or_seq_string")]
 | 
			
		||||
    pub lang: Vec<String>,
 | 
			
		||||
    pub active: bool,
 | 
			
		||||
    pub password: Option<String>,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl Vnc {
 | 
			
		||||
    fn build_rhai_type(builder: &mut TypeBuilder<Self>) {
 | 
			
		||||
        builder
 | 
			
		||||
            .with_name("Vnc")
 | 
			
		||||
            .with_get("server_ip", |v: &mut Vnc| v.server_ip.clone())
 | 
			
		||||
            .with_get("server_ipv6_net", |v: &mut Vnc| v.server_ipv6_net.clone())
 | 
			
		||||
            .with_get("server_number", |v: &mut Vnc| v.server_number)
 | 
			
		||||
            .with_get("dist", |v: &mut Vnc| v.dist.clone())
 | 
			
		||||
            .with_get("lang", |v: &mut Vnc| v.lang.clone())
 | 
			
		||||
            .with_get("active", |v: &mut Vnc| v.active)
 | 
			
		||||
            .with_get("password", |v: &mut Vnc| v.password.clone());
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(Debug, Deserialize, Clone, CustomType)]
 | 
			
		||||
#[rhai_type(extra = Self::build_rhai_type)]
 | 
			
		||||
pub struct Windows {
 | 
			
		||||
    pub server_ip: String,
 | 
			
		||||
    pub server_ipv6_net: String,
 | 
			
		||||
    pub server_number: i32,
 | 
			
		||||
    #[serde(deserialize_with = "option_string_or_seq_string")]
 | 
			
		||||
    pub dist: Option<Vec<String>>,
 | 
			
		||||
    #[serde(deserialize_with = "option_string_or_seq_string")]
 | 
			
		||||
    pub lang: Option<Vec<String>>,
 | 
			
		||||
    pub active: bool,
 | 
			
		||||
    pub password: Option<String>,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl Windows {
 | 
			
		||||
    fn build_rhai_type(builder: &mut TypeBuilder<Self>) {
 | 
			
		||||
        builder
 | 
			
		||||
            .with_name("Windows")
 | 
			
		||||
            .with_get("server_ip", |w: &mut Windows| w.server_ip.clone())
 | 
			
		||||
            .with_get("server_ipv6_net", |w: &mut Windows| {
 | 
			
		||||
                w.server_ipv6_net.clone()
 | 
			
		||||
            })
 | 
			
		||||
            .with_get("server_number", |w: &mut Windows| w.server_number)
 | 
			
		||||
            .with_get("dist", |w: &mut Windows| w.dist.clone())
 | 
			
		||||
            .with_get("lang", |w: &mut Windows| w.lang.clone())
 | 
			
		||||
            .with_get("active", |w: &mut Windows| w.active)
 | 
			
		||||
            .with_get("password", |w: &mut Windows| w.password.clone());
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(Debug, Deserialize, Clone, CustomType)]
 | 
			
		||||
#[rhai_type(extra = Self::build_rhai_type)]
 | 
			
		||||
pub struct Plesk {
 | 
			
		||||
    pub server_ip: String,
 | 
			
		||||
    pub server_ipv6_net: String,
 | 
			
		||||
    pub server_number: i32,
 | 
			
		||||
    #[serde(deserialize_with = "string_or_seq_string")]
 | 
			
		||||
    pub dist: Vec<String>,
 | 
			
		||||
    #[serde(deserialize_with = "string_or_seq_string")]
 | 
			
		||||
    pub lang: Vec<String>,
 | 
			
		||||
    pub active: bool,
 | 
			
		||||
    pub password: Option<String>,
 | 
			
		||||
    pub hostname: Option<String>,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl Plesk {
 | 
			
		||||
    fn build_rhai_type(builder: &mut TypeBuilder<Self>) {
 | 
			
		||||
        builder
 | 
			
		||||
            .with_name("Plesk")
 | 
			
		||||
            .with_get("server_ip", |p: &mut Plesk| p.server_ip.clone())
 | 
			
		||||
            .with_get("server_ipv6_net", |p: &mut Plesk| {
 | 
			
		||||
                p.server_ipv6_net.clone()
 | 
			
		||||
            })
 | 
			
		||||
            .with_get("server_number", |p: &mut Plesk| p.server_number)
 | 
			
		||||
            .with_get("dist", |p: &mut Plesk| p.dist.clone())
 | 
			
		||||
            .with_get("lang", |p: &mut Plesk| p.lang.clone())
 | 
			
		||||
            .with_get("active", |p: &mut Plesk| p.active)
 | 
			
		||||
            .with_get("password", |p: &mut Plesk| p.password.clone())
 | 
			
		||||
            .with_get("hostname", |p: &mut Plesk| p.hostname.clone());
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(Debug, Deserialize, Clone, CustomType)]
 | 
			
		||||
#[rhai_type(extra = Self::build_rhai_type)]
 | 
			
		||||
pub struct Cpanel {
 | 
			
		||||
    pub server_ip: String,
 | 
			
		||||
    pub server_ipv6_net: String,
 | 
			
		||||
    pub server_number: i32,
 | 
			
		||||
    #[serde(deserialize_with = "string_or_seq_string")]
 | 
			
		||||
    pub dist: Vec<String>,
 | 
			
		||||
    #[serde(deserialize_with = "string_or_seq_string")]
 | 
			
		||||
    pub lang: Vec<String>,
 | 
			
		||||
    pub active: bool,
 | 
			
		||||
    pub password: Option<String>,
 | 
			
		||||
    pub hostname: Option<String>,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl Cpanel {
 | 
			
		||||
    fn build_rhai_type(builder: &mut TypeBuilder<Self>) {
 | 
			
		||||
        builder
 | 
			
		||||
            .with_name("Cpanel")
 | 
			
		||||
            .with_get("server_ip", |c: &mut Cpanel| c.server_ip.clone())
 | 
			
		||||
            .with_get("server_ipv6_net", |c: &mut Cpanel| {
 | 
			
		||||
                c.server_ipv6_net.clone()
 | 
			
		||||
            })
 | 
			
		||||
            .with_get("server_number", |c: &mut Cpanel| c.server_number)
 | 
			
		||||
            .with_get("dist", |c: &mut Cpanel| c.dist.clone())
 | 
			
		||||
            .with_get("lang", |c: &mut Cpanel| c.lang.clone())
 | 
			
		||||
            .with_get("active", |c: &mut Cpanel| c.active)
 | 
			
		||||
            .with_get("password", |c: &mut Cpanel| c.password.clone())
 | 
			
		||||
            .with_get("hostname", |c: &mut Cpanel| c.hostname.clone());
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
fn string_or_seq_string<'de, D>(deserializer: D) -> Result<Vec<String>, D::Error>
 | 
			
		||||
where
 | 
			
		||||
    D: Deserializer<'de>,
 | 
			
		||||
{
 | 
			
		||||
    let value = Value::deserialize(deserializer)?;
 | 
			
		||||
    match value {
 | 
			
		||||
        Value::String(s) => Ok(vec![s]),
 | 
			
		||||
        Value::Array(a) => a
 | 
			
		||||
            .into_iter()
 | 
			
		||||
            .map(|v| {
 | 
			
		||||
                v.as_str()
 | 
			
		||||
                    .map(ToString::to_string)
 | 
			
		||||
                    .ok_or(serde::de::Error::custom("expected string"))
 | 
			
		||||
            })
 | 
			
		||||
            .collect(),
 | 
			
		||||
        _ => Err(serde::de::Error::custom(
 | 
			
		||||
            "expected string or array of strings",
 | 
			
		||||
        )),
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
fn option_string_or_seq_string<'de, D>(
 | 
			
		||||
    deserializer: D,
 | 
			
		||||
) -> Result<Option<Vec<String>>, D::Error>
 | 
			
		||||
where
 | 
			
		||||
    D: Deserializer<'de>,
 | 
			
		||||
{
 | 
			
		||||
    let value = Value::deserialize(deserializer)?;
 | 
			
		||||
    match value {
 | 
			
		||||
        Value::Null => Ok(None),
 | 
			
		||||
        Value::String(s) => Ok(Some(vec![s])),
 | 
			
		||||
        Value::Array(a) => Ok(Some(
 | 
			
		||||
            a.into_iter()
 | 
			
		||||
                .map(|v| {
 | 
			
		||||
                    v.as_str()
 | 
			
		||||
                        .map(ToString::to_string)
 | 
			
		||||
                        .ok_or(serde::de::Error::custom("expected string"))
 | 
			
		||||
                })
 | 
			
		||||
                .collect::<Result<Vec<String>, _>>()?,
 | 
			
		||||
        )),
 | 
			
		||||
        _ => Err(serde::de::Error::custom(
 | 
			
		||||
            "expected string or array of strings",
 | 
			
		||||
        )),
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(Debug, Deserialize)]
 | 
			
		||||
pub struct ApiError {
 | 
			
		||||
    #[allow(dead_code)]
 | 
			
		||||
    pub status: i32,
 | 
			
		||||
    #[allow(dead_code)]
 | 
			
		||||
    pub code: String,
 | 
			
		||||
    #[allow(dead_code)]
 | 
			
		||||
    pub message: String,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(Debug, Deserialize)]
 | 
			
		||||
pub struct ErrorResponse {
 | 
			
		||||
    pub error: ApiError,
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										25
									
								
								src/config.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										25
									
								
								src/config.rs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,25 @@
 | 
			
		||||
use std::env;
 | 
			
		||||
 | 
			
		||||
#[derive(Clone)]
 | 
			
		||||
pub struct Config {
 | 
			
		||||
    pub username: String,
 | 
			
		||||
    pub password: String,
 | 
			
		||||
    pub api_url: String,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl Config {
 | 
			
		||||
    pub fn from_env() -> Result<Self, String> {
 | 
			
		||||
        let username = env::var("HETZNER_USERNAME")
 | 
			
		||||
            .map_err(|_| "HETZNER_USERNAME environment variable not set".to_string())?;
 | 
			
		||||
        let password = env::var("HETZNER_PASSWORD")
 | 
			
		||||
            .map_err(|_| "HETZNER_PASSWORD environment variable not set".to_string())?;
 | 
			
		||||
        let api_url = env::var("HETZNER_API_URL")
 | 
			
		||||
            .unwrap_or_else(|_| "https://robot-ws.your-server.de".to_string());
 | 
			
		||||
 | 
			
		||||
        Ok(Config {
 | 
			
		||||
            username,
 | 
			
		||||
            password,
 | 
			
		||||
            api_url,
 | 
			
		||||
        })
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										44
									
								
								src/main.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										44
									
								
								src/main.rs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,44 @@
 | 
			
		||||
mod api;
 | 
			
		||||
mod config;
 | 
			
		||||
mod scripting;
 | 
			
		||||
 | 
			
		||||
use crate::api::Client;
 | 
			
		||||
use crate::config::Config;
 | 
			
		||||
use crate::scripting::setup_engine;
 | 
			
		||||
use dotenv::dotenv;
 | 
			
		||||
use std::env;
 | 
			
		||||
use std::fs;
 | 
			
		||||
use std::process;
 | 
			
		||||
 | 
			
		||||
fn main() {
 | 
			
		||||
    dotenv().ok();
 | 
			
		||||
 | 
			
		||||
    if let Err(e) = run() {
 | 
			
		||||
        eprintln!("Error: {}", e);
 | 
			
		||||
        let mut source = e.source();
 | 
			
		||||
        while let Some(s) = source {
 | 
			
		||||
            eprintln!("Caused by: {}", s);
 | 
			
		||||
            source = s.source();
 | 
			
		||||
        }
 | 
			
		||||
        process::exit(1);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
fn run() -> Result<(), Box<dyn std::error::Error>> {
 | 
			
		||||
    let args: Vec<String> = env::args().collect();
 | 
			
		||||
    if args.len() != 2 {
 | 
			
		||||
        eprintln!("Usage: {} <path-to-script>", args[0]);
 | 
			
		||||
        return Ok(());
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    let config = Config::from_env()?;
 | 
			
		||||
    let client = Client::new(config.clone());
 | 
			
		||||
 | 
			
		||||
    let (engine, mut scope) = setup_engine(client);
 | 
			
		||||
 | 
			
		||||
    let script = fs::read_to_string(&args[1])?;
 | 
			
		||||
 | 
			
		||||
    engine.run_with_scope(&mut scope, &script)?;
 | 
			
		||||
 | 
			
		||||
    Ok(())
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										63
									
								
								src/scripting/boot.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										63
									
								
								src/scripting/boot.rs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,63 @@
 | 
			
		||||
use crate::api::{
 | 
			
		||||
    models::{Boot, Rescue},
 | 
			
		||||
    Client,
 | 
			
		||||
};
 | 
			
		||||
use rhai::{plugin::*, Engine};
 | 
			
		||||
 | 
			
		||||
pub fn register(engine: &mut Engine) {
 | 
			
		||||
    let boot_module = exported_module!(boot_api);
 | 
			
		||||
    engine.register_global_module(boot_module.into());
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[export_module]
 | 
			
		||||
pub mod boot_api {
 | 
			
		||||
    use super::*;
 | 
			
		||||
    use rhai::EvalAltResult;
 | 
			
		||||
 | 
			
		||||
    #[rhai_fn(name = "get_boot_configuration", return_raw)]
 | 
			
		||||
    pub fn get_boot_configuration(
 | 
			
		||||
        client: &mut Client,
 | 
			
		||||
        server_number: i64,
 | 
			
		||||
    ) -> Result<Boot, Box<EvalAltResult>> {
 | 
			
		||||
        client
 | 
			
		||||
            .get_boot_configuration(server_number as i32)
 | 
			
		||||
            .map_err(|e| e.to_string().into())
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    #[rhai_fn(name = "get_rescue_boot_configuration", return_raw)]
 | 
			
		||||
    pub fn get_rescue_boot_configuration(
 | 
			
		||||
        client: &mut Client,
 | 
			
		||||
        server_number: i64,
 | 
			
		||||
    ) -> Result<Rescue, Box<EvalAltResult>> {
 | 
			
		||||
        client
 | 
			
		||||
            .get_rescue_boot_configuration(server_number as i32)
 | 
			
		||||
            .map_err(|e| e.to_string().into())
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    #[rhai_fn(name = "enable_rescue_mode", return_raw)]
 | 
			
		||||
    pub fn enable_rescue_mode(
 | 
			
		||||
        client: &mut Client,
 | 
			
		||||
        server_number: i64,
 | 
			
		||||
        os: &str,
 | 
			
		||||
        authorized_keys: rhai::Array,
 | 
			
		||||
    ) -> Result<Rescue, Box<EvalAltResult>> {
 | 
			
		||||
        let keys: Vec<String> = authorized_keys
 | 
			
		||||
            .into_iter()
 | 
			
		||||
            .map(|k| k.into_string().unwrap())
 | 
			
		||||
            .collect();
 | 
			
		||||
 | 
			
		||||
        client
 | 
			
		||||
            .enable_rescue_mode(server_number as i32, os, Some(&keys))
 | 
			
		||||
            .map_err(|e| e.to_string().into())
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    #[rhai_fn(name = "disable_rescue_mode", return_raw)]
 | 
			
		||||
    pub fn disable_rescue_mode(
 | 
			
		||||
        client: &mut Client,
 | 
			
		||||
        server_number: i64,
 | 
			
		||||
    ) -> Result<Rescue, Box<EvalAltResult>> {
 | 
			
		||||
        client
 | 
			
		||||
            .disable_rescue_mode(server_number as i32)
 | 
			
		||||
            .map_err(|e| e.to_string().into())
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										30
									
								
								src/scripting/mod.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										30
									
								
								src/scripting/mod.rs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,30 @@
 | 
			
		||||
use crate::api::Client;
 | 
			
		||||
use crate::api::models::{Server, SshKey};
 | 
			
		||||
use rhai::{Engine, Scope};
 | 
			
		||||
 | 
			
		||||
pub mod server;
 | 
			
		||||
pub mod ssh_keys;
 | 
			
		||||
pub mod boot;
 | 
			
		||||
 | 
			
		||||
pub fn setup_engine(client: Client) -> (Engine, Scope<'static>) {
 | 
			
		||||
    let mut engine = Engine::new();
 | 
			
		||||
    let mut scope = Scope::new();
 | 
			
		||||
 | 
			
		||||
    engine.build_type::<Server>();
 | 
			
		||||
    engine.build_type::<SshKey>();
 | 
			
		||||
    engine.build_type::<crate::api::models::Boot>();
 | 
			
		||||
    engine.build_type::<crate::api::models::Rescue>();
 | 
			
		||||
    engine.build_type::<crate::api::models::Linux>();
 | 
			
		||||
    engine.build_type::<crate::api::models::Vnc>();
 | 
			
		||||
    engine.build_type::<crate::api::models::Windows>();
 | 
			
		||||
    engine.build_type::<crate::api::models::Plesk>();
 | 
			
		||||
    engine.build_type::<crate::api::models::Cpanel>();
 | 
			
		||||
 | 
			
		||||
    server::register(&mut engine);
 | 
			
		||||
    ssh_keys::register(&mut engine);
 | 
			
		||||
    boot::register(&mut engine);
 | 
			
		||||
 | 
			
		||||
    scope.push("hetzner", client);
 | 
			
		||||
 | 
			
		||||
    (engine, scope)
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										92
									
								
								src/scripting/server.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										92
									
								
								src/scripting/server.rs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,92 @@
 | 
			
		||||
use crate::api::{models::Server, Client};
 | 
			
		||||
use prettytable::{row, Table};
 | 
			
		||||
use rhai::{plugin::*, Array, Dynamic};
 | 
			
		||||
 | 
			
		||||
pub fn register(engine: &mut Engine) {
 | 
			
		||||
    let server_module = exported_module!(server_api);
 | 
			
		||||
    engine.register_global_module(server_module.into());
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[export_module]
 | 
			
		||||
pub mod server_api {
 | 
			
		||||
    use super::*;
 | 
			
		||||
    use rhai::EvalAltResult;
 | 
			
		||||
 | 
			
		||||
    #[rhai_fn(name = "get_server", return_raw)]
 | 
			
		||||
    pub fn get_server(
 | 
			
		||||
        client: &mut Client,
 | 
			
		||||
        server_number: i64,
 | 
			
		||||
    ) -> Result<Server, Box<EvalAltResult>> {
 | 
			
		||||
        client
 | 
			
		||||
            .get_server(server_number as i32)
 | 
			
		||||
            .map_err(|e| e.to_string().into())
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    #[rhai_fn(name = "get_servers", return_raw)]
 | 
			
		||||
    pub fn get_servers(client: &mut Client) -> Result<Array, Box<EvalAltResult>> {
 | 
			
		||||
        let servers = client
 | 
			
		||||
            .get_servers()
 | 
			
		||||
            .map_err(|e| Into::<Box<EvalAltResult>>::into(e.to_string()))?;
 | 
			
		||||
        Ok(servers.into_iter().map(Dynamic::from).collect())
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    #[rhai_fn(name = "print_servers_table")]
 | 
			
		||||
    pub fn print_servers_table(servers: Array) {
 | 
			
		||||
        let mut table = Table::new();
 | 
			
		||||
        table.add_row(row![
 | 
			
		||||
            "Server Number",
 | 
			
		||||
            "Server Name",
 | 
			
		||||
            "Server IP",
 | 
			
		||||
            "Product",
 | 
			
		||||
            "Datacenter",
 | 
			
		||||
            "Status"
 | 
			
		||||
        ]);
 | 
			
		||||
 | 
			
		||||
        for server_dynamic in servers {
 | 
			
		||||
            let server: Server = server_dynamic
 | 
			
		||||
                .try_cast::<Server>()
 | 
			
		||||
                .expect("could not cast to server");
 | 
			
		||||
            table.add_row(row![
 | 
			
		||||
                server.server_number,
 | 
			
		||||
                server.server_name,
 | 
			
		||||
                server.server_ip,
 | 
			
		||||
                server.product,
 | 
			
		||||
                server.dc,
 | 
			
		||||
                server.status
 | 
			
		||||
            ]);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        table.printstd();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    #[rhai_fn(name = "print_server_details")]
 | 
			
		||||
    pub fn print_server_details(server: Server) {
 | 
			
		||||
        let mut table = Table::new();
 | 
			
		||||
        table.add_row(row!["Property", "Value"]);
 | 
			
		||||
        table.add_row(row!["Server Number", server.server_number]);
 | 
			
		||||
        table.add_row(row!["Server Name", server.server_name]);
 | 
			
		||||
        table.add_row(row!["Server IP", server.server_ip]);
 | 
			
		||||
        table.add_row(row!["IPv6 Network", server.server_ipv6_net]);
 | 
			
		||||
        table.add_row(row!["Product", server.product]);
 | 
			
		||||
        table.add_row(row!["Datacenter", server.dc]);
 | 
			
		||||
        table.add_row(row!["Traffic", server.traffic]);
 | 
			
		||||
        table.add_row(row!["Status", server.status]);
 | 
			
		||||
        table.add_row(row!["Cancelled", server.cancelled]);
 | 
			
		||||
        table.add_row(row!["Paid Until", server.paid_until]);
 | 
			
		||||
        table.add_row(row!["Reset", server.reset.unwrap_or(false)]);
 | 
			
		||||
        table.add_row(row!["Rescue", server.rescue.unwrap_or(false)]);
 | 
			
		||||
        table.add_row(row!["VNC", server.vnc.unwrap_or(false)]);
 | 
			
		||||
        table.add_row(row!["Windows", server.windows.unwrap_or(false)]);
 | 
			
		||||
        table.add_row(row!["Plesk", server.plesk.unwrap_or(false)]);
 | 
			
		||||
        table.add_row(row!["cPanel", server.cpanel.unwrap_or(false)]);
 | 
			
		||||
        table.add_row(row!["WOL", server.wol.unwrap_or(false)]);
 | 
			
		||||
        table.add_row(row!["Hot Swap", server.hot_swap.unwrap_or(false)]);
 | 
			
		||||
        table.add_row(row![
 | 
			
		||||
            "Linked Storagebox",
 | 
			
		||||
            server
 | 
			
		||||
                .linked_storagebox
 | 
			
		||||
                .map_or("N/A".to_string(), |id| id.to_string())
 | 
			
		||||
        ]);
 | 
			
		||||
        table.printstd();
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										91
									
								
								src/scripting/ssh_keys.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										91
									
								
								src/scripting/ssh_keys.rs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,91 @@
 | 
			
		||||
use crate::api::{models::SshKey, Client};
 | 
			
		||||
use prettytable::{row, Table};
 | 
			
		||||
use rhai::{plugin::*, Array, Dynamic, Engine};
 | 
			
		||||
 | 
			
		||||
pub fn register(engine: &mut Engine) {
 | 
			
		||||
    let ssh_keys_module = exported_module!(ssh_keys_api);
 | 
			
		||||
    engine.register_global_module(ssh_keys_module.into());
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[export_module]
 | 
			
		||||
pub mod ssh_keys_api {
 | 
			
		||||
    use super::*;
 | 
			
		||||
    use rhai::EvalAltResult;
 | 
			
		||||
 | 
			
		||||
    #[rhai_fn(name = "get_ssh_keys", return_raw)]
 | 
			
		||||
    pub fn get_ssh_keys(client: &mut Client) -> Result<Array, Box<EvalAltResult>> {
 | 
			
		||||
        let ssh_keys = client
 | 
			
		||||
            .get_ssh_keys()
 | 
			
		||||
            .map_err(|e| Into::<Box<EvalAltResult>>::into(e.to_string()))?;
 | 
			
		||||
        Ok(ssh_keys.into_iter().map(Dynamic::from).collect())
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    #[rhai_fn(name = "get_ssh_key", return_raw)]
 | 
			
		||||
    pub fn get_ssh_key(
 | 
			
		||||
        client: &mut Client,
 | 
			
		||||
        fingerprint: &str,
 | 
			
		||||
    ) -> Result<SshKey, Box<EvalAltResult>> {
 | 
			
		||||
        client
 | 
			
		||||
            .get_ssh_key(fingerprint)
 | 
			
		||||
            .map_err(|e| e.to_string().into())
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    #[rhai_fn(name = "add_ssh_key", return_raw)]
 | 
			
		||||
    pub fn add_ssh_key(
 | 
			
		||||
        client: &mut Client,
 | 
			
		||||
        name: &str,
 | 
			
		||||
        data: &str,
 | 
			
		||||
    ) -> Result<SshKey, Box<EvalAltResult>> {
 | 
			
		||||
        client
 | 
			
		||||
            .add_ssh_key(name, data)
 | 
			
		||||
            .map_err(|e| e.to_string().into())
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    #[rhai_fn(name = "update_ssh_key_name", return_raw)]
 | 
			
		||||
    pub fn update_ssh_key_name(
 | 
			
		||||
        client: &mut Client,
 | 
			
		||||
        fingerprint: &str,
 | 
			
		||||
        name: &str,
 | 
			
		||||
    ) -> Result<SshKey, Box<EvalAltResult>> {
 | 
			
		||||
        client
 | 
			
		||||
            .update_ssh_key_name(fingerprint, name)
 | 
			
		||||
            .map_err(|e| e.to_string().into())
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    #[rhai_fn(name = "delete_ssh_key", return_raw)]
 | 
			
		||||
    pub fn delete_ssh_key(
 | 
			
		||||
        client: &mut Client,
 | 
			
		||||
        fingerprint: &str,
 | 
			
		||||
    ) -> Result<(), Box<EvalAltResult>> {
 | 
			
		||||
        client
 | 
			
		||||
            .delete_ssh_key(fingerprint)
 | 
			
		||||
            .map_err(|e| e.to_string().into())
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    #[rhai_fn(name = "print_ssh_keys_table")]
 | 
			
		||||
    pub fn print_ssh_keys_table(keys: Array) {
 | 
			
		||||
        let mut table = Table::new();
 | 
			
		||||
        table.add_row(row![
 | 
			
		||||
            "Name",
 | 
			
		||||
            "Fingerprint",
 | 
			
		||||
            "Type",
 | 
			
		||||
            "Size",
 | 
			
		||||
            "Created At"
 | 
			
		||||
        ]);
 | 
			
		||||
 | 
			
		||||
        for key_dynamic in keys {
 | 
			
		||||
            let key: SshKey = key_dynamic
 | 
			
		||||
                .try_cast::<SshKey>()
 | 
			
		||||
                .expect("could not cast to SshKey");
 | 
			
		||||
            table.add_row(row![
 | 
			
		||||
                key.name,
 | 
			
		||||
                key.fingerprint,
 | 
			
		||||
                key.key_type,
 | 
			
		||||
                key.size,
 | 
			
		||||
                key.created_at
 | 
			
		||||
            ]);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        table.printstd();
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
		Reference in New Issue
	
	Block a user