Compare commits
	
		
			2 Commits
		
	
	
	| Author | SHA1 | Date | |
|---|---|---|---|
|  | 84948c90bc | ||
|  | ea66636c05 | 
							
								
								
									
										34
									
								
								README.md
									
									
									
									
									
								
							
							
						
						
									
										34
									
								
								README.md
									
									
									
									
									
								
							| @@ -30,9 +30,31 @@ cargo run -- examples/01_create_server.rhai | |||||||
|  |  | ||||||
| The `examples/` directory contains a collection of scripts demonstrating the available functionality. For detailed examples, please see the files in that directory: | The `examples/` directory contains a collection of scripts demonstrating the available functionality. For detailed examples, please see the files in that directory: | ||||||
|  |  | ||||||
| -   [`examples/01_create_server.rhai`](examples/01_create_server.rhai): Shows how to create a new server with various configuration options using a builder pattern. | - [`examples/01_create_server.rhai`](examples/01_create_server.rhai): Shows how to create a new server with various configuration options using a builder pattern. | ||||||
| -   [`examples/02_list_servers.rhai`](examples/02_list_servers.rhai): Lists all servers in your project. | - [`examples/02_list_servers.rhai`](examples/02_list_servers.rhai): Lists all servers in your project. | ||||||
| -   [`examples/03_get_server_details.rhai`](examples/03_get_server_details.rhai): Fetches and displays detailed information for a single server. | - [`examples/03_get_server_details.rhai`](examples/03_get_server_details.rhai): Fetches and displays detailed information for a single server. | ||||||
| -   [`examples/04_server_actions.rhai`](examples/04_server_actions.rhai): Demonstrates how to reboot, reset, and manage rescue mode for a server. | - [`examples/04_server_actions.rhai`](examples/04_server_actions.rhai): Demonstrates how to reboot, reset, and manage rescue mode for a server. | ||||||
| -   [`examples/05_list_ssh_keys.rhai`](examples/05_list_ssh_keys.rhai): Lists all SSH keys in your project. | - [`examples/05_list_ssh_keys.rhai`](examples/05_list_ssh_keys.rhai): Lists all SSH keys in your project. | ||||||
| -   [`examples/06_list_images.rhai`](examples/06_list_images.rhai): Shows how to list system images and snapshots, with examples of filtering and sorting. | - [`examples/06_list_images.rhai`](examples/06_list_images.rhai): Shows how to list system images and snapshots, with examples of filtering and sorting. | ||||||
|  | - [`examples/07_install_image.rhai`](examples/07_install_image.rhai): Demonstrates server rebuild functionality using install_image (equivalent to traditional installimage). | ||||||
|  |  | ||||||
|  | ## Features | ||||||
|  |  | ||||||
|  | ### Server Management | ||||||
|  |  | ||||||
|  | - **Create servers** with flexible configuration options | ||||||
|  | - **List and inspect** server details and status | ||||||
|  | - **Reboot and reset** servers | ||||||
|  | - **Rebuild servers** with new images (install_image functionality) | ||||||
|  | - **Rescue mode** management | ||||||
|  |  | ||||||
|  | ### Image Management | ||||||
|  |  | ||||||
|  | - **List images** with advanced filtering (by type, status, architecture, etc.) | ||||||
|  | - **Support for all image types**: system, backup, snapshot, and app images | ||||||
|  | - **Flexible image selection** by name or ID | ||||||
|  |  | ||||||
|  | ### SSH Key Management | ||||||
|  |  | ||||||
|  | - **List SSH keys** in your project | ||||||
|  | - **Automatic SSH key assignment** during server creation | ||||||
|   | |||||||
							
								
								
									
										161
									
								
								examples/07_install_image.rhai
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										161
									
								
								examples/07_install_image.rhai
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,161 @@ | |||||||
|  | // This script demonstrates how to rebuild a server with a new image using install_image. | ||||||
|  | // The install_image function is the Hetzner Cloud equivalent of the traditional installimage command. | ||||||
|  |  | ||||||
|  | // Initialize the Hetzner client with your API token. | ||||||
|  | let client = new_hetzner_client(get_env("HETZNER_API_TOKEN")); | ||||||
|  |  | ||||||
|  | // Replace this with the ID of the server you want to rebuild. | ||||||
|  | // WARNING: This will DESTROY ALL DATA on the target server! | ||||||
|  | let server_id = 1234567; // FIXME: Replace with a real server ID | ||||||
|  |  | ||||||
|  | // The install_image function rebuilds a server by overwriting its disk with a new image. | ||||||
|  | // This is equivalent to the traditional installimage command used on dedicated servers. | ||||||
|  | // | ||||||
|  | // Available image types: | ||||||
|  | // - system: Official OS images (e.g., "ubuntu-22.04", "debian-12") | ||||||
|  | // - backup: Automatic backups of your servers | ||||||
|  | // - snapshot: Manual snapshots you've created | ||||||
|  | // - app: Application images (if available) | ||||||
|  | // | ||||||
|  | // The function accepts either an image name or image ID: | ||||||
|  | // client.install_image(server_id, "ubuntu-22.04");  // By name | ||||||
|  | // client.install_image(server_id, "15512617");      // By ID | ||||||
|  |  | ||||||
|  | // Get current server information before rebuilding. | ||||||
|  | print("Getting current server information..."); | ||||||
|  | let server = client.get_server(server_id); | ||||||
|  | print(`Server: ${server.name} (ID: ${server.id})`); | ||||||
|  | print(`Current Status: ${server.status}`); | ||||||
|  | print(`Current Image: ${server.image.name}`); | ||||||
|  | print(""); | ||||||
|  |  | ||||||
|  | // List available system images to choose from. | ||||||
|  | print("Available system images (first 5):"); | ||||||
|  | let system_images_params = new_list_images_params_builder() | ||||||
|  |     .with_type("system") | ||||||
|  |     .with_status("available"); | ||||||
|  | let system_images = client.list_images(system_images_params); | ||||||
|  |  | ||||||
|  | for i in 0..5 { | ||||||
|  |     if i < system_images.len() { | ||||||
|  |         let img = system_images[i]; | ||||||
|  |         print(`  ${img.id}: ${img.name} (${img.type})`); | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | print(""); | ||||||
|  |  | ||||||
|  | // List backup images for this server (if any). | ||||||
|  | print("Backup images for this server:"); | ||||||
|  | let backup_images_params = new_list_images_params_builder() | ||||||
|  |     .with_type("backup") | ||||||
|  |     .with_bound_to(server_id.to_string()); | ||||||
|  | let backup_images = client.list_images(backup_images_params); | ||||||
|  |  | ||||||
|  | if backup_images.len() > 0 { | ||||||
|  |     for i in 0..backup_images.len() { | ||||||
|  |         let img = backup_images[i]; | ||||||
|  |         print(`  ${img.id}: ${img.name} (${img.type})`); | ||||||
|  |     } | ||||||
|  | } else { | ||||||
|  |     print("  No backup images found for this server"); | ||||||
|  | } | ||||||
|  | print(""); | ||||||
|  |  | ||||||
|  | // --- Rebuild Server with System Image --- | ||||||
|  | // WARNING: This will DESTROY ALL DATA on the server! | ||||||
|  | // The server will be automatically powered off before the rebuild. | ||||||
|  |  | ||||||
|  | // Example 1: Rebuild with Ubuntu 22.04 (by name) | ||||||
|  | // WARNING: Uncomment the following lines to perform the actual rebuild | ||||||
|  | // print("Initiating server rebuild with Ubuntu 22.04..."); | ||||||
|  | // client.install_image(server_id, "ubuntu-22.04"); | ||||||
|  | // print("✅ Server rebuild request sent successfully!"); | ||||||
|  | print("⚠️  install_image call is commented out for safety. Uncomment to execute."); | ||||||
|  | print(""); | ||||||
|  |  | ||||||
|  | // Example 2: Rebuild with specific image ID | ||||||
|  | // client.install_image(server_id, "15512617"); // Replace with actual image ID | ||||||
|  | // print("Server rebuild initiated with image ID 15512617"); | ||||||
|  |  | ||||||
|  | // --- Restore from Backup --- | ||||||
|  | // If backup images are available, you can restore from them: | ||||||
|  | if backup_images.len() > 0 { | ||||||
|  |     let backup_img = backup_images[0]; | ||||||
|  |     // client.install_image(server_id, backup_img.id.to_string()); | ||||||
|  |     // print(`Server restore initiated from backup: ${backup_img.name}`); | ||||||
|  |     // print(`Backup available: ${backup_img.name} (ID: ${backup_img.id})`); | ||||||
|  | } else { | ||||||
|  |     // print("No backup images available for this server"); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // --- Monitor Rebuild Progress --- | ||||||
|  | // The following section demonstrates how to monitor rebuild progress | ||||||
|  | // Uncomment when you uncomment the install_image call above | ||||||
|  | /* | ||||||
|  | print("Monitoring server status during rebuild..."); | ||||||
|  | print("Note: Server rebuild typically takes 1-3 minutes to complete."); | ||||||
|  | print(""); | ||||||
|  |  | ||||||
|  | // Poll server status every 5 seconds to monitor progress | ||||||
|  | let max_attempts = 60; // Maximum 5 minutes of polling | ||||||
|  | let attempt = 0; | ||||||
|  | let rebuild_seen = false; | ||||||
|  | let running_count = 0; | ||||||
|  |  | ||||||
|  | while attempt < max_attempts { | ||||||
|  |     let current_server = client.get_server(server_id); | ||||||
|  |     let status = current_server.status; // Use status directly | ||||||
|  |  | ||||||
|  |     print(`Attempt ${attempt + 1}: Server status is '${status}'`); | ||||||
|  |  | ||||||
|  |     // Check for rebuilding status (case-insensitive) | ||||||
|  |     if status == "Rebuilding" || status == "rebuilding" { | ||||||
|  |         rebuild_seen = true; | ||||||
|  |         print("   → Server is currently being rebuilt..."); | ||||||
|  |     } else if status == "Off" || status == "off" { | ||||||
|  |         print("   → Server is powered off (normal during rebuild)"); | ||||||
|  |     } else if status == "Running" || status == "running" { | ||||||
|  |         if rebuild_seen { | ||||||
|  |             print(""); | ||||||
|  |             print("🎉 Server rebuild completed successfully!"); | ||||||
|  |             print(`✅ Server is now running with image: ${current_server.image.name}`); | ||||||
|  |  | ||||||
|  |             // Verify the image changed | ||||||
|  |             if current_server.image.name == "ubuntu-22.04" || current_server.image.name == "Ubuntu 22.04" { | ||||||
|  |                 print("✅ Image verification: Ubuntu 22.04 installation confirmed!"); | ||||||
|  |             } else { | ||||||
|  |                 print(`⚠️  Image verification: Expected 'ubuntu-22.04', got '${current_server.image.name}'`); | ||||||
|  |             } | ||||||
|  |             break; // Exit immediately after rebuild completion | ||||||
|  |         } else { | ||||||
|  |             print("   → Server is running (waiting for rebuild to start...)"); | ||||||
|  |         } | ||||||
|  |     } else { | ||||||
|  |         print(`   → Server status: ${status}`); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     attempt = attempt + 1; | ||||||
|  |  | ||||||
|  |     if attempt < max_attempts { | ||||||
|  |         print("   Waiting 5 seconds before next check..."); | ||||||
|  |         sleep(5); // Wait 5 seconds before polling again | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | if attempt >= max_attempts { | ||||||
|  |     print(""); | ||||||
|  |     print("⚠️  Timeout: Server rebuild is taking longer than expected."); | ||||||
|  |     print("   Check the Hetzner Cloud console for current status."); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | print(""); | ||||||
|  | print("💡 Tip: You can also monitor rebuild progress in the Hetzner Cloud console."); | ||||||
|  | */ | ||||||
|  |  | ||||||
|  | print(""); | ||||||
|  | print("To use this example:"); | ||||||
|  | print("1. Replace server_id with your actual server ID"); | ||||||
|  | print("2. Uncomment the install_image call and monitoring section"); | ||||||
|  | print("3. Run the script to rebuild your server"); | ||||||
|  | print(""); | ||||||
|  | print("⚠️  Remember: install_image will DESTROY ALL DATA on the target server!"); | ||||||
| @@ -1,6 +1,6 @@ | |||||||
| use crate::hetzner_api::{ | use crate::hetzner_api::{ | ||||||
|     ListImagesParamsBuilder, HetznerClient, ServerBuilder, WrappedCreateServerResponse, |     HetznerClient, ListImagesParamsBuilder, ServerBuilder, WrappedCreateServerResponse, | ||||||
|     WrappedServer, WrappedSshKey, WrappedImage, |     WrappedImage, WrappedServer, WrappedSshKey, | ||||||
| }; | }; | ||||||
| use std::sync::mpsc::{Receiver, Sender}; | use std::sync::mpsc::{Receiver, Sender}; | ||||||
| use tokio::runtime::Builder; | use tokio::runtime::Builder; | ||||||
| @@ -18,6 +18,7 @@ pub enum Request { | |||||||
|     DisableRescueMode(HetznerClient, i64), |     DisableRescueMode(HetznerClient, i64), | ||||||
|     ListSshKeys(HetznerClient), |     ListSshKeys(HetznerClient), | ||||||
|     ListImages(HetznerClient, ListImagesParamsBuilder), |     ListImages(HetznerClient, ListImagesParamsBuilder), | ||||||
|  |     InstallImage(HetznerClient, i64, String), | ||||||
| } | } | ||||||
|  |  | ||||||
| pub enum Response { | pub enum Response { | ||||||
| @@ -31,17 +32,15 @@ pub enum Response { | |||||||
|     EnableRescueMode(Result<String, String>), |     EnableRescueMode(Result<String, String>), | ||||||
|     DisableRescueMode(Result<(), String>), |     DisableRescueMode(Result<(), String>), | ||||||
|     ListImages(Result<Vec<WrappedImage>, String>), |     ListImages(Result<Vec<WrappedImage>, String>), | ||||||
|  |     InstallImage(Result<(), String>), | ||||||
| } | } | ||||||
|  |  | ||||||
| pub fn run_worker( | pub fn run_worker(command_rx: Receiver<Request>, reply_tx: Sender<Response>) { | ||||||
|     command_rx: Receiver<Request>, |  | ||||||
|     reply_tx: Sender<Response>, |  | ||||||
| ) { |  | ||||||
|     std::thread::spawn(move || { |     std::thread::spawn(move || { | ||||||
|         let rt = Builder::new_current_thread() |         let rt = Builder::new_current_thread() | ||||||
|             .enable_all() |             .enable_all() | ||||||
|             .build() |             .build() | ||||||
|             .unwrap(); |             .expect("Failed to create async runtime"); | ||||||
|  |  | ||||||
|         while let Ok(request) = command_rx.recv() { |         while let Ok(request) = command_rx.recv() { | ||||||
|             let response = match request { |             let response = match request { | ||||||
| @@ -58,7 +57,9 @@ pub fn run_worker( | |||||||
|                     Response::CreateServer(result) |                     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) => { | ||||||
| @@ -68,15 +69,21 @@ pub fn run_worker( | |||||||
|                     Response::GetServerStatus(result) |                     Response::GetServerStatus(result) | ||||||
|                 } |                 } | ||||||
|                 Request::GetServer(client, server_id) => { |                 Request::GetServer(client, server_id) => { | ||||||
|                     let result = rt.block_on(client.get_server(server_id)).map_err(|e| e.to_string()); |                     let result = rt | ||||||
|  |                         .block_on(client.get_server(server_id)) | ||||||
|  |                         .map_err(|e| e.to_string()); | ||||||
|                     Response::GetServer(result) |                     Response::GetServer(result) | ||||||
|                 } |                 } | ||||||
|                 Request::RebootServer(client, server_id) => { |                 Request::RebootServer(client, server_id) => { | ||||||
|                     let result = rt.block_on(client.reboot_server(server_id)).map_err(|e| e.to_string()); |                     let result = rt | ||||||
|  |                         .block_on(client.reboot_server(server_id)) | ||||||
|  |                         .map_err(|e| e.to_string()); | ||||||
|                     Response::RebootServer(result) |                     Response::RebootServer(result) | ||||||
|                 } |                 } | ||||||
|                 Request::ResetServer(client, server_id) => { |                 Request::ResetServer(client, server_id) => { | ||||||
|                     let result = rt.block_on(client.reset_server(server_id)).map_err(|e| e.to_string()); |                     let result = rt | ||||||
|  |                         .block_on(client.reset_server(server_id)) | ||||||
|  |                         .map_err(|e| e.to_string()); | ||||||
|                     Response::ResetServer(result) |                     Response::ResetServer(result) | ||||||
|                 } |                 } | ||||||
|                 Request::EnableRescueMode(client, server_id, ssh_keys) => { |                 Request::EnableRescueMode(client, server_id, ssh_keys) => { | ||||||
| @@ -89,7 +96,8 @@ pub fn run_worker( | |||||||
|                     let result = rt |                     let result = rt | ||||||
|                         .block_on(async { |                         .block_on(async { | ||||||
|                             let ssh_keys = client.list_ssh_keys().await?; |                             let ssh_keys = client.list_ssh_keys().await?; | ||||||
|                             let ssh_key_ids: Vec<i64> = ssh_keys.into_iter().map(|k| k.0.id).collect(); |                             let ssh_key_ids: Vec<i64> = | ||||||
|  |                                 ssh_keys.into_iter().map(|k| k.0.id).collect(); | ||||||
|                             println!("Passing in the following ssh key ids: {:#?}", ssh_key_ids); |                             println!("Passing in the following ssh key ids: {:#?}", ssh_key_ids); | ||||||
|                             client |                             client | ||||||
|                                 .enable_rescue_mode_for_server(server_id, ssh_key_ids) |                                 .enable_rescue_mode_for_server(server_id, ssh_key_ids) | ||||||
| @@ -99,15 +107,25 @@ pub fn run_worker( | |||||||
|                     Response::EnableRescueMode(result) |                     Response::EnableRescueMode(result) | ||||||
|                 } |                 } | ||||||
|                 Request::DisableRescueMode(client, server_id) => { |                 Request::DisableRescueMode(client, server_id) => { | ||||||
|                     let result = rt.block_on(client.disable_rescue_mode_for_server(server_id)).map_err(|e| e.to_string()); |                     let result = rt | ||||||
|  |                         .block_on(client.disable_rescue_mode_for_server(server_id)) | ||||||
|  |                         .map_err(|e| e.to_string()); | ||||||
|                     Response::DisableRescueMode(result) |                     Response::DisableRescueMode(result) | ||||||
|                 } |                 } | ||||||
|                 Request::ListSshKeys(client) => { |                 Request::ListSshKeys(client) => { | ||||||
|                     let result = rt.block_on(client.list_ssh_keys()).map_err(|e| e.to_string()); |                     let result = rt | ||||||
|  |                         .block_on(client.list_ssh_keys()) | ||||||
|  |                         .map_err(|e| e.to_string()); | ||||||
|                     Response::ListSshKeys(result) |                     Response::ListSshKeys(result) | ||||||
|                 } |                 } | ||||||
|  |                 Request::InstallImage(client, server_id, image) => { | ||||||
|  |                     let result = rt | ||||||
|  |                         .block_on(client.install_image(server_id, image)) | ||||||
|  |                         .map_err(|e| e.to_string()); | ||||||
|  |                     Response::InstallImage(result) | ||||||
|  |                 } | ||||||
|             }; |             }; | ||||||
|             reply_tx.send(response).expect("Failed to send response"); |             reply_tx.send(response).expect("Failed to send response"); | ||||||
|         } |         } | ||||||
|     }); |     }); | ||||||
| } | } | ||||||
|   | |||||||
| @@ -1,15 +1,15 @@ | |||||||
| use hcloud::apis::{ | use hcloud::apis::{ | ||||||
|     configuration::Configuration, |     configuration::Configuration, | ||||||
|     images_api::{self, ListImagesParams}, |  | ||||||
|     servers_api::{ |     servers_api::{ | ||||||
|         self, CreateServerParams, DisableRescueModeForServerParams, |         self, CreateServerParams, DisableRescueModeForServerParams, | ||||||
|         EnableRescueModeForServerParams, ListServersParams, ResetServerParams, |         EnableRescueModeForServerParams, ListServersParams, RebuildServerFromImageParams, | ||||||
|  |         ResetServerParams, | ||||||
|     }, |     }, | ||||||
|     ssh_keys_api::{self, ListSshKeysParams}, |     ssh_keys_api::{self, ListSshKeysParams}, | ||||||
| }; | }; | ||||||
| use hcloud::models::{ | use hcloud::models::{ | ||||||
|     CreateServerRequest, CreateServerResponse, EnableRescueModeForServerRequest, Image, Server, |     CreateServerRequest, CreateServerResponse, EnableRescueModeForServerRequest, Image, | ||||||
|     SshKey, |     RebuildServerFromImageRequest, Server, SshKey, | ||||||
| }; | }; | ||||||
| use serde::{Deserialize, Serialize}; | use serde::{Deserialize, Serialize}; | ||||||
| use serde_json::Value; | use serde_json::Value; | ||||||
| @@ -155,21 +155,6 @@ impl ListImagesParamsBuilder { | |||||||
|         self.architecture = Some(architecture); |         self.architecture = Some(architecture); | ||||||
|         self |         self | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     fn build(self, page: i64, per_page: i64) -> ListImagesParams { |  | ||||||
|         ListImagesParams { |  | ||||||
|             sort: self.sort, |  | ||||||
|             r#type: self.r#type, |  | ||||||
|             status: self.status, |  | ||||||
|             bound_to: self.bound_to, |  | ||||||
|             include_deprecated: self.include_deprecated, |  | ||||||
|             name: self.name, |  | ||||||
|             label_selector: self.label_selector, |  | ||||||
|             architecture: self.architecture, |  | ||||||
|             page: Some(page), |  | ||||||
|             per_page: Some(per_page), |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
| } | } | ||||||
|  |  | ||||||
| impl HetznerClient { | impl HetznerClient { | ||||||
| @@ -179,7 +164,7 @@ impl HetznerClient { | |||||||
|         let client = reqwest::Client::builder() |         let client = reqwest::Client::builder() | ||||||
|             .timeout(std::time::Duration::from_secs(5)) |             .timeout(std::time::Duration::from_secs(5)) | ||||||
|             .build() |             .build() | ||||||
|             .unwrap(); |             .expect("Failed to create HTTP client"); | ||||||
|         configuration.client = client; |         configuration.client = client; | ||||||
|  |  | ||||||
|         Self { configuration } |         Self { configuration } | ||||||
| @@ -340,11 +325,11 @@ impl HetznerClient { | |||||||
|         let mut all_images = Vec::new(); |         let mut all_images = Vec::new(); | ||||||
|         let mut page = 1; |         let mut page = 1; | ||||||
|         let per_page = 50; |         let per_page = 50; | ||||||
|      |  | ||||||
|         loop { |         loop { | ||||||
|             let mut url = "https://api.hetzner.cloud/v1/images".to_string(); |             let mut url = "https://api.hetzner.cloud/v1/images".to_string(); | ||||||
|             let mut query_params = Vec::new(); |             let mut query_params = Vec::new(); | ||||||
|      |  | ||||||
|             if let Some(sort) = &builder.sort { |             if let Some(sort) = &builder.sort { | ||||||
|                 query_params.push(format!("sort={}", sort)); |                 query_params.push(format!("sort={}", sort)); | ||||||
|             } |             } | ||||||
| @@ -369,30 +354,35 @@ impl HetznerClient { | |||||||
|             if let Some(architecture) = &builder.architecture { |             if let Some(architecture) = &builder.architecture { | ||||||
|                 query_params.push(format!("architecture={}", architecture)); |                 query_params.push(format!("architecture={}", architecture)); | ||||||
|             } |             } | ||||||
|      |  | ||||||
|             query_params.push(format!("page={}", page)); |             query_params.push(format!("page={}", page)); | ||||||
|             query_params.push(format!("per_page={}", per_page)); |             query_params.push(format!("per_page={}", per_page)); | ||||||
|      |  | ||||||
|             if !query_params.is_empty() { |             if !query_params.is_empty() { | ||||||
|                 url.push('?'); |                 url.push('?'); | ||||||
|                 url.push_str(&query_params.join("&")); |                 url.push_str(&query_params.join("&")); | ||||||
|             } |             } | ||||||
|      |  | ||||||
|             let response: Value = self |             let response: Value = self | ||||||
|                 .configuration |                 .configuration | ||||||
|                 .client |                 .client | ||||||
|                 .get(&url) |                 .get(&url) | ||||||
|                 .bearer_auth(self.configuration.bearer_access_token.as_ref().unwrap()) |                 .bearer_auth( | ||||||
|  |                     self.configuration | ||||||
|  |                         .bearer_access_token | ||||||
|  |                         .as_ref() | ||||||
|  |                         .ok_or("API token not configured")?, | ||||||
|  |                 ) | ||||||
|                 .send() |                 .send() | ||||||
|                 .await? |                 .await? | ||||||
|                 .json() |                 .json() | ||||||
|                 .await?; |                 .await?; | ||||||
|      |  | ||||||
|             if let Some(images_json) = response.get("images").and_then(|i| i.as_array()) { |             if let Some(images_json) = response.get("images").and_then(|i| i.as_array()) { | ||||||
|                 if images_json.is_empty() { |                 if images_json.is_empty() { | ||||||
|                     break; |                     break; | ||||||
|                 } |                 } | ||||||
|      |  | ||||||
|                 let images: Vec<WrappedImage> = images_json |                 let images: Vec<WrappedImage> = images_json | ||||||
|                     .iter() |                     .iter() | ||||||
|                     .filter_map(|image_value| { |                     .filter_map(|image_value| { | ||||||
| @@ -402,12 +392,12 @@ impl HetznerClient { | |||||||
|                         } |                         } | ||||||
|                     }) |                     }) | ||||||
|                     .collect(); |                     .collect(); | ||||||
|      |  | ||||||
|                 all_images.extend(images); |                 all_images.extend(images); | ||||||
|             } else { |             } else { | ||||||
|                 break; |                 break; | ||||||
|             } |             } | ||||||
|      |  | ||||||
|             if response |             if response | ||||||
|                 .get("meta") |                 .get("meta") | ||||||
|                 .and_then(|m| m.get("pagination")) |                 .and_then(|m| m.get("pagination")) | ||||||
| @@ -417,10 +407,24 @@ impl HetznerClient { | |||||||
|             { |             { | ||||||
|                 break; |                 break; | ||||||
|             } |             } | ||||||
|      |  | ||||||
|             page += 1; |             page += 1; | ||||||
|         } |         } | ||||||
|      |  | ||||||
|         Ok(all_images) |         Ok(all_images) | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     pub async fn install_image( | ||||||
|  |         &self, | ||||||
|  |         server_id: i64, | ||||||
|  |         image: String, | ||||||
|  |     ) -> Result<(), Box<dyn std::error::Error>> { | ||||||
|  |         let params = RebuildServerFromImageParams { | ||||||
|  |             id: server_id, | ||||||
|  |             rebuild_server_from_image_request: Some(RebuildServerFromImageRequest::new(image)), | ||||||
|  |         }; | ||||||
|  |  | ||||||
|  |         servers_api::rebuild_server_from_image(&self.configuration, params).await?; | ||||||
|  |         Ok(()) | ||||||
|  |     } | ||||||
| } | } | ||||||
|   | |||||||
| @@ -4,6 +4,7 @@ use crate::hetzner_api::{ | |||||||
|     HetznerClient, ListImagesParamsBuilder, ServerBuilder, WrappedCreateServerResponse, |     HetznerClient, ListImagesParamsBuilder, ServerBuilder, WrappedCreateServerResponse, | ||||||
|     WrappedImage, WrappedServer, WrappedSshKey, |     WrappedImage, WrappedServer, WrappedSshKey, | ||||||
| }; | }; | ||||||
|  | use hcloud::models::Image; | ||||||
| use prettytable::{Cell, Row, Table}; | use prettytable::{Cell, Row, Table}; | ||||||
| use rhai::{Engine, EvalAltResult}; | use rhai::{Engine, EvalAltResult}; | ||||||
| use std::env; | use std::env; | ||||||
| @@ -122,7 +123,10 @@ pub fn register_hetzner_api( | |||||||
|  |  | ||||||
|     engine |     engine | ||||||
|         .register_type_with_name::<ListImagesParamsBuilder>("ListImagesParamsBuilder") |         .register_type_with_name::<ListImagesParamsBuilder>("ListImagesParamsBuilder") | ||||||
|         .register_fn("new_list_images_params_builder", ListImagesParamsBuilder::new) |         .register_fn( | ||||||
|  |             "new_list_images_params_builder", | ||||||
|  |             ListImagesParamsBuilder::new, | ||||||
|  |         ) | ||||||
|         .register_fn("with_sort", ListImagesParamsBuilder::with_sort) |         .register_fn("with_sort", ListImagesParamsBuilder::with_sort) | ||||||
|         .register_fn("with_type", ListImagesParamsBuilder::with_type) |         .register_fn("with_type", ListImagesParamsBuilder::with_type) | ||||||
|         .register_fn("with_status", ListImagesParamsBuilder::with_status) |         .register_fn("with_status", ListImagesParamsBuilder::with_status) | ||||||
| @@ -136,31 +140,53 @@ pub fn register_hetzner_api( | |||||||
|             "with_label_selector", |             "with_label_selector", | ||||||
|             ListImagesParamsBuilder::with_label_selector, |             ListImagesParamsBuilder::with_label_selector, | ||||||
|         ) |         ) | ||||||
|         .register_fn("with_architecture", ListImagesParamsBuilder::with_architecture); |         .register_fn( | ||||||
|  |             "with_architecture", | ||||||
|  |             ListImagesParamsBuilder::with_architecture, | ||||||
|  |         ); | ||||||
|  |  | ||||||
|     engine |     engine | ||||||
|         .register_fn("list_images", { |         .register_fn("list_images", { | ||||||
|             let bridge = api_bridge.clone(); |             let bridge = api_bridge.clone(); | ||||||
|             move |client: &mut HetznerClient, builder: ListImagesParamsBuilder| { |             move |client: &mut HetznerClient, builder: ListImagesParamsBuilder| { | ||||||
|                 bridge.call(Request::ListImages(client.clone(), builder), |response| { |                 bridge.call( | ||||||
|                     match response { |                     Request::ListImages(client.clone(), builder), | ||||||
|  |                     |response| match response { | ||||||
|                         Response::ListImages(result) => result.map_err(|e| e.into()), |                         Response::ListImages(result) => result.map_err(|e| e.into()), | ||||||
|                         _ => Err("Unexpected response".into()), |                         _ => Err("Unexpected response".into()), | ||||||
|                     } |                     }, | ||||||
|                 }) |                 ) | ||||||
|  |             } | ||||||
|  |         }) | ||||||
|  |         .register_fn("install_image", { | ||||||
|  |             let bridge = api_bridge.clone(); | ||||||
|  |             move |client: &mut HetznerClient, server_id: i64, image: &str| { | ||||||
|  |                 bridge.call( | ||||||
|  |                     Request::InstallImage(client.clone(), server_id, image.to_string()), | ||||||
|  |                     |response| match response { | ||||||
|  |                         Response::InstallImage(result) => result.map_err(|e| e.into()), | ||||||
|  |                         _ => Err("Unexpected response".into()), | ||||||
|  |                     }, | ||||||
|  |                 ) | ||||||
|             } |             } | ||||||
|         }) |         }) | ||||||
|         .register_type_with_name::<WrappedImage>("Image") |         .register_type_with_name::<WrappedImage>("Image") | ||||||
|         .register_get("id", |image: &mut WrappedImage| image.0.id) |         .register_get("id", |image: &mut WrappedImage| image.0.id) | ||||||
|         .register_get("name", |image: &mut WrappedImage| image.0.name.clone()) |         .register_get("name", |image: &mut WrappedImage| { | ||||||
|  |             image.0.name.clone().unwrap_or("Unknown".to_string()) | ||||||
|  |         }) | ||||||
|         .register_get("description", |image: &mut WrappedImage| { |         .register_get("description", |image: &mut WrappedImage| { | ||||||
|             image.0.description.clone() |             image.0.description.clone() | ||||||
|         }) |         }) | ||||||
|         .register_get("status", |image: &mut WrappedImage| { |         .register_get("status", |image: &mut WrappedImage| { | ||||||
|             format!("{:?}", image.0.status) |             format!("{:?}", image.0.status) | ||||||
|         }) |         }) | ||||||
|         .register_get("type", |image: &mut WrappedImage| format!("{:?}", image.0.r#type)) |         .register_get("type", |image: &mut WrappedImage| { | ||||||
|         .register_get("created", |image: &mut WrappedImage| image.0.created.clone()) |             format!("{:?}", image.0.r#type) | ||||||
|  |         }) | ||||||
|  |         .register_get("created", |image: &mut WrappedImage| { | ||||||
|  |             image.0.created.clone() | ||||||
|  |         }) | ||||||
|         .register_get("os_flavor", |image: &mut WrappedImage| { |         .register_get("os_flavor", |image: &mut WrappedImage| { | ||||||
|             format!("{:?}", image.0.os_flavor) |             format!("{:?}", image.0.os_flavor) | ||||||
|         }) |         }) | ||||||
| @@ -170,6 +196,10 @@ pub fn register_hetzner_api( | |||||||
|  |  | ||||||
|     engine |     engine | ||||||
|         .register_iterator::<Vec<WrappedImage>>() |         .register_iterator::<Vec<WrappedImage>>() | ||||||
|  |         .register_fn("len", |list: &mut Vec<WrappedImage>| list.len() as i64) | ||||||
|  |         .register_indexer_get(|list: &mut Vec<WrappedImage>, index: i64| { | ||||||
|  |             list[index as usize].clone() | ||||||
|  |         }) | ||||||
|         .register_fn( |         .register_fn( | ||||||
|             "show_table", |             "show_table", | ||||||
|             |images: &mut Vec<WrappedImage>| -> Result<String, Box<EvalAltResult>> { |             |images: &mut Vec<WrappedImage>| -> Result<String, Box<EvalAltResult>> { | ||||||
| @@ -215,12 +245,7 @@ pub fn register_hetzner_api( | |||||||
|         .register_fn( |         .register_fn( | ||||||
|             "with_ssh_keys", |             "with_ssh_keys", | ||||||
|             |builder: ServerBuilder, ssh_keys: rhai::Array| { |             |builder: ServerBuilder, ssh_keys: rhai::Array| { | ||||||
|                 builder.with_ssh_keys( |                 builder.with_ssh_keys(ssh_keys.into_iter().map(|k| k.as_int().unwrap()).collect()) | ||||||
|                     ssh_keys |  | ||||||
|                         .into_iter() |  | ||||||
|                         .map(|k| k.as_int().unwrap()) |  | ||||||
|                         .collect(), |  | ||||||
|                 ) |  | ||||||
|             }, |             }, | ||||||
|         ); |         ); | ||||||
|  |  | ||||||
| @@ -269,6 +294,22 @@ pub fn register_hetzner_api( | |||||||
|         }) |         }) | ||||||
|         .register_get("rescue_enabled", |server: &mut WrappedServer| { |         .register_get("rescue_enabled", |server: &mut WrappedServer| { | ||||||
|             server.0.rescue_enabled |             server.0.rescue_enabled | ||||||
|  |         }) | ||||||
|  |         .register_get("image", |server: &mut WrappedServer| { | ||||||
|  |             server | ||||||
|  |                 .0 | ||||||
|  |                 .image | ||||||
|  |                 .as_ref() | ||||||
|  |                 .map(|img| WrappedImage((**img).clone())) | ||||||
|  |                 .unwrap_or_else(|| { | ||||||
|  |                     // Create a dummy image if none exists | ||||||
|  |                     WrappedImage(Image { | ||||||
|  |                         id: 0, | ||||||
|  |                         name: Some("No image".to_string()), | ||||||
|  |                         description: "No image associated with this server".to_string(), | ||||||
|  |                         ..Default::default() | ||||||
|  |                     }) | ||||||
|  |                 }) | ||||||
|         }); |         }); | ||||||
|  |  | ||||||
|     engine |     engine | ||||||
| @@ -440,6 +481,11 @@ pub fn register_hetzner_api( | |||||||
|         env::var(key).unwrap_or("".to_string()) |         env::var(key).unwrap_or("".to_string()) | ||||||
|     }); |     }); | ||||||
|  |  | ||||||
|  |     // Register sleep function for polling operations | ||||||
|  |     engine.register_fn("sleep", |seconds: i64| { | ||||||
|  |         std::thread::sleep(std::time::Duration::from_secs(seconds as u64)); | ||||||
|  |     }); | ||||||
|  |  | ||||||
|     engine |     engine | ||||||
|         .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) | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user