11mod version;
22use futures:: Stream ;
33use reqwest:: NoProxy ;
4- use serde:: Deserialize ;
54use version:: TEdgeTomlVersion ;
65
76mod append_remove;
@@ -62,7 +61,6 @@ use std::net::Ipv4Addr;
6261use std:: num:: NonZeroU16 ;
6362use std:: path:: PathBuf ;
6463use std:: sync:: Arc ;
65- use strum:: IntoEnumIterator ;
6664use tedge_api:: mqtt_topics:: EntityTopicId ;
6765pub use tedge_config_macros:: ConfigNotSet ;
6866pub use tedge_config_macros:: MultiError ;
@@ -104,16 +102,18 @@ impl std::ops::Deref for TEdgeConfig {
104102 }
105103}
106104
107- async fn read_file_if_exists ( path : & Utf8Path ) -> anyhow:: Result < Option < String > > {
105+ async fn read_file_if_exists (
106+ path : & Utf8Path ,
107+ config_dir : & Utf8Path ,
108+ ) -> anyhow:: Result < Option < String > > {
108109 match tokio:: fs:: read_to_string ( path) . await {
109110 Ok ( contents) => Ok ( Some ( contents) ) ,
110111 Err ( e) if e. kind ( ) == ErrorKind :: NotFound => Ok ( None ) ,
111112 Err ( e) => {
112- let dir = path. parent ( ) . unwrap ( ) ;
113113 // If the error is actually with the mappers directory as a whole,
114114 // feed that back to the user
115- if let Err ( dir_error) = tokio:: fs:: read_dir ( dir ) . await {
116- Err ( dir_error) . context ( format ! ( "failed to read {dir }" ) )
115+ if let Err ( dir_error) = tokio:: fs:: read_dir ( config_dir ) . await {
116+ Err ( dir_error) . context ( format ! ( "failed to read {config_dir }" ) )
117117 } else {
118118 Err ( e) . context ( format ! ( "failed to read mapper configuration from {path}" ) )
119119 }
@@ -132,34 +132,41 @@ impl TEdgeConfigDto {
132132 use futures:: StreamExt ;
133133 use futures:: TryStreamExt ;
134134
135- let mappers_dir = location. tedge_config_root_path ( ) . join ( "mappers" ) ;
135+ let mappers_dir = location. mappers_config_dir ( ) ;
136136 let all_profiles = location. mapper_config_profiles :: < T > ( ) . await ;
137137 let ty = T :: expected_cloud_type ( ) ;
138138 if let Some ( profiles) = all_profiles {
139+ let config_paths = location. config_path :: < T > ( ) ;
140+ let default_profile_path = config_paths. path_for ( None :: < & ProfileName > ) ;
141+
139142 if !dto. is_default ( ) {
140- tracing:: warn!( "{ty} configuration found in `tedge.toml`, but this will be ignored in favour of configuration in {mappers_dir}/{ty}.toml and {mappers_dir}/{ty}.d" )
143+ let wildcard_profile_path = config_paths. path_for ( Some ( "*" ) ) ;
144+ tracing:: warn!( "{ty} configuration found in `tedge.toml`, but this will be ignored in favour of configuration in {default_profile_path} and {wildcard_profile_path}" )
141145 }
142- let toml_path = mappers_dir. join ( format ! ( "{ty}.toml" ) ) ;
143- let default_profile_toml = read_file_if_exists ( & toml_path) . await ?;
146+
147+ let default_profile_toml =
148+ read_file_if_exists ( & default_profile_path, & config_paths. base_dir ) . await ?;
144149 let mut default_profile_config: T :: CloudDto = default_profile_toml. map_or_else (
145150 || Ok ( <_ >:: default ( ) ) ,
146151 |toml| {
147152 toml:: from_str ( & toml) . with_context ( || {
148- format ! ( "failed to deserialise mapper config in {toml_path }" )
153+ format ! ( "failed to deserialise mapper config in {default_profile_path }" )
149154 } )
150155 } ,
151156 ) ?;
152- default_profile_config. set_mapper_config_dir ( mappers_dir. clone ( ) ) ;
157+ default_profile_config. set_mappers_root_dir ( mappers_dir. clone ( ) ) ;
158+ default_profile_config. set_mapper_config_file ( default_profile_path) ;
153159 dto. non_profile = default_profile_config;
154160
155- dto. profiles = profiles
161+ dto. profiles = futures :: stream :: iter ( profiles)
156162 . filter_map ( futures:: future:: ready)
157163 . then ( |profile| async {
158- let toml_path = mappers_dir . join ( format ! ( "{ty}.d/{ profile}.toml" ) ) ;
164+ let toml_path = config_paths . path_for ( Some ( & profile) ) ;
159165 let profile_toml = tokio:: fs:: read_to_string ( & toml_path) . await ?;
160166 let mut profiled_config: T :: CloudDto = toml:: from_str ( & profile_toml)
161167 . context ( "failed to deserialise mapper config" ) ?;
162- profiled_config. set_mapper_config_dir ( mappers_dir. clone ( ) ) ;
168+ profiled_config. set_mappers_root_dir ( mappers_dir. clone ( ) ) ;
169+ profiled_config. set_mapper_config_file ( toml_path) ;
163170 Ok :: < _ , anyhow:: Error > ( ( profile, profiled_config) )
164171 } )
165172 . try_collect ( )
@@ -227,10 +234,6 @@ impl TEdgeConfig {
227234 ) ?)
228235 }
229236
230- pub fn profiled_config_directories ( & self ) -> impl Iterator < Item = Utf8PathBuf > + use < ' _ > {
231- CloudType :: iter ( ) . map ( |ty| self . location . mappers_config_dir ( ) . join ( format ! ( "{ty}.d" ) ) )
232- }
233-
234237 fn all_profiles < ' a , T > ( & ' a self ) -> Box < dyn Iterator < Item = Option < ProfileName > > + ' a >
235238 where
236239 T : ExpectedCloudType ,
@@ -421,6 +424,12 @@ impl CloudConfig for DynCloudConfig<'_> {
421424 Self :: Borrow ( config) => config. key_pin ( ) ,
422425 }
423426 }
427+ fn mapper_config_location ( & self ) -> & Utf8Path {
428+ match self {
429+ Self :: Arc ( config) => config. mapper_config_location ( ) ,
430+ Self :: Borrow ( config) => config. mapper_config_location ( ) ,
431+ }
432+ }
424433}
425434
426435/// The keys that can be read from the configuration
@@ -439,12 +448,6 @@ pub static READABLE_KEYS: Lazy<Vec<(Cow<'static, str>, doku::Type)>> = Lazy::new
439448 struct_field_paths ( None , & fields)
440449} ) ;
441450
442- #[ derive( Debug , Clone , PartialEq , Eq , Deserialize , doku:: Document , serde:: Serialize ) ]
443- pub enum MapperConfigLocation {
444- TedgeToml ,
445- SeparateFile ( #[ doku( as = "String" ) ] camino:: Utf8PathBuf ) ,
446- }
447-
448451define_tedge_config ! {
449452 #[ tedge_config( reader( skip) ) ]
450453 config: {
@@ -576,6 +579,10 @@ define_tedge_config! {
576579 #[ serde( skip) ]
577580 mapper_config_dir: Utf8PathBuf ,
578581
582+ #[ tedge_config( reader( skip) ) ]
583+ #[ serde( skip) ]
584+ mapper_config_file: Utf8PathBuf ,
585+
579586 /// Endpoint URL of Cumulocity tenant
580587 #[ tedge_config( example = "your-tenant.cumulocity.com" ) ]
581588 // Config consumers should use `c8y.http`/`c8y.mqtt` as appropriate, hence this field is private
@@ -820,6 +827,10 @@ define_tedge_config! {
820827 #[ serde( skip) ]
821828 mapper_config_dir: Utf8PathBuf ,
822829
830+ #[ tedge_config( reader( skip) ) ]
831+ #[ serde( skip) ]
832+ mapper_config_file: Utf8PathBuf ,
833+
823834 /// Endpoint URL of Azure IoT tenant
824835 #[ tedge_config( example = "myazure.azure-devices.net" ) ]
825836 url: ConnectUrl ,
@@ -911,6 +922,10 @@ define_tedge_config! {
911922 #[ serde( skip) ]
912923 mapper_config_dir: Utf8PathBuf ,
913924
925+ #[ tedge_config( reader( skip) ) ]
926+ #[ serde( skip) ]
927+ mapper_config_file: Utf8PathBuf ,
928+
914929 /// Endpoint URL of AWS IoT tenant
915930 #[ tedge_config( example = "your-endpoint.amazonaws.com" ) ]
916931 url: ConnectUrl ,
@@ -1411,6 +1426,7 @@ pub trait CloudConfig {
14111426 fn root_cert_path ( & self ) -> & Utf8Path ;
14121427 fn key_uri ( & self ) -> Option < Arc < str > > ;
14131428 fn key_pin ( & self ) -> Option < Arc < str > > ;
1429+ fn mapper_config_location ( & self ) -> & Utf8Path ;
14141430}
14151431
14161432impl < T : SpecialisedCloudConfig > CloudConfig for MapperConfig < T > {
@@ -1433,6 +1449,10 @@ impl<T: SpecialisedCloudConfig> CloudConfig for MapperConfig<T> {
14331449 fn key_pin ( & self ) -> Option < Arc < str > > {
14341450 self . device . key_pin . clone ( )
14351451 }
1452+
1453+ fn mapper_config_location ( & self ) -> & Utf8Path {
1454+ & self . location
1455+ }
14361456}
14371457
14381458fn c8y_topic_prefix ( ) -> TopicPrefix {
@@ -1816,7 +1836,8 @@ mod tests {
18161836 async fn mapper_config_reads_non_profiled_mapper ( ) {
18171837 let ttd = TempTedgeDir :: new ( ) ;
18181838 ttd. dir ( "mappers" )
1819- . file ( "c8y.toml" )
1839+ . dir ( "c8y" )
1840+ . file ( "tedge.toml" )
18201841 . with_toml_content ( toml:: toml! {
18211842 url = "example.com"
18221843
@@ -1838,8 +1859,8 @@ mod tests {
18381859 async fn mapper_config_reads_profiled_mapper ( ) {
18391860 let ttd = TempTedgeDir :: new ( ) ;
18401861 ttd. dir ( "mappers" )
1841- . dir ( "c8y.d " )
1842- . file ( "myprofile .toml" )
1862+ . dir ( "c8y.myprofile " )
1863+ . file ( "tedge .toml" )
18431864 . with_toml_content ( toml:: toml! {
18441865 url = "example.com"
18451866
@@ -1861,8 +1882,8 @@ mod tests {
18611882 async fn mapper_config_fails_if_profile_is_not_migrated_but_directory_exists ( ) {
18621883 let ttd = TempTedgeDir :: new ( ) ;
18631884 ttd. dir ( "mappers" )
1864- . dir ( "c8y.d " )
1865- . file ( "myprofile .toml" )
1885+ . dir ( "c8y.myprofile " )
1886+ . file ( "tedge .toml" )
18661887 . with_toml_content ( toml:: toml! {
18671888 url = "example.com"
18681889
@@ -1882,7 +1903,8 @@ mod tests {
18821903 async fn mapper_config_fails_if_profile_is_not_migrated_but_default_profile_is ( ) {
18831904 let ttd = TempTedgeDir :: new ( ) ;
18841905 ttd. dir ( "mappers" )
1885- . file ( "c8y.toml" )
1906+ . dir ( "c8y" )
1907+ . file ( "tedge.toml" )
18861908 . with_toml_content ( toml:: toml! {
18871909 url = "example.com"
18881910
@@ -1936,7 +1958,7 @@ mod tests {
19361958 async fn mapper_config_falls_back_to_tedge_toml_config_if_only_another_cloud_is_using_new_format (
19371959 ) {
19381960 let ttd = TempTedgeDir :: new ( ) ;
1939- ttd. dir ( "mappers" ) . file ( "c8y.toml" ) ;
1961+ ttd. dir ( "mappers" ) . dir ( "c8y" ) . file ( "tedge .toml") ;
19401962 ttd. file ( "tedge.toml" ) . with_toml_content ( toml:: toml! {
19411963 az. url = "az.url"
19421964 } ) ;
@@ -1987,7 +2009,8 @@ mod tests {
19872009 async fn all_mapper_configs_succeeds_when_profile_directory_does_not_exist ( ) {
19882010 let ttd = TempTedgeDir :: new ( ) ;
19892011 ttd. dir ( "mappers" )
1990- . file ( "c8y.toml" )
2012+ . dir ( "c8y" )
2013+ . file ( "tedge.toml" )
19912014 . with_toml_content ( toml:: toml! {
19922015 url = "example.com"
19932016 } ) ;
@@ -2007,18 +2030,21 @@ mod tests {
20072030 async fn all_mapper_configs_includes_both_default_and_profiles_when_directory_exists ( ) {
20082031 let ttd = TempTedgeDir :: new ( ) ;
20092032 let mappers_dir = ttd. dir ( "mappers" ) ;
2010- mappers_dir. file ( "c8y.toml" ) . with_toml_content ( toml:: toml! {
2011- url = "default.example.com"
2012- } ) ;
20132033 mappers_dir
2014- . dir ( "c8y.d" )
2015- . file ( "profile1.toml" )
2034+ . dir ( "c8y" )
2035+ . file ( "tedge.toml" )
2036+ . with_toml_content ( toml:: toml! {
2037+ url = "default.example.com"
2038+ } ) ;
2039+ mappers_dir
2040+ . dir ( "c8y.profile1" )
2041+ . file ( "tedge.toml" )
20162042 . with_toml_content ( toml:: toml! {
20172043 url = "profile1.example.com"
20182044 } ) ;
20192045 mappers_dir
2020- . dir ( "c8y.d " )
2021- . file ( "profile2 .toml" )
2046+ . dir ( "c8y.profile2 " )
2047+ . file ( "tedge .toml" )
20222048 . with_toml_content ( toml:: toml! {
20232049 url = "profile2.example.com"
20242050 } ) ;
@@ -2049,13 +2075,15 @@ mod tests {
20492075 let ttd = TempTedgeDir :: new ( ) ;
20502076 // C8y mapper with no profile directory
20512077 ttd. dir ( "mappers" )
2052- . file ( "c8y.toml" )
2078+ . dir ( "c8y" )
2079+ . file ( "tedge.toml" )
20532080 . with_toml_content ( toml:: toml! {
20542081 url = "c8y.example.com"
20552082 } ) ;
20562083 // Az mapper with no profile directory
20572084 ttd. dir ( "mappers" )
2058- . file ( "az.toml" )
2085+ . dir ( "az" )
2086+ . file ( "tedge.toml" )
20592087 . with_toml_content ( toml:: toml! {
20602088 url = "az.example.com"
20612089 } ) ;
0 commit comments