pict-rs/src/config.rs

385 lines
11 KiB
Rust
Raw Normal View History

2020-06-07 19:12:19 +00:00
use std::{collections::HashSet, net::SocketAddr, path::PathBuf};
use structopt::StructOpt;
2021-09-18 21:29:30 +00:00
use url::Url;
2020-06-07 00:54:06 +00:00
2021-10-23 04:48:56 +00:00
use crate::magick::ValidInputType;
#[derive(Clone, Debug, StructOpt)]
pub(crate) struct Args {
#[structopt(short, long, help = "Path to the pict-rs configuration file")]
config_file: Option<PathBuf>,
2021-11-01 02:11:35 +00:00
#[structopt(long, help = "Path to a file defining a store migration")]
migrate_file: Option<PathBuf>,
#[structopt(flatten)]
overrides: Overrides,
}
2021-10-28 05:17:37 +00:00
fn is_false(b: &bool) -> bool {
!b
}
#[derive(Clone, Debug, serde::Serialize, structopt::StructOpt)]
2021-10-28 05:17:37 +00:00
#[serde(rename_all = "snake_case")]
pub(crate) struct Overrides {
#[structopt(
short,
long,
help = "Whether to skip validating images uploaded via the internal import API"
)]
2021-10-28 05:17:37 +00:00
#[serde(skip_serializing_if = "is_false")]
skip_validate_imports: bool,
#[structopt(short, long, help = "The address and port the server binds to.")]
2021-10-28 05:17:37 +00:00
#[serde(skip_serializing_if = "Option::is_none")]
addr: Option<SocketAddr>,
2020-06-07 00:54:06 +00:00
#[structopt(short, long, help = "The path to the data directory, e.g. data/")]
2021-10-28 05:17:37 +00:00
#[serde(skip_serializing_if = "Option::is_none")]
path: Option<PathBuf>,
2020-06-07 01:44:26 +00:00
#[structopt(
short,
long,
2020-06-16 20:55:24 +00:00
help = "An optional image format to convert all uploaded files into, supports 'jpg', 'png', and 'webp'"
2020-06-07 01:44:26 +00:00
)]
2021-10-28 05:17:37 +00:00
#[serde(skip_serializing_if = "Option::is_none")]
2021-10-19 04:51:04 +00:00
image_format: Option<Format>,
2020-06-07 19:12:19 +00:00
#[structopt(
short,
long,
2021-10-19 04:51:04 +00:00
help = "An optional list of filters to permit, supports 'identity', 'thumbnail', 'resize', 'crop', and 'blur'"
2020-06-07 19:12:19 +00:00
)]
2021-10-28 05:17:37 +00:00
#[serde(skip_serializing_if = "Option::is_none")]
2021-10-19 04:51:04 +00:00
filters: Option<Vec<String>>,
#[structopt(
short,
long,
help = "Specify the maximum allowed uploaded file size (in Megabytes)"
)]
2021-10-28 05:17:37 +00:00
#[serde(skip_serializing_if = "Option::is_none")]
max_file_size: Option<usize>,
2020-07-11 21:28:49 +00:00
#[structopt(long, help = "Specify the maximum width in pixels allowed on an image")]
2021-10-28 05:17:37 +00:00
#[serde(skip_serializing_if = "Option::is_none")]
max_image_width: Option<usize>,
#[structopt(long, help = "Specify the maximum width in pixels allowed on an image")]
2021-10-28 05:17:37 +00:00
#[serde(skip_serializing_if = "Option::is_none")]
max_image_height: Option<usize>,
#[structopt(long, help = "Specify the maximum area in pixels allowed in an image")]
2021-10-28 05:17:37 +00:00
#[serde(skip_serializing_if = "Option::is_none")]
max_image_area: Option<usize>,
2022-03-22 02:18:51 +00:00
#[structopt(
long,
help = "Specify the number of bytes sled is allowed to use for it's cache"
)]
#[serde(skip_serializing_if = "Option::is_none")]
sled_cache_capacity: Option<u64>,
#[cfg(feature = "console")]
#[structopt(
long,
help = "Specify the number of events the console subscriber is allowed to buffer"
)]
#[serde(skip_serializing_if = "Option::is_none")]
console_buffer_capacity: Option<usize>,
2020-07-11 21:28:49 +00:00
#[structopt(
long,
help = "An optional string to be checked on requests to privileged endpoints"
)]
2021-10-28 05:17:37 +00:00
#[serde(skip_serializing_if = "Option::is_none")]
2020-07-11 21:28:49 +00:00
api_key: Option<String>,
2021-09-14 01:22:42 +00:00
2021-09-18 21:29:30 +00:00
#[structopt(
short,
long,
2021-10-19 04:44:56 +00:00
help = "Enable OpenTelemetry Tracing exports to the given OpenTelemetry collector"
2021-09-18 21:29:30 +00:00
)]
2021-10-28 05:17:37 +00:00
#[serde(skip_serializing_if = "Option::is_none")]
2021-09-18 21:29:30 +00:00
opentelemetry_url: Option<Url>,
#[structopt(subcommand)]
2021-10-28 05:17:37 +00:00
#[serde(skip_serializing_if = "Option::is_none")]
store: Option<Store>,
}
2021-10-28 05:17:37 +00:00
impl Overrides {
fn is_default(&self) -> bool {
!self.skip_validate_imports
&& self.addr.is_none()
&& self.path.is_none()
&& self.image_format.is_none()
&& self.filters.is_none()
&& self.max_file_size.is_none()
&& self.max_image_width.is_none()
&& self.max_image_height.is_none()
&& self.max_image_area.is_none()
&& self.sled_cache_capacity.is_none()
&& self.default_console_settings()
2021-10-28 05:17:37 +00:00
&& self.api_key.is_none()
&& self.opentelemetry_url.is_none()
&& self.store.is_none()
}
fn default_console_settings(&self) -> bool {
#[cfg(feature = "console")]
return self.console_buffer_capacity.is_none();
#[cfg(not(feature = "console"))]
true
}
2021-10-28 05:17:37 +00:00
}
2021-11-01 02:11:35 +00:00
#[derive(Clone, Debug, serde::Deserialize, serde::Serialize)]
#[serde(rename_all = "snake_case")]
pub(crate) struct Migrate {
from: Store,
to: Store,
}
impl Migrate {
pub(crate) fn from(&self) -> &Store {
&self.from
}
pub(crate) fn to(&self) -> &Store {
&self.to
}
}
#[derive(Clone, Debug, serde::Deserialize, serde::Serialize, structopt::StructOpt)]
2021-10-28 05:17:37 +00:00
#[serde(rename_all = "snake_case")]
#[serde(tag = "type")]
pub(crate) enum Store {
FileStore {
// defaults to {config.path}
2021-11-01 02:11:35 +00:00
#[structopt(
long,
help = "Path in which pict-rs will create it's 'files' directory"
)]
2021-10-28 05:17:37 +00:00
#[serde(skip_serializing_if = "Option::is_none")]
path: Option<PathBuf>,
},
#[cfg(feature = "object-storage")]
S3Store {
2021-11-01 02:11:35 +00:00
#[structopt(long, help = "Name of the bucket in which pict-rs will store images")]
bucket_name: String,
2021-10-28 05:17:37 +00:00
2021-11-01 02:11:35 +00:00
#[structopt(
long,
help = "Region in which the bucket exists, can be an http endpoint"
)]
region: crate::serde_str::Serde<s3::Region>,
2021-10-28 05:17:37 +00:00
#[serde(skip_serializing_if = "Option::is_none")]
#[structopt(long)]
access_key: Option<String>,
2021-10-28 05:17:37 +00:00
#[structopt(long)]
#[serde(skip_serializing_if = "Option::is_none")]
secret_key: Option<String>,
2021-10-28 05:17:37 +00:00
#[structopt(long)]
#[serde(skip_serializing_if = "Option::is_none")]
security_token: Option<String>,
2021-10-28 05:17:37 +00:00
#[structopt(long)]
#[serde(skip_serializing_if = "Option::is_none")]
session_token: Option<String>,
},
}
#[derive(Clone, Debug, serde::Deserialize, serde::Serialize)]
2021-10-28 05:17:37 +00:00
#[serde(rename_all = "snake_case")]
pub(crate) struct Config {
skip_validate_imports: bool,
addr: SocketAddr,
path: PathBuf,
image_format: Option<Format>,
filters: Option<Vec<String>>,
max_file_size: usize,
max_image_width: usize,
max_image_height: usize,
max_image_area: usize,
sled_cache_capacity: u64,
#[cfg(feature = "console")]
console_buffer_capacity: usize,
api_key: Option<String>,
opentelemetry_url: Option<Url>,
store: Store,
}
#[derive(serde::Serialize)]
2021-10-28 05:17:37 +00:00
#[serde(rename_all = "snake_case")]
pub(crate) struct Defaults {
skip_validate_imports: bool,
addr: SocketAddr,
max_file_size: usize,
max_image_width: usize,
max_image_height: usize,
max_image_area: usize,
sled_cache_capacity: u64,
#[cfg(feature = "console")]
console_buffer_capacity: usize,
store: Store,
}
impl Defaults {
fn new() -> Self {
Defaults {
skip_validate_imports: false,
addr: ([0, 0, 0, 0], 8080).into(),
max_file_size: 40,
max_image_width: 10_000,
max_image_height: 10_000,
2021-10-29 01:59:11 +00:00
max_image_area: 40_000_000,
sled_cache_capacity: 1024 * 1024 * 64, // 16 times smaller than sled's default of 1GB
#[cfg(feature = "console")]
console_buffer_capacity: 1024 * 128,
store: Store::FileStore { path: None },
}
}
2020-06-07 00:54:06 +00:00
}
impl Config {
pub(crate) fn build() -> anyhow::Result<Self> {
let args = Args::from_args();
2021-11-01 02:11:35 +00:00
if let Some(path) = args.migrate_file {
2022-02-26 18:22:30 +00:00
let migrate_config = config::Config::builder()
.add_source(config::File::from(path))
.build()?;
let migrate: Migrate = migrate_config.try_deserialize()?;
2021-11-01 02:11:35 +00:00
crate::MIGRATE.set(migrate).unwrap();
}
2022-02-26 18:22:30 +00:00
let mut base_config =
config::Config::builder().add_source(config::Config::try_from(&Defaults::new())?);
if let Some(path) = args.config_file {
2022-02-26 18:22:30 +00:00
base_config = base_config.add_source(config::File::from(path));
};
2021-10-28 05:17:37 +00:00
if !args.overrides.is_default() {
let merging = config::Config::try_from(&args.overrides)?;
2022-02-26 18:22:30 +00:00
base_config = base_config.add_source(merging);
2021-10-28 05:17:37 +00:00
}
2022-02-26 18:22:30 +00:00
let config: Self = base_config
.add_source(config::Environment::with_prefix("PICTRS").separator("__"))
.build()?
.try_deserialize()?;
2021-10-28 05:17:37 +00:00
Ok(config)
}
pub(crate) fn store(&self) -> &Store {
&self.store
}
2020-06-07 00:54:06 +00:00
pub(crate) fn bind_address(&self) -> SocketAddr {
self.addr
}
pub(crate) fn data_dir(&self) -> PathBuf {
self.path.clone()
}
2020-06-07 01:44:26 +00:00
pub(crate) fn sled_cache_capacity(&self) -> u64 {
self.sled_cache_capacity
}
#[cfg(feature = "console")]
pub(crate) fn console_buffer_capacity(&self) -> usize {
self.console_buffer_capacity
}
2020-06-07 01:44:26 +00:00
pub(crate) fn format(&self) -> Option<Format> {
self.image_format
2020-06-07 01:44:26 +00:00
}
2020-06-07 19:12:19 +00:00
2021-10-19 04:51:04 +00:00
pub(crate) fn allowed_filters(&self) -> Option<HashSet<String>> {
self.filters.as_ref().map(|wl| wl.iter().cloned().collect())
2020-06-07 19:12:19 +00:00
}
pub(crate) fn validate_imports(&self) -> bool {
!self.skip_validate_imports
}
pub(crate) fn max_file_size(&self) -> usize {
self.max_file_size
}
2020-07-11 21:28:49 +00:00
pub(crate) fn max_width(&self) -> usize {
self.max_image_width
}
pub(crate) fn max_height(&self) -> usize {
self.max_image_height
}
pub(crate) fn max_area(&self) -> usize {
self.max_image_area
}
2020-07-11 21:28:49 +00:00
pub(crate) fn api_key(&self) -> Option<&str> {
self.api_key.as_deref()
2020-07-11 21:28:49 +00:00
}
2021-09-14 01:22:42 +00:00
2021-09-18 21:29:30 +00:00
pub(crate) fn opentelemetry_url(&self) -> Option<&Url> {
self.opentelemetry_url.as_ref()
2021-09-14 01:22:42 +00:00
}
2020-06-07 01:44:26 +00:00
}
#[derive(Debug, thiserror::Error)]
#[error("Invalid format supplied, {0}")]
pub(crate) struct FormatError(String);
#[derive(Copy, Clone, Debug, serde::Deserialize, serde::Serialize)]
2021-10-28 05:17:37 +00:00
#[serde(rename_all = "snake_case")]
2020-06-07 01:44:26 +00:00
pub(crate) enum Format {
Jpeg,
Png,
2020-06-16 20:55:24 +00:00
Webp,
2020-06-07 01:44:26 +00:00
}
2020-06-16 21:34:37 +00:00
impl Format {
pub(crate) fn as_magick_format(&self) -> &'static str {
2020-06-16 21:34:37 +00:00
match self {
Format::Jpeg => "JPEG",
Format::Png => "PNG",
Format::Webp => "WEBP",
}
}
2021-10-23 04:48:56 +00:00
pub(crate) fn as_hint(&self) -> Option<ValidInputType> {
2021-10-23 04:48:56 +00:00
match self {
Format::Jpeg => Some(ValidInputType::Jpeg),
Format::Png => Some(ValidInputType::Png),
Format::Webp => Some(ValidInputType::Webp),
}
}
2020-06-16 21:34:37 +00:00
}
2020-06-07 01:44:26 +00:00
impl std::str::FromStr for Format {
type Err = FormatError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s {
"png" => Ok(Format::Png),
"jpg" => Ok(Format::Jpeg),
2020-06-16 20:55:24 +00:00
"webp" => Ok(Format::Webp),
2020-06-07 01:44:26 +00:00
other => Err(FormatError(other.to_string())),
}
}
2020-06-07 00:54:06 +00:00
}