char/dns-ez

cloudflare dns management via plaintext zonefiles

git clone https://git.t4t.associates/char/dns-ez

Charlotte Somadd support for wix provider :(c6d896d

main
2.7 KiB84 linesraw
1use std::fmt;
2
3use anyhow::{Context, Result};
4use clap::ValueEnum;
5
6use crate::dns::{ForeignRecord, Name, Record};
7use crate::reconcile::Action;
8use crate::{cloudflare, wix};
9
10/// Where a zone is hosted.
11#[derive(Clone, Copy, Debug, PartialEq, Eq, ValueEnum)]
12pub enum Kind {
13    Cloudflare,
14    Wix,
15}
16
17impl Kind {
18    pub fn connect(self) -> Result<Box<dyn Provider>> {
19        let var = |name: &str, hint: &str| {
20            std::env::var(name).with_context(|| format!("set {name} to {hint}"))
21        };
22        Ok(match self {
23            Kind::Cloudflare => Box::new(cloudflare::Client::new(
24                var("CF_API_TOKEN", "a Cloudflare API token")
25                    .or_else(|e| std::env::var("CLOUDFLARE_API_TOKEN").map_err(|_| e))?,
26            )),
27            Kind::Wix => Box::new(wix::Client::new(
28                var("WIX_API_TOKEN", "a Wix account-level API token")?,
29                var(
30                    "WIX_ACCOUNT_ID",
31                    "the Wix account ID shown beside your API keys",
32                )?,
33            )),
34        })
35    }
36}
37
38impl fmt::Display for Kind {
39    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
40        f.write_str(
41            self.to_possible_value()
42                .expect("no skipped variants")
43                .get_name(),
44        )
45    }
46}
47
48/// A DNS host. Providers address records differently (Cloudflare by ID, Wix
49/// by whole RRset), so `apply` takes plain record changes and resolves them
50/// against the zone as it stands at apply time.
51pub trait Provider {
52    fn list_zones(&self) -> Result<Vec<String>>;
53    fn records(&self, zone: &Name) -> Result<Vec<Listed>>;
54    fn apply(&self, zone: &Name, actions: &[Action]) -> Result<()>;
55}
56
57/// A record at the provider: managed records are reconciled, everything else
58/// is kept only for display.
59pub enum Listed {
60    Managed(Record),
61    Foreign(ForeignRecord),
62}
63
64/// Split the provider's records into the ones we manage and the ones we
65/// leave alone. Apex NS records belong to the provider's nameservers and
66/// are untouchable, so they count as foreign too.
67pub fn split_listed(listed: Vec<Listed>, apex: &Name) -> (Vec<Record>, Vec<ForeignRecord>) {
68    let (mut managed, mut foreign) = (Vec::new(), Vec::new());
69    for l in listed {
70        match l {
71            Listed::Managed(r) if r.is_apex_ns(apex) => {
72                foreign.push(ForeignRecord {
73                    name: r.name.to_string(),
74                    rtype: r.rtype().to_string(),
75                    ttl: r.ttl,
76                    content: r.data.to_string(),
77                });
78            }
79            Listed::Managed(r) => managed.push(r),
80            Listed::Foreign(f) => foreign.push(f),
81        }
82    }
83    (managed, foreign)
84}