diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index 0900fd9..2694803 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -21,7 +21,7 @@ jobs: run: | cargo fmt --all -- --check cargo clippy --all-targets --all-features -- -A clippy::result_large_err -A clippy::type_complexity -A clippy::too_many_arguments - cargo build --verbose + cargo build --bins --examples --tests --benches --verbose - name: Run tests run: | diff --git a/src/s3/client.rs b/src/s3/client.rs index 1ff76c5..870af58 100644 --- a/src/s3/client.rs +++ b/src/s3/client.rs @@ -40,6 +40,7 @@ use std::collections::{HashMap, VecDeque}; use std::fs::File; use std::io::prelude::*; use std::io::Read; +use std::path::{Path, PathBuf}; use std::sync::Arc; use xmltree::Element; @@ -203,6 +204,92 @@ fn parse_list_objects_common_prefixes( Ok(()) } +/// Client Builder manufactures a Client using given parameters. +#[derive(Debug, Default)] +pub struct ClientBuilder { + base_url: BaseUrl, + provider: Option>>, + ssl_cert_file: Option, + ignore_cert_check: Option, + app_info: Option<(String, String)>, +} + +impl ClientBuilder { + /// Creates a builder given a base URL for the MinIO service or other AWS S3 + /// compatible object storage service. + pub fn new(base_url: BaseUrl) -> Self { + let mut c = ClientBuilder::default(); + c.base_url = base_url; + c + } + + /// Set the credential provider. If not set anonymous access is used. + pub fn provider( + mut self, + provider: Option>, + ) -> Self { + self.provider = provider.map(Arc::new); + self + } + + /// Set the app info as an Option of (app_name, app_version) pair. This will + /// show up in the client's user-agent. + pub fn app_info(mut self, app_info: Option<(String, String)>) -> Self { + self.app_info = app_info; + self + } + + /// Set file for loading a trust certificate. + pub fn ssl_cert_file(mut self, ssl_cert_file: Option<&Path>) -> Self { + self.ssl_cert_file = ssl_cert_file.map(PathBuf::from); + self + } + + /// Set flag to ignore certificate check. + pub fn ignore_cert_check(mut self, ignore_cert_check: Option) -> Self { + self.ignore_cert_check = ignore_cert_check; + self + } + + /// Build the Client. + pub fn build(self) -> Result { + let mut builder = reqwest::Client::builder().no_gzip(); + + let info = os_info::get(); + let mut user_agent = String::from("MinIO (") + + &info.os_type().to_string() + + "; " + + info.architecture().unwrap_or("unknown") + + ") minio-rs/" + + env!("CARGO_PKG_VERSION"); + + if let Some((app_name, app_version)) = self.app_info { + user_agent.push_str(format!(" {app_name}/{app_version}").as_str()); + builder = builder.user_agent(user_agent); + } + + if let Some(v) = self.ignore_cert_check { + builder = builder.danger_accept_invalid_certs(v); + } + + if let Some(v) = self.ssl_cert_file { + let mut buf = Vec::new(); + File::open(v)?.read_to_end(&mut buf)?; + let cert = reqwest::Certificate::from_pem(&buf)?; + builder = builder.add_root_certificate(cert); + } + + let client = builder.build()?; + + Ok(Client { + client, + base_url: self.base_url, + provider: self.provider, + region_map: DashMap::new(), + }) + } +} + /// Simple Storage Service (aka S3) client to perform bucket and object operations. /// /// If credential provider is passed, all S3 operation requests are signed using @@ -235,38 +322,14 @@ impl Client { pub fn new( base_url: BaseUrl, provider: Option>, - ssl_cert_file: Option, + ssl_cert_file: Option<&Path>, ignore_cert_check: Option, ) -> Result { - let info = os_info::get(); - let user_agent = String::from("MinIO (") - + &info.os_type().to_string() - + "; " - + info.architecture().unwrap_or("unknown") - + ") minio-rs/" - + env!("CARGO_PKG_VERSION"); - - let mut builder = reqwest::Client::builder() - .no_gzip() - .user_agent(user_agent.to_string()); - if let Some(v) = ignore_cert_check { - builder = builder.danger_accept_invalid_certs(v); - } - if let Some(v) = ssl_cert_file { - let mut buf = Vec::new(); - File::open(v.to_string())?.read_to_end(&mut buf)?; - let cert = reqwest::Certificate::from_pem(&buf)?; - builder = builder.add_root_certificate(cert); - } - - let client = builder.build()?; - - Ok(Client { - client, - base_url, - provider: provider.map(|v| Arc::new(v)), - region_map: DashMap::new(), - }) + ClientBuilder::new(base_url) + .provider(provider) + .ssl_cert_file(ssl_cert_file) + .ignore_cert_check(ignore_cert_check) + .build() } fn build_headers( diff --git a/tests/tests.rs b/tests/tests.rs index a3a0db2..44ae4d0 100644 --- a/tests/tests.rs +++ b/tests/tests.rs @@ -21,6 +21,7 @@ use rand::distributions::{Alphanumeric, DistString}; use sha2::{Digest, Sha256}; use std::collections::HashMap; use std::io::BufReader; +use std::path::{Path, PathBuf}; use std::{fs, io}; use tokio::sync::mpsc; @@ -78,7 +79,7 @@ struct ClientTest { access_key: String, secret_key: String, ignore_cert_check: Option, - ssl_cert_file: Option, + ssl_cert_file: Option, client: Client, test_bucket: String, } @@ -92,7 +93,7 @@ impl ClientTest { secret_key: String, static_provider: StaticProvider, ignore_cert_check: Option, - ssl_cert_file: Option, + ssl_cert_file: Option<&Path>, ) -> ClientTest { let client = Client::new( base_url.clone(), @@ -107,7 +108,7 @@ impl ClientTest { access_key, secret_key, ignore_cert_check, - ssl_cert_file, + ssl_cert_file: ssl_cert_file.map(PathBuf::from), client, test_bucket: rand_bucket_name(), } @@ -537,10 +538,11 @@ impl ClientTest { let listen_task = move || async move { let static_provider = StaticProvider::new(&access_key, &secret_key, None); + let ssl_cert_file = &ssl_cert_file; let client = Client::new( base_url, Some(Box::new(static_provider)), - ssl_cert_file, + ssl_cert_file.as_deref(), ignore_cert_check, ) .unwrap(); @@ -1150,7 +1152,7 @@ async fn s3_tests() -> Result<(), Box> { let value = std::env::var("SSL_CERT_FILE")?; let mut ssl_cert_file = None; if !value.is_empty() { - ssl_cert_file = Some(value); + ssl_cert_file = Some(Path::new(&value)); } let ignore_cert_check = std::env::var("IGNORE_CERT_CHECK").is_ok(); let region = std::env::var("SERVER_REGION").ok();