From fd915a09ebbc8d041ffc4ac88a04f79836c45cda Mon Sep 17 00:00:00 2001 From: Robert Bikar Date: Wed, 13 Mar 2024 15:20:31 +0100 Subject: [PATCH] Add Client.create_repository() [RHELDST-22483] `Client` is now capable of creating a repository on pulp server. The repository is initialized with proper `Importer` in order to enable sync/upload of content to new repositories. Another notable changes: * 409 status is not retried in requests as it is used as indicator of existing repository. * Added create_repository() method to FakeClient as well. * Small changes in the repository.yaml schema as some of the fields don't have to be necessarily initialized when creating repo but can be updated later. * Added `Importer` pulp object mapping class and related subclasses that encapsulate minimal required fields for this object with valid defaults. * added `"importers": True` to search options for repos * fixed converter of `notes.signatures` on `Repository` - empty string is now converted to empty list, not to list with empty string --- CHANGELOG.md | 2 +- docs/api/files.rst | 2 + docs/api/pulpcore.rst | 2 + docs/api/yum.rst | 2 + pubtools/pulplib/__init__.py | 3 + pubtools/pulplib/_impl/client/client.py | 79 +++++++- pubtools/pulplib/_impl/client/retry.py | 4 +- pubtools/pulplib/_impl/fake/client.py | 9 + pubtools/pulplib/_impl/model/__init__.py | 3 + pubtools/pulplib/_impl/model/common.py | 12 ++ .../_impl/model/repository/__init__.py | 6 +- .../pulplib/_impl/model/repository/base.py | 57 +++++- .../pulplib/_impl/model/repository/file.py | 25 ++- .../pulplib/_impl/model/repository/yum.py | 25 ++- pubtools/pulplib/_impl/model/unit/base.py | 12 -- pubtools/pulplib/_impl/model/unit/erratum.py | 3 +- pubtools/pulplib/_impl/model/unit/modulemd.py | 3 +- pubtools/pulplib/_impl/model/unit/rpm.py | 3 +- pubtools/pulplib/_impl/schema/repository.yaml | 8 +- setup.py | 2 +- tests/fake/test_fake_create_repo.py | 16 ++ tests/repository/test_yum_repository.py | 188 +++++++++++++++++- 22 files changed, 436 insertions(+), 30 deletions(-) create mode 100644 tests/fake/test_fake_create_repo.py diff --git a/CHANGELOG.md b/CHANGELOG.md index c940dbb3..4900f6d8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,7 +7,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] -- n/a +- Added `Client.create_repository()` method ## [2.38.1] - 2023-12-14 diff --git a/docs/api/files.rst b/docs/api/files.rst index 1d70fd8b..90be3524 100644 --- a/docs/api/files.rst +++ b/docs/api/files.rst @@ -14,6 +14,8 @@ Repository .. autoclass:: pubtools.pulplib.FileSyncOptions() :members: +.. autoclass:: pubtools.pulplib.FileImporter() + :members: Units ----- diff --git a/docs/api/pulpcore.rst b/docs/api/pulpcore.rst index 08b12255..4baabce9 100644 --- a/docs/api/pulpcore.rst +++ b/docs/api/pulpcore.rst @@ -27,6 +27,8 @@ Repository .. autoclass:: pubtools.pulplib.SyncOptions() :members: +.. autoclass:: pubtools.pulplib.Importer() + :members: Units ----- diff --git a/docs/api/yum.rst b/docs/api/yum.rst index 8c9d5274..b964b943 100644 --- a/docs/api/yum.rst +++ b/docs/api/yum.rst @@ -14,6 +14,8 @@ Repository .. autoclass:: pubtools.pulplib.YumSyncOptions() :members: +.. autoclass:: pubtools.pulplib.YumImporter() + :members: Units: RPM ---------- diff --git a/pubtools/pulplib/__init__.py b/pubtools/pulplib/__init__.py index 615badc6..b27ae7d8 100644 --- a/pubtools/pulplib/__init__.py +++ b/pubtools/pulplib/__init__.py @@ -31,5 +31,8 @@ Task, MaintenanceReport, MaintenanceEntry, + Importer, + YumImporter, + FileImporter, ) from ._impl.fake import FakeController diff --git a/pubtools/pulplib/_impl/client/client.py b/pubtools/pulplib/_impl/client/client.py index 673189d5..2e3f13ff 100644 --- a/pubtools/pulplib/_impl/client/client.py +++ b/pubtools/pulplib/_impl/client/client.py @@ -278,7 +278,7 @@ def search_repository(self, criteria=None): Each page will contain a collection of :class:`~pubtools.pulplib.Repository` objects. """ - search_options = {"distributors": True} + search_options = {"distributors": True, "importers": True} return self._search( Repository, "repositories", criteria=criteria, search_options=search_options ) @@ -1054,3 +1054,80 @@ def _do_sync(self, repo_id, sync_options): return self._task_executor.submit( self._do_request, method="POST", url=url, json=body ) + + def create_repository(self, repo): + """Create a repository with initial data provided in the + argument. Importer for repository is automatically associated + if available. If repository already exists, warning is logged. + After repository has been successfully created or if repository already exists, + it is checked if expected and current repository configuration values are + correct. + + Args: + repo (:class:`~pubtools.pulplib.Repository`) + A repository object used for creation. + + Returns: + Future[Repository] + A future which is resolved with a value of ``Repository`` once the + repository has been created. + + .. versionadded:: 2.39.0 + """ + url = os.path.join(self._url, "pulp/api/v2/repositories/") + + body = repo._to_data() + repo_id = body["id"] + + importer = body.pop("importers", []) + body["importer_type_id"] = importer[0]["importer_type_id"] if importer else None + body["importer_config"] = importer[0]["config"] if importer else None + + def log_existing_repo(exception): + if ( + getattr(exception, "response", None) is not None + and exception.response.status_code == 409 + ): + LOG.warning("Repository %s already exists", repo_id) + return None + + raise exception + + def check_repo(repo_on_server): + try: + assert ( + repo_on_server == repo + ), "Repo exists on server with unexpected values" + except AssertionError: + if importer: + for attr in ["type_id", "config"]: + expected = getattr(repo.importer, attr) + current = getattr(repo_on_server.importer, attr) + if expected != current: + LOG.error( + "Repository %s contains wrong importer %s\n" + "\t expected: %s\n" + "\t current: %s\n", + repo_id, + attr, + expected, + current, + ) + LOG.exception( + "Repository %s exists on server and contains unexpected values", + repo_id, + ) + raise + + return f_return(repo_on_server) + + LOG.debug("Creating repository %s", repo_id) + out = self._request_executor.submit( + self._do_request, method="POST", url=url, json=body + ) + + out = f_map(out, error_fn=log_existing_repo) + out = f_flat_map(out, lambda _: self.get_repository(repo_id)) + out = f_flat_map(out, check_repo) + + return f_proxy(out) diff --git a/pubtools/pulplib/_impl/client/retry.py b/pubtools/pulplib/_impl/client/retry.py index e98d29c1..822da36c 100644 --- a/pubtools/pulplib/_impl/client/retry.py +++ b/pubtools/pulplib/_impl/client/retry.py @@ -29,8 +29,8 @@ def should_retry(self, attempt, future): exception = future.exception() if exception and getattr(exception, "response", None) is not None: - # if returned status code is 404, never retry on that - if exception.response.status_code == 404: + # if returned status code is 404 or 409, never retry on that + if exception.response.status_code in (404, 409): return False if exception and retry: diff --git a/pubtools/pulplib/_impl/fake/client.py b/pubtools/pulplib/_impl/fake/client.py index 7990e5ff..1c1c3fdb 100644 --- a/pubtools/pulplib/_impl/fake/client.py +++ b/pubtools/pulplib/_impl/fake/client.py @@ -651,3 +651,12 @@ def _do_sync(self, repo_id, sync_config): # pylint:disable = unused-argument self._state.sync_history.append(Sync(repo_f.result(), [task], sync_config)) return f_return([task]) + + def create_repository(self, repo): + with self._state.lock: + if repo.id not in [ + existing_repo.id for existing_repo in self._state.repositories + ]: + self._state.repositories.append(repo) + + return self.get_repository(repo.id) diff --git a/pubtools/pulplib/_impl/model/__init__.py b/pubtools/pulplib/_impl/model/__init__.py index d66d8277..796a1e14 100644 --- a/pubtools/pulplib/_impl/model/__init__.py +++ b/pubtools/pulplib/_impl/model/__init__.py @@ -9,6 +9,9 @@ FileSyncOptions, ContainerSyncOptions, YumSyncOptions, + Importer, + FileImporter, + YumImporter, ) from .unit import ( Unit, diff --git a/pubtools/pulplib/_impl/model/common.py b/pubtools/pulplib/_impl/model/common.py index 4472ea98..526e17ff 100644 --- a/pubtools/pulplib/_impl/model/common.py +++ b/pubtools/pulplib/_impl/model/common.py @@ -234,3 +234,15 @@ def _delete(self, resource_type, resource_id): delete_f = client._delete_resource(resource_type, resource_id) delete_f = f_map(delete_f, self.__detach) return f_proxy(delete_f) + + +def schemaless_init(cls, data): + # Construct and return an instance of (attrs-using) cls from + # pulp data, where data in pulp has no schema at all (and hence + # every field could possibly be missing). + kwargs = {} + for key in [fld.name for fld in attr.fields(cls)]: + if key in data: + kwargs[key] = data[key] + + return cls(**kwargs) diff --git a/pubtools/pulplib/_impl/model/repository/__init__.py b/pubtools/pulplib/_impl/model/repository/__init__.py index 7fa39a87..dbbce4a2 100644 --- a/pubtools/pulplib/_impl/model/repository/__init__.py +++ b/pubtools/pulplib/_impl/model/repository/__init__.py @@ -1,4 +1,4 @@ -from .base import Repository, PublishOptions, SyncOptions +from .base import Repository, PublishOptions, SyncOptions, Importer from .container import ContainerImageRepository, ContainerSyncOptions -from .yum import YumRepository, YumSyncOptions -from .file import FileRepository, FileSyncOptions +from .yum import YumRepository, YumSyncOptions, YumImporter +from .file import FileRepository, FileSyncOptions, FileImporter diff --git a/pubtools/pulplib/_impl/model/repository/base.py b/pubtools/pulplib/_impl/model/repository/base.py index 7c2a38b4..ca254ce8 100644 --- a/pubtools/pulplib/_impl/model/repository/base.py +++ b/pubtools/pulplib/_impl/model/repository/base.py @@ -8,10 +8,11 @@ from frozenlist2 import frozenlist from more_executors.futures import f_proxy, f_map, f_flat_map +from frozendict.core import frozendict # pylint: disable=no-name-in-module from .repo_lock import RepoLock from ..attr import pulp_attrib, PULP2_FIELD, PULP2_MUTABLE from ..common import PulpObject, Deletable, DetachedException -from ..convert import frozenlist_or_none_converter +from ..convert import frozenlist_or_none_converter, frozendict_or_none_converter from ..distributor import Distributor from ...criteria import Criteria, Matcher from ...schema import load_schema @@ -19,6 +20,7 @@ from ...hooks import pm from ...util import dict_put, lookup, ABSENT +from ..common import schemaless_init LOG = logging.getLogger("pubtools.pulplib") @@ -35,6 +37,44 @@ def decorate(klass): return decorate +@attr.s(kw_only=True, frozen=True) +class Importer(PulpObject): + """ + Importer is a pulp object that needs to be associated with repository + in order to successfully sync or upload content to it. + """ + + type_id = pulp_attrib(default=None, type=str, pulp_field="importer_type_id") + """ + Type id of the importer. + """ + config = pulp_attrib( + default=attr.Factory(frozendict), + type=frozendict, + converter=frozendict_or_none_converter, + pulp_field="config", + ) + """ + Configuration dictionary of the importer. + """ + + @classmethod + def _from_data(cls, data): + # Convert from raw list/dict as provided in Pulp responses into model. + if isinstance(data, list): + return cls._from_data(data[0]) if data else schemaless_init(cls, data) + + return schemaless_init(cls, data) + + def _to_data(self): + return [ + { + "importer_type_id": self.type_id, + "config": self.config, + } + ] + + @attr.s(kw_only=True, frozen=True) class PublishOptions(object): """Options controlling a repository @@ -249,7 +289,7 @@ class Repository(PulpObject, Deletable): default=attr.Factory(frozenlist), type=list, pulp_field="notes.signatures", - pulp_py_converter=lambda sigs: sigs.split(","), + pulp_py_converter=lambda sigs: sigs.split(",") if sigs else [], py_pulp_converter=",".join, converter=lambda keys: frozenlist([k.strip() for k in keys]), ) @@ -339,6 +379,19 @@ class Repository(PulpObject, Deletable): .. versionadded:: 2.37.0 """ + importer = pulp_attrib( + default=Importer(), + type=Importer, + pulp_field="importers", + pulp_py_converter=Importer._from_data, + py_pulp_converter=Importer._to_data, + ) + """ + An object of :class:`~pubtools.pulplib.Importer` that is associated with the repository. + + .. versionadded:: 2.39.0 + """ + @distributors.validator def _check_repo_id(self, _, value): # checks if distributor's repository id is same as the repository it diff --git a/pubtools/pulplib/_impl/model/repository/file.py b/pubtools/pulplib/_impl/model/repository/file.py index 630847ad..b5f46a49 100644 --- a/pubtools/pulplib/_impl/model/repository/file.py +++ b/pubtools/pulplib/_impl/model/repository/file.py @@ -4,7 +4,7 @@ from attr import validators from frozenlist2 import frozenlist -from .base import Repository, SyncOptions, repo_type +from .base import Repository, SyncOptions, repo_type, Importer from ...model.unit import FileUnit from ..attr import pulp_attrib from ... import compat_attr as attr @@ -13,6 +13,16 @@ LOG = logging.getLogger("pubtools.pulplib") +@attr.s(kw_only=True, frozen=True) +class FileImporter(Importer): + type_id = pulp_attrib( + default="iso_importer", type=str, pulp_field="importer_type_id" + ) + """ + Specific importer_type_id for File repositories. + """ + + @attr.s(kw_only=True, frozen=True) class FileSyncOptions(SyncOptions): """Options controlling a file repository @@ -49,6 +59,19 @@ class FileRepository(Repository): converter=frozenlist, ) + importer = pulp_attrib( + default=FileImporter(), + type=FileImporter, + pulp_field="importers", + pulp_py_converter=FileImporter._from_data, + py_pulp_converter=FileImporter._to_data, + ) + """ + An object of :class:`~pubtools.pulplib.FileImporter` that is associated with the repository. + + .. versionadded:: 2.39.0 + """ + def upload_file(self, file_obj, relative_url=None, **kwargs): """Upload a file to this repository. diff --git a/pubtools/pulplib/_impl/model/repository/yum.py b/pubtools/pulplib/_impl/model/repository/yum.py index 1516bc5c..32e50d53 100644 --- a/pubtools/pulplib/_impl/model/repository/yum.py +++ b/pubtools/pulplib/_impl/model/repository/yum.py @@ -3,7 +3,7 @@ from frozenlist2 import frozenlist from more_executors.futures import f_map, f_proxy, f_return, f_zip, f_flat_map -from .base import Repository, SyncOptions, repo_type +from .base import Repository, SyncOptions, repo_type, Importer from ..attr import pulp_attrib from ..common import DetachedException from ...model.unit import RpmUnit @@ -11,6 +11,16 @@ from ...criteria import Criteria, Matcher +@attr.s(kw_only=True, frozen=True) +class YumImporter(Importer): + type_id = pulp_attrib( + default="yum_importer", type=str, pulp_field="importer_type_id" + ) + """ + Specific importer_type_id for Yum repositories. + """ + + @attr.s(kw_only=True, frozen=True) class YumSyncOptions(SyncOptions): """Options controlling a yum repository @@ -103,6 +113,19 @@ class YumRepository(Repository): ) """Version of UBI config that should be used for population of this repository.""" + importer = pulp_attrib( + default=YumImporter(), + type=YumImporter, + pulp_field="importers", + pulp_py_converter=YumImporter._from_data, + py_pulp_converter=YumImporter._to_data, + ) + """ + An object of :class:`~pubtools.pulplib.YumImporter` that is associated with the repository. + + .. versionadded:: 2.39.0 + """ + def get_binary_repository(self): """Find and return the binary repository relating to this repository. diff --git a/pubtools/pulplib/_impl/model/unit/base.py b/pubtools/pulplib/_impl/model/unit/base.py index f3c4a8dc..502935c8 100644 --- a/pubtools/pulplib/_impl/model/unit/base.py +++ b/pubtools/pulplib/_impl/model/unit/base.py @@ -139,15 +139,3 @@ def _usermeta_from_kwargs(cls, **kwargs): ) return out - - -def schemaless_init(cls, data): - # Construct and return an instance of (attrs-using) cls from - # pulp data, where data in pulp has no schema at all (and hence - # every field could possibly be missing). - kwargs = {} - for key in [fld.name for fld in attr.fields(cls)]: - if key in data: - kwargs[key] = data[key] - - return cls(**kwargs) diff --git a/pubtools/pulplib/_impl/model/unit/erratum.py b/pubtools/pulplib/_impl/model/unit/erratum.py index 96eb57db..eebcfc3a 100644 --- a/pubtools/pulplib/_impl/model/unit/erratum.py +++ b/pubtools/pulplib/_impl/model/unit/erratum.py @@ -1,8 +1,9 @@ from frozenlist2 import frozenlist -from .base import Unit, PulpObject, unit_type, schemaless_init +from .base import Unit, PulpObject, unit_type from ..attr import pulp_attrib +from ..common import schemaless_init from ... import compat_attr as attr from ..validate import ( optional_bool, diff --git a/pubtools/pulplib/_impl/model/unit/modulemd.py b/pubtools/pulplib/_impl/model/unit/modulemd.py index 9a162b90..adc1bed3 100644 --- a/pubtools/pulplib/_impl/model/unit/modulemd.py +++ b/pubtools/pulplib/_impl/model/unit/modulemd.py @@ -1,8 +1,9 @@ import re -from .base import Unit, unit_type, schemaless_init +from .base import Unit, unit_type from ..attr import pulp_attrib +from ..common import schemaless_init from ... import compat_attr as attr from ..convert import ( frozenlist_or_none_converter, diff --git a/pubtools/pulplib/_impl/model/unit/rpm.py b/pubtools/pulplib/_impl/model/unit/rpm.py index 2fb9bbbd..f9800e8d 100644 --- a/pubtools/pulplib/_impl/model/unit/rpm.py +++ b/pubtools/pulplib/_impl/model/unit/rpm.py @@ -1,9 +1,10 @@ import datetime from pubtools.pulplib._impl.model.validate import optional_list_of -from .base import Unit, unit_type, schemaless_init +from .base import Unit, unit_type from ..attr import pulp_attrib +from ..common import schemaless_init from ... import compat_attr as attr from ..convert import ( frozenlist_or_none_converter, diff --git a/pubtools/pulplib/_impl/schema/repository.yaml b/pubtools/pulplib/_impl/schema/repository.yaml index 47f43d22..aca79362 100644 --- a/pubtools/pulplib/_impl/schema/repository.yaml +++ b/pubtools/pulplib/_impl/schema/repository.yaml @@ -69,7 +69,9 @@ properties: # Note that this field is not set by Pulp itself. Only certain tools # are expected to initialize this field when creating a repo. created: - type: string + anyOf: + - type: "null" + - type: string # example: 2019-06-05T11:56:50Z pattern: "^[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}:[0-9]{2}Z" @@ -100,7 +102,9 @@ properties: # Version of ubi config that should be used for population of this repository ubi_config_version: - type: string + anyOf: + - type: "null" + - type: string # Flag indicating whether the repository is visible in production instance # of download service. Stored as string. diff --git a/setup.py b/setup.py index 5ffb7554..0e83561c 100644 --- a/setup.py +++ b/setup.py @@ -21,7 +21,7 @@ def get_requirements(): setup( name="pubtools-pulplib", - version="2.38.1", + version="2.39.0", packages=find_packages(exclude=["tests"]), package_data={"pubtools.pulplib._impl.schema": ["*.yaml"]}, url="https://github.com/release-engineering/pubtools-pulplib", diff --git a/tests/fake/test_fake_create_repo.py b/tests/fake/test_fake_create_repo.py new file mode 100644 index 00000000..06363e69 --- /dev/null +++ b/tests/fake/test_fake_create_repo.py @@ -0,0 +1,16 @@ +from pubtools.pulplib import FakeController, Repository + + +def test_create_repository(): + """Client.create_repository() with fake client adds new repositories to controller.""" + controller = FakeController() + + client = controller.client + repo_1 = client.create_repository(Repository(id="repo1")) + repo_2 = client.create_repository(Repository(id="repo2")) + + # adding already existing repository has no effect + _ = client.create_repository(Repository(id="repo1")) + # The change should be reflected in the controller, + # with two repositories present + assert controller.repositories == [repo_1.result(), repo_2.result()] diff --git a/tests/repository/test_yum_repository.py b/tests/repository/test_yum_repository.py index 0c38cb7d..b082139a 100644 --- a/tests/repository/test_yum_repository.py +++ b/tests/repository/test_yum_repository.py @@ -1,6 +1,8 @@ import pytest +import logging +import requests -from pubtools.pulplib import Repository, YumRepository, DetachedException +from pubtools.pulplib import Repository, YumRepository, DetachedException, YumImporter def test_from_data_gives_yum_repository(): @@ -59,6 +61,24 @@ def test_from_data_skip_rsync_repodata(): assert repo.skip_rsync_repodata +def test_from_data_importer(): + repo = Repository.from_data( + { + "id": "my-repo", + "notes": {"_repo-type": "rpm-repo"}, + "importers": [ + { + "importer_type_id": "yum_importer", + "config": {"foo": "bar"}, + } + ], + } + ) + + assert repo.importer.type_id == "yum_importer" + assert repo.importer.config == {"foo": "bar"} + + def test_populate_attrs(): """test populate attributes are correctly parsed from repo notes""" repo = Repository.from_data( @@ -193,3 +213,169 @@ def test_related_repositories_detached_client(): with pytest.raises(DetachedException): repo_binary_test.get_binary_repository() + + +def test_create_repository(client, requests_mocker): + repo = YumRepository(id="yum_repo_new") + + # create request + requests_mocker.post( + "https://pulp.example.com/pulp/api/v2/repositories/", + json={}, + ) + + # repo search request + requests_mocker.post( + "https://pulp.example.com/pulp/api/v2/repositories/search/", + json=[ + { + "id": "yum_repo_new", + "notes": {"_repo-type": "rpm-repo"}, + "importers": [ + { + "importer_type_id": "yum_importer", + "config": {}, + } + ], + } + ], + ) + + out = client.create_repository(repo) + # check return value of create_repository() call + assert out.result() == repo + + hist = requests_mocker.request_history + # there should be exactly 2 requests sent - create and search + assert len(hist) == 2 + + create_query = hist[0].json() + # check id of repository sent in request body + assert create_query["id"] == "yum_repo_new" + # check importer data sent in request body that + # are automatically added for yum repository + assert create_query["importer_type_id"] == "yum_importer" + assert create_query["importer_config"] == {} + + # check the search request for created repo + search_query = hist[1].json() + assert search_query == { + "criteria": { + "skip": 0, + "limit": 2000, + "filters": {"id": {"$eq": "yum_repo_new"}}, + }, + "distributors": True, + "importers": True, + } + + +def test_create_repository_already_exists(client, requests_mocker, caplog): + repo = YumRepository(id="yum_repo_existing") + + requests_mocker.post( + "https://pulp.example.com/pulp/api/v2/repositories/", + status_code=409, + text="Conflict 409 status", + ) + + # repo search request + requests_mocker.post( + "https://pulp.example.com/pulp/api/v2/repositories/search/", + json=[ + { + "id": "yum_repo_existing", + "notes": {"_repo-type": "rpm-repo"}, + "importers": [ + { + "importer_type_id": "yum_importer", + "config": {}, + } + ], + } + ], + ) + + with caplog.at_level(logging.WARNING): + out = client.create_repository(repo) + # check return value of create_repository() call + assert out.result() == repo + + hist = requests_mocker.request_history + # there should be 2 request sent - attempt to create and search for repo, + # 409 status is never retried + assert len(hist) == 2 + + query = hist[0].json() + # check id of repository sent in request body + assert query["id"] == "yum_repo_existing" + # check importer data sent in request body that + # are automatically added for yum repository + assert query["importer_type_id"] == "yum_importer" + assert query["importer_config"] == {} + # check logged information about existing repository + assert "Repository yum_repo_existing already exists" in caplog.text + + # check the search request for existing repo + search_query = hist[1].json() + assert search_query == { + "criteria": { + "skip": 0, + "limit": 2000, + "filters": {"id": {"$eq": "yum_repo_existing"}}, + }, + "distributors": True, + "importers": True, + } + + +def test_create_repository_wrong_data(client, requests_mocker, caplog): + repo = YumRepository( + id="yum_repo_existing", importer=YumImporter(config={"new": "value"}) + ) + + requests_mocker.post( + "https://pulp.example.com/pulp/api/v2/repositories/", + status_code=409, + text="Conflict 409 status", + ) + + # repo search request + requests_mocker.post( + "https://pulp.example.com/pulp/api/v2/repositories/search/", + json=[ + { + "id": "yum_repo_existing", + "notes": {"_repo-type": "rpm-repo"}, + "importers": [ + { + "importer_type_id": "yum_importer", + "config": {"current": "value"}, + } + ], + } + ], + ) + with caplog.at_level(logging.WARNING): + with pytest.raises(AssertionError): + client.create_repository(repo).result() + + for text in ( + "Repository yum_repo_existing already exists", + "Repository yum_repo_existing contains wrong importer config", + "Repository yum_repo_existing exists on server and contains unexpected values", + ): + assert text in caplog.text + + +def test_create_repository_raises_exception(client, requests_mocker): + repo = YumRepository(id="yum_repo") + + requests_mocker.post( + "https://pulp.example.com/pulp/api/v2/repositories/", + status_code=400, + text="Client error 400", + ) + + with pytest.raises(requests.exceptions.HTTPError): + client.create_repository(repo).result()