diff options
author | Michael Neumann <mneumann@ntecs.de> | 2021-04-13 15:04:30 +0200 |
---|---|---|
committer | GitHub <noreply@github.com> | 2021-04-13 15:04:30 +0200 |
commit | 697eacb6e9e851cd856b9eb792bbca6e400dd331 (patch) | |
tree | 37c2b7e236eff8519a8d66ff1879ef2e9f836003 /tedge_config | |
parent | e69dea81e85ec977c95339646aac3585c47a5a5f (diff) |
[CIT-304] Migrate tedge config to use the new tedge_config crate (#168)
- Disentangle TEdgeConfig from `tedge config` CLI
- Disentangle `tedge config` commands from CLI parsing
- Do all the parsing in `config/cli.rs`.
- Commands are defined in `config/commands/*.rs`. One (sub-)command per file.
- The implementation of the commands become easier to read
and understand, and you can test each command independently.
- Do not list valid keys when running `tedge config [get|set|unset] --help`
- When running `tedge config get --help`, the output is now:
ARGS:
<key> Configuration key. Run `tedge config list --doc` for available keys
- Prior to this commit, a list of keys was shown after the `<key>`, but
formatting was pretty ugly.
- The list of valid keys was generated by the macro. Get rid of it.
IMHO this was a mistake in the first place.
- Instead, point the user at running `tedge config list --doc` for a list
of valid keys. TODO: Currently the `tedge config list --doc` output
does not mention whether a given key is read-only or not.
- If we want the previous behaviour, it's better to just maintain the
list of valid keys in the structopt comment.
- tedge_config - Fix KEY's of some ConfigSettings
These are the keys that are used in tedge/tests/main.rs.
- Use crate tedge_config in `tedge config` CLI
- Add `ConfigSettingAccessorStringExt` trait to `tedge_config`. We need
that in order to get/set keys as strings. It provides a
`query_string`, `query_string_optional` and `update_string` methods.
- Provide a default implementation for `ConfigSettingAccessorStringExt`
for all ConfigSettings on `TEdgeConfig`.
- Introduce newtype `FilePath` and get rid of `PathBuf` in `TEdgeConfig`
and `TEdgeConfigDto`. We need this because `PathBuf` does not
implement `TryInto<String>` which we need for the default
implementation mentioned in the previous step.
- Make `TEdgeConfig` clonable. This is required when we update or unset
a config setting in cli/config/commands/{set,unset}.rs. In the command
we receive a `config: TEdgeConfig`, but as we are accessing it through
the `&self` in `execute`, we cannot modify it. To get a mutable
version of `config`, simply clone it.
- For those `tedge config` commands that need to write a configuration
back, inject a `TEdgeConfigRepository`. TODO: We should already inject
the `TEdgeConfigRepository` in `build_command`.
- Get rid of parts of the macro. We still need to keep parts of it
around for the other commands.
- Pass BuildCommandContext to BuildCommand::build_command
- This allows to inject TEdgeConfigRepository for commands that need to
write a config.
- Especially useful during the migration period, where we can still
provide `build_command` with an old config::TEdgeConfig.
- TODO: Some tests failing. I think some of the test conditions are wrong!
- Fix test
- We changed the default value
- When writing tedge.toml, create parent directory
- This fixes the tests.
- Create $HOME/.tedge or /etc/tedge if it does not exist yet.
- This needs some more love. Permissions should be set correctly.
- Remove underscored from trait definition
- Split TEdgeConfigDefaults from TEdgeConfigLocation
- `TEdgeConfigLocation` only tells us something about where the
`tedge.config` is located (and about it's root directory).
- `TEdgeConfigDefaults` only tells us something about the default values
used in `TEdgeConfig` as fallbacks.
- Both of them are independent entities.
- You can derive `TEdgeConfigDefaults::from(&TEdgeConfigLocation)`.
- Drop `Clone` - Use `config_repo.load()` instead of `config.clone()`
- Silence a few clippy warnings
- Rename BuildCommandContext to just BuildContext
Diffstat (limited to 'tedge_config')
-rw-r--r-- | tedge_config/src/config_setting.rs | 28 | ||||
-rw-r--r-- | tedge_config/src/lib.rs | 5 | ||||
-rw-r--r-- | tedge_config/src/models/file_path.rs | 40 | ||||
-rw-r--r-- | tedge_config/src/models/mod.rs | 3 | ||||
-rw-r--r-- | tedge_config/src/settings.rs | 15 | ||||
-rw-r--r-- | tedge_config/src/tedge_config.rs | 63 | ||||
-rw-r--r-- | tedge_config/src/tedge_config_defaults.rs | 72 | ||||
-rw-r--r-- | tedge_config/src/tedge_config_dto.rs | 9 | ||||
-rw-r--r-- | tedge_config/src/tedge_config_location.rs | 166 | ||||
-rw-r--r-- | tedge_config/src/tedge_config_repository.rs | 29 | ||||
-rw-r--r-- | tedge_config/tests/test_tedge_config.rs | 198 |
11 files changed, 386 insertions, 242 deletions
diff --git a/tedge_config/src/config_setting.rs b/tedge_config/src/config_setting.rs index c34d1fc9..fde2c46c 100644 --- a/tedge_config/src/config_setting.rs +++ b/tedge_config/src/config_setting.rs @@ -27,6 +27,24 @@ pub trait ConfigSettingAccessor<T: ConfigSetting> { fn unset(&mut self, _setting: T) -> ConfigSettingResult<()>; } +/// Extension trait that provides methods to query a setting as a String or +/// update a setting provided a String value. +pub trait ConfigSettingAccessorStringExt<T: ConfigSetting>: ConfigSettingAccessor<T> { + /// Read a configuration setting and convert it into a String. + fn query_string(&self, setting: T) -> ConfigSettingResult<String>; + + fn query_string_optional(&self, setting: T) -> ConfigSettingResult<Option<String>> { + match self.query_string(setting) { + Ok(value) => Ok(Some(value)), + Err(ConfigSettingError::ConfigNotSet { .. }) => Ok(None), + Err(err) => Err(err), + } + } + + /// Update a configuration setting from a String value + fn update_string(&mut self, setting: T, value: String) -> ConfigSettingResult<()>; +} + pub type ConfigSettingResult<T> = Result<T, ConfigSettingError>; #[derive(thiserror::Error, Debug)] @@ -37,6 +55,12 @@ pub enum ConfigSettingError { )] ConfigNotSet { key: &'static str }, - #[error("Readonly setting")] - ReadonlySetting, + #[error("Readonly setting: {message}")] + ReadonlySetting { message: &'static str }, + + #[error("Conversion from String failed")] + ConversionFromStringFailed, + + #[error("Conversion into String failed")] + ConversionIntoStringFailed, } diff --git a/tedge_config/src/lib.rs b/tedge_config/src/lib.rs index ce268495..2efe321c 100644 --- a/tedge_config/src/lib.rs +++ b/tedge_config/src/lib.rs @@ -3,10 +3,13 @@ mod error; mod models; mod settings; mod tedge_config; +mod tedge_config_defaults; mod tedge_config_dto; mod tedge_config_location; mod tedge_config_repository; use self::tedge_config_dto::*; pub use self::{config_setting::*, error::*, models::*, settings::*}; -pub use self::{tedge_config::*, tedge_config_location::*, tedge_config_repository::*}; +pub use self::{ + tedge_config::*, tedge_config_defaults::*, tedge_config_location::*, tedge_config_repository::*, +}; diff --git a/tedge_config/src/models/file_path.rs b/tedge_config/src/models/file_path.rs new file mode 100644 index 00000000..e74dff98 --- /dev/null +++ b/tedge_config/src/models/file_path.rs @@ -0,0 +1,40 @@ +use std::convert::TryInto; +use std::path::PathBuf; + +/// Represents a path to a file or directory. +/// +/// We need this newtype in order to implement `TryInto<String>`. +/// `PathBuf` does not implement `TryInto<String>`. +#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, Eq, PartialEq)] +#[serde(transparent)] +pub struct FilePath(PathBuf); + +#[derive(thiserror::Error, Debug)] +#[error("FilePath to String conversion failed: {0:?}")] +pub struct FilePathToStringConversionFailure(std::ffi::OsString); + +impl<T> From<T> for FilePath +where + PathBuf: From<T>, +{ + fn from(input: T) -> Self { + Self(PathBuf::from(input)) + } +} + +impl Into<PathBuf> for FilePath { + fn into(self) -> PathBuf { + self.0 + } +} + +impl TryInto<String> for FilePath { + type Error = FilePathToStringConversionFailure; + + fn try_into(self) -> Result<String, FilePathToStringConversionFailure> { + self.0 + .into_os_string() + .into_string() + .map_err(FilePathToStringConversionFailure) + } +} diff --git a/tedge_config/src/models/mod.rs b/tedge_config/src/models/mod.rs index ef226c0f..ce81e3af 100644 --- a/tedge_config/src/models/mod.rs +++ b/tedge_config/src/models/mod.rs @@ -1,3 +1,4 @@ pub mod connect_url; +pub mod file_path; -pub use self::connect_url::*; +pub use self::{connect_url::*, file_path::*}; diff --git a/tedge_config/src/settings.rs b/tedge_config/src/settings.rs index e85c5cf2..f247cfe7 100644 --- a/tedge_config/src/settings.rs +++ b/tedge_config/src/settings.rs @@ -1,5 +1,4 @@ use crate::{config_setting::*, models::*}; -use std::path::PathBuf; /// /// Identifier of the device within the fleet. It must be globally @@ -34,7 +33,7 @@ impl ConfigSetting for DeviceKeyPathSetting { const DESCRIPTION: &'static str = "Path to the private key file. Example: /home/user/.tedge/tedge-private-key.pem"; - type Value = PathBuf; + type Value = FilePath; } /// @@ -51,7 +50,7 @@ impl ConfigSetting for DeviceCertPathSetting { const DESCRIPTION: &'static str = "Path to the certificate file. Example: /home/user/.tedge/tedge-certificate.crt"; - type Value = PathBuf; + type Value = FilePath; } /// @@ -79,14 +78,14 @@ impl ConfigSetting for C8yUrlSetting { pub struct C8yRootCertPathSetting; impl ConfigSetting for C8yRootCertPathSetting { - const KEY: &'static str = "c8y.root_cert_path"; + const KEY: &'static str = "c8y.root.cert.path"; const DESCRIPTION: &'static str = concat!( "Path where Cumulocity root certificate(s) are located. ", "Example: /home/user/.tedge/c8y-trusted-root-certificates.pem" ); - type Value = PathBuf; + type Value = FilePath; } /// @@ -98,7 +97,7 @@ impl ConfigSetting for C8yRootCertPathSetting { pub struct AzureUrlSetting; impl ConfigSetting for AzureUrlSetting { - const KEY: &'static str = "device.id"; + const KEY: &'static str = "azure.url"; const DESCRIPTION: &'static str = concat!( "Tenant endpoint URL of Azure IoT tenant. ", @@ -117,12 +116,12 @@ impl ConfigSetting for AzureUrlSetting { pub struct AzureRootCertPathSetting; impl ConfigSetting for AzureRootCertPathSetting { - const KEY: &'static str = "azure.root_cert_path"; + const KEY: &'static str = "azure.root.cert.path"; const DESCRIPTION: &'static str = concat!( "Path where Azure IoT root certificate(s) are located. ", "Example: /home/user/.tedge/azure-trusted-root-certificates.pem" ); - type Value = PathBuf; + type Value = FilePath; } diff --git a/tedge_config/src/tedge_config.rs b/tedge_config/src/tedge_config.rs index 89d7b86f..644680bd 100644 --- a/tedge_config/src/tedge_config.rs +++ b/tedge_config/src/tedge_config.rs @@ -1,5 +1,5 @@ use crate::*; -use std::path::PathBuf; +use std::convert::{TryFrom, TryInto}; /// Represents the complete configuration of a thin edge device. /// This configuration is a wrapper over the device specific configurations @@ -9,6 +9,7 @@ use std::path::PathBuf; pub struct TEdgeConfig { pub(crate) data: TEdgeConfigDto, pub(crate) config_location: TEdgeConfigLocation, + pub(crate) config_defaults: TEdgeConfigDefaults, } impl ConfigSettingAccessor<DeviceIdSetting> for TEdgeConfig { @@ -23,11 +24,21 @@ impl ConfigSettingAccessor<DeviceIdSetting> for TEdgeConfig { } fn update(&mut self, _setting: DeviceIdSetting, _value: String) -> ConfigSettingResult<()> { - Err(ConfigSettingError::ReadonlySetting) + Err(ConfigSettingError::ReadonlySetting { + message: concat!( + "Setting the device id is only allowed with `tedge cert create`.\n", + "To set 'device.id', use `tedge cert create --device-id <id>`." + ), + }) } fn unset(&mut self, _setting: DeviceIdSetting) -> ConfigSettingResult<()> { - Err(ConfigSettingError::ReadonlySetting) + Err(ConfigSettingError::ReadonlySetting { + message: concat!( + "Setting the device id is only allowed with `tedge cert create`.\n", + "To set 'device.id', use `tedge cert create --device-id <id>`." + ), + }) } } @@ -76,19 +87,19 @@ impl ConfigSettingAccessor<C8yUrlSetting> for TEdgeConfig { } impl ConfigSettingAccessor<DeviceCertPathSetting> for TEdgeConfig { - fn query(&self, _setting: DeviceCertPathSetting) -> ConfigSettingResult<PathBuf> { + fn query(&self, _setting: DeviceCertPathSetting) -> ConfigSettingResult<FilePath> { Ok(self .data .device .cert_path .clone() - .unwrap_or_else(|| self.config_location.default_device_cert_path.clone())) + .unwrap_or_else(|| self.config_defaults.default_device_cert_path.clone())) } fn update( &mut self, _setting: DeviceCertPathSetting, - value: PathBuf, + value: FilePath, ) -> ConfigSettingResult<()> { self.data.device.cert_path = Some(value); Ok(()) @@ -101,19 +112,19 @@ impl ConfigSettingAccessor<DeviceCertPathSetting> for TEdgeConfig { } impl ConfigSettingAccessor<DeviceKeyPathSetting> for TEdgeConfig { - fn query(&self, _setting: DeviceKeyPathSetting) -> ConfigSettingResult<PathBuf> { + fn query(&self, _setting: DeviceKeyPathSetting) -> ConfigSettingResult<FilePath> { Ok(self .data .device .key_path .clone() - .unwrap_or_else(|| self.config_location.default_device_key_path.clone())) + .unwrap_or_else(|| self.config_defaults.default_device_key_path.clone())) } fn update( &mut self, _setting: DeviceKeyPathSetting, - value: PathBuf, + value: FilePath, ) -> ConfigSettingResult<()> { self.data.device.key_path = Some(value); Ok(()) @@ -126,19 +137,19 @@ impl ConfigSettingAccessor<DeviceKeyPathSetting> for TEdgeConfig { } impl ConfigSettingAccessor<AzureRootCertPathSetting> for TEdgeConfig { - fn query(&self, _setting: AzureRootCertPathSetting) -> ConfigSettingResult<PathBuf> { + fn query(&self, _setting: AzureRootCertPathSetting) -> ConfigSettingResult<FilePath> { Ok(self .data .azure .root_cert_path .clone() - .unwrap_or_else(|| self.config_location.default_azure_root_cert_path.clone())) + .unwrap_or_else(|| self.config_defaults.default_azure_root_cert_path.clone())) } fn update( &mut self, _setting: AzureRootCertPathSetting, - value: PathBuf, + value: FilePath, ) -> ConfigSettingResult<()> { self.data.azure.root_cert_path = Some(value); Ok(()) @@ -151,19 +162,19 @@ impl ConfigSettingAccessor<AzureRootCertPathSetting> for TEdgeConfig { } impl ConfigSettingAccessor<C8yRootCertPathSetting> for TEdgeConfig { - fn query(&self, _setting: C8yRootCertPathSetting) -> ConfigSettingResult<PathBuf> { + fn query(&self, _setting: C8yRootCertPathSetting) -> ConfigSettingResult<FilePath> { Ok(self .data .c8y .root_cert_path .clone() - .unwrap_or_else(|| self.config_location.default_c8y_root_cert_path.clone())) + .unwrap_or_else(|| self.config_defaults.default_c8y_root_cert_path.clone())) } fn update( &mut self, _setting: C8yRootCertPathSetting, - value: PathBuf, + value: FilePath, ) -> ConfigSettingResult<()> { self.data.c8y.root_cert_path = Some(value); Ok(()) @@ -174,3 +185,25 @@ impl ConfigSettingAccessor<C8yRootCertPathSetting> for TEdgeConfig { Ok(()) } } + +/// Generic extension trait implementation for all `ConfigSetting`s of `TEdgeConfig` +/// that provide `TryFrom`/`TryInto` implementations for `String`. +impl<T, E, F> ConfigSettingAccessorStringExt<T> for TEdgeConfig +where + T: ConfigSetting, + TEdgeConfig: ConfigSettingAccessor<T>, + T::Value: TryFrom<String, Error = E>, + T::Value: TryInto<String, Error = F>, +{ + fn query_string(&self, setting: T) -> ConfigSettingResult<String> { + self.query(setting)? + .try_into() + .map_err(|_e| ConfigSettingError::ConversionIntoStringFailed) + } + + fn update_string(&mut self, setting: T, string_value: String) -> ConfigSettingResult<()> { + T::Value::try_from(string_value) + .map_err(|_e| ConfigSettingError::ConversionFromStringFailed) + .and_then(|value| self.update(setting, value)) + } +} diff --git a/tedge_config/src/tedge_config_defaults.rs b/tedge_config/src/tedge_config_defaults.rs new file mode 100644 index 00000000..6896c51b --- /dev/null +++ b/tedge_config/src/tedge_config_defaults.rs @@ -0,0 +1,72 @@ +use crate::models::FilePath; +use crate::TEdgeConfigLocation; +use std::path::Path; + +const DEFAULT_ETC_PATH: &str = "/etc"; + +/// Stores default values for use by `TEdgeConfig` in case no configuration setting +/// is available. +/// +/// We DO NOT base the defaults on the currently executing user. Instead, we derive +/// the defaults from the location of the `tedge.toml` file. This allows run +/// `sudo tedge -c '$HOME/.tedge/tedge.toml ...` where the defaults are picked up +/// correctly. +/// +/// The choice, where to find `tedge.toml` on the other hand is based on the executing user AND the +/// env `$HOME`. But once we have found `tedge.toml`, we never again have to care about the +/// executing user (except when `chown`ing files...). +/// +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct TEdgeConfigDefaults { + /// Default device cert path + pub default_device_cert_path: FilePath, + + /// Default device key path + pub default_device_key_path: FilePath, + + /// Default path for azure root certificates + pub default_azure_root_cert_path: FilePath, + + /// Default path for c8y root certificates + pub default_c8y_root_cert_path: FilePath, +} + +impl From<&TEdgeConfigLocation> for TEdgeConfigDefaults { + fn from(config_location: &TEdgeConfigLocation) -> Self { + let system_cert_path = Path::new(DEFAULT_ETC_PATH).join("ssl").join("certs"); + Self { + default_device_cert_path: config_location + .tedge_config_root_path() + .join("device-certs") + .join("tedge-certificate.pem") + .into(), + default_device_key_path: config_location + .tedge_config_root_path() + .join("device-certs") + .join("tedge-private-key.pem") + .into(), + default_azure_root_cert_path: system_cert_path.clone().into(), + default_c8y_root_cert_path: system_cert_path.into(), + } + } +} + +#[test] +fn test_from_tedge_config_location() { + let config_location = TEdgeConfigLocation::from_custom_root("/opt/etc/_tedge"); + let defaults = TEdgeConfigDefaults::from(&config_location); + + assert_eq!( + defaults, + TEdgeConfigDefaults { + default_device_cert_path: FilePath::from( + "/opt/etc/_tedge/device-certs/tedge-certificate.pem" + ), + default_device_key_path: FilePath::from( + "/opt/etc/_tedge/device-certs/tedge-private-key.pem" + ), + default_azure_root_cert_path: FilePath::from("/etc/ssl/certs"), + default_c8y_root_cert_path: FilePath::from("/etc/ssl/certs") + } + ); +} diff --git a/tedge_config/src/tedge_config_dto.rs b/tedge_config/src/tedge_config_dto.rs index 7f67770d..2927901c 100644 --- a/tedge_config/src/tedge_config_dto.rs +++ b/tedge_config/src/tedge_config_dto.rs @@ -2,7 +2,6 @@ use crate::*; use serde::{Deserialize, Serialize}; -use std::path::PathBuf; #[serde(deny_unknown_fields)] #[derive(Debug, Default, Deserialize, Serialize)] @@ -28,11 +27,11 @@ pub(crate) struct DeviceConfigDto { /// Path where the device's private key is stored. /// Defaults to $HOME/.tedge/tedge-private.pem - pub(crate) key_path: Option<PathBuf>, + pub(crate) key_path: Option<FilePath>, /// Path where the device's certificate is stored. /// Defaults to $HOME/.tedge/tedge-certificate.crt - pub(crate) cert_path: Option<PathBuf>, + pub(crate) cert_path: Option<FilePath>, } /// Represents the Cumulocity specific configurations defined in the @@ -48,7 +47,7 @@ pub(crate) struct CumulocityConfigDto { /// The path where Cumulocity root certificate(s) are stored. /// The value can be a directory path as well as the path of the direct certificate file. - pub(crate) root_cert_path: Option<PathBuf>, + pub(crate) root_cert_path: Option<FilePath>, } #[serde(deny_unknown_fields)] @@ -56,5 +55,5 @@ pub(crate) struct CumulocityConfigDto { pub(crate) struct AzureConfigDto { pub(crate) connect: Option<String>, pub(crate) url: Option<ConnectUrl>, - pub(crate) root_cert_path: Option<PathBuf>, + pub(crate) root_cert_path: Option<FilePath>, } diff --git a/tedge_config/src/tedge_config_location.rs b/tedge_config/src/tedge_config_location.rs index fdf45f51..1ca2ff28 100644 --- a/tedge_config/src/tedge_config_location.rs +++ b/tedge_config/src/tedge_config_location.rs @@ -3,137 +3,89 @@ use std::path::{Path, PathBuf}; const DEFAULT_ETC_PATH: &str = "/etc"; const TEDGE_CONFIG_FILE: &str = "tedge.toml"; -/// Information about where `tedge.toml` is located and the defaults that are based -/// on that location. +/// Information about where `tedge.toml` is located. /// /// Broadly speaking, we distinguish two different locations: /// -/// - System-wide locations under `/etc/tedge` +/// - System-wide locations under `/etc/tedge` or `/usr/local/etc/tedge`. /// - User-local locations under `$HOME/.tedge` /// -/// We DO NOT base the defaults on the currently executing user. Instead, we base -/// the defaults on the location of the `tedge.toml` file. If it is located in -/// `/etc`, regardless of the executing user, we use defaults that use system-wide -/// locations (e.g. `/etc/ssl/certs`). Whereas if `tedge.toml` is located in a users -/// home directory, we base the defaults on locations within the users home directory. -/// -/// This allows run `sudo tedge -c '$HOME/.tedge/tedge.toml ...` where the defaults are picked up -/// correctly. -/// -/// The choice, where to find `tedge.toml` on the other hand is based on the executing user AND the -/// env `$HOME`. But once we have found `tedge.toml`, we never again have to care about the -/// executing user (except when `chown`ing files...). -/// -#[derive(Debug, Clone)] +#[derive(Debug, Clone, Eq, PartialEq)] pub struct TEdgeConfigLocation { - /// Full path to `tedge.toml`. - pub tedge_config_path: PathBuf, - - /// Default device cert path - pub default_device_cert_path: PathBuf, - - /// Default device key path - pub default_device_key_path: PathBuf, - - /// Default path for azure root certificates - pub default_azure_root_cert_path: PathBuf, + /// Root directory where `tedge.toml` and other tedge related configuration files are located. + pub tedge_config_root_path: PathBuf, - /// Default path for c8y root certificates - pub default_c8y_root_cert_path: PathBuf, + /// Full path to the `tedge.toml` file. + pub tedge_config_file_path: PathBuf, } impl TEdgeConfigLocation { - /// `tedge.toml` is located in `/etc/tedge`. All defaults are based on system locations. + pub fn from_custom_root(tedge_config_root_path: impl AsRef<Path>) -> Self { + Self { + tedge_config_root_path: tedge_config_root_path.as_ref().to_path_buf(), + tedge_config_file_path: tedge_config_root_path.as_ref().join(TEDGE_CONFIG_FILE), + } + } + + /// `tedge.toml` is located in `/etc/tedge`. pub fn from_default_system_location() -> Self { - Self::from_system_location(DEFAULT_ETC_PATH) + Self::from_custom_root(Path::new(DEFAULT_ETC_PATH).join("tedge")) } - /// `tedge.toml` is located in `${etc_path}/tedge`. All defaults are based on system locations. - pub fn from_system_location(etc_path: impl AsRef<Path>) -> Self { - let etc_path = etc_path.as_ref(); - let tedge_path = etc_path.join("tedge"); - Self { - tedge_config_path: tedge_path.join(TEDGE_CONFIG_FILE), - default_device_cert_path: tedge_path - .join("device-certs") - .join("tedge-certificate.pem"), - default_device_key_path: tedge_path - .join("device-certs") - .join("tedge-private-key.pem"), - default_azure_root_cert_path: etc_path.join("ssl").join("certs"), - default_c8y_root_cert_path: etc_path.join("ssl").join("certs"), - } + /// `tedge.toml` is located in `${etc_path}/tedge`. + pub fn from_custom_etc_location(custom_etc_path: impl AsRef<Path>) -> Self { + Self::from_custom_root(custom_etc_path.as_ref().join("tedge")) } - /// `tedge.toml` is located in `$HOME/.tedge/tedge.toml`. All defaults are relative to the - /// `$HOME/.tedge` directory. + /// `tedge.toml` is located in `${home_path}/.tedge`. pub fn from_users_home_location(home_path: impl AsRef<Path>) -> Self { - let etc_path = Path::new(DEFAULT_ETC_PATH); - let tedge_path = home_path.as_ref().join(".tedge"); - Self { - tedge_config_path: tedge_path.join(TEDGE_CONFIG_FILE), - default_device_cert_path: tedge_path - .join("device-certs") - .join("tedge-certificate.pem"), - default_device_key_path: tedge_path - .join("device-certs") - .join("tedge-private-key.pem"), - default_azure_root_cert_path: etc_path.join("ssl").join("certs"), - default_c8y_root_cert_path: etc_path.join("ssl").join("certs"), - } + Self::from_custom_root(home_path.as_ref().join(".tedge")) + } + + pub fn tedge_config_root_path(&self) -> &Path { + &self.tedge_config_root_path + } + pub fn tedge_config_file_path(&self) -> &Path { + &self.tedge_config_file_path } } #[test] -fn test_from_default_system_location() { - let config_location = TEdgeConfigLocation::from_default_system_location(); - assert_eq!( - config_location.tedge_config_path, - PathBuf::from("/etc/tedge/tedge.toml") - ); +fn test_from_custom_root() { + let config_location = TEdgeConfigLocation::from_custom_root("/opt/etc/tedge"); assert_eq!( - config_location.default_device_cert_path, - PathBuf::from("/etc/tedge/device-certs/tedge-certificate.pem") + config_location.tedge_config_root_path, + PathBuf::from("/opt/etc/tedge") ); assert_eq!( - config_location.default_device_key_path, - PathBuf::from("/etc/tedge/device-certs/tedge-private-key.pem") - ); - assert_eq!( - config_location.default_azure_root_cert_path, - PathBuf::from("/etc/ssl/certs") - ); - assert_eq!( - config_location.default_c8y_root_cert_path, - PathBuf::from("/etc/ssl/certs") + config_location.tedge_config_file_path, + PathBuf::from("/opt/etc/tedge/tedge.toml") ); } #[test] -fn test_from_system_location() { - // "/usr/local/etc" is often used for installed services on FreeBSD - let config_location = TEdgeConfigLocation::from_system_location("/usr/local/etc"); - assert_eq!( - config_location.tedge_config_path, - PathBuf::from("/usr/local/etc/tedge/tedge.toml") - ); +fn test_from_default_system_location() { + let config_location = TEdgeConfigLocation::from_default_system_location(); assert_eq!( - config_location.default_device_cert_path, - PathBuf::from("/usr/local/etc/tedge/device-certs/tedge-certificate.pem") + config_location.tedge_config_root_path, + PathBuf::from("/etc/tedge") ); assert_eq!( - config_location.default_device_key_path, - PathBuf::from("/usr/local/etc/tedge/device-certs/tedge-private-key.pem") + config_location.tedge_config_file_path, + PathBuf::from("/etc/tedge/tedge.toml") ); - // XXX: This should actually be "/etc/ssl/certs". +} + +#[test] +fn test_from_custom_etc_location() { + let config_location = TEdgeConfigLocation::from_custom_etc_location("/usr/local/etc"); assert_eq!( - config_location.default_azure_root_cert_path, - PathBuf::from("/usr/local/etc/ssl/certs") + config_location.tedge_config_root_path, + PathBuf::from("/usr/local/etc/tedge") ); - // XXX: This should actually be "/etc/ssl/certs". assert_eq!( - config_location.default_c8y_root_cert_path, - PathBuf::from("/usr/local/etc/ssl/certs") + config_location.tedge_config_file_path, + PathBuf::from("/usr/local/etc/tedge/tedge.toml") ); } @@ -141,23 +93,11 @@ fn test_from_system_location() { fn test_from_users_home_location() { let config_location = TEdgeConfigLocation::from_users_home_location("/home/user"); assert_eq!( - config_location.tedge_config_path, - PathBuf::from("/home/user/.tedge/tedge.toml") - ); - assert_eq!( - config_location.default_device_cert_path, - PathBuf::from("/home/user/.tedge/device-certs/tedge-certificate.pem") + config_location.tedge_config_root_path, + PathBuf::from("/home/user/.tedge") ); assert_eq!( - config_location.default_device_key_path, - PathBuf::from("/home/user/.tedge/device-certs/tedge-private-key.pem") - ); - assert_eq!( - config_location.default_azure_root_cert_path, - PathBuf::from("/etc/ssl/certs") - ); - assert_eq!( - config_location.default_c8y_root_cert_path, - PathBuf::from("/etc/ssl/certs") + config_location.tedge_config_file_path, + PathBuf::from("/home/user/.tedge/tedge.toml") ); } diff --git a/tedge_config/src/tedge_config_repository.rs b/tedge_config/src/tedge_config_repository.rs index 97e60ecd..c7fdd2b2 100644 --- a/tedge_config/src/tedge_config_repository.rs +++ b/tedge_config/src/tedge_config_repository.rs @@ -7,6 +7,7 @@ use tempfile::NamedTempFile; /// pub struct TEdgeConfigRepository { config_location: TEdgeConfigLocation, + config_defaults: TEdgeConfigDefaults, } pub trait ConfigRepository<T> { @@ -19,7 +20,8 @@ impl ConfigRepository<TEdgeConfig> for TEdgeConfigRepository { type Error = TEdgeConfigError; fn load(&self) -> Result<TEdgeConfig, TEdgeConfigError> { - let config = self.read_file_or_default(self.config_location.tedge_config_path.clone())?; + let config = + self.read_file_or_default(self.config_location.tedge_config_file_path().into())?; Ok(config) } @@ -28,7 +30,14 @@ impl ConfigRepository<TEdgeConfig> for TEdgeConfigRepository { let toml = toml::to_string_pretty(&config.data)?; let mut file = NamedTempFile::new()?; file.write_all(toml.as_bytes())?; - match file.persist(self.config_location.tedge_config_path.clone()) { + + // Create $HOME/.tedge or /etc/tedge directory if it does not exist + if !self.config_location.tedge |