Improve configuration documentation

This commit is contained in:
Aode (lion) 2021-10-28 00:17:37 -05:00
parent f9f4fc63d6
commit f596edc363
4 changed files with 209 additions and 62 deletions

View File

@ -9,10 +9,10 @@ _a simple image hosting service_
## Usage ## Usage
### Running ### Running
``` ```
pict-rs 0.3.0-alpha.39 pict-rs 0.3.0-alpha.42
USAGE: USAGE:
pict-rs [FLAGS] [OPTIONS] --path <path> pict-rs [FLAGS] [OPTIONS] [SUBCOMMAND]
FLAGS: FLAGS:
-h, --help Prints help information -h, --help Prints help information
@ -20,33 +20,65 @@ FLAGS:
-V, --version Prints version information -V, --version Prints version information
OPTIONS: OPTIONS:
-a, --addr <addr> -a, --addr <addr> The address and port the server binds to.
The address and port the server binds to. [env: PICTRS_ADDR=] [default: 0.0.0.0:8080] --api-key <api-key> An optional string to be checked on requests to privileged endpoints
-c, --config-file <config-file> Path to the pict-rs configuration file
--api-key <api-key>
An optional string to be checked on requests to privileged endpoints [env: PICTRS_API_KEY=]
-f, --filters <filters>... -f, --filters <filters>...
An optional list of filters to permit, supports 'identity', 'thumbnail', 'resize', 'crop', and 'blur' [env: An optional list of filters to permit, supports 'identity', 'thumbnail', 'resize', 'crop', and 'blur'
PICTRS_ALLOWED_FILTERS=]
-i, --image-format <image-format> -i, --image-format <image-format>
An optional image format to convert all uploaded files into, supports 'jpg', 'png', and 'webp' [env: An optional image format to convert all uploaded files into, supports 'jpg', 'png', and 'webp'
PICTRS_FORMAT=]
-m, --max-file-size <max-file-size>
Specify the maximum allowed uploaded file size (in Megabytes) [env: PICTRS_MAX_FILE_SIZE=] [default: 40]
--max-image-height <max-image-height>
Specify the maximum width in pixels allowed on an image [env: PICTRS_MAX_IMAGE_HEIGHT=] [default: 10000]
--max-image-width <max-image-width>
Specify the maximum width in pixels allowed on an image [env: PICTRS_MAX_IMAGE_WIDTH=] [default: 10000]
-m, --max-file-size <max-file-size> Specify the maximum allowed uploaded file size (in Megabytes)
--max-image-area <max-image-area> Specify the maximum area in pixels allowed in an image
--max-image-height <max-image-height> Specify the maximum width in pixels allowed on an image
--max-image-width <max-image-width> Specify the maximum width in pixels allowed on an image
-o, --opentelemetry-url <opentelemetry-url> -o, --opentelemetry-url <opentelemetry-url>
Enable OpenTelemetry Tracing exports to the given OpenTelemetry collector [env: PICTRS_OPENTELEMETRY_URL=] Enable OpenTelemetry Tracing exports to the given OpenTelemetry collector
-p, --path <path> The path to the data directory, e.g. data/ [env: PICTRS_PATH=] -p, --path <path> The path to the data directory, e.g. data/
SUBCOMMANDS:
file-store
help Prints this message or the help of the given subcommand(s)
s3-store
``` ```
```
pict-rs-file-store 0.3.0-alpha.42
USAGE:
pict-rs file-store [OPTIONS]
FLAGS:
-h, --help Prints help information
-V, --version Prints version information
OPTIONS:
--path <path>
```
```
pict-rs-s3-store 0.3.0-alpha.42
USAGE:
pict-rs s3-store [OPTIONS] --bucket-name <bucket-name> --region <region>
FLAGS:
-h, --help Prints help information
-V, --version Prints version information
OPTIONS:
--access-key <access-key>
--bucket-name <bucket-name>
--region <region>
--secret-key <secret-key>
--security-token <security-token>
--session-token <session-token>
```
See [`pict-rs.toml`](https://git.asonix.dog/asonix/pict-rs/src/branch/main/pict-rs.toml) for more configuration
#### Example: #### Example:
Running on all interfaces, port 8080, storing data in /opt/data Running on all interfaces, port 8080, storing data in /opt/data
``` ```
@ -60,6 +92,10 @@ Running locally, port 8080, storing data in data/, and only allowing the `thumbn
``` ```
$ ./pict-rs -a 127.0.0.1:8080 -p data/ -w thumbnail identity $ ./pict-rs -a 127.0.0.1:8080 -p data/ -w thumbnail identity
``` ```
Running from a configuration file
```
$ ./pict-rs -c ./pict-rs.toml
```
#### Docker #### Docker
Run the following commands: Run the following commands:

View File

@ -1,65 +1,116 @@
## Required: path to store pict-rs database ## Required: path to store pict-rs database
# environment variable: PICTRS_PATH
path = './data' path = './data'
## Optional: pict-rs binding address ## Optional: pict-rs binding address
# environment variable: PICTRS_ADDR
# default: 0.0.0.0:8080 # default: 0.0.0.0:8080
addr = '0.0.0.0:8080' addr = '0.0.0.0:8080'
## Optional: format to transcode all uploaded images ## Optional: format to transcode all uploaded images
# environment variable: PICTRS_IMAGE_FORMAT
# valid options: 'jpeg', 'png', 'webp' # valid options: 'jpeg', 'png', 'webp'
# default: empty # default: empty
# #
# Not specifying image-format means images will be stored in their original format # Not specifying image_format means images will be stored in their original format
# This does not affect gif or mp4 uploads # This does not affect gif or mp4 uploads
image-format = 'jpeg' image_format = 'jpeg'
## Optional: permitted image processing filters ## Optional: permitted image processing filters
# environment variable: PICTRS_FILTERS
# valid options: 'identity', 'thumbnail', 'resize', 'crop', 'blur' # valid options: 'identity', 'thumbnail', 'resize', 'crop', 'blur'
# default: empty # default: empty
# #
# Not specifying filters implies all filters are permitted # Not specifying filters implies all filters are permitted
filters = [ filters = [
'identity', 'identity',
'thumbnail', 'thumbnail',
'resize', 'resize',
'crop', 'crop',
'blur', 'blur',
] ]
## Optional: image bounds ## Optional: image bounds
# environment variable: PICTRS_MAX_FILE_SIZE
# default: 40 # default: 40
max-file-size = 40 # in Megabytes max_file_size = 40 # in Megabytes
# environment variable: PICTRS_MAX_IMAGE_WIDTH
# default: 10,000 # default: 10,000
max-image-width = 10000 # in Pixels max_image_width = 10000 # in Pixels
# environment variable: PICTRS_MAX_IMAGE_HEIGHT
# default: 10,000 # default: 10,000
max-image-height = 10000 # in Pixels max_image_height = 10000 # in Pixels
# environment variable: PICTRS_MAX_IMAGE_AREA
# default: 40,000 # default: 40,000
max-image-area = 40000 # in Pixels max_image_area = 40000 # in Pixels
## Optional: ## Optional: skip image validation on the import endpoint
# environment variable: PICTRS_SKIP_VALIDATE_IMPORTS
# default: false # default: false
skip-validate-imports = false skip_validate_imports = false
## Optional: url for exporting otlp traces ## Optional: shared secret for internal endpoints
# environment variable: PICTRS_API_KEY
# default: empty # default: empty
# #
# Not specifying opentelemetry-url means no traces will be exported # Not specifying api_key disables internal endpoints
opentelemetry-url = 'http://localhost:4317/' api_key = 'API_KEY'
## Optional: url for exporting otlp traces
# environment variable: PICTRS_OPENTELEMETRY_URL
# default: empty
#
# Not specifying opentelemetry_url means no traces will be exported
opentelemetry_url = 'http://localhost:4317/'
## Optional: store definition ## Optional: store definition
# default: empty # default store: file_store
#
# Not specifying a store will default to using a file-store in the same directory provided by the `path` field defined above
[store] [store]
type = 'file-store' type = "file_store"
path = './data'
## Example file store
# [store]
#
# # environment variable: PICTRS_STORE__TYPE
# type = 'file_store'
#
# # Optional: file path
# # environment variable: PICTRS_STORE__PATH
# # default: empty
# #
# # Not specifying path means pict-rs' top-level `path` config is used
# path = './data'
## Example s3 store ## Example s3 store
# [store] # [store]
# type = 's3-store' #
# bucket-name = 'rust-s3' # # environment variable: PICTRS_STORE__TYPE
# type = 's3_store'
#
# # Required: bucket name
# # environment variable: PICTRS_STORE__BUCKET_NAME
# bucket_name = 'rust_s3'
#
# # Required: bucket region
# # environment variable: PICTRS_STORE__REGION
# region = 'eu-central-1' # can also be endpoint of local s3 store # region = 'eu-central-1' # can also be endpoint of local s3 store
# access-key = 'ACCESS_KEY' #
# secret-key = 'SECRET_KEY' # # Optional: bucket access key
# security-token = 'SECURITY_TOKEN' # # environment variable: PICTRS_STORE__ACCESS_KEY
# session-token = 'SESSION-TOKEN' # # default: empty
# access_key = 'ACCESS_KEY'
#
# # Optional: bucket secret key
# # environment variable: PICTRS_STORE__SECRET_KEY
# # default: empty
# secret_key = 'SECRET_KEY'
#
# # Optional: bucket security token
# # environment variable: PICTRS_STORE__SECURITY_TOKEN
# # default: empty
# security_token = 'SECURITY_TOKEN'
#
# # Optional: bucket session token
# # environment variable: PICTRS_STORE__SESSION_TOKEN
# # default: empty
# session_token = 'SESSION_TOKEN'

View File

@ -13,20 +13,27 @@ pub(crate) struct Args {
overrides: Overrides, overrides: Overrides,
} }
fn is_false(b: &bool) -> bool {
!b
}
#[derive(Clone, Debug, serde::Serialize, structopt::StructOpt)] #[derive(Clone, Debug, serde::Serialize, structopt::StructOpt)]
#[serde(rename_all = "kebab-case")] #[serde(rename_all = "snake_case")]
pub(crate) struct Overrides { pub(crate) struct Overrides {
#[structopt( #[structopt(
short, short,
long, long,
help = "Whether to skip validating images uploaded via the internal import API" help = "Whether to skip validating images uploaded via the internal import API"
)] )]
#[serde(skip_serializing_if = "is_false")]
skip_validate_imports: bool, skip_validate_imports: bool,
#[structopt(short, long, help = "The address and port the server binds to.")] #[structopt(short, long, help = "The address and port the server binds to.")]
#[serde(skip_serializing_if = "Option::is_none")]
addr: Option<SocketAddr>, addr: Option<SocketAddr>,
#[structopt(short, long, help = "The path to the data directory, e.g. data/")] #[structopt(short, long, help = "The path to the data directory, e.g. data/")]
#[serde(skip_serializing_if = "Option::is_none")]
path: Option<PathBuf>, path: Option<PathBuf>,
#[structopt( #[structopt(
@ -34,6 +41,7 @@ pub(crate) struct Overrides {
long, long,
help = "An optional image format to convert all uploaded files into, supports 'jpg', 'png', and 'webp'" help = "An optional image format to convert all uploaded files into, supports 'jpg', 'png', and 'webp'"
)] )]
#[serde(skip_serializing_if = "Option::is_none")]
image_format: Option<Format>, image_format: Option<Format>,
#[structopt( #[structopt(
@ -41,6 +49,7 @@ pub(crate) struct Overrides {
long, long,
help = "An optional list of filters to permit, supports 'identity', 'thumbnail', 'resize', 'crop', and 'blur'" help = "An optional list of filters to permit, supports 'identity', 'thumbnail', 'resize', 'crop', and 'blur'"
)] )]
#[serde(skip_serializing_if = "Option::is_none")]
filters: Option<Vec<String>>, filters: Option<Vec<String>>,
#[structopt( #[structopt(
@ -48,21 +57,26 @@ pub(crate) struct Overrides {
long, long,
help = "Specify the maximum allowed uploaded file size (in Megabytes)" help = "Specify the maximum allowed uploaded file size (in Megabytes)"
)] )]
#[serde(skip_serializing_if = "Option::is_none")]
max_file_size: Option<usize>, max_file_size: Option<usize>,
#[structopt(long, help = "Specify the maximum width in pixels allowed on an image")] #[structopt(long, help = "Specify the maximum width in pixels allowed on an image")]
#[serde(skip_serializing_if = "Option::is_none")]
max_image_width: Option<usize>, max_image_width: Option<usize>,
#[structopt(long, help = "Specify the maximum width in pixels allowed on an image")] #[structopt(long, help = "Specify the maximum width in pixels allowed on an image")]
#[serde(skip_serializing_if = "Option::is_none")]
max_image_height: Option<usize>, max_image_height: Option<usize>,
#[structopt(long, help = "Specify the maximum area in pixels allowed in an image")] #[structopt(long, help = "Specify the maximum area in pixels allowed in an image")]
#[serde(skip_serializing_if = "Option::is_none")]
max_image_area: Option<usize>, max_image_area: Option<usize>,
#[structopt( #[structopt(
long, long,
help = "An optional string to be checked on requests to privileged endpoints" help = "An optional string to be checked on requests to privileged endpoints"
)] )]
#[serde(skip_serializing_if = "Option::is_none")]
api_key: Option<String>, api_key: Option<String>,
#[structopt( #[structopt(
@ -70,33 +84,69 @@ pub(crate) struct Overrides {
long, long,
help = "Enable OpenTelemetry Tracing exports to the given OpenTelemetry collector" help = "Enable OpenTelemetry Tracing exports to the given OpenTelemetry collector"
)] )]
#[serde(skip_serializing_if = "Option::is_none")]
opentelemetry_url: Option<Url>, opentelemetry_url: Option<Url>,
#[structopt(subcommand)] #[structopt(subcommand)]
#[serde(skip_serializing_if = "Option::is_none")]
store: Option<Store>, store: Option<Store>,
} }
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.api_key.is_none()
&& self.opentelemetry_url.is_none()
&& self.store.is_none()
}
}
#[derive(Clone, Debug, serde::Deserialize, serde::Serialize, structopt::StructOpt)] #[derive(Clone, Debug, serde::Deserialize, serde::Serialize, structopt::StructOpt)]
#[serde(rename_all = "kebab-case")] #[serde(rename_all = "snake_case")]
#[serde(tag = "type")] #[serde(tag = "type")]
pub(crate) enum Store { pub(crate) enum Store {
FileStore { FileStore {
// defaults to {config.path} // defaults to {config.path}
#[structopt(long)]
#[serde(skip_serializing_if = "Option::is_none")]
path: Option<PathBuf>, path: Option<PathBuf>,
}, },
#[cfg(feature = "object-storage")] #[cfg(feature = "object-storage")]
S3Store { S3Store {
#[structopt(long)]
bucket_name: String, bucket_name: String,
#[structopt(long)]
region: crate::serde_str::Serde<s3::Region>, region: crate::serde_str::Serde<s3::Region>,
#[serde(skip_serializing_if = "Option::is_none")]
#[structopt(long)]
access_key: Option<String>, access_key: Option<String>,
#[structopt(long)]
#[serde(skip_serializing_if = "Option::is_none")]
secret_key: Option<String>, secret_key: Option<String>,
#[structopt(long)]
#[serde(skip_serializing_if = "Option::is_none")]
security_token: Option<String>, security_token: Option<String>,
#[structopt(long)]
#[serde(skip_serializing_if = "Option::is_none")]
session_token: Option<String>, session_token: Option<String>,
}, },
} }
#[derive(Clone, Debug, serde::Deserialize, serde::Serialize)] #[derive(Clone, Debug, serde::Deserialize, serde::Serialize)]
#[serde(rename_all = "kebab-case")] #[serde(rename_all = "snake_case")]
pub(crate) struct Config { pub(crate) struct Config {
skip_validate_imports: bool, skip_validate_imports: bool,
addr: SocketAddr, addr: SocketAddr,
@ -113,7 +163,7 @@ pub(crate) struct Config {
} }
#[derive(serde::Serialize)] #[derive(serde::Serialize)]
#[serde(rename_all = "kebab-case")] #[serde(rename_all = "snake_case")]
pub(crate) struct Defaults { pub(crate) struct Defaults {
skip_validate_imports: bool, skip_validate_imports: bool,
addr: SocketAddr, addr: SocketAddr,
@ -141,17 +191,25 @@ impl Defaults {
impl Config { impl Config {
pub(crate) fn build() -> anyhow::Result<Self> { pub(crate) fn build() -> anyhow::Result<Self> {
let args = Args::from_args(); let args = Args::from_args();
let mut base_config = config::Config::try_from(&Defaults::new())?; let mut base_config = config::Config::new();
base_config.merge(config::Config::try_from(&Defaults::new())?)?;
if let Some(path) = args.config_file { if let Some(path) = args.config_file {
base_config.merge(config::File::from(path))?; base_config.merge(config::File::from(path))?;
}; };
base_config.merge(config::Config::try_from(&args.overrides)?)?; if !args.overrides.is_default() {
let merging = config::Config::try_from(&args.overrides)?;
base_config.merge(config::Environment::with_prefix("PICTRS"))?; base_config.merge(merging)?;
}
Ok(base_config.try_into()?) base_config.merge(config::Environment::with_prefix("PICTRS").separator("__"))?;
let config: Self = base_config.try_into()?;
println!("{:#?}", config);
Ok(config)
} }
pub(crate) fn store(&self) -> &Store { pub(crate) fn store(&self) -> &Store {
@ -208,7 +266,7 @@ impl Config {
pub(crate) struct FormatError(String); pub(crate) struct FormatError(String);
#[derive(Copy, Clone, Debug, serde::Deserialize, serde::Serialize)] #[derive(Copy, Clone, Debug, serde::Deserialize, serde::Serialize)]
#[serde(rename_all = "kebab-case")] #[serde(rename_all = "snake_case")]
pub(crate) enum Format { pub(crate) enum Format {
Jpeg, Jpeg,
Png, Png,

View File

@ -220,10 +220,12 @@ where
fn call(&self, req: ServiceRequest) -> Self::Future { fn call(&self, req: ServiceRequest) -> Self::Future {
if let Some(value) = req.headers().get("x-api-token") { if let Some(value) = req.headers().get("x-api-token") {
if value.to_str().is_ok() && value.to_str().ok() == self.0.as_deref() { if let (Ok(header), Some(api_key)) = (value.to_str(), &self.0) {
return InternalFuture::Internal { if header == api_key {
future: self.1.call(req), return InternalFuture::Internal {
}; future: self.1.call(req),
};
}
} }
} }