Expand cfgsync rustdoc coverage further

This commit is contained in:
andrussal 2026-03-12 07:35:22 +01:00
parent b90734483b
commit fd154a9487
9 changed files with 58 additions and 2 deletions

View File

@ -8,6 +8,7 @@ pub struct RegistrationSnapshot {
} }
impl RegistrationSnapshot { impl RegistrationSnapshot {
/// Creates a stable registration snapshot sorted by node identifier.
#[must_use] #[must_use]
pub fn new(mut registrations: Vec<NodeRegistration>) -> Self { pub fn new(mut registrations: Vec<NodeRegistration>) -> Self {
registrations.sort_by(|left, right| left.identifier.cmp(&right.identifier)); registrations.sort_by(|left, right| left.identifier.cmp(&right.identifier));
@ -15,21 +16,25 @@ impl RegistrationSnapshot {
Self { registrations } Self { registrations }
} }
/// Returns the number of registrations in the snapshot.
#[must_use] #[must_use]
pub fn len(&self) -> usize { pub fn len(&self) -> usize {
self.registrations.len() self.registrations.len()
} }
/// Returns `true` when the snapshot contains no registrations.
#[must_use] #[must_use]
pub fn is_empty(&self) -> bool { pub fn is_empty(&self) -> bool {
self.registrations.is_empty() self.registrations.is_empty()
} }
/// Iterates registrations in deterministic identifier order.
#[must_use] #[must_use]
pub fn iter(&self) -> impl Iterator<Item = &NodeRegistration> { pub fn iter(&self) -> impl Iterator<Item = &NodeRegistration> {
self.registrations.iter() self.registrations.iter()
} }
/// Looks up a registration by its stable node identifier.
#[must_use] #[must_use]
pub fn get(&self, identifier: &str) -> Option<&NodeRegistration> { pub fn get(&self, identifier: &str) -> Option<&NodeRegistration> {
self.registrations self.registrations

View File

@ -2,15 +2,21 @@ use serde::{Deserialize, Serialize};
use crate::NodeArtifactFile; use crate::NodeArtifactFile;
/// Top-level cfgsync bundle containing per-node file payloads. /// Static cfgsync artifact bundle.
///
/// This is the bundle-oriented source format used when all artifacts are known
/// ahead of time and no registration-time materialization is required.
#[derive(Debug, Clone, Serialize, Deserialize)] #[derive(Debug, Clone, Serialize, Deserialize)]
pub struct NodeArtifactsBundle { pub struct NodeArtifactsBundle {
/// Per-node artifact entries keyed by identifier.
pub nodes: Vec<NodeArtifactsBundleEntry>, pub nodes: Vec<NodeArtifactsBundleEntry>,
/// Files that should be served alongside every node-specific entry.
#[serde(default)] #[serde(default)]
pub shared_files: Vec<NodeArtifactFile>, pub shared_files: Vec<NodeArtifactFile>,
} }
impl NodeArtifactsBundle { impl NodeArtifactsBundle {
/// Creates a bundle with node-specific entries only.
#[must_use] #[must_use]
pub fn new(nodes: Vec<NodeArtifactsBundleEntry>) -> Self { pub fn new(nodes: Vec<NodeArtifactsBundleEntry>) -> Self {
Self { Self {
@ -19,6 +25,7 @@ impl NodeArtifactsBundle {
} }
} }
/// Attaches files that should be served alongside every node entry.
#[must_use] #[must_use]
pub fn with_shared_files(mut self, shared_files: Vec<NodeArtifactFile>) -> Self { pub fn with_shared_files(mut self, shared_files: Vec<NodeArtifactFile>) -> Self {
self.shared_files = shared_files; self.shared_files = shared_files;
@ -26,7 +33,7 @@ impl NodeArtifactsBundle {
} }
} }
/// Artifact set for a single node resolved by identifier. /// One node entry inside a static cfgsync bundle.
#[derive(Debug, Clone, Serialize, Deserialize)] #[derive(Debug, Clone, Serialize, Deserialize)]
pub struct NodeArtifactsBundleEntry { pub struct NodeArtifactsBundleEntry {
/// Stable node identifier used by cfgsync lookup. /// Stable node identifier used by cfgsync lookup.

View File

@ -18,10 +18,14 @@ pub enum ClientError {
Decode(serde_json::Error), Decode(serde_json::Error),
} }
/// Result of probing cfgsync for a node's current artifact availability.
#[derive(Debug, Clone, Copy, PartialEq, Eq)] #[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ConfigFetchStatus { pub enum ConfigFetchStatus {
/// The node payload is ready and can be fetched successfully.
Ready, Ready,
/// The node has registered but artifacts are not ready yet.
NotReady, NotReady,
/// The server does not know how to materialize artifacts for this node.
Missing, Missing,
} }
@ -33,6 +37,7 @@ pub struct CfgsyncClient {
} }
impl CfgsyncClient { impl CfgsyncClient {
/// Creates a cfgsync client pointed at the given server base URL.
#[must_use] #[must_use]
pub fn new(base_url: impl Into<String>) -> Self { pub fn new(base_url: impl Into<String>) -> Self {
let mut base_url = base_url.into(); let mut base_url = base_url.into();
@ -45,6 +50,7 @@ impl CfgsyncClient {
} }
} }
/// Returns the normalized cfgsync server base URL used for requests.
#[must_use] #[must_use]
pub fn base_url(&self) -> &str { pub fn base_url(&self) -> &str {
&self.base_url &self.base_url
@ -63,6 +69,8 @@ impl CfgsyncClient {
self.post_json("/node", payload).await self.post_json("/node", payload).await
} }
/// Probes whether artifacts for a node are ready, missing, or still
/// pending.
pub async fn fetch_node_config_status( pub async fn fetch_node_config_status(
&self, &self,
payload: &NodeRegistration, payload: &NodeRegistration,

View File

@ -110,8 +110,11 @@ impl<'de> Deserialize<'de> for RegistrationPayload {
/// Node metadata recorded before config materialization. /// Node metadata recorded before config materialization.
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct NodeRegistration { pub struct NodeRegistration {
/// Stable node identifier used for registration and artifact lookup.
pub identifier: String, pub identifier: String,
/// IPv4 address advertised as part of registration.
pub ip: Ipv4Addr, pub ip: Ipv4Addr,
/// Adapter-owned payload interpreted only by the app materializer.
#[serde(default, skip_serializing_if = "RegistrationPayload::is_empty")] #[serde(default, skip_serializing_if = "RegistrationPayload::is_empty")]
pub metadata: RegistrationPayload, pub metadata: RegistrationPayload,
} }
@ -170,8 +173,11 @@ impl NodeArtifactsPayload {
#[derive(Debug, Clone, Serialize, Deserialize)] #[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")] #[serde(rename_all = "snake_case")]
pub enum CfgsyncErrorCode { pub enum CfgsyncErrorCode {
/// No artifact payload is available for the requested node.
MissingConfig, MissingConfig,
/// The node is registered but artifacts are not ready yet.
NotReady, NotReady,
/// An unexpected server-side failure occurred.
Internal, Internal,
} }
@ -179,7 +185,9 @@ pub enum CfgsyncErrorCode {
#[derive(Debug, Clone, Serialize, Deserialize, Error)] #[derive(Debug, Clone, Serialize, Deserialize, Error)]
#[error("{code:?}: {message}")] #[error("{code:?}: {message}")]
pub struct CfgsyncErrorResponse { pub struct CfgsyncErrorResponse {
/// Machine-readable failure category.
pub code: CfgsyncErrorCode, pub code: CfgsyncErrorCode,
/// Human-readable error details.
pub message: String, pub message: String,
} }
@ -214,13 +222,17 @@ impl CfgsyncErrorResponse {
/// Resolution outcome for a requested node identifier. /// Resolution outcome for a requested node identifier.
pub enum ConfigResolveResponse { pub enum ConfigResolveResponse {
/// Artifacts are ready for the requested node.
Config(NodeArtifactsPayload), Config(NodeArtifactsPayload),
/// Artifacts could not be resolved for the requested node.
Error(CfgsyncErrorResponse), Error(CfgsyncErrorResponse),
} }
/// Outcome for a node registration request. /// Outcome for a node registration request.
pub enum RegisterNodeResponse { pub enum RegisterNodeResponse {
/// Registration was accepted.
Registered, Registered,
/// Registration failed.
Error(CfgsyncErrorResponse), Error(CfgsyncErrorResponse),
} }

View File

@ -16,7 +16,9 @@ pub struct RenderedCfgsync {
/// Output paths used when materializing rendered cfgsync files. /// Output paths used when materializing rendered cfgsync files.
#[derive(Debug, Clone, Copy)] #[derive(Debug, Clone, Copy)]
pub struct CfgsyncOutputPaths<'a> { pub struct CfgsyncOutputPaths<'a> {
/// Output path for the rendered server config YAML.
pub config_path: &'a Path, pub config_path: &'a Path,
/// Output path for the rendered static bundle YAML.
pub bundle_path: &'a Path, pub bundle_path: &'a Path,
} }
@ -55,10 +57,15 @@ pub fn write_rendered_cfgsync(
/// Optional overrides applied to a cfgsync template document. /// Optional overrides applied to a cfgsync template document.
#[derive(Debug, Clone, Default)] #[derive(Debug, Clone, Default)]
pub struct CfgsyncConfigOverrides { pub struct CfgsyncConfigOverrides {
/// Override for the HTTP listen port.
pub port: Option<u16>, pub port: Option<u16>,
/// Override for the expected initial host count.
pub n_hosts: Option<usize>, pub n_hosts: Option<usize>,
/// Minimum timeout to enforce on the rendered template.
pub timeout_floor_secs: Option<u64>, pub timeout_floor_secs: Option<u64>,
/// Override for the bundle path written into cfgsync config.
pub bundle_path: Option<String>, pub bundle_path: Option<String>,
/// Optional OTLP metrics endpoint injected into tracing settings.
pub metrics_otlp_ingest_url: Option<String>, pub metrics_otlp_ingest_url: Option<String>,
} }

View File

@ -14,6 +14,7 @@ pub struct CfgsyncServerState {
} }
impl CfgsyncServerState { impl CfgsyncServerState {
/// Wraps a node config source for use by cfgsync HTTP handlers.
#[must_use] #[must_use]
pub fn new(repo: Arc<dyn NodeConfigSource>) -> Self { pub fn new(repo: Arc<dyn NodeConfigSource>) -> Self {
Self { repo } Self { repo }
@ -83,6 +84,8 @@ fn error_status(code: &CfgsyncErrorCode) -> StatusCode {
} }
} }
/// Builds the primary cfgsync router with registration and node artifact
/// routes.
pub fn build_cfgsync_router(state: CfgsyncServerState) -> Router { pub fn build_cfgsync_router(state: CfgsyncServerState) -> Router {
Router::new() Router::new()
.route("/register", post(register_node)) .route("/register", post(register_node))
@ -91,6 +94,7 @@ pub fn build_cfgsync_router(state: CfgsyncServerState) -> Router {
} }
#[doc(hidden)] #[doc(hidden)]
/// Builds the legacy cfgsync router that still serves `/init-with-node`.
pub fn build_legacy_cfgsync_router(state: CfgsyncServerState) -> Router { pub fn build_legacy_cfgsync_router(state: CfgsyncServerState) -> Router {
Router::new() Router::new()
.route("/register", post(register_node)) .route("/register", post(register_node))

View File

@ -9,8 +9,10 @@ use crate::{
/// Source of cfgsync node payloads. /// Source of cfgsync node payloads.
pub trait NodeConfigSource: Send + Sync { pub trait NodeConfigSource: Send + Sync {
/// Records a node registration before config resolution.
fn register(&self, registration: NodeRegistration) -> RegisterNodeResponse; fn register(&self, registration: NodeRegistration) -> RegisterNodeResponse;
/// Resolves the current artifact payload for a previously registered node.
fn resolve(&self, registration: &NodeRegistration) -> ConfigResolveResponse; fn resolve(&self, registration: &NodeRegistration) -> ConfigResolveResponse;
} }
@ -20,11 +22,13 @@ pub struct StaticConfigSource {
} }
impl StaticConfigSource { impl StaticConfigSource {
/// Builds an in-memory source from fully formed payloads.
#[must_use] #[must_use]
pub fn from_payloads(configs: HashMap<String, NodeArtifactsPayload>) -> Arc<Self> { pub fn from_payloads(configs: HashMap<String, NodeArtifactsPayload>) -> Arc<Self> {
Arc::new(Self { configs }) Arc::new(Self { configs })
} }
/// Builds an in-memory source from a static bundle document.
#[must_use] #[must_use]
pub fn from_bundle(bundle: NodeArtifactsBundle) -> Arc<Self> { pub fn from_bundle(bundle: NodeArtifactsBundle) -> Arc<Self> {
Self::from_payloads(bundle_to_payload_map(bundle)) Self::from_payloads(bundle_to_payload_map(bundle))
@ -73,6 +77,7 @@ pub enum BundleLoadError {
}, },
} }
/// Converts a static bundle into the node payload map used by static sources.
#[must_use] #[must_use]
pub fn bundle_to_payload_map(bundle: NodeArtifactsBundle) -> HashMap<String, NodeArtifactsPayload> { pub fn bundle_to_payload_map(bundle: NodeArtifactsBundle) -> HashMap<String, NodeArtifactsPayload> {
let shared_files = bundle.shared_files; let shared_files = bundle.shared_files;
@ -91,6 +96,7 @@ pub fn bundle_to_payload_map(bundle: NodeArtifactsBundle) -> HashMap<String, Nod
.collect() .collect()
} }
/// Loads a cfgsync bundle YAML file from disk.
pub fn load_bundle(path: &Path) -> Result<NodeArtifactsBundle, BundleLoadError> { pub fn load_bundle(path: &Path) -> Result<NodeArtifactsBundle, BundleLoadError> {
let path_string = path.display().to_string(); let path_string = path.display().to_string();
let raw = fs::read_to_string(path).map_err(|source| BundleLoadError::ReadBundle { let raw = fs::read_to_string(path).map_err(|source| BundleLoadError::ReadBundle {

View File

@ -24,11 +24,13 @@ pub struct ArtifactOutputMap {
} }
impl ArtifactOutputMap { impl ArtifactOutputMap {
/// Creates an empty artifact output map.
#[must_use] #[must_use]
pub fn new() -> Self { pub fn new() -> Self {
Self::default() Self::default()
} }
/// Routes one artifact path from the payload to a local output path.
#[must_use] #[must_use]
pub fn route( pub fn route(
mut self, mut self,

View File

@ -16,6 +16,7 @@ use thiserror::Error;
/// Runtime cfgsync server config loaded from YAML. /// Runtime cfgsync server config loaded from YAML.
#[derive(Debug, Clone, PartialEq, Eq)] #[derive(Debug, Clone, PartialEq, Eq)]
pub struct CfgsyncServerConfig { pub struct CfgsyncServerConfig {
/// HTTP port to bind the cfgsync server on.
pub port: u16, pub port: u16,
/// Source used by the runtime-managed cfgsync server. /// Source used by the runtime-managed cfgsync server.
pub source: CfgsyncServerSource, pub source: CfgsyncServerSource,
@ -31,7 +32,9 @@ pub struct CfgsyncServerConfig {
#[derive(Debug, Clone, Deserialize, PartialEq, Eq)] #[derive(Debug, Clone, Deserialize, PartialEq, Eq)]
#[serde(tag = "kind", rename_all = "snake_case")] #[serde(tag = "kind", rename_all = "snake_case")]
pub enum CfgsyncServerSource { pub enum CfgsyncServerSource {
/// Serve a static precomputed artifact bundle directly.
Bundle { bundle_path: String }, Bundle { bundle_path: String },
/// Require node registration before serving artifacts from a static bundle.
RegistrationBundle { bundle_path: String }, RegistrationBundle { bundle_path: String },
} }
@ -100,6 +103,8 @@ impl CfgsyncServerConfig {
} }
} }
/// Builds a config that serves a static bundle behind the registration
/// flow.
#[must_use] #[must_use]
pub fn for_registration_bundle(port: u16, bundle_path: impl Into<String>) -> Self { pub fn for_registration_bundle(port: u16, bundle_path: impl Into<String>) -> Self {
Self { Self {