use std::net::{Ipv4Addr, Ipv6Addr}; use anyhow::{anyhow, Result}; use async_trait::async_trait; use reqwest::header; use serde::Serialize; use tracing::*; use crate::provider::DnsProvider; use crate::Opt; pub struct GandiProvider { client: reqwest::Client, } impl GandiProvider { pub fn new(opts: &Opt) -> Result { let api_key = opts .gandi_api_key .clone() .ok_or_else(|| anyhow!("Must specify D53_GANDI_API_KEY"))?; let mut headers = header::HeaderMap::new(); let mut auth_value = header::HeaderValue::from_str(&format!("Apikey {}", api_key))?; auth_value.set_sensitive(true); headers.insert(header::AUTHORIZATION, auth_value); let client = reqwest::Client::builder() .default_headers(headers) .use_rustls_tls() .build()?; Ok(Self { client }) } async fn put_rrset(&self, url: &str, rrset: &GandiRrset) -> Result<()> { debug!(url = url, body = format!("{:?}", rrset), "PUT"); let http = self.client.put(url).json(rrset).send().await?; if !http.status().is_success() { warn!( url = url, http_status = http.status().to_string(), "PUT returned error" ); } http.error_for_status()?; Ok(()) } } #[async_trait] impl DnsProvider for GandiProvider { fn provider(&self) -> &'static str { "gandi" } async fn update_a( &self, domain: &str, subdomain: Option<&str>, targets: &[Ipv4Addr], ) -> Result<()> { let url = format!( "https://api.gandi.net/v5/livedns/domains/{}/records/{}/A", domain, subdomain.unwrap_or("@") ); let rrset = GandiRrset { rrset_values: targets.iter().map(ToString::to_string).collect::>(), rrset_ttl: 300, }; self.put_rrset(&url, &rrset).await } async fn update_aaaa( &self, domain: &str, subdomain: Option<&str>, targets: &[Ipv6Addr], ) -> Result<()> { let url = format!( "https://api.gandi.net/v5/livedns/domains/{}/records/{}/AAAA", domain, subdomain.unwrap_or("@") ); let rrset = GandiRrset { rrset_values: targets.iter().map(ToString::to_string).collect::>(), rrset_ttl: 300, }; self.put_rrset(&url, &rrset).await } async fn update_cname( &self, domain: &str, subdomain: Option<&str>, target: &str, ) -> Result<()> { let url = format!( "https://api.gandi.net/v5/livedns/domains/{}/records/{}/CNAME", domain, subdomain.unwrap_or("@") ); let rrset = GandiRrset { rrset_values: vec![target.to_string()], rrset_ttl: 300, }; self.put_rrset(&url, &rrset).await } } #[derive(Serialize, Debug)] struct GandiRrset { rrset_values: Vec, rrset_ttl: u32, }