Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fixes #23773: Make rudder-package able to uninstall packages #5190

Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
44 changes: 11 additions & 33 deletions relay/sources/rudder-package/src/archive.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,9 @@ use std::{
fs::{self, *},
io::{Cursor, Read},
path::{Path, PathBuf},
process::Command,
};

use crate::{
cmd::CmdOutput,
database::{Database, InstalledPlugin},
plugin::Metadata,
versions::RudderVersion,
Expand All @@ -37,7 +35,7 @@ impl fmt::Display for PackageType {
}

#[derive(Debug, Clone, Copy)]
enum PackageScript {
pub enum PackageScript {
Postinst,
Postrm,
Preinst,
Expand All @@ -56,7 +54,7 @@ impl fmt::Display for PackageScript {
}

#[derive(Debug, Clone, Copy)]
enum PackageScriptArg {
pub enum PackageScriptArg {
Install,
Upgrade,
None,
Expand Down Expand Up @@ -90,7 +88,11 @@ impl Rpkg {
fn get_txz_dst(&self, txz_name: &str) -> String {
Fdall marked this conversation as resolved.
Show resolved Hide resolved
// Build the destination path
if txz_name == "scripts.txz" {
return PACKAGES_FOLDER.to_string();
return PathBuf::from(PACKAGES_FOLDER)
.join(self.metadata.name.clone())
.as_path()
.display()
.to_string();
}
return self.metadata.content.get(txz_name).unwrap().to_string();
}
Expand Down Expand Up @@ -211,7 +213,8 @@ impl Rpkg {
self.unpack_embedded_txz("script.txz", PACKAGES_FOLDER)?;
// Run preinst if any
let install_or_upgrade: PackageScriptArg = PackageScriptArg::Install;
self.run_package_script(PackageScript::Preinst, install_or_upgrade)?;
self.metadata
.run_package_script(PackageScript::Preinst, install_or_upgrade)?;
// Extract archive content
let keys = self.metadata.content.keys().clone();
for txz_name in keys {
Expand All @@ -231,7 +234,8 @@ impl Rpkg {
Database::write(PACKAGES_DATABASE_PATH, db)?;
// Run postinst if any
let install_or_upgrade: PackageScriptArg = PackageScriptArg::Install;
self.run_package_script(PackageScript::Postinst, install_or_upgrade)?;
self.metadata
.run_package_script(PackageScript::Postinst, install_or_upgrade)?;
// Update the webapp xml file if the plugin contains one or more jar file
debug!("Enabling the associated jars if any");
match self.metadata.jar_files.clone() {
Expand All @@ -247,32 +251,6 @@ impl Rpkg {
debug!("Install completed");
Ok(())
}

fn run_package_script(&self, script: PackageScript, arg: PackageScriptArg) -> Result<()> {
debug!(
"Running package script '{}' with args '{}' for rpkg '{}'...",
script, arg, self.path
);
let package_script_path = Path::new(PACKAGES_FOLDER)
.join(self.metadata.name.clone())
.join(script.to_string());
if !package_script_path.exists() {
debug!("Skipping as the script does not exist.");
return Ok(());
}
let mut binding = Command::new(package_script_path);
let cmd = binding.arg(arg.to_string());
let r = match CmdOutput::new(cmd) {
std::result::Result::Ok(a) => a,
Err(e) => {
bail!("Could not execute package script '{}'`n{}", script, e);
}
};
if !r.output.status.success() {
debug!("Package script execution return unexpected exit code.");
}
Ok(())
}
}

fn read_metadata(path: &str) -> Result<Metadata> {
Expand Down
6 changes: 5 additions & 1 deletion relay/sources/rudder-package/src/cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,11 @@ pub enum Command {
force: bool,

#[clap()]
package: String,
package: Vec<String>,
},
List {},
Uninstall {
#[clap()]
package: Vec<String>,
},
}
92 changes: 89 additions & 3 deletions relay/sources/rudder-package/src/database.rs
Original file line number Diff line number Diff line change
@@ -1,13 +1,23 @@
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: 2023 Normation SAS

use std::{collections::HashMap, fs::*, io::BufWriter};
use std::{
collections::HashMap,
fs::{self, *},
io::BufWriter,
path::PathBuf,
};

use anyhow::{Context, Result};
use anyhow::{anyhow, Context, Result};
use serde::{Deserialize, Serialize};

use super::archive::Rpkg;
use crate::plugin;
use crate::{
archive::{PackageScript, PackageScriptArg},
plugin,
webapp_xml::WebappXml,
PACKAGES_DATABASE_PATH, PACKAGES_FOLDER,
};
use log::debug;

#[derive(Serialize, Deserialize, PartialEq, Eq, Debug, Clone)]
Expand Down Expand Up @@ -38,6 +48,44 @@ impl Database {
Some(installed) => installed.metadata.version == r.metadata.version,
}
}

pub fn uninstall(&mut self, plugin_name: &str) -> Result<()> {
// Force to use plugin long qualified name
if !plugin_name.starts_with("rudder-plugin-") {
plugin_name.to_owned().insert_str(0, "rudder-plugin-{}")
};
// Return Ok if not installed
if !self.plugins.contains_key(plugin_name) {
debug!("Plugin {} is not installed.", plugin_name);
return Ok(());
}
debug!("Uninstalling plugin {}", plugin_name);
// Disable the jar files if any
let installed_plugin = self.plugins.get(plugin_name).ok_or(anyhow!(
"Could not extract data for plugin {} in the database",
plugin_name
))?;
installed_plugin.disable()?;
installed_plugin
.metadata
.run_package_script(PackageScript::Prerm, PackageScriptArg::None)?;
match installed_plugin.remove_installed_files() {
Ok(()) => (),
Err(e) => debug!("{}", e),
}
installed_plugin
.metadata
.run_package_script(PackageScript::Postrm, PackageScriptArg::None)?;
// Remove associated package scripts and plugin folder
fs::remove_dir_all(
PathBuf::from(PACKAGES_FOLDER).join(installed_plugin.metadata.name.clone()),
)?;
// Update the database
let mut updated_db = self.clone();
updated_db.plugins.remove(plugin_name);
Database::write(PACKAGES_DATABASE_PATH, updated_db)?;
Fdall marked this conversation as resolved.
Show resolved Hide resolved
Ok(())
}
}

#[derive(Serialize, Deserialize, PartialEq, Eq, Debug, Clone)]
Expand All @@ -48,6 +96,44 @@ pub struct InstalledPlugin {
pub metadata: plugin::Metadata,
}

impl InstalledPlugin {
pub fn disable(&self) -> Result<()> {
debug!("Disabling plugin {}", self.metadata.name);
let x = WebappXml::new(PACKAGES_DATABASE_PATH.to_owned());
match &self.metadata.jar_files {
None => {
println!("Plugin {} does not support the enable/disable feature, it will always be enabled if installed.", self.metadata.name);
Ok(())
}
Some(jars) => jars.iter().try_for_each(|j| x.disable_jar(j.to_string())),
}
}
pub fn enable(&self) -> Result<()> {
debug!("Enabling plugin {}", self.metadata.name);
let x = WebappXml::new(PACKAGES_DATABASE_PATH.to_owned());
match &self.metadata.jar_files {
None => {
println!("Plugin {} does not support the enable/disable feature, it will always be enabled if installed.", self.metadata.name);
Ok(())
}
Some(jars) => jars.iter().try_for_each(|j| x.enable_jar(j.to_string())),
}
}

pub fn remove_installed_files(&self) -> Result<()> {
self.files.clone().into_iter().try_for_each(|f| {
let m = PathBuf::from(f.clone());
if m.is_dir() {
debug!("Removing file '{}'", f);
fs::remove_dir(f).map_err(anyhow::Error::from)
} else {
debug!("Removing folder '{}' if empty", f);
fs::remove_file(f).map_err(anyhow::Error::from)
}
})
}
}

#[cfg(test)]
mod tests {
use std::str::FromStr;
Expand Down
69 changes: 40 additions & 29 deletions relay/sources/rudder-package/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,9 @@ pub fn run() -> Result<()> {
Command::Install { force, package } => {
return action::install(force, package, repo);
}
Command::Uninstall { package } => {
return action::uninstall(package);
}
_ => {
error!("This command is not implemented");
}
Expand All @@ -97,36 +100,44 @@ pub mod action {
};
use std::path::Path;

pub fn install(force: bool, package: String, repository: Repository) -> Result<()> {
let rpkg_path = if Path::new(&package).exists() {
package
} else {
// Find compatible plugin if any
let webapp_version = RudderVersion::from_path(RUDDER_VERSION_PATH)?;
let index = RepoIndex::from_path(REPOSITORY_INDEX_PATH)?;
let to_dl_and_install = match index.get_compatible_plugin(webapp_version, &package) {
None => bail!("Could not find any compatible '{}' plugin with the current Rudder version in the configured repository.", package),
Some(p) => {
debug!("Found a compatible plugin in the repository:\n{:?}", p);
p
}
pub fn uninstall(packages: Vec<String>) -> Result<()> {
let mut db = Database::read(PACKAGES_DATABASE_PATH)?;
packages.iter().try_for_each(|p| db.uninstall(p))
}

pub fn install(force: bool, packages: Vec<String>, repository: Repository) -> Result<()> {
packages.iter().try_for_each(|package| {
let rpkg_path = if Path::new(&package).exists() {
package.clone()
} else {
// Find compatible plugin if any
let webapp_version = RudderVersion::from_path(RUDDER_VERSION_PATH)?;
let index = RepoIndex::from_path(REPOSITORY_INDEX_PATH)?;
let to_dl_and_install = match index.get_compatible_plugin(webapp_version, package) {
None => bail!("Could not find any compatible '{}' plugin with the current Rudder version in the configured repository.", package),
Some(p) => {
debug!("Found a compatible plugin in the repository:\n{:?}", p);
p
}
};
let dest = Path::new(TMP_PLUGINS_FOLDER).join(
Path::new(&to_dl_and_install.path)
.file_name()
.ok_or_else(|| {
anyhow!(
"Could not retrieve filename from path '{}'",
to_dl_and_install.path
)
})?,
);
// Download rpkg
repository.clone().download(&to_dl_and_install.path, &dest)?;
dest.as_path().display().to_string()
};
let dest = Path::new(TMP_PLUGINS_FOLDER).join(
Path::new(&to_dl_and_install.path)
.file_name()
.ok_or_else(|| {
anyhow!(
"Could not retrieve filename from path '{}'",
to_dl_and_install.path
)
})?,
);
// Download rpkg
repository.download(&to_dl_and_install.path, &dest)?;
dest.as_path().display().to_string()
};
let rpkg = Rpkg::from_path(&rpkg_path)?;
rpkg.install(force)?;
let rpkg = Rpkg::from_path(&rpkg_path)?;
rpkg.install(force)?;
Ok(())
})?;
restart_webapp()
}

Expand Down
41 changes: 39 additions & 2 deletions relay/sources/rudder-package/src/plugin.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,18 @@
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: 2023 Normation SAS

use std::collections::HashMap;
use std::{collections::HashMap, path::Path, process::Command};

use anyhow::bail;
use log::debug;
use serde::{Deserialize, Serialize};

use crate::{archive, dependency::Dependencies, versions};
use crate::{
archive::{self, PackageScript, PackageScriptArg},
cmd::CmdOutput,
dependency::Dependencies,
versions, PACKAGES_FOLDER,
};

#[derive(Serialize, Deserialize, PartialEq, Eq, Debug, Clone)]
pub struct Metadata {
Expand All @@ -29,4 +36,34 @@ impl Metadata {
pub fn is_compatible(&self, webapp_version: &str) -> bool {
self.version.rudder_version.is_compatible(webapp_version)
}

pub fn run_package_script(
&self,
script: PackageScript,
arg: PackageScriptArg,
) -> Result<(), anyhow::Error> {
debug!(
"Running package script '{}' with args '{}' for plugin '{}' in version '{}-{}'...",
script, arg, self.name, self.version.rudder_version, self.version.plugin_version
);
let package_script_path = Path::new(PACKAGES_FOLDER)
.join(self.name.clone())
.join(script.to_string());
if !package_script_path.exists() {
debug!("Skipping as the script does not exist.");
return Ok(());
}
let mut binding = Command::new(package_script_path);
let cmd = binding.arg(arg.to_string());
let r = match CmdOutput::new(cmd) {
Ok(a) => a,
Err(e) => {
bail!("Could not execute package script '{}'`n{}", script, e);
}
};
if !r.output.status.success() {
debug!("Package script execution return unexpected exit code.");
}
Ok(())
}
}
1 change: 1 addition & 0 deletions relay/sources/rudder-package/src/repository.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ use crate::config::{Configuration, Credentials};

static APP_USER_AGENT: &str = concat!(env!("CARGO_PKG_NAME"), "/", env!("CARGO_PKG_VERSION"),);

#[derive(Clone)]
pub struct Repository {
inner: Client,
creds: Option<Credentials>,
Expand Down