Your browser sucks, JavaScript disabled! Gunnar's console needs JavaScript and WebAssembly. Git over SSH on port 2222 does not.

nordisk/monetize

crates/monetize-estate-side/src/pump/config.rs

//! **Where this box reports, and as whom** (moved from Holger's `holger-fleet`, P4a). The
//! installer's feed (the same keys every product's boxes read: `fleet`, `fleet_instance`, `fleet_at`, `fleet_ca`, `monetize_pubkey`, plain
//! or offered by a VM as `offer.<vm>.<key>`), else what an enrolment kept at
//! [`CONFIG_REL`].

use std::collections::BTreeMap;
use std::path::Path;
use std::time::Duration;

use serde::{Deserialize, Serialize};

/// What an enrolment keeps under the data root.
pub const CONFIG_REL: &str = "fleet/fleet.json";

pub const FEED_FLEET: &str = "fleet";
pub const FEED_FLEET_INSTANCE: &str = "fleet_instance";
pub const FEED_FLEET_AT: &str = "fleet_at";
pub const FEED_FLEET_CA: &str = "fleet_ca";
pub const FEED_MONETIZE: &str = "monetize_pubkey";

/// The longest pause between reports when monetize names none.
pub const DEFAULT_EVERY: Duration = Duration::from_secs(30);

#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
#[serde(default)]
pub struct FleetConfig {
    /// The ONE monetize's `Fleet` door (`https://monetize.ignalina.dk`).
    pub url: String,
    /// Where to connect (`host:port`) when the URL's host is not what this box dials.
    pub at: Option<String>,
    /// PEM the door's certificate is checked against; none = the public roots.
    pub ca_pem: Option<String>,
    /// The estate this box reports for.
    pub instance: String,
    /// `main` | `twin`; empty = what the box is.
    pub role: String,
    /// The key orders verify under (hex).
    pub monetize_pubkey: String,
}

impl FleetConfig {
    /// From a feed; `None` when it names no door or no estate.
    pub fn from_feed(feed: &BTreeMap<String, String>) -> Option<FleetConfig> {
        let get = |k: &str| {
            feed.get(k)
                .map(|v| v.trim().to_string())
                .filter(|v| !v.is_empty())
                .or_else(|| {
                    feed.iter()
                        .find(|(key, v)| {
                            key.starts_with("offer.")
                                && key.rsplit('.').next() == Some(k)
                                && !v.trim().is_empty()
                        })
                        .map(|(_, v)| v.trim().to_string())
                })
        };
        let url = get(FEED_FLEET)?;
        let instance = get(FEED_FLEET_INSTANCE)?;
        Some(FleetConfig {
            url,
            at: get(FEED_FLEET_AT),
            ca_pem: get(FEED_FLEET_CA),
            instance,
            role: String::new(),
            monetize_pubkey: get(FEED_MONETIZE).unwrap_or_default(),
        })
    }

    /// What an enrolment kept, if any.
    pub fn load(root: &Path) -> std::io::Result<Option<FleetConfig>> {
        match std::fs::read(root.join(CONFIG_REL)) {
            Ok(b) => serde_json::from_slice(&b)
                .map(Some)
                .map_err(|e| std::io::Error::new(std::io::ErrorKind::InvalidData, e)),
            Err(e) if e.kind() == std::io::ErrorKind::NotFound => Ok(None),
            Err(e) => Err(e),
        }
    }

    pub fn save(&self, root: &Path) -> std::io::Result<()> {
        let bytes = serde_json::to_vec_pretty(self).map_err(std::io::Error::other)?;
        crate::write_atomic(&root.join(CONFIG_REL), &bytes, true)
    }

    /// The key orders verify under, when it is one.
    pub fn key(&self) -> Option<[u8; 32]> {
        crate::key32(&self.monetize_pubkey)
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn the_feed_names_the_door_the_estate_and_the_key_plain_or_offered() {
        let k = "11".repeat(32);
        let feed: BTreeMap<String, String> = [
            ("offer.front.fleet", "https://monetize.ignalina.dk"),
            ("offer.front.fleet_instance", "acme"),
            ("monetize_pubkey", k.as_str()),
        ]
        .iter()
        .map(|(a, b)| (a.to_string(), b.to_string()))
        .collect();
        let c = FleetConfig::from_feed(&feed).unwrap();
        assert_eq!(
            (c.url.as_str(), c.instance.as_str()),
            ("https://monetize.ignalina.dk", "acme")
        );
        assert_eq!(c.key(), Some([0x11; 32]));
        assert_eq!(FleetConfig::from_feed(&BTreeMap::new()), None);
        let dir = tempfile::tempdir().unwrap();
        c.save(dir.path()).unwrap();
        assert_eq!(FleetConfig::load(dir.path()).unwrap(), Some(c));
    }
}