Commit 95afc5ed authored by Mariam Fahmy's avatar Mariam Fahmy Committed by Aleix Pol Gonzalez
Browse files

Add support for rpm-ostree

This allows to upgrade systems based on rpm-ostree like Fedora Kinoite
or Fedora Silverblue.
parent ebd45f23
......@@ -45,4 +45,9 @@ if(BUILD_FwupdBackend AND TARGET PkgConfig::Fwupd)
add_subdirectory(FwupdBackend)
endif()
option(BUILD_RpmOstreeBackend "Build RpmOstree support." "ON")
if(BUILD_RpmOstreeBackend)
add_subdirectory(RpmOstreeBackend)
endif()
set(CMAKE_MODULE_PATH ${ECM_MODULE_DIR})
set_source_files_properties(org.projectatomic.rpmostree1.xml PROPERTIES
INCLUDE dbustypes.h
)
set(rpmostree1_xml ${CMAKE_INSTALL_PREFIX}/${DBUS_INTERFACES_INSTALL_DIR}/org.projectatomic.rpmostree1.xml)
qt5_add_dbus_interface(ostree1_SRCS org.projectatomic.rpmostree1.xml rpmostree1)
add_library(rpmostree-backend MODULE RpmOstreeResource.cpp RpmOstreeBackend.cpp RpmOstreeTransaction.cpp RpmOstree.qrc ${ostree1_SRCS})
target_link_libraries(rpmostree-backend PRIVATE Discover::Common Qt5::DBus KF5::CoreAddons KF5::I18n)
install(TARGETS rpmostree-backend DESTINATION ${PLUGIN_INSTALL_DIR}/discover)
add_library(RpmOstreeNotifier MODULE RpmOstreeNotifier.cpp)
target_link_libraries(RpmOstreeNotifier Discover::Notifiers Qt5::Concurrent PkgConfig::Flatpak)
set_target_properties(RpmOstreeNotifier PROPERTIES INSTALL_RPATH ${CMAKE_INSTALL_FULL_LIBDIR}/plasma-discover)
install(TARGETS RpmOstreeNotifier DESTINATION ${PLUGIN_INSTALL_DIR}/discover-notifier)
<!DOCTYPE RCC>
<RCC version="1.0">
<qresource>
<file>qml/RemoteRefsButton.qml</file>
</qresource>
</RCC>
/*
* SPDX-FileCopyrightText: 2020 Aleix Pol Gonzalez <aleixpol@kde.org>
* SPDX-FileCopyrightText: 2021 Mariam Fahmy Sobhy <mariamfahmy66@gmail.com>
*
* SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
*/
#include "RpmOstreeBackend.h"
#include "RpmOstreeResource.h"
#include "RpmOstreeTransaction.h"
#include <QDBusInterface>
#include <QDBusMessage>
#include <QDBusObjectPath>
#include <QDBusPendingReply>
#include <QDebug>
#include <QList>
#include <QMap>
#include <QStringList>
#include <QVariant>
#include <QVariantList>
#include <QFile>
#include <Transaction/Transaction.h>
#include <resources/SourcesModel.h>
#include <resources/StandardBackendUpdater.h>
#include <QStandardItemModel>
DISCOVER_BACKEND_PLUGIN(RpmOstreeBackend)
class RpmOstreeSourcesBackend : public AbstractSourcesBackend
{
public:
explicit RpmOstreeSourcesBackend(AbstractResourcesBackend *parent)
: AbstractSourcesBackend(parent)
, m_model(new QStandardItemModel(this))
{
auto it = new QStandardItem(QStringLiteral("rpm-ostree"));
it->setData(QStringLiteral("rpm-ostree"), IdRole);
m_model->appendRow(it);
}
QAbstractItemModel *sources() override
{
return m_model;
}
bool addSource(const QString &) override
{
return false;
}
bool removeSource(const QString &) override
{
return false;
}
QString idDescription() override
{
return QStringLiteral("rpm-ostree");
}
QVariantList actions() const override
{
return {};
}
bool supportsAdding() const override
{
return false;
}
bool canMoveSources() const override
{
return false;
}
private:
QStandardItemModel *const m_model;
};
RpmOstreeBackend::RpmOstreeBackend(QObject *parent)
: AbstractResourcesBackend(parent)
, isDeploymentUpdate(true)
, m_fetching(true)
, m_reviews(AppStreamIntegration::global()->reviews())
, m_updater(new StandardBackendUpdater(this))
{
connect(m_updater, &StandardBackendUpdater::updatesCountChanged, this, &RpmOstreeBackend::updatesCountChanged);
getDeployments();
SourcesModel::global()->addSourcesBackend(new RpmOstreeSourcesBackend(this));
executeCheckUpdateProcess();
executeRemoteRefsProcess();
}
void RpmOstreeBackend::getDeployments()
{
// reading Deployments property from "org.projectatomic.rpmostree1.Sysroot" interface
QDBusInterface interface(QStringLiteral("org.projectatomic.rpmostree1"),
QStringLiteral("/org/projectatomic/rpmostree1/Sysroot"),
QStringLiteral("org.freedesktop.DBus.Properties"),
QDBusConnection::systemBus());
QDBusMessage result = interface.call(QStringLiteral("Get"), QStringLiteral("org.projectatomic.rpmostree1.Sysroot"), QStringLiteral("Deployments"));
QList<QVariant> outArgs = result.arguments();
QVariant first = outArgs.at(0);
QDBusVariant dbvFirst = first.value<QDBusVariant>();
QVariant vFirst = dbvFirst.variant();
QDBusArgument dbusArgs = vFirst.value<QDBusArgument>();
// storing the extracted deployments from DBus
dbusArgs.beginArray();
while (!dbusArgs.atEnd()) {
QMap<QString, QVariant> map;
dbusArgs >> map;
QDBusArgument dbusArgsSignature = map[QStringLiteral("signatures")].value<QDBusArgument>();
dbusArgsSignature >> map[QStringLiteral("signatures")];
QDBusVariant dbvFirst1 = map[QStringLiteral("signatures")].value<QDBusVariant>();
QVariant vFirst1 = dbvFirst1.variant();
QDBusArgument dbusArgsSign = vFirst1.value<QDBusArgument>();
dbusArgsSign >> map[QStringLiteral("signatures")];
QString baseVersion = map[QStringLiteral("version")].toString();
QString deploymentName = baseVersion;
deploymentName.remove(8, deploymentName.size() - 1);
baseVersion.remove(0, 7);
// creating a deployment struct
DeploymentInformation extractDeployment;
extractDeployment.name = deploymentName;
extractDeployment.baseVersion = baseVersion;
extractDeployment.booted = map[QStringLiteral("booted")].toBool();
extractDeployment.baseChecksum = map[QStringLiteral("checksum")].toString();
extractDeployment.layeredPackages = map[QStringLiteral("packages")].toString();
extractDeployment.localPackages = map[QStringLiteral("requested-local-packages")].toString();
extractDeployment.signature = map[QStringLiteral("signatures")].toString();
extractDeployment.origin = map[QStringLiteral("origin")].toString();
extractDeployment.timestamp = map[QStringLiteral("timestamp")].toULongLong();
deploymentsList.push_back(extractDeployment);
}
dbusArgs.endArray();
// create a resource for each deployment
for (const DeploymentInformation &deployment : deploymentsList) {
QString name = deployment.name;
QString baseVersion = deployment.baseVersion;
QString baseChecksum = deployment.baseChecksum;
QString signature = deployment.signature;
QString layeredPackages = deployment.layeredPackages;
QString localPackages = deployment.localPackages;
QString origin = deployment.origin;
qulonglong timestamp = deployment.timestamp;
bool booted = deployment.booted;
RpmOstreeResource *deploymentResource = new RpmOstreeResource(name, baseVersion, baseChecksum, signature, layeredPackages, localPackages, origin, timestamp, this);
// changing the state of the booted deployment resource to Installed.
if (booted) {
deploymentResource->setState(AbstractResource::Installed);
}
m_resources.push_back(deploymentResource);
}
}
void RpmOstreeBackend::toggleFetching()
{
m_fetching = !m_fetching;
checkForUpdatesNeeded();
emit fetchingChanged();
}
void RpmOstreeBackend::checkForUpdatesNeeded()
{
if (!m_newVersion.isEmpty()) {
m_newVersion.remove(0, 25);
m_newVersion.remove(13, m_newVersion.size() - 13);
m_resources[0]->setNewVersion(m_newVersion);
m_resources[0]->setState(AbstractResource::Upgradeable);
connect(m_resources[0], &RpmOstreeResource::stateChanged, this, &RpmOstreeBackend::updatesCountChanged);
}
}
void RpmOstreeBackend::executeCheckUpdateProcess()
{
QProcess *process = new QProcess();
connect(process, &QProcess::readyReadStandardError, [process]() {
QByteArray readError = process->readAllStandardError();
});
// catch data output
connect(process, &QProcess::readyReadStandardOutput, this, [process, this]() {
QByteArray readOutput = process->readAllStandardOutput();
this->getQProcessUpdateOutput(readOutput);
});
// delete process instance when done, and get the exit status to handle errors.
connect(process, QOverload<int, QProcess::ExitStatus>::of(&QProcess::finished), [=](int exitCode, QProcess::ExitStatus exitStatus) {
qWarning() << "process exited with code " << exitCode;
toggleFetching();
process->deleteLater();
});
process->setProcessChannelMode(QProcess::MergedChannels);
process->start(QStringLiteral("rpm-ostree update --check"));
}
void RpmOstreeBackend::getQProcessUpdateOutput(QByteArray readOutput)
{
QList<QByteArray> checkUpdateOutput = readOutput.split('\n');
for (const QByteArray &output : checkUpdateOutput) {
if (output.contains(QByteArray("Version"))) {
m_newVersion = QString::fromLocal8Bit(output);
}
}
}
void RpmOstreeBackend::executeRemoteRefsProcess()
{
if (!m_remoteRefsList.isEmpty())
m_remoteRefsList.clear();
QProcess *process = new QProcess(this);
connect(process, &QProcess::readyReadStandardError, [process]() {
QByteArray readError = process->readAllStandardError();
});
// catch data output
connect(process, &QProcess::readyReadStandardOutput, this, [process, this]() {
QByteArray readOutput = process->readAllStandardOutput();
this->getQProcessRefsOutput(readOutput);
});
// delete process instance when done, and get the exit status to handle errors.
QObject::connect(process, QOverload<int, QProcess::ExitStatus>::of(&QProcess::finished), [=](int exitCode, QProcess::ExitStatus exitStatus) {
qWarning() << "process exited with code " << exitCode;
settingRemoteRefsDeploymentResource();
process->deleteLater();
});
process->setProcessChannelMode(QProcess::MergedChannels);
process->start(QStringLiteral("ostree --repo=/ostree/repo remote refs kinoite"));
}
void RpmOstreeBackend::getQProcessRefsOutput(QByteArray readOutput)
{
QList<QByteArray> remoteRefsList;
remoteRefsList = readOutput.split('\n');
QString kinoite = QStringLiteral("kinoite");
for (const QByteArray &refs : remoteRefsList) {
QString refsToString = QString::fromLocal8Bit(refs);
if (refsToString.count(kinoite) <= 1)
continue;
m_remoteRefsList.push_back(refsToString);
}
}
void RpmOstreeBackend::settingRemoteRefsDeploymentResource()
{
m_resources[0]->setRemoteRefsList(m_remoteRefsList);
}
int RpmOstreeBackend::updatesCount() const
{
return m_updater->updatesCount();
}
bool RpmOstreeBackend::isValid() const
{
return QFile::exists(QStringLiteral("/run/ostree-booted"));
}
ResultsStream *RpmOstreeBackend::search(const AbstractResourcesBackend::Filters &filter)
{
QVector<AbstractResource *> res;
for (AbstractResource *r : m_resources) {
if (r->state() >= filter.state)
res.push_back(r);
}
return new ResultsStream(QStringLiteral("rpm-ostree"), res);
}
Transaction *RpmOstreeBackend::installApplication(AbstractResource *app, const AddonList &addons)
{
updateCurrentDeployment();
return new RpmOstreeTransaction(qobject_cast<RpmOstreeResource *>(app), addons, Transaction::InstallRole, transactionUpdatePath, true);
}
Transaction *RpmOstreeBackend::installApplication(AbstractResource *app)
{
bool deploymentUpdate = isDeploymentUpdate;
if (isDeploymentUpdate) {
updateCurrentDeployment();
} else {
isDeploymentUpdate = true;
}
return new RpmOstreeTransaction(qobject_cast<RpmOstreeResource *>(app), Transaction::InstallRole, transactionUpdatePath, deploymentUpdate);
}
Transaction *RpmOstreeBackend::removeApplication(AbstractResource *)
{
return nullptr;
}
void RpmOstreeBackend::updateCurrentDeployment()
{
OrgProjectatomicRpmostree1OSInterface interface (QStringLiteral("org.projectatomic.rpmostree1"),
QStringLiteral("/org/projectatomic/rpmostree1/fedora"),
QDBusConnection::systemBus(),
this);
QVariantMap options;
QVariantMap modifiers;
QString name;
QDBusPendingReply<QString> reply = interface.UpdateDeployment(modifiers, options);
reply.waitForFinished();
if (!reply.isError()) {
transactionUpdatePath = reply.argumentAt(0).value<QString>();
} else {
qDebug() << "Error occurs when performing the UpdateDeployment: " << reply.error();
}
}
void RpmOstreeBackend::checkForUpdates()
{
if (m_fetching)
return;
executeCheckUpdateProcess();
executeRemoteRefsProcess();
QTimer::singleShot(500, this, &RpmOstreeBackend::toggleFetching);
}
void RpmOstreeBackend::perfromSystemUpgrade(QString selectedRefs)
{
OrgProjectatomicRpmostree1OSInterface interface (QStringLiteral("org.projectatomic.rpmostree1"),
QStringLiteral("/org/projectatomic/rpmostree1/fedora"),
QDBusConnection::systemBus(),
this);
isDeploymentUpdate = false;
QVariantMap options;
QStringList packages;
QDBusPendingReply<QString> reply = interface.Rebase(options, selectedRefs, packages);
reply.waitForFinished();
if (!reply.isError()) {
transactionUpdatePath = reply.argumentAt(0).value<QString>();
installApplication(m_resources[0]);
} else {
qDebug() << "Error occurs when performing the Rebase: " << reply.error();
}
}
AbstractBackendUpdater *RpmOstreeBackend::backendUpdater() const
{
return m_updater;
}
AbstractReviewsBackend *RpmOstreeBackend::reviewsBackend() const
{
return m_reviews.data();
}
QString RpmOstreeBackend::displayName() const
{
return QStringLiteral("rpm-ostree");
}
bool RpmOstreeBackend::hasApplications() const
{
return true;
}
#include "RpmOstreeBackend.moc"
/*
* SPDX-FileCopyrightText: 2020 Aleix Pol Gonzalez <aleixpol@kde.org>
* SPDX-FileCopyrightText: 2021 Mariam Fahmy Sobhy <mariamfahmy66@gmail.com>
*
* SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
*/
#ifndef RPMOSTREE1BACKEND_H
#define RPMOSTREE1BACKEND_H
#include <resources/AbstractResourcesBackend.h>
#include "rpmostree1.h"
#include <QMetaType>
#include <QProcess>
#include <QSharedPointer>
#include <QThreadPool>
#include <QtDBus/QDBusPendingReply>
#include <AppStreamQt/component.h>
#include <appstream/AppStreamIntegration.h>
#include <appstream/OdrsReviewsBackend.h>
class RpmOstreeReviewsBackend;
class OdrsReviewsBackend;
class RpmOstreeResource;
class StandardBackendUpdater;
class RpmOstreeBackend : public AbstractResourcesBackend
{
Q_OBJECT
public:
explicit RpmOstreeBackend(QObject *parent = nullptr);
/*
* Getting the list of deployments from the rpm-ostree DBus class and
* converting each deployment to resource and detecting the currently running deployment.
* It is corresponding to "rpm-ostree status".
*/
void getDeployments();
/*
* Executing "rpm-ostree update --check" using QProcess to check if
* there is a new deployment version avaliable.
*/
void executeCheckUpdateProcess();
/*
* Getting the output resulting from executing the QProcess update check.
*/
void getQProcessUpdateOutput(QByteArray readOutput);
/*
* Calling UpdateDeployment method from the rpm-ostree DBus class when
* there is a new deployment version avaliable .
*/
void updateCurrentDeployment();
/*
* Executing "ostree remote refs kinoite" using QProcess to get
* a list of the avaliable remote refs list.
*/
void executeRemoteRefsProcess();
/*
* Getting the output resulting from executing the QProcess remote refs list.
*/
void getQProcessRefsOutput(QByteArray readOutput);
/*
* Setting the current remote refs list to the current running deployment resource.
*/
void settingRemoteRefsDeploymentResource();
/*
* Calling Rebase method from the rpm-ostree DBus class when
* there is a new kinoite refs.
*/
void perfromSystemUpgrade(QString);
int updatesCount() const override;
AbstractBackendUpdater *backendUpdater() const override;
AbstractReviewsBackend *reviewsBackend() const override;
ResultsStream *search(const AbstractResourcesBackend::Filters &search) override;
// return true when OSTreeRPMBackend is ready to be loaded
bool isValid() const override;
Transaction *installApplication(AbstractResource *) override;
Transaction *installApplication(AbstractResource *, const AddonList &) override;
Transaction *removeApplication(AbstractResource *) override;
bool isFetching() const override
{
return m_fetching;
}
void checkForUpdates() override;
QString displayName() const override;
bool hasApplications() const override;
public Q_SLOTS:
void toggleFetching();
private:
struct DeploymentInformation {
QString name;
bool booted;
QString baseVersion;
QString baseChecksum;
QString layeredPackages;
QString localPackages;
QString signature;
QString origin;
qulonglong timestamp;
};
QSharedPointer<OdrsReviewsBackend> m_reviews;
StandardBackendUpdater *m_updater;
QVector<RpmOstreeResource *> m_resources;
QVector<DeploymentInformation> deploymentsList;
QString transactionUpdatePath;
QStringList m_remoteRefsList;
bool m_fetching;
QString m_newVersion;
/*
* Checking if the required update is deployment update or system upgrade
* by default isDeploymentUpdate is true
*/
bool isDeploymentUpdate;
/*
* Extracting the new avaliable version of the deployment from the output
* resulting from QProcess update deployment and setting the current running
* deployment resource to AbstractResource::Upgradeable
*/
void checkForUpdatesNeeded();
};
#endif
\ No newline at end of file
/*
* SPDX-FileCopyrightText: 2021 Mariam Fahmy Sobhy <mariamfahmy66@gmail.com>
*
* SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
*/
#include "RpmOstreeNotifier.h"
#include <QDebug>
#include <QFileSystemWatcher>
#include <QProcess>
#include <QTimer>
RpmOstreeNotifier::RpmOstreeNotifier(QObject *parent)
: BackendNotifierModule(parent)
, m_newUpdate(false)
, m_needsReboot(false)
{
QFileSystemWatcher *watcher = new QFileSystemWatcher(this);
watcher->addPath(QStringLiteral("/ostree/deploy/fedora/deploy/"));
connect(watcher, &QFileSystemWatcher::fileChanged, this, &RpmOstreeNotifier::recheckSystemUpdateNeeded);
QFileSystemWatcher *fileWatcher = new QFileSystemWatcher(this);
fileWatcher->addPath(QStringLiteral("/tmp/discover-ostree-changed"));
connect(fileWatcher, &QFileSystemWatcher::fileChanged, this, &RpmOstreeNotifier::nowNeedsReboot);
}
RpmOstreeNotifier::~RpmOstreeNotifier()
{
}
void RpmOstreeNotifier::recheckSystemUpdateNeeded()
{
QProcess *process = new QProcess();
connect(process, &QProcess::readyReadStandardError, [process]() {
QByteArray readError = process->