Skip to content

Commit 5b44c12

Browse files
Merge pull request #2735 from didier-wenzek/refactor/deprecate-tedge-config-repository
refactor: deprecate TEdgeConfigRepository
2 parents 7c88c76 + 8cad9f7 commit 5b44c12

File tree

36 files changed

+339
-356
lines changed

36 files changed

+339
-356
lines changed

crates/common/tedge_config/src/lib.rs

Lines changed: 23 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -7,21 +7,34 @@ pub use self::tedge_config_cli::error::*;
77
pub use self::tedge_config_cli::models::*;
88
pub use self::tedge_config_cli::tedge_config::*;
99
pub use self::tedge_config_cli::tedge_config_location::*;
10-
pub use self::tedge_config_cli::tedge_config_repository::*;
1110
pub use camino::Utf8Path as Path;
1211
pub use camino::Utf8PathBuf as PathBuf;
1312
pub use certificate::CertificateError;
1413
pub use tedge_config_macros::all_or_nothing;
1514
pub use tedge_config_macros::OptionalConfig;
1615

17-
/// loads the new tedge config from system default
18-
pub fn get_new_tedge_config() -> Result<TEdgeConfig, TEdgeConfigError> {
19-
let tedge_config_location = TEdgeConfigLocation::default();
20-
TEdgeConfigRepository::new(tedge_config_location).load()
21-
}
16+
impl TEdgeConfig {
17+
pub fn try_new(config_location: TEdgeConfigLocation) -> Result<Self, TEdgeConfigError> {
18+
config_location.load()
19+
}
20+
21+
pub fn load(config_dir: &Path) -> Result<TEdgeConfig, TEdgeConfigError> {
22+
let config_location = TEdgeConfigLocation::from_custom_root(config_dir);
23+
TEdgeConfig::try_new(config_location)
24+
}
2225

23-
/// loads the tedge config from a config directory
24-
pub fn load_tedge_config(config_dir: &Path) -> Result<TEdgeConfig, TEdgeConfigError> {
25-
let tedge_config_location = TEdgeConfigLocation::from_custom_root(config_dir);
26-
TEdgeConfigRepository::new(tedge_config_location).load()
26+
#[cfg(feature = "test")]
27+
/// A test only method designed for injecting configuration into tests
28+
///
29+
/// ```
30+
/// use tedge_config::TEdgeConfig;
31+
/// let config = TEdgeConfig::load_toml_str("service.ty = \"service\"");
32+
///
33+
/// assert_eq!(&config.service.ty, "service");
34+
/// // Defaults are preserved
35+
/// assert_eq!(config.sudo.enable, true);
36+
/// ```
37+
pub fn load_toml_str(toml: &str) -> TEdgeConfig {
38+
TEdgeConfigLocation::load_toml_str(toml)
39+
}
2740
}

crates/common/tedge_config/src/tedge_config_cli/mod.rs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
pub mod config_setting;
22
pub mod error;
33
pub mod tedge_config_location;
4-
pub mod tedge_config_repository;
54

65
mod figment;
76
pub mod models;

crates/common/tedge_config/src/tedge_config_cli/tedge_config.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ impl<T> OptionalConfigError<T> for OptionalConfig<T> {
4242
}
4343
}
4444

45+
#[derive(Clone)]
4546
pub struct TEdgeConfig(TEdgeConfigReader);
4647

4748
impl std::ops::Deref for TEdgeConfig {

crates/common/tedge_config/src/tedge_config_cli/tedge_config_location.rs

Lines changed: 232 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,19 @@
1+
use std::fs;
12
use std::path::Path;
23

4+
use crate::tedge_config_cli::figment::ConfigSources;
5+
use crate::tedge_config_cli::figment::FileAndEnvironment;
6+
use crate::tedge_config_cli::figment::FileOnly;
7+
use crate::tedge_config_cli::figment::UnusedValueWarnings;
8+
use crate::ConfigSettingResult;
9+
use crate::TEdgeConfig;
10+
use crate::TEdgeConfigDto;
11+
use crate::TEdgeConfigError;
312
use camino::Utf8Path;
413
use camino::Utf8PathBuf;
14+
use serde::Serialize;
15+
use tedge_utils::fs::atomically_write_file_sync;
16+
use tracing::debug;
517

618
pub const DEFAULT_TEDGE_CONFIG_PATH: &str = "/etc/tedge";
719
const TEDGE_CONFIG_FILE: &str = "tedge.toml";
@@ -47,30 +59,227 @@ impl TEdgeConfigLocation {
4759
pub fn tedge_config_file_path(&self) -> &Utf8Path {
4860
&self.tedge_config_file_path
4961
}
50-
}
5162

52-
#[test]
53-
fn test_from_custom_root() {
54-
let config_location = TEdgeConfigLocation::from_custom_root("/opt/etc/tedge");
55-
assert_eq!(
56-
config_location.tedge_config_root_path,
57-
Utf8Path::new("/opt/etc/tedge")
58-
);
59-
assert_eq!(
60-
config_location.tedge_config_file_path,
61-
Utf8Path::new("/opt/etc/tedge/tedge.toml")
62-
);
63+
pub fn update_toml(
64+
&self,
65+
update: &impl Fn(&mut TEdgeConfigDto) -> ConfigSettingResult<()>,
66+
) -> Result<(), TEdgeConfigError> {
67+
let mut config = self.load_dto::<FileOnly>(self.toml_path())?;
68+
update(&mut config)?;
69+
70+
self.store(&config)
71+
}
72+
73+
fn toml_path(&self) -> &Utf8Path {
74+
self.tedge_config_file_path()
75+
}
76+
77+
pub fn load(&self) -> Result<TEdgeConfig, TEdgeConfigError> {
78+
let dto = self.load_dto::<FileAndEnvironment>(self.toml_path())?;
79+
debug!(
80+
"Loading configuration from {:?}",
81+
self.tedge_config_file_path
82+
);
83+
Ok(TEdgeConfig::from_dto(&dto, self))
84+
}
85+
86+
fn load_dto<Sources: ConfigSources>(
87+
&self,
88+
path: &Utf8Path,
89+
) -> Result<TEdgeConfigDto, TEdgeConfigError> {
90+
let (dto, warnings) = self.load_dto_with_warnings::<Sources>(path)?;
91+
92+
warnings.emit();
93+
94+
Ok(dto)
95+
}
96+
97+
#[cfg(feature = "test")]
98+
/// A test only method designed for injecting configuration into tests
99+
///
100+
/// ```
101+
/// use tedge_config::TEdgeConfigLocation;
102+
/// let config = TEdgeConfigLocation::load_toml_str("service.ty = \"service\"");
103+
///
104+
/// assert_eq!(&config.service.ty, "service");
105+
/// // Defaults are preserved
106+
/// assert_eq!(config.sudo.enable, true);
107+
/// ```
108+
pub fn load_toml_str(toml: &str) -> TEdgeConfig {
109+
let dto = super::figment::extract_from_toml_str(toml).unwrap();
110+
TEdgeConfig::from_dto(&dto, &TEdgeConfigLocation::default())
111+
}
112+
113+
fn load_dto_with_warnings<Sources: ConfigSources>(
114+
&self,
115+
path: &Utf8Path,
116+
) -> Result<(TEdgeConfigDto, UnusedValueWarnings), TEdgeConfigError> {
117+
let (mut dto, mut warnings): (TEdgeConfigDto, _) =
118+
super::figment::extract_data::<_, Sources>(path)?;
119+
120+
if let Some(migrations) = dto.config.version.unwrap_or_default().migrations() {
121+
'migrate_toml: {
122+
let Ok(config) = std::fs::read_to_string(self.toml_path()) else {
123+
break 'migrate_toml;
124+
};
125+
126+
tracing::info!("Migrating tedge.toml configuration to version 2");
127+
128+
let toml = toml::de::from_str(&config)?;
129+
let migrated_toml = migrations
130+
.into_iter()
131+
.fold(toml, |toml, migration| migration.apply_to(toml));
132+
133+
self.store(&migrated_toml)?;
134+
135+
// Reload DTO to get the settings in the right place
136+
(dto, warnings) = super::figment::extract_data::<_, Sources>(self.toml_path())?;
137+
}
138+
}
139+
140+
Ok((dto, warnings))
141+
}
142+
143+
// TODO: Explicitly set the file permissions in this function and file ownership!
144+
fn store<S: Serialize>(&self, config: &S) -> Result<(), TEdgeConfigError> {
145+
let toml = toml::to_string_pretty(&config)?;
146+
147+
// Create `$HOME/.tedge` or `/etc/tedge` directory in case it does not exist yet
148+
if !self.tedge_config_root_path.exists() {
149+
fs::create_dir(self.tedge_config_root_path())?;
150+
}
151+
152+
atomically_write_file_sync(self.toml_path(), toml.as_bytes())?;
153+
Ok(())
154+
}
63155
}
64156

65-
#[test]
66-
fn test_from_default_system_location() {
67-
let config_location = TEdgeConfigLocation::default();
68-
assert_eq!(
69-
config_location.tedge_config_root_path,
70-
Utf8Path::new("/etc/tedge")
71-
);
72-
assert_eq!(
73-
config_location.tedge_config_file_path,
74-
Utf8Path::new("/etc/tedge/tedge.toml")
75-
);
157+
#[cfg(test)]
158+
mod tests {
159+
use tedge_test_utils::fs::TempTedgeDir;
160+
161+
use crate::TEdgeConfigReader;
162+
163+
use super::*;
164+
165+
#[test]
166+
fn test_from_custom_root() {
167+
let config_location = TEdgeConfigLocation::from_custom_root("/opt/etc/tedge");
168+
assert_eq!(
169+
config_location.tedge_config_root_path,
170+
Utf8Path::new("/opt/etc/tedge")
171+
);
172+
assert_eq!(
173+
config_location.tedge_config_file_path,
174+
Utf8Path::new("/opt/etc/tedge/tedge.toml")
175+
);
176+
}
177+
178+
#[test]
179+
fn test_from_default_system_location() {
180+
let config_location = TEdgeConfigLocation::default();
181+
assert_eq!(
182+
config_location.tedge_config_root_path,
183+
Utf8Path::new("/etc/tedge")
184+
);
185+
assert_eq!(
186+
config_location.tedge_config_file_path,
187+
Utf8Path::new("/etc/tedge/tedge.toml")
188+
);
189+
}
190+
191+
#[test]
192+
fn old_toml_can_be_read_in_its_entirety() {
193+
let toml = r#"[device]
194+
key_path = "/tedge/device-key.pem"
195+
cert_path = "/tedge/device-cert.pem"
196+
type = "a-device"
197+
198+
[c8y]
199+
url = "something.latest.stage.c8y.io"
200+
root_cert_path = "/c8y/root-cert.pem"
201+
smartrest_templates = [
202+
"id1",
203+
"id2",
204+
]
205+
206+
[az]
207+
url = "something.azure.com"
208+
root_cert_path = "/az/root-cert.pem"
209+
mapper_timestamp = true
210+
211+
[aws]
212+
url = "something.amazonaws.com"
213+
root_cert_path = "/aws/root-cert.pem"
214+
mapper_timestamp = false
215+
216+
[mqtt]
217+
bind_address = "192.168.0.1"
218+
port = 1886
219+
client_host = "192.168.0.1"
220+
client_port = 1885
221+
client_ca_file = "/mqtt/ca.crt"
222+
client_ca_path = "/mqtt/ca"
223+
external_port = 8765
224+
external_bind_address = "0.0.0.0"
225+
external_bind_interface = "wlan0"
226+
external_capath = "/mqtt/external/ca.pem"
227+
external_certfile = "/mqtt/external/cert.pem"
228+
external_keyfile = "/mqtt/external/key.pem"
229+
230+
[mqtt.client_auth]
231+
cert_file = "/mqtt/auth/cert.pem"
232+
key_file = "/mqtt/auth/key.pem"
233+
234+
[http]
235+
port = 1234
236+
237+
[software]
238+
default_plugin_type = "my-plugin"
239+
240+
[tmp]
241+
path = "/tmp-path"
242+
243+
[logs]
244+
path = "/logs-path"
245+
246+
[run]
247+
path = "/run-path"
248+
lock_files = false
249+
250+
[data]
251+
path = "/data-path"
252+
253+
[firmware]
254+
child_update_timeout = 3429
255+
256+
[service]
257+
type = "a-service-type""#;
258+
let (_tempdir, config_location) = create_temp_tedge_config(toml).unwrap();
259+
let toml_path = config_location.tedge_config_file_path();
260+
let (dto, warnings) = config_location
261+
.load_dto_with_warnings::<FileOnly>(toml_path)
262+
.unwrap();
263+
264+
// Figment will warn us if we're not using a field. If we've migrated
265+
// everything successfully, then no warnings will be emitted
266+
assert_eq!(warnings, UnusedValueWarnings::default());
267+
268+
let reader = TEdgeConfigReader::from_dto(&dto, &config_location);
269+
270+
assert_eq!(reader.device.cert_path, "/tedge/device-cert.pem");
271+
assert_eq!(reader.device.key_path, "/tedge/device-key.pem");
272+
assert_eq!(reader.device.ty, "a-device");
273+
assert_eq!(u16::from(reader.mqtt.bind.port), 1886);
274+
assert_eq!(u16::from(reader.mqtt.client.port), 1885);
275+
}
276+
277+
fn create_temp_tedge_config(
278+
content: &str,
279+
) -> std::io::Result<(TempTedgeDir, TEdgeConfigLocation)> {
280+
let dir = TempTedgeDir::new();
281+
dir.file("tedge.toml").with_raw_content(content);
282+
let config_location = TEdgeConfigLocation::from_custom_root(dir.path());
283+
Ok((dir, config_location))
284+
}
76285
}

0 commit comments

Comments
 (0)