Skip to content

Commit

Permalink
Remove "Checking for updates" stage from updater
Browse files Browse the repository at this point in the history
Resolves #478
  • Loading branch information
eloquence committed Apr 10, 2020
1 parent 2c67f92 commit 436bbf6
Show file tree
Hide file tree
Showing 6 changed files with 171 additions and 536 deletions.
109 changes: 12 additions & 97 deletions launcher/sdw_updater_gui/Updater.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,37 +45,9 @@ def get_dom0_path(folder):
return os.path.join(os.path.expanduser("~"), folder)


def check_all_updates():
def apply_updates(vms=current_templates.keys()):
"""
Check for updates for all vms listed in current_templates above
"""

sdlog.info("Checking for all updates")

for progress_current, vm in enumerate(current_templates.keys()):
# yield the progress percentage for UI updates
progress_percentage = int(
((progress_current + 1) / len(current_templates.keys())) * 100
)
update_results = check_updates(vm)
yield vm, progress_percentage, update_results


def check_updates(vm):
"""
Check for updates for a single VM
"""
if vm == "dom0":
return _check_updates_dom0()
elif vm == "fedora":
return _check_updates_fedora()
else:
return _check_updates_debian(vm)


def apply_updates(vms):
"""
Apply updates to the TemplateVMs of VM list specified in parameter
Apply updates to all TemplateVMs
"""
sdlog.info("Applying all updates")

Expand All @@ -91,86 +63,29 @@ def apply_updates(vms):
yield vm, progress_percentage, upgrade_results


def _check_updates_dom0():
"""
Check for dom0 updates
"""
try:
subprocess.check_call(["sudo", "qubes-dom0-update", "--check-only"])
except subprocess.CalledProcessError as e:
sdlog.error("dom0 updates required or cannot check for updates")
sdlog.error(str(e))
return UpdateStatus.UPDATES_REQUIRED

sdlog.info("dom0 is up to date")
return UpdateStatus.UPDATES_OK


def _check_updates_fedora():
"""
Check for updates to the default Fedora TemplateVM. Fedora has a very rapid
release cycle and there are almost always updates to fedora VMs. Let's just
return UPDATES_REQUIRED and always upgrade those VMs, since they no longer
trigger a full workstation reboot on upgrade.
"""
return UpdateStatus.UPDATES_REQUIRED


def _check_updates_debian(vm):
"""
Check for updates for a given Debian-based TemplateVM
"""
updates_required = False
try:
# There is no apt command that uses exit codes in such a way that we can discover if
# updates are required by relying on exit codes.
# Since we don't want to use --pass-io and parse the output, we have to count
# the lines on the vm output
sdlog.info("Checking for updates {}:{}".format(vm, current_templates[vm]))
subprocess.check_call(["qvm-run", current_templates[vm], "sudo apt update"])
subprocess.check_call(
[
"qvm-run",
current_templates[vm],
"[[ $(apt list --upgradable | wc -l) -eq 1 ]]",
]
)
except subprocess.CalledProcessError as e:
sdlog.error(
"Updates required for {} or cannot check for updates".format(
current_templates[vm]
)
)
sdlog.error(str(e))
updates_required = True
finally:
reboot_status = _safely_shutdown_vm(current_templates[vm])
if reboot_status == UpdateStatus.UPDATES_FAILED:
return reboot_status

if not updates_required:
sdlog.info("{} is up to date".format(current_templates[vm]))
return UpdateStatus.UPDATES_OK
else:
return UpdateStatus.UPDATES_REQUIRED


def _apply_updates_dom0():
"""
Apply updates to dom0. Any update to dom0 will require a reboot after
the upgrade.
"""
sdlog.info("Updating dom0")
try:
subprocess.check_call(["sudo", "qubes-dom0-update", "-y"])
output = subprocess.check_output(["sudo", "qubes-dom0-update", "-y"]).decode(
"utf-8"
)
except subprocess.CalledProcessError as e:
sdlog.error(
"An error has occurred updating dom0. Please contact your administrator."
)
sdlog.error(str(e))
return UpdateStatus.UPDATES_FAILED
sdlog.info("dom0 update successful")
return UpdateStatus.REBOOT_REQUIRED

if output.find("No packages downloaded") != -1:
sdlog.info("No dom0 updates available, no reboot needed.")
return UpdateStatus.UPDATES_OK
else:
sdlog.info("dom0 updates have been applied and a reboot is required.")
return UpdateStatus.REBOOT_REQUIRED


def _apply_updates_vm(vm):
Expand Down
156 changes: 23 additions & 133 deletions launcher/sdw_updater_gui/UpdaterApp.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,96 +30,34 @@ def __init__(self, parent=None):

self.progress = 0
self.setupUi(self)

# We use a single dialog with button visibility toggled at different
# stages. In the first stage, we only show the "Start Updates" and
# "Cancel" buttons.

self.applyUpdatesButton.setEnabled(True)
self.applyUpdatesButton.show()
self.applyUpdatesButton.clicked.connect(self.apply_all_updates)

self.cancelButton.setEnabled(True)
self.cancelButton.show()
self.cancelButton.clicked.connect(self.exit_launcher)

self.clientOpenButton.setEnabled(False)
self.clientOpenButton.hide()
self.clientOpenButton.clicked.connect(launch_securedrop_client)

self.rebootButton.setEnabled(False)
self.rebootButton.hide()
self.rebootButton.clicked.connect(self.reboot_workstation)
self.applyUpdatesButton.setEnabled(False)
self.applyUpdatesButton.hide()
self.applyUpdatesButton.clicked.connect(self.apply_all_updates)

self.cancelButton.setEnabled(False)
self.cancelButton.hide()
self.cancelButton.clicked.connect(self.exit_launcher)

self.show()

self.proposedActionDescription.setText(
strings.description_status_checking_updates
)
self.proposedActionDescription.setText(strings.description_introduction)

self.progress += 1
self.progressBar.setProperty("value", self.progress)

self.vms_to_update = []

logger.info("Starting UpdateThread")
self.update_thread = UpdateThread()
self.update_thread.update_signal.connect(self.update_status)
self.update_thread.progress_signal.connect(self.update_progress_bar)
self.update_thread.start()

@pyqtSlot(dict)
def update_status(self, result):
"""
This slot will receive update signals from UpdateThread, thread which
is used to check for TemplateVM updates
"""
logger.info("Signal: update_status {}".format(str(result)))
self.progress = 100
self.progressBar.setProperty("value", self.progress)

if result["recommended_action"] == UpdateStatus.UPDATES_REQUIRED:
logger.info("Updates required")
self.vms_to_update = self.get_vms_that_need_upgrades(result)
self.applyUpdatesButton.setEnabled(True)
self.applyUpdatesButton.show()
self.cancelButton.setEnabled(True)
self.cancelButton.show()
self.proposedActionDescription.setText(
strings.description_status_updates_available
)
elif result["recommended_action"] == UpdateStatus.UPDATES_OK:
logger.info("VMs up-to-date, OK to start client")
self.clientOpenButton.setEnabled(True)
self.clientOpenButton.show()
self.cancelButton.setEnabled(True)
self.cancelButton.show()
self.proposedActionDescription.setText(
strings.description_status_up_to_date
)
elif result["recommended_action"] == UpdateStatus.REBOOT_REQUIRED:
logger.info("Reboot will be required")
# We also have further updates to do, let's apply updates and reboot
# once those are done
if len(self.get_vms_that_need_upgrades(result)) > 0:
logger.info("Reboot will be after applying upgrades")
self.vms_to_update = self.get_vms_that_need_upgrades(result)
self.applyUpdatesButton.setEnabled(True)
self.applyUpdatesButton.show()
self.cancelButton.setEnabled(True)
self.cancelButton.show()
self.proposedActionDescription.setText(
strings.description_status_updates_available
)
# No updates required, show reboot button.
else:
logger.info("Reboot required")
self.rebootButton.setEnabled(True)
self.rebootButton.show()
self.cancelButton.setEnabled(True)
self.cancelButton.show()
self.proposedActionDescription.setText(
strings.description_status_reboot_required
)
else:
logger.error("Error checking for updates")
logger.error(str(result))
self.proposedActionDescription.setText(
strings.description_error_check_updates_failed
)
self.progressBar.hide()

@pyqtSlot(dict)
def upgrade_status(self, result):
Expand Down Expand Up @@ -160,9 +98,9 @@ def upgrade_status(self, result):
@pyqtSlot(int)
def update_progress_bar(self, value):
"""
This slot will receive updates from UpdateThread and UpgradeThread which
will provide a int representing the percentage of the progressBar. This
slot will update the progressBar value once it receives the signal.
This slot will receive updates from UpgradeThread which will provide a
int representing the percentage of the progressBar. This slot will
update the progressBar value once it receives the signal.
"""
current_progress = int(value)
if current_progress <= 0:
Expand All @@ -174,18 +112,6 @@ def update_progress_bar(self, value):
self.progress = current_progress
self.progressBar.setProperty("value", self.progress)

def get_vms_that_need_upgrades(self, results):
"""
Helper method that returns a list of VMs that need upgrades based
on the results returned by the UpdateThread
"""
vms_to_upgrade = []
for vm in results.keys():
if vm != "recommended_action": # ignore this higher_level key
if results[vm] == UpdateStatus.UPDATES_REQUIRED:
vms_to_upgrade.append(vm)
return vms_to_upgrade

def apply_all_updates(self):
"""
Method used by the applyUpdatesButton that will create and start an
Expand All @@ -194,15 +120,14 @@ def apply_all_updates(self):
logger.info("Starting UpgradeThread")
self.progress = 5
self.progressBar.setProperty("value", self.progress)
self.progressBar.show()
self.proposedActionDescription.setText(
strings.description_status_applying_updates
)
self.applyUpdatesButton.setEnabled(False)
self.applyUpdatesButton.hide()
self.cancelButton.setEnabled(False)
self.cancelButton.hide()
# Create thread with list of VMs to update
self.upgrade_thread = UpgradeThread(self.vms_to_update)
self.upgrade_thread = UpgradeThread()
self.upgrade_thread.start()
self.upgrade_thread.upgrade_signal.connect(self.upgrade_status)
self.upgrade_thread.progress_signal.connect(self.update_progress_bar)
Expand All @@ -227,39 +152,6 @@ def exit_launcher(self):
sys.exit()


class UpdateThread(QThread):
"""
This thread will check for TemplateVM updates
"""

update_signal = pyqtSignal("PyQt_PyObject")
progress_signal = pyqtSignal("int")

def __init__(self):
QThread.__init__(self)

def run(self):
update_generator = Updater.check_all_updates()
results = {}

for vm, progress, result in update_generator:
results[vm] = result
self.progress_signal.emit(progress)

run_results = Updater.overall_update_status(results)
Updater._write_updates_status_flag_to_disk(run_results)
# Write the "last updated" date to disk if the system is up-to-date.
# Even though no updates have been newly applied at this stage, for the
# purposes of security checks, all we need to know is that the system is
# up-to-date as of this run.
if run_results == UpdateStatus.UPDATES_OK:
Updater._write_last_updated_flags_to_disk()
# populate signal contents
message = results # copy all the information from results
message["recommended_action"] = run_results
self.update_signal.emit(message)


class UpgradeThread(QThread):
"""
This thread will apply updates for TemplateVMs based on the VM list
Expand All @@ -268,14 +160,12 @@ class UpgradeThread(QThread):

upgrade_signal = pyqtSignal("PyQt_PyObject")
progress_signal = pyqtSignal("int")
vms_to_upgrade = []

def __init__(self, vms):
def __init__(self):
QThread.__init__(self)
self.vms_to_upgrade = vms

def run(self):
upgrade_generator = Updater.apply_updates(self.vms_to_upgrade)
upgrade_generator = Updater.apply_updates()
results = {}

for vm, progress, result in upgrade_generator:
Expand Down
Loading

0 comments on commit 436bbf6

Please sign in to comment.