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

RFC: Add XML output option to 'dnf5 advisories list' #1524

Closed
Show file tree
Hide file tree
Changes from all 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
Add XML output option to 'dnf5 advisories list'
Add an option to output the list of applicable advisories in the same
XML format as the updateinfo.xml file in the repository.

This allows tooling to have a consistent machine readable format for
getting what advisories apply to a system. Resorting to parsing text
output of commands is (naturally) error prone, and designed for humans
to look at rather than machines.

Alternatives for these tools include using the APIs of
dnf/yum/other-rpm-package-manager, and keeping up with their evolution.
For tooling that supports a wide range of distributions and versions,
this can become rather onerous, especially across the many
different security/patch management products.

With a documented format, we can help these tools get the correct
information both on available updates, and what ones are applicable to
particular systems. Using the same format these tools need to be able to
parse already avoids the need to get multiple parsers correct.

Signed-off-by: Stewart Smith <trawets@amazon.com>
  • Loading branch information
stewartsmith committed May 31, 2024
commit 76a87eebe10ad30b59645d4899542ca9a387e918
19 changes: 12 additions & 7 deletions dnf5/commands/advisory/advisory_list.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ along with libdnf. If not, see <https://www.gnu.org/licenses/>.

#include <libdnf5-cli/output/adapters/advisory.hpp>
#include <libdnf5-cli/output/advisorylist.hpp>
#include <libdnf5-cli/output/xmladvisorylist.hpp>
#include <libdnf5/advisory/advisory_package.hpp>
#include <libdnf5/rpm/package_query.hpp>

Expand Down Expand Up @@ -51,22 +52,26 @@ void AdvisoryListCommand::process_and_print_queries(
packages.filter_upgradable();
not_installed_pkgs = advisories.get_advisory_packages_sorted(packages, libdnf5::sack::QueryCmp::GT);
} else { // available is the default
libdnf5::rpm::PackageQuery installed_packages(ctx.get_base());
installed_packages.filter_installed();
installed_packages.filter_latest_evr();
packages = libdnf5::rpm::PackageQuery(ctx.get_base());
packages.filter_installed();
packages.filter_latest_evr();

add_running_kernel_packages(ctx.get_base(), installed_packages);
add_running_kernel_packages(ctx.get_base(), packages);

if (package_specs.size() > 0) {
installed_packages.filter_name(package_specs, libdnf5::sack::QueryCmp::IGLOB);
packages.filter_name(package_specs, libdnf5::sack::QueryCmp::IGLOB);
}

not_installed_pkgs = advisories.get_advisory_packages_sorted(installed_packages, libdnf5::sack::QueryCmp::GT);
not_installed_pkgs = advisories.get_advisory_packages_sorted(packages, libdnf5::sack::QueryCmp::GT);
}

std::vector<std::unique_ptr<libdnf5::cli::output::IAdvisoryPackage>> cli_installed_pkgs;
std::vector<std::unique_ptr<libdnf5::cli::output::IAdvisoryPackage>> cli_not_installed_pkgs;
if (with_bz->get_value()) {
if (as_xml->get_value()) {
libdnf5::advisory::AdvisoryQuery advisory_subset(advisories);
advisory_subset.filter_packages(packages, libdnf5::sack::QueryCmp::GT);
libdnf5::cli::output::print_xmladvisorylist(advisory_subset);
} else if (with_bz->get_value()) {
libdnf5::cli::output::print_advisorylist_references_table(not_installed_pkgs, installed_pkgs, "bugzilla");
} else if (with_cve->get_value()) {
libdnf5::cli::output::print_advisorylist_references_table(not_installed_pkgs, installed_pkgs, "cve");
Expand Down
1 change: 1 addition & 0 deletions dnf5/commands/advisory/advisory_subcommand.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ void AdvisorySubCommand::set_argument_parser() {
advisory_severity = std::make_unique<AdvisorySeverityOption>(*this);
advisory_bz = std::make_unique<BzOption>(*this);
advisory_cve = std::make_unique<CveOption>(*this);
as_xml = std::make_unique<AdvisoryListAsXmlOption>(*this);

with_bz = std::make_unique<AdvisoryWithBzOption>(*this);
with_cve = std::make_unique<AdvisoryWithCveOption>(*this);
Expand Down
1 change: 1 addition & 0 deletions dnf5/commands/advisory/advisory_subcommand.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ class AdvisorySubCommand : public Command {
std::unique_ptr<AdvisorySpecArguments> advisory_specs{nullptr};
std::unique_ptr<AdvisoryWithBzOption> with_bz{nullptr};
std::unique_ptr<AdvisoryWithCveOption> with_cve{nullptr};
std::unique_ptr<AdvisoryListAsXmlOption> as_xml{nullptr};

AdvisorySubCommand(Context & context, const std::string & name) : Command(context, name) {}

Expand Down
5 changes: 5 additions & 0 deletions dnf5/commands/advisory/arguments.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,11 @@ class AdvisorySpecArguments : public libdnf5::cli::session::StringArgumentList {
: StringArgumentList(command, "advisory-spec", _("List of patterns matched against advisory names.")) {}
};

class AdvisoryListAsXmlOption : public libdnf5::cli::session::BoolOption {
public:
explicit AdvisoryListAsXmlOption(libdnf5::cli::session::Command & command)
: BoolOption(command, "xml-output", '\0', _("Output advisory list in updateinfo.xml format."), false) {}
};

} // namespace dnf5

Expand Down
2 changes: 1 addition & 1 deletion include/libdnf5-cli/output/adapters/advisory_tmpl.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -175,7 +175,7 @@ class AdvisoryAdapter : public IAdvisory {
return ret;
}

std::vector<std::unique_ptr<IAdvisoryCollection>> get_collections() const override {
std::vector<std::unique_ptr<IAdvisoryCollection>> get_collections() override {
std::vector<std::unique_ptr<IAdvisoryCollection>> ret;
auto collections = pkg.get_collections();
ret.reserve(collections.size());
Expand Down
2 changes: 1 addition & 1 deletion include/libdnf5-cli/output/interfaces/advisory.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,7 @@ class IAdvisory {
virtual std::string get_rights() const = 0;
virtual std::string get_message() const = 0;
virtual std::vector<std::unique_ptr<IAdvisoryReference>> get_references() const = 0;
virtual std::vector<std::unique_ptr<IAdvisoryCollection>> get_collections() const = 0;
virtual std::vector<std::unique_ptr<IAdvisoryCollection>> get_collections() = 0;
};

} // namespace libdnf5::cli::output
Expand Down
39 changes: 39 additions & 0 deletions include/libdnf5-cli/output/xmladvisorylist.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
/*
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 Lesser General Public License as published by
the Free Software Foundation, either version 2.1 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 Lesser General Public License for more details.

You should have received a copy of the GNU Lesser General Public License
along with libdnf. If not, see <https://www.gnu.org/licenses/>.
*/


#ifndef LIBDNF5_CLI_OUTPUT_XMLADVISORYLIST_HPP
#define LIBDNF5_CLI_OUTPUT_XMLADVISORYLIST_HPP

#include "interfaces/advisory.hpp"

#include <libdnf5/advisory/advisory.hpp>
#include <libdnf5/advisory/advisory_package.hpp>
#include <libdnf5/advisory/advisory_reference.hpp>
#include <libdnf5/advisory/advisory_collection.hpp>
#include <libdnf5/advisory/advisory_query.hpp>

namespace libdnf5::cli::output {

void print_xmladvisorylist(
libdnf5::advisory::AdvisoryQuery advisories);

} // namespace libdnf5::cli::output

#endif // LIBDNF5_CLI_OUTPUT_XMLADVISORYLIST_HPP
5 changes: 5 additions & 0 deletions include/libdnf5/advisory/advisory_package.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,11 @@ class AdvisoryPackage {
/// @return Arch of this AdvisoryPackage as std::string.
std::string get_arch() const;

/// Get the filename of this AdvisoryPackage.
///
/// @return Filename of this AdvisoryPackage as std::string.
std::string get_filename() const;

/// Get NEVRA of this AdvisoryPackage.
///
/// @return NEVRA of this AdvisoryPackage as std::string.
Expand Down
File renamed without changes.
4 changes: 4 additions & 0 deletions libdnf5-cli/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,10 @@ pkg_check_modules(SMARTCOLS REQUIRED smartcols)
list(APPEND LIBDNF5_CLI_PC_REQUIRES_PRIVATE "${SMARTCOLS_MODULE_NAME}")
target_link_libraries(libdnf5-cli PRIVATE ${SMARTCOLS_LIBRARIES})

pkg_check_modules(LIBXML2 REQUIRED libxml-2.0)
list(APPEND LIBDNF5_PC_REQUIRES_PRIVATE "${LIBXML2_MODULE_NAME}")
include_directories(${LIBXML2_INCLUDE_DIRS})
target_link_libraries(libdnf5 PRIVATE ${LIBXML2_LIBRARIES})

# sort the pkg-config requires and concatenate them into a string
list(SORT LIBDNF5_CLI_PC_REQUIRES)
Expand Down
123 changes: 123 additions & 0 deletions libdnf5-cli/output/xmladvisorylist.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
/*
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 Lesser General Public License as published by
the Free Software Foundation, either version 2.1 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 Lesser General Public License for more details.

You should have received a copy of the GNU Lesser General Public License
along with libdnf. If not, see <https://www.gnu.org/licenses/>.
*/

#include "libdnf5-cli/output/xmladvisorylist.hpp"

#include "utils/string.hpp"

#include <libxml/tree.h>
#include <libxml/xmlwriter.h>
#include <libdnf5/utils/xml.hpp>

#include <unistd.h>
#include <iostream>

#include "libdnf5/utils/bgettext/bgettext-mark-domain.h"

namespace libdnf5::cli::output {

namespace {
void add_advisory_to_node(xmlNodePtr node, auto & advisory) {
xmlNodePtr update_node = xmlNewChild(node, NULL, BAD_CAST "update", NULL);
xmlNewProp(update_node, BAD_CAST "from", BAD_CAST advisory.get_vendor().c_str());
xmlNewProp(update_node, BAD_CAST "status", BAD_CAST advisory.get_status().c_str());
xmlNewProp(update_node, BAD_CAST "type", BAD_CAST advisory.get_type().c_str());
xmlNewProp(update_node, BAD_CAST "version", BAD_CAST "2.0");

utils::xml::add_subnode_with_text(update_node, "id", advisory.get_name());
utils::xml::add_subnode_with_text(update_node, "severity", advisory.get_severity());
utils::xml::add_subnode_with_text(update_node, "title", advisory.get_title());

std::string message = advisory.get_message();
if (message.size() > 0)
utils::xml::add_subnode_with_text(update_node, "message", message);

// libsolv uses the later of issued/updated, so just display as updated
// This is probably a FIXME
unsigned long long buildtime = advisory.get_buildtime();
xmlNodePtr buildtime_node = xmlNewChild(update_node, NULL, BAD_CAST "updated", NULL);
xmlNewProp(buildtime_node, BAD_CAST "date", BAD_CAST libdnf5::utils::string::format_epoch(buildtime).c_str());

utils::xml::add_subnode_with_text(update_node, "description", advisory.get_description());

std::string rights = advisory.get_rights();
if (rights.size() > 0)
utils::xml::add_subnode_with_text(update_node, "rights", rights);

auto references = advisory.get_references();
if (references.size() > 0) {
xmlNodePtr references_node = xmlNewChild(update_node, NULL, BAD_CAST "references", NULL);
for (auto & ref : advisory.get_references()) {
xmlNodePtr ref_node = xmlNewChild(references_node, NULL, BAD_CAST "reference", NULL);
xmlNewProp(ref_node, BAD_CAST "href", BAD_CAST ref.get_url().c_str());
xmlNewProp(ref_node, BAD_CAST "id", BAD_CAST ref.get_id().c_str());
xmlNewProp(ref_node, BAD_CAST "title", BAD_CAST ref.get_title().c_str());
xmlNewProp(ref_node, BAD_CAST "type", BAD_CAST ref.get_type().c_str());
}
}
auto collections = advisory.get_collections();
if (collections.size() > 0) {
for (auto & collection : collections) {
auto pkgs = collection.get_packages();
if (pkgs.size() > 0) {
xmlNodePtr pkglist_node = xmlNewChild(update_node, NULL, BAD_CAST "pkglist", NULL);
xmlNodePtr collection_node = xmlNewChild(pkglist_node, NULL, BAD_CAST "collection", NULL);
for (auto & pkg : pkgs) {
xmlNodePtr package_node = xmlNewChild(collection_node, NULL, BAD_CAST "package", NULL);
xmlNewProp(package_node, BAD_CAST "name", BAD_CAST pkg.get_name().c_str());
xmlNewProp(package_node, BAD_CAST "version", BAD_CAST pkg.get_version().c_str());
xmlNewProp(package_node, BAD_CAST "release", BAD_CAST pkg.get_release().c_str());
xmlNewProp(package_node, BAD_CAST "epoch", BAD_CAST pkg.get_epoch().c_str());
xmlNewProp(package_node, BAD_CAST "arch", BAD_CAST pkg.get_arch().c_str());
std::string filename = pkg.get_filename();
if (filename.size() > 0)
xmlNewChild(package_node, NULL, BAD_CAST "filename", BAD_CAST filename.c_str());
if (pkg.get_reboot_suggested())
xmlNewChild(package_node, NULL, BAD_CAST "reboot_suggested", BAD_CAST "True");
if (pkg.get_restart_suggested())
xmlNewChild(package_node, NULL, BAD_CAST "restart_suggested", BAD_CAST "True");
if (pkg.get_relogin_suggested())
xmlNewChild(package_node, NULL, BAD_CAST "relogin_suggested", BAD_CAST "True");
}
}
}
}
}
}

void print_xmladvisorylist(
libdnf5::advisory::AdvisoryQuery advisories) {
xmlDocPtr doc = xmlNewDoc(BAD_CAST "1.0");
xmlNodePtr node_updates = xmlNewNode(NULL, BAD_CAST "updates");
xmlDocSetRootElement(doc, node_updates);

for (auto advisory : advisories) {
add_advisory_to_node(node_updates, advisory);
}

xmlChar *mem;
int sz;
xmlDocDumpFormatMemory(doc, &mem, &sz, 1);
std::cout << (char*)mem << std::endl;
xmlFree(mem);

xmlFreeDoc(doc);
}

}
7 changes: 7 additions & 0 deletions libdnf5/advisory/advisory_package.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,9 @@ std::string AdvisoryPackage::get_arch() const {
std::string AdvisoryPackage::get_nevra() const {
return p_impl->get_name() + "-" + p_impl->get_evr() + "." + p_impl->get_arch();
}
std::string AdvisoryPackage::get_filename() const {
return p_impl->get_filename();
}
AdvisoryId AdvisoryPackage::get_advisory_id() const {
return p_impl->get_advisory_id();
}
Expand Down Expand Up @@ -133,6 +136,10 @@ std::string AdvisoryPackage::Impl::get_arch() const {
return get_rpm_pool(base).id2str(arch);
}

std::string AdvisoryPackage::Impl::get_filename() const {
return filename;
}

bool AdvisoryPackage::Impl::is_resolved_in(const libdnf5::rpm::PackageSet & pkgs) const {
auto & pool = get_rpm_pool(base);
auto sack = base->get_rpm_package_sack();
Expand Down
1 change: 1 addition & 0 deletions libdnf5/advisory/advisory_package_private.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ class AdvisoryPackage::Impl {
std::string get_release() const;
std::string get_evr() const;
std::string get_arch() const;
std::string get_filename() const;
Id get_name_id() const { return name; };
Id get_evr_id() const { return evr; };
Id get_arch_id() const { return arch; };
Expand Down
2 changes: 1 addition & 1 deletion libdnf5/comps/environment/environment.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ along with libdnf. If not, see <https://www.gnu.org/licenses/>.
#include "libdnf5/comps/environment/environment.hpp"

#include "solv/pool.hpp"
#include "utils/xml.hpp"
#include "libdnf5/utils/xml.hpp"

#include "libdnf5/base/base.hpp"
#include "libdnf5/comps/environment/query.hpp"
Expand Down
2 changes: 1 addition & 1 deletion libdnf5/comps/group/group.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ along with libdnf. If not, see <https://www.gnu.org/licenses/>.

#include "solv/pool.hpp"
#include "utils/string.hpp"
#include "utils/xml.hpp"
#include "libdnf5/utils/xml.hpp"

#include "libdnf5/base/base.hpp"
#include "libdnf5/comps/group/package.hpp"
Expand Down
2 changes: 1 addition & 1 deletion libdnf5/repo/repo_sack.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ along with libdnf. If not, see <https://www.gnu.org/licenses/>.
#include "utils/fs/utils.hpp"
#include "utils/string.hpp"
#include "utils/url.hpp"
#include "utils/xml.hpp"
#include "libdnf5/utils/xml.hpp"

#include "libdnf5/base/base.hpp"
#include "libdnf5/common/exception.hpp"
Expand Down
2 changes: 1 addition & 1 deletion libdnf5/utils/xml.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ along with libdnf. If not, see <https://www.gnu.org/licenses/>.
*/


#include "xml.hpp"
#include <libdnf5/utils/xml.hpp>

#include <libxml/tree.h>

Expand Down
Loading