2026-03-09 08:48:05 +01:00
|
|
|
use std::{fs, path::Path, sync::Arc};
|
|
|
|
|
|
|
|
|
|
use anyhow::Context as _;
|
2026-03-10 13:56:27 +01:00
|
|
|
use cfgsync_adapter::{
|
2026-03-10 14:24:00 +01:00
|
|
|
ArtifactSet, CachedSnapshotMaterializer, MaterializedArtifacts,
|
2026-03-10 13:56:27 +01:00
|
|
|
RegistrationSnapshotMaterializer, SnapshotConfigSource,
|
|
|
|
|
};
|
2026-03-10 11:03:51 +01:00
|
|
|
use cfgsync_core::{
|
2026-03-10 13:56:27 +01:00
|
|
|
BundleConfigSource, CfgsyncServerState, NodeArtifactsBundle, NodeConfigSource, RunCfgsyncError,
|
|
|
|
|
serve_cfgsync,
|
2026-03-10 11:03:51 +01:00
|
|
|
};
|
2026-03-09 08:48:05 +01:00
|
|
|
use serde::Deserialize;
|
2026-03-10 11:06:16 +01:00
|
|
|
use thiserror::Error;
|
2026-03-09 08:48:05 +01:00
|
|
|
|
|
|
|
|
/// Runtime cfgsync server config loaded from YAML.
|
|
|
|
|
#[derive(Debug, Deserialize, Clone)]
|
2026-03-10 11:06:16 +01:00
|
|
|
pub struct CfgsyncServerConfig {
|
2026-03-09 08:48:05 +01:00
|
|
|
pub port: u16,
|
|
|
|
|
pub bundle_path: String,
|
2026-03-10 09:18:29 +01:00
|
|
|
#[serde(default)]
|
2026-03-10 11:06:16 +01:00
|
|
|
pub serving_mode: CfgsyncServingMode,
|
2026-03-09 08:48:05 +01:00
|
|
|
}
|
|
|
|
|
|
2026-03-10 11:06:16 +01:00
|
|
|
#[derive(Debug, Clone, Copy, Deserialize, PartialEq, Eq, Default)]
|
|
|
|
|
#[serde(rename_all = "snake_case")]
|
|
|
|
|
pub enum CfgsyncServingMode {
|
|
|
|
|
#[default]
|
|
|
|
|
Bundle,
|
|
|
|
|
Registration,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[derive(Debug, Error)]
|
|
|
|
|
pub enum LoadCfgsyncServerConfigError {
|
|
|
|
|
#[error("failed to read cfgsync config file {path}: {source}")]
|
|
|
|
|
Read {
|
|
|
|
|
path: String,
|
|
|
|
|
#[source]
|
|
|
|
|
source: std::io::Error,
|
|
|
|
|
},
|
|
|
|
|
#[error("failed to parse cfgsync config file {path}: {source}")]
|
|
|
|
|
Parse {
|
|
|
|
|
path: String,
|
|
|
|
|
#[source]
|
|
|
|
|
source: serde_yaml::Error,
|
|
|
|
|
},
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl CfgsyncServerConfig {
|
2026-03-09 08:48:05 +01:00
|
|
|
/// Loads cfgsync runtime server config from a YAML file.
|
2026-03-10 11:06:16 +01:00
|
|
|
pub fn load_from_file(path: &Path) -> Result<Self, LoadCfgsyncServerConfigError> {
|
|
|
|
|
let config_path = path.display().to_string();
|
|
|
|
|
let config_content =
|
|
|
|
|
fs::read_to_string(path).map_err(|source| LoadCfgsyncServerConfigError::Read {
|
|
|
|
|
path: config_path.clone(),
|
|
|
|
|
source,
|
|
|
|
|
})?;
|
|
|
|
|
|
2026-03-10 12:30:53 +01:00
|
|
|
serde_yaml::from_str(&config_content).map_err(|source| {
|
|
|
|
|
LoadCfgsyncServerConfigError::Parse {
|
|
|
|
|
path: config_path,
|
|
|
|
|
source,
|
|
|
|
|
}
|
2026-03-10 11:06:16 +01:00
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[must_use]
|
|
|
|
|
pub fn for_bundle(port: u16, bundle_path: impl Into<String>) -> Self {
|
|
|
|
|
Self {
|
|
|
|
|
port,
|
|
|
|
|
bundle_path: bundle_path.into(),
|
|
|
|
|
serving_mode: CfgsyncServingMode::Bundle,
|
|
|
|
|
}
|
|
|
|
|
}
|
2026-03-09 08:48:05 +01:00
|
|
|
|
2026-03-10 11:06:16 +01:00
|
|
|
#[must_use]
|
|
|
|
|
pub fn for_registration(port: u16, bundle_path: impl Into<String>) -> Self {
|
|
|
|
|
Self {
|
|
|
|
|
port,
|
|
|
|
|
bundle_path: bundle_path.into(),
|
|
|
|
|
serving_mode: CfgsyncServingMode::Registration,
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-10 11:03:51 +01:00
|
|
|
fn load_bundle_provider(bundle_path: &Path) -> anyhow::Result<Arc<dyn NodeConfigSource>> {
|
|
|
|
|
let provider = BundleConfigSource::from_yaml_file(bundle_path)
|
2026-03-09 08:48:05 +01:00
|
|
|
.with_context(|| format!("loading cfgsync provider from {}", bundle_path.display()))?;
|
|
|
|
|
|
|
|
|
|
Ok(Arc::new(provider))
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-10 12:30:53 +01:00
|
|
|
fn load_registration_source(bundle_path: &Path) -> anyhow::Result<Arc<dyn NodeConfigSource>> {
|
2026-03-10 09:18:29 +01:00
|
|
|
let bundle = load_bundle_yaml(bundle_path)?;
|
2026-03-10 14:24:00 +01:00
|
|
|
let materialized = build_materialized_artifacts(bundle);
|
|
|
|
|
let provider = SnapshotConfigSource::new(materialized);
|
2026-03-10 09:18:29 +01:00
|
|
|
|
|
|
|
|
Ok(Arc::new(provider))
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-10 11:08:17 +01:00
|
|
|
fn load_bundle_yaml(bundle_path: &Path) -> anyhow::Result<NodeArtifactsBundle> {
|
2026-03-10 09:18:29 +01:00
|
|
|
let raw = fs::read_to_string(bundle_path)
|
|
|
|
|
.with_context(|| format!("reading cfgsync bundle from {}", bundle_path.display()))?;
|
|
|
|
|
|
|
|
|
|
serde_yaml::from_str(&raw)
|
|
|
|
|
.with_context(|| format!("parsing cfgsync bundle from {}", bundle_path.display()))
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-10 14:24:00 +01:00
|
|
|
fn build_materialized_artifacts(bundle: NodeArtifactsBundle) -> MaterializedArtifacts {
|
2026-03-10 09:18:29 +01:00
|
|
|
let nodes = bundle
|
|
|
|
|
.nodes
|
|
|
|
|
.into_iter()
|
2026-03-10 14:24:00 +01:00
|
|
|
.map(|node| cfgsync_adapter::NodeArtifacts {
|
2026-03-10 09:18:29 +01:00
|
|
|
identifier: node.identifier,
|
|
|
|
|
files: node.files,
|
|
|
|
|
})
|
|
|
|
|
.collect();
|
|
|
|
|
|
2026-03-10 14:24:00 +01:00
|
|
|
MaterializedArtifacts::new(
|
|
|
|
|
cfgsync_adapter::NodeArtifactsCatalog::new(nodes),
|
|
|
|
|
ArtifactSet::new(bundle.shared_files),
|
|
|
|
|
)
|
2026-03-10 09:18:29 +01:00
|
|
|
}
|
|
|
|
|
|
2026-03-09 08:48:05 +01:00
|
|
|
fn resolve_bundle_path(config_path: &Path, bundle_path: &str) -> std::path::PathBuf {
|
|
|
|
|
let path = Path::new(bundle_path);
|
|
|
|
|
if path.is_absolute() {
|
|
|
|
|
return path.to_path_buf();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
config_path
|
|
|
|
|
.parent()
|
|
|
|
|
.unwrap_or_else(|| Path::new("."))
|
|
|
|
|
.join(path)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Loads runtime config and starts cfgsync HTTP server process.
|
2026-03-10 12:30:53 +01:00
|
|
|
pub async fn serve_cfgsync_from_config(config_path: &Path) -> anyhow::Result<()> {
|
2026-03-10 11:06:16 +01:00
|
|
|
let config = CfgsyncServerConfig::load_from_file(config_path)?;
|
2026-03-09 08:48:05 +01:00
|
|
|
let bundle_path = resolve_bundle_path(config_path, &config.bundle_path);
|
|
|
|
|
|
2026-03-10 09:18:29 +01:00
|
|
|
let state = build_server_state(&config, &bundle_path)?;
|
2026-03-10 11:12:01 +01:00
|
|
|
serve_cfgsync(config.port, state).await?;
|
2026-03-09 08:48:05 +01:00
|
|
|
|
|
|
|
|
Ok(())
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-10 13:56:27 +01:00
|
|
|
/// Runs a registration-backed cfgsync server directly from a snapshot
|
|
|
|
|
/// materializer.
|
|
|
|
|
pub async fn serve_snapshot_cfgsync<M>(port: u16, materializer: M) -> Result<(), RunCfgsyncError>
|
|
|
|
|
where
|
|
|
|
|
M: RegistrationSnapshotMaterializer + 'static,
|
|
|
|
|
{
|
|
|
|
|
let provider = SnapshotConfigSource::new(CachedSnapshotMaterializer::new(materializer));
|
|
|
|
|
let state = CfgsyncServerState::new(Arc::new(provider));
|
|
|
|
|
|
|
|
|
|
serve_cfgsync(port, state).await
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-10 09:18:29 +01:00
|
|
|
fn build_server_state(
|
2026-03-10 11:06:16 +01:00
|
|
|
config: &CfgsyncServerConfig,
|
2026-03-10 09:18:29 +01:00
|
|
|
bundle_path: &Path,
|
2026-03-10 11:03:51 +01:00
|
|
|
) -> anyhow::Result<CfgsyncServerState> {
|
2026-03-10 11:06:16 +01:00
|
|
|
let repo = match config.serving_mode {
|
|
|
|
|
CfgsyncServingMode::Bundle => load_bundle_provider(bundle_path)?,
|
2026-03-10 12:30:53 +01:00
|
|
|
CfgsyncServingMode::Registration => load_registration_source(bundle_path)?,
|
2026-03-10 09:18:29 +01:00
|
|
|
};
|
2026-03-09 08:48:05 +01:00
|
|
|
|
2026-03-10 11:03:51 +01:00
|
|
|
Ok(CfgsyncServerState::new(repo))
|
2026-03-09 08:48:05 +01:00
|
|
|
}
|