Watch
0
0
Fork
You've already forked hyperhive
0
hyperhive/swarm-wire/src/hive_status.rs

110 lines
4.3 KiB
Rust

//! 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<u64> {
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<String>,
}
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));
}
}
}