Skip to main content

purple_ssh/providers/
mod.rs

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