Skip to main content

purple_ssh/providers/
mod.rs

1pub mod config;
2mod digitalocean;
3mod hetzner;
4mod linode;
5pub mod sync;
6mod vultr;
7
8use thiserror::Error;
9
10/// A host discovered from a cloud provider API.
11#[derive(Debug, Clone)]
12#[allow(dead_code)]
13pub struct ProviderHost {
14    /// Provider-assigned server ID.
15    pub server_id: String,
16    /// Server name/label.
17    pub name: String,
18    /// Public IPv4 address.
19    pub ip: String,
20    /// Provider tags/labels.
21    pub tags: Vec<String>,
22}
23
24/// Errors from provider API calls.
25#[derive(Debug, Error)]
26pub enum ProviderError {
27    #[error("HTTP error: {0}")]
28    Http(String),
29    #[error("Failed to parse response: {0}")]
30    Parse(String),
31    #[error("Authentication failed. Check your API token.")]
32    AuthFailed,
33    #[error("Rate limited. Try again in a moment.")]
34    RateLimited,
35}
36
37/// Trait implemented by each cloud provider.
38pub trait Provider {
39    /// Full provider name (e.g. "digitalocean").
40    fn name(&self) -> &str;
41    /// Short label for aliases (e.g. "do").
42    fn short_label(&self) -> &str;
43    /// Fetch all servers from the provider API.
44    fn fetch_hosts(&self, token: &str) -> Result<Vec<ProviderHost>, ProviderError>;
45}
46
47/// All known provider names.
48pub const PROVIDER_NAMES: &[&str] = &["digitalocean", "vultr", "linode", "hetzner"];
49
50/// Get a provider implementation by name.
51pub fn get_provider(name: &str) -> Option<Box<dyn Provider>> {
52    match name {
53        "digitalocean" => Some(Box::new(digitalocean::DigitalOcean)),
54        "vultr" => Some(Box::new(vultr::Vultr)),
55        "linode" => Some(Box::new(linode::Linode)),
56        "hetzner" => Some(Box::new(hetzner::Hetzner)),
57        _ => None,
58    }
59}
60
61/// Map a ureq error to a ProviderError.
62fn map_ureq_error(err: ureq::Error) -> ProviderError {
63    match err {
64        ureq::Error::Status(401, _) | ureq::Error::Status(403, _) => ProviderError::AuthFailed,
65        ureq::Error::Status(429, _) => ProviderError::RateLimited,
66        ureq::Error::Status(code, _) => ProviderError::Http(format!("HTTP {}", code)),
67        ureq::Error::Transport(t) => ProviderError::Http(t.to_string()),
68    }
69}