logs and metrics permissions, tolerant record decode, hive capabilities
This commit is contained in:
parent
b2694b0a92
commit
04598542b6
3 changed files with 226 additions and 8 deletions
|
|
@ -69,7 +69,13 @@ pub const IMPORT_REPLY_DEADLINE: std::time::Duration = std::time::Duration::from
|
|||
/// older hive the fields it knows.
|
||||
#[derive(Debug, Clone, Default, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
|
||||
pub struct AgentPermissions {
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
/// Names this build does not know are skipped on decode, so a permission
|
||||
/// a newer controller adds does not cost this reader the record.
|
||||
#[serde(
|
||||
default,
|
||||
skip_serializing_if = "Option::is_none",
|
||||
deserialize_with = "hive_types::deserialize_known"
|
||||
)]
|
||||
pub permissions: Option<Vec<hive_types::Permission>>,
|
||||
/// Whether the agent's traffic leaves the host unfiltered. Absent is "on":
|
||||
/// an agent whose key predates the field keeps the open access it had, and
|
||||
|
|
@ -155,6 +161,23 @@ mod tests {
|
|||
assert_eq!(ruth.permissions, Some(vec![Permission::ManageRootAgent]));
|
||||
}
|
||||
|
||||
/// A record naming a permission this build does not know still decodes,
|
||||
/// with that name skipped and the other fields intact: the reader holds
|
||||
/// what it knows instead of nothing.
|
||||
#[test]
|
||||
fn a_record_with_an_unknown_permission_still_decodes() {
|
||||
use hive_types::Permission;
|
||||
let record: AgentPermissions = serde_json::from_str(
|
||||
r#"{"permissions":["messaging","from-the-future","logs"],"unrestricted_internet":false}"#,
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(
|
||||
record.permissions,
|
||||
Some(vec![Permission::Messaging, Permission::Logs])
|
||||
);
|
||||
assert_eq!(record.unrestricted_internet, Some(false));
|
||||
}
|
||||
|
||||
/// The flag is left out when unset, so a record that never named it
|
||||
/// encodes as before, and an explicit `false` is kept on the wire.
|
||||
#[test]
|
||||
|
|
|
|||
73
swarm-wire/src/hive_status.rs
Normal file
73
swarm-wire/src/hive_status.rs
Normal file
|
|
@ -0,0 +1,73 @@
|
|||
//! What a hive's status snapshot (`swarm_queue_client::status`) says about
|
||||
//! what the hive can do, next to its readiness.
|
||||
//!
|
||||
//! The snapshot is opaque to the controller except for this field. A snapshot
|
||||
//! from a hive that predates it has none, which reads as an empty list.
|
||||
|
||||
/// The hive decodes a permission list that names a permission it does not
|
||||
/// know by skipping that name, and runs agents on a harness that does too.
|
||||
/// swarm-controller writes `logs` or `metrics` into an agent's record only
|
||||
/// when the agent's hive advertises this.
|
||||
pub const TOLERANT_PERMISSIONS: &str = "tolerant-permissions";
|
||||
|
||||
/// 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"));
|
||||
}
|
||||
|
||||
/// 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));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -19,6 +19,7 @@ pub mod builder;
|
|||
pub mod claude_login;
|
||||
pub mod egress_allow;
|
||||
pub mod forge_events;
|
||||
pub mod hive_status;
|
||||
pub mod notices;
|
||||
pub mod notify_flows;
|
||||
pub mod notify_router;
|
||||
|
|
|
|||
Loading…
Reference in a new issue