Skip to content

Commit 085e9b0

Browse files
committed
Add content reader to decouple NFS file reads [RHELDST-26339]
Reading content of the push items strongly depends on their presence as file on NFS or locally-mounted filesystem. However, with advent of new push item sources, it likely that the push items might not be stored as a file or be locally available. Hence, this PR adds an `opener` to the pushitems that should fetch the corresponding bit stream which will be accessible from `content()`. `opener` should be defined in the `Source` while creating the pushitems to get the bits from the specific content source. PushItem is not bound to have an `opener` and `content()` will return the bits as file object only when the `opener` is defined. This adds a provision to transfer the responsibility of fetching the content from the consumer that is using the content/pushitems to the `Source` and corresponding `pushitem` here, abstracting the reading mechanism from the user and provide flexibility to fetch from different locations/protocols.
1 parent 2a5062a commit 085e9b0

38 files changed

+378
-2
lines changed

src/pushsource/_impl/list_cmd.py

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,10 @@
3636

3737
LOG = logging.getLogger("pushsource-ls")
3838

39+
EXCLUDED_ATTRIBUTES = [
40+
"opener",
41+
]
42+
3943

4044
class ItemDumper(yaml.SafeDumper):
4145
# Custom dumper adding support for any types appearing on pushitems
@@ -77,7 +81,13 @@ def format_python_black(item):
7781

7882

7983
def format_yaml(item):
80-
data = {type(item).__name__: attr.asdict(item, recurse=True)}
84+
data = {
85+
type(item).__name__: attr.asdict(
86+
item,
87+
recurse=True,
88+
filter=lambda attribute, _: attribute.name not in EXCLUDED_ATTRIBUTES,
89+
)
90+
}
8191
return yaml.dump([data], Dumper=ItemDumper)
8292

8393

src/pushsource/_impl/model/base.py

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
optional,
1717
optional_str,
1818
)
19+
from ..reader import PushItemReader
1920

2021

2122
LOG = logging.getLogger("pushsource")
@@ -178,6 +179,14 @@ def _default_build_info(self):
178179
doesn't enforce this.
179180
"""
180181

182+
opener = attr.ib(type=callable, default=None, repr=False)
183+
"""The opener, when given a push item, should return a file-like object
184+
suitable for reading this item's bytes. The object can be retrieved from
185+
:meth:`~pushsource.PushItem.content()` method.
186+
187+
.. versionadded:: 2.51.0
188+
"""
189+
181190
def with_checksums(self):
182191
"""Return a copy of this push item with checksums present.
183192
@@ -248,3 +257,36 @@ def with_checksums(self):
248257
updated_sums[attribute] = hasher.hexdigest()
249258

250259
return attr.evolve(self, **updated_sums)
260+
261+
def content(self):
262+
"""Returns a read-only, non-seekable content of this push item.
263+
264+
For push items representing a single file, content will obtain a stream
265+
for reading that file's bytes.
266+
For example, ``RpmPushItem.content`` can be used to read the content of
267+
an RPM; ``VMIPushItem.content`` can be used to read the content of a VMI
268+
and so on.
269+
270+
Not every type of push item can be read in this way.
271+
For example, a single ``ContainerImagePushItem`` may represent any number
272+
of artifacts making up a container image, to be accessed from a container
273+
image registry in the usual way - not using this method. Other types of
274+
push items such as ``ErratumPushItem`` may be understood as metadata-only
275+
items and do not themselves have any content. For items such as these,
276+
this method will return None.
277+
278+
279+
Returns:
280+
:class:`~io.BufferedReader`
281+
A non-seekable object of the push item content
282+
``None``
283+
If the :attr:`~pushsource.PushItem.opener` in the pushitem is not defined to read
284+
the content.
285+
286+
.. versionadded:: 2.51.0
287+
"""
288+
return (
289+
PushItemReader(self.opener(self), self.src or self.name)
290+
if self.opener
291+
else None
292+
)

src/pushsource/_impl/model/comps.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
from .base import PushItem
22
from .. import compat_attr as attr
3+
from ..utils.openers import open_src_local
34

45

56
@attr.s()
@@ -12,3 +13,11 @@ class CompsXmlPushItem(PushItem):
1213
1314
This library does not verify that the referenced file is valid.
1415
"""
16+
17+
opener = attr.ib(type=callable, default=open_src_local, repr=False)
18+
"""Identical to :attr:`~pushsource.PushItem.opener`.
19+
20+
This defaults to reading content as file from :attr:`~pushsource.PushItem.src`
21+
22+
.. versionadded:: 2.51.0
23+
"""

src/pushsource/_impl/model/directory.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
from .base import PushItem
22
from .. import compat_attr as attr
3+
from ..utils.openers import open_src_local
34

45

56
@attr.s()
@@ -9,3 +10,11 @@ class DirectoryPushItem(PushItem):
910
On a directory push item, the src attribute contains the full path to a directory tree.
1011
It should generally be interpreted as a request to recursively publish that entire directory
1112
tree as-is."""
13+
14+
opener = attr.ib(type=callable, default=open_src_local, repr=False)
15+
"""Identical to :attr:`~pushsource.PushItem.opener`.
16+
17+
This defaults to reading content as file from :attr:`~pushsource.PushItem.src`
18+
19+
.. versionadded:: 2.51.0
20+
"""

src/pushsource/_impl/model/file.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
from .base import PushItem
22
from .. import compat_attr as attr
33
from .conv import optional_str, convert_maybe
4+
from ..utils.openers import open_src_local
45

56

67
@attr.s()
@@ -52,3 +53,11 @@ def _check_order(self, _, value):
5253
# - This check will also filter out NaN.
5354
if not -99999 <= value <= 99999:
5455
raise ValueError("display_order must be within range -99999 .. 99999")
56+
57+
opener = attr.ib(type=callable, default=open_src_local, repr=False)
58+
"""Identical to :attr:`~pushsource.PushItem.opener`.
59+
60+
This defaults to reading content as file from :attr:`~pushsource.PushItem.src`
61+
62+
.. versionadded:: 2.51.0
63+
"""

src/pushsource/_impl/model/modulemd.py

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
from .base import PushItem
22
from .. import compat_attr as attr
3+
from ..utils.openers import open_src_local
34

45

56
@attr.s()
@@ -14,6 +15,14 @@ class ModuleMdPushItem(PushItem):
1415
modulemd stream.
1516
"""
1617

18+
opener = attr.ib(type=callable, default=open_src_local, repr=False)
19+
"""Identical to :attr:`~pushsource.PushItem.opener`.
20+
21+
This defaults to reading content as file from :attr:`~pushsource.PushItem.src`
22+
23+
.. versionadded:: 2.51.0
24+
"""
25+
1726

1827
@attr.s()
1928
class ModuleMdSourcePushItem(PushItem):
@@ -25,3 +34,11 @@ class ModuleMdSourcePushItem(PushItem):
2534
This library does not verify that the referenced file is a valid
2635
modulemd source document.
2736
"""
37+
38+
opener = attr.ib(type=callable, default=open_src_local, repr=False)
39+
"""Identical to :attr:`~pushsource.PushItem.opener`.
40+
41+
This defaults to reading content as file from :attr:`~pushsource.PushItem.src`
42+
43+
.. versionadded:: 2.51.0
44+
"""

src/pushsource/_impl/model/productid.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
from .base import PushItem
88
from .conv import convert_maybe, sloppylist
99
from .. import compat_attr as attr
10+
from ..utils.openers import open_src_local
1011

1112

1213
# Red Hat OID namespace is "1.3.6.1.4.1.2312.9",
@@ -114,3 +115,11 @@ def _load_products(self, path):
114115
)
115116
result.append(product)
116117
return result
118+
119+
opener = attr.ib(type=callable, default=open_src_local, repr=False)
120+
"""Identical to :attr:`~pushsource.PushItem.opener`.
121+
122+
This defaults to reading content as file from :attr:`~pushsource.PushItem.src`
123+
124+
.. versionadded:: 2.51.0
125+
"""

src/pushsource/_impl/model/rpm.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
from .base import PushItem
22
from .conv import optional_str
33
from .. import compat_attr as attr
4+
from ..utils.openers import open_src_local
45

56

67
@attr.s()
@@ -32,3 +33,11 @@ class RpmPushItem(PushItem):
3233
.. _ghc-8.4-820200708061905.9edba152: https://koji.fedoraproject.org/koji/buildinfo?buildID=1767200
3334
.. _ghc-8.4.4-74.module_el8+12161+cf1bd7f2: https://koji.fedoraproject.org/koji/buildinfo?buildID=1767130
3435
"""
36+
37+
opener = attr.ib(type=callable, default=open_src_local, repr=False)
38+
"""Identical to :attr:`~pushsource.PushItem.opener`.
39+
40+
This defaults to reading content as file from :attr:`~pushsource.PushItem.src`
41+
42+
.. versionadded:: 2.51.0
43+
"""

src/pushsource/_impl/reader.py

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
from io import BufferedReader, SEEK_SET, UnsupportedOperation
2+
3+
4+
class PushItemReader(BufferedReader):
5+
# Internal class to ensure that the file-like content object returned by
6+
# the push items are read-only and non-seekable with a name attribute.
7+
def __init__(self, raw, name, **kwargs):
8+
super(PushItemReader, self).__init__(raw, **kwargs)
9+
self._name = name
10+
11+
@property
12+
def name(self):
13+
return self._name
14+
15+
def seekable(self):
16+
return False
17+
18+
def seek(self, offset, whence=SEEK_SET):
19+
raise UnsupportedOperation(f"Seek unsupported while reading {self.name}")

src/pushsource/_impl/utils/openers.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
def open_src_local(item):
2+
# default opener for the push items
3+
# assumes that the item's 'src' points to the
4+
# locally-accessible file
5+
return open(item.src, "rb")

tests/baseline/cases/direct-cgw.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ items:
1515
- dest
1616
md5sum: null
1717
name: cgw.yaml
18+
opener: null
1819
origin: direct
1920
sha256sum: null
2021
signing_key: null

tests/baseline/cases/direct-comps.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ items:
1414
dest: []
1515
md5sum: null
1616
name: mycomps.xml
17+
opener: open_src_local
1718
origin: direct
1819
sha256sum: null
1920
signing_key: null

tests/baseline/cases/direct-dir.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ items:
1515
- /destdir
1616
md5sum: null
1717
name: srcdir
18+
opener: open_src_local
1819
origin: direct
1920
sha256sum: null
2021
signing_key: null

tests/baseline/cases/direct-file.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ items:
1818
display_order: null
1919
md5sum: null
2020
name: custom-filename
21+
opener: open_src_local
2122
origin: direct
2223
sha256sum: null
2324
signing_key: null

tests/baseline/cases/direct-modulemd-src.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ items:
1414
dest: []
1515
md5sum: null
1616
name: modules.src.txt
17+
opener: open_src_local
1718
origin: direct
1819
sha256sum: null
1920
signing_key: null

tests/baseline/cases/direct-modulemd.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ items:
1414
dest: []
1515
md5sum: null
1616
name: my-best-module
17+
opener: open_src_local
1718
origin: direct
1819
sha256sum: null
1920
signing_key: null

tests/baseline/cases/direct-productid.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ items:
1717
- repo3
1818
md5sum: null
1919
name: some-cert
20+
opener: open_src_local
2021
origin: direct
2122
products:
2223
- architecture:

tests/baseline/cases/direct-rpm.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ items:
1515
md5sum: null
1616
module_build: null
1717
name: test.rpm
18+
opener: open_src_local
1819
origin: custom-origin
1920
sha256sum: null
2021
signing_key: A1B2C3

0 commit comments

Comments
 (0)