//! iroh-docs key conventions.
//!
//! All entries for a host live under the prefix `host/<host_id>/`:
//!
//! - `host/<id>/manifest` — controller-written [`crate::Manifest`] (JSON)
//! - `host/<id>/status` — agent-written [`crate::Status`] (JSON)
//! - `host/<id>/files/<path>` — controller-written raw file body, one entry
//! per [`crate::FileResource`], so bodies replicate alongside the manifest.
//!
//! Keys are opaque byte strings to iroh-docs; we use UTF-8. The agent never
//! parses file keys back into paths — it derives each key from the manifest's
//! file list — so embedding the destination path verbatim (slashes and all) is
//! fine and keeps entries human-inspectable.
use crate::HostId;
/// Top-level prefix under which every host's entries live.
pub const HOST_PREFIX: &str = "host/";
/// Key for a host's manifest entry.
pub fn manifest(host: &HostId) -> String {
format!("host/{host}/manifest")
}
/// Key for a host's status entry.
pub fn status(host: &HostId) -> String {
format!("host/{host}/status")
}
/// Key for a host's live service-state report ([`crate::ServiceReport`]),
/// written by the agent whenever a monitored service's runtime state changes.
pub fn service_report(host: &HostId) -> String {
format!("host/{host}/service-states")
}
/// Key for a host's periodic system metrics ([`crate::HostMetrics`]) —
/// uptime and load average.
pub fn metrics(host: &HostId) -> String {
format!("host/{host}/metrics")
}
/// Prefix matching every entry belonging to a host (manifest, status, files).
pub fn host_prefix(host: &HostId) -> String {
format!("host/{host}/")
}
/// Prefix matching every file body entry for a host.
pub fn files_prefix(host: &HostId) -> String {
format!("host/{host}/files/")
}
/// Key for a single file body entry, given its absolute destination path.
pub fn file(host: &HostId, dest_path: &str) -> String {
// Destination paths are absolute (leading `/`); strip it so the key reads
// `host/<id>/files/etc/...` rather than `host/<id>/files//etc/...`.
let rel = dest_path.strip_prefix('/').unwrap_or(dest_path);
format!("host/{host}/files/{rel}")
}
// ---- adoption keyspace -----------------------------------------------------
//
// Adoption is an *inbound proposal* from an agent: it never touches the
// controller-owned `manifest`/`files` entries. Instead the agent writes its
// discovered files under `host/<id>/adopt/`, which the operator reviews and
// accepts (turning them into normal managed config). Keeping it in a separate
// prefix means a proposal can never be mistaken for desired state.
/// Suffix identifying an adoption report entry; see [`host_from_adopt_key`].
pub const ADOPT_MANIFEST_SUFFIX: &str = "/adopt/manifest";
/// Key for a host's adoption report ([`crate::AdoptionReport`]).
pub fn adopt_manifest(host: &HostId) -> String {
format!("host/{host}/adopt/manifest")
}
/// Prefix matching every adoption entry (report + proposed file bodies) for a
/// host — used to clear a proposal once accepted or dismissed.
pub fn adopt_prefix(host: &HostId) -> String {
format!("host/{host}/adopt/")
}
/// Prefix matching a host's proposed file bodies.
pub fn adopt_files_prefix(host: &HostId) -> String {
format!("host/{host}/adopt/files/")
}
/// Key for a single proposed file body, given its absolute source path on the
/// agent's machine.
pub fn adopt_file(host: &HostId, src_path: &str) -> String {
let rel = src_path.strip_prefix('/').unwrap_or(src_path);
format!("host/{host}/adopt/files/{rel}")
}
/// If `key` is an adoption-report key (`host/<id>/adopt/manifest`), return the
/// host id. Used to enumerate hosts with pending proposals.
pub fn host_from_adopt_key(key: &str) -> Option<&str> {
key.strip_prefix("host/")?
.strip_suffix(ADOPT_MANIFEST_SUFFIX)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn file_key_is_under_files_prefix() {
let host = "web-01".to_string();
let k = file(&host, "/etc/containers/systemd/web.container");
assert!(k.starts_with(&files_prefix(&host)));
assert!(k.starts_with(&host_prefix(&host)));
assert_eq!(k, "host/web-01/files/etc/containers/systemd/web.container");
}
#[test]
fn manifest_and_status_share_host_prefix() {
let host = "db".to_string();
assert!(manifest(&host).starts_with(&host_prefix(&host)));
assert!(status(&host).starts_with(&host_prefix(&host)));
}
#[test]
fn adopt_keys_are_isolated_from_managed_state() {
let host = "web-01".to_string();
// A proposed file must never collide with the managed files prefix.
assert!(!adopt_file(&host, "/etc/x").starts_with(&files_prefix(&host)));
assert!(adopt_file(&host, "/etc/x").starts_with(&adopt_files_prefix(&host)));
assert!(adopt_manifest(&host).starts_with(&adopt_prefix(&host)));
}
#[test]
fn host_from_adopt_key_roundtrips() {
let host = "web-01".to_string();
assert_eq!(host_from_adopt_key(&adopt_manifest(&host)), Some("web-01"));
assert_eq!(host_from_adopt_key(&manifest(&host)), None);
assert_eq!(host_from_adopt_key("garbage"), None);
}
}
neiam
/devendra
public · Issues · Pulls · Labels · Forks · Compare · Actions failure · Badges · Packages
⭐
Log in to mark this repository.
5.1 KiB · text
History
d69022d