Skip to main content

dnslib/vendors/pangolin/
mod.rs

1pub mod client;
2pub mod mapping;
3pub mod responses;
4pub mod service;
5
6use std::env;
7
8use crate::control_plane::config::{self as app_config, DnsServerConfig};
9use crate::core::error::{Error, Result};
10use crate::core::secret::ApiToken;
11use crate::vendors::runtime::ClientOverrides;
12
13pub fn client_from_server(
14    server: &DnsServerConfig,
15    overrides: ClientOverrides<'_>,
16) -> Result<client::PangolinClient> {
17    let base_url = overrides
18        .base_url
19        .map(ToOwned::to_owned)
20        .or_else(|| env::var("DNSYNC_PANGOLIN_BASE_URL").ok())
21        .or_else(|| server.base_url_env.as_ref().and_then(|k| env::var(k).ok()))
22        .or_else(|| server.base_url.clone())
23        .unwrap_or_else(|| app_config::PANGOLIN_DEFAULT_BASE_URL.to_string());
24    let token = overrides
25        .token
26        .map(ToOwned::to_owned)
27        .or_else(|| env::var("DNSYNC_PANGOLIN_API_TOKEN").ok())
28        .or_else(|| server.token_env.as_ref().and_then(|k| env::var(k).ok()))
29        .or_else(|| server.token.clone())
30        .ok_or_else(|| {
31            Error::parse(
32                "Pangolin API token is required from --token, DNSYNC_PANGOLIN_API_TOKEN, token_env, or config token",
33            )
34        })
35        .map(ApiToken::new)?;
36    let org_id = env::var("DNSYNC_PANGOLIN_ORG_ID")
37        .ok()
38        .or_else(|| server.org_id.clone())
39        .ok_or_else(|| {
40            Error::parse("Pangolin org ID is required from DNSYNC_PANGOLIN_ORG_ID or config org_id")
41        })?;
42    client::PangolinClient::new(base_url, token, org_id)
43}
44
45#[cfg(test)]
46mod tests {
47    use super::*;
48
49    static ENV_LOCK: std::sync::Mutex<()> = std::sync::Mutex::new(());
50
51    fn clear_pangolin_env() {
52        // SAFETY: callers hold ENV_LOCK while mutating these process-wide env vars.
53        unsafe {
54            std::env::remove_var("DNSYNC_PANGOLIN_BASE_URL");
55            std::env::remove_var("DNSYNC_PANGOLIN_API_TOKEN");
56            std::env::remove_var("DNSYNC_PANGOLIN_ORG_ID");
57        }
58    }
59
60    #[test]
61    fn client_uses_default_base_url_from_config() {
62        let _guard = ENV_LOCK.lock().unwrap();
63        clear_pangolin_env();
64        let app_config: app_config::AppConfig = toml::from_str(
65            r#"
66                [[servers]]
67                id = "cloud"
68                vendor = "pangolin"
69                token = "pangolin-token"
70                org_id = "org_123"
71            "#,
72        )
73        .unwrap();
74        let server = app_config.selected_server(Some("cloud")).unwrap();
75
76        let client = client_from_server(server, ClientOverrides::default()).unwrap();
77
78        assert_eq!(client.base_url(), app_config::PANGOLIN_DEFAULT_BASE_URL);
79        assert_eq!(client.org_id, "org_123");
80    }
81
82    #[test]
83    fn client_from_server_uses_vendor_env_without_overrides() {
84        let _guard = ENV_LOCK.lock().unwrap();
85        clear_pangolin_env();
86        // SAFETY: this test serializes access to these process-wide env vars.
87        unsafe {
88            std::env::set_var("DNSYNC_PANGOLIN_BASE_URL", "https://pangolin.example/v1");
89            std::env::set_var("DNSYNC_PANGOLIN_API_TOKEN", "pangolin-env-token");
90            std::env::set_var("DNSYNC_PANGOLIN_ORG_ID", "env-org");
91        }
92        let app_config: app_config::AppConfig = toml::from_str(
93            r#"
94                [[servers]]
95                id = "cloud"
96                vendor = "pangolin"
97            "#,
98        )
99        .unwrap();
100        let server = app_config.selected_server(Some("cloud")).unwrap();
101
102        let client = client_from_server(server, ClientOverrides::default()).unwrap();
103
104        assert_eq!(client.base_url(), "https://pangolin.example/v1");
105        assert_eq!(client.org_id, "env-org");
106
107        // SAFETY: this test serializes access to these process-wide env vars.
108        clear_pangolin_env();
109    }
110}