Skip to content

Commit 4125b70

Browse files
committed
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.
1 parent dfe6dd8 commit 4125b70

22 files changed

+295
-26
lines changed

CHANGELOG.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
77

88
## [Unreleased]
99

10-
- n/a
10+
- Added `Client.create_repository()` method
1111

1212
## [2.38.1] - 2023-12-14
1313

docs/api/files.rst

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@ Repository
1414
.. autoclass:: pubtools.pulplib.FileSyncOptions()
1515
:members:
1616

17+
.. autoclass:: pubtools.pulplib.IsoImporter()
18+
:members:
1719

1820
Units
1921
-----

docs/api/pulpcore.rst

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,8 @@ Repository
2727
.. autoclass:: pubtools.pulplib.SyncOptions()
2828
:members:
2929

30+
.. autoclass:: pubtools.pulplib.Importer()
31+
:members:
3032

3133
Units
3234
-----

docs/api/yum.rst

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@ Repository
1414
.. autoclass:: pubtools.pulplib.YumSyncOptions()
1515
:members:
1616

17+
.. autoclass:: pubtools.pulplib.YumImporter()
18+
:members:
1719

1820
Units: RPM
1921
----------

pubtools/pulplib/__init__.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,5 +31,8 @@
3131
Task,
3232
MaintenanceReport,
3333
MaintenanceEntry,
34+
Importer,
35+
YumImporter,
36+
IsoImporter,
3437
)
3538
from ._impl.fake import FakeController

pubtools/pulplib/_impl/client/client.py

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1054,3 +1054,46 @@ def _do_sync(self, repo_id, sync_options):
10541054
return self._task_executor.submit(
10551055
self._do_request, method="POST", url=url, json=body
10561056
)
1057+
1058+
def create_repository(self, repo):
1059+
"""Create a repository with initial data provided in the
1060+
argument. Importer for repository is automatically associated
1061+
if available. If repository already exists, only warning is logged.
1062+
1063+
Args:
1064+
repo (:class:`~pubtools.pulplib.Repository`)
1065+
A repository object used for creation.
1066+
1067+
Returns:
1068+
Future
1069+
A future which is resolved with a value of ``None`` once the
1070+
repository has been created.
1071+
1072+
.. versionadded:: 2.39.0
1073+
"""
1074+
url = os.path.join(self._url, "pulp/api/v2/repositories/")
1075+
1076+
body = repo._to_data()
1077+
repo_id = body["id"]
1078+
1079+
importer = body.pop("importers", [])
1080+
body["importer_type_id"] = importer[0]["importer_type_id"] if importer else None
1081+
body["importer_config"] = importer[0]["config"] if importer else None
1082+
1083+
def log_existing_repo(exception):
1084+
if (
1085+
getattr(exception, "response", None) is not None
1086+
and exception.response.status_code == 409
1087+
):
1088+
LOG.warning("Repository %s already exists", repo_id)
1089+
return None
1090+
1091+
raise exception
1092+
1093+
LOG.debug("Creating repository %s", repo_id)
1094+
out = self._request_executor.submit(
1095+
self._do_request, method="POST", url=url, json=body
1096+
)
1097+
1098+
out = f_map(out, error_fn=log_existing_repo)
1099+
return f_map(out, lambda _: None)

pubtools/pulplib/_impl/client/retry.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,8 +29,8 @@ def should_retry(self, attempt, future):
2929

3030
exception = future.exception()
3131
if exception and getattr(exception, "response", None) is not None:
32-
# if returned status code is 404, never retry on that
33-
if exception.response.status_code == 404:
32+
# if returned status code is 404 or 409, never retry on that
33+
if exception.response.status_code in (404, 409):
3434
return False
3535

3636
if exception and retry:

pubtools/pulplib/_impl/fake/client.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -651,3 +651,12 @@ def _do_sync(self, repo_id, sync_config): # pylint:disable = unused-argument
651651
self._state.sync_history.append(Sync(repo_f.result(), [task], sync_config))
652652

653653
return f_return([task])
654+
655+
def create_repository(self, repo):
656+
with self._state.lock:
657+
if repo.id not in [
658+
existing_repo.id for existing_repo in self._state.repositories
659+
]:
660+
self._state.repositories.append(repo)
661+
662+
return f_return(None)

pubtools/pulplib/_impl/model/__init__.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,9 @@
99
FileSyncOptions,
1010
ContainerSyncOptions,
1111
YumSyncOptions,
12+
Importer,
13+
IsoImporter,
14+
YumImporter,
1215
)
1316
from .unit import (
1417
Unit,

pubtools/pulplib/_impl/model/common.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -234,3 +234,15 @@ def _delete(self, resource_type, resource_id):
234234
delete_f = client._delete_resource(resource_type, resource_id)
235235
delete_f = f_map(delete_f, self.__detach)
236236
return f_proxy(delete_f)
237+
238+
239+
def schemaless_init(cls, data):
240+
# Construct and return an instance of (attrs-using) cls from
241+
# pulp data, where data in pulp has no schema at all (and hence
242+
# every field could possibly be missing).
243+
kwargs = {}
244+
for key in [fld.name for fld in attr.fields(cls)]:
245+
if key in data:
246+
kwargs[key] = data[key]
247+
248+
return cls(**kwargs)
Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
from .base import Repository, PublishOptions, SyncOptions
1+
from .base import Repository, PublishOptions, SyncOptions, Importer
22
from .container import ContainerImageRepository, ContainerSyncOptions
3-
from .yum import YumRepository, YumSyncOptions
4-
from .file import FileRepository, FileSyncOptions
3+
from .yum import YumRepository, YumSyncOptions, YumImporter
4+
from .file import FileRepository, FileSyncOptions, IsoImporter

pubtools/pulplib/_impl/model/repository/base.py

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
from ...hooks import pm
2020
from ...util import dict_put, lookup, ABSENT
2121

22+
from ..common import schemaless_init
2223

2324
LOG = logging.getLogger("pubtools.pulplib")
2425

@@ -35,6 +36,39 @@ def decorate(klass):
3536
return decorate
3637

3738

39+
@attr.s(kw_only=True, frozen=True)
40+
class Importer(PulpObject):
41+
"""
42+
Importer is a pulp object that needs to be associated with repository
43+
in order to successfully sync or upload content to it.
44+
"""
45+
46+
type_id = pulp_attrib(default=None, type=str, pulp_field="importer_type_id")
47+
"""
48+
Type id of the importer.
49+
"""
50+
config = pulp_attrib(default=None, type=dict, pulp_field="config")
51+
"""
52+
Configuration dictionary of the importer.
53+
"""
54+
55+
@classmethod
56+
def _from_data(cls, data):
57+
# Convert from raw list/dict as provided in Pulp responses into model.
58+
if isinstance(data, list):
59+
return cls._from_data(data[0]) if data else schemaless_init(cls, data)
60+
61+
return schemaless_init(cls, data)
62+
63+
def _to_data(self):
64+
return [
65+
{
66+
"importer_type_id": self.type_id,
67+
"config": self.config,
68+
}
69+
]
70+
71+
3872
@attr.s(kw_only=True, frozen=True)
3973
class PublishOptions(object):
4074
"""Options controlling a repository
@@ -339,6 +373,19 @@ class Repository(PulpObject, Deletable):
339373
.. versionadded:: 2.37.0
340374
"""
341375

376+
importer = pulp_attrib(
377+
default=Importer(),
378+
type=Importer,
379+
pulp_field="importers",
380+
pulp_py_converter=Importer._from_data,
381+
py_pulp_converter=Importer._to_data,
382+
)
383+
"""
384+
An object of :class:`~pubtools.pulplib.Importer` that is associated with the repository.
385+
386+
.. versionadded:: 2.39.0
387+
"""
388+
342389
@distributors.validator
343390
def _check_repo_id(self, _, value):
344391
# checks if distributor's repository id is same as the repository it

pubtools/pulplib/_impl/model/repository/file.py

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
from attr import validators
55
from frozenlist2 import frozenlist
66

7-
from .base import Repository, SyncOptions, repo_type
7+
from .base import Repository, SyncOptions, repo_type, Importer
88
from ...model.unit import FileUnit
99
from ..attr import pulp_attrib
1010
from ... import compat_attr as attr
@@ -13,6 +13,16 @@
1313
LOG = logging.getLogger("pubtools.pulplib")
1414

1515

16+
@attr.s(kw_only=True, frozen=True)
17+
class IsoImporter(Importer):
18+
type_id = pulp_attrib(
19+
default="iso_importer", type=str, pulp_field="importer_type_id"
20+
)
21+
"""
22+
Specific importer_type_id for File repositories.
23+
"""
24+
25+
1626
@attr.s(kw_only=True, frozen=True)
1727
class FileSyncOptions(SyncOptions):
1828
"""Options controlling a file repository
@@ -49,6 +59,19 @@ class FileRepository(Repository):
4959
converter=frozenlist,
5060
)
5161

62+
importer = pulp_attrib(
63+
default=IsoImporter(),
64+
type=IsoImporter,
65+
pulp_field="importers",
66+
pulp_py_converter=IsoImporter._from_data,
67+
py_pulp_converter=IsoImporter._to_data,
68+
)
69+
"""
70+
An object of :class:`~pubtools.pulplib.IsoImporter` that is associated with the repository.
71+
72+
.. versionadded:: 2.39.0
73+
"""
74+
5275
def upload_file(self, file_obj, relative_url=None, **kwargs):
5376
"""Upload a file to this repository.
5477

pubtools/pulplib/_impl/model/repository/yum.py

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,14 +3,24 @@
33
from frozenlist2 import frozenlist
44

55
from more_executors.futures import f_map, f_proxy, f_return, f_zip, f_flat_map
6-
from .base import Repository, SyncOptions, repo_type
6+
from .base import Repository, SyncOptions, repo_type, Importer
77
from ..attr import pulp_attrib
88
from ..common import DetachedException
99
from ...model.unit import RpmUnit
1010
from ... import compat_attr as attr, comps
1111
from ...criteria import Criteria, Matcher
1212

1313

14+
@attr.s(kw_only=True, frozen=True)
15+
class YumImporter(Importer):
16+
type_id = pulp_attrib(
17+
default="yum_importer", type=str, pulp_field="importer_type_id"
18+
)
19+
"""
20+
Specific importer_type_id for Yum repositories.
21+
"""
22+
23+
1424
@attr.s(kw_only=True, frozen=True)
1525
class YumSyncOptions(SyncOptions):
1626
"""Options controlling a yum repository
@@ -103,6 +113,19 @@ class YumRepository(Repository):
103113
)
104114
"""Version of UBI config that should be used for population of this repository."""
105115

116+
importer = pulp_attrib(
117+
default=YumImporter(),
118+
type=YumImporter,
119+
pulp_field="importers",
120+
pulp_py_converter=YumImporter._from_data,
121+
py_pulp_converter=YumImporter._to_data,
122+
)
123+
"""
124+
An object of :class:`~pubtools.pulplib.YumImporter` that is associated with the repository.
125+
126+
.. versionadded:: 2.39.0
127+
"""
128+
106129
def get_binary_repository(self):
107130
"""Find and return the binary repository relating to this repository.
108131

pubtools/pulplib/_impl/model/unit/base.py

Lines changed: 0 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -139,15 +139,3 @@ def _usermeta_from_kwargs(cls, **kwargs):
139139
)
140140

141141
return out
142-
143-
144-
def schemaless_init(cls, data):
145-
# Construct and return an instance of (attrs-using) cls from
146-
# pulp data, where data in pulp has no schema at all (and hence
147-
# every field could possibly be missing).
148-
kwargs = {}
149-
for key in [fld.name for fld in attr.fields(cls)]:
150-
if key in data:
151-
kwargs[key] = data[key]
152-
153-
return cls(**kwargs)

pubtools/pulplib/_impl/model/unit/erratum.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
from frozenlist2 import frozenlist
22

3-
from .base import Unit, PulpObject, unit_type, schemaless_init
3+
from .base import Unit, PulpObject, unit_type
44

55
from ..attr import pulp_attrib
6+
from ..common import schemaless_init
67
from ... import compat_attr as attr
78
from ..validate import (
89
optional_bool,

pubtools/pulplib/_impl/model/unit/modulemd.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
import re
22

3-
from .base import Unit, unit_type, schemaless_init
3+
from .base import Unit, unit_type
44

55
from ..attr import pulp_attrib
6+
from ..common import schemaless_init
67
from ... import compat_attr as attr
78
from ..convert import (
89
frozenlist_or_none_converter,

pubtools/pulplib/_impl/model/unit/rpm.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
11
import datetime
22

33
from pubtools.pulplib._impl.model.validate import optional_list_of
4-
from .base import Unit, unit_type, schemaless_init
4+
from .base import Unit, unit_type
55

66
from ..attr import pulp_attrib
7+
from ..common import schemaless_init
78
from ... import compat_attr as attr
89
from ..convert import (
910
frozenlist_or_none_converter,

pubtools/pulplib/_impl/schema/repository.yaml

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,9 @@ properties:
6969
# Note that this field is not set by Pulp itself. Only certain tools
7070
# are expected to initialize this field when creating a repo.
7171
created:
72-
type: string
72+
anyOf:
73+
- type: "null"
74+
- type: string
7375
# example: 2019-06-05T11:56:50Z
7476
pattern: "^[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}:[0-9]{2}Z"
7577

@@ -100,7 +102,9 @@ properties:
100102

101103
# Version of ubi config that should be used for population of this repository
102104
ubi_config_version:
103-
type: string
105+
anyOf:
106+
- type: "null"
107+
- type: string
104108

105109
# Flag indicating whether the repository is visible in production instance
106110
# of download service. Stored as string.

setup.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ def get_requirements():
2121

2222
setup(
2323
name="pubtools-pulplib",
24-
version="2.38.1",
24+
version="2.39.0",
2525
packages=find_packages(exclude=["tests"]),
2626
package_data={"pubtools.pulplib._impl.schema": ["*.yaml"]},
2727
url="https://github.com/release-engineering/pubtools-pulplib",

0 commit comments

Comments
 (0)