cloud_node_discovery/providers/
upcloud.rs

1#![cfg(feature = "upcloud")]
2use crate::Provider;
3use crate::{DiscoveryError, Node};
4use upcloud_sdk::client::Client;
5use upcloud_sdk::resources::server::ServerOperations;
6use upcloud_sdk::types::common::LabelFilter;
7
8use upcloud_sdk::error::Error as UpcloudError;
9
10use std::collections::HashMap;
11
12use async_trait::async_trait;
13
14pub struct UpcloudProvider {
15    client: Client,
16    zone: String,
17    label_key: String,
18    label_value: String,
19}
20
21impl UpcloudProvider {
22    pub async fn new(config: &HashMap<String, String>) -> Result<Self, DiscoveryError> {
23        // Extract required configuration
24        let zone = config
25            .get("zone")
26            .ok_or_else(|| DiscoveryError::ConfigError("zone is required".to_string()))?
27            .clone();
28        let label_key = config
29            .get("label_key")
30            .ok_or_else(|| DiscoveryError::ConfigError("label_key is required".to_string()))?
31            .clone();
32        let label_value = config
33            .get("label_value")
34            .ok_or_else(|| DiscoveryError::ConfigError("label_value is required".to_string()))?
35            .clone();
36
37        // Initialize AWS SDK client with region
38        let client = Client::new()?;
39
40        Ok(UpcloudProvider {
41            client,
42            zone,
43            label_key,
44            label_value,
45        })
46    }
47}
48
49#[async_trait]
50impl Provider for UpcloudProvider {
51    async fn discover(&self) -> Result<Vec<Node>, DiscoveryError> {
52        let label_filter = LabelFilter::new().with(&self.label_key, &self.label_value);
53        let servers_by_labels = self.client.list_servers_by_labels(&label_filter).await?;
54
55        // Transform instances into nodes
56        let mut nodes = Vec::new();
57
58        for server in servers_by_labels.server {
59            let server_details = self.client.get_server(&server.uuid).await?;
60            // Skip instances without a private IP
61            let mut private_ip: &str = "";
62            let mut utility_ip: &str = "";
63            let mut public_ip: &str = "";
64            for interface_iter in server_details.networking.iter() {
65                for interface in interface_iter.interfaces.interface.iter() {
66                    if interface.interface_type == "private" {
67                        private_ip = interface.ip_addresses.ip_address[0].address.as_ref().unwrap().as_str();
68                    }
69                    if interface.interface_type == "utility" {
70                        utility_ip = interface.ip_addresses.ip_address[0].address.as_ref().unwrap().as_str();
71                    }
72                    if interface.interface_type == "public" {
73                        public_ip = interface.ip_addresses.ip_address[0].address.as_ref().unwrap().as_str();
74                    }
75                }
76            }
77
78            // Collect instance tags into metadata
79            let mut metadata = HashMap::new();
80            for x in server.labels.iter() {
81                for label in x.label.iter() {
82                    metadata.insert(label.key.to_string(), label.value.to_string());
83                }
84            }
85
86            // Add instance ID and region to metadata
87            metadata.insert("uuid".to_string(), server.uuid.to_string());
88            metadata.insert("zone".to_string(), server.zone.clone());
89            metadata.insert("utility_ip".to_string(), utility_ip.to_string());
90            metadata.insert("public_ip".to_string(), public_ip.to_string());
91            metadata.insert("zone".to_string(), self.zone.clone());
92
93            nodes.push(Node {
94                address: private_ip.to_string(),
95                meta: metadata,
96            });
97        }
98
99        Ok(nodes)
100    }
101}
102
103impl From<UpcloudError> for DiscoveryError {
104    fn from(error: UpcloudError) -> Self {
105        DiscoveryError::ProviderError(error.to_string())
106    }
107}