diff options
author | Lukasz Woznicki <75632179+makr11st@users.noreply.github.com> | 2021-11-24 20:54:56 +0000 |
---|---|---|
committer | GitHub <noreply@github.com> | 2021-11-24 20:54:56 +0000 |
commit | a4ffeccf60090e4456755bc53a6e3b8c8038e855 (patch) | |
tree | 9583f187114913a92866571920dd3bb205bd50a3 /plugins | |
parent | 8217e80670e76dbf9168780f5e0545355a39f8f3 (diff) |
Restructure directories of the workspace (#559)
* Restructure directories of the workspace
* Rename c8y_translator_lib to c8y_translator
* Update comment on how to get dummy plugin path
Signed-off-by: Lukasz Woznicki <lukasz.woznicki@softwareag.com>
Diffstat (limited to 'plugins')
-rw-r--r-- | plugins/tedge_apama_plugin/Cargo.toml | 15 | ||||
-rw-r--r-- | plugins/tedge_apama_plugin/src/error.rs | 26 | ||||
-rw-r--r-- | plugins/tedge_apama_plugin/src/main.rs | 175 | ||||
-rw-r--r-- | plugins/tedge_apt_plugin/Cargo.toml | 28 | ||||
-rw-r--r-- | plugins/tedge_apt_plugin/src/error.rs | 33 | ||||
-rw-r--r-- | plugins/tedge_apt_plugin/src/main.rs | 262 | ||||
-rw-r--r-- | plugins/tedge_apt_plugin/src/module_check.rs | 76 | ||||
-rw-r--r-- | plugins/tedge_apt_plugin/tests/main.rs | 163 | ||||
-rw-r--r-- | plugins/tedge_docker_plugin/tedge_docker_plugin.sh | 112 | ||||
-rw-r--r-- | plugins/tedge_dummy_plugin/Cargo.toml | 11 | ||||
-rw-r--r-- | plugins/tedge_dummy_plugin/src/main.rs | 102 | ||||
-rw-r--r-- | plugins/tedge_dummy_plugin/src/tests.rs | 0 |
12 files changed, 1003 insertions, 0 deletions
diff --git a/plugins/tedge_apama_plugin/Cargo.toml b/plugins/tedge_apama_plugin/Cargo.toml new file mode 100644 index 00000000..bc0c155a --- /dev/null +++ b/plugins/tedge_apama_plugin/Cargo.toml @@ -0,0 +1,15 @@ +[package] +name = "tedge_apama_plugin" +version = "0.4.3" +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/plugins/tedge_apama_plugin/src/error.rs b/plugins/tedge_apama_plugin/src/error.rs new file mode 100644 index 00000000..0e32850c --- /dev/null +++ b/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/plugins/tedge_apama_plugin/src/main.rs b/plugins/tedge_apama_plugin/src/main.rs new file mode 100644 index 00000000..de71c9c1 --- /dev/null +++ b/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, update 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); + } + } +} diff --git a/plugins/tedge_apt_plugin/Cargo.toml b/plugins/tedge_apt_plugin/Cargo.toml new file mode 100644 index 00000000..0d7ca421 --- /dev/null +++ b/plugins/tedge_apt_plugin/Cargo.toml @@ -0,0 +1,28 @@ +[package] +name = "tedge_apt_plugin" +version = "0.4.3" +authors = ["thin-edge.io team <info@thin-edge.io>"] +edition = "2018" +license = "Apache-2.0" +description = "Thin.edge.io plugin for software management using apt" + +[package.metadata.deb] +assets = [ + ["target/release/tedge_apt_plugin", "/etc/tedge/sm-plugins/apt", "755"], +] + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +structopt = "0.3" +thiserror = "1.0" +csv = "1.1" +serde = { version = "1", features = ["derive"] } + +[dev-dependencies] +anyhow = "1.0" +hamcrest2 = "0.3" +reqwest = { version = "0.11", default-features = false, features = [ "blocking", "rustls-tls" ] } +serial_test = "0.5" +tedge_utils = { path = "../../crates/common/tedge_utils" } +test-case = "1.2" diff --git a/plugins/tedge_apt_plugin/src/error.rs b/plugins/tedge_apt_plugin/src/error.rs new file mode 100644 index 00000000..07c8399a --- /dev/null +++ b/plugins/tedge_apt_plugin/src/error.rs @@ -0,0 +1,33 @@ +#[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("Parsing Debian package failed for `{file}`")] + ParsingError { file: String }, + + #[error(transparent)] + FromCsv(#[from] csv::Error), + + #[error("Validation of {package} failed with version mismatch. Installed version: {installed}, Expected version: {expected}")] + VersionMismatch { + package: String, + installed: String, + expected: String, + }, +} + +impl InternalError { + pub fn exec_error(cmd: impl Into<String>, from: std::io::Error) -> InternalError { + InternalError::ExecError { + cmd: cmd.into(), + from, + } + } +} diff --git a/plugins/tedge_apt_plugin/src/main.rs b/plugins/tedge_apt_plugin/src/main.rs new file mode 100644 index 00000000..44a0e0c8 --- /dev/null +++ b/plugins/tedge_apt_plugin/src/main.rs @@ -0,0 +1,262 @@ +mod error; +mod module_check; + +use crate::error::InternalError; +use crate::module_check::PackageMetadata; +use serde::Deserialize; +use std::io::{self}; +use std::process::{Command, ExitStatus, Stdio}; +use structopt::StructOpt; + +#[derive(StructOpt)] +struct AptCli { + #[structopt(subcommand)] + operation: PluginOp, +} + +#[derive(StructOpt)] +pub enum PluginOp { + /// List all the installed modules + List, + + /// Install a module + Install { + module: String, + #[structopt(short = "v", long = "--module-version")] + version: Option<String>, + #[structopt(long = "--file")] + file_path: Option<String>, + }, + + /// Uninstall a module + Remove { + module: String, + #[structopt(short = "v", long = "--module-version")] + version: Option<String>, + }, + + /// Install or remove multiple modules at once + UpdateList, + + /// Prepare a sequences of install/remove commands + Prepare, + + /// Finalize a sequences of install/remove commands + Finalize, +} + +#[derive(Debug, Deserialize)] +#[serde(rename_all = "lowercase")] +enum UpdateAction { + Install, + Remove, +} +#[derive(Debug, Deserialize)] +struct SoftwareModuleUpdate { + pub action: UpdateAction, + pub name: String, + #[serde(default)] + pub version: Option<String>, + #[serde(default)] + pub path: Option<String>, +} + +fn run(operation: PluginOp) -> Result<ExitStatus, InternalError> { + let status = match operation { + PluginOp::List {} => { + let apt = Command::new("apt") + .args(vec!["--manual-installed", "list"]) + .stdout(Stdio::piped()) // To pipe apt.stdout into awk.stdin + .spawn() + .map_err(|err| InternalError::exec_error("apt", err))?; + + // apt output = openssl/focal-security,now 1.1.1f-1ubuntu2.3 amd64 [installed] + // awk -F '[/ ]' = $1 ^ $2 ^ $3 ^ $4 + // awk print = name ^ ^ version ^ + let status = Command::new("awk") + .args(vec![ + "-F", + "[/ ]", + r#"{if ($1 != "Listing...") { print $1"\t"$3}}"#, + ]) + .stdin(apt.stdout.unwrap()) // Cannot panic: apt.stdout has been set + .status() + .map_err(|err| InternalError::exec_error("awk", err))?; + + status + } + + PluginOp::Install { + module, + version, + file_path, + } => { + let (installer, _metadata) = get_installer(module, version, file_path)?; + run_cmd("apt-get", &format!("install --quiet --yes {}", installer))? + } + + PluginOp::Remove { module, version } => { + if let Some(version) = version { + // check the version mentioned present or not + run_cmd( + "apt-get", + &format!("remove --quiet --yes {}={}", module, version), + )? + } else { + run_cmd("apt-get", &format!("remove --quiet --yes {}", module))? + } + } + + PluginOp::UpdateList => { + let mut updates: Vec<SoftwareModuleUpdate> = Vec::new(); + let mut rdr = csv::ReaderBuilder::new() + .has_headers(false) + .delimiter(b'\t') + .from_reader(io::stdin()); + for result in rdr.deserialize() { + updates.push(result?); + } + + // Maintaining this metadata list to keep the debian package symlinks until the installation is complete, + // which will get cleaned up once it goes out of scope after this block + let mut metadata_vec = Vec::new(); + let mut args: Vec<String> = Vec::new(); + args.push("install".into()); + args.push("--quiet".into()); + args.push("--yes".into()); + for update_module in updates { + match update_module.action { + UpdateAction::Install => { + let (installer, metadata) = get_installer( + update_module.name, + update_module.version, + update_module.path, + )?; + args.push(installer); + metadata_vec.push(metadata); + } + UpdateAction::Remove => { + if let Some(version) = update_module.version { + validate_version(update_module.name.as_str(), version.as_str())? + } + + // Adding a '-' at the end of the package name like 'rolldice-' instructs apt to treat it as removal + args.push(format!("{}-", update_module.name)) + } + }; + } + + println!("apt-get install args: {:?}", args); + let status = Command::new("apt-get") + .args(args) + .stdin(Stdio::null()) + .status() + .map_err(|err| InternalError::exec_error("apt-get", err))?; + + return Ok(status); + } + + PluginOp::Prepare => run_cmd("apt-get", "update --quiet --yes")?, + + PluginOp::Finalize => run_cmd("apt-get", "auto-remove --quiet --yes")?, + }; + + Ok(status) +} + +fn get_installer( + module: String, + version: Option<String>, + file_path: Option<String>, +) -> Result<(String, Option<PackageMetadata>), InternalError> { + match (&version, &file_path) { + (None, None) => Ok((module, None)), + + (Some(version), None) => Ok((format!("{}={}", module, version), None)), + + (None, Some(file_path)) => { + let mut package = PackageMetadata::try_new(file_path)?; + let () = + package.validate_package(&[&format!("Package: {}", &module), "Debian package"])?; + + Ok((format!("{}", package.file_path().display()), Some(package))) + } + + (Some(version), Some(file_path)) => { + let mut package = PackageMetadata::try_new(file_path)?; + let () = package.validate_package(&[ + &format!("Version: {}", &version), + &format!("Package: {}", &module), + "Debian package", + ])?; + + Ok((format!("{}", package.file_path().display()), Some(package))) + } + } +} + +/// Validate if the provided module version matches the currently installed version +fn validate_version(module_name: &str, module_version: &str) -> Result<(), InternalError> { + // Get the current installed version of the provided package + let output = Command::new("apt") + .arg("list") + .arg("--installed") + .arg(module_name) + .output() + .map_err(|err| InternalError::exec_error("apt-get", err))?; + + let stdout = String::from_utf8(output.stdout)?; + + // Check if the installed version and the provided version match + let second_line = stdout.lines().nth(1); //Ignore line 0 which is always 'Listing...' + if let Some(package_info) = second_line { + if let Some(installed_version) = package_info.split_whitespace().nth(1) + // Value at index 0 is the package name + { + if installed_version != module_version { + return Err(InternalError::VersionMismatch { + package: module_name.into(), + installed: installed_version.into(), + expected: module_version.into(), + }); + } + } + } + + Ok(()) +} + +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 apt = AptCli::from_args(); + + match run(apt.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); + } + } +} diff --git a/plugins/tedge_apt_plugin/src/module_check.rs b/plugins/tedge_apt_plugin/src/module_check.rs new file mode 100644 index 00000000..e6680700 --- /dev/null +++ b/plugins/tedge_apt_plugin/src/module_check.rs @@ -0,0 +1,76 @@ +use crate::error::InternalError; +use std::process::{Command, Stdio}; +use std::{ + ffi::OsStr, + path::{Path, PathBuf}, +}; + +pub struct PackageMetadata { + file_path: PathBuf, + metadata: String, + remove_modified: bool, +} + +impl PackageMetadata { + pub fn try_new(file_path: &str) -> Result<Self, InternalError> { + let metadata = String::from_utf8(Self::get_module_metadata(file_path)?)?; + + Ok(Self { + file_path: PathBuf::from(file_path), + metadata, + remove_modified: false, + }) + } + + fn metadata_contains_all(&self, patterns: &[&str]) -> bool { + for pattern in patterns { + if !&self.metadata.contains(pattern) { + return false; + }; + } + true + } + + fn get_module_metadata(file_path: &str) -> Result<Vec<u8>, InternalError> { + Ok(Command::new("dpkg") + .arg("-I") + .arg(file_path) + .stdout(Stdio::piped()) + .output()? + .stdout) + } + + pub fn validate_package(&mut self, contain_args: &[&str]) -> Result<(), InternalError> { + if self.metadata_contains_all(contain_args) { + // In the current implementation using `apt-get` it is required that the file has '.deb' extension (if we use dpkg extension doesn't matter). + if self.file_path.extension() != Some(OsStr::new("deb")) { + let new_path = PathBuf::from(format!( + "{}.deb", + self.file_path().to_string_lossy().to_string() + )); + + let _res = std::os::unix::fs::symlink(self.file_path(), &new_path); + self.file_path = new_path; + self.remove_modified = true; + } + + Ok(()) + } else { + Err(InternalError::ParsingError { + file: self.file_path().to_string_lossy().to_string(), + }) + } + } + + pub fn file_path(&self) -> &Path { + &self.file_path + } +} + +impl Drop for PackageMetadata { + fn drop(&mut self) { + if self.remove_modified { + let _res = std::fs::remove_file(&self.file_path); + } + } +} diff --git a/plugins/tedge_apt_plugin/tests/main.rs b/plugins/tedge_apt_plugin/tests/main.rs new file mode 100644 index 00000000..c1fcb5b5 --- /dev/null +++ b/plugins/tedge_apt_plugin/tests/main.rs @@ -0,0 +1,163 @@ +use hamcrest2::prelude::*; +use serial_test::serial; +use std::process::{Command, Stdio}; +use std::sync::Once; +use tedge_utils::fs::atomically_write_file_sync; +use test_case::test_case; + +const TEDGE_APT_COMMAND: &str = "/etc/tedge/sm-plugins/apt"; +const APT_COMMAND: &str = "/usr/bin/apt-get"; +const PACKAGE_NAME: &str = "rolldice"; +const PACKAGE_VERSION: &str = "1.16-1+b3"; + +#[cfg(target_arch = "x86_64")] +const PACKAGE_URL: &str = + "http://ftp.br.debian.org/debian/pool/main/r/rolldice/rolldice_1.16-1+b3_amd64.deb"; + +#[cfg(target_arch = "x86_64")] +const PACKAGE_FILE_PATH: &str = "/tmp/rolldice_1.16-1+b3_amd64.deb"; + +#[cfg(target_arch = "aarch64")] +const PACKAGE_URL: &str = + "http://ftp.br.debian.org/debian/pool/main/r/rolldice/rolldice_1.16-1+b3_arm64.deb"; + +#[cfg(target_arch = "aarch64")] +const PACKAGE_FILE_PATH: &str = "/tmp/rolldice_1.16-1+b3_arm64.deb"; + +#[cfg(target_arch = "arm")] +const PACKAGE_URL: &str = + "http://ftp.br.debian.org/debian/pool/main/r/rolldice/rolldice_1.16-1+b3_armhf.deb"; + +#[cfg(target_arch = "arm")] +const PACKAGE_FILE_PATH: &str = "/tmp/rolldice_1.16-1+b3_armhf.deb"; + +static DOWNLOAD_PACKAGE_BINARY: Once = Once::new(); + +pub fn download_package_binary_once() { + DOWNLOAD_PACKAGE_BINARY.call_once_force(|_state| { + simple_download(PACKAGE_URL); + }); +} + +fn simple_download(url: &str) { + let response = reqwest::blocking::get(url).unwrap(); + let content = response.bytes().unwrap(); + + atomically_write_file_sync("/tmp/rolldice.deb", PACKAGE_FILE_PATH, content.as_ref()).unwrap(); +} + +/// executes a `cmd` with `args` +/// returns the stdout, stderr and exit code +fn run_cmd(cmd: &str, args: &str) -> anyhow::Result<(String, String, i32)> { + let args = args.split_whitespace().collect::<Vec<&str>>(); + let output = Command::new(cmd) + .args(args) + .stdin(Stdio::null()) + .stdout(Stdio::piped()) + .stderr(Stdio::piped()) + .output()?; + + let stdout = String::from_utf8(output.stdout).unwrap(); + let stderr = String::from_utf8(output.stderr).unwrap(); + let status_code = output.status.code().unwrap(); + Ok((stdout, stderr, status_code)) +} + +// Parameters: +// +// input command +// +// expected stderr +// +// expected exit code +// +// description about the test +#[test_case( + &format!("install {} --file {}", PACKAGE_NAME, "wrong_path"), + "ERROR: Parsing Debian package failed", + 5 + ; "wrong path" +)] +#[test_case( + &format!("install {} --file {} --module-version {}", PACKAGE_NAME, "not/a/package/path", PACKAGE_VERSION), + "ERROR: Parsing Debian package failed", + 5 + ; "wrong path with right version" +)] +#[test_case( + &format!("install {} --file {} --module-version {}", PACKAGE_NAME, PACKAGE_FILE_PATH, "some_version"), + "ERROR: Parsing Debian package failed", + 5 + ; "right path with wrong version" +)] +#[test_case( + &format!("install {} --file {} --module-version {}", PACKAGE_NAME, "not/a/package/path", "some_version"), + "ERROR: Parsing Debian package failed", + 5 + ; "wrong path with wrong version" +)] +fn install_from_local_file_fail( + input_command: &str, + expected_stderr: &str, + expected_exit_code: i32, +) -> anyhow::Result<()> { + // no setup needed, wrong arguments are provided to tedge apt plugin + let (stdout, stderr, exit_code) = run_cmd(TEDGE_APT_COMMAND, input_command)?; + + // asserting command failed + assert_that!(stdout.is_empty(), true); + assert_that!(stderr, match_regex(expected_stderr)); + assert_that!(exit_code, eq(expected_exit_code)); + Ok(()) +} + +// Parameters: +// +// input command +// +// expected stderr +// +// expected exit code +// +// description about the test +#[test_case( + &format!("install {} --file {}", PACKAGE_NAME, PACKAGE_FILE_PATH), + &format!("The following NEW packages will be installed\n {}", PACKAGE_NAME), + 0 + ; "path" +)] +#[serial] +#[test_case( + &format!("install {} --file {} --module-version {}", PACKAGE_NAME, PACKAGE_FILE_PATH, PACKAGE_VERSION), + &format!("The following NEW packages will be installed\n {}", PACKAGE_NAME), + 0 + ; "path with version" +)] +#[serial] +#[test_case( + &format!("install {} --module-version {} --file {}", PACKAGE_NAME, PACKAGE_VERSION, PACKAGE_FILE_PATH), + &format!("The following NEW packages will be installed\n {}", PACKAGE_NAME), + 0 + ; "version with path" +)] +#[serial] +fn install_from_local_file_success( + input_command: &str, + expected_stdout: &str, + expected_exit_code: i32, +) -> anyhow::Result<()> { + // fetching the debian package & removing rolldice in case it is already installed. + // only executed once. + download_package_binary_once(); + let _ = run_cmd(APT_COMMAND, &format!("remove {} -y", PACKAGE_NAME))?; + + // execute command to install from local file + let (stdout, stderr, exit_code) = run_cmd(TEDGE_APT_COMMAND, input_command)?; + + // asserting success + assert_that!(stdout, match_regex(expected_stdout)); + assert_that!(stderr.is_empty(), true); + assert_that!(exit_code, eq(expected_exit_code)); + + Ok(()) +} diff --git a/plugins/tedge_docker_plugin/tedge_docker_plugin.sh b/plugins/tedge_docker_plugin/tedge_docker_plugin.sh new file mode 100644 index 00000000..9aee79ea --- /dev/null +++ b/plugins/tedge_docker_plugin/tedge_docker_plugin.sh @@ -0,0 +1,112 @@ +#!/bin/sh + +usage() { + cat << EOF +USAGE: + docker <SUBCOMMAND> + +SUBCOMMANDS: + list List all the installed modules + prepare Prepare a sequences of install/remove commands + install Install a module + remove Uninstall a module + finalize Finalize a sequences of install/remove commands +EOF +} + +unsupported_args_check() { + if ! [ -z $1 ]; then + echo "Unsupported arguments: $@" + exit 1 + fi +} + +extract_image_tag_from_args() { + IMAGE_NAME="$1" + if [ -z "$IMAGE_NAME" ]; then + echo "Image name is a mandatory argument" + exit 1 + fi + shift # Pop image name from args list + IMAGE_TAG=$IMAGE_NAME + + if ! [ -z $1 ]; then + case "$1" in + --module-version) + IMAGE_VERSION="$2" + IMAGE_TAG=$IMAGE_NAME:$IMAGE_VERSION + shift 2 # Pop --version and the version value from the args list + ;; + *) + echo "Unsupported argument option: $1" + exit 1 + ;; |