diff --git a/providers/base/bin/get_firmware_info_fwupd.py b/providers/base/bin/get_firmware_info_fwupd.py new file mode 100755 index 0000000000..c0d2957850 --- /dev/null +++ b/providers/base/bin/get_firmware_info_fwupd.py @@ -0,0 +1,108 @@ +#!/usr/bin/env python3 +# This file is part of Checkbox. +# +# Copyright 2024 Canonical Ltd. +# Written by: +# Stanley Huang +# +# Checkbox is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 3, +# as published by the Free Software Foundation. +# +# Checkbox is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Checkbox. If not, see . + +import os +import sys +import json +import shlex +import logging +import subprocess +from checkbox_support.snap_utils.snapd import Snapd + + +def get_fwupdmgr_services_versions(): + """Show fwupd client and daemon versions + + Returns: + list: fwupd client and daemon versions + """ + fwupd_vers = subprocess.run( + shlex.split("fwupdmgr --version --json"), + capture_output=True) + fwupd_vers = json.loads(fwupd_vers.stdout).get("Versions", []) + + return fwupd_vers + + +def get_fwupd_runtime_version(): + """Get fwupd runtime version + + Returns: + tuple: fwupd runtime version + """ + runtime_ver = () + + for ver in get_fwupdmgr_services_versions(): + if (ver.get("Type") == "runtime" and + ver.get("AppstreamId") == "org.freedesktop.fwupd"): + runtime_ver = tuple(map(int, ver.get("Version").split("."))) + + return runtime_ver + + +def get_firmware_info_fwupd(): + """ + Dump firmware information for all devices detected by fwupd + """ + if Snapd().list("fwupd"): + # Dump firmware info by fwupd snap + subprocess.run(shlex.split("fwupd.fwupdmgr get-devices --json")) + else: + # Dump firmware info by fwupd debian package + runtime_ver = get_fwupd_runtime_version() + # Apply workaround to unset the SNAP for the fwupd issue + # See details from following PR + # https://github.com/canonical/checkbox/pull/1089 + + # SNAP environ is avaialble, so it's running on checkbox snap + # Unset the environ variable if debian fwupd lower than 1.9.14 + if os.environ.get("SNAP") and runtime_ver < (1, 9, 14): + del os.environ["SNAP"] + + subprocess.run(shlex.split("fwupdmgr get-devices --json")) + + +if __name__ == "__main__": + + root_logger = logging.getLogger() + root_logger.setLevel(logging.INFO) + logger_format = "%(asctime)s %(levelname)-8s %(message)s" + date_format = "%Y-%m-%d %H:%M:%S" + + # Log DEBUG and INFO to stdout, others to stderr + stdout_handler = logging.StreamHandler(sys.stdout) + stdout_handler.setFormatter(logging.Formatter(logger_format, date_format)) + + stderr_handler = logging.StreamHandler(sys.stderr) + stderr_handler.setFormatter(logging.Formatter(logger_format, date_format)) + + stdout_handler.setLevel(logging.DEBUG) + stderr_handler.setLevel(logging.WARNING) + + # Add a filter to the stdout handler to limit log records to + # INFO level and below + stdout_handler.addFilter(lambda record: record.levelno <= logging.INFO) + + root_logger.addHandler(stderr_handler) + root_logger.addHandler(stdout_handler) + + try: + get_firmware_info_fwupd() + except Exception as err: + logging.error(err) diff --git a/providers/base/tests/test_get_firmware_info_fwupd.py b/providers/base/tests/test_get_firmware_info_fwupd.py new file mode 100644 index 0000000000..dbc8b6bbae --- /dev/null +++ b/providers/base/tests/test_get_firmware_info_fwupd.py @@ -0,0 +1,178 @@ +import os +import json +import unittest +import subprocess +from unittest.mock import patch +import get_firmware_info_fwupd + + +class TestGetFirmwareInfo(unittest.TestCase): + + @patch("json.loads") + @patch("subprocess.run") + def test_get_deb_fwupd_version_success(self, mock_subporcess, mock_json): + + dict_resp = { + "Versions": [ + { + "Type": "runtime", + "AppstreamId": "org.freedesktop.fwupd", + "Version": "1.9.14" + }, + { + "Type": "compile", + "AppstreamId": "org.freedesktop.fwupd", + "Version": "1.7.9" + } + ] + } + json_resp = json.dumps(dict_resp) + mock_subporcess.return_value = subprocess.CompletedProcess( + returncode=0, + stdout=json_resp, + args=["fwupdmgr", "--version", "--json"] + ) + mock_json.return_value = dict_resp + + fwupd_vers = get_firmware_info_fwupd.get_fwupdmgr_services_versions() + mock_subporcess.assert_called_with( + ['fwupdmgr', '--version', '--json'], + capture_output=True) + mock_json.assert_called_with(json_resp) + self.assertListEqual(dict_resp["Versions"], fwupd_vers) + + @patch("json.loads") + @patch("subprocess.run") + def test_get_deb_fwupd_version_key_not_match( + self, mock_subporcess, mock_json): + + dict_resp = { + "Services": [ + { + "Type": "runtime", + "AppstreamId": "org.freedesktop.fwupd", + "Version": "1.9.14" + }, + { + "Type": "compile", + "AppstreamId": "org.freedesktop.fwupd", + "Version": "1.7.9" + } + ] + } + json_resp = json.dumps(dict_resp) + mock_subporcess.return_value = subprocess.CompletedProcess( + returncode=0, + stdout=json_resp, + args=["fwupdmgr", "--version", "--json"] + ) + mock_json.return_value = dict_resp + + fwupd_vers = get_firmware_info_fwupd.get_fwupdmgr_services_versions() + mock_subporcess.assert_called_with( + ['fwupdmgr', '--version', '--json'], capture_output=True) + mock_json.assert_called_with(json_resp) + self.assertListEqual([], fwupd_vers) + + @patch("get_firmware_info_fwupd.get_fwupdmgr_services_versions") + def test_get_deb_fwupd_runtime_version_success(self, mock_fwupd_vers): + + expected_fwupd_ver = (1, 7, 9) + fwupd_vers_resp = [ + { + "Type": "runtime", + "AppstreamId": "org.freedesktop.fwupd", + "Version": "1.7.9" + }, + { + "Type": "compile", + "AppstreamId": "org.freedesktop.fwupd", + "Version": "1.7.9" + } + ] + + mock_fwupd_vers.return_value = fwupd_vers_resp + runtime_ver = get_firmware_info_fwupd.get_fwupd_runtime_version() + self.assertEqual(expected_fwupd_ver, runtime_ver) + + @patch("get_firmware_info_fwupd.get_fwupdmgr_services_versions") + def test_get_deb_fwupd_runtime_version_failed(self, mock_fwupd_vers): + + fwupd_vers_resp = [ + { + "Type": "compile", + "AppstreamId": "org.freedesktop.fwupd", + "Version": "1.7.9" + } + ] + + mock_fwupd_vers.return_value = fwupd_vers_resp + runtime_ver = get_firmware_info_fwupd.get_fwupd_runtime_version() + self.assertEqual((), runtime_ver) + + @patch("subprocess.run") + @patch("checkbox_support.snap_utils.snapd.Snapd.list") + def test_get_firmware_data_by_fwupd_snap( + self, mock_snapd, mock_subporcess): + + mock_snapd.return_value = { + "id": "HpOj37PuyuaMUZY0NQhtwnp7oS5P8u5R", + "title": "fwupd", + "summary": "Firmware updates for Linux" + } + get_firmware_info_fwupd.get_firmware_info_fwupd() + mock_snapd.assert_called_with("fwupd") + mock_subporcess.assert_called_with( + ['fwupd.fwupdmgr', 'get-devices', '--json']) + + @patch.dict(os.environ, {"SNAP": "checkbox-snap"}) + @patch("subprocess.run") + @patch("get_firmware_info_fwupd.get_fwupd_runtime_version") + @patch("checkbox_support.snap_utils.snapd.Snapd.list") + def test_get_firmware_data_by_fwupd1914_deb_on_checkbox_snap( + self, mock_snapd, mock_fwupd_ver, mock_subporcess): + + mock_snapd.return_value = None + mock_fwupd_ver.return_value = (1, 9, 14) + + get_firmware_info_fwupd.get_firmware_info_fwupd() + mock_snapd.assert_called_with("fwupd") + self.assertEqual( + os.environ.get("SNAP"), "checkbox-snap") + mock_subporcess.assert_called_with( + ['fwupdmgr', 'get-devices', '--json']) + + @patch.dict(os.environ, {"SNAP": "checkbox-snap"}) + @patch("subprocess.run") + @patch("get_firmware_info_fwupd.get_fwupd_runtime_version") + @patch("checkbox_support.snap_utils.snapd.Snapd.list") + def test_get_firmware_data_by_fwupd_deb179_on_checkbox_snap( + self, mock_snapd, mock_fwupd_ver, mock_subporcess): + + mock_snapd.return_value = False + mock_fwupd_ver.return_value = (1, 7, 9) + + # SNAP env is available before get_firmware_info_fwupd been called + self.assertEqual(os.environ.get("SNAP"), "checkbox-snap") + get_firmware_info_fwupd.get_firmware_info_fwupd() + mock_snapd.assert_called_with("fwupd") + # SNAP env is empty after get_firmware_info_fwupd been called + self.assertIsNone(os.environ.get("SNAP")) + mock_subporcess.assert_called_with( + ['fwupdmgr', 'get-devices', '--json']) + + @patch("subprocess.run") + @patch("get_firmware_info_fwupd.get_fwupd_runtime_version") + @patch("checkbox_support.snap_utils.snapd.Snapd.list") + def test_get_firmware_data_by_fwupd_deb_on_checkbox_deb( + self, mock_snapd, mock_fwupd_ver, mock_subporcess): + + mock_snapd.return_value = False + mock_fwupd_ver.return_value = (1, 7, 9) + + # SNAP env is empty + self.assertIsNone(os.environ.get("SNAP")) + get_firmware_info_fwupd.get_firmware_info_fwupd() + mock_snapd.assert_called_with("fwupd") + mock_subporcess.assert_called_with( + ['fwupdmgr', 'get-devices', '--json']) diff --git a/providers/base/units/firmware/jobs.pxu b/providers/base/units/firmware/jobs.pxu index 88b5d3b07a..133fb0f2a5 100644 --- a/providers/base/units/firmware/jobs.pxu +++ b/providers/base/units/firmware/jobs.pxu @@ -107,3 +107,13 @@ plugin: attachment depends: firmware/fwts_dump command: [ -f "$PLAINBOX_SESSION_SHARE/acpidump.log" ] && gzip -c "$PLAINBOX_SESSION_SHARE/acpidump.log" + +id: firmware/fwupdmgr_get_devices +plugin: attachment +category_id: com.canonical.plainbox::firmware +_summary: Collect the device firmware update information +_purpose: Attach information about the devices, as reported by the fwupdmgr +requires: + executable.name in ("fwupdmgr", "fwupd.fwupdmgr") +command: + get_firmware_info_fwupd.py diff --git a/providers/base/units/firmware/test-plan.pxu b/providers/base/units/firmware/test-plan.pxu index 4f63d19418..9776fca9ca 100644 --- a/providers/base/units/firmware/test-plan.pxu +++ b/providers/base/units/firmware/test-plan.pxu @@ -36,3 +36,28 @@ include: firmware/fwts_desktop_diagnosis firmware/fwts_desktop_diagnosis_results.log.gz + +id: firmware-fwupdmgr-full +unit: test plan +_name: fwupdmgr test +_description: Firmware update manager tests +include: +nested_part: + firmware-fwupdmgr-automated + firmware-fwupdmgr-manual + +id: firmware-fwupdmgr-automated +unit: test plan +_name: Auto fwupdmgr tests +_description: Automated firmware update manager tests +bootstrap_include: + executable + environment +include: + firmware/fwupdmgr_get_devices + +id: firmware-fwupdmgr-manual +unit: test plan +_name: Manual fwupdmgr tests +_description: Manual firmware update manager tests +include: diff --git a/providers/certification-client/units/client-cert-desktop-18-04.pxu b/providers/certification-client/units/client-cert-desktop-18-04.pxu index 6e8cd47755..2c76685430 100644 --- a/providers/certification-client/units/client-cert-desktop-18-04.pxu +++ b/providers/certification-client/units/client-cert-desktop-18-04.pxu @@ -99,6 +99,7 @@ nested_part: disk-cert-automated misc-client-cert-automated fingerprint-automated + firmware-fwupdmgr-automated keys-cert-automated led-cert-automated mediacard-cert-automated diff --git a/providers/certification-client/units/client-cert-desktop-20-04.pxu b/providers/certification-client/units/client-cert-desktop-20-04.pxu index ac5db74840..6540283c67 100644 --- a/providers/certification-client/units/client-cert-desktop-20-04.pxu +++ b/providers/certification-client/units/client-cert-desktop-20-04.pxu @@ -102,6 +102,7 @@ nested_part: disk-cert-automated misc-client-cert-automated fingerprint-automated + firmware-fwupdmgr-automated keys-cert-automated led-cert-automated mediacard-cert-automated diff --git a/providers/certification-client/units/client-cert-desktop-22-04.pxu b/providers/certification-client/units/client-cert-desktop-22-04.pxu index cfec9505c9..69b489120e 100644 --- a/providers/certification-client/units/client-cert-desktop-22-04.pxu +++ b/providers/certification-client/units/client-cert-desktop-22-04.pxu @@ -106,6 +106,7 @@ nested_part: disk-cert-automated misc-client-cert-automated fingerprint-automated + firmware-fwupdmgr-automated keys-cert-automated led-cert-automated mediacard-cert-automated diff --git a/providers/certification-client/units/client-cert-desktop-24-04.pxu b/providers/certification-client/units/client-cert-desktop-24-04.pxu index b985673543..3b30f7b98e 100644 --- a/providers/certification-client/units/client-cert-desktop-24-04.pxu +++ b/providers/certification-client/units/client-cert-desktop-24-04.pxu @@ -96,6 +96,7 @@ nested_part: disk-cert-automated misc-client-cert-automated fingerprint-automated + firmware-fwupdmgr-automated keys-cert-automated led-cert-automated mediacard-cert-automated