Configurable external resources (closes #355)
This commit is contained in:
@@ -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"
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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"
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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": (),
|
||||
}))
|
||||
|
||||
Reference in New Issue
Block a user