diff --git a/mrhlpr/mr.py b/mrhlpr/mr.py index 22d42753d639acbda9354586f7813b53a2df2303..8b6bc8923f59ca4e0c23dd0c6a18bba3671ba331 100644 --- a/mrhlpr/mr.py +++ b/mrhlpr/mr.py @@ -44,6 +44,12 @@ class MergeRequestStatus: state: str +@dataclass +class PipelineMetadata: + host_arch_job: dict + project_id: int + + def get_status( mr_id: int, no_cache: bool = False, origin: Optional[gitlab.GitLabOrigin] = None ) -> MergeRequestStatus: @@ -119,7 +125,7 @@ def get_status( ) -def get_artifacts_zip(mr_id, no_cache=False, origin): +def get_pipeline_metadata(mr_id, no_cache: bool, origin: gitlab.GitLabOrigin) -> PipelineMetadata: """Download artifacts from the GitLab API. :param mr_id: merge request ID @@ -155,8 +161,57 @@ def get_artifacts_zip(mr_id, no_cache=False, origin): ) exit(1) + return PipelineMetadata(host_arch_job=job, project_id=pipeline_project_id) + + +class UnknownReleaseError(ValueError): ... + + +def get_artifacts_zip(mr_id: int, no_cache: bool, origin: gitlab.GitLabOrigin) -> str: + pipeline_metadata = get_pipeline_metadata(mr_id, no_cache, origin) # Download artifacts zip (with cache) - return gitlab.download_artifacts_zip(origin.api, pipeline_project_id, job) + return gitlab.download_artifacts_zip( + origin.api, + pipeline_metadata.project_id, + pipeline_metadata.host_arch_job, + ) + + +def target_branch_to_pmos_release(target_branch: str) -> str: + if target_branch == "master": + return "edge" + + pattern = re.compile(r"^v\d\d\.\d\d$") + + if pattern.match(target_branch): + return target_branch[1:] + + raise UnknownReleaseError + + +def get_artifacts_repo_urls( + mr_id: int, no_cache: bool, origin: gitlab.GitLabOrigin, alpine_mr: bool +) -> list[str]: + pipeline_metadata = get_pipeline_metadata(mr_id, no_cache, origin) + url_mr = "/projects/{}/merge_requests/{}".format( + origin.api_project_id, + mr_id, + ) + api = gitlab.download_json(origin, url_mr, no_cache=True) + + host_arch_job_web_url = pipeline_metadata.host_arch_job["web_url"] + if alpine_mr: + return [ + f"{host_arch_job_web_url}/artifacts/raw/packages/main", + f"{host_arch_job_web_url}/artifacts/raw/packages/community", + f"{host_arch_job_web_url}/artifacts/raw/packages/testing", + ] + else: + pmos_release = target_branch_to_pmos_release(api["target_branch"]) + + return [ + f"{host_arch_job_web_url}/artifacts/raw/packages/{pmos_release}", + ] def checkout( diff --git a/mrtest/add_packages.py b/mrtest/add_packages.py index e00d2ab8ed171cd78f348ac95bd095bb7b52b3a7..d44b7872071883aa74b33e16af3b2fb5a123975d 100644 --- a/mrtest/add_packages.py +++ b/mrtest/add_packages.py @@ -7,6 +7,7 @@ import os import shutil import zipfile import subprocess +from typing import Literal from urllib.error import HTTPError import mrhlpr.mr @@ -68,7 +69,7 @@ def run_apk_add(origin, mr_id, apk_paths): subprocess.run(cmd, check=True) -def confirm_mr_id(origin, mr_id): +def confirm_mr_id(origin, mr_id: int, action: Literal["add", "upgrade"]) -> None: """ :param origin: gitlab origin information, see gitlab.parse_git_origin() :param mr_id: merge request ID @@ -76,6 +77,7 @@ def confirm_mr_id(origin, mr_id): link = f"https://{origin.host}/{origin.project_id}/-/merge_requests/{mr_id}" status = mrhlpr.mr.get_status(mr_id, origin=origin) + action_description = "select and then install" if action == "add" else "upgrade" print("Welcome to mrtest, this tool allows downloading and installing") print("Alpine packages from merge requests.") @@ -85,7 +87,7 @@ def confirm_mr_id(origin, mr_id): print("Malicious code may make your device permanently unusable, steal") print("your passwords, and worse.") print() - print("You are about to select and then install packages from:") + print(f"You are about to {action_description} packages from:") print(link) print("\t“\033[1m" + status.title + "\033[mâ€") print("\t(\033[3m" + status.source + ":" + status.source_branch + "\033[m)") @@ -111,7 +113,7 @@ def add_packages(origin, mr_id, no_cache): :param no_cache: instead of using a cache for api calls / downloads where it makes sense, always download a fresh copy """ - confirm_mr_id(origin, mr_id) + confirm_mr_id(origin, mr_id, "add") try: zip_path = mrhlpr.mr.get_artifacts_zip(mr_id, no_cache, origin) diff --git a/mrtest/frontend.py b/mrtest/frontend.py index 05552bd97483f89a3116cd967a4ef1ad1f06de8d..2c1075822afdd62bfc95999703d18dfe46661d5b 100644 --- a/mrtest/frontend.py +++ b/mrtest/frontend.py @@ -8,6 +8,7 @@ import sys import mrtest.add_packages import mrtest.origin +import mrtest.upgrade_packages import mrtest.zap_packages try: @@ -18,7 +19,16 @@ except ImportError: def parse_args_parser_add(sub): """:param sub: argparser's subparser""" - parser = sub.add_parser("add", help="install/upgrade to packages from a MR") + parser = sub.add_parser("add", help="install packages from an MR") + parser.add_argument( + "-a", "--alpine", action="store_true", help="use alpine's aports instead of pmOS' pmaports" + ) + parser.add_argument("mr_id", type=int, help="merge request ID") + + +def parse_args_parser_upgrade(sub) -> None: + """:param sub: argparser's subparser""" + parser = sub.add_parser("upgrade", help="upgrade to packages from an MR") parser.add_argument( "-a", "--alpine", action="store_true", help="use alpine's aports instead of pmOS' pmaports" ) @@ -45,6 +55,7 @@ def parse_args(): sub.required = True parse_args_parser_add(sub) + parse_args_parser_upgrade(sub) parse_args_parser_zap(sub) if "argcomplete" in sys.modules: @@ -56,8 +67,10 @@ def main(): args = parse_args() if args.verbose: logging.getLogger().setLevel(logging.DEBUG) + origin = mrtest.origin.aports if args.alpine else mrtest.origin.pmaports if args.action == "add": - origin = mrtest.origin.aports if args.alpine else mrtest.origin.pmaports mrtest.add_packages.add_packages(origin, args.mr_id, args.no_cache) + elif args.action == "upgrade": + mrtest.upgrade_packages.upgrade_from_mr(origin, args.mr_id, args.alpine) elif args.action == "zap": mrtest.zap_packages.zap_packages() diff --git a/mrtest/upgrade_packages.py b/mrtest/upgrade_packages.py new file mode 100644 index 0000000000000000000000000000000000000000..8103cea20231639b50c2d7dd0cbee3c45a6ad93b --- /dev/null +++ b/mrtest/upgrade_packages.py @@ -0,0 +1,30 @@ +# Copyright 2024 Stefan Hansson +# SPDX-License-Identifier: GPL-3.0-or-later + +import logging +import subprocess + +# sudo apk upgrade -X https://gitlab.com/postmarketOS/pmaports/-/jobs/7926831421/artifacts/raw/packages/edge --allow-untrusted +import mrtest +from mrhlpr.mr import get_artifacts_repo_urls +from mrtest.add_packages import confirm_mr_id + + +def upgrade_from_mr(origin: str, mr_id: int, alpine_mr: bool) -> None: + confirm_mr_id(origin, mr_id, "upgrade") + repo_urls = get_artifacts_repo_urls(mr_id, True, origin, alpine_mr) + + repo_args = [] + + for repo_url in repo_urls: + repo_args.append("-X") + repo_args.append(repo_url) + + cmd = ["apk", "upgrade", *repo_args, "--allow-untrusted", "--force-missing-repositories"] + + if not mrtest.is_root_user(): # type: ignore[attr-defined] + cmd = [mrtest.get_sudo()] + cmd # type: ignore[attr-defined] + + print("Upgrading packages...") + logging.debug(f"+ {cmd}") + subprocess.run(cmd, check=True)