Configurable external resources (closes #355)

This commit is contained in:
mdecimus
2024-04-15 13:50:47 +02:00
parent 35fcfb8c81
commit 0267f28156
5 changed files with 72 additions and 45 deletions

View File

@@ -37,13 +37,12 @@ use utils::{
use crate::{
config::{server::Servers, tracers::Tracers},
manager::SPAMFILTER_URL,
Core, SharedCore,
};
use super::{
config::{ConfigManager, Patterns},
download_resource, WEBADMIN_KEY, WEBADMIN_URL,
WEBADMIN_KEY,
};
pub struct BootManager {
@@ -177,12 +176,11 @@ impl BootManager {
.filter(|v| !v.is_empty())
.is_none()
{
match manager.fetch_external_config(SPAMFILTER_URL).await {
match manager.fetch_config_resource("spam-filter").await {
Ok(external_config) => {
tracing::info!(
context = "config",
event = "import",
url = SPAMFILTER_URL,
version = external_config.version,
"Imported spam filter rules"
);
@@ -221,13 +219,12 @@ impl BootManager {
{
match blob_store.get_blob(WEBADMIN_KEY, 0..usize::MAX).await {
Ok(Some(_)) => (),
Ok(None) => match download_resource(WEBADMIN_URL).await {
Ok(None) => match manager.fetch_resource("webadmin").await {
Ok(bytes) => match blob_store.put_blob(WEBADMIN_KEY, &bytes).await {
Ok(_) => {
tracing::info!(
context = "webadmin",
event = "download",
url = WEBADMIN_URL,
"Downloaded webadmin bundle"
);
}

View File

@@ -37,8 +37,6 @@ use utils::{
glob::GlobPattern,
};
use super::download_resource;
#[derive(Default)]
pub struct ConfigManager {
pub cfg_local: ArcSwap<BTreeMap<String, String>>,
@@ -320,9 +318,9 @@ impl ConfigManager {
})
}
pub async fn update_external_config(&self, url: &str) -> store::Result<Option<String>> {
pub async fn update_config_resource(&self, resource_id: &str) -> store::Result<Option<String>> {
let external = self
.fetch_external_config(url)
.fetch_config_resource(resource_id)
.await
.map_err(store::Error::InternalError)?;
@@ -337,7 +335,7 @@ impl ConfigManager {
tracing::debug!(
context = "config",
event = "update",
url = url,
resource_id = resource_id,
version = external.version,
"Configuration version is up-to-date"
);
@@ -345,8 +343,11 @@ impl ConfigManager {
}
}
pub(crate) async fn fetch_external_config(&self, url: &str) -> Result<ExternalConfig, String> {
let config = String::from_utf8(download_resource(url).await?)
pub(crate) async fn fetch_config_resource(
&self,
resource_id: &str,
) -> Result<ExternalConfig, String> {
let config = String::from_utf8(self.fetch_resource(resource_id).await?)
.map_err(|err| format!("Configuration file has invalid UTF-8: {err}"))?;
let config = Config::new(config)
.map_err(|err| format!("Failed to parse external configuration: {err}"))?;
@@ -375,7 +376,7 @@ impl ConfigManager {
event = "import",
key = key,
value = value,
url = url,
resource_id = resource_id,
"Ignoring key"
);
}

View File

@@ -25,28 +25,56 @@ use std::time::Duration;
use crate::USER_AGENT;
use self::config::ConfigManager;
pub mod boot;
pub mod config;
pub mod reload;
pub mod webadmin;
pub const SPAMFILTER_URL: &str = "https://get.stalw.art/resources/config/spamfilter.toml";
pub const WEBADMIN_URL: &str =
const DEFAULT_SPAMFILTER_URL: &str = "https://get.stalw.art/resources/config/spamfilter.toml";
const DEFAULT_WEBADMIN_URL: &str =
"https://github.com/stalwartlabs/webadmin/releases/latest/download/webadmin.zip";
pub const WEBADMIN_KEY: &[u8] = "STALWART_WEBADMIN".as_bytes();
async fn download_resource(url: &str) -> Result<Vec<u8>, String> {
reqwest::Client::builder()
.timeout(Duration::from_secs(60))
.user_agent(USER_AGENT)
.build()
.unwrap_or_default()
.get(url)
.send()
.await
.map_err(|err| format!("Failed to fetch {url}: {err}"))?
.bytes()
.await
.map_err(|err| format!("Failed to fetch {url}: {err}"))
.map(|bytes| bytes.to_vec())
impl ConfigManager {
pub async fn fetch_resource(&self, resource_id: &str) -> Result<Vec<u8>, String> {
if let Some(url) = self
.get(&format!("config.resource.{resource_id}"))
.await
.map_err(|err| {
format!("Failed to fetch configuration key 'resource.{resource_id}': {err}",)
})?
{
fetch_resource(&url).await
} else {
match resource_id {
"spam-filter" => fetch_resource(DEFAULT_SPAMFILTER_URL).await,
"webadmin" => fetch_resource(DEFAULT_WEBADMIN_URL).await,
_ => Err(format!("Unknown resource: {resource_id}")),
}
}
}
}
async fn fetch_resource(url: &str) -> Result<Vec<u8>, String> {
if let Some(path) = url.strip_prefix("file://") {
tokio::fs::read(path)
.await
.map_err(|err| format!("Failed to read {path}: {err}"))
} else {
reqwest::Client::builder()
.timeout(Duration::from_secs(60))
.user_agent(USER_AGENT)
.build()
.unwrap_or_default()
.get(url)
.send()
.await
.map_err(|err| format!("Failed to fetch {url}: {err}"))?
.bytes()
.await
.map_err(|err| format!("Failed to fetch {url}: {err}"))
.map(|bytes| bytes.to_vec())
}
}

View File

@@ -30,7 +30,9 @@ use ahash::AHashMap;
use arc_swap::ArcSwap;
use store::BlobStore;
use super::{download_resource, WEBADMIN_KEY, WEBADMIN_URL};
use crate::Core;
use super::WEBADMIN_KEY;
pub struct WebAdminManager {
bundle_path: TempDir,
@@ -128,12 +130,17 @@ impl WebAdminManager {
Ok(())
}
pub async fn update_and_unpack(&self, blob_store: &BlobStore) -> store::Result<()> {
let bytes = download_resource(WEBADMIN_URL).await.map_err(|err| {
store::Error::InternalError(format!("Failed to download webadmin: {err}"))
})?;
blob_store.put_blob(WEBADMIN_KEY, &bytes).await?;
self.unpack(blob_store).await
pub async fn update_and_unpack(&self, core: &Core) -> store::Result<()> {
let bytes = core
.storage
.config
.fetch_resource("webadmin")
.await
.map_err(|err| {
store::Error::InternalError(format!("Failed to download webadmin: {err}"))
})?;
core.storage.blob.put_blob(WEBADMIN_KEY, &bytes).await?;
self.unpack(&core.storage.blob).await
}
}

View File

@@ -21,7 +21,6 @@
* for more details.
*/
use common::manager::SPAMFILTER_URL;
use hyper::Method;
use jmap_proto::error::request::RequestError;
use serde_json::json;
@@ -96,7 +95,7 @@ impl JMAP {
.core
.storage
.config
.update_external_config(SPAMFILTER_URL)
.update_config_resource("spam-filter")
.await
{
Ok(result) => JsonResponse::new(json!({
@@ -107,12 +106,7 @@ impl JMAP {
}
}
(Some("webadmin"), &Method::GET) => {
match self
.inner
.webadmin
.update_and_unpack(&self.core.storage.blob)
.await
{
match self.inner.webadmin.update_and_unpack(&self.core).await {
Ok(_) => JsonResponse::new(json!({
"data": (),
}))