summaryrefslogtreecommitdiffstats
path: root/crates
diff options
context:
space:
mode:
authorRina Fujino <18257209+rina23q@users.noreply.github.com>2022-05-20 11:43:08 +0200
committerGitHub <noreply@github.com>2022-05-20 11:43:08 +0200
commitc48f29172f72ec0052aa88747665c6f6e00d11fc (patch)
treef7315f0691a6ef4ada8155e51566fcb0cd388c61 /crates
parent7afc83cd0ef4659f44bc7c8969f7f63cc535d721 (diff)
parent840c42b2d7fc320ff7ce3366d805bae249979bd2 (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.rs8
-rw-r--r--crates/common/tedge_utils/Cargo.toml1
-rw-r--r--crates/common/tedge_utils/src/file.rs285
-rw-r--r--crates/core/c8y_api/src/http_proxy.rs15
-rw-r--r--crates/core/c8y_smartrest/src/smartrest_deserializer.rs2
-rw-r--r--crates/core/c8y_smartrest/src/smartrest_serializer.rs29
-rw-r--r--crates/core/tedge_mapper/src/c8y/tests.rs1
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())
}