Use materializing cfgsync provider at runtime

This commit is contained in:
andrussal 2026-03-10 09:18:29 +01:00
parent 911d09e2c1
commit b775f7fd81
6 changed files with 73 additions and 23 deletions

2
Cargo.lock generated
View File

@ -922,6 +922,7 @@ version = "0.1.0"
dependencies = [
"cfgsync-artifacts",
"cfgsync-core",
"serde",
"thiserror 2.0.18",
]
@ -954,6 +955,7 @@ name = "cfgsync-runtime"
version = "0.1.0"
dependencies = [
"anyhow",
"cfgsync-adapter",
"cfgsync-core",
"clap",
"serde",

View File

@ -15,4 +15,5 @@ workspace = true
[dependencies]
cfgsync-artifacts = { workspace = true }
cfgsync-core = { workspace = true }
serde = { workspace = true }
thiserror = { workspace = true }

View File

@ -5,18 +5,19 @@ use cfgsync_core::{
CfgSyncErrorResponse, CfgSyncPayload, ConfigProvider, NodeRegistration, RegistrationResponse,
RepoResponse,
};
use serde::{Deserialize, Serialize};
use thiserror::Error;
/// Type-erased cfgsync adapter error used to preserve source context.
pub type DynCfgsyncError = Box<dyn Error + Send + Sync + 'static>;
/// Per-node rendered config output used to build cfgsync bundles.
#[derive(Debug, Clone)]
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CfgsyncNodeConfig {
/// Stable node identifier resolved by the adapter.
pub identifier: String,
/// Serialized config payload for the node.
pub config_yaml: String,
/// Files served to the node after cfgsync registration.
pub files: Vec<ArtifactFile>,
}
/// Node artifacts produced by a cfgsync materializer.
@ -260,7 +261,7 @@ fn build_node_entry<E: CfgsyncEnv>(
Ok(CfgsyncNodeConfig {
identifier: E::node_identifier(index, node),
config_yaml,
files: vec![ArtifactFile::new("/config.yaml", &config_yaml)],
})
}
@ -278,11 +279,12 @@ fn build_rewritten_node_config<E: CfgsyncEnv>(
}
fn build_node_artifacts_from_config(config: &CfgsyncNodeConfig) -> CfgsyncNodeArtifacts {
CfgsyncNodeArtifacts::new(vec![ArtifactFile::new("/config.yaml", &config.config_yaml)])
CfgsyncNodeArtifacts::new(config.files.clone())
}
#[cfg(test)]
mod tests {
use cfgsync_artifacts::ArtifactFile;
use cfgsync_core::{CfgSyncErrorCode, ConfigProvider, NodeRegistration, RepoResponse};
use super::{CfgsyncNodeCatalog, CfgsyncNodeConfig, MaterializingConfigProvider};
@ -291,19 +293,19 @@ mod tests {
fn catalog_resolves_identifier() {
let catalog = CfgsyncNodeCatalog::new(vec![CfgsyncNodeConfig {
identifier: "node-1".to_owned(),
config_yaml: "key: value".to_owned(),
files: vec![ArtifactFile::new("/config.yaml", "key: value")],
}]);
let node = catalog.resolve("node-1").expect("resolve node config");
assert_eq!(node.config_yaml, "key: value");
assert_eq!(node.files[0].content, "key: value");
}
#[test]
fn materializing_provider_resolves_registered_node() {
let catalog = CfgsyncNodeCatalog::new(vec![CfgsyncNodeConfig {
identifier: "node-1".to_owned(),
config_yaml: "key: value".to_owned(),
files: vec![ArtifactFile::new("/config.yaml", "key: value")],
}]);
let provider = MaterializingConfigProvider::new(catalog);
let registration = NodeRegistration {
@ -323,7 +325,7 @@ mod tests {
fn materializing_provider_reports_not_ready_before_registration() {
let catalog = CfgsyncNodeCatalog::new(vec![CfgsyncNodeConfig {
identifier: "node-1".to_owned(),
config_yaml: "key: value".to_owned(),
files: vec![ArtifactFile::new("/config.yaml", "key: value")],
}]);
let provider = MaterializingConfigProvider::new(catalog);
let registration = NodeRegistration {

View File

@ -13,14 +13,15 @@ version = { workspace = true }
workspace = true
[dependencies]
anyhow = "1"
cfgsync-core = { workspace = true }
clap = { version = "4", features = ["derive"] }
serde = { workspace = true }
serde_yaml = { workspace = true }
thiserror = { workspace = true }
tokio = { default-features = false, features = ["macros", "net", "rt-multi-thread"], version = "1" }
tracing = { workspace = true }
anyhow = "1"
cfgsync-adapter = { workspace = true }
cfgsync-core = { workspace = true }
clap = { version = "4", features = ["derive"] }
serde = { workspace = true }
serde_yaml = { workspace = true }
thiserror = { workspace = true }
tokio = { default-features = false, features = ["macros", "net", "rt-multi-thread"], version = "1" }
tracing = { workspace = true }
[dev-dependencies]
tempfile = { workspace = true }

View File

@ -1,7 +1,8 @@
use std::{fs, path::Path, sync::Arc};
use anyhow::Context as _;
use cfgsync_core::{CfgSyncState, ConfigProvider, FileConfigProvider, run_cfgsync};
use cfgsync_adapter::{CfgsyncNodeCatalog, MaterializingConfigProvider};
use cfgsync_core::{CfgSyncBundle, CfgSyncState, ConfigProvider, FileConfigProvider, run_cfgsync};
use serde::Deserialize;
/// Runtime cfgsync server config loaded from YAML.
@ -9,6 +10,8 @@ use serde::Deserialize;
pub struct CfgSyncServerConfig {
pub port: u16,
pub bundle_path: String,
#[serde(default)]
pub registration_flow: bool,
}
impl CfgSyncServerConfig {
@ -22,13 +25,42 @@ impl CfgSyncServerConfig {
}
}
fn load_bundle(bundle_path: &Path) -> anyhow::Result<Arc<dyn ConfigProvider>> {
fn load_bundle_provider(bundle_path: &Path) -> anyhow::Result<Arc<dyn ConfigProvider>> {
let provider = FileConfigProvider::from_yaml_file(bundle_path)
.with_context(|| format!("loading cfgsync provider from {}", bundle_path.display()))?;
Ok(Arc::new(provider))
}
fn load_materializing_provider(bundle_path: &Path) -> anyhow::Result<Arc<dyn ConfigProvider>> {
let bundle = load_bundle_yaml(bundle_path)?;
let catalog = build_node_catalog(bundle);
let provider = MaterializingConfigProvider::new(catalog);
Ok(Arc::new(provider))
}
fn load_bundle_yaml(bundle_path: &Path) -> anyhow::Result<CfgSyncBundle> {
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()))
}
fn build_node_catalog(bundle: CfgSyncBundle) -> CfgsyncNodeCatalog {
let nodes = bundle
.nodes
.into_iter()
.map(|node| cfgsync_adapter::CfgsyncNodeConfig {
identifier: node.identifier,
files: node.files,
})
.collect();
CfgsyncNodeCatalog::new(nodes)
}
fn resolve_bundle_path(config_path: &Path, bundle_path: &str) -> std::path::PathBuf {
let path = Path::new(bundle_path);
if path.is_absolute() {
@ -46,14 +78,21 @@ pub async fn run_cfgsync_server(config_path: &Path) -> anyhow::Result<()> {
let config = CfgSyncServerConfig::load_from_file(config_path)?;
let bundle_path = resolve_bundle_path(config_path, &config.bundle_path);
let state = build_server_state(&bundle_path)?;
let state = build_server_state(&config, &bundle_path)?;
run_cfgsync(config.port, state).await?;
Ok(())
}
fn build_server_state(bundle_path: &Path) -> anyhow::Result<CfgSyncState> {
let repo = load_bundle(bundle_path)?;
fn build_server_state(
config: &CfgSyncServerConfig,
bundle_path: &Path,
) -> anyhow::Result<CfgSyncState> {
let repo = if config.registration_flow {
load_materializing_provider(bundle_path)?
} else {
load_bundle_provider(bundle_path)?
};
Ok(CfgSyncState::new(repo))
}

View File

@ -54,7 +54,7 @@ fn build_cfgsync_bundle<E: CfgsyncEnv>(
.into_iter()
.map(|node| CfgSyncBundleNode {
identifier: node.identifier,
files: vec![build_bundle_file("/config.yaml", node.config_yaml)],
files: node.files,
})
.collect();
@ -121,6 +121,11 @@ fn build_cfgsync_server_config() -> Value {
Value::String("cfgsync.bundle.yaml".to_string()),
);
root.insert(
Value::String("registration_flow".to_string()),
Value::Bool(true),
);
Value::Mapping(root)
}