From 626d021edb2de85a538c3966f48154f6c2ac240c Mon Sep 17 00:00:00 2001 From: Kelvin Fan Date: Thu, 11 Feb 2021 17:42:43 -0500 Subject: [PATCH] app/upgrade: Do not upgrade if updates driver registered Do not perform an upgrade if detected that an updates driver has been registered. Add --bypass-driver option to force an upgrade regardless of whether an updates driver has been registered. --- src/app/rpmostree-builtin-upgrade.cxx | 89 ++++++++++++++++++++++++++- tests/vmcheck/test-misc-2.sh | 47 +++++++------- 2 files changed, 113 insertions(+), 23 deletions(-) diff --git a/src/app/rpmostree-builtin-upgrade.cxx b/src/app/rpmostree-builtin-upgrade.cxx index 62de5ba2..d4a876e6 100644 --- a/src/app/rpmostree-builtin-upgrade.cxx +++ b/src/app/rpmostree-builtin-upgrade.cxx @@ -28,6 +28,7 @@ #include "rpmostree-libbuiltin.h" #include "rpmostree-rpm-util.h" #include "rpmostree-dbus-helpers.h" +#include "rpmostreed-transaction-types.h" #include @@ -42,6 +43,7 @@ static gboolean opt_cache_only; static gboolean opt_download_only; static char *opt_automatic; static gboolean opt_lock_finalization; +static gboolean opt_bypass_driver; /* "check-diff" is deprecated, replaced by "preview" */ static GOptionEntry option_entries[] = { @@ -58,9 +60,41 @@ static GOptionEntry option_entries[] = { { "unchanged-exit-77", 0, 0, G_OPTION_ARG_NONE, &opt_unchanged_exit_77, "If no new deployment made, exit 77", NULL }, { "trigger-automatic-update-policy", 0, G_OPTION_FLAG_HIDDEN, G_OPTION_ARG_NONE, &opt_automatic, "For automated use only; triggered by automatic timer", NULL }, { "lock-finalization", 0, G_OPTION_FLAG_HIDDEN, G_OPTION_ARG_NONE, &opt_lock_finalization, "Prevent automatic deployment finalization on shutdown", NULL }, + { "bypass-driver", 0, 0, G_OPTION_ARG_NONE, &opt_bypass_driver, "Force an upgrade even if an updates driver is registered.", NULL}, { NULL } }; +/* Get the `Documentation` property `update_driver_sd_unit`. Documentation is returned + * as a string array GVariant in `unit_doc_array`. */ +static gboolean +get_update_driver_doc (GDBusConnection *connection, + const char *update_driver_sd_unit, + GVariant **unit_doc_array, + GCancellable *cancellable, + GError **error) +{ + const char *update_driver_objpath = NULL; + if (!get_sd_unit_objpath (connection, update_driver_sd_unit, &update_driver_objpath, + cancellable, error)) + return FALSE; + + /* Look up `Documentation` property of update driver's systemd unit. */ + g_autoptr(GDBusProxy) update_driver_unit_obj_proxy = + g_dbus_proxy_new_sync (connection, G_DBUS_PROXY_FLAGS_DO_NOT_CONNECT_SIGNALS, NULL, + "org.freedesktop.systemd1", update_driver_objpath, + "org.freedesktop.systemd1.Unit", cancellable, error); + if (!update_driver_unit_obj_proxy) + return FALSE; + + *unit_doc_array = + g_dbus_proxy_get_cached_property (update_driver_unit_obj_proxy, "Documentation"); + if (!*unit_doc_array) + return glnx_throw (error, "Documentation property not found in proxy's cache (%s)", + update_driver_objpath); + + return TRUE; +} + gboolean rpmostree_builtin_upgrade (int argc, char **argv, @@ -76,6 +110,7 @@ rpmostree_builtin_upgrade (int argc, const char *const *install_pkgs = NULL; const char *const *uninstall_pkgs = NULL; + GBusType bus_type; if (!rpmostree_option_context_parse (context, option_entries, &argc, &argv, @@ -84,7 +119,7 @@ rpmostree_builtin_upgrade (int argc, &install_pkgs, &uninstall_pkgs, &sysroot_proxy, - &peer_pid, NULL, + &peer_pid, &bus_type, error)) return FALSE; @@ -129,6 +164,58 @@ rpmostree_builtin_upgrade (int argc, g_print ("note: automatic updates (%s) are enabled\n", policy); } + if (!opt_bypass_driver) + { + g_autofree char *update_driver_sd_unit = NULL; + g_autofree char *update_driver_name = NULL; + if (!get_driver_info (&update_driver_name, &update_driver_sd_unit, error)) + return FALSE; + + /* Notify user that an updates driver is registered and upgrades should be + * done through the driver. */ + if (update_driver_sd_unit && update_driver_name) + { + g_autoptr(GString) error_msg = g_string_new(NULL); + g_string_printf (error_msg, "Updates are driven by %s (%s)\n", + update_driver_name, update_driver_sd_unit); + /* only try to get unit's `Documentation` if we're on the system bus */ + if (bus_type == G_BUS_TYPE_SYSTEM) + { + g_autoptr(GVariant) update_driver_docs_array = NULL; + g_autoptr(GError) local_error = NULL; + GDBusConnection *connection = + g_dbus_proxy_get_connection (G_DBUS_PROXY (sysroot_proxy)); + if (!get_update_driver_doc (connection, update_driver_sd_unit, &update_driver_docs_array, + cancellable, &local_error)) + { + g_printerr ("%s", local_error->message); + } + else if (update_driver_docs_array) + { + g_string_append_printf (error_msg, "See %s's documentation", update_driver_name); + gsize docs_len; + g_autofree const char **update_driver_docs = + g_variant_get_strv (update_driver_docs_array, &docs_len); + if (docs_len > 0) + { + g_string_append_printf (error_msg, " at "); + for (guint i = 0; i < docs_len; i++) + g_string_append_printf (error_msg, "%s%s", update_driver_docs[i], + i < docs_len - 1 ? ", " : "\n"); + } + else + { + g_string_append_printf (error_msg, "\n"); + } + } + } + + g_string_append_printf (error_msg, "Use --bypass-driver to bypass %s and force an upgrade anyway", + update_driver_name); + return glnx_throw (error, "%s", error_msg->str); + } + } + g_autoptr(GVariant) previous_deployment = rpmostree_os_dup_default_deployment (os_proxy); const gboolean check_or_preview = (opt_check || opt_preview); diff --git a/tests/vmcheck/test-misc-2.sh b/tests/vmcheck/test-misc-2.sh index 46da3771..ad54a2a0 100755 --- a/tests/vmcheck/test-misc-2.sh +++ b/tests/vmcheck/test-misc-2.sh @@ -44,12 +44,11 @@ assert_streq "$(vm_get_booted_csum)" "${booted_csum}" vm_assert_journal_has_content $cursor 'Not finalizing; found /run/ostree/staged-deployment-locked' echo "ok locked rebase staging" -# This also now tests custom client IDs in the journal and the `deploy --register-driver` option. +# This also now tests custom client IDs in the journal. cursor=$(vm_get_journal_cursor) vm_cmd env RPMOSTREE_CLIENT_ID=testing-agent-id \ rpm-ostree deploy revision="${commit}" \ - --lock-finalization --register-driver TestDriver -vm_cmd test -f /run/rpm-ostree/update-driver.gv + --lock-finalization vm_cmd test -f /run/ostree/staged-deployment-locked if vm_rpmostree finalize-deployment; then assert_not_reached "finalized without expected checksum" @@ -60,30 +59,11 @@ vm_cmd journalctl --after-cursor "'$cursor'" -u rpm-ostreed -o json | jq -r '.AG assert_file_has_content agent.txt testing-agent-id vm_cmd journalctl --after-cursor "'$cursor'" -u rpm-ostreed -o json | jq -r '.AGENT_SD_UNIT//""' > agent_sd_unit.txt assert_file_has_content agent_sd_unit.txt sshd.service -vm_cmd rpm-ostree status > status.txt -assert_file_has_content status.txt 'AutomaticUpdatesDriver: TestDriver' -vm_cmd rpm-ostree status -v > verbose_status.txt -assert_file_has_content verbose_status.txt 'AutomaticUpdatesDriver: TestDriver (sshd.service)' -assert_file_has_content verbose_status.txt ' DriverState: active' -vm_assert_status_jq ".\"update-driver\"[\"driver-name\"] == \"TestDriver\"" \ - ".\"update-driver\"[\"driver-sd-unit\"] == \"sshd.service\"" vm_reboot_cmd rpm-ostree finalize-deployment "${commit}" assert_streq "$(vm_get_booted_csum)" "${commit}" vm_assert_journal_has_content $cursor "Finalized deployment; rebooting into ${commit}" echo "ok finalize-deployment" -# Test `deploy --register-driver` option with empty string as revision. -vm_cmd rpm-ostree deploy \'\' \ - --register-driver=OtherTestDriver -vm_cmd test -f /run/rpm-ostree/update-driver.gv -vm_cmd rpm-ostree status > status.txt -assert_file_has_content status.txt 'AutomaticUpdatesDriver: OtherTestDriver' -vm_cmd rpm-ostree status -v > verbose_status.txt -assert_file_has_content verbose_status.txt 'AutomaticUpdatesDriver: OtherTestDriver (sshd.service)' -vm_assert_status_jq ".\"update-driver\"[\"driver-name\"] == \"OtherTestDriver\"" \ - ".\"update-driver\"[\"driver-sd-unit\"] == \"sshd.service\"" -echo "ok deploy --register-driver with empty string revision" - # Custom origin and local repo rebases. This is essentially the RHCOS workflow. # https://github.com/projectatomic/rpm-ostree/pull/1406 # https://github.com/projectatomic/rpm-ostree/pull/1732 @@ -308,3 +288,26 @@ vm_rpmostree status > status.txt assert_file_has_content status.txt "failed to finalize previous deployment" assert_file_has_content status.txt "error: opendir" echo "ok previous staged failure in status" + +# Test `deploy --register-driver` option. +vm_cmd rpm-ostree deploy \'\' \ + --register-driver=TestDriver +vm_cmd test -f /run/rpm-ostree/update-driver.gv +vm_cmd rpm-ostree status > status.txt +assert_file_has_content status.txt 'AutomaticUpdatesDriver: TestDriver' +vm_cmd rpm-ostree status -v > verbose_status.txt +assert_file_has_content verbose_status.txt 'AutomaticUpdatesDriver: TestDriver (sshd.service)' +vm_assert_status_jq ".\"update-driver\"[\"driver-name\"] == \"TestDriver\"" \ + ".\"update-driver\"[\"driver-sd-unit\"] == \"sshd.service\"" +echo "ok deploy --register-driver with empty string revision" + +# Ensure that we are prevented from upgrading when an updates driver is registered +if vm_rpmostree upgrade 2>err.txt; then + assert_not_reached "Upgrade with updates driver registered unexpected succeeded" +fi +assert_file_has_content err.txt 'Updates are driven by TestDriver' +# Bypass updates driver to force an upgrade +vm_rpmostree upgrade --bypass-driver 2>err.txt +assert_not_file_has_content err.txt 'Updates are driven by TestDriver' +vm_rpmostree cleanup -p +echo "ok upgrade when updates driver is registered"