Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
1751f46
updater: non-interactive updater
piotrbartman May 23, 2024
819fdd9
updater: unify cli options with cli updater
piotrbartman May 27, 2024
d7c282e
updater: return 100 if no vm was updated
piotrbartman May 27, 2024
32508dd
updater: update tests
piotrbartman May 27, 2024
22e6599
updater: show success dialog in non-interactive mode
piotrbartman May 28, 2024
47bea95
updater: show success dialog if nothing to do
piotrbartman May 28, 2024
4c824ec
updater: fix selection if the intro page is skipped
piotrbartman May 28, 2024
32bf290
updater: unify cli args with backend updater
piotrbartman Jun 10, 2024
a677000
updater: get default value of '--update-if-stale' from dom0 settings
piotrbartman Jun 11, 2024
644299a
updater: return 130 if user cancels updating
piotrbartman Jun 11, 2024
462ce53
updater: make test reproducible
piotrbartman Jun 11, 2024
f92e565
updater: set progress as 100 if update fails or was cancelled
piotrbartman Jun 11, 2024
9f59b61
updater: apply the same selection criteria to dom0
piotrbartman Jun 11, 2024
91bd5c4
updater: make pylint happy
piotrbartman Jun 11, 2024
ceec6a0
updater: updaate dom0 tests
piotrbartman Jun 11, 2024
713df49
updater: show only one confirmation to the user
piotrbartman Jun 14, 2024
73174dd
updater: fix force updating
piotrbartman Jun 17, 2024
1711106
updater: make pylint happy
piotrbartman Jun 17, 2024
5abe625
updater: test is_stale(dom0)
piotrbartman Jun 21, 2024
ff9900e
updater: test apply and update flags
piotrbartman Jun 21, 2024
15dc1f1
updater: add --signal-no-updates flag
piotrbartman Jun 26, 2024
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
9 changes: 9 additions & 0 deletions locale/en/LC_MESSAGES/desktop-linux-manager.po
Original file line number Diff line number Diff line change
Expand Up @@ -548,3 +548,12 @@ msgstr ""

msgid "failed to update."
msgstr ""

msgid "Qubes OS is up to date."
msgstr ""

msgid "All selected qubes have been updated."
msgstr ""

msgid "There are no updates available for the selected Qubes."
msgstr ""
55 changes: 41 additions & 14 deletions qui/updater/intro_page.py
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,7 @@ def refresh_update_list(self, update_if_stale):
if not self.active:
return

cmd = ['qubes-vm-update', '--dry-run',
cmd = ['qubes-vm-update', '--quiet', '--dry-run',
'--update-if-stale', str(update_if_stale)]

to_update = self._get_stale_qubes(cmd)
Expand Down Expand Up @@ -175,12 +175,14 @@ def select_rows(self):
in self.head_checkbox.allowed

def select_rows_ignoring_conditions(self, cliargs, dom0):
cmd = ['qubes-vm-update', '--dry-run']
cmd = ['qubes-vm-update', '--dry-run', '--quiet']

args = [a for a in dir(cliargs) if not a.startswith("_")]
for arg in args:
if arg in ("dom0", "no-restart", "restart", "max_concurrency",
"log"):
if arg in ("non_interactive", "non_default_select",
"dom0",
"restart", "apply_to_sys", "apply_to_all", "no_apply",
"max_concurrency", "log"):
continue
value = getattr(cliargs, arg)
if value:
Expand All @@ -189,13 +191,16 @@ def select_rows_ignoring_conditions(self, cliargs, dom0):
vms_without_dom0 = vms.difference({"dom0"})
if not vms_without_dom0:
continue
value = ",".join(vms_without_dom0)
value = ",".join(sorted(vms_without_dom0))
cmd.append(f"--{arg.replace('_', '-')}")
if isinstance(value, str):
cmd.append(value)
if not isinstance(value, bool):
cmd.append(str(value))

to_update = set()
if cmd[2:]:
non_default_select = [
'--' + arg for arg in cliargs.non_default_select if arg != 'dom0']
non_default = [a for a in cmd if a in non_default_select]
if non_default or cliargs.non_interactive:
to_update = self._get_stale_qubes(cmd)

to_update = self._handle_cli_dom0(dom0, to_update, cliargs)
Expand Down Expand Up @@ -224,19 +229,41 @@ def _get_stale_qubes(self, cmd):

@staticmethod
def _handle_cli_dom0(dom0, to_update, cliargs):
if not cliargs.targets and not cliargs.all:
if bool(dom0.features.get(
'updates-available', False)):
to_update.add('dom0')
if cliargs.dom0 or cliargs.all:
to_update.add("dom0")
default_select = not any(
(getattr(cliargs, arg)
for arg in cliargs.non_default_select if arg != 'all'))
if ((
default_select and cliargs.non_interactive
or cliargs.all
or cliargs.dom0
) and (
cliargs.force_update
or bool(dom0.features.get('updates-available', False))
or is_stale(dom0, cliargs.update_if_stale)
)):
to_update.add('dom0')

if cliargs.targets and "dom0" in cliargs.targets.split(","):
to_update.add("dom0")
if cliargs.skip and "dom0" in cliargs.skip.split(","):
to_update = to_update.difference({"dom0"})
return to_update


def is_stale(vm, expiration_period):
if expiration_period is None:
return False
today = datetime.today()
last_update_str = vm.features.get(
'last-updates-check',
datetime.fromtimestamp(0).strftime('%Y-%m-%d %H:%M:%S')
)
last_update = datetime.fromisoformat(last_update_str)
if (today - last_update).days > expiration_period:
return True
return False


class UpdateRowWrapper(RowWrapper):
COLUMN_NUM = 9
_SELECTION = 1
Expand Down
22 changes: 15 additions & 7 deletions qui/updater/progress_page.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,8 @@ def __init__(
log,
header_label,
next_button,
cancel_button
cancel_button,
callback
):
self.builder = builder
self.log = log
Expand All @@ -54,6 +55,7 @@ def __init__(
self.vms_to_update = None
self.exit_triggered = False
self.update_thread = None
self.after_update_callback = callback

self.update_details = QubeUpdateDetails(self.builder)

Expand Down Expand Up @@ -127,9 +129,10 @@ def perform_update(self, settings):
if templs:
self.update_templates(templs, settings)

GLib.idle_add(self.next_button.set_sensitive, True)
GLib.idle_add(self.header_label.set_text, l("Update finished"))
GLib.idle_add(self.cancel_button.set_visible, False)
GLib.idle_add(self.next_button.set_sensitive, True)
self.after_update_callback()

def update_admin_vm(self, admins):
"""Runs command to update dom0."""
Expand Down Expand Up @@ -327,6 +330,7 @@ def do_update_templates(
['qubes-vm-update',
'--show-output',
'--just-print-progress',
'--force-update',
*args,
'--targets', targets],
stderr=subprocess.PIPE, stdout=subprocess.PIPE)
Expand Down Expand Up @@ -378,6 +382,7 @@ def handle_err_line(self, untrusted_line, rows):
try:
if status == "done":
update_status = UpdateStatus.from_name(info)
GLib.idle_add(rows[name].set_update_progress, 100)
GLib.idle_add(rows[name].set_status, update_status)
except KeyError:
return
Expand Down Expand Up @@ -458,16 +463,19 @@ def get_update_summary(self):
2. number of vms that tried to update but no update was found,
3. vms that update was canceled before starting.
"""
vm_updated_num = len(
updated = len(
[row for row in self.vms_to_update
if row.status == UpdateStatus.Success])
vm_no_updates_num = len(
no_updates = len(
[row for row in self.vms_to_update
if row.status == UpdateStatus.NoUpdatesFound])
vm_failed_num = len(
failed = len(
[row for row in self.vms_to_update
if row.status == UpdateStatus.Error])
cancelled = len(
[row for row in self.vms_to_update
if row.status in (UpdateStatus.Error, UpdateStatus.Cancelled)])
return vm_updated_num, vm_no_updates_num, vm_failed_num
if row.status == UpdateStatus.Cancelled])
return updated, no_updates, failed, cancelled


class Ticker:
Expand Down
34 changes: 21 additions & 13 deletions qui/updater/summary_page.py
Original file line number Diff line number Diff line change
Expand Up @@ -237,7 +237,7 @@ def select_rows(self):
AppVMType.EXCLUDED in self.head_checkbox.allowed
)

def restart_selected_vms(self):
def restart_selected_vms(self, show_only_error: bool):
self.log.debug("Start restarting")
self.restart_thread = threading.Thread(
target=self.perform_restart
Expand All @@ -251,7 +251,7 @@ def restart_selected_vms(self):
time.sleep(0.01)

if self.restart_thread.is_alive():
# show wainting dialog
# show waiting dialog
spinner = Gtk.Spinner()
spinner.start()
dialog = show_dialog(None, l("Applying updates to qubes"), l(
Expand All @@ -272,7 +272,7 @@ def restart_selected_vms(self):
spinner.stop()
dialog.destroy()
self.log.debug("Hide restart dialog")
self._show_status_dialog()
self._show_status_dialog(show_only_error)

def perform_restart(self):

Expand All @@ -298,10 +298,8 @@ def perform_restart(self):
self.restart_vms(to_restart)
self.shutdown_domains(to_shutdown)

if not self.err:
if self.status is RestartStatus.NONE:
self.status = RestartStatus.OK
else:
self.status = RestartStatus.ERROR

def shutdown_domains(self, to_shutdown):
"""
Expand All @@ -317,6 +315,7 @@ def shutdown_domains(self, to_shutdown):
self.err += vm.name + " cannot shutdown: " + str(err) + '\n'
self.log.error("Cannot shutdown %s because %s",
vm.name, str(err))
self.status = RestartStatus.ERROR_TMPL_DOWN

asyncio.run(wait_for_domain_shutdown(wait_for))

Expand All @@ -336,22 +335,24 @@ def restart_vms(self, to_restart):
except qubesadmin.exc.QubesVMError as err:
self.err += vm.name + " cannot start: " + str(err) + '\n'
self.log.error("Cannot start %s because %s", vm.name, str(err))
self.status = RestartStatus.ERROR_APP_DOWN

def _show_status_dialog(self):
if self.status == RestartStatus.OK:
def _show_status_dialog(self, show_only_error: bool):
if self.status == RestartStatus.OK and not show_only_error:
show_dialog_with_icon(
None,
l("Success"),
l("All qubes were restarted/shutdown successfully."),
buttons=RESPONSES_OK,
icon_name="qubes-check-yes"
)
elif self.status == RestartStatus.ERROR:
elif self.status.is_error():
show_error(None, "Failure",
l("During restarting following errors occurs: ")
+ self.err
)
self.log.error("Restart error: %s", self.err)
self.status = RestartStatus.ERROR_APP_START


class RestartRowWrapper(RowWrapper):
Expand Down Expand Up @@ -421,10 +422,17 @@ class AppVMType:


class RestartStatus(Enum):
ERROR = 0
OK = 1
NOTHING_TO_DO = 2
NONE = 3
OK = 0
NOTHING_TO_DO = 100
NONE = -1
ERROR_TMPL_DOWN = 11
ERROR_APP_DOWN = 12
ERROR_APP_START = 13

def is_error(self: 'RestartStatus') -> bool:
return self in [
RestartStatus.ERROR_TMPL_DOWN, RestartStatus.ERROR_APP_DOWN,
RestartStatus.ERROR_APP_START]


class RestartHeaderCheckbox(HeaderCheckbox):
Expand Down
4 changes: 4 additions & 0 deletions qui/updater/tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,10 @@

@pytest.fixture
def test_qapp():
return test_qapp_impl()


def test_qapp_impl():
"""Test QubesApp"""
qapp = QubesTest()
qapp._local_name = 'dom0' # pylint: disable=protected-access
Expand Down
Loading