diff options
-rw-r--r-- | Cargo.lock | 72 | ||||
-rw-r--r-- | Cargo.toml | 1 | ||||
-rw-r--r-- | sm/plugins/tedge_apama_plugin/Cargo.toml | 15 | ||||
-rw-r--r-- | sm/plugins/tedge_apama_plugin/src/error.rs | 26 | ||||
-rw-r--r-- | sm/plugins/tedge_apama_plugin/src/main.rs | 175 |
5 files changed, 289 insertions, 0 deletions
@@ -305,6 +305,27 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c4872d67bab6358e59559027aa3b9157c53d9358c51423c17554809a8858e0f8" [[package]] +name = "bzip2" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6afcd980b5f3a45017c57e57a2fcccbb351cc43a356ce117ef760ef8052b89b0" +dependencies = [ + "bzip2-sys", + "libc", +] + +[[package]] +name = "bzip2-sys" +version = "0.1.11+1.0.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "736a955f3fa7875102d57c82b8cac37ec45224a07fd32d58f9f7a186b6cd4cdc" +dependencies = [ + "cc", + "libc", + "pkg-config", +] + +[[package]] name = "c8y_smartrest" version = "0.4.2" dependencies = [ @@ -464,6 +485,15 @@ dependencies = [ ] [[package]] +name = "crc32fast" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81156fece84ab6a9f2afdb109ce3ae577e42b1228441eded99bd77f627953b1a" +dependencies = [ + "cfg-if 1.0.0", +] + +[[package]] name = "criterion" version = "0.3.5" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -743,6 +773,18 @@ dependencies = [ ] [[package]] +name = "flate2" +version = "1.0.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e6988e897c1c9c485f43b47a529cef42fde0547f9d8d41a7062518f1d8fc53f" +dependencies = [ + "cfg-if 1.0.0", + "crc32fast", + "libc", + "miniz_oxide", +] + +[[package]] name = "float-cmp" version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -1746,6 +1788,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" [[package]] +name = "pkg-config" +version = "0.3.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "12295df4f294471248581bc09bef3c38a5e46f1e36d6a37353621a0c6c357e1f" + +[[package]] name = "plotters" version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -2639,6 +2687,16 @@ dependencies = [ ] [[package]] +name = "tedge_apama_plugin" +version = "0.4.2" +dependencies = [ + "serde", + "structopt", + "thiserror", + "zip", +] + +[[package]] name = "tedge_apt_plugin" version = "0.4.2" dependencies = [ @@ -3444,3 +3502,17 @@ name = "zeroize" version = "1.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bf68b08513768deaa790264a7fac27a58cbf2705cfcdc9448362229217d7e970" + +[[package]] +name = "zip" +version = "0.5.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93ab48844d61251bb3835145c521d88aa4031d7139e8485990f60ca911fa0815" +dependencies = [ + "byteorder", + "bzip2", + "crc32fast", + "flate2", + "thiserror", + "time", +] @@ -20,6 +20,7 @@ members = [ "sm/tedge_agent", "sm/plugins/tedge_apt_plugin", "sm/plugins/tedge_dummy_plugin", + "sm/plugins/tedge_apama_plugin", "tedge_config", "tedge", ] diff --git a/sm/plugins/tedge_apama_plugin/Cargo.toml b/sm/plugins/tedge_apama_plugin/Cargo.toml new file mode 100644 index 00000000..958e2e4b --- /dev/null +++ b/sm/plugins/tedge_apama_plugin/Cargo.toml @@ -0,0 +1,15 @@ +[package] +name = "tedge_apama_plugin" +version = "0.4.2" +authors = ["thin-edge.io team <info@thin-edge.io>"] +edition = "2018" +license = "Apache-2.0" +description = "thin.edge.io plugin for installing apama projects" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +structopt = "0.3" +thiserror = "1.0" +serde = { version = "1", features = ["derive"] } +zip = "0.5" diff --git a/sm/plugins/tedge_apama_plugin/src/error.rs b/sm/plugins/tedge_apama_plugin/src/error.rs new file mode 100644 index 00000000..0e32850c --- /dev/null +++ b/sm/plugins/tedge_apama_plugin/src/error.rs @@ -0,0 +1,26 @@ +#[derive(thiserror::Error, Debug)] +pub enum InternalError { + #[error("Fail to run `{cmd}`: {from}")] + ExecError { cmd: String, from: std::io::Error }, + + #[error(transparent)] + FromIo(#[from] std::io::Error), + + #[error(transparent)] + FromUtf8(#[from] std::string::FromUtf8Error), + + #[error(transparent)] + FromZipError(#[from] zip::result::ZipError), + + #[error("Apama not installed at /opt/softwareag/Apama")] + ApamaNotInstalled, +} + +impl InternalError { + pub fn exec_error(cmd: impl Into<String>, from: std::io::Error) -> InternalError { + InternalError::ExecError { + cmd: cmd.into(), + from, + } + } +} diff --git a/sm/plugins/tedge_apama_plugin/src/main.rs b/sm/plugins/tedge_apama_plugin/src/main.rs new file mode 100644 index 00000000..87015d76 --- /dev/null +++ b/sm/plugins/tedge_apama_plugin/src/main.rs @@ -0,0 +1,175 @@ +mod error; + +use crate::error::InternalError; +use std::fs::{self, File}; +use std::os::unix::prelude::ExitStatusExt; +use std::path::Path; +use std::process::{Command, ExitStatus, Stdio}; +use structopt::StructOpt; + +/// This plugin supports the installation, updation and removal of a single unversioned apama project named "project". +/// Installation of multiple parallel projects is not supported. +/// Installing a project will replace the existing project with the new one. +/// Delta update of a project(for eg: updating just the `mon` file definitions in the project) is not supported either. +#[derive(StructOpt)] +struct ApamaCli { + #[structopt(subcommand)] + operation: PluginOp, +} + +#[derive(StructOpt)] +pub enum PluginOp { + /// List the one and only apama project if one is installed + List, + + /// Install an apama project + Install { + module: String, + #[structopt(short = "v", long = "--module-version")] + version: Option<String>, + #[structopt(long = "--file")] + file_path: String, + }, + + /// Remove an apama project + Remove { + module: String, + #[structopt(short = "v", long = "--module-version")] + version: Option<String>, + }, + + /// Prepare a sequences of install/remove commands + Prepare, + + /// Finalize a sequences of install/remove commands + Finalize, +} + +const APAMA_ENV_EXE: &str = "/opt/softwareag/Apama/bin/apama_env"; +const TEDGE_APAMA_PROJECT_DIR: &str = "/etc/tedge/apama/project"; +const TMP_APAMA_PROJECT_DIR: &str = "/tmp/tedge_apama_project"; +const APAMA_PROJECT_NAME: &str = "project"; + +fn run(operation: PluginOp) -> Result<ExitStatus, InternalError> { + let success = ExitStatus::from_raw(0); + + let tedge_env_exe_path = Path::new(APAMA_ENV_EXE); + + if !tedge_env_exe_path.exists() { + return Err(InternalError::ApamaNotInstalled); + } + + let tedge_apama_project_path = Path::new(TEDGE_APAMA_PROJECT_DIR); + let tmp_apama_project_path = Path::new(TMP_APAMA_PROJECT_DIR); + + let status = match operation { + // Since there can only be a single project named `project`, print its name if installed + PluginOp::List => { + if tedge_apama_project_path.exists() { + println!("{}\t", APAMA_PROJECT_NAME) + } + success + } + + PluginOp::Prepare => success, + + PluginOp::Finalize => { + // Cleanup any temporary artefacts created by this plugin + if tmp_apama_project_path.exists() { + fs::remove_dir_all(tmp_apama_project_path)?; + } + success + } + + PluginOp::Install { + module: _, + version: _, + file_path, + } => { + let archive_path = Path::new(&file_path); + let archive_file = File::open(&archive_path)?; + + let mut archive = zip::ZipArchive::new(archive_file)?; + + // TODO: Validate the zip to be valid apama project before extraction? + println!("Extracting the archive at {}", file_path); + archive.extract(tmp_apama_project_path)?; + println!("Extraction successful"); + + // Deleting existing project as the rename API expects the target dir to be empty + if tedge_apama_project_path.exists() { + println!("Removing existing project at {}", TEDGE_APAMA_PROJECT_DIR); + fs::remove_dir_all(tedge_apama_project_path)?; + println!("Removal of existing project successful"); + } + + println!( + "Installing newly extracted project to {}", + TEDGE_APAMA_PROJECT_DIR + ); + fs::create_dir_all(tedge_apama_project_path)?; + fs::rename(tmp_apama_project_path, tedge_apama_project_path)?; + println!("Installation of new project successful"); + + println!("Restarting apama to load the new project"); + run_cmd("service", "apama restart")?; + println!("Restart of apama service successful"); + + success + } + + PluginOp::Remove { + module: _, + version: _, + } => { + if tedge_apama_project_path.exists() { + println!("Stopping apama service"); + run_cmd("service", "apama stop")?; + println!("Stopping apama service successful"); + + println!("Removing existing project at {}", TEDGE_APAMA_PROJECT_DIR); + fs::remove_dir_all(tedge_apama_project_path)?; + println!("Removal of existing project successful"); + } + + success + } + }; + + Ok(status) +} + +fn run_cmd(cmd: &str, args: &str) -> Result<ExitStatus, InternalError> { + let args: Vec<&str> = args.split_whitespace().collect(); + let status = Command::new(cmd) + .args(args) + .stdin(Stdio::null()) + .status() + .map_err(|err| InternalError::exec_error(cmd, err))?; + Ok(status) +} + +fn main() { + // On usage error, the process exits with a status code of 1 + let apama = ApamaCli::from_args(); + + match run(apama.operation) { + Ok(status) if status.success() => { + std::process::exit(0); + } + + Ok(status) => { + if status.code().is_some() { + std::process::exit(2); + } else { + eprintln!("Interrupted by a signal!"); + std::process::exit(4); + } + } + + Err(err) => { + eprintln!("ERROR: {}", err); + std::process::exit(5); + } + } +} |