Skip to content

Commit d25eaaf

Browse files
committed
address comments...
1 parent bff065d commit d25eaaf

File tree

3 files changed

+82
-56
lines changed

3 files changed

+82
-56
lines changed

tmt/steps/prepare/artifact/providers/_copr.py

Lines changed: 27 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,12 @@
44

55
import re
66
import types
7-
from abc import abstractmethod
8-
from functools import cached_property
97
from typing import Any, Optional
108

119
import tmt.log
1210
import tmt.utils
1311
import tmt.utils.hints
12+
from tmt.container import container
1413
from tmt.steps.prepare.artifact.providers import ArtifactProvider
1514

1615
copr: Optional[types.ModuleType] = None
@@ -34,29 +33,43 @@
3433

3534

3635
COPR_URL = 'https://copr.fedorainfracloud.org/coprs'
37-
COPR_REPO_PATTERN = re.compile(r'^(@)?([^/]+)/([^/]+)$')
36+
COPR_REPO_PATTERN = re.compile(r'^(?P<group>@)?(?P<name>[^/]+)/(?P<project>[^/]+)$')
3837

3938

40-
def parse_copr_repo(copr_repo: str) -> tuple[bool, str, str]:
39+
@container(frozen=True)
40+
class CoprRepo:
41+
is_group: bool
42+
name: str
43+
project: str
44+
45+
46+
def parse_copr_repo(copr_repo: str) -> CoprRepo:
4147
"""
4248
Parse a COPR repository identifier into its components.
4349
"""
4450
matched = COPR_REPO_PATTERN.match(copr_repo)
4551
if not matched:
4652
raise tmt.utils.PrepareError(f"Invalid copr repository '{copr_repo}'.")
47-
is_group, name, project = matched.groups()
48-
return bool(is_group), name, project
53+
return CoprRepo(
54+
is_group=bool(matched.group('group')),
55+
name=matched.group('name'),
56+
project=matched.group('project'),
57+
)
4958

5059

5160
def build_copr_repo_url(copr_repo: str, chroot: str) -> str:
5261
"""
5362
Construct the URL for a COPR ``.repo`` file.
63+
64+
This is a workaround until the COPR API provides a dedicated endpoint
65+
for fetching repository files.
66+
See `fedora-copr/copr#4119 <https://github.com/fedora-copr/copr/issues/4119>`__.
5467
"""
55-
is_group, name, project = parse_copr_repo(copr_repo)
56-
group = 'group_' if is_group else ''
57-
parts = [COPR_URL] + (['g'] if is_group else [])
58-
parts += [name, project, 'repo', chroot]
59-
parts += [f"{group}{name}-{project}-{chroot}.repo"]
68+
repo = parse_copr_repo(copr_repo)
69+
group = 'group_' if repo.is_group else ''
70+
parts = [COPR_URL] + (['g'] if repo.is_group else [])
71+
parts += [repo.name, repo.project, 'repo', chroot]
72+
parts += [f"{group}{repo.name}-{repo.project}-{chroot}.repo"]
6073
return '/'.join(parts)
6174

6275

@@ -95,26 +108,13 @@ def _initialize_session(self) -> 'Client':
95108
except Exception as error:
96109
raise tmt.utils.GeneralError("Failed to initialize Copr client session.") from error
97110

98-
@property
99-
@abstractmethod
100-
def _copr_owner(self) -> str:
101-
raise NotImplementedError
102-
103-
@property
104-
@abstractmethod
105-
def _copr_project(self) -> str:
106-
raise NotImplementedError
107-
108-
@cached_property
109-
def project_info(self) -> Any:
111+
def _fetch_project_info(self, ownername: str, projectname: str) -> Any:
110112
"""
111113
Fetch and return the COPR project metadata.
112114
"""
113115
try:
114-
return self._session.project_proxy.get(
115-
ownername=self._copr_owner, projectname=self._copr_project
116-
)
116+
return self._session.project_proxy.get(ownername=ownername, projectname=projectname)
117117
except Exception as error:
118118
raise tmt.utils.GeneralError(
119-
f"Failed to fetch COPR project info for '{self._copr_owner}/{self._copr_project}'."
119+
f"Failed to fetch COPR project info for '{ownername}/{projectname}'."
120120
) from error

tmt/steps/prepare/artifact/providers/copr_build.py

Lines changed: 13 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
from collections.abc import Sequence
66
from functools import cached_property
77
from shlex import quote
8-
from typing import TYPE_CHECKING, Optional
8+
from typing import TYPE_CHECKING, Any, Optional
99
from urllib.parse import urljoin
1010

1111
import tmt.log
@@ -53,23 +53,23 @@ def __init__(self, raw_id: str, repository_priority: int, logger: tmt.log.Logger
5353
raise ValueError(f"Invalid provider id '{self.id}'.") from error
5454

5555
@cached_property
56-
def build_info(self) -> Optional["Munch"]:
56+
def build_info(self) -> "Munch":
5757
"""
5858
Fetch and return the build metadata.
5959
60-
:returns: the build metadata, or ``None`` if not found.
60+
:raises GeneralError: If the build is not found.
6161
"""
62-
return self._session.build_proxy.get(self.build_id)
62+
result = self._session.build_proxy.get(self.build_id)
63+
if result is None:
64+
raise tmt.utils.GeneralError(f"COPR build '{self.build_id}' not found.")
65+
return result
6366

64-
@property
65-
def _copr_owner(self) -> str:
66-
assert self.build_info is not None
67-
return str(self.build_info.ownername)
68-
69-
@property
70-
def _copr_project(self) -> str:
71-
assert self.build_info is not None
72-
return str(self.build_info.projectname)
67+
@cached_property
68+
def project_info(self) -> Any:
69+
return self._fetch_project_info(
70+
ownername=str(self.build_info.ownername),
71+
projectname=str(self.build_info.projectname),
72+
)
7373

7474
@cached_property
7575
def is_pulp(self) -> bool:
@@ -163,7 +163,6 @@ def make_rpm_artifact(self, rpm_meta: dict[str, str]) -> ArtifactInfo:
163163
version_info = RpmVersion.from_rpm_meta(rpm_meta)
164164
filename = f"{version_info}.rpm"
165165
if self.is_pulp:
166-
assert self.build_info is not None
167166
base_url = urljoin(
168167
f"{self.build_info.repo_url}/",
169168
f"{self.chroot}/Packages/{filename[0]}",

tmt/steps/prepare/artifact/providers/copr_repository.py

Lines changed: 42 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,38 @@
2727
COPR_REPOSITORY_PATTERN = re.compile(r'^(?:@[^/]+/[^/]+|[^@/]+/[^/]+)$')
2828

2929

30+
def _guess_chroot(os_release: dict[str, str], arch: str) -> str:
31+
"""
32+
Guess the COPR chroot name for the given OS.
33+
"""
34+
distro_id = os_release.get('ID', '').lower()
35+
version = os_release.get('VERSION_ID', '')
36+
37+
if distro_id == 'fedora':
38+
if version.lower() == 'rawhide':
39+
return f'fedora-rawhide-{arch}'
40+
if 'rawhide' in os_release.get('REDHAT_SUPPORT_PRODUCT_VERSION', '').lower():
41+
return f'fedora-rawhide-{arch}'
42+
return f'fedora-{version}-{arch}'
43+
44+
if distro_id == 'mageia':
45+
if 'Cauldron' in os_release.get('NAME', ''):
46+
return f'mageia-cauldron-{arch}'
47+
return f'mageia-{version}-{arch}'
48+
49+
if distro_id in ('opensuse-tumbleweed', 'opensuse-leap', 'opensuse'):
50+
if 'tumbleweed' in distro_id:
51+
return f'opensuse-tumbleweed-{arch}'
52+
return f'opensuse-leap-{version}-{arch}'
53+
54+
if distro_id == 'amzn':
55+
return f'amazonlinux-{version}-{arch}'
56+
57+
# Default: RHEL/CentOS/Rocky/Alma/etc. → epel
58+
major = version.split('.')[0]
59+
return f'epel-{major}-{arch}'
60+
61+
3062
@provides_artifact_provider('copr.repository')
3163
class CoprRepositoryProvider(CoprArtifactProvider):
3264
"""
@@ -46,20 +78,15 @@ class CoprRepositoryProvider(CoprArtifactProvider):
4678
"""
4779

4880
copr_repo: str # Parsed Copr repository name (e.g. 'packit/packit-dev')
49-
repository: Repository
81+
repository: Optional[Repository]
5082

5183
def __init__(self, raw_id: str, repository_priority: int, logger: tmt.log.Logger):
5284
super().__init__(raw_id, repository_priority, logger)
5385
self.copr_repo = self.id
54-
self._is_group, self._name, self._project = parse_copr_repo(self.copr_repo)
55-
56-
@property
57-
def _copr_owner(self) -> str:
58-
return f'@{self._name}' if self._is_group else self._name
59-
60-
@property
61-
def _copr_project(self) -> str:
62-
return self._project
86+
self.repository = None
87+
repo = parse_copr_repo(self.copr_repo)
88+
owner = f'@{repo.name}' if repo.is_group else repo.name
89+
self.project_info = self._fetch_project_info(ownername=owner, projectname=repo.project)
6390

6491
@classmethod
6592
def _extract_provider_id(cls, raw_id: str) -> ArtifactProviderId:
@@ -92,6 +119,9 @@ def _download_artifact(self, artifact: ArtifactInfo, guest: Guest, destination:
92119
)
93120

94121
def get_repositories(self) -> list[Repository]:
122+
assert self.repository is not None, (
123+
"fetch_contents() must be called before get_repositories()."
124+
)
95125
self.logger.info(f"Providing repository '{self.repository.name}' for installation")
96126
return [self.repository]
97127

@@ -106,15 +136,12 @@ def fetch_contents(
106136
"""
107137
chroot_repos = self.project_info.chroot_repos
108138
os_release = guest.facts.os_release_content
109-
chroot = (
110-
f"{os_release.get('ID', '')}-{os_release.get('VERSION_ID', '')}"
111-
f"-{guest.facts.arch or ''}"
112-
)
139+
chroot = _guess_chroot(os_release, guest.facts.arch or '')
113140

114141
if chroot not in chroot_repos:
115142
raise GeneralError(
116143
f"COPR repository '{self.copr_repo}' has no chroot '{chroot}'. "
117-
f"Available chroots: {', '.join(sorted(chroot_repos))}"
144+
f"Available: {', '.join(sorted(chroot_repos))}"
118145
)
119146

120147
url = build_copr_repo_url(self.copr_repo, chroot)

0 commit comments

Comments
 (0)