Skip to content

Commit 2b59267

Browse files
bajertompsss
andauthored
Fix failed download and copy of artifact rpms (#5108)
Fixes #4886 and #5018. Second commit might be irrelevant since the first one raises on the no-download problem. Not sure if a scenario where the `try/except` block would actually go with the `except` clause could happen now, but #5020 should introduce an option to allow for this behavior, so I kept the checks. Co-authored-by: Petr Šplíchal <psplicha@redhat.com>
1 parent de9ffb2 commit 2b59267

9 files changed

Lines changed: 42 additions & 59 deletions

File tree

tests/prepare/artifact/providers/file/test.sh

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,14 @@ rlJournalStart
5555
prepare --how artifact --provide file:$LIB_DIR/../rpms/bar" \
5656
0 "Run directory"
5757
rlPhaseEnd
58+
59+
rlPhaseStartTest "$phase_prefix Fail on nonexistent file"
60+
rlRun -s "tmt run -i $run --scratch -vvv --all \
61+
provision -h $PROVISION_HOW --image $image \
62+
prepare --how artifact --provide file:/no/such/package.rpm" \
63+
2 "Nonexistent file should fail"
64+
rlAssertGrep "No artifacts were downloaded" $rlRun_LOG
65+
rlPhaseEnd
5866
done <<< "$IMAGES"
5967

6068
rlPhaseStartCleanup

tmt/steps/prepare/artifact/__init__.py

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -285,11 +285,12 @@ def go(
285285
provider.fetch_contents(guest, download_path)
286286

287287
# Then, have the provider contribute to the shared repository
288-
provider.contribute_to_shared_repo(
289-
guest=guest,
290-
source_path=download_path,
291-
shared_repo_dir=shared_repo_dir,
292-
)
288+
if provider.downloads_artifacts:
289+
provider.contribute_to_shared_repo(
290+
guest=guest,
291+
source_path=download_path,
292+
shared_repo_dir=shared_repo_dir,
293+
)
293294

294295
except tmt.utils.PrepareError:
295296
raise

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

Lines changed: 24 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
from functools import cached_property
99
from re import Pattern
1010
from shlex import quote
11-
from typing import TYPE_CHECKING, Any, Optional
11+
from typing import TYPE_CHECKING, Any, ClassVar, Optional
1212

1313
import tmt.log
1414
import tmt.utils
@@ -115,6 +115,10 @@ class ArtifactProvider(ABC):
115115
#: The PrepareArtifact phase that owns this provider.
116116
parent: "PrepareArtifact"
117117

118+
#: Whether this provider downloads individual artifact files.
119+
#: Providers that only register repositories should set this to ``False``.
120+
downloads_artifacts: ClassVar[bool] = True
121+
118122
def __post_init__(self) -> None:
119123
self.id = self._extract_provider_id(self.raw_id)
120124

@@ -219,6 +223,11 @@ def fetch_contents(
219223
f"Unexpected error downloading '{artifact}'."
220224
) from error
221225

226+
if not downloaded_paths:
227+
raise tmt.utils.PrepareError(
228+
f"No artifacts were downloaded for provider '{self.raw_id}'. "
229+
f"Verify the provider identifier is correct."
230+
)
222231
self.logger.info(f"Successfully downloaded '{len(downloaded_paths)}' artifacts.")
223232
return downloaded_paths
224233

@@ -284,15 +293,11 @@ def enumerate_artifacts(self, guest: Guest) -> None:
284293
f"Enumerated {len(packages)} packages from repository '{repository.name}'."
285294
)
286295

287-
# B027: "... is an empty method in an abstract base class, but has
288-
# no abstract decorator" - expected, it's a default implementation
289-
# provided for subclasses. It is acceptable to do nothing.
290-
def contribute_to_shared_repo( # noqa: B027
296+
def contribute_to_shared_repo(
291297
self,
292298
guest: Guest,
293299
source_path: Path,
294300
shared_repo_dir: Path,
295-
exclude_patterns: Optional[list[Pattern[str]]] = None,
296301
) -> None:
297302
"""
298303
Contribute artifacts to the shared repository.
@@ -305,10 +310,20 @@ def contribute_to_shared_repo( # noqa: B027
305310
:param source_path: path where the artifacts are located (source for contribution).
306311
:param shared_repo_dir: path to the shared repository directory where
307312
artifacts should be contributed.
308-
:param exclude_patterns: if set, artifacts whose names match any
309-
of the given regular expressions would not be contributed.
310313
"""
311-
pass
314+
try:
315+
guest.execute(
316+
ShellScript(f"cp {quote(str(source_path))}/*.rpm {quote(str(shared_repo_dir))}")
317+
)
318+
except tmt.utils.RunError as error:
319+
if error.stderr and "No such file" in error.stderr:
320+
self.logger.warning(f"No artifacts to contribute from '{source_path}'.")
321+
return
322+
raise tmt.utils.PrepareError(
323+
f"Failed to copy artifacts from '{source_path}' to '{shared_repo_dir}'."
324+
) from error
325+
326+
self.logger.info(f"Contributed artifacts from '{source_path}' to '{shared_repo_dir}'.")
312327

313328
@property
314329
def artifact_metadata(self) -> list[dict[str, Any]]:

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

Lines changed: 0 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -5,23 +5,20 @@
55
import types
66
from collections.abc import Sequence
77
from functools import cached_property
8-
from shlex import quote
98
from typing import TYPE_CHECKING, Any, Optional
109
from urllib.parse import urljoin
1110

1211
import tmt.log
1312
import tmt.utils
1413
import tmt.utils.hints
1514
from tmt.container import container, simple_field
16-
from tmt.guest import Guest
1715
from tmt.package_managers._rpm import RpmVersion
1816
from tmt.steps.prepare.artifact.providers import (
1917
ArtifactInfo,
2018
ArtifactProvider,
2119
ArtifactProviderId,
2220
provides_artifact_provider,
2321
)
24-
from tmt.utils import ShellScript
2522

2623
if TYPE_CHECKING:
2724
from munch import Munch
@@ -216,15 +213,3 @@ def artifacts(self) -> Sequence[ArtifactInfo]:
216213
rpm_metas = self._fetch_results_json() if self.is_pulp else self.build_packages
217214

218215
return [self.make_rpm_artifact(rpm_meta) for rpm_meta in rpm_metas]
219-
220-
def contribute_to_shared_repo(
221-
self,
222-
guest: Guest,
223-
source_path: tmt.utils.Path,
224-
shared_repo_dir: tmt.utils.Path,
225-
exclude_patterns: Optional[list[tmt.utils.Pattern[str]]] = None,
226-
) -> None:
227-
guest.execute(
228-
ShellScript(f"cp {quote(str(source_path))}/*.rpm {quote(str(shared_repo_dir))}")
229-
)
230-
self.logger.info(f"Contributed artifacts from '{source_path}' to '{shared_repo_dir}'.")

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,7 @@ class CoprRepositoryProvider(ArtifactProvider):
6767
"""
6868

6969
repository: Optional[Repository] = None
70+
downloads_artifacts = False
7071

7172
@classmethod
7273
def _extract_provider_id(cls, raw_id: str) -> ArtifactProviderId:

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

Lines changed: 1 addition & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,7 @@
22
import urllib.parse
33
from collections.abc import Sequence
44
from functools import cached_property
5-
from shlex import quote
6-
from typing import ClassVar, Optional
5+
from typing import ClassVar
76

87
import tmt.utils
98
from tmt.container import container, simple_field
@@ -16,7 +15,6 @@
1615
DownloadError,
1716
provides_artifact_provider,
1817
)
19-
from tmt.utils import ShellScript
2018

2119

2220
@provides_artifact_provider("file")
@@ -126,15 +124,3 @@ def _download_artifact(
126124
self.logger.info(f"Successfully downloaded: '{artifact.id}'.")
127125
except Exception as error:
128126
raise DownloadError(f"Failed to download '{artifact}'.") from error
129-
130-
def contribute_to_shared_repo(
131-
self,
132-
guest: Guest,
133-
source_path: tmt.utils.Path,
134-
shared_repo_dir: tmt.utils.Path,
135-
exclude_patterns: Optional[list[tmt.utils.Pattern[str]]] = None,
136-
) -> None:
137-
guest.execute(
138-
ShellScript(f"cp {quote(str(source_path))}/*.rpm {quote(str(shared_repo_dir))}")
139-
)
140-
self.logger.info(f"Contributed artifacts from '{source_path}' to '{shared_repo_dir}'.")

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

Lines changed: 0 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -6,23 +6,20 @@
66
from abc import abstractmethod
77
from collections.abc import Iterator, Sequence
88
from functools import cached_property
9-
from shlex import quote
109
from typing import Any, Optional, TypeVar
1110
from urllib.parse import urljoin
1211

1312
import tmt.log
1413
import tmt.utils
1514
import tmt.utils.hints
1615
from tmt.container import container, simple_field
17-
from tmt.guest import Guest
1816
from tmt.package_managers._rpm import RpmVersion
1917
from tmt.steps.prepare.artifact.providers import (
2018
ArtifactInfo,
2119
ArtifactProvider,
2220
ArtifactProviderId,
2321
provides_artifact_provider,
2422
)
25-
from tmt.utils import ShellScript
2623

2724
koji: Optional[types.ModuleType] = None
2825

@@ -178,18 +175,6 @@ def _extract_provider_id(cls, raw_id: str) -> ArtifactProviderId:
178175
) from exc
179176
return value
180177

181-
def contribute_to_shared_repo(
182-
self,
183-
guest: Guest,
184-
source_path: tmt.utils.Path,
185-
shared_repo_dir: tmt.utils.Path,
186-
exclude_patterns: Optional[list[tmt.utils.Pattern[str]]] = None,
187-
) -> None:
188-
guest.execute(
189-
ShellScript(f"cp {quote(str(source_path))}/*.rpm {quote(str(shared_repo_dir))}")
190-
)
191-
self.logger.info(f"Contributed artifacts from '{source_path}' to '{shared_repo_dir}'.")
192-
193178
def make_rpm_artifact(self, rpm_meta: dict[str, Any]) -> ArtifactInfo:
194179
"""
195180
Create a normal build RPM artifact from metadata returned by listBuildRPMs.

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ class RepositoryFileProvider(ArtifactProvider):
4444
"""
4545

4646
repository: Repository = simple_field(init=False)
47+
downloads_artifacts = False
4748

4849
@classmethod
4950
def _extract_provider_id(cls, raw_id: str) -> ArtifactProviderId:

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ class RepositoryUrlProvider(ArtifactProvider):
4242
# is made dynamic.
4343

4444
repository: Repository = simple_field(init=False)
45+
downloads_artifacts = False
4546

4647
@classmethod
4748
def _extract_provider_id(cls, raw_id: str) -> ArtifactProviderId:

0 commit comments

Comments
 (0)