diff options
-rw-r--r-- | .gitignore | 1 | ||||
-rw-r--r-- | tedge/src/cli.rs | 5 | ||||
-rw-r--r-- | tedge/src/cli/connect/c8y.rs | 4 | ||||
-rw-r--r-- | tedge/src/cli/disconnect/c8y.rs | 83 | ||||
-rw-r--r-- | tedge/src/cli/disconnect/mod.rs | 28 | ||||
-rw-r--r-- | tedge/src/utils/paths.rs | 11 | ||||
-rw-r--r-- | tedge/src/utils/services.rs | 15 | ||||
-rw-r--r-- | tedge/tests/main.rs | 13 |
8 files changed, 157 insertions, 3 deletions
@@ -10,3 +10,4 @@ Cargo.lock **/*.rs.bk .idea/ +.tmp/ diff --git a/tedge/src/cli.rs b/tedge/src/cli.rs index b81b8b6a..82ac0b03 100644 --- a/tedge/src/cli.rs +++ b/tedge/src/cli.rs @@ -4,6 +4,7 @@ use structopt::clap; use structopt::StructOpt; mod connect; +mod disconnect; #[derive(StructOpt, Debug)] #[structopt( @@ -44,6 +45,9 @@ enum TEdgeCmd { /// Connect to connector provider Connect(connect::ConnectCmd), + /// Remove bridge connection for a provider + Disconnect(disconnect::DisconnectCmd), + /// Publish a message on a topic and subscribe a topic. Mqtt(super::mqtt::MqttCmd), } @@ -54,6 +58,7 @@ impl TEdgeCmd { TEdgeCmd::Cert(ref cmd) => cmd, TEdgeCmd::Config(ref cmd) => cmd, TEdgeCmd::Connect(ref cmd) => cmd, + TEdgeCmd::Disconnect(cmd) => cmd, TEdgeCmd::Mqtt(ref cmd) => cmd, } } diff --git a/tedge/src/cli/connect/c8y.rs b/tedge/src/cli/connect/c8y.rs index e0a3d156..f9db9a83 100644 --- a/tedge/src/cli/connect/c8y.rs +++ b/tedge/src/cli/connect/c8y.rs @@ -48,10 +48,10 @@ enum ConnectError { #[error(transparent)] PersistError(#[from] PersistError), - #[error("Couldn't find path to 'sudo'. Update $PATH variable with 'sudo' path. \n{0}")] + #[error("Couldn't find path to 'sudo'. Update $PATH variable with 'sudo' path.\n{0}")] SudoNotFound(#[from] which::Error), - #[error("Provided endpoint url is not valid, provide valid url. \n{0}")] + #[error("Provided endpoint url is not valid, provide valid url.\n{0}")] UrlParse(#[from] url::ParseError), #[error(transparent)] diff --git a/tedge/src/cli/disconnect/c8y.rs b/tedge/src/cli/disconnect/c8y.rs new file mode 100644 index 00000000..3d2f6586 --- /dev/null +++ b/tedge/src/cli/disconnect/c8y.rs @@ -0,0 +1,83 @@ +use structopt::StructOpt; + +use crate::command::Command; +use crate::config::{ConfigError, TEdgeConfig, C8Y_CONNECT, TEDGE_HOME_DIR}; +use crate::utils::{paths, services}; + +const C8Y_CONFIG_FILENAME: &str = "c8y-bridge.conf"; +const TEDGE_BRIDGE_CONF_DIR_PATH: &str = "bridges"; + +#[derive(thiserror::Error, Debug)] +pub enum DisconnectError { + #[error(transparent)] + Configuration(#[from] ConfigError), + + #[error("File operation error. Check permissions for {1}.")] + FileOperationFailed(#[source] std::io::Error, String), + + #[error(transparent)] + IoError(#[from] std::io::Error), + + #[error(transparent)] + PathsError(#[from] paths::PathsError), + + #[error(transparent)] + ServicesError(#[from] services::ServicesError), +} + +#[derive(StructOpt, Debug)] +pub struct Disconnect {} + +impl Command for Disconnect { + fn to_string(&self) -> String { + "execute 'tedge disconnect'.".into() + } + + fn run(&self, _verbose: u8) -> Result<(), anyhow::Error> { + Ok(self.stop_bridge()?) + } +} + +impl Disconnect { + fn stop_bridge(&self) -> Result<(), DisconnectError> { + // Check if bridge exists and stop with code 0 if it doesn't. + let bridge_conf_path = paths::build_path_from_home(&[ + TEDGE_HOME_DIR, + TEDGE_BRIDGE_CONF_DIR_PATH, + C8Y_CONFIG_FILENAME, + ])?; + + println!("Removing c8y bridge.\n"); + match std::fs::remove_file(&bridge_conf_path) { + // If we found the bridge config file we and removed it we also need to update tedge config file + // and carry on to see if we need to restart mosquitto. + Ok(()) => Ok(self.update_tedge_config()?), + + // If bridge config file was not found we assume that the bridge doesn't exist, + // we make sure tedge config 'c8y.connect' is in correct state and we finish early returning exit code 0. + Err(e) if e.kind() == std::io::ErrorKind::NotFound => { + let _ = self.update_tedge_config()?; + println!("Bridge doesn't exist. Operation finished!"); + return Ok(()); + } + + Err(e) => Err(DisconnectError::FileOperationFailed(e, bridge_conf_path)), + }?; + + // Deviation from specification: + // * Check if mosquitto is running, restart only if it was active before, if not don't do anything. + println!("Applying changes to mosquitto.\n"); + if services::check_mosquitto_is_running()? { + services::mosquitto_restart_daemon()?; + } + + println!("Bridge successfully disconnected!"); + Ok(()) + } + + fn update_tedge_config(&self) -> Result<(), DisconnectError> { + let mut config = TEdgeConfig::from_default_config()?; + TEdgeConfig::set_config_value(&mut config, C8Y_CONNECT, "false".into())?; + Ok(TEdgeConfig::write_to_default_config(&config)?) + } +} diff --git a/tedge/src/cli/disconnect/mod.rs b/tedge/src/cli/disconnect/mod.rs new file mode 100644 index 00000000..5eadfcbb --- /dev/null +++ b/tedge/src/cli/disconnect/mod.rs @@ -0,0 +1,28 @@ +use crate::command::Command; +use structopt::StructOpt; + +mod c8y; + +#[derive(StructOpt, Debug)] +pub enum DisconnectCmd { + /// Remove bridge connection to Cumulocity. + C8y(c8y::Disconnect), +} + +impl DisconnectCmd { + fn sub_command(&self) -> &dyn Command { + match self { + DisconnectCmd::C8y(cmd) => cmd, + } + } +} + +impl Command for DisconnectCmd { + fn to_string(&self) -> String { + self.sub_command().to_string() + } + + fn run(&self, verbose: u8) -> Result<(), anyhow::Error> { + self.sub_command().run(verbose) + } +} diff --git a/tedge/src/utils/paths.rs b/tedge/src/utils/paths.rs index b75e11ba..e8fe1f13 100644 --- a/tedge/src/utils/paths.rs +++ b/tedge/src/utils/paths.rs @@ -16,6 +16,9 @@ pub enum PathsError { #[error("User's Home Directory not found.")] HomeDirNotFound, + #[error(transparent)] + IoError(#[from] std::io::Error), + #[error("Path conversion to String failed: {path:?}.")] PathToStringFailed { path: OsString }, @@ -92,4 +95,12 @@ mod tests { assert_eq!(home_dir(), None); std::env::set_var("HOME", home); } + + #[test] + fn pathbuf_to_string_ok() { + let pathbuf: PathBuf = "test".into(); + let expected: String = "test".into(); + let result = pathbuf_to_string(pathbuf).unwrap(); + assert_eq!(result, expected); + } } diff --git a/tedge/src/utils/services.rs b/tedge/src/utils/services.rs index a2fb5e01..a57bb7b0 100644 --- a/tedge/src/utils/services.rs +++ b/tedge/src/utils/services.rs @@ -23,7 +23,7 @@ pub enum ServicesError { #[error(transparent)] IoError(#[from] std::io::Error), - #[error("Couldn't find path to 'sudo'. Update $PATH variable with 'sudo' path. \n{0}")] + #[error("Couldn't find path to 'sudo'. Update $PATH variable with 'sudo' path.\n{0}")] SudoNotFound(#[from] which::Error), #[error( @@ -39,6 +39,7 @@ type ExitCode = i32; const MOSQUITTOCMD_IS_ACTIVE: ExitCode = 130; const MOSQUITTOCMD_SUCCESS: ExitCode = 3; +const SYSTEMCTL_SERVICE_RUNNING: ExitCode = 0; const SYSTEMCTL_SUCCESS: ExitCode = 0; const SYSTEMCTL_STATUS_SUCCESS: ExitCode = 3; @@ -50,6 +51,18 @@ pub fn all_services_available() -> Result<(), ServicesError> { .and_then(|()| mosquitto_is_active_daemon()) } +pub fn check_mosquitto_is_running() -> Result<bool, ServicesError> { + let status = cmd_nullstdio_args_with_code( + SystemCtlCmd::Cmd.as_str(), + &[SystemCtlCmd::IsActive.as_str(), MosquittoCmd::Cmd.as_str()], + )?; + + match status.code() { + Some(SYSTEMCTL_SERVICE_RUNNING) => Ok(true), + _ => Ok(false), + } +} + // Note that restarting a unit with this command does not necessarily flush out all of the unit's resources before it is started again. // For example, the per-service file descriptor storage facility (see FileDescriptorStoreMax= in systemd.service(5)) will remain intact // as long as the unit has a job pending, and is only cleared when the unit is fully stopped and no jobs are pending anymore. diff --git a/tedge/tests/main.rs b/tedge/tests/main.rs index 0400dbd6..bb6bc184 100644 --- a/tedge/tests/main.rs +++ b/tedge/tests/main.rs @@ -224,6 +224,19 @@ mod tests { } #[test] + fn tedge_disconnect_c8y_no_bridge_config() { + let temp_dir = tempfile::tempdir().unwrap(); + let temp_dir_path = temp_dir.path(); + let test_home_str = temp_dir_path.to_str().unwrap(); + + // If file doesn't exist exit code will be 0. + tedge_command_with_test_home(test_home_str, &["disconnect", "c8y"]) + .unwrap() + .assert() + .success(); + } + + #[test] fn run_config_list_all() { let temp_dir = tempfile::tempdir().unwrap(); let test_home_str = temp_dir.path().to_str().unwrap(); |