Skip to content
Snippets Groups Projects
Commit 934de160 authored by Nick Mathewson's avatar Nick Mathewson :game_die:
Browse files

Merge branch 'arti-config-2' into 'main'

Abolish arti-config, replacing with tombstone crate

See merge request !508
parents f482a5bd 4b86818b
No related branches found
No related tags found
1 merge request!508Abolish arti-config, replacing with tombstone crate
Showing
with 291 additions and 349 deletions
......@@ -75,7 +75,6 @@ version = "0.3.0"
dependencies = [
"anyhow",
"arti-client",
"arti-config",
"async-ctrlc",
"cfg-if 1.0.0",
"clap",
......@@ -110,7 +109,6 @@ dependencies = [
"anyhow",
"arti",
"arti-client",
"arti-config",
"clap",
"float-ord",
"fs-mistrust",
......@@ -120,6 +118,7 @@ dependencies = [
"serde_json",
"tokio",
"tokio-socks",
"tor-config",
"tor-rtcompat",
"tracing",
"tracing-subscriber",
......@@ -164,20 +163,6 @@ dependencies = [
[[package]]
name = "arti-config"
version = "0.3.0"
dependencies = [
"arti-client",
"config",
"derive_builder_fork_arti",
"fs-mistrust",
"once_cell",
"regex",
"serde",
"tempfile",
"thiserror",
"toml",
"tor-circmgr",
"tor-config",
]
[[package]]
name = "arti-hyper"
......@@ -204,7 +189,6 @@ dependencies = [
"anyhow",
"arti",
"arti-client",
"arti-config",
"async-trait",
"cfg-if 1.0.0",
"clap",
......@@ -217,6 +201,7 @@ dependencies = [
"serde",
"tokio",
"tor-checkable",
"tor-config",
"tor-dirmgr",
"tor-error",
"tor-netdoc",
......@@ -3428,6 +3413,7 @@ dependencies = [
"directories",
"dirs",
"educe",
"fs-mistrust",
"once_cell",
"paste",
"regex",
......@@ -3435,6 +3421,7 @@ dependencies = [
"serde",
"serde_json",
"shellexpand-fork",
"tempfile",
"thiserror",
"toml",
"tor-basic-utils",
......
......@@ -22,9 +22,9 @@ serde_json = "1.0.50"
tracing = "0.1.18"
tracing-subscriber = { version = "0.3.0", features = ["env-filter"] }
tokio = { version = "1.7", features = ["full"] }
tor-config = { path = "../tor-config", version = "0.3.0"}
tor-rtcompat = { path = "../tor-rtcompat", version = "0.3.0", features = ["tokio", "native-tls"] }
arti = { path = "../arti", version = "0.3.0"}
arti-config = { path = "../arti-config", version = "0.3.0"}
arti-client = { package = "arti-client", path = "../arti-client", version = "0.3.0"}
fs-mistrust = { path = "../fs-mistrust", version = "0.1.0" }
tokio-socks = "0.5"
......@@ -56,6 +56,7 @@ use std::thread::JoinHandle;
use std::time::SystemTime;
use tokio::io::{AsyncRead, AsyncReadExt, AsyncWrite, AsyncWriteExt};
use tokio_socks::tcp::Socks5Stream;
use tor_config::ConfigurationSources;
use tor_rtcompat::Runtime;
use tracing::info;
......@@ -354,7 +355,7 @@ fn main() -> Result<()> {
)
.get_matches();
info!("Parsing Arti configuration...");
let mut config_sources = arti_config::ConfigurationSources::new_empty();
let mut config_sources = ConfigurationSources::new_empty();
matches
.values_of_os("arti-config")
.unwrap_or_default()
......
......@@ -389,4 +389,13 @@ mod test {
assert_ne!(val, TorClientConfig::default());
}
#[test]
fn check_default() {
// We don't want to second-guess the directories crate too much
// here, so we'll just make sure it does _something_ plausible.
let dflt = default_config_file().unwrap();
assert!(dflt.ends_with("arti.toml"));
}
}
......@@ -10,21 +10,3 @@ description = "High-level configuration for the Arti Tor implementation"
keywords = ["tor", "arti"]
categories = ["config"]
repository = "https://gitlab.torproject.org/tpo/core/arti.git/"
[dependencies]
arti-client = { package = "arti-client", path = "../arti-client", version = "0.3.0"}
tor-circmgr = { package = "tor-circmgr", path = "../tor-circmgr", version = "0.3.0"}
tor-config = { package = "tor-config", path = "../tor-config", version = "0.3.0", features = [
"expand-paths",
] }
fs-mistrust = { path = "../fs-mistrust", version = "0.1.0" }
config = { version = "0.13", default-features = false, features = ["toml"] }
once_cell = "1"
serde = { version = "1.0.103", features = ["derive"] }
toml = "0.5"
regex = { version = "1", default-features = false, features = ["std"] }
thiserror = "1"
derive_builder = { version = "0.11.2", package = "derive_builder_fork_arti" }
[dev-dependencies]
tempfile = "3"
# arti-config
`arti-config`: Tools for configuration management in Arti
`arti-config`: Removed crate. (Tools for configuration management in Arti)
## Overview
This crate is part of
This crate was part of
[Arti](https://gitlab.torproject.org/tpo/core/arti/), a project to
implement [Tor](https://www.torproject.org/) in Rust.
It provides a client configuration tool using `serde` and `config`,
plus extra features defined here for convenience.
## ⚠ Stability Warning ⚠
The design of this crate, and of the configuration system for
Arti, is likely to change significantly before the release of Arti
1.0.0. For more information see ticket [#285].
[#285]: https://gitlab.torproject.org/tpo/core/arti/-/issues/285
License: MIT OR Apache-2.0
The project continues, but this particular crate is now superseded.
This empty crate is published as a tombstone.
//! `arti-config`: Tools for configuration management in Arti
//! `arti-config`: Removed crate. (Tools for configuration management in Arti)
//!
//! # Overview
//!
//! This crate is part of
//! This crate was part of
//! [Arti](https://gitlab.torproject.org/tpo/core/arti/), a project to
//! implement [Tor](https://www.torproject.org/) in Rust.
//!
//! It provides a client configuration tool using `serde` and `config`,
//! plus extra features defined here for convenience.
//!
//! # ⚠ Stability Warning ⚠
//!
//! The design of this crate, and of the configuration system for
//! Arti, is likely to change significantly before the release of Arti
//! 1.0.0. For more information see ticket [#285].
//!
//! [#285]: https://gitlab.torproject.org/tpo/core/arti/-/issues/285
#![deny(missing_docs)]
#![warn(noop_method_call)]
#![deny(unreachable_pub)]
#![warn(clippy::all)]
#![deny(clippy::await_holding_lock)]
#![deny(clippy::cargo_common_metadata)]
#![deny(clippy::cast_lossless)]
#![deny(clippy::checked_conversions)]
#![warn(clippy::cognitive_complexity)]
#![deny(clippy::debug_assert_with_mut_call)]
#![deny(clippy::exhaustive_enums)]
#![deny(clippy::exhaustive_structs)]
#![deny(clippy::expl_impl_clone_on_copy)]
#![deny(clippy::fallible_impl_from)]
#![deny(clippy::implicit_clone)]
#![deny(clippy::large_stack_arrays)]
#![warn(clippy::manual_ok_or)]
#![deny(clippy::missing_docs_in_private_items)]
#![deny(clippy::missing_panics_doc)]
#![warn(clippy::needless_borrow)]
#![warn(clippy::needless_pass_by_value)]
#![warn(clippy::option_option)]
#![warn(clippy::rc_buffer)]
#![deny(clippy::ref_option_ref)]
#![warn(clippy::semicolon_if_nothing_returned)]
#![warn(clippy::trait_duplication_in_bounds)]
#![deny(clippy::unnecessary_wraps)]
#![warn(clippy::unseparated_literal_suffix)]
#![deny(clippy::unwrap_used)]
use tor_config::CmdLine;
use config::ConfigError;
/// The synchronous configuration builder type we use.
///
/// (This is a type alias that config should really provide.)
type ConfigBuilder = config::builder::ConfigBuilder<config::builder::DefaultState>;
use std::path::{Path, PathBuf};
/// A description of where to find our configuration options.
#[derive(Clone, Debug, Default)]
pub struct ConfigurationSources {
/// List of files to read (in order).
files: Vec<(PathBuf, MustRead)>,
/// A list of command-line options to apply after parsing the files.
options: Vec<String>,
/// We will check all files we read
mistrust: fs_mistrust::Mistrust,
}
/// Rules for whether we should proceed if a configuration file is unreadable.
///
/// Some files (like the default configuration file) are okay to skip if they
/// aren't present. Others (like those specified on the command line) really
/// need to be there.
#[derive(Clone, Debug, Copy, Eq, PartialEq)]
enum MustRead {
/// This file is okay to skip if it isn't present,
TolerateAbsence,
/// This file must be present and readable.
MustRead,
}
impl ConfigurationSources {
/// Create a new empty [`ConfigurationSources`].
pub fn new_empty() -> Self {
Self::default()
}
/// Establish a [`ConfigurationSources`] the usual way from a command line and defaults
///
/// The caller should have parsed the program's command line, and extracted (inter alia)
///
/// * `config_files_options`: Paths of config file(s)
/// * `cmdline_toml_override_options`: Overrides ("key=value")
///
/// The caller should also provide `default_config_file`, the default location of the
/// configuration file. This is used if no file(s) are specified on the command line.
///
/// `mistrust` is used to check whether the configuration files have appropriate permissions.
pub fn from_cmdline<F, O>(
default_config_file: impl Into<PathBuf>,
config_files_options: impl IntoIterator<Item = F>,
cmdline_toml_override_options: impl IntoIterator<Item = O>,
) -> Self
where
F: Into<PathBuf>,
O: Into<String>,
{
let mut cfg_sources = ConfigurationSources::new_empty();
let mut any_files = false;
for f in config_files_options {
let f = f.into();
cfg_sources.push_file(f);
any_files = true;
}
if !any_files {
let default = default_config_file.into();
cfg_sources.push_optional_file(default);
}
for s in cmdline_toml_override_options {
cfg_sources.push_option(s);
}
cfg_sources
}
/// Add `p` to the list of files that we want to read configuration from.
///
/// Configuration files are loaded and applied in the order that they are
/// added to this object.
///
/// If the listed file is absent, loading the configuration won't succeed.
pub fn push_file(&mut self, p: impl Into<PathBuf>) {
self.files.push((p.into(), MustRead::MustRead));
}
/// As `push_file`, but if the listed file can't be loaded, loading the
/// configuration can still succeed.
pub fn push_optional_file(&mut self, p: impl Into<PathBuf>) {
self.files.push((p.into(), MustRead::TolerateAbsence));
}
/// Add `s` to the list of overridden options to apply to our configuration.
///
/// Options are applied after all configuration files are loaded, in the
/// order that they are added to this object.
///
/// The format for `s` is as in [`CmdLine`].
pub fn push_option(&mut self, option: impl Into<String>) {
self.options.push(option.into());
}
/// Sets the filesystem permission mistrust
pub fn set_mistrust(&mut self, mistrust: fs_mistrust::Mistrust) {
self.mistrust = mistrust;
}
/// Reads the filesystem permission mistrust
pub fn mistrust(&self) -> &fs_mistrust::Mistrust {
&self.mistrust
}
/// Return an iterator over the files that we care about.
pub fn files(&self) -> impl Iterator<Item = &Path> {
self.files.iter().map(|(f, _)| f.as_path())
}
/// Load the configuration into a new [`config::Config`].
pub fn load(&self) -> Result<config::Config, ConfigError> {
let mut builder = config::Config::builder();
builder = add_sources(builder, &self.mistrust, &self.files, &self.options)?;
builder.build()
}
}
/// Add every file and commandline source to `builder`, returning a new
/// builder.
fn add_sources<P>(
mut builder: ConfigBuilder,
mistrust: &fs_mistrust::Mistrust,
files: &[(P, MustRead)],
opts: &[String],
) -> Result<ConfigBuilder, ConfigError>
where
P: AsRef<Path>,
{
for (path, must_read) in files {
let required = must_read == &MustRead::MustRead;
match mistrust.verifier().require_file().check(&path) {
Ok(()) => {}
Err(fs_mistrust::Error::NotFound(_)) if !required => {}
Err(e) => return Err(ConfigError::Foreign(e.into())),
}
// Not going to use File::with_name here, since it doesn't
// quite do what we want.
let f: config::File<_, _> = path.as_ref().into();
builder = builder.add_source(f.format(config::FileFormat::Toml).required(required));
}
let mut cmdline = CmdLine::new();
for opt in opts {
cmdline.push_toml_line(opt.clone());
}
builder = builder.add_source(cmdline);
Ok(builder)
}
#[cfg(test)]
mod test {
#![allow(clippy::unwrap_used)]
use super::*;
use arti_client::config::default_config_file;
use tempfile::tempdir;
static EX_TOML: &str = "
[hello]
world = \"stuff\"
friends = 4242
";
/// Load from a set of files and option strings, without taking
/// the arti defaults into account.
fn load_nodefaults<P: AsRef<Path>>(
files: &[(P, MustRead)],
opts: &[String],
) -> Result<config::Config, config::ConfigError> {
let mut mistrust = fs_mistrust::Mistrust::new();
mistrust.dangerously_trust_everyone();
add_sources(config::Config::builder(), &mistrust, files, opts)
.unwrap()
.build()
}
#[test]
fn non_required_file() {
let td = tempdir().unwrap();
let dflt = td.path().join("a_file");
let files = vec![(dflt, MustRead::TolerateAbsence)];
load_nodefaults(&files, Default::default()).unwrap();
}
static EX2_TOML: &str = "
[hello]
world = \"nonsense\"
";
#[test]
fn both_required_and_not() {
let td = tempdir().unwrap();
let dflt = td.path().join("a_file");
let cf = td.path().join("other_file");
std::fs::write(&cf, EX2_TOML).unwrap();
let files = vec![(dflt, MustRead::TolerateAbsence), (cf, MustRead::MustRead)];
let c = load_nodefaults(&files, Default::default()).unwrap();
assert!(c.get_string("hello.friends").is_err());
assert_eq!(c.get_string("hello.world").unwrap(), "nonsense".to_string());
}
#[test]
fn load_two_files_with_cmdline() {
let td = tempdir().unwrap();
let cf1 = td.path().join("a_file");
let cf2 = td.path().join("other_file");
std::fs::write(&cf1, EX_TOML).unwrap();
std::fs::write(&cf2, EX2_TOML).unwrap();
let v = vec![(cf1, MustRead::TolerateAbsence), (cf2, MustRead::MustRead)];
let v2 = vec!["other.var=present".to_string()];
let c = load_nodefaults(&v, &v2).unwrap();
assert_eq!(c.get_string("hello.friends").unwrap(), "4242".to_string());
assert_eq!(c.get_string("hello.world").unwrap(), "nonsense".to_string());
assert_eq!(c.get_string("other.var").unwrap(), "present".to_string());
}
#[test]
fn check_default() {
// We don't want to second-guess the directories crate too much
// here, so we'll just make sure it does _something_ plausible.
let dflt = default_config_file().unwrap();
assert!(dflt.ends_with("arti.toml"));
}
}
//! The project continues, but this particular crate is now superseded.
//! This empty crate is published as a tombstone.
......@@ -27,9 +27,9 @@ tor-netdoc = { package = "tor-netdoc", path = "../tor-netdoc", version = "0.3.0"
"dangerous-expose-struct-fields",
] }
tor-checkable = { path = "../tor-checkable", version = "0.3.0", features = ["experimental-api"] }
tor-config = { path = "../tor-config", version = "0.3.0"}
tor-rtcompat = { path = "../tor-rtcompat", version = "0.3.0"}
tor-error = { path = "../tor-error", version = "0.3.0"}
arti-config = { path = "../arti-config", version = "0.3.0"}
anyhow = "1.0.23"
async-trait = "0.1.2"
......
......@@ -8,6 +8,8 @@ use clap::{App, AppSettings, Arg, SubCommand};
use std::str::FromStr;
use std::time::Duration;
use tor_config::ConfigurationSources;
/// Helper: parse an optional string as a number of seconds.
fn int_str_to_secs(s: Option<&str>) -> Result<Option<Duration>> {
match s {
......@@ -122,7 +124,7 @@ pub(crate) fn parse_cmdline() -> Result<Job> {
let config = {
// TODO: this is mostly duplicate code.
let mut cfg_sources = arti_config::ConfigurationSources::new_empty();
let mut cfg_sources = ConfigurationSources::new_empty();
let config_files = matches.values_of_os("config-files").unwrap_or_default();
......
......@@ -88,6 +88,7 @@ use arti::ArtiConfig;
use arti_client::TorClient;
use futures::task::SpawnExt;
use rt::badtcp::BrokenTcpProvider;
use tor_config::ConfigurationSources;
use tor_dirmgr::filter::DirFilter;
use tor_rtcompat::{PreferredRuntime, Runtime, SleepProviderExt};
......@@ -209,7 +210,7 @@ struct Job {
console_log: String,
/// Where we're getting our configuration from.
config: arti_config::ConfigurationSources,
config: ConfigurationSources,
/// What we expect to happen.
expectation: Option<Expectation>,
......
......@@ -30,7 +30,6 @@ tor-config = { path = "../tor-config", version = "0.3.0"}
tor-error = { path = "../tor-error", version = "0.3.0", default-features = false }
tor-rtcompat = { path = "../tor-rtcompat", version = "0.3.0", default-features = false }
tor-socksproto = { path = "../tor-socksproto", version = "0.3.0"}
arti-config = { path = "../arti-config", version = "0.3.0"}
anyhow = "1.0.23"
async-ctrlc = { version = "1.2.0", optional = true }
......
......@@ -133,6 +133,7 @@ pub use logging::{LoggingConfig, LoggingConfigBuilder};
use arti_client::config::default_config_file;
use arti_client::{TorClient, TorClientConfig};
use safelog::with_safe_logging_suppressed;
use tor_config::ConfigurationSources;
use tor_rtcompat::{BlockOn, Runtime};
use anyhow::{Context, Result};
......@@ -184,7 +185,7 @@ pub async fn run<R: Runtime>(
runtime: R,
socks_port: u16,
dns_port: u16,
config_sources: arti_config::ConfigurationSources,
config_sources: ConfigurationSources,
arti_config: ArtiConfig,
client_config: TorClientConfig,
fs_mistrust_disabled: bool,
......@@ -374,7 +375,7 @@ pub fn main_main() -> Result<()> {
};
let cfg_sources = {
let mut cfg_sources = arti_config::ConfigurationSources::from_cmdline(
let mut cfg_sources = ConfigurationSources::from_cmdline(
default_config_file()?,
matches.values_of_os("config-files").unwrap_or_default(),
matches.values_of("option").unwrap_or_default(),
......
......@@ -8,6 +8,7 @@ use std::time::Duration;
use arti_client::config::Reconfigure;
use arti_client::TorClient;
use notify::Watcher;
use tor_config::ConfigurationSources;
use tor_rtcompat::Runtime;
use tracing::{debug, info, warn};
......@@ -21,7 +22,7 @@ const POLL_INTERVAL: Duration = Duration::from_secs(10);
/// Whenever one or more files in `files` changes, try to reload our
/// configuration from them and tell TorClient about it.
pub fn watch_for_config_changes<R: Runtime>(
sources: arti_config::ConfigurationSources,
sources: ConfigurationSources,
original: ArtiConfig,
client: TorClient<R>,
) -> anyhow::Result<()> {
......@@ -79,7 +80,7 @@ pub fn watch_for_config_changes<R: Runtime>(
///
/// Return true if we should stop watching for configuration changes.
fn reconfigure<R: Runtime>(
sources: &arti_config::ConfigurationSources,
sources: &ConfigurationSources,
original: &ArtiConfig,
client: &TorClient<R>,
) -> anyhow::Result<bool> {
......
......@@ -23,6 +23,7 @@ thiserror = "1"
config = { version = "0.13", default-features = false, features = ["toml"] }
derive_builder = { version = "0.11.2", package = "derive_builder_fork_arti" }
educe = "0.4.6"
fs-mistrust = { path = "../fs-mistrust", version = "0.1.0" }
once_cell = "1"
paste = "1"
regex = { version = "1", default-features = false, features = ["std"] }
......@@ -37,5 +38,6 @@ config = { version = "0.13", default-features = false, features = ["toml"] }
dirs = "4.0.0"
rmp-serde = "1"
serde_json = "1.0.50"
tempfile = "3"
toml = "0.5"
tracing-test = "0.2"
......@@ -51,6 +51,7 @@ mod err;
pub mod list_builder;
mod mut_cfg;
mod path;
pub mod sources;
pub use cmdline::CmdLine;
pub use config as config_crate;
......@@ -60,6 +61,7 @@ pub use mut_cfg::MutCfg;
pub use paste::paste;
pub use path::{CfgPath, CfgPathError};
pub use serde;
pub use sources::ConfigurationSources;
pub use tor_basic_utils::macro_first_nonempty;
......
//! `ConfigurationSources`: Helper for handling configuration files
//!
//! This module provides [`ConfigurationSources`].
//!
//! This layer brings together the functionality of [`config::File`],
//! [`fs_mistrust`] and [`tor_config::cmdline`](crate::cmdline).
//!
//! A `ConfigurationSources` records a set of filenames of TOML files,
//! ancillary instructions for reading them,
//! and also a set of command line options.
//!
//! Usually, call [`ConfigurationSources::from_cmdline`],
//! perhaps [`set_mistrust`](ConfigurationSources::set_mistrust),
//! and finally [`load`](ConfigurationSources::load).
//! The resulting [`config::Config`] can then be deserialized.
use crate::CmdLine;
use config::ConfigError;
/// The synchronous configuration builder type we use.
///
/// (This is a type alias that config should really provide.)
type ConfigBuilder = config::builder::ConfigBuilder<config::builder::DefaultState>;
use std::path::{Path, PathBuf};
/// A description of where to find our configuration options.
#[derive(Clone, Debug, Default)]
pub struct ConfigurationSources {
/// List of files to read (in order).
files: Vec<(PathBuf, MustRead)>,
/// A list of command-line options to apply after parsing the files.
options: Vec<String>,
/// We will check all files we read
mistrust: fs_mistrust::Mistrust,
}
/// Rules for whether we should proceed if a configuration file is unreadable.
///
/// Some files (like the default configuration file) are okay to skip if they
/// aren't present. Others (like those specified on the command line) really
/// need to be there.
#[derive(Clone, Debug, Copy, Eq, PartialEq)]
enum MustRead {
/// This file is okay to skip if it isn't present,
TolerateAbsence,
/// This file must be present and readable.
MustRead,
}
impl ConfigurationSources {
/// Create a new empty [`ConfigurationSources`].
pub fn new_empty() -> Self {
Self::default()
}
/// Establish a [`ConfigurationSources`] the usual way from a command line and defaults
///
/// The caller should have parsed the program's command line, and extracted (inter alia)
///
/// * `config_files_options`: Paths of config file(s)
/// * `cmdline_toml_override_options`: Overrides ("key=value")
///
/// The caller should also provide `default_config_file`, the default location of the
/// configuration file. This is used if no file(s) are specified on the command line.
///
/// `mistrust` is used to check whether the configuration files have appropriate permissions.
pub fn from_cmdline<F, O>(
default_config_file: impl Into<PathBuf>,
config_files_options: impl IntoIterator<Item = F>,
cmdline_toml_override_options: impl IntoIterator<Item = O>,
) -> Self
where
F: Into<PathBuf>,
O: Into<String>,
{
let mut cfg_sources = ConfigurationSources::new_empty();
let mut any_files = false;
for f in config_files_options {
let f = f.into();
cfg_sources.push_file(f);
any_files = true;
}
if !any_files {
let default = default_config_file.into();
cfg_sources.push_optional_file(default);
}
for s in cmdline_toml_override_options {
cfg_sources.push_option(s);
}
cfg_sources
}
/// Add `p` to the list of files that we want to read configuration from.
///
/// Configuration files are loaded and applied in the order that they are
/// added to this object.
///
/// If the listed file is absent, loading the configuration won't succeed.
pub fn push_file(&mut self, p: impl Into<PathBuf>) {
self.files.push((p.into(), MustRead::MustRead));
}
/// As `push_file`, but if the listed file can't be loaded, loading the
/// configuration can still succeed.
pub fn push_optional_file(&mut self, p: impl Into<PathBuf>) {
self.files.push((p.into(), MustRead::TolerateAbsence));
}
/// Add `s` to the list of overridden options to apply to our configuration.
///
/// Options are applied after all configuration files are loaded, in the
/// order that they are added to this object.
///
/// The format for `s` is as in [`CmdLine`].
pub fn push_option(&mut self, option: impl Into<String>) {
self.options.push(option.into());
}
/// Sets the filesystem permission mistrust
pub fn set_mistrust(&mut self, mistrust: fs_mistrust::Mistrust) {
self.mistrust = mistrust;
}
/// Reads the filesystem permission mistrust
pub fn mistrust(&self) -> &fs_mistrust::Mistrust {
&self.mistrust
}
/// Return an iterator over the files that we care about.
pub fn files(&self) -> impl Iterator<Item = &Path> {
self.files.iter().map(|(f, _)| f.as_path())
}
/// Load the configuration into a new [`config::Config`].
pub fn load(&self) -> Result<config::Config, ConfigError> {
let mut builder = config::Config::builder();
builder = add_sources(builder, &self.mistrust, &self.files, &self.options)?;
builder.build()
}
}
/// Add every file and commandline source to `builder`, returning a new
/// builder.
fn add_sources<P>(
mut builder: ConfigBuilder,
mistrust: &fs_mistrust::Mistrust,
files: &[(P, MustRead)],
opts: &[String],
) -> Result<ConfigBuilder, ConfigError>
where
P: AsRef<Path>,
{
for (path, must_read) in files {
let required = must_read == &MustRead::MustRead;
match mistrust.verifier().require_file().check(&path) {
Ok(()) => {}
Err(fs_mistrust::Error::NotFound(_)) if !required => {}
Err(e) => return Err(ConfigError::Foreign(e.into())),
}
// Not going to use File::with_name here, since it doesn't
// quite do what we want.
let f: config::File<_, _> = path.as_ref().into();
builder = builder.add_source(f.format(config::FileFormat::Toml).required(required));
}
let mut cmdline = CmdLine::new();
for opt in opts {
cmdline.push_toml_line(opt.clone());
}
builder = builder.add_source(cmdline);
Ok(builder)
}
#[cfg(test)]
mod test {
#![allow(clippy::unwrap_used)]
use super::*;
use tempfile::tempdir;
static EX_TOML: &str = "
[hello]
world = \"stuff\"
friends = 4242
";
/// Load from a set of files and option strings, without taking
/// the arti defaults into account.
fn load_nodefaults<P: AsRef<Path>>(
files: &[(P, MustRead)],
opts: &[String],
) -> Result<config::Config, config::ConfigError> {
let mut mistrust = fs_mistrust::Mistrust::new();
mistrust.dangerously_trust_everyone();
add_sources(config::Config::builder(), &mistrust, files, opts)
.unwrap()
.build()
}
#[test]
fn non_required_file() {
let td = tempdir().unwrap();
let dflt = td.path().join("a_file");
let files = vec![(dflt, MustRead::TolerateAbsence)];
load_nodefaults(&files, Default::default()).unwrap();
}
static EX2_TOML: &str = "
[hello]
world = \"nonsense\"
";
#[test]
fn both_required_and_not() {
let td = tempdir().unwrap();
let dflt = td.path().join("a_file");
let cf = td.path().join("other_file");
std::fs::write(&cf, EX2_TOML).unwrap();
let files = vec![(dflt, MustRead::TolerateAbsence), (cf, MustRead::MustRead)];
let c = load_nodefaults(&files, Default::default()).unwrap();
assert!(c.get_string("hello.friends").is_err());
assert_eq!(c.get_string("hello.world").unwrap(), "nonsense".to_string());
}
#[test]
fn load_two_files_with_cmdline() {
let td = tempdir().unwrap();
let cf1 = td.path().join("a_file");
let cf2 = td.path().join("other_file");
std::fs::write(&cf1, EX_TOML).unwrap();
std::fs::write(&cf2, EX2_TOML).unwrap();
let v = vec![(cf1, MustRead::TolerateAbsence), (cf2, MustRead::MustRead)];
let v2 = vec!["other.var=present".to_string()];
let c = load_nodefaults(&v, &v2).unwrap();
assert_eq!(c.get_string("hello.friends").unwrap(), "4242".to_string());
assert_eq!(c.get_string("hello.world").unwrap(), "nonsense".to_string());
assert_eq!(c.get_string("other.var").unwrap(), "present".to_string());
}
}
......@@ -55,3 +55,5 @@ BREAKING: default_config_file moved to arti_client, and changed to return Result
GREAKING: ConfigurationSource::new_empty renamed from ::new
BREAKING: ConfigurationSource methods take Into<String> and Into<PathBuf> now
BREAKING: ARTI_DEFAULTS removed, in favour of ARTI_EXAMPLE_CONFIG in the arti crate
BREAKING: ConfigurationSource is now in the tor-config crate.
DEPRECATION: arti-config is to be abolished. Currently it is merely an empty tombstone.
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment