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

View File

@ -2,15 +2,21 @@ use serde::{Deserialize, Serialize};
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)]
pub struct NodeArtifactsBundle {
/// Per-node artifact entries keyed by identifier.
pub nodes: Vec<NodeArtifactsBundleEntry>,
/// Files that should be served alongside every node-specific entry.
#[serde(default)]
pub shared_files: Vec<NodeArtifactFile>,
}
impl NodeArtifactsBundle {
/// Creates a bundle with node-specific entries only.
#[must_use]
pub fn new(nodes: Vec<NodeArtifactsBundleEntry>) -> Self {
Self {
@ -19,6 +25,7 @@ impl NodeArtifactsBundle {
}
}
/// Attaches files that should be served alongside every node entry.
#[must_use]
pub fn with_shared_files(mut self, shared_files: Vec<NodeArtifactFile>) -> Self {
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)]
pub struct NodeArtifactsBundleEntry {
/// Stable node identifier used by cfgsync lookup.

View File

@ -18,10 +18,14 @@ pub enum ClientError {
Decode(serde_json::Error),
}
/// Result of probing cfgsync for a node's current artifact availability.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ConfigFetchStatus {
/// The node payload is ready and can be fetched successfully.
Ready,
/// The node has registered but artifacts are not ready yet.
NotReady,
/// The server does not know how to materialize artifacts for this node.
Missing,
}
@ -33,6 +37,7 @@ pub struct CfgsyncClient {
}
impl CfgsyncClient {
/// Creates a cfgsync client pointed at the given server base URL.
#[must_use]
pub fn new(base_url: impl Into<String>) -> Self {
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]
pub fn base_url(&self) -> &str {
&self.base_url
@ -63,6 +69,8 @@ impl CfgsyncClient {
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(
&self,
payload: &NodeRegistration,

View File

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

View File

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

View File

@ -14,6 +14,7 @@ pub struct CfgsyncServerState {
}
impl CfgsyncServerState {
/// Wraps a node config source for use by cfgsync HTTP handlers.
#[must_use]
pub fn new(repo: Arc<dyn NodeConfigSource>) -> Self {
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 {
Router::new()
.route("/register", post(register_node))
@ -91,6 +94,7 @@ pub fn build_cfgsync_router(state: CfgsyncServerState) -> Router {
}
#[doc(hidden)]
/// Builds the legacy cfgsync router that still serves `/init-with-node`.
pub fn build_legacy_cfgsync_router(state: CfgsyncServerState) -> Router {
Router::new()
.route("/register", post(register_node))

View File

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

View File

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

View File

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