From d594b7027a76860321d5780673eb8f12411c2946 Mon Sep 17 00:00:00 2001 From: Jaroslav Rohel Date: Fri, 8 Sep 2023 20:08:09 +0200 Subject: [PATCH] [dnf5 plugin] config-manager Manage libdnf5 configuration. Subcommands: addrepo Add a repository from the specified configuration file or create a new repository setopts Set configuration and repositories options unsetopts Uset/remove configuration and repositories options setvars Set variables unsetvars Unset/remove variables Note: Main configuration: libdnf5 reads the distribution configuration from the drop-in directory ("/usr/share/dnf5/libdnf.conf.d") and user configuration from drop-in directory ("/etc/dnf/libdnf5.conf.d"). Last, it loads the user configuration file (by default "/etc/dnf/dnf.conf"). The latter has the highest priority and is modified by config-manager. Repository configuration: Libdnf5 loads the repositories configuration and then loads the configuration overrides. Configuration overrides are stored in files in the "/usr/share/dnf5/repos.override.d" and "/etc/dnf/repos.override.d" directories. The files are sorted alphabetically. The override from the next file overrides the previous one - the last override value wins. The config-manager writes the repositories configuration changes to the file "/etc/dnf/repos.override.d/99-config-manager.repo". --- dnf5-plugins/CMakeLists.txt | 1 + .../config-manager_plugin/CMakeLists.txt | 18 + .../config-manager_plugin/addrepo.cpp | 476 ++++++++++++++++++ .../config-manager_plugin/addrepo.hpp | 57 +++ .../config-manager_plugin/config-manager.cpp | 59 +++ .../config-manager_plugin/config-manager.hpp | 40 ++ .../config-manager_cmd_plugin.cpp | 74 +++ .../config-manager_plugin/setopts.cpp | 206 ++++++++ .../config-manager_plugin/setopts.hpp | 47 ++ .../config-manager_plugin/setvars.cpp | 120 +++++ .../config-manager_plugin/setvars.hpp | 44 ++ .../config-manager_plugin/unsetopts.cpp | 164 ++++++ .../config-manager_plugin/unsetopts.hpp | 46 ++ .../config-manager_plugin/unsetvars.cpp | 78 +++ .../config-manager_plugin/unsetvars.hpp | 44 ++ dnf5.spec | 9 +- 16 files changed, 1482 insertions(+), 1 deletion(-) create mode 100644 dnf5-plugins/config-manager_plugin/CMakeLists.txt create mode 100644 dnf5-plugins/config-manager_plugin/addrepo.cpp create mode 100644 dnf5-plugins/config-manager_plugin/addrepo.hpp create mode 100644 dnf5-plugins/config-manager_plugin/config-manager.cpp create mode 100644 dnf5-plugins/config-manager_plugin/config-manager.hpp create mode 100644 dnf5-plugins/config-manager_plugin/config-manager_cmd_plugin.cpp create mode 100644 dnf5-plugins/config-manager_plugin/setopts.cpp create mode 100644 dnf5-plugins/config-manager_plugin/setopts.hpp create mode 100644 dnf5-plugins/config-manager_plugin/setvars.cpp create mode 100644 dnf5-plugins/config-manager_plugin/setvars.hpp create mode 100644 dnf5-plugins/config-manager_plugin/unsetopts.cpp create mode 100644 dnf5-plugins/config-manager_plugin/unsetopts.hpp create mode 100644 dnf5-plugins/config-manager_plugin/unsetvars.cpp create mode 100644 dnf5-plugins/config-manager_plugin/unsetvars.hpp diff --git a/dnf5-plugins/CMakeLists.txt b/dnf5-plugins/CMakeLists.txt index 4c9571d68..e311b35b9 100644 --- a/dnf5-plugins/CMakeLists.txt +++ b/dnf5-plugins/CMakeLists.txt @@ -6,5 +6,6 @@ include_directories("${PROJECT_SOURCE_DIR}/dnf5/include/") add_subdirectory("builddep_plugin") add_subdirectory("changelog_plugin") +add_subdirectory("config-manager_plugin") add_subdirectory("copr_plugin") add_subdirectory("repoclosure_plugin") diff --git a/dnf5-plugins/config-manager_plugin/CMakeLists.txt b/dnf5-plugins/config-manager_plugin/CMakeLists.txt new file mode 100644 index 000000000..cf00856a1 --- /dev/null +++ b/dnf5-plugins/config-manager_plugin/CMakeLists.txt @@ -0,0 +1,18 @@ +# set gettext domain for translations +add_definitions(-DGETTEXT_DOMAIN=\"dnf5_cmd_config-manager\") + +add_library(config-manager_cmd_plugin MODULE config-manager.cpp addrepo.cpp setopts.cpp unsetopts.cpp setvars.cpp unsetvars.cpp config-manager_cmd_plugin.cpp) + +# disable the 'lib' prefix in order to create changelog_cmd_plugin.so +set_target_properties(config-manager_cmd_plugin PROPERTIES PREFIX "") + +pkg_check_modules(LIBFMT REQUIRED fmt) +target_link_libraries(libdnf5-cli PUBLIC ${LIBFMT_LIBRARIES}) + +find_package(CURL 7.62.0 REQUIRED) +include_directories(${CURL_INCLUDE_DIR}) + +target_link_libraries(config-manager_cmd_plugin PRIVATE libdnf5 libdnf5-cli) +target_link_libraries(config-manager_cmd_plugin PRIVATE dnf5) + +install(TARGETS config-manager_cmd_plugin LIBRARY DESTINATION ${CMAKE_INSTALL_FULL_LIBDIR}/dnf5/plugins/) diff --git a/dnf5-plugins/config-manager_plugin/addrepo.cpp b/dnf5-plugins/config-manager_plugin/addrepo.cpp new file mode 100644 index 000000000..472b7422b --- /dev/null +++ b/dnf5-plugins/config-manager_plugin/addrepo.cpp @@ -0,0 +1,476 @@ +/* +Copyright Contributors to the libdnf project. + +This file is part of libdnf: https://github.com/rpm-software-management/libdnf/ + +Libdnf is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 2 of the License, or +(at your option) any later version. + +Libdnf is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with libdnf. If not, see . +*/ + +#include "addrepo.hpp" + +#include +#include +#include +#include +#include +#include + +#include +#include + +namespace dnf5 { + +using namespace libdnf5::cli; + +namespace { + +const std::filesystem::path CFG_MANAGER_REPOS_OVERRIDE_FILENAME = "99-config_manager.repo"; + +const std::filesystem::path CFG_MANAGER_REPOS_OVERRIDE_FILEPATH = + libdnf5::REPOS_OVERRIDE_DIR / CFG_MANAGER_REPOS_OVERRIDE_FILENAME; + + +// Extracts a specific part of the URL from a URL string. +std::string get_url_part(const std::string & url, CURLUPart what_part) { + std::string ret; + CURLUcode rc; + CURLU * c_url = curl_url(); + rc = curl_url_set(c_url, CURLUPART_URL, url.c_str(), 0); + if (!rc) { + char * part; + rc = curl_url_get(c_url, what_part, &part, 0); + if (!rc) { + ret = part; + curl_free(part); + } + } + curl_url_cleanup(c_url); + return ret; +} + + +// Computes CRC32 checksum of input string. +// Slow bitwise implementation. Used to calculate the checksum of the omitted part of long URLs. +uint32_t crc32(std::string_view input) { + const uint32_t polynomial = 0x04C11DB7; + uint32_t crc = 0; + + for (auto ch : input) { + crc ^= static_cast(ch) << 24; + for (int i = 0; i < 8; ++i) { + if ((crc & 0x80000000) != 0) { + crc = (crc << 1) ^ polynomial; + } else { + crc <<= 1; + } + } + } + return crc; +} + + +// Converts all letters consider illegal in repository id to their "_XX" versions (XX - hex code). +std::string escape(const std::string & text) { + static constexpr const char * digits = "0123456789ABCDEF"; + char tmp[] = "_XX"; + std::string ret; + ret.reserve(text.size() * 3); + for (const char ch : text) { + if ((ch >= '0' && ch <= '9') || (ch >= 'A' && ch <= 'Z') || (ch >= 'a' && ch <= 'z') || ch == '-' || + ch == '.' || ch == ':' || ch == '_') { + ret += ch; + } else { + const auto uch = static_cast(ch); + tmp[2] = digits[uch & 0x0F]; + tmp[1] = digits[(uch >> 4) & 0x0F]; + ret += tmp; + } + } + return ret; +} + + +// Regular expressions to sanitise filename +const std::regex RE_SCHEME{R"(^\w+:/*(\w+:|www\.)?)"}; +const std::regex RE_SLASH{R"([?/:&#|~\*\[\]\(\)'\\]+)"}; +const std::regex RE_BEGIN{"^[,.]*"}; +const std::regex RE_FINAL{"[,.]*$"}; + +// Returns a filename suitable for the filesystem and for repository id. +// Strips dangerous and common characters, encodes some characters and limits the length. +std::string sanitize_url(const std::string & url) { + std::string ret; + ret = std::regex_replace(url, RE_SCHEME, ""); + ret = std::regex_replace(ret, RE_SLASH, "_"); + ret = std::regex_replace(ret, RE_BEGIN, ""); + ret = std::regex_replace(ret, RE_FINAL, ""); + ret = escape(ret); + + // Limits length of url. + // Copies the first and last 100 characters. The substring in between is replaced by a crc32 checksum. + if (ret.size() > 250) { + std::string_view tmp{ret}; + ret = fmt::format( + "{}-{:08X}-{}", tmp.substr(0, 100), crc32(tmp.substr(100, tmp.size() - 200)), tmp.substr(tmp.size() - 100)); + } + + return ret; +} + +} // namespace + + +void ConfigManagerAddRepoCommand::set_argument_parser() { + auto & ctx = get_context(); + auto & parser = ctx.get_argument_parser(); + + auto & cmd = *get_argument_parser_command(); + cmd.set_description("Add a repository from the specified configuration file or create a new repository"); + + auto from_repofile_opt = parser.add_new_named_arg("from-repofile"); + from_repofile_opt->set_long_name("from-repofile"); + from_repofile_opt->set_description("Download repository configuration file, test it and put it in reposdir"); + from_repofile_opt->set_has_value(true); + from_repofile_opt->set_arg_value_help("REPO_CONFIGURATION_FILE_URL"); + from_repofile_opt->set_parse_hook_func([this](ArgumentParser::NamedArg *, const char *, const char * value) { + repofile_url = value; + return true; + }); + cmd.register_named_arg(from_repofile_opt); + + auto repo_id_opt = parser.add_new_named_arg("id"); + repo_id_opt->set_long_name("id"); + repo_id_opt->set_description("Set id for newly created repository"); + repo_id_opt->set_has_value(true); + repo_id_opt->set_arg_value_help("REPO_ID"); + repo_id_opt->set_parse_hook_func([this](ArgumentParser::NamedArg *, const char *, const char * value) { + repo_id = value; + return true; + }); + cmd.register_named_arg(repo_id_opt); + + auto overwrite_opt = parser.add_new_named_arg("overwrite"); + overwrite_opt->set_long_name("overwrite"); + overwrite_opt->set_description("Allow to overwrite an existing repository configuration file"); + overwrite_opt->set_has_value(false); + overwrite_opt->set_parse_hook_func([this](ArgumentParser::NamedArg *, const char *, const char *) { + overwrite = true; + return true; + }); + cmd.register_named_arg(overwrite_opt); + + auto save_filename_opt = parser.add_new_named_arg("save-filename"); + save_filename_opt->set_long_name("save-filename"); + save_filename_opt->set_description( + "Set the name of the configuration file of the added repository. The \".repo\" extension is added if it is " + "missing."); + save_filename_opt->set_has_value(true); + save_filename_opt->set_arg_value_help("FILENAME"); + save_filename_opt->set_parse_hook_func([this](ArgumentParser::NamedArg *, const char *, const char * value) { + save_filename = value; + return true; + }); + cmd.register_named_arg(save_filename_opt); + + auto set_opt = parser.add_new_named_arg("set"); + set_opt->set_long_name("set"); + set_opt->set_description("Set option in newly created repository"); + set_opt->set_has_value(true); + set_opt->set_arg_value_help("REPO_OPTION=VALUE"); + set_opt->set_parse_hook_func( + [this]( + [[maybe_unused]] ArgumentParser::NamedArg * arg, [[maybe_unused]] const char * option, const char * value) { + auto val = strchr(value + 1, '='); + if (!val) { + throw libdnf5::cli::ArgumentParserError(M_("set: Badly formatted argument value \"{}\""), value); + } + std::string key{value, val}; + std::string key_value{val + 1}; + // Save the global option for later writing to a file. + const auto [it, inserted] = repo_opts.insert({key, key_value}); + if (!inserted) { + if (it->second != key_value) { + throw Error( + M_("Sets the \"{}\" option again with a different value: \"{}\" != \"{}\""), + key, + it->second, + key_value); + } + } + return true; + }); + cmd.register_named_arg(set_opt); + + // Set conflicting arguments + repo_id_opt->add_conflict_argument(*from_repofile_opt); + set_opt->add_conflict_argument(*from_repofile_opt); +} + + +void ConfigManagerAddRepoCommand::pre_configure() { + auto & ctx = get_context(); + auto & base = ctx.base; + auto logger = base.get_logger(); + + const auto & repo_dirs = get_repo_dir_paths(); + if (repo_dirs.empty()) { + throw Error(M_("Missing path to repository configuration directory")); + } + + std::filesystem::path repo_dir = repo_dirs.front(); + + std::string url; + if (!repofile_url.empty()) { + url = repofile_url; + } else if (auto it = repo_opts.find("baseurl"); it != repo_opts.end() && !it->second.empty()) { + const auto urls = libdnf5::OptionStringList(std::vector{}).from_string(it->second); + if (urls.empty()) { + throw Error(M_("Bad baseurl: {}={}"), it->first, it->second); + } + url = urls[0]; + } + if (url.empty()) { + if (auto it = repo_opts.find("mirrorlist"); it != repo_opts.end() && !it->second.empty()) { + url = it->second; + } else if (auto it = repo_opts.find("metalink"); it != repo_opts.end() && !it->second.empty()) { + url = it->second; + } else { + throw ArgumentParserMissingDependentArgumentError( + M_("One of --from-repofile=, --set=baseurl=, --set=mirrorlist=, --set=metalink= " + "must be set to a non-empty URL")); + } + } + + if (!repofile_url.empty()) { + add_repo_from_repofile(url, repo_dir); + } else { + create_repo(url, repo_dir); + } +} + + +void ConfigManagerAddRepoCommand::add_repo_from_repofile( + const std::string & url, const std::filesystem::path & repo_dir) { + auto & ctx = get_context(); + auto & base = ctx.base; + auto logger = base.get_logger(); + + logger->info("config-manager: Adding repofile from: {}", url); + + std::string full_url; + if (get_url_part(url, CURLUPART_SCHEME) == "") { + full_url += "file://" + std::filesystem::absolute(url).string(); + } else { + full_url = url; + } + + if (save_filename.empty()) { + save_filename = std::filesystem::path(get_url_part(full_url, CURLUPART_PATH)).filename(); + } + if (!save_filename.ends_with(".repo")) { + save_filename += ".repo"; + } + auto dest_path = repo_dir / save_filename; + + test_filepath(dest_path); + + auto tmpfilepath = dest_path.string() + ".XXXXXX"; + auto fd = mkstemp(tmpfilepath.data()); + if (fd == -1) { + throw std::filesystem::filesystem_error( + "cannot create temporary file", tmpfilepath, std::error_code(errno, std::system_category())); + } + close(fd); + + try { + try { + libdnf5::repo::FileDownloader downloader(base); + downloader.add(full_url, tmpfilepath); + downloader.download(); + } catch (const libdnf5::repo::FileDownloadError & e) { + throw Error( + M_("Failed to download repository configuration file \"{}\": {}"), full_url, std::string{e.what()}); + } + + libdnf5::ConfigParser parser; + parser.read(tmpfilepath); + std::vector repo_ids; + repo_ids.reserve(parser.get_data().size()); + for (const auto & [repo_id, opts] : parser.get_data()) { + repo_ids.emplace_back(repo_id); + } + test_ids(repo_ids, dest_path); + + libdnf5::repo::ConfigRepo repo_conf(ctx.base.get_config(), "temporary_to_check_repository_options"); + for (const auto & [repo_id, repo_opts] : parser.get_data()) { + for (const auto & [key, key_val] : repo_opts) { + try { + // Test if the repository option are known. + repo_conf.opt_binds().at(key).new_string(libdnf5::Option::Priority::RUNTIME, key_val); + } catch (const libdnf5::OptionBindsOptionNotFoundError & ex) { + ctx.print_info(fmt::format("Unknown repository option found: {}={}", key, key_val)); + } catch (const libdnf5::OptionInvalidValueError & ex) { + throw Error(M_("Cannot set repository option \"{}={}\": {}"), key, key_val, std::string{ex.what()}); + } + } + } + } catch (const Error & ex) { + std::error_code ec; + std::filesystem::remove(tmpfilepath, ec); + throw; + } + + // All tests passed. Renames the configuration file to the final name. + std::filesystem::rename(tmpfilepath, dest_path); +} + + +void ConfigManagerAddRepoCommand::create_repo(const std::string & url, const std::filesystem::path & repo_dir) { + auto & ctx = get_context(); + auto & base = ctx.base; + auto logger = base.get_logger(); + + if (repo_id.empty()) { + repo_id = sanitize_url(url); + } + + if (save_filename.empty()) { + save_filename = repo_id; + } + if (!save_filename.ends_with(".repo")) { + save_filename += ".repo"; + } + auto dest_path = repo_dir / save_filename; + + logger->info("config-manager: Adding new repo \"{}\" to file : {}", repo_id, dest_path.string()); + + test_filepath(dest_path); + test_ids({repo_id}, dest_path); + + libdnf5::ConfigParser parser; + parser.add_section(repo_id); + + // Sets the default repository name. May be overwritten with "--set=name=". + parser.set_value(repo_id, "name", "created by dnf5 config-manager from " + url); + // Enables repository by default. The repository can be disabled with "--set=name=0". + parser.set_value(repo_id, "enabled", "1"); + + libdnf5::repo::ConfigRepo repo_conf(ctx.base.get_config(), "temporary_to_check_repository_options"); + for (const auto & [key, key_val] : repo_opts) { + try { + // Test repository option. + repo_conf.opt_binds().at(key).new_string(libdnf5::Option::Priority::COMMANDLINE, key_val); + + parser.set_value(repo_id, key, key_val); + } catch (const libdnf5::OptionBindsOptionNotFoundError & ex) { + throw Error(M_("Unknown repository option found: {}={}"), key, key_val); + } catch (const libdnf5::OptionInvalidValueError & ex) { + throw Error(M_("Cannot set repository option \"{}={}\": {}"), key, key_val, std::string{ex.what()}); + } + } + + try { + parser.write(dest_path, false); + } catch (const std::runtime_error & e) { + throw Error( + M_("Failed to save repository configuration file \"{}\": {}"), dest_path.native(), std::string{e.what()}); + } +} + + +std::filesystem::path ConfigManagerAddRepoCommand::get_config_file_path() const { + const auto & config = get_context().base.get_config(); + std::filesystem::path conf_path{config.get_config_file_path_option().get_value()}; + const auto & conf_path_priority = config.get_config_file_path_option().get_priority(); + const auto & use_host_config = config.get_use_host_config_option().get_value(); + if (!use_host_config && conf_path_priority < libdnf5::Option::Priority::COMMANDLINE) { + conf_path = config.get_installroot_option().get_value() / conf_path.relative_path(); + } + return conf_path; +} + + +std::vector ConfigManagerAddRepoCommand::get_repo_dir_paths() const { + const auto & config = get_context().base.get_config(); + const auto & repo_dirs = config.get_reposdir_option().get_value(); + return {repo_dirs.begin(), repo_dirs.end()}; +} + + +void ConfigManagerAddRepoCommand::test_filepath(const std::filesystem::path & dest_path) const { + if (!overwrite && std::filesystem::exists(dest_path)) { + libdnf5::ConfigParser parser; + parser.read(dest_path); + std::string repo_ids; + for (const auto & [repo_id, opts] : parser.get_data()) { + repo_ids += ' ' + repo_id; + } + throw Error( + M_("Overwriting is no allowed and a file named \"{}\" already exists and configures repositories with " + "IDs:{}"), + dest_path.string(), + repo_ids); + } +} + + +void ConfigManagerAddRepoCommand::test_ids( + const std::vector & repo_ids, const std::filesystem::path & dest_path) const { + auto & ctx = get_context(); + auto & base = ctx.base; + auto logger = base.get_logger(); + + if (const auto conf_path = get_config_file_path(); std::filesystem::exists(conf_path)) { + libdnf5::ConfigParser parser; + parser.read(conf_path); + for (const auto & repo_id : repo_ids) { + if (parser.has_section(repo_id)) { + throw Error( + M_("A repository with id \"{}\" already configured in file: {}"), repo_id, conf_path.string()); + } + } + } + + const auto repo_dirs = get_repo_dir_paths(); + for (const auto & dir : repo_dirs) { + if (std::filesystem::exists(dir)) { + std::error_code ec; + std::filesystem::directory_iterator di(dir, ec); + if (ec) { + logger->warning("Cannot read repositories from directory \"{}\": {}", dir.string(), ec.message()); + continue; + } + for (auto & dentry : di) { + const auto & path = dentry.path(); + if (path == dest_path) { + continue; + } + if (path.extension() == ".repo") { + libdnf5::ConfigParser parser; + parser.read(path); + for (const auto & repo_id : repo_ids) { + if (parser.has_section(repo_id)) { + throw Error( + M_("A repository with id \"{}\" already configured in file: {}"), + repo_id, + path.string()); + } + } + } + } + } + } +} + +} // namespace dnf5 diff --git a/dnf5-plugins/config-manager_plugin/addrepo.hpp b/dnf5-plugins/config-manager_plugin/addrepo.hpp new file mode 100644 index 000000000..75d286a48 --- /dev/null +++ b/dnf5-plugins/config-manager_plugin/addrepo.hpp @@ -0,0 +1,57 @@ +/* +Copyright Contributors to the libdnf project. + +This file is part of libdnf: https://github.com/rpm-software-management/libdnf/ + +Libdnf is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 2 of the License, or +(at your option) any later version. + +Libdnf is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with libdnf. If not, see . +*/ + + +#ifndef DNF5_COMMANDS_CONFIG_MANAGER_CONFIG_MANAGER_ADDREPO_HPP +#define DNF5_COMMANDS_CONFIG_MANAGER_CONFIG_MANAGER_ADDREPO_HPP + +#include + +#include +#include +#include +#include + +namespace dnf5 { + +class ConfigManagerAddRepoCommand : public Command { +public: + explicit ConfigManagerAddRepoCommand(Context & context) : Command(context, "addrepo") {} + void set_argument_parser() override; + void pre_configure() override; + +private: + void add_repo_from_repofile(const std::string & url, const std::filesystem::path & repo_dir); + void create_repo(const std::string & url, const std::filesystem::path & repo_dir); + std::filesystem::path get_config_file_path() const; + std::vector get_repo_dir_paths() const; + void test_filepath(const std::filesystem::path & dest_path) const; + void test_ids(const std::vector & repo_ids, const std::filesystem::path & dest_path) const; + + std::string repofile_url; + std::string repo_id; + bool overwrite{false}; + std::string save_filename; + std::map repo_opts; +}; + +} // namespace dnf5 + + +#endif // DNF5_COMMANDS_CONFIG_MANAGER_CONFIG_MANAGER_ADDREPO_HPP diff --git a/dnf5-plugins/config-manager_plugin/config-manager.cpp b/dnf5-plugins/config-manager_plugin/config-manager.cpp new file mode 100644 index 000000000..429a51402 --- /dev/null +++ b/dnf5-plugins/config-manager_plugin/config-manager.cpp @@ -0,0 +1,59 @@ +/* +Copyright Contributors to the libdnf project. + +This file is part of libdnf: https://github.com/rpm-software-management/libdnf/ + +Libdnf is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 2 of the License, or +(at your option) any later version. + +Libdnf is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with libdnf. If not, see . +*/ + +#include "config-manager.hpp" + +#include "addrepo.hpp" +#include "setopts.hpp" +#include "setvars.hpp" +#include "unsetopts.hpp" +#include "unsetvars.hpp" + +namespace dnf5 { + +using namespace libdnf5::cli; + +void ConfigManagerCommand::set_parent_command() { + auto * arg_parser_parent_cmd = get_session().get_argument_parser().get_root_command(); + auto * arg_parser_this_cmd = get_argument_parser_command(); + arg_parser_parent_cmd->register_command(arg_parser_this_cmd); + arg_parser_parent_cmd->get_group("subcommands").register_argument(arg_parser_this_cmd); +} + +void ConfigManagerCommand::set_argument_parser() { + auto & cmd = *get_argument_parser_command(); + cmd.set_description("Manage configuration options and repositories"); +} + +void ConfigManagerCommand::register_subcommands() { + auto * config_manager_commands_group = get_context().get_argument_parser().add_new_group("config-manager_commands"); + config_manager_commands_group->set_header("Commands:"); + get_argument_parser_command()->register_group(config_manager_commands_group); + register_subcommand(std::make_unique(get_context()), config_manager_commands_group); + register_subcommand(std::make_unique(get_context()), config_manager_commands_group); + register_subcommand(std::make_unique(get_context()), config_manager_commands_group); + register_subcommand(std::make_unique(get_context()), config_manager_commands_group); + register_subcommand(std::make_unique(get_context()), config_manager_commands_group); +} + +void ConfigManagerCommand::pre_configure() { + throw_missing_command(); +} + +} // namespace dnf5 diff --git a/dnf5-plugins/config-manager_plugin/config-manager.hpp b/dnf5-plugins/config-manager_plugin/config-manager.hpp new file mode 100644 index 000000000..d14c7680a --- /dev/null +++ b/dnf5-plugins/config-manager_plugin/config-manager.hpp @@ -0,0 +1,40 @@ +/* +Copyright Contributors to the libdnf project. + +This file is part of libdnf: https://github.com/rpm-software-management/libdnf/ + +Libdnf is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 2 of the License, or +(at your option) any later version. + +Libdnf is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with libdnf. If not, see . +*/ + + +#ifndef DNF5_COMMANDS_CONFIG_MANAGER_CONFIG_MANAGER_HPP +#define DNF5_COMMANDS_CONFIG_MANAGER_CONFIG_MANAGER_HPP + +#include + +namespace dnf5 { + +class ConfigManagerCommand : public Command { +public: + explicit ConfigManagerCommand(Context & context) : Command(context, "config-manager") {} + void set_parent_command() override; + void set_argument_parser() override; + void register_subcommands() override; + void pre_configure() override; +}; + +} // namespace dnf5 + + +#endif // DNF5_COMMANDS_CONFIG_MANAGER_CONFIG_MANAGER_HPP diff --git a/dnf5-plugins/config-manager_plugin/config-manager_cmd_plugin.cpp b/dnf5-plugins/config-manager_plugin/config-manager_cmd_plugin.cpp new file mode 100644 index 000000000..8da8769be --- /dev/null +++ b/dnf5-plugins/config-manager_plugin/config-manager_cmd_plugin.cpp @@ -0,0 +1,74 @@ +#include "config-manager.hpp" + +#include + +#include + +using namespace dnf5; + +namespace { + +constexpr const char * PLUGIN_NAME{"config-manager"}; +constexpr PluginVersion PLUGIN_VERSION{.major = 0, .minor = 1, .micro = 0}; + +constexpr const char * attrs[]{"author.name", "author.email", "description", nullptr}; +constexpr const char * attrs_value[]{"Jaroslav Rohel", "jrohel@redhat.com", "config-manager command"}; + +class ConfigManagerCmdPlugin : public IPlugin { +public: + using IPlugin::IPlugin; + + PluginAPIVersion get_api_version() const noexcept override { return PLUGIN_API_VERSION; } + + const char * get_name() const noexcept override { return PLUGIN_NAME; } + + PluginVersion get_version() const noexcept override { return PLUGIN_VERSION; } + + const char * const * get_attributes() const noexcept override { return attrs; } + + const char * get_attribute(const char * attribute) const noexcept override { + for (size_t i = 0; attrs[i]; ++i) { + if (std::strcmp(attribute, attrs[i]) == 0) { + return attrs_value[i]; + } + } + return nullptr; + } + + std::vector> create_commands() override; + + void finish() noexcept override {} +}; + + +std::vector> ConfigManagerCmdPlugin::create_commands() { + std::vector> commands; + commands.push_back(std::make_unique(get_context())); + return commands; +} + + +} // namespace + + +PluginAPIVersion dnf5_plugin_get_api_version(void) { + return PLUGIN_API_VERSION; +} + +const char * dnf5_plugin_get_name(void) { + return PLUGIN_NAME; +} + +PluginVersion dnf5_plugin_get_version(void) { + return PLUGIN_VERSION; +} + +IPlugin * dnf5_plugin_new_instance([[maybe_unused]] ApplicationVersion application_version, Context & context) try { + return new ConfigManagerCmdPlugin(context); +} catch (...) { + return nullptr; +} + +void dnf5_plugin_delete_instance(IPlugin * plugin_object) { + delete plugin_object; +} diff --git a/dnf5-plugins/config-manager_plugin/setopts.cpp b/dnf5-plugins/config-manager_plugin/setopts.cpp new file mode 100644 index 000000000..58291c716 --- /dev/null +++ b/dnf5-plugins/config-manager_plugin/setopts.cpp @@ -0,0 +1,206 @@ +/* +Copyright Contributors to the libdnf project. + +This file is part of libdnf: https://github.com/rpm-software-management/libdnf/ + +Libdnf is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 2 of the License, or +(at your option) any later version. + +Libdnf is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with libdnf. If not, see . +*/ + +#include "setopts.hpp" + +#include +#include + +#include + +namespace dnf5 { + +using namespace libdnf5::cli; + +namespace { + +constexpr std::string_view REPOS_OVERRIDE_CFG_HEADER = + "# Generated by dnf5 config-manager.\n# Do not modify this file manually, use dnf5 config-manager instead.\n"; + +const std::filesystem::path CFG_MANAGER_REPOS_OVERRIDE_FILENAME = "99-config_manager.repo"; + +const std::filesystem::path CFG_MANAGER_REPOS_OVERRIDE_FILEPATH = + libdnf5::REPOS_OVERRIDE_DIR / CFG_MANAGER_REPOS_OVERRIDE_FILENAME; + + +void modify_config( + libdnf5::ConfigParser & parser, const std::string & section_id, const std::map & opts) { + if (!parser.has_section(section_id)) { + parser.add_section(section_id); + } + for (const auto & [key, value] : opts) { + parser.set_value(section_id, key, value); + } +} + +} // namespace + + +void ConfigManagerCommandSetOpts::set_argument_parser() { + auto & ctx = get_context(); + auto & parser = ctx.get_argument_parser(); + + auto & cmd = *get_argument_parser_command(); + cmd.set_description("Set configuration and repositories options"); + + auto opts_vals = + parser.add_new_positional_arg("optvals", ArgumentParser::PositionalArg::AT_LEAST_ONE, nullptr, nullptr); + opts_vals->set_description("List of options with values. Format: \"[REPO_ID.]option=value\""); + opts_vals->set_parse_hook_func([this, &ctx]( + [[maybe_unused]] ArgumentParser::PositionalArg * arg, + int argc, + const char * const argv[]) { + for (int i = 0; i < argc; ++i) { + auto value = argv[i]; + auto val = strchr(value + 1, '='); + if (!val) { + throw libdnf5::cli::ArgumentParserError(M_("optval: Badly formatted argument value \"{}\""), value); + } + std::string key{value, val}; + std::string key_value{val + 1}; + auto dot_pos = key.rfind('.'); + if (dot_pos != std::string::npos) { + if (dot_pos == key.size() - 1) { + throw libdnf5::cli::ArgumentParserError( + M_("optval: Badly formatted argument value: Last key character cannot be '.': {}"), value); + } + + // Save the repository option for later processing (solving glob patter, writing to file). + auto repo_id = key.substr(0, dot_pos); + if (repo_id.empty()) { + throw libdnf5::cli::ArgumentParserError( + M_("optval: Empty repository id is not allowed: {}"), value); + } + auto repo_key = key.substr(dot_pos + 1); + const auto [it, inserted] = in_repos_setopts[repo_id].insert({repo_key, key_value}); + if (!inserted) { + if (it->second != key_value) { + throw Error( + M_("Sets the \"{}\" option of the repository \"{}\" again with a different value: \"{}\" " + "!= \"{}\""), + repo_key, + repo_id, + it->second, + key_value); + } + } + } else { + // Save the global option for later writing to a file. + const auto [it, inserted] = main_setopts.insert({key, key_value}); + if (!inserted) { + if (it->second != key_value) { + throw Error( + M_("Sets the \"{}\" option again with a different value: \"{}\" != \"{}\""), + key, + it->second, + key_value); + } + } + + // Immediately apply the global option for the runtime. + auto & conf = ctx.base.get_config(); + try { + conf.opt_binds().at(key).new_string(libdnf5::Option::Priority::COMMANDLINE, value); + } catch (const std::exception & ex) { + throw Error(M_("Cannot set option: \"{}\": {}"), value, std::string(ex.what())); + } + } + } + return true; + }); + cmd.register_positional_arg(opts_vals); +} + + +void ConfigManagerCommandSetOpts::pre_configure() { + auto & ctx = get_context(); + + // Modify main configuration file. + if (!main_setopts.empty()) { + libdnf5::ConfigParser parser; + + const auto & cfg_filepath = ctx.base.get_config().get_config_file_path_option().get_value(); + + if (std::filesystem::exists(cfg_filepath)) { + parser.read(cfg_filepath); + } + + modify_config(parser, "main", main_setopts); + parser.write(cfg_filepath, false); + } +} + + +void ConfigManagerCommandSetOpts::configure() { + auto & ctx = get_context(); + + libdnf5::repo::RepoQuery repo_query(ctx.base); + for (auto & [in_repo_id, repo_setopts] : in_repos_setopts) { + auto query = repo_query; + query.filter_id(in_repo_id, libdnf5::sack::QueryCmp::GLOB); + if (query.empty()) { + throw Error(M_("No matching repository to modify: {}"), in_repo_id); + } + for (auto repo : query) { + auto & repo_conf = repo->get_config(); + auto repo_id = repo_conf.get_id(); + for (const auto & [key, value] : repo_setopts) { + // Save the repository option for later writing to a file. + const auto [it, inserted] = matching_repos_setopts[repo_id].insert({key, value}); + if (!inserted) { + if (it->second != value) { + throw Error( + M_("Sets the \"{}\" option of the repository \"{}\" again with a different value: \"{}\" " + "!= \"{}\""), + key, + repo_id, + it->second, + value); + } + } + + // Apply repository options for the runtime. + try { + repo_conf.opt_binds().at(key).new_string(libdnf5::Option::Priority::COMMANDLINE, value); + } catch (const std::exception & ex) { + throw Error(M_("Cannot set repository option \"{}={}\": {}"), key, value, std::string(ex.what())); + } + } + } + } + + // Write new and modify existing options in the repositories overrides configuration file. + if (!matching_repos_setopts.empty()) { + libdnf5::ConfigParser parser; + + if (std::filesystem::exists(CFG_MANAGER_REPOS_OVERRIDE_FILEPATH)) { + parser.read(CFG_MANAGER_REPOS_OVERRIDE_FILEPATH); + } + + parser.get_header() = REPOS_OVERRIDE_CFG_HEADER; + + for (const auto & [repo_id, repo_opts] : matching_repos_setopts) { + modify_config(parser, repo_id, repo_opts); + } + + parser.write(CFG_MANAGER_REPOS_OVERRIDE_FILEPATH, false); + } +} + +} // namespace dnf5 diff --git a/dnf5-plugins/config-manager_plugin/setopts.hpp b/dnf5-plugins/config-manager_plugin/setopts.hpp new file mode 100644 index 000000000..ae4815c8d --- /dev/null +++ b/dnf5-plugins/config-manager_plugin/setopts.hpp @@ -0,0 +1,47 @@ +/* +Copyright Contributors to the libdnf project. + +This file is part of libdnf: https://github.com/rpm-software-management/libdnf/ + +Libdnf is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 2 of the License, or +(at your option) any later version. + +Libdnf is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with libdnf. If not, see . +*/ + + +#ifndef DNF5_COMMANDS_CONFIG_MANAGER_SETOPTS_HPP +#define DNF5_COMMANDS_CONFIG_MANAGER_SETOPTS_HPP + +#include + +#include +#include + +namespace dnf5 { + +class ConfigManagerCommandSetOpts : public Command { +public: + explicit ConfigManagerCommandSetOpts(Context & context) : Command(context, "setopts") {} + void set_argument_parser() override; + void pre_configure() override; + void configure() override; + +private: + std::map main_setopts; + std::map> in_repos_setopts; + std::map> matching_repos_setopts; +}; + +} // namespace dnf5 + + +#endif // DNF5_COMMANDS_CONFIG_MANAGER_SETOPTS_HPP diff --git a/dnf5-plugins/config-manager_plugin/setvars.cpp b/dnf5-plugins/config-manager_plugin/setvars.cpp new file mode 100644 index 000000000..5e74d7ff4 --- /dev/null +++ b/dnf5-plugins/config-manager_plugin/setvars.cpp @@ -0,0 +1,120 @@ +/* +Copyright Contributors to the libdnf project. + +This file is part of libdnf: https://github.com/rpm-software-management/libdnf/ + +Libdnf is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 2 of the License, or +(at your option) any later version. + +Libdnf is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with libdnf. If not, see . +*/ + +#include "setvars.hpp" + +#include +#include + +#include +#include + +namespace dnf5 { + +using namespace libdnf5::cli; + +namespace { + +void check_variable_name(const std::string & name) { + if (name.find_first_not_of("ABCDEFGHIJKLMNOPQRSTUVWXYZ" + "abcdefghijklmnopqrstuvwxyz" + "0123456789" + "_") != std::string::npos) { + throw Error(M_("Variable name can contain only ASCII letters, numbers and '_': {}"), name); + } +} + +} // namespace + + +void ConfigManagerCommandSetVars::set_argument_parser() { + auto & ctx = get_context(); + auto & parser = ctx.get_argument_parser(); + + auto & cmd = *get_argument_parser_command(); + cmd.set_description("Set variables"); + + auto vars_vals = + parser.add_new_positional_arg("varvals", ArgumentParser::PositionalArg::AT_LEAST_ONE, nullptr, nullptr); + vars_vals->set_description("List of variables with values. Format: \"variable=value\""); + vars_vals->set_parse_hook_func( + [this, &ctx]([[maybe_unused]] ArgumentParser::PositionalArg * arg, int argc, const char * const argv[]) { + for (int i = 0; i < argc; ++i) { + auto value = argv[i]; + auto val = strchr(value + 1, '='); + if (!val) { + throw libdnf5::cli::ArgumentParserError(M_("varval: Badly formatted argument value \"{}\""), value); + } + std::string var_name{value, val}; + std::string var_value{val + 1}; + + check_variable_name(var_name); + + // Save the global option for later writing to a file. + const auto [it, inserted] = setvars.insert({var_name, var_value}); + if (!inserted) { + if (it->second != var_value) { + throw Error( + M_("Sets the \"{}\" variable again with a different value: \"{}\" != \"{}\""), + var_name, + it->second, + var_value); + } + } + + // Immediately apply the variable for the runtime. + auto vars = ctx.base.get_vars(); + try { + vars->set(var_name, var_value, libdnf5::Vars::Priority::COMMANDLINE); + } catch (const std::exception & ex) { + throw Error(M_("Cannot set variable \"{}\": {}"), value, std::string(ex.what())); + } + } + return true; + }); + cmd.register_positional_arg(vars_vals); +} + + +void ConfigManagerCommandSetVars::pre_configure() { + auto & ctx = get_context(); + + if (!setvars.empty()) { + const auto & vars_dirs = ctx.base.get_config().get_varsdir_option().get_value(); + if (vars_dirs.empty()) { + throw Error(M_("Missing path to vars directory")); + } + + const std::filesystem::path vars_dir = vars_dirs.back(); + + for (const auto & [name, value] : setvars) { + const auto filepath = vars_dir / name; + std::ofstream file; + file.exceptions(std::ofstream::failbit | std::ofstream::badbit); + try { + file.open(filepath, std::ios_base::trunc | std::ios_base::binary); + file << value; + } catch (const std::ios_base::failure & e) { + throw Error(M_("Cannot write variable to file \"{}\": {}"), filepath.native(), e.what()); + } + } + } +} + +} // namespace dnf5 diff --git a/dnf5-plugins/config-manager_plugin/setvars.hpp b/dnf5-plugins/config-manager_plugin/setvars.hpp new file mode 100644 index 000000000..d5f47ef80 --- /dev/null +++ b/dnf5-plugins/config-manager_plugin/setvars.hpp @@ -0,0 +1,44 @@ +/* +Copyright Contributors to the libdnf project. + +This file is part of libdnf: https://github.com/rpm-software-management/libdnf/ + +Libdnf is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 2 of the License, or +(at your option) any later version. + +Libdnf is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with libdnf. If not, see . +*/ + + +#ifndef DNF5_COMMANDS_CONFIG_MANAGER_SETVARS_HPP +#define DNF5_COMMANDS_CONFIG_MANAGER_SETVARS_HPP + +#include + +#include +#include + +namespace dnf5 { + +class ConfigManagerCommandSetVars : public Command { +public: + explicit ConfigManagerCommandSetVars(Context & context) : Command(context, "setvars") {} + void set_argument_parser() override; + void pre_configure() override; + +private: + std::map setvars; +}; + +} // namespace dnf5 + + +#endif // DNF5_COMMANDS_CONFIG_MANAGER_SETVARS_HPP diff --git a/dnf5-plugins/config-manager_plugin/unsetopts.cpp b/dnf5-plugins/config-manager_plugin/unsetopts.cpp new file mode 100644 index 000000000..47b0f8394 --- /dev/null +++ b/dnf5-plugins/config-manager_plugin/unsetopts.cpp @@ -0,0 +1,164 @@ +/* +Copyright Contributors to the libdnf project. + +This file is part of libdnf: https://github.com/rpm-software-management/libdnf/ + +Libdnf is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 2 of the License, or +(at your option) any later version. + +Libdnf is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with libdnf. If not, see . +*/ + +#include "unsetopts.hpp" + +#include +#include + +#include + +namespace dnf5 { + +using namespace libdnf5::cli; + +namespace { + +const std::filesystem::path CFG_MANAGER_REPOS_OVERRIDE_FILENAME = "99-config_manager.repo"; + +const std::filesystem::path CFG_MANAGER_REPOS_OVERRIDE_FILEPATH = + libdnf5::REPOS_OVERRIDE_DIR / CFG_MANAGER_REPOS_OVERRIDE_FILENAME; + +bool remove_from_config( + libdnf5::ConfigParser & parser, const std::string & section_id, const std::set & keys) { + bool removed = false; + for (const auto & key : keys) { + removed |= parser.remove_option(section_id, key); + } + return removed; +} + +} // namespace + + +void ConfigManagerCommandUnsetOpts::set_argument_parser() { + auto & ctx = get_context(); + auto & parser = ctx.get_argument_parser(); + + auto & cmd = *get_argument_parser_command(); + cmd.set_description("Unset/remove configuration and repositories options"); + + auto opts_vals = + parser.add_new_positional_arg("options", ArgumentParser::PositionalArg::AT_LEAST_ONE, nullptr, nullptr); + opts_vals->set_description("List of options to unset"); + opts_vals->set_parse_hook_func( + [this, &ctx]([[maybe_unused]] ArgumentParser::PositionalArg * arg, int argc, const char * const argv[]) { + for (int i = 0; i < argc; ++i) { + auto value = argv[i]; + std::string key{value}; + auto dot_pos = key.rfind('.'); + if (dot_pos != std::string::npos) { + if (dot_pos == key.size() - 1) { + throw libdnf5::cli::ArgumentParserError( + M_("remove-opt: Badly formatted argument value: Last key character cannot be '.': {}"), + value); + } + + // Save the repository option for later processing (solving glob patter, writing to file). + auto repo_id = key.substr(0, dot_pos); + if (repo_id.empty()) { + throw libdnf5::cli::ArgumentParserError( + M_("remove-opt: Empty repository id is not allowed: {}"), value); + } + auto repo_key = key.substr(dot_pos + 1); + in_repos_opts_to_remove[repo_id].insert(repo_key); + } else { + // Save the global option for later removing from the file. + main_opts_to_remove.insert(key); + + // Test if the global option is known. + auto & conf = ctx.base.get_config(); + try { + conf.opt_binds().at(key); + } catch (const libdnf5::OptionBindsOptionNotFoundError & ex) { + ctx.base.get_logger()->warning( + "config-manager: Request to remove unknown main option from config file: {}", key); + } + } + } + return true; + }); + cmd.register_positional_arg(opts_vals); +} + + +void ConfigManagerCommandUnsetOpts::pre_configure() { + auto & ctx = get_context(); + + // Modify main configuration file. + const auto & cfg_filepath = ctx.base.get_config().get_config_file_path_option().get_value(); + if (!main_opts_to_remove.empty() && std::filesystem::exists(cfg_filepath)) { + libdnf5::ConfigParser parser; + bool changed = false; + + parser.read(cfg_filepath); + + changed |= remove_from_config(parser, "main", main_opts_to_remove); + + if (changed) { + parser.write(cfg_filepath, false); + } + } + + // Remove options from repositories overrides configuration file, remove empty sections. + { + if (!in_repos_opts_to_remove.empty() && std::filesystem::exists(CFG_MANAGER_REPOS_OVERRIDE_FILEPATH)) { + libdnf5::repo::ConfigRepo repo_conf(ctx.base.get_config(), "temporary_to_check_known_options"); + + libdnf5::ConfigParser parser; + bool changed = false; + parser.read(CFG_MANAGER_REPOS_OVERRIDE_FILEPATH); + + std::vector empty_config_sections; + for (const auto & [repo_id, setopts] : parser.get_data()) { + for (const auto & [in_repoid, keys] : in_repos_opts_to_remove) { + if (libdnf5::sack::match_string(repo_id, libdnf5::sack::QueryCmp::GLOB, in_repoid)) { + // Test if the repository optiona are known. + for (const auto & key : keys) { + try { + repo_conf.opt_binds().at(key); + } catch (const libdnf5::OptionBindsOptionNotFoundError & ex) { + ctx.base.get_logger()->warning( + "config-manager: Request to remove unknown repository option from config file: {}", + key); + } + } + + changed |= remove_from_config(parser, repo_id, keys); + } + } + if (setopts.empty()) { + empty_config_sections.emplace_back(repo_id); + } + } + + // Clean config - remove empty sections. + for (const auto & section : empty_config_sections) { + parser.remove_section(section); + changed = true; + } + + if (changed) { + parser.write(CFG_MANAGER_REPOS_OVERRIDE_FILEPATH, false); + } + } + } +} + +} // namespace dnf5 diff --git a/dnf5-plugins/config-manager_plugin/unsetopts.hpp b/dnf5-plugins/config-manager_plugin/unsetopts.hpp new file mode 100644 index 000000000..463725a40 --- /dev/null +++ b/dnf5-plugins/config-manager_plugin/unsetopts.hpp @@ -0,0 +1,46 @@ +/* +Copyright Contributors to the libdnf project. + +This file is part of libdnf: https://github.com/rpm-software-management/libdnf/ + +Libdnf is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 2 of the License, or +(at your option) any later version. + +Libdnf is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with libdnf. If not, see . +*/ + + +#ifndef DNF5_COMMANDS_CONFIG_MANAGER_UNSETOPTS_HPP +#define DNF5_COMMANDS_CONFIG_MANAGER_UNSETOPTS_HPP + +#include + +#include +#include +#include + +namespace dnf5 { + +class ConfigManagerCommandUnsetOpts : public Command { +public: + explicit ConfigManagerCommandUnsetOpts(Context & context) : Command(context, "unsetopts") {} + void set_argument_parser() override; + void pre_configure() override; + +private: + std::set main_opts_to_remove; + std::map> in_repos_opts_to_remove; +}; + +} // namespace dnf5 + + +#endif // DNF5_COMMANDS_CONFIG_MANAGER_UNSETOPTS_HPP diff --git a/dnf5-plugins/config-manager_plugin/unsetvars.cpp b/dnf5-plugins/config-manager_plugin/unsetvars.cpp new file mode 100644 index 000000000..ad240b3d1 --- /dev/null +++ b/dnf5-plugins/config-manager_plugin/unsetvars.cpp @@ -0,0 +1,78 @@ +/* +Copyright Contributors to the libdnf project. + +This file is part of libdnf: https://github.com/rpm-software-management/libdnf/ + +Libdnf is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 2 of the License, or +(at your option) any later version. + +Libdnf is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with libdnf. If not, see . +*/ + +#include "unsetvars.hpp" + +#include +#include +#include + +#include +#include + +namespace dnf5 { + +using namespace libdnf5::cli; + +void ConfigManagerCommandUnsetVars::set_argument_parser() { + auto & ctx = get_context(); + auto & parser = ctx.get_argument_parser(); + + auto & cmd = *get_argument_parser_command(); + cmd.set_description("Unset/remove variables"); + + auto vars = + parser.add_new_positional_arg("variables", ArgumentParser::PositionalArg::AT_LEAST_ONE, nullptr, nullptr); + vars->set_description("List of variables to unset"); + vars->set_parse_hook_func( + [this]([[maybe_unused]] ArgumentParser::PositionalArg * arg, int argc, const char * const argv[]) { + for (int i = 0; i < argc; ++i) { + std::string var_name{argv[i]}; + // Save the global option for later removing from the file. + vars_to_remove.insert(var_name); + } + return true; + }); + cmd.register_positional_arg(vars); +} + +void ConfigManagerCommandUnsetVars::pre_configure() { + auto & ctx = get_context(); + if (!vars_to_remove.empty()) { + const auto & vars_dirs = ctx.base.get_config().get_varsdir_option().get_value(); + if (vars_dirs.empty()) { + throw Error(M_("Missing path to vars directory")); + } + + const std::filesystem::path vars_dir = vars_dirs.back(); + + if (!vars_to_remove.empty()) { + for (const auto & name : vars_to_remove) { + const auto filepath = vars_dir / name; + try { + std::filesystem::remove(filepath); + } catch (const std::filesystem::filesystem_error & e) { + throw Error(M_("Cannot remove variable file \"{}\": {}"), filepath.native(), e.what()); + } + } + } + } +} + +} // namespace dnf5 diff --git a/dnf5-plugins/config-manager_plugin/unsetvars.hpp b/dnf5-plugins/config-manager_plugin/unsetvars.hpp new file mode 100644 index 000000000..303214c78 --- /dev/null +++ b/dnf5-plugins/config-manager_plugin/unsetvars.hpp @@ -0,0 +1,44 @@ +/* +Copyright Contributors to the libdnf project. + +This file is part of libdnf: https://github.com/rpm-software-management/libdnf/ + +Libdnf is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 2 of the License, or +(at your option) any later version. + +Libdnf is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with libdnf. If not, see . +*/ + + +#ifndef DNF5_COMMANDS_CONFIG_MANAGER_UNSETVARS_HPP +#define DNF5_COMMANDS_CONFIG_MANAGER_UNSETVARS_HPP + +#include + +#include +#include + +namespace dnf5 { + +class ConfigManagerCommandUnsetVars : public Command { +public: + explicit ConfigManagerCommandUnsetVars(Context & context) : Command(context, "unsetvars") {} + void set_argument_parser() override; + void pre_configure() override; + +private: + std::set vars_to_remove; +}; + +} // namespace dnf5 + + +#endif // DNF5_COMMANDS_CONFIG_MANAGER_UNSETVARS_HPP diff --git a/dnf5.spec b/dnf5.spec index ea34264e5..d504245db 100644 --- a/dnf5.spec +++ b/dnf5.spec @@ -170,6 +170,10 @@ BuildRequires: libubsan BuildRequires: pkgconfig(smartcols) %endif +%if %{with dnf5_plugins} +BuildRequires: libcurl-devel >= 7.62.0 +%endif + %if %{with dnf5daemon_server} # required for dnf5daemon-server BuildRequires: pkgconfig(sdbus-c++) >= 0.8.1 @@ -638,13 +642,16 @@ Package management service with a DBus interface. Summary: Plugins for dnf5 License: LGPL-2.1-or-later Requires: dnf5%{?_isa} = %{version}-%{release} +Requires: libcurl%{?_isa} >= 7.62.0 Provides: dnf5-command(builddep) Provides: dnf5-command(changelog) +Provides: dnf5-command(config-manager) Provides: dnf5-command(copr) Provides: dnf5-command(repoclosure) %description -n dnf5-plugins -Core DNF5 plugins that enhance dnf5 with builddep, changelog, copr, and repoclosure commands. +Core DNF5 plugins that enhance dnf5 with builddep, changelog, +config-manager, copr, and repoclosure commands. %files -n dnf5-plugins %{_libdir}/dnf5/plugins/*.so