diff --git a/Cargo.lock b/Cargo.lock index 37f4acb..f9bf1be 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -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", diff --git a/cfgsync/adapter/Cargo.toml b/cfgsync/adapter/Cargo.toml index 7a15ad1..bcc4da7 100644 --- a/cfgsync/adapter/Cargo.toml +++ b/cfgsync/adapter/Cargo.toml @@ -15,4 +15,5 @@ workspace = true [dependencies] cfgsync-artifacts = { workspace = true } cfgsync-core = { workspace = true } +serde = { workspace = true } thiserror = { workspace = true } diff --git a/cfgsync/adapter/src/lib.rs b/cfgsync/adapter/src/lib.rs index ff35b4e..d4da5ed 100644 --- a/cfgsync/adapter/src/lib.rs +++ b/cfgsync/adapter/src/lib.rs @@ -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; /// 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, } /// Node artifacts produced by a cfgsync materializer. @@ -260,7 +261,7 @@ fn build_node_entry( 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( } 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 { diff --git a/cfgsync/runtime/Cargo.toml b/cfgsync/runtime/Cargo.toml index 045bc73..f708ba1 100644 --- a/cfgsync/runtime/Cargo.toml +++ b/cfgsync/runtime/Cargo.toml @@ -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 } diff --git a/cfgsync/runtime/src/server.rs b/cfgsync/runtime/src/server.rs index a038a43..ba987f3 100644 --- a/cfgsync/runtime/src/server.rs +++ b/cfgsync/runtime/src/server.rs @@ -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> { +fn load_bundle_provider(bundle_path: &Path) -> anyhow::Result> { 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> { + 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 { + 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 { - let repo = load_bundle(bundle_path)?; +fn build_server_state( + config: &CfgSyncServerConfig, + bundle_path: &Path, +) -> anyhow::Result { + let repo = if config.registration_flow { + load_materializing_provider(bundle_path)? + } else { + load_bundle_provider(bundle_path)? + }; Ok(CfgSyncState::new(repo)) } diff --git a/logos/runtime/ext/src/cfgsync/mod.rs b/logos/runtime/ext/src/cfgsync/mod.rs index 074223d..f607a9c 100644 --- a/logos/runtime/ext/src/cfgsync/mod.rs +++ b/logos/runtime/ext/src/cfgsync/mod.rs @@ -54,7 +54,7 @@ fn build_cfgsync_bundle( .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) }