-
+
-
@@ -145,6 +120,61 @@
+ -
+
+
+ Qt::Horizontal
+
+
+
+ 40
+ 20
+
+
+
+
+ -
+
+
+
+
+
+ Reboot
+
+
+ true
+
+
+
+ -
+
+
+
+
+
+ Start Updates
+
+
+ true
+
+
+ false
+
+
+
+ -
+
+
+
+
+
+ Cancel
+
+
+ true
+
+
+
diff --git a/launcher/sdw_updater_gui/strings.py b/launcher/sdw_updater_gui/strings.py
index a73e54f7e..937f10a4f 100644
--- a/launcher/sdw_updater_gui/strings.py
+++ b/launcher/sdw_updater_gui/strings.py
@@ -1,28 +1,14 @@
-# checking for updates
-description_status_checking_updates = (
- " Checking for updates...
This should take around 3 to 5 minutes.
"
-)
-
-# Results of update search
-description_status_updates_available = (
- " Security updates required.
Before you can safely use the SecureDrop "
- "app, Qubes needs to download and install critical security updates.
"
- "Updates should take around 5 to 10 minutes. The computer may need to be restarted "
- "after updates are complete.
"
- "Network access will be briefly interrupted as VMs are rebooting.
"
- "Any interruption in this process may break Workstation components.
"
- "Please close this window if now is a bad time, or click Start Updates "
+description_introduction = (
+ "
Security updates required
Before you can safely use the SecureDrop "
+ "app, we need to download and install any available security updates. "
+ "This typically takes between 5 and 30 minutes.
"
+ "Any interruption of the update process may break Workstation components.
"
+ "As part of the update process, system VMs will be restarted, and you will briefly "
+ "lose network access."
+ "
Please click Cancel if now is a bad time, or click Start Updates "
"to continue.
"
)
-description_status_up_to_date = (
- " No updates today!
"
- "Click Continue to launch the SecureDrop app.
"
-)
-description_error_check_updates_failed = (
- " Cannot check for updates
There was an error retrieving updates. "
- "Please check your Internet connection. If this problem persists, "
- "please contact your administrator."
-)
+
# Applying updates
description_status_applying_updates = (
" Downloading and installing updates...
"
diff --git a/launcher/tests/test_updater.py b/launcher/tests/test_updater.py
index ee0b5c7d3..93048086f 100644
--- a/launcher/tests/test_updater.py
+++ b/launcher/tests/test_updater.py
@@ -61,224 +61,6 @@ def test_updater_vms_present():
assert len(updater.current_templates) == 9
-@mock.patch("Updater.sdlog.error")
-@mock.patch("Updater.sdlog.info")
-def test_check_updates_fedora_always_needs_updates(mocked_info, mocked_error):
- status = updater._check_updates_fedora()
- assert status == UpdateStatus.UPDATES_REQUIRED
- assert not mocked_info.called
- assert not mocked_error.called
-
-
-@mock.patch("subprocess.check_call", return_value=0)
-@mock.patch("Updater.sdlog.error")
-@mock.patch("Updater.sdlog.info")
-def test_check_updates_dom0_up_to_date(mocked_info, mocked_error, mocked_call, capsys):
- status = updater._check_updates_dom0()
- assert status == UpdateStatus.UPDATES_OK
- mocked_info.assert_called_once_with("dom0 is up to date")
- assert not mocked_error.called
-
-
-@mock.patch(
- "subprocess.check_call", side_effect=subprocess.CalledProcessError(1, "check_call")
-)
-@mock.patch("Updater.sdlog.error")
-@mock.patch("Updater.sdlog.info")
-def test_check_updates_dom0_needs_updates(
- mocked_info, mocked_error, mocked_call, capsys
-):
- status = updater._check_updates_dom0()
- assert status == UpdateStatus.UPDATES_REQUIRED
- error_log = [
- call("dom0 updates required or cannot check for updates"),
- call("Command 'check_call' returned non-zero exit status 1."),
- ]
- mocked_error.assert_has_calls(error_log)
- assert not mocked_info.called
-
-
-@mock.patch("subprocess.check_output", return_value="0")
-@mock.patch("subprocess.check_call", return_value=0)
-@mock.patch("Updater.sdlog.error")
-@mock.patch("Updater.sdlog.info")
-def test_check_debian_updates_up_to_date(
- mocked_info, mocked_error, mocked_call, mocked_output, capsys
-):
- status = updater._check_updates_debian("sd-app")
- assert status == UpdateStatus.UPDATES_OK
- info_log = [
- call("Checking for updates {}:{}".format("sd-app", "sd-app-buster-template")),
- call("{} is up to date".format("sd-app-buster-template")),
- ]
- mocked_info.assert_has_calls(info_log)
- assert not mocked_error.called
-
-
-@mock.patch(
- "subprocess.check_output", side_effect=["0", "0"],
-)
-@mock.patch(
- "subprocess.check_call",
- side_effect=[subprocess.CalledProcessError(1, "check_call"), "0"],
-)
-@mock.patch("Updater.sdlog.error")
-@mock.patch("Updater.sdlog.info")
-def test_check_updates_debian_updates_required(
- mocked_info, mocked_error, mocked_call, mocked_output, capsys
-):
- status = updater._check_updates_debian("sd-app")
- assert status == UpdateStatus.UPDATES_REQUIRED
- error_log = [
- call(
- "Updates required for {} or cannot check for updates".format(
- "sd-app-buster-template"
- )
- ),
- call("Command 'check_call' returned non-zero exit status 1."),
- ]
- info_log = [
- call("Checking for updates {}:{}".format("sd-app", "sd-app-buster-template"))
- ]
- mocked_error.assert_has_calls(error_log)
- mocked_info.assert_has_calls(info_log)
-
-
-@mock.patch(
- "subprocess.check_output",
- side_effect=subprocess.CalledProcessError(1, "check_output",),
-)
-@mock.patch(
- "subprocess.check_call", side_effect=subprocess.CalledProcessError(1, "check_call")
-)
-@mock.patch("Updater.sdlog.error")
-@mock.patch("Updater.sdlog.info")
-def test_check_debian_updates_failed(
- mocked_info, mocked_error, mocked_call, mocked_output, capsys
-):
- status = updater._check_updates_debian("sd-app")
- assert status == UpdateStatus.UPDATES_FAILED
- error_log = [
- call(
- "Updates required for {} or cannot check for updates".format(
- "sd-app-buster-template"
- )
- ),
- call("Command 'check_call' returned non-zero exit status 1."),
- call("Failed to shut down {}".format("sd-app-buster-template")),
- call("Command 'check_output' returned non-zero exit status 1."),
- ]
- info_log = [
- call("Checking for updates {}:{}".format("sd-app", "sd-app-buster-template"))
- ]
- mocked_error.assert_has_calls(error_log)
- mocked_info.assert_has_calls(info_log)
-
-
-@mock.patch(
- "subprocess.check_output", side_effect="0",
-)
-@mock.patch(
- "subprocess.check_call",
- side_effect=[subprocess.CalledProcessError(1, "check_call"), "0", "0"],
-)
-@mock.patch("Updater.sdlog.error")
-@mock.patch("Updater.sdlog.info")
-def test_check_debian_has_updates(
- mocked_info, mocked_error, mocked_call, mocked_output, capsys
-):
- error_log = [
- call(
- "Updates required for {} or cannot check for updates".format(
- "sd-log-buster-template"
- )
- ),
- call("Command 'check_call' returned non-zero exit status 1."),
- ]
- info_log = [
- call("Checking for updates {}:{}".format("sd-log", "sd-log-buster-template"))
- ]
-
- status = updater._check_updates_debian("sd-log")
- assert status == UpdateStatus.UPDATES_REQUIRED
-
- mocked_error.assert_has_calls(error_log)
- mocked_info.assert_has_calls(info_log)
-
-
-@mock.patch("Updater.sdlog.error")
-@mock.patch("Updater.sdlog.info")
-def test_check_updates_fedora_calls_fedora(mocked_info, mocked_error):
- status = updater.check_updates("fedora")
- assert status == UpdateStatus.UPDATES_REQUIRED
-
-
-@pytest.mark.parametrize("vm", current_templates.keys())
-@mock.patch("subprocess.check_output")
-@mock.patch("subprocess.check_call")
-@mock.patch("Updater.sdlog.error")
-@mock.patch("Updater.sdlog.info")
-def test_check_updates_calls_correct_commands(
- mocked_info, mocked_error, mocked_call, mocked_output, vm
-):
- status = updater.check_updates(vm)
- if vm == "fedora":
- assert status == UpdateStatus.UPDATES_REQUIRED
- else:
- assert status == UpdateStatus.UPDATES_OK
-
- if vm in debian_based_vms:
- subprocess_call_list = [
- call(["qvm-run", current_templates[vm], "sudo apt update"]),
- call(
- [
- "qvm-run",
- current_templates[vm],
- "[[ $(apt list --upgradable | wc -l) -eq 1 ]]",
- ]
- ),
- ]
- check_output_call_list = [
- call(["qvm-shutdown", "--wait", current_templates[vm]], stderr=-1),
- ]
- elif vm == "dom0":
- subprocess_call_list = [call(["sudo", "qubes-dom0-update", "--check-only"])]
- check_output_call_list = []
- elif vm == "fedora":
- subprocess_call_list = []
- check_output_call_list = []
- else:
- pytest.fail("Unupported VM: {}".format(vm))
-
- mocked_call.assert_has_calls(subprocess_call_list)
- mocked_output.assert_has_calls(check_output_call_list)
- assert not mocked_error.called
-
-
-@mock.patch("Updater.check_updates", return_value={"test": UpdateStatus.UPDATES_OK})
-@mock.patch("subprocess.check_call")
-@mock.patch("Updater.sdlog.error")
-@mock.patch("Updater.sdlog.info")
-def test_check_all_updates(
- mocked_info, mocked_error, mocked_call, mocked_check_updates
-):
-
- update_generator = updater.check_all_updates()
- results = {}
-
- for vm, progress, result in update_generator:
- results[vm] = result
- assert progress is not None
- results[vm] = result
-
- check_updates_call_list = [call(x) for x in current_templates.keys()]
- mocked_check_updates.assert_has_calls(check_updates_call_list)
-
- assert not mocked_call.called
- assert not mocked_error.called
- assert updater.overall_update_status(results) == UpdateStatus.UPDATES_OK
-
-
@mock.patch("Updater._write_updates_status_flag_to_disk")
@mock.patch("Updater._write_last_updated_flags_to_disk")
@mock.patch("Updater._apply_updates_vm")
@@ -464,33 +246,55 @@ def test_write_last_updated_flags_dom0_folder_creation_fail(
mocked_error.assert_has_calls(error_log)
-@mock.patch("subprocess.check_output")
-@mock.patch("subprocess.check_call")
+@mock.patch("subprocess.check_output", return_value=b"")
@mock.patch("Updater._write_updates_status_flag_to_disk")
@mock.patch("Updater._write_last_updated_flags_to_disk")
@mock.patch("Updater.shutdown_and_start_vms")
@mock.patch("Updater._apply_updates_vm")
@mock.patch("Updater.sdlog.error")
@mock.patch("Updater.sdlog.info")
-def test_apply_updates_dom0_success(
+def test_apply_updates_dom0_updates_applied(
mocked_info,
mocked_error,
apply_vm,
shutdown,
write_updated,
write_status,
- mocked_call,
mocked_output,
):
result = updater._apply_updates_dom0()
assert result == UpdateStatus.REBOOT_REQUIRED
+ mocked_output.assert_called_once_with(["sudo", "qubes-dom0-update", "-y"])
+ assert not mocked_error.called
+ assert not apply_vm.called
+
+
+@mock.patch("subprocess.check_output", return_value=b"No packages downloaded")
+@mock.patch("Updater._write_updates_status_flag_to_disk")
+@mock.patch("Updater._write_last_updated_flags_to_disk")
+@mock.patch("Updater.shutdown_and_start_vms")
+@mock.patch("Updater._apply_updates_vm")
+@mock.patch("Updater.sdlog.error")
+@mock.patch("Updater.sdlog.info")
+def test_apply_updates_dom0_no_updates(
+ mocked_info,
+ mocked_error,
+ apply_vm,
+ shutdown,
+ write_updated,
+ write_status,
+ mocked_call,
+):
+ result = updater._apply_updates_dom0()
+ assert result == UpdateStatus.UPDATES_OK
mocked_call.assert_called_once_with(["sudo", "qubes-dom0-update", "-y"])
assert not mocked_error.called
assert not apply_vm.called
@mock.patch(
- "subprocess.check_call", side_effect=subprocess.CalledProcessError(1, "check_call")
+ "subprocess.check_output",
+ side_effect=subprocess.CalledProcessError(1, "check_output"),
)
@mock.patch("Updater.sdlog.error")
@mock.patch("Updater.sdlog.info")
@@ -498,7 +302,7 @@ def test_apply_updates_dom0_failure(mocked_info, mocked_error, mocked_call):
result = updater._apply_updates_dom0()
error_log = [
call("An error has occurred updating dom0. Please contact your administrator."),
- call("Command 'check_call' returned non-zero exit status 1."),
+ call("Command 'check_output' returned non-zero exit status 1."),
]
assert result == UpdateStatus.UPDATES_FAILED
mocked_error.assert_has_calls(error_log)