//! What a hive or an agent's harness says it can do, in the same `capabilities` //! list of two documents: a hive's status snapshot //! (`swarm_queue_client::status`, next to its readiness) and an agent's //! turn-state header (`swarm_queue_client::agent_state_subject`). //! //! Readers take nothing else from the snapshot or header here. A document that //! predates the field has none, which reads as an empty list. /// The publisher decodes a permission list that names a permission it does not /// know by skipping that name. swarm-controller writes `logs` or `metrics` into /// an agent's record only when both its hive and its running harness advertise /// this. pub const TOLERANT_PERMISSIONS: &str = "tolerant-permissions"; /// A hive lists this when it builds a custom agent from `internal/meta` alone /// and keeps no checkout of its config: no `applied` or `proposed` repo and no /// config mount in the container. swarm-controller turns an archetype agent /// into a custom one only on a hive that advertises it. pub const NO_CONFIG_CHECKOUT: &str = "no-config-checkout"; /// The snapshot field holding the revision of the egress deny list /// (`crate::egress_allow::DENY_KEY`) the hive last rendered into its squid /// include, `0` when the swarm has no deny list. A hive that predates the /// field publishes none. pub const EGRESS_DENY_APPLIED: &str = "egress_deny_applied"; /// The deny-list revision a stored snapshot says its hive rendered: `None` for /// a snapshot without the field (an older hive, or one that has not read the /// list yet) or with one that is not a number. #[must_use] pub fn egress_deny_applied(snapshot: &serde_json::Value) -> Option { snapshot.get(EGRESS_DENY_APPLIED)?.as_u64() } /// The part of a hive's status snapshot this module describes. #[derive(Debug, Clone, Default, PartialEq, Eq, serde::Serialize, serde::Deserialize)] pub struct Capabilities { /// Capability names; unknown names are ignored by every reader. #[serde(default)] pub capabilities: Vec, } impl Capabilities { /// Whether the hive advertises `name`. #[must_use] pub fn has(&self, name: &str) -> bool { self.capabilities.iter().any(|c| c == name) } /// The capabilities in a stored snapshot; none when it has no such field /// or is not an object. #[must_use] pub fn of_snapshot(snapshot: &serde_json::Value) -> Self { serde_json::from_value(snapshot.clone()).unwrap_or_default() } } #[cfg(test)] mod tests { use super::*; /// A snapshot from before the field existed: it parses, with nothing /// advertised. #[test] fn a_snapshot_without_the_field_advertises_nothing() { let old = serde_json::json!({"status": "ok", "warnings": []}); let caps = Capabilities::of_snapshot(&old); assert!(!caps.has(TOLERANT_PERMISSIONS)); assert_eq!(caps, Capabilities::default()); } #[test] fn an_advertised_capability_is_found_among_unknown_ones() { let new = serde_json::json!({ "status": "ok", "warnings": [], "capabilities": ["from-the-future", TOLERANT_PERMISSIONS], }); assert!(Capabilities::of_snapshot(&new).has(TOLERANT_PERMISSIONS)); assert!(!Capabilities::of_snapshot(&new).has("other")); } #[test] fn the_applied_deny_revision_is_read_only_when_it_is_a_number() { let at = |v: serde_json::Value| { egress_deny_applied(&serde_json::json!({ EGRESS_DENY_APPLIED: v })) }; assert_eq!(at(serde_json::json!(7)), Some(7)); assert_eq!(at(serde_json::json!(0)), Some(0)); assert_eq!(at(serde_json::json!("7")), None); assert_eq!( egress_deny_applied(&serde_json::json!({"status": "ok"})), None ); assert_eq!(egress_deny_applied(&serde_json::json!(null)), None); } /// A snapshot that is not an object, or whose field is mistyped, grants /// nothing rather than failing the read. #[test] fn a_malformed_snapshot_advertises_nothing() { for bad in [ serde_json::json!(null), serde_json::json!("ok"), serde_json::json!({"capabilities": "tolerant-permissions"}), ] { assert!(!Capabilities::of_snapshot(&bad).has(TOLERANT_PERMISSIONS)); } } }