Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Include image information in Checkbox submission (New) #1460

Merged
merged 3 commits into from
Dec 2, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
13 changes: 13 additions & 0 deletions checkbox-ng/plainbox/impl/session/system_information.py
Original file line number Diff line number Diff line change
Expand Up @@ -253,6 +253,19 @@
)


class ImageInfoCollector(Collector):
COLLECTOR_NAME = "image_info"

def __init__(self):
super().__init__(

Check warning on line 260 in checkbox-ng/plainbox/impl/session/system_information.py

View check run for this annotation

Codecov / codecov/patch

checkbox-ng/plainbox/impl/session/system_information.py#L260

Added line #L260 was not covered by tests
collection_cmd=[
"python3",
str(vendor.IMAGE_INFO),
],
version_cmd=["python3", str(vendor.IMAGE_INFO), "--version"],
)


if __name__ == "__main__":
collection = collect()
print(collection.to_json())
1 change: 1 addition & 0 deletions checkbox-ng/plainbox/vendor/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,3 +31,4 @@
from pathlib import Path

INXI_PATH = (Path(__file__).parent / "inxi").resolve()
IMAGE_INFO = (Path(__file__).parent / "image_info.py").resolve()
146 changes: 146 additions & 0 deletions checkbox-ng/plainbox/vendor/image_info.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
#!/usr/bin/env python3
import sys
import json
import argparse
import subprocess

BASE_URL = "https://oem-share.canonical.com/partners"
VERSION = 1


def parse_args(argv):
parser = argparse.ArgumentParser()
parser.add_argument(
"--version",
"-v",
help="Prints the version of the tool and exits",
action="store_true",
)

return parser.parse_args(argv)


def parse_ubuntu_report():
try:
return json.loads(
subprocess.check_output(
["ubuntu-report", "show"],
universal_newlines=True,
),
)
except FileNotFoundError:
raise SystemExit(
"ubuntu-report is not installed, "
"install it to collect this information"
)


def dcd_string_to_info(dcd_string):
"""
Creates a dict with all available information that can be extracted from
the dcd string, at the very least:
- project
- series
- kernel type
- build date
- build number
- url
"""
# prefix, should always be present
dcd_string = dcd_string.replace("canonical-", "")
dcd_string = dcd_string.replace("oem-", "")
dcd_string_arr = dcd_string.split("-")
if len(dcd_string_arr) == 5:
project, series, kernel_type, build_date, build_number = dcd_string_arr
info = {
"base_url": BASE_URL,
"project": project,
"series": series,
"kernel_type": kernel_type,
"build_date": build_date,
"build_number": build_number,
}
info["url"] = (
"{base_url}/{project}/share/releases/"
"{series}/{kernel_type}/{build_date}-{build_number}/"
).format(**info)
elif len(dcd_string_arr) == 6:
(
project,
series,
kernel_type,
kernel_version,
build_date,
build_number,
) = dcd_string_arr
info = {
"base_url": BASE_URL,
"project": project,
"series": series,
"kernel_type": kernel_type,
"kernel_version": kernel_version,
"build_date": build_date,
"build_number": build_number,
}
info["url"] = (
"{base_url}/{project}/share/releases/"
"{series}/{kernel_type}-{kernel_version}/"
"{build_date}-{build_number}/"
).format(**info)
elif len(dcd_string_arr) == 7:
(
project,
series,
kernel_type,
kernel_version,
kernel_suffix,
build_date,
build_number,
) = dcd_string_arr
info = {
"base_url": BASE_URL,
"project": project,
"series": series,
"kernel_type": kernel_type,
"kernel_version": kernel_version,
"kernel_suffix": kernel_suffix,
"build_date": build_date,
"build_number": build_number,
}
info["url"] = (
"{base_url}/{project}/share/releases/"
"{series}/{kernel_type}-{kernel_version}-{kernel_suffix}/"
"{build_date}-{build_number}/"
).format(**info)
else:
raise SystemExit("Unknown dcd string format: {}".format(dcd_string))
return info


def dcd_info():
ubuntu_report = parse_ubuntu_report()
print(
"Parsed report: {}".format(json.dumps(ubuntu_report)), file=sys.stderr
)
try:
dcd_string = ubuntu_report["OEM"]["DCD"]
except KeyError:
raise SystemExit(
"Unable to find the OEM DCD string in the parsed report"
)
return dcd_string_to_info(dcd_string)


def main(argv=None):
if argv is None:
argv = sys.argv[1:]
args = parse_args(argv)
if args.version:
print(VERSION)
return
info = dcd_info()
json.dump(info, sys.stdout)


if __name__ == "__main__":
main(sys.argv[1:])
128 changes: 128 additions & 0 deletions checkbox-ng/plainbox/vendor/test_image_info.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
from unittest import TestCase
from unittest.mock import patch
from plainbox.vendor import image_info


class TestDCDStringToInfo(TestCase):
def test_len_5(self):
example = "canonical-oem-pinktiger-noble-hwe-20240709-33.iso"
info = image_info.dcd_string_to_info(example)

self.assertEqual(info["project"], "pinktiger")
self.assertEqual(info["series"], "noble")

def test_len_6(self):
example = "canonical-oem-pinktiger-noble-oem-24.04a-20240709-33.iso"
info = image_info.dcd_string_to_info(example)

self.assertEqual(info["project"], "pinktiger")
self.assertEqual(info["series"], "noble")

def test_len_7(self):
example = (
"canonical-oem-pinktiger-noble-oem-24.04a-proposed-20240709-33.iso"
)
info = image_info.dcd_string_to_info(example)

self.assertEqual(info["project"], "pinktiger")
self.assertEqual(info["series"], "noble")

def test_len_wrong(self):
with self.assertRaises(SystemExit):
image_info.dcd_string_to_info("canonical-oem-test-stub-dcd")

with self.assertRaises(SystemExit):
image_info.dcd_string_to_info("")


class TestDCDStringE2E(TestCase):
@patch("subprocess.check_output")
def test_ok_output(self, mock_check_output):
mock_check_output.return_value = """{
"Version": "24.04",
"OEM": {
"Vendor": "Some Inc.",
"Product": "13312",
"Family": "NOFAMILY",
"DCD": "canonical-oem-pinktiger-noble-oem-24.04a-20240823-74"
},
"BIOS": {
"Vendor": "Some Inc.",
"Version": "0.1.20"
},
"Arch": "amd64",
"HwCap": "x86-64-v3",
"GPU": [
{
"Vendor": "0000",
"Model": "0000"
}
],
"RAM": 16,
"Partitions": [
477.7,
1.1
],
"Autologin": true,
"LivePatch": false,
"Session": {
"DE": "",
"Name": "",
"Type": "tty"
},
"Language": "en_US",
"Timezone": "Etc/UTC",
"Install": {
"Type": "Flutter",
"OEM": false,
"Media": "Ubuntu OEM 24.04.1 LTS",
"Stages": {
"20": "loading",
"218": "done"
}
}
}"""
image_info.main([])

@patch("subprocess.check_output")
def test_fail_no_dcd_output(self, mock_check_output):
mock_check_output.return_value = """{
"Version": "24.04",
"BIOS": {
"Vendor": "Some Inc.",
"Version": "0.1.20"
},
"Arch": "amd64",
"HwCap": "x86-64-v3",
"GPU": [
{
"Vendor": "0000",
"Model": "0000"
}
],
"RAM": 16,
"Partitions": [
477.7,
1.1
],
"Autologin": true,
"LivePatch": false,
"Session": {
"DE": "",
"Name": "",
"Type": "tty"
},
"Language": "en_US",
"Timezone": "Etc/UTC",
"Install": {
"Type": "Flutter",
"OEM": false,
"Media": "Ubuntu OEM 24.04.1 LTS",
"Stages": {
"20": "loading",
"218": "done"
}
}
}"""
with self.assertRaises(SystemExit):
image_info.main([])
Loading