110 lines
4.3 KiB
Rust
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));
|
|
}
|
|
}
|
|
}
|