Skip to content

Add IoT image url check in collector (New) #1867

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

Draft
wants to merge 5 commits into
base: main
Choose a base branch
from
Draft
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
57 changes: 56 additions & 1 deletion checkbox-ng/plainbox/vendor/image_info.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,11 @@
import json
import argparse
import subprocess
import re
from pathlib import Path

BASE_URL = "https://oem-share.canonical.com/partners"
DCD_FILE_IOT = Path("/run/mnt/ubuntu-seed/.disk/info")
VERSION = 1


Expand Down Expand Up @@ -38,7 +41,7 @@ def parse_ubuntu_report():
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:
the PC's dcd string, at the very least:
- project
- series
- kernel type
Expand Down Expand Up @@ -117,7 +120,59 @@ def dcd_string_to_info(dcd_string):
return info


def dcd_string_to_info_iot(dcd_string):
"""
Convert IoT's dcd string to a URL based on specified rules.

# Regex pattern:
^canonical-oem- : Must start with "canonical-oem-"
([a-zA-Z0-9]+) : Project name (alphanumeric, mandatory)
:([a-zA-Z0-9-]+) : Series (alphanumeric and dash, mandatory)
:([0-9.-]+) : Build ID (numbers, dot, dash, mandatory)
(:(.*))? : Additional info (anything, optional) - currently unused
"""
pattern = (
r"^canonical-oem-([a-zA-Z0-9]+):([a-zA-Z0-9-]+):([0-9.-]+)(:(.*))?$"
)

match = re.match(pattern, dcd_string)
if not match:
raise ValueError("Invalid DCD format: {}".format(dcd_string))

project_name, series, build_id, _, additional_info = match.groups()

info = {
"base_url": BASE_URL,
"project": project_name,
"series": series,
"build_id": build_id,
}
image_name = "{}-{}-{}.tar.xz".format(project_name, series, build_id)
info["url"] = (
"{base_url}/{project}/share/{series}/{build_id}/{image_name}"
).format(
base_url=BASE_URL,
project=project_name,
series=series,
build_id=build_id,
image_name=image_name,
)

return info


def dcd_info():
try:
if DCD_FILE_IOT.is_file():
with open(str(DCD_FILE_IOT), "r", encoding="utf-8") as f:
dcd_string = f.read().strip()
print(
"Found IoT dcd string: {}".format(dcd_string), file=sys.stderr
)
return dcd_string_to_info_iot(dcd_string)
except (IOError, OSError):
print("IoT dcd file not found. Assuming PC platform", file=sys.stderr)

ubuntu_report = parse_ubuntu_report()
print(
"Parsed report: {}".format(json.dumps(ubuntu_report)), file=sys.stderr
Expand Down
119 changes: 119 additions & 0 deletions checkbox-ng/plainbox/vendor/test_image_info.py
Original file line number Diff line number Diff line change
Expand Up @@ -126,3 +126,122 @@ def test_fail_no_dcd_output(self, mock_check_output):
}"""
with self.assertRaises(SystemExit):
image_info.main([])


class TestDCDStringToInfoIoT(TestCase):
def test_valid_dcd_with_all_fields(self):
example = (
"canonical-oem-carlsbad:element-v2-uc24:20241205.15:v2-uc24-x01"
)
info = image_info.dcd_string_to_info_iot(example)

self.assertEqual(info["project"], "carlsbad")
self.assertEqual(info["series"], "element-v2-uc24")
self.assertEqual(info["build_id"], "20241205.15")
self.assertEqual(
info["url"],
"https://oem-share.canonical.com/partners/carlsbad/share/element-v2-uc24/20241205.15/carlsbad-element-v2-uc24-20241205.15.tar.xz",
)

def test_valid_dcd_no_additional_info(self):
example = "canonical-oem-shiner:x8high35-som-pdk:20250507-1170:"
info = image_info.dcd_string_to_info_iot(example)

self.assertEqual(info["project"], "shiner")
self.assertEqual(info["series"], "x8high35-som-pdk")
self.assertEqual(info["build_id"], "20250507-1170")
self.assertEqual(
info["url"],
"https://oem-share.canonical.com/partners/shiner/share/x8high35-som-pdk/20250507-1170/shiner-x8high35-som-pdk-20250507-1170.tar.xz",
)

def test_invalid_dcd_formats(self):
invalid_examples = [
# Missing canonical-oem- prefix
"projectalpha:series:20241210:",
# Missing project name
"canonical-oem-:series:20241210:",
# Missing series
"canonical-oem-project::",
# Missing build ID
"canonical-oem-project:series::",
# Invalid project name (special chars)
"canonical-oem-project@123:series:20241210:",
# Invalid series (special chars)
"canonical-oem-project:series@123:20241210:",
# Invalid build ID (letters)
"canonical-oem-project:series:abc123:",
# Empty string
"",
# Wrong format
"canonical-oem-test-stub-dcd",
]

for example in invalid_examples:
with self.assertRaises(
ValueError, msg="Should fail for: {}".format(example)
):
image_info.dcd_string_to_info_iot(example)


class TestDCDStringE2EIoT(TestCase):
@patch("builtins.open")
@patch("pathlib.Path.is_file")
def test_dcd_info_iot_path_all_fields(self, mock_is_file, mock_open):
mock_is_file.return_value = True
mock_file = mock_open.return_value.__enter__.return_value
mock_file.read.return_value = (
"canonical-oem-carlsbad:element-v2-uc24:20241205.15:v2-uc24-x01"
)

info = image_info.dcd_info()

self.assertEqual(info["project"], "carlsbad")
self.assertEqual(info["series"], "element-v2-uc24")
self.assertEqual(info["build_id"], "20241205.15")
self.assertEqual(
info["url"],
"https://oem-share.canonical.com/partners/carlsbad/share/element-v2-uc24/20241205.15/carlsbad-element-v2-uc24-20241205.15.tar.xz",
)

@patch("builtins.open")
@patch("pathlib.Path.is_file")
def test_dcd_info_iot_path_no_additional_info(
self, mock_is_file, mock_open
):
mock_is_file.return_value = True
mock_file = mock_open.return_value.__enter__.return_value
mock_file.read.return_value = (
"canonical-oem-shiner:x8high35-som-pdk:20250507-1170:"
)

info = image_info.dcd_info()

self.assertEqual(info["project"], "shiner")
self.assertEqual(info["series"], "x8high35-som-pdk")
self.assertEqual(info["build_id"], "20250507-1170")
self.assertEqual(
info["url"],
"https://oem-share.canonical.com/partners/shiner/share/x8high35-som-pdk/20250507-1170/shiner-x8high35-som-pdk-20250507-1170.tar.xz",
)

@patch("pathlib.Path.is_file")
@patch("subprocess.check_output")
def test_dcd_info_iot_path_not_exists(
self, mock_check_output, mock_is_file
):
mock_is_file.return_value = False
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"
}
}"""

info = image_info.dcd_info()
# Since iot dcd file is missing, this must be pc platform
self.assertEqual(info["project"], "pinktiger")
self.assertEqual(info["series"], "noble")
Loading