- Introduced a new `proxy` module in the service layer. - Created `ProxyServiceTrait` for managing proxy configurations. - Implemented various configuration types including access rules, cache zones, limit rules, and more. - Added rendering logic for Nginx configuration components. - Developed a repository implementation for fetching and merging proxy configurations from the database. - Enhanced error handling with `ProxyServiceError` for better clarity on configuration issues.
445 lines
17 KiB
Rust
445 lines
17 KiB
Rust
use std::collections::HashMap;
|
|
|
|
use sea_orm::{DatabaseConnection, prelude::*};
|
|
|
|
use crate::service::proxy::types::{
|
|
Mergeable, OverrideRef, ProxyConfig, ProxyServiceError, ProxyServiceResult, ProxyType,
|
|
};
|
|
|
|
#[async_trait::async_trait]
|
|
pub trait ProxyRepo: Send + Sync + 'static {
|
|
// get the raw config for the given proxy_id. This should return the config of the given proxy_id without merging it with its parent configs (if any).
|
|
async fn get_proxy_raw_config(&self, proxy_id: uuid::Uuid) -> ProxyServiceResult<ProxyConfig>;
|
|
|
|
// get the raw config for the given proxy_id. This should return the config of the given proxy_id and all its parent configs (if any) without merging them. The returned vector is ordered from the leaf (given proxy_id) down to the root config (most specific to least specific).
|
|
async fn get_proxy_raw_configs(
|
|
&self,
|
|
proxy_id: uuid::Uuid,
|
|
) -> ProxyServiceResult<Vec<ProxyConfig>>;
|
|
|
|
// get the merged config for the given proxy_id. This should merge the config of the given proxy_id with its parent configs (if any) and return the final merged config.
|
|
async fn get_merged_proxy_config(
|
|
&self,
|
|
proxy_id: uuid::Uuid,
|
|
) -> ProxyServiceResult<ProxyConfig>;
|
|
}
|
|
|
|
pub(crate) struct ProxyRepoImpl {
|
|
db: DatabaseConnection,
|
|
}
|
|
|
|
impl ProxyRepoImpl {
|
|
pub fn new(db: DatabaseConnection) -> Self {
|
|
Self { db }
|
|
}
|
|
}
|
|
|
|
#[async_trait::async_trait]
|
|
impl ProxyRepo for ProxyRepoImpl {
|
|
async fn get_proxy_raw_configs(
|
|
&self,
|
|
proxy_config_id: uuid::Uuid,
|
|
) -> ProxyServiceResult<Vec<ProxyConfig>> {
|
|
let mut proxy_config_id_frontier = vec![proxy_config_id];
|
|
let mut visited = std::collections::HashSet::new();
|
|
|
|
let mut configs: Vec<ProxyConfig> = Vec::new();
|
|
|
|
while let Some(current_id) = proxy_config_id_frontier.pop() {
|
|
if visited.contains(¤t_id) {
|
|
continue;
|
|
}
|
|
visited.insert(current_id);
|
|
//
|
|
let config = self.get_proxy_raw_config(current_id).await?;
|
|
//
|
|
if let Some(parent_ids) = &config.parent_config_id {
|
|
proxy_config_id_frontier.extend(parent_ids);
|
|
}
|
|
configs.push(config);
|
|
}
|
|
|
|
Ok(configs)
|
|
}
|
|
|
|
async fn get_proxy_raw_config(&self, proxy_id: uuid::Uuid) -> ProxyServiceResult<ProxyConfig> {
|
|
let proxy_entity = crate::db::entities::proxy_config::Entity::find_by_id(proxy_id)
|
|
.one(&self.db)
|
|
.await?
|
|
.ok_or(ProxyServiceError::ConfigNotFound)?;
|
|
|
|
// Resolve parent configs from config_inheritance
|
|
let parent_config_id = {
|
|
let inheritance = crate::db::entities::config_inheritance::Entity::find()
|
|
.filter(crate::db::entities::config_inheritance::Column::ChildConfigId.eq(proxy_id))
|
|
.all(&self.db)
|
|
.await?;
|
|
if inheritance.is_empty() {
|
|
None
|
|
} else {
|
|
Some(
|
|
inheritance
|
|
.into_iter()
|
|
.map(|ci| ci.parent_config_id)
|
|
.collect(),
|
|
)
|
|
}
|
|
};
|
|
|
|
// Direct children of proxy_config
|
|
let server_blocks = crate::db::entities::server_block::Entity::find()
|
|
.filter(crate::db::entities::server_block::Column::ConfigId.eq(proxy_id))
|
|
.all(&self.db)
|
|
.await?;
|
|
|
|
let upstreams = crate::db::entities::upstream::Entity::find()
|
|
.filter(crate::db::entities::upstream::Column::ConfigId.eq(proxy_id))
|
|
.all(&self.db)
|
|
.await?;
|
|
|
|
// Children of server_blocks
|
|
let server_block_ids: Vec<uuid::Uuid> = server_blocks.iter().map(|sb| sb.id).collect();
|
|
|
|
let (location_blocks, log_settings, server_access_rules) = if server_block_ids.is_empty() {
|
|
(vec![], vec![], vec![])
|
|
} else {
|
|
(
|
|
crate::db::entities::location_block::Entity::find()
|
|
.filter(
|
|
crate::db::entities::location_block::Column::ServerId
|
|
.is_in(server_block_ids.clone()),
|
|
)
|
|
.all(&self.db)
|
|
.await?,
|
|
crate::db::entities::log_setting::Entity::find()
|
|
.filter(
|
|
crate::db::entities::log_setting::Column::ServerId
|
|
.is_in(server_block_ids.clone()),
|
|
)
|
|
.all(&self.db)
|
|
.await?,
|
|
crate::db::entities::access_rule::Entity::find()
|
|
.filter(
|
|
crate::db::entities::access_rule::Column::ServerId.is_in(server_block_ids),
|
|
)
|
|
.all(&self.db)
|
|
.await?,
|
|
)
|
|
};
|
|
|
|
// Children of location_blocks
|
|
let location_block_ids: Vec<uuid::Uuid> = location_blocks.iter().map(|lb| lb.id).collect();
|
|
|
|
let (location_access_rules, limit_rules, proxy_settings, rewrite_rules) =
|
|
if location_block_ids.is_empty() {
|
|
(vec![], vec![], vec![], vec![])
|
|
} else {
|
|
(
|
|
crate::db::entities::access_rule::Entity::find()
|
|
.filter(
|
|
crate::db::entities::access_rule::Column::LocationId
|
|
.is_in(location_block_ids.clone()),
|
|
)
|
|
.all(&self.db)
|
|
.await?,
|
|
crate::db::entities::limit_rule::Entity::find()
|
|
.filter(
|
|
crate::db::entities::limit_rule::Column::LocationId
|
|
.is_in(location_block_ids.clone()),
|
|
)
|
|
.all(&self.db)
|
|
.await?,
|
|
crate::db::entities::proxy_setting::Entity::find()
|
|
.filter(
|
|
crate::db::entities::proxy_setting::Column::LocationId
|
|
.is_in(location_block_ids.clone()),
|
|
)
|
|
.all(&self.db)
|
|
.await?,
|
|
crate::db::entities::rewrite_rule::Entity::find()
|
|
.filter(
|
|
crate::db::entities::rewrite_rule::Column::LocationId
|
|
.is_in(location_block_ids),
|
|
)
|
|
.all(&self.db)
|
|
.await?,
|
|
)
|
|
};
|
|
|
|
// Collect referenced IDs for zone and cert lookups
|
|
let limit_zone_ids: Vec<uuid::Uuid> = limit_rules.iter().map(|lr| lr.zone_id).collect();
|
|
let cache_zone_ids: Vec<uuid::Uuid> = proxy_settings
|
|
.iter()
|
|
.filter_map(|ps| ps.cache_zone)
|
|
.collect();
|
|
let ssl_cert_ids: Vec<uuid::Uuid> = server_blocks
|
|
.iter()
|
|
.filter_map(|sb| sb.ssl_cert_id)
|
|
.collect();
|
|
|
|
let limit_zones = if limit_zone_ids.is_empty() {
|
|
vec![]
|
|
} else {
|
|
crate::db::entities::limit_zone::Entity::find()
|
|
.filter(crate::db::entities::limit_zone::Column::Id.is_in(limit_zone_ids))
|
|
.all(&self.db)
|
|
.await?
|
|
};
|
|
|
|
let cache_zones = if cache_zone_ids.is_empty() {
|
|
vec![]
|
|
} else {
|
|
crate::db::entities::cache_zone::Entity::find()
|
|
.filter(crate::db::entities::cache_zone::Column::Id.is_in(cache_zone_ids))
|
|
.all(&self.db)
|
|
.await?
|
|
};
|
|
|
|
let ssl_certificates = if ssl_cert_ids.is_empty() {
|
|
vec![]
|
|
} else {
|
|
crate::db::entities::ssl_certificate::Entity::find()
|
|
.filter(crate::db::entities::ssl_certificate::Column::Id.is_in(ssl_cert_ids))
|
|
.all(&self.db)
|
|
.await?
|
|
};
|
|
|
|
// ── Group child IDs by parent ────────────────────────────────────────
|
|
|
|
// location_block IDs grouped by server_id
|
|
let loc_block_ids_by_server: HashMap<uuid::Uuid, Vec<OverrideRef>> = {
|
|
let mut map: HashMap<_, Vec<_>> = HashMap::new();
|
|
for lb in &location_blocks {
|
|
map.entry(lb.server_id).or_default().push(OverrideRef {
|
|
id: lb.id,
|
|
override_of_id: lb.override_of_id,
|
|
});
|
|
}
|
|
map
|
|
};
|
|
|
|
// log_setting IDs grouped by server_id
|
|
let log_setting_ids_by_server: HashMap<uuid::Uuid, Vec<OverrideRef>> = {
|
|
let mut map: HashMap<_, Vec<_>> = HashMap::new();
|
|
for ls in &log_settings {
|
|
map.entry(ls.server_id).or_default().push(OverrideRef {
|
|
id: ls.id,
|
|
override_of_id: ls.override_of_id,
|
|
});
|
|
}
|
|
map
|
|
};
|
|
|
|
// server-level access_rule IDs grouped by server_id
|
|
let server_ar_ids_by_server: HashMap<uuid::Uuid, Vec<OverrideRef>> = {
|
|
let mut map: HashMap<_, Vec<_>> = HashMap::new();
|
|
for ar in &server_access_rules {
|
|
if let Some(sid) = ar.server_id {
|
|
map.entry(sid).or_default().push(OverrideRef {
|
|
id: ar.id,
|
|
override_of_id: ar.override_of_id,
|
|
});
|
|
}
|
|
}
|
|
map
|
|
};
|
|
|
|
// location-level access_rule IDs grouped by location_id
|
|
let loc_ar_ids_by_location: HashMap<uuid::Uuid, Vec<OverrideRef>> = {
|
|
let mut map: HashMap<_, Vec<_>> = HashMap::new();
|
|
for ar in &location_access_rules {
|
|
if let Some(lid) = ar.location_id {
|
|
map.entry(lid).or_default().push(OverrideRef {
|
|
id: ar.id,
|
|
override_of_id: ar.override_of_id,
|
|
});
|
|
}
|
|
}
|
|
map
|
|
};
|
|
|
|
// limit_rule IDs grouped by location_id
|
|
let lr_ids_by_location: HashMap<uuid::Uuid, Vec<OverrideRef>> = {
|
|
let mut map: HashMap<_, Vec<_>> = HashMap::new();
|
|
for lr in &limit_rules {
|
|
map.entry(lr.location_id).or_default().push(OverrideRef {
|
|
id: lr.id,
|
|
override_of_id: lr.override_of_id,
|
|
});
|
|
}
|
|
map
|
|
};
|
|
|
|
// proxy_setting IDs grouped by location_id
|
|
let ps_ids_by_location: HashMap<uuid::Uuid, Vec<OverrideRef>> = {
|
|
let mut map: HashMap<_, Vec<_>> = HashMap::new();
|
|
for ps in &proxy_settings {
|
|
map.entry(ps.location_id).or_default().push(OverrideRef {
|
|
id: ps.id,
|
|
override_of_id: ps.override_of_id,
|
|
});
|
|
}
|
|
map
|
|
};
|
|
|
|
// rewrite_rule IDs grouped by location_id
|
|
let rr_ids_by_location: HashMap<uuid::Uuid, Vec<OverrideRef>> = {
|
|
let mut map: HashMap<_, Vec<_>> = HashMap::new();
|
|
for rr in &rewrite_rules {
|
|
map.entry(rr.location_id).or_default().push(OverrideRef {
|
|
id: rr.id,
|
|
override_of_id: rr.override_of_id,
|
|
});
|
|
}
|
|
map
|
|
};
|
|
|
|
// location_block IDs grouped by proxy_pass_upstream_id (reverse FK)
|
|
let loc_block_ids_by_upstream: HashMap<uuid::Uuid, Vec<OverrideRef>> = {
|
|
let mut map: HashMap<_, Vec<_>> = HashMap::new();
|
|
for lb in &location_blocks {
|
|
if let Some(up_id) = lb.proxy_pass_upstream_id {
|
|
map.entry(up_id).or_default().push(OverrideRef {
|
|
id: lb.id,
|
|
override_of_id: lb.override_of_id,
|
|
});
|
|
}
|
|
}
|
|
map
|
|
};
|
|
|
|
// ── Build child-ID tuples for each config type ───────────────────────
|
|
|
|
let location_block_tuples: Vec<(
|
|
crate::db::entities::location_block::Model,
|
|
Vec<OverrideRef>,
|
|
Vec<OverrideRef>,
|
|
Vec<OverrideRef>,
|
|
Vec<OverrideRef>,
|
|
)> = location_blocks
|
|
.into_iter()
|
|
.map(|lb| {
|
|
let ar_ids = loc_ar_ids_by_location
|
|
.get(&lb.id)
|
|
.cloned()
|
|
.unwrap_or_default();
|
|
let lr_ids = lr_ids_by_location.get(&lb.id).cloned().unwrap_or_default();
|
|
let ps_ids = ps_ids_by_location.get(&lb.id).cloned().unwrap_or_default();
|
|
let rr_ids = rr_ids_by_location.get(&lb.id).cloned().unwrap_or_default();
|
|
(lb, ar_ids, lr_ids, ps_ids, rr_ids)
|
|
})
|
|
.collect();
|
|
|
|
let server_block_tuples: Vec<(
|
|
crate::db::entities::server_block::Model,
|
|
Vec<OverrideRef>,
|
|
Vec<OverrideRef>,
|
|
Vec<OverrideRef>,
|
|
Vec<OverrideRef>,
|
|
)> = server_blocks
|
|
.into_iter()
|
|
.map(|sb| {
|
|
let ar_ids = server_ar_ids_by_server
|
|
.get(&sb.id)
|
|
.cloned()
|
|
.unwrap_or_default();
|
|
let lb_ids = loc_block_ids_by_server
|
|
.get(&sb.id)
|
|
.cloned()
|
|
.unwrap_or_default();
|
|
let ls_ids = log_setting_ids_by_server
|
|
.get(&sb.id)
|
|
.cloned()
|
|
.unwrap_or_default();
|
|
let sc_ids: Vec<OverrideRef> = sb
|
|
.ssl_cert_id
|
|
.into_iter()
|
|
.map(|id| OverrideRef {
|
|
id,
|
|
override_of_id: None,
|
|
})
|
|
.collect();
|
|
(sb, ar_ids, lb_ids, ls_ids, sc_ids)
|
|
})
|
|
.collect();
|
|
|
|
let upstream_tuples: Vec<(crate::db::entities::upstream::Model, Vec<OverrideRef>)> =
|
|
upstreams
|
|
.into_iter()
|
|
.map(|u| {
|
|
let lb_ids = loc_block_ids_by_upstream
|
|
.get(&u.id)
|
|
.cloned()
|
|
.unwrap_or_default();
|
|
(u, lb_ids)
|
|
})
|
|
.collect();
|
|
|
|
// Combine server-level and location-level access rules into one flat list
|
|
let all_access_rules: Vec<crate::db::entities::access_rule::Model> = {
|
|
let mut ars =
|
|
Vec::with_capacity(server_access_rules.len() + location_access_rules.len());
|
|
ars.extend(server_access_rules);
|
|
ars.extend(location_access_rules);
|
|
ars
|
|
};
|
|
|
|
Ok(ProxyConfig {
|
|
id: proxy_entity.id,
|
|
name: proxy_entity.name,
|
|
r#type: ProxyType::Nginx,
|
|
description: proxy_entity.description,
|
|
parent_config_id,
|
|
server_blocks: server_block_tuples
|
|
.into_iter()
|
|
.map(|m| (m.0.id, m.into()))
|
|
.collect(),
|
|
upstreams: upstream_tuples
|
|
.into_iter()
|
|
.map(|m| (m.0.id, m.into()))
|
|
.collect(),
|
|
location_blocks: location_block_tuples
|
|
.into_iter()
|
|
.map(|m| (m.0.id, m.into()))
|
|
.collect(),
|
|
access_rules: all_access_rules
|
|
.into_iter()
|
|
.map(|m| (m.id, m.into()))
|
|
.collect(),
|
|
cache_zones: cache_zones.into_iter().map(|m| (m.id, m.into())).collect(),
|
|
limit_rules: limit_rules.into_iter().map(|m| (m.id, m.into())).collect(),
|
|
limit_zones: limit_zones.into_iter().map(|m| (m.id, m.into())).collect(),
|
|
log_settings: log_settings.into_iter().map(|m| (m.id, m.into())).collect(),
|
|
proxy_settings: proxy_settings
|
|
.into_iter()
|
|
.map(|m| (m.id, m.into()))
|
|
.collect(),
|
|
rewrite_rules: rewrite_rules
|
|
.into_iter()
|
|
.map(|m| (m.id, m.into()))
|
|
.collect(),
|
|
ssl_certificates: ssl_certificates
|
|
.into_iter()
|
|
.map(|m| (m.id, m.into()))
|
|
.collect(),
|
|
})
|
|
}
|
|
|
|
async fn get_merged_proxy_config(
|
|
&self,
|
|
proxy_id: uuid::Uuid,
|
|
) -> ProxyServiceResult<ProxyConfig> {
|
|
let configs = self.get_proxy_raw_configs(proxy_id).await?;
|
|
|
|
// configs is ordered [leaf, ..., root] (most specific first)
|
|
// self.merge(other) means self overrides other
|
|
// So start with leaf and merge each ancestor into it
|
|
let mut iter = configs.into_iter();
|
|
let mut merged = iter.next().ok_or(ProxyServiceError::ConfigNotFound)?;
|
|
for config in iter {
|
|
merged.merge(config);
|
|
}
|
|
Ok(merged)
|
|
}
|
|
}
|