feat: Add server rebuild example using install_image #1
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)
|
||||||
|
Loading…
Reference in New Issue
Block a user