summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--.gitignore1
-rw-r--r--tedge/src/cli.rs5
-rw-r--r--tedge/src/cli/connect/c8y.rs4
-rw-r--r--tedge/src/cli/disconnect/c8y.rs83
-rw-r--r--tedge/src/cli/disconnect/mod.rs28
-rw-r--r--tedge/src/utils/paths.rs11
-rw-r--r--tedge/src/utils/services.rs15
-rw-r--r--tedge/tests/main.rs13
8 files changed, 157 insertions, 3 deletions
diff --git a/.gitignore b/.gitignore
index d819f1fe..020dd2a7 100644
--- a/.gitignore
+++ b/.gitignore
@@ -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();