diff options
author | Rina Fujino <18257209+rina23q@users.noreply.github.com> | 2022-05-20 11:43:08 +0200 |
---|---|---|
committer | GitHub <noreply@github.com> | 2022-05-20 11:43:08 +0200 |
commit | c48f29172f72ec0052aa88747665c6f6e00d11fc (patch) | |
tree | f7315f0691a6ef4ada8155e51566fcb0cd388c61 /crates | |
parent | 7afc83cd0ef4659f44bc7c8969f7f63cc535d721 (diff) | |
parent | 840c42b2d7fc320ff7ce3366d805bae249979bd2 (diff) |
Merge pull request #1145 from rina23q/feature/1107/enhance-config-management
Finalize config management
Diffstat (limited to 'crates')
-rw-r--r-- | crates/common/mqtt_channel/src/topics.rs | 8 | ||||
-rw-r--r-- | crates/common/tedge_utils/Cargo.toml | 1 | ||||
-rw-r--r-- | crates/common/tedge_utils/src/file.rs | 285 | ||||
-rw-r--r-- | crates/core/c8y_api/src/http_proxy.rs | 15 | ||||
-rw-r--r-- | crates/core/c8y_smartrest/src/smartrest_deserializer.rs | 2 | ||||
-rw-r--r-- | crates/core/c8y_smartrest/src/smartrest_serializer.rs | 29 | ||||
-rw-r--r-- | crates/core/tedge_mapper/src/c8y/tests.rs | 1 |
7 files changed, 255 insertions, 86 deletions
diff --git a/crates/common/mqtt_channel/src/topics.rs b/crates/common/mqtt_channel/src/topics.rs index fe4fca67..156376dc 100644 --- a/crates/common/mqtt_channel/src/topics.rs +++ b/crates/common/mqtt_channel/src/topics.rs @@ -74,7 +74,7 @@ impl TopicFilter { } } - /// Check if the pattern is valid and at it to this topic filter. + /// Check if the pattern is valid and add it to this topic filter. pub fn add(&mut self, pattern: &str) -> Result<(), MqttError> { let pattern = String::from(pattern); if rumqttc::valid_filter(&pattern) { @@ -85,6 +85,12 @@ impl TopicFilter { } } + /// Assuming the pattern is valid and add it to this topic filter. + pub fn add_unchecked(&mut self, pattern: &str) { + let pattern = String::from(pattern); + self.patterns.push(pattern); + } + /// Add all the other topics to this one. pub fn add_all(&mut self, other: TopicFilter) { for pattern in other.patterns { diff --git a/crates/common/tedge_utils/Cargo.toml b/crates/common/tedge_utils/Cargo.toml index e5767cb5..95329275 100644 --- a/crates/common/tedge_utils/Cargo.toml +++ b/crates/common/tedge_utils/Cargo.toml @@ -25,6 +25,7 @@ users = "0.11.0" [dev-dependencies] assert_matches = "1.5" +tokio = { version = "1.12", features = [ "rt-multi-thread"] } whoami = "1.2.1" diff --git a/crates/common/tedge_utils/src/file.rs b/crates/common/tedge_utils/src/file.rs index cc819e05..6b714750 100644 --- a/crates/common/tedge_utils/src/file.rs +++ b/crates/common/tedge_utils/src/file.rs @@ -2,6 +2,7 @@ use nix::unistd::*; use std::fs::File; use std::os::linux::fs::MetadataExt; use std::os::unix::fs::PermissionsExt; +use std::path::{Path, PathBuf}; use std::{fs, io}; use users::{get_group_by_name, get_user_by_name}; @@ -24,6 +25,9 @@ pub enum FileError { #[error(transparent)] Errno(#[from] nix::errno::Errno), + + #[error("The path is not accessible. {path:?}")] + PathNotAccessible { path: PathBuf }, } pub fn create_directory_with_user_group( @@ -32,22 +36,14 @@ pub fn create_directory_with_user_group( group: &str, mode: u32, ) -> Result<(), FileError> { - match fs::create_dir(dir) { - Ok(_) => { - change_owner_and_permission(dir, user, group, mode)?; - } + let perm_entry = PermissionEntry::new(Some(user.into()), Some(group.into()), Some(mode)); + let () = perm_entry.create_directory(Path::new(dir))?; + Ok(()) +} - Err(e) => { - if e.kind() == io::ErrorKind::AlreadyExists { - return Ok(()); - } else { - return Err(FileError::DirectoryCreateFailed { - dir: dir.to_string(), - from: e, - }); - } - } - } +pub fn create_directory_with_mode(dir: &str, mode: u32) -> Result<(), FileError> { + let perm_entry = PermissionEntry::new(None, None, Some(mode)); + let () = perm_entry.create_directory(Path::new(dir))?; Ok(()) } @@ -57,36 +53,87 @@ pub fn create_file_with_user_group( group: &str, mode: u32, ) -> Result<(), FileError> { - match File::create(file) { - Ok(_) => { - change_owner_and_permission(file, user, group, mode)?; + let perm_entry = PermissionEntry::new(Some(user.into()), Some(group.into()), Some(mode)); + let () = perm_entry.create_file(Path::new(file))?; + Ok(()) +} + +pub fn create_file_with_mode(file: &str, mode: u32) -> Result<(), FileError> { + let perm_entry = PermissionEntry::new(None, None, Some(mode)); + let () = perm_entry.create_file(Path::new(file))?; + Ok(()) +} + +#[derive(Debug, PartialEq, Eq, Default, Clone)] +pub struct PermissionEntry { + pub user: Option<String>, + pub group: Option<String>, + pub mode: Option<u32>, +} + +impl PermissionEntry { + pub fn new(user: Option<String>, group: Option<String>, mode: Option<u32>) -> Self { + Self { user, group, mode } + } + + pub fn apply(&self, path: &Path) -> Result<(), FileError> { + match (&self.user, &self.group) { + (Some(user), Some(group)) => { + let () = change_user_and_group(path, user, group)?; + } + (Some(user), None) => { + let () = change_user(path, user)?; + } + (None, Some(group)) => { + let () = change_group(path, group)?; + } + (None, None) => {} + } + + if let Some(mode) = &self.mode { + let () = change_mode(path, *mode)?; + } + + Ok(()) + } + + fn create_directory(&self, dir: &Path) -> Result<(), FileError> { + match fs::create_dir(dir) { + Ok(_) => { + let () = self.apply(dir)?; + Ok(()) + } + Err(e) if e.kind() == io::ErrorKind::AlreadyExists => Ok(()), + Err(e) => Err(FileError::DirectoryCreateFailed { + dir: dir.display().to_string(), + from: e, + }), } - Err(e) => { - if e.kind() == io::ErrorKind::AlreadyExists { - return Ok(()); - } else { - return Err(FileError::FileCreateFailed { - file: file.to_string(), - from: e, - }); + } + + fn create_file(&self, file: &Path) -> Result<(), FileError> { + match File::create(file) { + Ok(_) => { + let () = self.apply(file)?; + Ok(()) } + Err(e) if e.kind() == io::ErrorKind::AlreadyExists => Ok(()), + Err(e) => Err(FileError::FileCreateFailed { + file: file.display().to_string(), + from: e, + }), } } - Ok(()) } -fn change_owner_and_permission( - file: &str, - user: &str, - group: &str, - mode: u32, -) -> Result<(), FileError> { +fn change_user_and_group(file: &Path, user: &str, group: &str) -> Result<(), FileError> { let ud = match get_user_by_name(user) { Some(user) => user.uid(), None => { return Err(FileError::UserNotFound { user: user.into() }); } }; + let uid = get_metadata(Path::new(file))?.st_uid(); let gd = match get_group_by_name(group) { Some(group) => group.gid(), @@ -96,115 +143,193 @@ fn change_owner_and_permission( }); } }; + let gid = get_metadata(Path::new(file))?.st_gid(); - let uid = fs::metadata(file) - .map_err(|e| FileError::MetaDataError { - name: file.to_string(), - from: e, - })? - .st_uid(); - let gid = fs::metadata(file) - .map_err(|e| FileError::MetaDataError { - name: file.to_string(), - from: e, - })? - .st_gid(); - - // if user and group is same as existing, then do not change + // if user and group are same as existing, then do not change if (ud != uid) && (gd != gid) { - chown( - file, - Some(Uid::from_raw(ud.into())), - Some(Gid::from_raw(gd.into())), - )?; + chown(file, Some(Uid::from_raw(ud)), Some(Gid::from_raw(gd)))?; } - let mut perm = fs::metadata(file) - .map_err(|e| FileError::MetaDataError { - name: file.to_string(), - from: e, - })? - .permissions(); + Ok(()) +} + +fn change_user(file: &Path, user: &str) -> Result<(), FileError> { + let ud = match get_user_by_name(user) { + Some(user) => user.uid(), + None => { + return Err(FileError::UserNotFound { user: user.into() }); + } + }; + + let uid = get_metadata(Path::new(file))?.st_uid(); + + // if user is same as existing, then do not change + if ud != uid { + chown(file, Some(Uid::from_raw(ud)), None)?; + } + + Ok(()) +} + +fn change_group(file: &Path, group: &str) -> Result<(), FileError> { + let gd = match get_group_by_name(group) { + Some(group) => group.gid(), + None => { + return Err(FileError::GroupNotFound { + group: group.into(), + }); + } + }; + + let gid = get_metadata(Path::new(file))?.st_gid(); + + // if group is same as existing, then do not change + if gd != gid { + chown(file, None, Some(Gid::from_raw(gd)))?; + } + + Ok(()) +} + +fn change_mode(file: &Path, mode: u32) -> Result<(), FileError> { + let mut perm = get_metadata(Path::new(file))?.permissions(); perm.set_mode(mode); fs::set_permissions(file, perm).map_err(|e| FileError::MetaDataError { - name: file.to_string(), + name: file.display().to_string(), from: e, })?; Ok(()) } + +/// Return metadata when the given path exists and accessible by user +pub fn get_metadata(path: &Path) -> Result<fs::Metadata, FileError> { + fs::metadata(&path).map_err(|_| FileError::PathNotAccessible { + path: path.to_path_buf(), + }) +} + +/// Return filename if the given path contains a filename +pub fn get_filename(path: PathBuf) -> Option<String> { + let filename = path.file_name()?.to_str()?.to_string(); + Some(filename) +} + #[cfg(test)] mod tests { use super::*; use std::os::unix::fs::PermissionsExt; use std::path::Path; + use tempfile::TempDir; #[test] fn create_file_correct_user_group() { + let temp_dir = TempDir::new().unwrap(); + let file_path = temp_dir.path().join("file").display().to_string(); + let user = whoami::username(); - let _ = create_file_with_user_group("/tmp/fcreate_test", &user, &user, 0o644).unwrap(); - assert!(Path::new("/tmp/fcreate_test").exists()); - let meta = std::fs::metadata("/tmp/fcreate_test").unwrap(); + let _ = create_file_with_user_group(file_path.as_str(), &user, &user, 0o644).unwrap(); + assert!(Path::new(file_path.as_str()).exists()); + let meta = std::fs::metadata(file_path.as_str()).unwrap(); let perm = meta.permissions(); println!("{:o}", perm.mode()); assert!(format!("{:o}", perm.mode()).contains("644")); - fs::remove_file("/tmp/fcreate_test").unwrap(); } #[test] fn create_file_wrong_user() { + let temp_dir = TempDir::new().unwrap(); + let file_path = temp_dir.path().join("file").display().to_string(); + let user = whoami::username(); - let err = create_file_with_user_group("/tmp/fcreate_wrong_user", "test", &user, 0o775) - .unwrap_err(); + let err = + create_file_with_user_group(file_path.as_str(), "test", &user, 0o775).unwrap_err(); assert!(err.to_string().contains("User not found")); - fs::remove_file("/tmp/fcreate_wrong_user").unwrap(); } #[test] fn create_file_wrong_group() { + let temp_dir = TempDir::new().unwrap(); + let file_path = temp_dir.path().join("file").display().to_string(); + let user = whoami::username(); - let err = create_file_with_user_group("/tmp/fcreate_wrong_group", &user, "test", 0o775) - .unwrap_err(); + let err = + create_file_with_user_group(file_path.as_str(), &user, "test", 0o775).unwrap_err(); assert!(err.to_string().contains("Group not found")); - fs::remove_file("/tmp/fcreate_wrong_group").unwrap(); + fs::remove_file(file_path.as_str()).unwrap(); } #[test] fn create_directory_with_correct_user_group() { + let temp_dir = TempDir::new().unwrap(); + let dir_path = temp_dir.path().join("dir").display().to_string(); + let user = whoami::username(); - let _ = - create_directory_with_user_group("/tmp/fcreate_test_dir", &user, &user, 0o775).unwrap(); + let _ = create_directory_with_user_group(dir_path.as_str(), &user, &user, 0o775).unwrap(); - assert!(Path::new("/tmp/fcreate_test_dir").exists()); - let meta = std::fs::metadata("/tmp/fcreate_test_dir").unwrap(); + assert!(Path::new(dir_path.as_str()).exists()); + let meta = fs::metadata(dir_path.as_str()).unwrap(); let perm = meta.permissions(); println!("{:o}", perm.mode()); assert!(format!("{:o}", perm.mode()).contains("775")); - fs::remove_dir("/tmp/fcreate_test_dir").unwrap(); } #[test] fn create_directory_with_wrong_user() { + let temp_dir = TempDir::new().unwrap(); + let dir_path = temp_dir.path().join("dir").display().to_string(); + let user = whoami::username(); - let err = create_directory_with_user_group("/tmp/wrong_user_dir", "test", &user, 0o775) - .unwrap_err(); + let err = + create_directory_with_user_group(dir_path.as_str(), "test", &user, 0o775).unwrap_err(); assert!(err.to_string().contains("User not found")); - fs::remove_dir("/tmp/wrong_user_dir").unwrap(); } #[test] fn create_directory_with_wrong_group() { + let temp_dir = TempDir::new().unwrap(); + let dir_path = temp_dir.path().join("dir").display().to_string(); + let user = whoami::username(); - let err = create_directory_with_user_group("/tmp/wrong_group_dir", &user, "test", 0o775) - .unwrap_err(); + let err = + create_directory_with_user_group(dir_path.as_str(), &user, "test", 0o775).unwrap_err(); assert!(err.to_string().contains("Group not found")); - fs::remove_dir("/tmp/wrong_group_dir").unwrap(); + } + + #[test] + fn change_file_permissions() { + let temp_dir = TempDir::new().unwrap(); + let file_path = temp_dir.path().join("file").display().to_string(); + + let user = whoami::username(); + let _ = create_file_with_user_group(file_path.as_str(), &user, &user, 0o644).unwrap(); + assert!(Path::new(file_path.as_str()).exists()); + + let meta = fs::metadata(file_path.as_str()).unwrap(); + let perm = meta.permissions(); + assert!(format!("{:o}", perm.mode()).contains("644")); + + let permission_set = PermissionEntry::new(None, None, Some(0o444)); + let () = permission_set.apply(Path::new(file_path.as_str())).unwrap(); + + let meta = fs::metadata(file_path.as_str()).unwrap(); + let perm = meta.permissions(); + assert!(format!("{:o}", perm.mode()).contains("444")); + } + + #[test] + fn verify_get_file_name() { + assert_eq!( + get_filename(PathBuf::from("/it/is/file.txt")), + Some("file.txt".to_string()) + ); + assert_eq!(get_filename(PathBuf::from("/")), None); } } diff --git a/crates/core/c8y_api/src/http_proxy.rs b/crates/core/c8y_api/src/http_proxy.rs index 5bd93f3a..78d8280b 100644 --- a/crates/core/c8y_api/src/http_proxy.rs +++ b/crates/core/c8y_api/src/http_proxy.rs @@ -8,7 +8,8 @@ use c8y_smartrest::{error::SMCumulocityMapperError, smartrest_deserializer::Smar use mockall::automock; use mqtt_channel::{Connection, PubChannel, StreamExt, Topic, TopicFilter}; use reqwest::Url; -use std::{collections::HashMap, path::Path, time::Duration}; +use std::path::Path; +use std::{collections::HashMap, time::Duration}; use tedge_config::{ C8yRootCertPathSetting, C8yUrlSetting, ConfigSettingAccessor, ConfigSettingAccessorStringExt, DeviceIdSetting, MqttBindAddressSetting, MqttPortSetting, TEdgeConfig, @@ -47,6 +48,7 @@ pub trait C8YHttpProxy: Send + Sync { async fn upload_config_file( &mut self, config_path: &Path, + config_type: &str, ) -> Result<String, SMCumulocityMapperError>; } @@ -413,13 +415,14 @@ impl C8YHttpProxy for JwtAuthHttpProxy { async fn upload_config_file( &mut self, config_path: &Path, + config_type: &str, ) -> Result<String, SMCumulocityMapperError> { let token = self.get_jwt_token().await?; // read the config file contents let config_content = std::fs::read_to_string(config_path)?; - let config_file_event = self.create_event(config_path.display().to_string(), None, None); + let config_file_event = self.create_event(config_type.to_string(), None, None); let event_response_id = self.send_event_internal(config_file_event).await?; let binary_upload_event_url = self .end_point @@ -588,12 +591,13 @@ mod tests { .create(); let config_content = "key=value"; + let config_type = "config_type"; let config_file = create_test_config_file_with_content(config_content)?; // Mock endpoint for config upload event creation let _config_file_event_mock = mock("POST", "/event/events/") .match_body(Matcher::PartialJson( - json!({ "type": config_file.path(), "text": config_file.path() }), + json!({ "type": config_type, "text": config_type }), )) .with_status(201) .with_body(json!({ "id": event_id }).to_string()) @@ -615,6 +619,7 @@ mod tests { .returning(|| Ok(SmartRestJwtResponse::default())); let http_client = reqwest::ClientBuilder::new().build().unwrap(); + let mut http_proxy = JwtAuthHttpProxy::new( jwt_token_retriver, http_client, @@ -624,7 +629,9 @@ mod tests { // Upload the config file and assert its binary URL assert_eq!( - http_proxy.upload_config_file(config_file.path()).await?, + http_proxy + .upload_config_file(config_file.path(), config_type) + .await?, mockito::server_url() + config_binary_url_path.as_str() ); diff --git a/crates/core/c8y_smartrest/src/smartrest_deserializer.rs b/crates/core/c8y_smartrest/src/smartrest_deserializer.rs index 362c4521..ff5c177c 100644 --- a/crates/core/c8y_smartrest/src/smartrest_deserializer.rs +++ b/crates/core/c8y_smartrest/src/smartrest_deserializer.rs @@ -244,7 +244,7 @@ pub struct SmartRestConfigUploadRequest { impl SmartRestRequestGeneric for SmartRestConfigUploadRequest {} -#[derive(Debug, Deserialize, Serialize, PartialEq)] +#[derive(Debug, Deserialize, Serialize, PartialEq, Clone)] pub struct SmartRestConfigDownloadRequest { pub message_id: String, pub device: String, diff --git a/crates/core/c8y_smartrest/src/smartrest_serializer.rs b/crates/core/c8y_smartrest/src/smartrest_serializer.rs index 5eabba04..503a345b 100644 --- a/crates/core/c8y_smartrest/src/smartrest_serializer.rs +++ b/crates/core/c8y_smartrest/src/smartrest_serializer.rs @@ -1,6 +1,8 @@ use crate::error::SmartRestSerializerError; +use crate::topic::C8yTopic; use agent_interface::{OperationStatus, SoftwareUpdateResponse}; use csv::{QuoteStyle, WriterBuilder}; +use mqtt_channel::Message; use serde::{Deserialize, Serialize, Serializer}; pub type SmartRest = String; @@ -209,6 +211,33 @@ fn serialize_smartrest<S: Serialize>(record: S) -> Result<String, SmartRestSeria Ok(csv) } +/// Helper to generate a SmartREST operation status message +pub trait TryIntoOperationStatusMessage { + fn executing() -> Result<Message, SmartRestSerializerError> { + let status = Self::status_executing()?; + Ok(Self::create_message(status)) + } + + fn successful(parameter: Option<String>) -> Result<Message, SmartRestSerializerError> { + let status = Self::status_successful(parameter)?; + Ok(Self::create_message(status)) + } + + fn failed(failure_reason: String) -> Result<Message, SmartRestSerializerError> { + let status = Self::status_failed(failure_reason)?; + Ok(Self::create_message(status)) + } + + fn create_message(payload: SmartRest) -> Message { + let topic = C8yTopic::SmartRestResponse.to_topic().unwrap(); // never fail + Message::new(&topic, payload) + } + + fn status_executing() -> Result<SmartRest, SmartRestSerializerError>; + fn status_successful(parameter: Option<String>) -> Result<SmartRest, SmartRestSerializerError>; + fn status_failed(failure_reason: String) -> Result<SmartRest, SmartRestSerializerError>; +} + #[cfg(test)] mod tests { use super::*; diff --git a/crates/core/tedge_mapper/src/c8y/tests.rs b/crates/core/tedge_mapper/src/c8y/tests.rs index e0366934..c15ce8f9 100644 --- a/crates/core/tedge_mapper/src/c8y/tests.rs +++ b/crates/core/tedge_mapper/src/c8y/tests.rs @@ -911,6 +911,7 @@ impl C8YHttpProxy for FakeC8YHttpProxy { async fn upload_config_file( &mut self, _config_path: &Path, + _config_type: &str, ) -> Result<String, SMCumulocityMapperError> { Ok("fake/upload/url".into()) } |