hrobot 7.0.0

Unofficial Hetzner Robot API client
Documentation
use std::time::Duration;

use hrobot::{
    api::{
        firewall::State,
        server::{self, Server, ServerId},
        vswitch::{ConnectionStatus, VSwitch, VSwitchId},
    },
    error::{ApiError, Error},
    AsyncRobot,
};
use tracing::info;

pub const PROVISIONED_SERVER_ID_PATH: &str = "provisioned-server";

/// Attempts to retrieve the provisioned server ID from a temporary file for 60 minutes.
/// Panics if the file is never populated.
#[allow(unused)]
pub async fn provisioned_server_id() -> ServerId {
    dotenvy::dotenv().ok();
    let robot = AsyncRobot::default();

    for i in 0..60 {
        if let Ok(contents) = std::fs::read_to_string(PROVISIONED_SERVER_ID_PATH) {
            let server_id = ServerId(u32::from_str_radix(&contents, 10).unwrap());

            if let Ok(server) = robot.get_server(server_id).await {
                if server.status == server::Status::Ready {
                    return server_id;
                } else {
                    info!("server available, but not yet marked as ready.");
                }
            } else {
                info!("got server id, but server is not available in robot interface yet.");
            }
        } else {
            info!("provisoned server id has not been written to path yet.");
        }

        tokio::time::sleep(Duration::from_secs(60)).await;
    }

    panic!("server was never provisioned.");
}

#[allow(unused)]
pub async fn provisioned_server() -> Server {
    let id = provisioned_server_id().await;
    let robot = AsyncRobot::default();

    robot.get_server(id).await.unwrap()
}

#[allow(unused)]
pub async fn wait_firewall_ready(robot: &AsyncRobot, server_id: ServerId) {
    // Retry every 15 seconds, 10 times.
    let mut tries = 0;
    while tries < 20 {
        tries += 1;
        tokio::time::sleep(std::time::Duration::from_secs(15)).await;
        let firewall = robot.get_firewall(server_id).await.unwrap();
        if firewall.status != State::InProcess {
            break;
        } else {
            info!("Firewall state for {server_id} is still \"in process\", checking again in 15s.");
        }
    }
}

#[allow(unused)]
pub async fn wait_vswitch_ready(robot: &AsyncRobot, id: VSwitchId) -> VSwitch {
    let mut tries = 20;
    loop {
        if tries == 0 {
            panic!("getting vswitch timed out");
        }

        match robot.get_vswitch(id).await {
            Ok(vswitch) => {
                // Ensure all servers are ready
                if vswitch
                    .servers
                    .iter()
                    .all(|server| server.status == ConnectionStatus::Ready)
                {
                    return vswitch;
                }
            }
            Err(Error::Api(ApiError::VswitchNotAvailable { .. })) => {
                info!("vswitch not available, waiting..");
            }
            Err(Error::Api(ApiError::VswitchInProcess { .. })) => {
                info!("vswitch in process, waiting..");
            }
            Err(err) => panic!("{}", err),
        };

        tokio::time::sleep(Duration::from_secs(15)).await;
        tries -= 1;
    }
}