Persist config from API

This commit is contained in:
Ara Sadoyan
2026-05-11 18:34:41 +02:00
parent 1cbb19ea90
commit 136ccc8e44
6 changed files with 33 additions and 32 deletions

View File

@@ -12,6 +12,7 @@ pub struct APIUpstreamProvider {
pub masterkey: String, pub masterkey: String,
pub certs_dir: String, pub certs_dir: String,
pub config_dir: String, pub config_dir: String,
pub upstreams_file: String,
// pub tls_address: Option<String>, // pub tls_address: Option<String>,
// pub tls_certificate: Option<String>, // pub tls_certificate: Option<String>,
// pub tls_key_file: Option<String>, // pub tls_key_file: Option<String>,

View File

@@ -127,13 +127,13 @@ pub struct AppConfig {
pub rungroup: Option<String>, pub rungroup: Option<String>,
} }
#[derive(Debug, Default, Clone, PartialEq, Eq)] #[derive(Debug, Default, Clone, PartialEq, Eq, Hash)]
pub struct InnerAuth { pub struct InnerAuth {
pub auth_type: Arc<str>, pub auth_type: Arc<str>,
pub auth_cred: Arc<str>, pub auth_cred: Arc<str>,
} }
#[derive(Debug, Clone, PartialEq, Eq)] #[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct InnerMap { pub struct InnerMap {
pub address: Arc<str>, pub address: Arc<str>,
pub port: u16, pub port: u16,

View File

@@ -101,46 +101,31 @@ pub fn clone_dashmap_into(original: &UpstreamsDashMap, cloned: &UpstreamsDashMap
} }
pub fn compare_dashmaps(map1: &UpstreamsDashMap, map2: &UpstreamsDashMap) -> bool { pub fn compare_dashmaps(map1: &UpstreamsDashMap, map2: &UpstreamsDashMap) -> bool {
let keys1: HashSet<_> = map1.iter().map(|entry| entry.key().clone()).collect(); if map1.len() != map2.len() {
let keys2: HashSet<_> = map2.iter().map(|entry| entry.key().clone()).collect();
if keys1 != keys2 {
return false; return false;
} }
for entry1 in map1.iter() { for entry1 in map1.iter() {
let hostname = entry1.key(); let Some(inner_map2) = map2.get(entry1.key()) else {
let inner_map1 = entry1.value();
let Some(inner_map2) = map2.get(hostname) else {
return false; return false;
}; };
let inner_keys1: HashSet<_> = inner_map1.iter().map(|e| e.key().clone()).collect(); let inner_map1 = entry1.value();
let inner_keys2: HashSet<_> = inner_map2.iter().map(|e| e.key().clone()).collect(); if inner_map1.len() != inner_map2.len() {
if inner_keys1 != inner_keys2 {
return false; return false;
} }
for path_entry in inner_map1.iter() { for path_entry in inner_map1.iter() {
let path = path_entry.key(); let Some(entry2) = inner_map2.get(path_entry.key()) else {
let (vec1, _counter1) = path_entry.value(); return false;
let Some(entry2) = inner_map2.get(path) else {
return false; // Path exists in map1 but not in map2
}; };
let (vec2, _counter2) = entry2.value(); let (vec1, _) = path_entry.value();
let (vec2, _) = entry2.value();
if vec1.len() != vec2.len() { if vec1.len() != vec2.len() {
return false; return false;
} }
for item in vec1.iter() { let set1: HashSet<_> = vec1.iter().collect();
let count1 = vec1.iter().filter(|&x| x == item).count(); let set2: HashSet<_> = vec2.iter().collect();
let count2 = vec2.iter().filter(|&x| x == item).count(); if set1 != set2 {
if count1 != count2 { return false;
return false;
}
} }
// let set1: HashSet<_> = vec1.iter().collect();
// let set2: HashSet<_> = vec2.iter().collect();
// if set1 != set2 {
// return false;
// }
} }
} }
true true

View File

@@ -59,6 +59,7 @@ impl BackgroundService for LB {
address: self.config.config_address.clone(), address: self.config.config_address.clone(),
masterkey: self.config.master_key.clone(), masterkey: self.config.master_key.clone(),
config_api_enabled: self.config.config_api_enabled, config_api_enabled: self.config.config_api_enabled,
upstreams_file: self.config.upstreams_conf.clone(),
// certs_dir: self.config.proxy_certificates.clone().unwrap_or_else(|| "/tmp".to_string()), // certs_dir: self.config.proxy_certificates.clone().unwrap_or_else(|| "/tmp".to_string()),
config_dir: confdir.clone(), config_dir: confdir.clone(),
certs_dir: certdir.clone(), certs_dir: certdir.clone(),

View File

@@ -278,7 +278,7 @@ impl ProxyHttp for LB {
let mut buf = String::with_capacity(80); let mut buf = String::with_capacity(80);
buf.push_str("backend_id="); buf.push_str("backend_id=");
buf.push_str(&tt); buf.push_str(&tt);
buf.push_str("; Path=/; Max-Age=600; HttpOnly; SameSite=Lax"); buf.push_str("; Path=/; Max-Age=86400; HttpOnly; SameSite=Lax");
let _ = _upstream_response.insert_header("set-cookie", buf.as_str()); let _ = _upstream_response.insert_header("set-cookie", buf.as_str());
} }
} }

View File

@@ -35,6 +35,7 @@ struct AppState {
master_key: String, master_key: String,
cert_creds: String, cert_creds: String,
certs_dir: String, certs_dir: String,
upstreams_file: String,
config_sender: Sender<Configuration>, config_sender: Sender<Configuration>,
config_api_enabled: bool, config_api_enabled: bool,
current_upstreams: Arc<UpstreamsDashMap>, current_upstreams: Arc<UpstreamsDashMap>,
@@ -48,6 +49,7 @@ pub async fn run_server(config: &APIUpstreamProvider, mut to_return: Sender<Conf
master_key: config.masterkey.clone(), master_key: config.masterkey.clone(),
cert_creds: credsfile, cert_creds: credsfile,
certs_dir: config.certs_dir.clone(), certs_dir: config.certs_dir.clone(),
upstreams_file: config.upstreams_file.clone(),
config_sender: to_return.clone(), config_sender: to_return.clone(),
config_api_enabled: config.config_api_enabled, config_api_enabled: config.config_api_enabled,
current_upstreams: upstreams_curr, current_upstreams: upstreams_curr,
@@ -98,12 +100,18 @@ async fn conf(State(st): State<AppState>, Query(params): Query<HashMap<String, S
return Response::builder().status(StatusCode::FORBIDDEN).body(Body::from("Config API is disabled !\n")).unwrap(); return Response::builder().status(StatusCode::FORBIDDEN).body(Body::from("Config API is disabled !\n")).unwrap();
} }
// if let Some(s) = headers.get("x-api-key").and_then(|v| v.to_str().ok()).or(params.get("key").map(|s| s.as_str())) { // if let Some(s) = headers.get("x-api-key").and_then(|v| v.to_str().ok()).or(params.get("key").map(|s| s.as_str())) {
if key_authorization(&headers, &params, &st.master_key) { if key_authorization(&headers, &params, &st.master_key) {
let strcontent = content.as_str(); let strcontent = content.as_str();
let parsed = serde_yml::from_str::<Config>(strcontent); let parsed = serde_yml::from_str::<Config>(strcontent);
match parsed { match parsed {
Ok(_) => { Ok(_) => {
drop(tokio::spawn(async move { apply_config(content.as_str(), st).await })); if let Some(_) = params.get("save") {
drop(tokio::spawn(async move { apply_config(content.as_str(), st, true).await }));
} else {
drop(tokio::spawn(async move { apply_config(content.as_str(), st, false).await }));
}
// apply_config(content.as_str(), st).await;
return Response::builder().status(StatusCode::OK).body(Body::from("Accepted! Applying in background\n")).unwrap(); return Response::builder().status(StatusCode::OK).body(Body::from("Accepted! Applying in background\n")).unwrap();
} }
Err(err) => { Err(err) => {
@@ -115,9 +123,15 @@ async fn conf(State(st): State<AppState>, Query(params): Query<HashMap<String, S
Response::builder().status(StatusCode::FORBIDDEN).body(Body::from("Access Denied !\n")).unwrap() Response::builder().status(StatusCode::FORBIDDEN).body(Body::from("Access Denied !\n")).unwrap()
} }
async fn apply_config(content: &str, mut st: AppState) { async fn apply_config(content: &str, mut st: AppState, save: bool) {
let sl = crate::utils::parceyaml::load_configuration(content, "content").await; let sl = crate::utils::parceyaml::load_configuration(content, "content").await;
if let Some(serverlist) = sl.0 { if let Some(serverlist) = sl.0 {
if save {
info!("Saving new upstreams to: {}", st.upstreams_file);
if let Err(err) = std::fs::write(&st.upstreams_file, content) {
error!("Error saving to: {} : {}", st.upstreams_file, err);
}
}
let _ = st.config_sender.send(serverlist).await; let _ = st.config_sender.send(serverlist).await;
} }
} }