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

dnfdaemon: system-upgrade API and command #1588

Merged
merged 3 commits into from
Aug 7, 2024
Merged
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
81 changes: 81 additions & 0 deletions dnf5daemon-client/commands/system-upgrade/system-upgrade.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
/*
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 <https://www.gnu.org/licenses/>.
*/

#include "system-upgrade.hpp"

#include "commands/shared_options.hpp"
#include "context.hpp"
#include "exception.hpp"
#include "utils/auth.hpp"

#include <dnf5daemon-server/dbus.hpp>
#include <libdnf5/conf/option_string.hpp>

namespace dnfdaemon::client {

using namespace libdnf5::cli;

void SystemUpgradeCommand::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("software_management_commands").register_argument(arg_parser_this_cmd);
}

void SystemUpgradeCommand::set_argument_parser() {
auto & parser = get_context().get_argument_parser();
auto & cmd = *get_argument_parser_command();

cmd.set_description("prepare system for upgrade to a new release");

auto no_downgrade = parser.add_new_named_arg("no_downgrade");
no_downgrade->set_long_name("no-downgrade");
no_downgrade->set_description(
"Do not install packages from the new release if they are older than what is currently installed");
no_downgrade->set_has_value(true);
no_downgrade->set_arg_value_help("<yes|no>");
no_downgrade->link_value(&no_downgrade_option);
cmd.register_named_arg(no_downgrade);

// TODO(mblaha): set the releasever named arg as required (currently no API for this)
}

void SystemUpgradeCommand::run() {
auto & ctx = get_context();

if (!libdnf5::utils::am_i_root()) {
throw UnprivilegedUserError();
}

// TODO(mblaha): check the target releasever is set and different from the detected one

dnfdaemon::KeyValueMap options = {};
if (no_downgrade_option.get_value()) {
options["mode"] = "upgrade";
}

ctx.session_proxy->callMethod("system_upgrade")
.onInterface(dnfdaemon::INTERFACE_RPM)
.withTimeout(static_cast<uint64_t>(-1))
.withArguments(options);

run_transaction(true);
}

} // namespace dnfdaemon::client
42 changes: 42 additions & 0 deletions dnf5daemon-client/commands/system-upgrade/system-upgrade.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
/*
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 <https://www.gnu.org/licenses/>.
*/

#ifndef DNF5DAEMON_CLIENT_COMMANDS_SYSTEM_UPGRADE_SYSTEM_UPGRADE_HPP
#define DNF5DAEMON_CLIENT_COMMANDS_SYSTEM_UPGRADE_SYSTEM_UPGRADE_HPP

#include "commands/command.hpp"

#include <libdnf5/conf/option_bool.hpp>

namespace dnfdaemon::client {

class SystemUpgradeCommand : public TransactionCommand {
public:
explicit SystemUpgradeCommand(Context & context) : TransactionCommand(context, "system-upgrade") {}
void set_parent_command() override;
void set_argument_parser() override;
void run() override;

private:
libdnf5::OptionBool no_downgrade_option{false};
};

} // namespace dnfdaemon::client

#endif
2 changes: 2 additions & 0 deletions dnf5daemon-client/main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ along with libdnf. If not, see <https://www.gnu.org/licenses/>.
#include "commands/repo/repo.hpp"
#include "commands/repolist/repolist.hpp"
#include "commands/repoquery/repoquery.hpp"
#include "commands/system-upgrade/system-upgrade.hpp"
#include "commands/upgrade/upgrade.hpp"
#include "context.hpp"

Expand Down Expand Up @@ -211,6 +212,7 @@ static void add_commands(Context & context) {

context.add_and_initialize_command(std::make_unique<AdvisoryCommand>(context));
context.add_and_initialize_command(std::make_unique<CleanCommand>(context));
context.add_and_initialize_command(std::make_unique<SystemUpgradeCommand>(context));
}

} // namespace dnfdaemon::client
Expand Down
18 changes: 18 additions & 0 deletions dnf5daemon-server/dbus/interfaces/org.rpm.dnf.v0.rpm.Rpm.xml
Original file line number Diff line number Diff line change
Expand Up @@ -212,6 +212,24 @@ along with libdnf. If not, see <https://www.gnu.org/licenses/>.
<arg name="options" type="a{sv}" direction="in" />
</method>

<!--
system_upgrade:
@options: an array of key/value pairs to modify system_upgrade behavior

Prepare a transaction for upgrade to the new distribution release. The prepared transaction should be executed during reboot (see `offline` option of the `do_transaction` method of the `Goal` interface).
The method relies on the `releasever` option being correctly set to the new distribution release during the `open_session()` call.

Following @options are supported:

- mode: string (one of "distrosync", "upgrade", default is "distrosync")
By default the system_upgrade behaves like `dnf distro-sync`, always installing packages from the new release, even if they are older than the currently installed version. If set to "upgrade", packages from the new release are not installed if they are older than what is currently installed (behave like `dnf upgrade`).

Unknown options are ignored.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

how to I set the release version to be upgraded to? It seems to be "detected" somehow, but one can upgrade both to N+1 and N+2. An explicit "upgrade to releasever X" would be better.

The python example suggests the releasever is set within the open_session. Could there be added an argument, which will override that value if set, here as well, please?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Releasever should be set using the open_session() option. It needs to be set before the repositories metadata are loaded and setting it here can be too late. I'll add this information to the method description.

-->
<method name="system_upgrade">
<arg name="options" type="a{sv}" direction="in" />
</method>

<!--
transaction_elem_progress:
@session_object_path: object path of the dnf5daemon session
Expand Down
53 changes: 53 additions & 0 deletions dnf5daemon-server/services/rpm/rpm.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,16 @@ void Rpm::dbus_register() {
[this](sdbus::MethodCall call) -> void {
session.get_threads_manager().handle_method(*this, &Rpm::remove, call, session.session_locale);
});
dbus_object->registerMethod(
dnfdaemon::INTERFACE_RPM,
"system_upgrade",
"a{sv}",
{"options"},
"",
{},
[this](sdbus::MethodCall call) -> void {
session.get_threads_manager().handle_method(*this, &Rpm::system_upgrade, call, session.session_locale);
});

dbus_object->registerSignal(
dnfdaemon::INTERFACE_RPM, dnfdaemon::SIGNAL_TRANSACTION_BEFORE_BEGIN, "ot", {"session_object_path", "total"});
Expand Down Expand Up @@ -623,3 +633,46 @@ sdbus::MethodReply Rpm::remove(sdbus::MethodCall & call) {
auto reply = call.createReply();
return reply;
}

sdbus::MethodReply Rpm::system_upgrade(sdbus::MethodCall & call) {
if (!session.check_authorization(dnfdaemon::POLKIT_EXECUTE_RPM_TRANSACTION, call.getSender())) {
throw std::runtime_error("Not authorized");
}
auto base = session.get_base();

// read options from dbus call
dnfdaemon::KeyValueMap options;
call >> options;

// check that releasever is different than the detected one
auto target_releasever = base->get_vars()->get_value("releasever");
const std::filesystem::path installroot{base->get_config().get_installroot_option().get_value()};
const auto & detected_releasever = libdnf5::Vars::detect_release(base->get_weak_ptr(), installroot);
if (detected_releasever != nullptr) {
if (target_releasever == *detected_releasever) {
throw sdbus::Error(
dnfdaemon::ERROR, fmt::format("Need a 'releasever' different than the current system version."));
}
}

// get the upgrade mode
std::string upgrade_mode = dnfdaemon::key_value_map_get<std::string>(options, "mode", "distrosync");

// fill the goal
auto & goal = session.get_goal();
libdnf5::GoalJobSettings settings;
if (upgrade_mode == "upgrade") {
goal.add_rpm_upgrade(settings);
} else if (upgrade_mode == "distrosync") {
goal.add_rpm_distro_sync(settings);
} else {
throw sdbus::Error(
dnfdaemon::ERROR,
fmt::format(
"Unsupported system-upgrade mode \"{}\". Only \"distrosync\" and \"upgrade\" modes are supported.",
upgrade_mode));
}

auto reply = call.createReply();
return reply;
}
1 change: 1 addition & 0 deletions dnf5daemon-server/services/rpm/rpm.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ class Rpm : public IDbusSessionService {
sdbus::MethodReply distro_sync(sdbus::MethodCall & call);
sdbus::MethodReply downgrade(sdbus::MethodCall & call);
sdbus::MethodReply reinstall(sdbus::MethodCall & call);
sdbus::MethodReply system_upgrade(sdbus::MethodCall & call);

void list_fd(sdbus::MethodCall & call, const std::string & transfer_id);
};
Expand Down
18 changes: 18 additions & 0 deletions doc/dnf_daemon/examples/print_upgrades_with_severities.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,21 @@
#!/usr/bin/python3
# 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 <https://www.gnu.org/licenses/>.

import dbus
import os

Expand Down
69 changes: 69 additions & 0 deletions doc/dnf_daemon/examples/system_upgrade.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
#!/usr/bin/python3
# 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 <https://www.gnu.org/licenses/>.

"""
This is an example how to perform a system-upgrade with dnf5daemon-server.
"""

import dbus

DNFDAEMON_BUS_NAME = 'org.rpm.dnf.v0'
DNFDAEMON_OBJECT_PATH = '/' + DNFDAEMON_BUS_NAME.replace('.', '/')

IFACE_SESSION_MANAGER = '{}.SessionManager'.format(DNFDAEMON_BUS_NAME)
IFACE_RPM = '{}.rpm.Rpm'.format(DNFDAEMON_BUS_NAME)
IFACE_GOAL = '{}.Goal'.format(DNFDAEMON_BUS_NAME)


bus = dbus.SystemBus()
iface_session = dbus.Interface(
bus.get_object(DNFDAEMON_BUS_NAME, DNFDAEMON_OBJECT_PATH),
dbus_interface=IFACE_SESSION_MANAGER)

# set the releasever to the new distribution release
session = iface_session.open_session(
dbus.Dictionary({"releasever": "40"}, signature=dbus.Signature('sv')))

iface_rpm = dbus.Interface(
bus.get_object(DNFDAEMON_BUS_NAME, session),
dbus_interface=IFACE_RPM)
iface_goal = dbus.Interface(
bus.get_object(DNFDAEMON_BUS_NAME, session),
dbus_interface=IFACE_GOAL)

# Add system upgrade to the transaction
options = {
"mode": "distrosync",
}
iface_rpm.system_upgrade(options)

# resolve the transaction
resolved, result = iface_goal.resolve({})

# now you can print the transaction table and ask the user for confirmation
print("Resolved.")

if result == 0:
# execute the transaction offline (durint the next reboot)
iface_goal.do_transaction({"offline": True}, timeout=2000)
print("Reboot to continue with system upgrade...")
else:
errors = iface_goal.get_transaction_problems_string()
print("Errors while resolving the transaction:")
for error in errors:
print(error)
Loading