We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
Make zchunk updateinfo a per-repo option
When I added support for zchunked updateinfo.xml in #2904, I missed the fact that my patch was creating zchunked updateinfo.xml for _every_ repository.  I don't think we want to be creating zchunk metadata for EPEL and Fedora 29 (and, apparently it's breaking some third-party mirroring tools, see https://bugzilla.redhat.com/show_bug.cgi?id=1721359), so we need to add a way of making the zchunked updateinfo.xml a per-repo option.

I'm happy to do the work on this, but I'm not sure where and how it should be implemented.


--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `bodhi/server/metadata.py`
Content:
```
1 # Copyright 2007-2019 Red Hat, Inc. and others.
2 #
3 # This file is part of Bodhi.
4 #
5 # This program is free software; you can redistribute it and/or modify
6 # it under the terms of the GNU General Public License as published by
7 # the Free Software Foundation; either version 2 of the License, or
8 # (at your option) any later version.
9 #
10 # This program is distributed in the hope that it will be useful,
11 # but WITHOUT ANY WARRANTY; without even the implied warranty of
12 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13 # GNU General Public License for more details.
14 #
15 # You should have received a copy of the GNU General Public License along
16 # with this program; if not, write to the Free Software Foundation, Inc.,
17 # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
18 """Create metadata files when composing repositories."""
19 from datetime import datetime
20 import logging
21 import os
22 import shelve
23 import shutil
24 import tempfile
25 
26 import createrepo_c as cr
27 
28 from bodhi.server import util
29 from bodhi.server.buildsys import get_session
30 from bodhi.server.config import config
31 from bodhi.server.models import Build, UpdateStatus, UpdateRequest, UpdateSuggestion
32 
33 
34 __version__ = '2.0'
35 log = logging.getLogger(__name__)
36 
37 
38 def insert_in_repo(comp_type, repodata, filetype, extension, source):
39     """
40     Inject a file into the repodata with the help of createrepo_c.
41 
42     Args:
43         comp_type (int): createrepo_c compression type indication.
44         repodata (str): The path to the repo where the metadata will be inserted.
45         filetype (str): What type of metadata will be inserted by createrepo_c.
46             This does allow any string to be inserted (custom types). There are some
47             types which are used with dnf repos as primary, updateinfo, comps, filelist etc.
48         extension (str): The file extension (xml, sqlite).
49         source (str): A file path. File holds the dump of metadata until
50             copied to the repodata folder.
51     """
52     log.info('Inserting %s.%s into %s', filetype, extension, repodata)
53     target_fname = os.path.join(repodata, '%s.%s' % (filetype, extension))
54     shutil.copyfile(source, target_fname)
55     repomd_xml = os.path.join(repodata, 'repomd.xml')
56     repomd = cr.Repomd(repomd_xml)
57     add_list = [(filetype, comp_type)]
58     if hasattr(cr, 'ZCK_COMPRESSION') and comp_type != cr.ZCK_COMPRESSION:
59         add_list.append((filetype + "_zck", cr.ZCK_COMPRESSION))
60     for (ft, ct) in add_list:
61         # create a new record for our repomd.xml
62         rec = cr.RepomdRecord(ft, target_fname)
63         # compress our metadata file with the comp_type
64         rec_comp = rec.compress_and_fill(cr.SHA256, ct)
65         # add hash to the compressed metadata file
66         rec_comp.rename_file()
67         # set type of metadata
68         rec_comp.type = ft
69         # insert metadata about our metadata in repomd.xml
70         repomd.set_record(rec_comp)
71     with open(repomd_xml, 'w') as repomd_file:
72         repomd_file.write(repomd.xml_dump())
73     os.unlink(target_fname)
74 
75 
76 def modifyrepo(comp_type, compose_path, filetype, extension, source):
77     """
78     Inject a file into the repodata for each architecture with the help of createrepo_c.
79 
80     Args:
81         compose_path (str): The path to the compose where the metadata will be inserted.
82         filetype (str): What type of metadata will be inserted by createrepo_c.
83             This does allow any string to be inserted (custom types). There are some
84             types which are used with dnf repos as primary, updateinfo, comps, filelist etc.
85         extension (str): The file extension (xml, sqlite).
86         source (str): A file path. File holds the dump of metadata until
87             copied to the repodata folder.
88     """
89     repo_path = os.path.join(compose_path, 'compose', 'Everything')
90     for arch in os.listdir(repo_path):
91         if arch == 'source':
92             repodata = os.path.join(repo_path, arch, 'tree', 'repodata')
93         else:
94             repodata = os.path.join(repo_path, arch, 'os', 'repodata')
95         insert_in_repo(comp_type, repodata, filetype, extension, source)
96 
97 
98 class UpdateInfoMetadata(object):
99     """
100     This class represents the updateinfo.xml yum metadata.
101 
102     It is generated during push time by the bodhi composer based on koji tags
103     and is injected into the yum repodata using the `modifyrepo_c` tool,
104     which is included in the `createrepo_c` package.
105     """
106 
107     def __init__(self, release, request, db, composedir, close_shelf=True):
108         """
109         Initialize the UpdateInfoMetadata object.
110 
111         Args:
112             release (bodhi.server.models.Release): The Release that is being composed.
113             request (bodhi.server.models.UpdateRequest): The Request that is being composed.
114             db (): A database session to be used for queries.
115             composedir (str): A path to the composedir.
116             close_shelf (bool): Whether to close the shelve, which is used to cache updateinfo
117                 between composes.
118         """
119         self.request = request
120         if request is UpdateRequest.stable:
121             self.tag = release.stable_tag
122         else:
123             self.tag = release.testing_tag
124 
125         self.db = db
126         self.updates = set()
127         self.builds = {}
128         self._from = config.get('bodhi_email')
129         if config.get('cache_dir'):
130             self.shelf = shelve.open(os.path.join(config.get('cache_dir'), '%s.shelve' % self.tag))
131         else:
132             # If we have no cache dir, let's at least cache in-memory.
133             self.shelf = {}
134             close_shelf = False
135         self._fetch_updates()
136 
137         self.uinfo = cr.UpdateInfo()
138 
139         self.comp_type = cr.XZ
140 
141         if release.id_prefix == 'FEDORA-EPEL':
142             # FIXME: I'm not sure which versions of RHEL support xz metadata
143             # compression, so use the lowest common denominator for now.
144             self.comp_type = cr.BZ2
145 
146         self.uinfo = cr.UpdateInfo()
147         for update in self.updates:
148             self.add_update(update)
149 
150         if close_shelf:
151             self.shelf.close()
152 
153     def _fetch_updates(self):
154         """Based on our given koji tag, populate a list of Update objects."""
155         log.debug("Fetching builds tagged with '%s'" % self.tag)
156         kojiBuilds = get_session().listTagged(self.tag, latest=True)
157         nonexistent = []
158         log.debug("%d builds found" % len(kojiBuilds))
159         for build in kojiBuilds:
160             self.builds[build['nvr']] = build
161             build_obj = self.db.query(Build).filter_by(nvr=str(build['nvr'])).first()
162             if build_obj:
163                 if build_obj.update:
164                     self.updates.add(build_obj.update)
165                 else:
166                     log.warning('%s does not have a corresponding update' % build['nvr'])
167             else:
168                 nonexistent.append(build['nvr'])
169         if nonexistent:
170             log.warning("Couldn't find the following koji builds tagged as "
171                         "%s in bodhi: %s" % (self.tag, nonexistent))
172 
173     def get_rpms(self, koji, nvr):
174         """
175         Retrieve the given RPM nvr from the cache if available, or from Koji if not available.
176 
177         Args:
178             koji (koji.ClientSession): An initialized Koji client.
179             nvr (str): The nvr for which you wish to retrieve Koji data.
180         Returns:
181             list: A list of dictionaries describing all the subpackages that are part of the given
182                 nvr.
183         """
184         if str(nvr) in self.shelf:
185             return self.shelf[str(nvr)]
186 
187         if nvr in self.builds:
188             buildid = self.builds[nvr]['id']
189         else:
190             buildid = koji.getBuild(nvr)['id']
191 
192         rpms = koji.listBuildRPMs(buildid)
193         self.shelf[str(nvr)] = rpms
194         return rpms
195 
196     def add_update(self, update):
197         """
198         Generate the extended metadata for a given update, adding it to self.uinfo.
199 
200         Args:
201             update (bodhi.server.models.Update): The Update to be added to self.uinfo.
202         """
203         rec = cr.UpdateRecord()
204         rec.version = __version__
205         rec.fromstr = config.get('bodhi_email')
206         rec.status = update.status.value
207         rec.type = update.type.value
208         rec.id = update.alias.encode('utf-8')
209         rec.title = update.title.encode('utf-8')
210         rec.severity = util.severity_updateinfo_str(update.severity.value)
211         rec.summary = ('%s %s update' % (update.get_title(),
212                                          update.type.value)).encode('utf-8')
213         rec.description = update.notes.encode('utf-8')
214         rec.release = update.release.long_name.encode('utf-8')
215         rec.rights = config.get('updateinfo_rights')
216 
217         if update.date_pushed:
218             rec.issued_date = update.date_pushed
219         else:
220             # Sometimes we only set the date_pushed after it's pushed out, however,
221             # it seems that Satellite does not like update entries without issued_date.
222             # Since we know that we are pushing it now, and the next push will get the data
223             # correctly, let's just insert utcnow().
224             rec.issued_date = datetime.utcnow()
225         if update.date_modified:
226             rec.updated_date = update.date_modified
227         else:
228             rec.updated_date = datetime.utcnow()
229 
230         col = cr.UpdateCollection()
231         col.name = update.release.long_name.encode('utf-8')
232         col.shortname = update.release.name.encode('utf-8')
233 
234         koji = get_session()
235         for build in update.builds:
236             rpms = self.get_rpms(koji, build.nvr)
237             for rpm in rpms:
238                 pkg = cr.UpdateCollectionPackage()
239                 pkg.name = rpm['name']
240                 pkg.version = rpm['version']
241                 pkg.release = rpm['release']
242                 if rpm['epoch'] is not None:
243                     pkg.epoch = str(rpm['epoch'])
244                 else:
245                     pkg.epoch = '0'
246                 pkg.arch = rpm['arch']
247 
248                 # TODO: how do we handle UpdateSuggestion.logout, etc?
249                 pkg.reboot_suggested = update.suggest is UpdateSuggestion.reboot
250 
251                 filename = '%s.%s.rpm' % (rpm['nvr'], rpm['arch'])
252                 pkg.filename = filename
253 
254                 # Build the URL
255                 if rpm['arch'] == 'src':
256                     arch = 'SRPMS'
257                 elif rpm['arch'] in ('noarch', 'i686'):
258                     arch = 'i386'
259                 else:
260                     arch = rpm['arch']
261 
262                 pkg.src = os.path.join(
263                     config.get('file_url'),
264                     update.status is UpdateStatus.testing and 'testing' or '',
265                     str(update.release.version), arch, filename[0], filename)
266 
267                 col.append(pkg)
268 
269         rec.append_collection(col)
270 
271         # Create references for each bug
272         for bug in update.bugs:
273             ref = cr.UpdateReference()
274             ref.type = 'bugzilla'
275             ref.id = str(bug.bug_id).encode('utf-8')
276             ref.href = bug.url.encode('utf-8')
277             ref.title = bug.title.encode('utf-8') if bug.title else ''
278             rec.append_reference(ref)
279 
280         self.uinfo.append(rec)
281 
282     def insert_updateinfo(self, compose_path):
283         """
284         Add the updateinfo.xml file to the repository.
285 
286         Args:
287             compose_path (str): The path to the compose where the metadata will be inserted.
288         """
289         fd, tmp_file_path = tempfile.mkstemp()
290         os.write(fd, self.uinfo.xml_dump().encode('utf-8'))
291         os.close(fd)
292         modifyrepo(self.comp_type, compose_path, 'updateinfo', 'xml', tmp_file_path)
293         os.unlink(tmp_file_path)
294
```
--- END FILES ---

Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.

Here is an example:

```diff
diff --git a/examples/server_async.py b/examples/server_async.py
--- a/examples/server_async.py
+++ b/examples/server_async.py
@@ -313,4 +313,4 @@
 
 
 if __name__ == "__main__":
-    asyncio.run(run_async_server("."), debug=True)
+    asyncio.run(run_async_server(), debug=True)
diff --git a/examples/server_sync.py b/examples/server_sync.py
--- a/examples/server_sync.py
+++ b/examples/server_sync.py
@@ -313,5 +313,5 @@
 
 
 if __name__ == "__main__":
-    server = run_sync_server(".")
+    server = run_sync_server()
     server.shutdown()

```

diff --git a/bodhi/server/metadata.py b/bodhi/server/metadata.py
--- a/bodhi/server/metadata.py
+++ b/bodhi/server/metadata.py
@@ -35,7 +35,7 @@
 log = logging.getLogger(__name__)
 
 
-def insert_in_repo(comp_type, repodata, filetype, extension, source):
+def insert_in_repo(comp_type, repodata, filetype, extension, source, zchunk):
     """
     Inject a file into the repodata with the help of createrepo_c.
 
@@ -48,6 +48,7 @@
         extension (str): The file extension (xml, sqlite).
         source (str): A file path. File holds the dump of metadata until
             copied to the repodata folder.
+        zchunk (bool): Whether zchunk data is supported for clients of this repo.
     """
     log.info('Inserting %s.%s into %s', filetype, extension, repodata)
     target_fname = os.path.join(repodata, '%s.%s' % (filetype, extension))
@@ -55,7 +56,7 @@
     repomd_xml = os.path.join(repodata, 'repomd.xml')
     repomd = cr.Repomd(repomd_xml)
     add_list = [(filetype, comp_type)]
-    if hasattr(cr, 'ZCK_COMPRESSION') and comp_type != cr.ZCK_COMPRESSION:
+    if zchunk and hasattr(cr, 'ZCK_COMPRESSION') and comp_type != cr.ZCK_COMPRESSION:
         add_list.append((filetype + "_zck", cr.ZCK_COMPRESSION))
     for (ft, ct) in add_list:
         # create a new record for our repomd.xml
@@ -73,7 +74,7 @@
     os.unlink(target_fname)
 
 
-def modifyrepo(comp_type, compose_path, filetype, extension, source):
+def modifyrepo(comp_type, compose_path, filetype, extension, source, zchunk):
     """
     Inject a file into the repodata for each architecture with the help of createrepo_c.
 
@@ -85,6 +86,7 @@
         extension (str): The file extension (xml, sqlite).
         source (str): A file path. File holds the dump of metadata until
             copied to the repodata folder.
+        zchunk (bool): Whether zchunk data is supported for clients of this repo.
     """
     repo_path = os.path.join(compose_path, 'compose', 'Everything')
     for arch in os.listdir(repo_path):
@@ -92,7 +94,7 @@
             repodata = os.path.join(repo_path, arch, 'tree', 'repodata')
         else:
             repodata = os.path.join(repo_path, arch, 'os', 'repodata')
-        insert_in_repo(comp_type, repodata, filetype, extension, source)
+        insert_in_repo(comp_type, repodata, filetype, extension, source, zchunk)
 
 
 class UpdateInfoMetadata(object):
@@ -138,11 +140,22 @@
 
         self.comp_type = cr.XZ
 
-        if release.id_prefix == 'FEDORA-EPEL':
+        # Some repos such as FEDORA-EPEL, are primarily targeted at
+        # distributions that use the yum client, which does not support zchunk metadata
+        self.legacy_repos = ['FEDORA-EPEL']
+        self.zchunk = True
+
+        if release.id_prefix in self.legacy_repos:
             # FIXME: I'm not sure which versions of RHEL support xz metadata
             # compression, so use the lowest common denominator for now.
             self.comp_type = cr.BZ2
 
+            log.warning(
+                'Zchunk data is disabled for repo {release.id_prefix} until it moves to a client'
+                ' with Zchunk support'
+            )
+            self.zchunk = False
+
         self.uinfo = cr.UpdateInfo()
         for update in self.updates:
             self.add_update(update)
@@ -289,5 +302,10 @@
         fd, tmp_file_path = tempfile.mkstemp()
         os.write(fd, self.uinfo.xml_dump().encode('utf-8'))
         os.close(fd)
-        modifyrepo(self.comp_type, compose_path, 'updateinfo', 'xml', tmp_file_path)
+        modifyrepo(self.comp_type,
+                   compose_path,
+                   'updateinfo',
+                   'xml',
+                   tmp_file_path,
+                   self.zchunk)
         os.unlink(tmp_file_path)

{"golden_diff": "diff --git a/bodhi/server/metadata.py b/bodhi/server/metadata.py\n--- a/bodhi/server/metadata.py\n+++ b/bodhi/server/metadata.py\n@@ -35,7 +35,7 @@\n log = logging.getLogger(__name__)\n \n \n-def insert_in_repo(comp_type, repodata, filetype, extension, source):\n+def insert_in_repo(comp_type, repodata, filetype, extension, source, zchunk):\n     \"\"\"\n     Inject a file into the repodata with the help of createrepo_c.\n \n@@ -48,6 +48,7 @@\n         extension (str): The file extension (xml, sqlite).\n         source (str): A file path. File holds the dump of metadata until\n             copied to the repodata folder.\n+        zchunk (bool): Whether zchunk data is supported for clients of this repo.\n     \"\"\"\n     log.info('Inserting %s.%s into %s', filetype, extension, repodata)\n     target_fname = os.path.join(repodata, '%s.%s' % (filetype, extension))\n@@ -55,7 +56,7 @@\n     repomd_xml = os.path.join(repodata, 'repomd.xml')\n     repomd = cr.Repomd(repomd_xml)\n     add_list = [(filetype, comp_type)]\n-    if hasattr(cr, 'ZCK_COMPRESSION') and comp_type != cr.ZCK_COMPRESSION:\n+    if zchunk and hasattr(cr, 'ZCK_COMPRESSION') and comp_type != cr.ZCK_COMPRESSION:\n         add_list.append((filetype + \"_zck\", cr.ZCK_COMPRESSION))\n     for (ft, ct) in add_list:\n         # create a new record for our repomd.xml\n@@ -73,7 +74,7 @@\n     os.unlink(target_fname)\n \n \n-def modifyrepo(comp_type, compose_path, filetype, extension, source):\n+def modifyrepo(comp_type, compose_path, filetype, extension, source, zchunk):\n     \"\"\"\n     Inject a file into the repodata for each architecture with the help of createrepo_c.\n \n@@ -85,6 +86,7 @@\n         extension (str): The file extension (xml, sqlite).\n         source (str): A file path. File holds the dump of metadata until\n             copied to the repodata folder.\n+        zchunk (bool): Whether zchunk data is supported for clients of this repo.\n     \"\"\"\n     repo_path = os.path.join(compose_path, 'compose', 'Everything')\n     for arch in os.listdir(repo_path):\n@@ -92,7 +94,7 @@\n             repodata = os.path.join(repo_path, arch, 'tree', 'repodata')\n         else:\n             repodata = os.path.join(repo_path, arch, 'os', 'repodata')\n-        insert_in_repo(comp_type, repodata, filetype, extension, source)\n+        insert_in_repo(comp_type, repodata, filetype, extension, source, zchunk)\n \n \n class UpdateInfoMetadata(object):\n@@ -138,11 +140,22 @@\n \n         self.comp_type = cr.XZ\n \n-        if release.id_prefix == 'FEDORA-EPEL':\n+        # Some repos such as FEDORA-EPEL, are primarily targeted at\n+        # distributions that use the yum client, which does not support zchunk metadata\n+        self.legacy_repos = ['FEDORA-EPEL']\n+        self.zchunk = True\n+\n+        if release.id_prefix in self.legacy_repos:\n             # FIXME: I'm not sure which versions of RHEL support xz metadata\n             # compression, so use the lowest common denominator for now.\n             self.comp_type = cr.BZ2\n \n+            log.warning(\n+                'Zchunk data is disabled for repo {release.id_prefix} until it moves to a client'\n+                ' with Zchunk support'\n+            )\n+            self.zchunk = False\n+\n         self.uinfo = cr.UpdateInfo()\n         for update in self.updates:\n             self.add_update(update)\n@@ -289,5 +302,10 @@\n         fd, tmp_file_path = tempfile.mkstemp()\n         os.write(fd, self.uinfo.xml_dump().encode('utf-8'))\n         os.close(fd)\n-        modifyrepo(self.comp_type, compose_path, 'updateinfo', 'xml', tmp_file_path)\n+        modifyrepo(self.comp_type,\n+                   compose_path,\n+                   'updateinfo',\n+                   'xml',\n+                   tmp_file_path,\n+                   self.zchunk)\n         os.unlink(tmp_file_path)\n", "issue": "Make zchunk updateinfo a per-repo option\nWhen I added support for zchunked updateinfo.xml in #2904, I missed the fact that my patch was creating zchunked updateinfo.xml for _every_ repository.  I don't think we want to be creating zchunk metadata for EPEL and Fedora 29 (and, apparently it's breaking some third-party mirroring tools, see https://bugzilla.redhat.com/show_bug.cgi?id=1721359), so we need to add a way of making the zchunked updateinfo.xml a per-repo option.\r\n\r\nI'm happy to do the work on this, but I'm not sure where and how it should be implemented.\r\n\n", "before_files": [{"content": "# Copyright 2007-2019 Red Hat, Inc. and others.\n#\n# This file is part of Bodhi.\n#\n# This program is free software; you can redistribute it and/or modify\n# it under the terms of the GNU General Public License as published by\n# the Free Software Foundation; either version 2 of the License, or\n# (at your option) any later version.\n#\n# This program is distributed in the hope that it will be useful,\n# but WITHOUT ANY WARRANTY; without even the implied warranty of\n# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n# GNU General Public License for more details.\n#\n# You should have received a copy of the GNU General Public License along\n# with this program; if not, write to the Free Software Foundation, Inc.,\n# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.\n\"\"\"Create metadata files when composing repositories.\"\"\"\nfrom datetime import datetime\nimport logging\nimport os\nimport shelve\nimport shutil\nimport tempfile\n\nimport createrepo_c as cr\n\nfrom bodhi.server import util\nfrom bodhi.server.buildsys import get_session\nfrom bodhi.server.config import config\nfrom bodhi.server.models import Build, UpdateStatus, UpdateRequest, UpdateSuggestion\n\n\n__version__ = '2.0'\nlog = logging.getLogger(__name__)\n\n\ndef insert_in_repo(comp_type, repodata, filetype, extension, source):\n    \"\"\"\n    Inject a file into the repodata with the help of createrepo_c.\n\n    Args:\n        comp_type (int): createrepo_c compression type indication.\n        repodata (str): The path to the repo where the metadata will be inserted.\n        filetype (str): What type of metadata will be inserted by createrepo_c.\n            This does allow any string to be inserted (custom types). There are some\n            types which are used with dnf repos as primary, updateinfo, comps, filelist etc.\n        extension (str): The file extension (xml, sqlite).\n        source (str): A file path. File holds the dump of metadata until\n            copied to the repodata folder.\n    \"\"\"\n    log.info('Inserting %s.%s into %s', filetype, extension, repodata)\n    target_fname = os.path.join(repodata, '%s.%s' % (filetype, extension))\n    shutil.copyfile(source, target_fname)\n    repomd_xml = os.path.join(repodata, 'repomd.xml')\n    repomd = cr.Repomd(repomd_xml)\n    add_list = [(filetype, comp_type)]\n    if hasattr(cr, 'ZCK_COMPRESSION') and comp_type != cr.ZCK_COMPRESSION:\n        add_list.append((filetype + \"_zck\", cr.ZCK_COMPRESSION))\n    for (ft, ct) in add_list:\n        # create a new record for our repomd.xml\n        rec = cr.RepomdRecord(ft, target_fname)\n        # compress our metadata file with the comp_type\n        rec_comp = rec.compress_and_fill(cr.SHA256, ct)\n        # add hash to the compressed metadata file\n        rec_comp.rename_file()\n        # set type of metadata\n        rec_comp.type = ft\n        # insert metadata about our metadata in repomd.xml\n        repomd.set_record(rec_comp)\n    with open(repomd_xml, 'w') as repomd_file:\n        repomd_file.write(repomd.xml_dump())\n    os.unlink(target_fname)\n\n\ndef modifyrepo(comp_type, compose_path, filetype, extension, source):\n    \"\"\"\n    Inject a file into the repodata for each architecture with the help of createrepo_c.\n\n    Args:\n        compose_path (str): The path to the compose where the metadata will be inserted.\n        filetype (str): What type of metadata will be inserted by createrepo_c.\n            This does allow any string to be inserted (custom types). There are some\n            types which are used with dnf repos as primary, updateinfo, comps, filelist etc.\n        extension (str): The file extension (xml, sqlite).\n        source (str): A file path. File holds the dump of metadata until\n            copied to the repodata folder.\n    \"\"\"\n    repo_path = os.path.join(compose_path, 'compose', 'Everything')\n    for arch in os.listdir(repo_path):\n        if arch == 'source':\n            repodata = os.path.join(repo_path, arch, 'tree', 'repodata')\n        else:\n            repodata = os.path.join(repo_path, arch, 'os', 'repodata')\n        insert_in_repo(comp_type, repodata, filetype, extension, source)\n\n\nclass UpdateInfoMetadata(object):\n    \"\"\"\n    This class represents the updateinfo.xml yum metadata.\n\n    It is generated during push time by the bodhi composer based on koji tags\n    and is injected into the yum repodata using the `modifyrepo_c` tool,\n    which is included in the `createrepo_c` package.\n    \"\"\"\n\n    def __init__(self, release, request, db, composedir, close_shelf=True):\n        \"\"\"\n        Initialize the UpdateInfoMetadata object.\n\n        Args:\n            release (bodhi.server.models.Release): The Release that is being composed.\n            request (bodhi.server.models.UpdateRequest): The Request that is being composed.\n            db (): A database session to be used for queries.\n            composedir (str): A path to the composedir.\n            close_shelf (bool): Whether to close the shelve, which is used to cache updateinfo\n                between composes.\n        \"\"\"\n        self.request = request\n        if request is UpdateRequest.stable:\n            self.tag = release.stable_tag\n        else:\n            self.tag = release.testing_tag\n\n        self.db = db\n        self.updates = set()\n        self.builds = {}\n        self._from = config.get('bodhi_email')\n        if config.get('cache_dir'):\n            self.shelf = shelve.open(os.path.join(config.get('cache_dir'), '%s.shelve' % self.tag))\n        else:\n            # If we have no cache dir, let's at least cache in-memory.\n            self.shelf = {}\n            close_shelf = False\n        self._fetch_updates()\n\n        self.uinfo = cr.UpdateInfo()\n\n        self.comp_type = cr.XZ\n\n        if release.id_prefix == 'FEDORA-EPEL':\n            # FIXME: I'm not sure which versions of RHEL support xz metadata\n            # compression, so use the lowest common denominator for now.\n            self.comp_type = cr.BZ2\n\n        self.uinfo = cr.UpdateInfo()\n        for update in self.updates:\n            self.add_update(update)\n\n        if close_shelf:\n            self.shelf.close()\n\n    def _fetch_updates(self):\n        \"\"\"Based on our given koji tag, populate a list of Update objects.\"\"\"\n        log.debug(\"Fetching builds tagged with '%s'\" % self.tag)\n        kojiBuilds = get_session().listTagged(self.tag, latest=True)\n        nonexistent = []\n        log.debug(\"%d builds found\" % len(kojiBuilds))\n        for build in kojiBuilds:\n            self.builds[build['nvr']] = build\n            build_obj = self.db.query(Build).filter_by(nvr=str(build['nvr'])).first()\n            if build_obj:\n                if build_obj.update:\n                    self.updates.add(build_obj.update)\n                else:\n                    log.warning('%s does not have a corresponding update' % build['nvr'])\n            else:\n                nonexistent.append(build['nvr'])\n        if nonexistent:\n            log.warning(\"Couldn't find the following koji builds tagged as \"\n                        \"%s in bodhi: %s\" % (self.tag, nonexistent))\n\n    def get_rpms(self, koji, nvr):\n        \"\"\"\n        Retrieve the given RPM nvr from the cache if available, or from Koji if not available.\n\n        Args:\n            koji (koji.ClientSession): An initialized Koji client.\n            nvr (str): The nvr for which you wish to retrieve Koji data.\n        Returns:\n            list: A list of dictionaries describing all the subpackages that are part of the given\n                nvr.\n        \"\"\"\n        if str(nvr) in self.shelf:\n            return self.shelf[str(nvr)]\n\n        if nvr in self.builds:\n            buildid = self.builds[nvr]['id']\n        else:\n            buildid = koji.getBuild(nvr)['id']\n\n        rpms = koji.listBuildRPMs(buildid)\n        self.shelf[str(nvr)] = rpms\n        return rpms\n\n    def add_update(self, update):\n        \"\"\"\n        Generate the extended metadata for a given update, adding it to self.uinfo.\n\n        Args:\n            update (bodhi.server.models.Update): The Update to be added to self.uinfo.\n        \"\"\"\n        rec = cr.UpdateRecord()\n        rec.version = __version__\n        rec.fromstr = config.get('bodhi_email')\n        rec.status = update.status.value\n        rec.type = update.type.value\n        rec.id = update.alias.encode('utf-8')\n        rec.title = update.title.encode('utf-8')\n        rec.severity = util.severity_updateinfo_str(update.severity.value)\n        rec.summary = ('%s %s update' % (update.get_title(),\n                                         update.type.value)).encode('utf-8')\n        rec.description = update.notes.encode('utf-8')\n        rec.release = update.release.long_name.encode('utf-8')\n        rec.rights = config.get('updateinfo_rights')\n\n        if update.date_pushed:\n            rec.issued_date = update.date_pushed\n        else:\n            # Sometimes we only set the date_pushed after it's pushed out, however,\n            # it seems that Satellite does not like update entries without issued_date.\n            # Since we know that we are pushing it now, and the next push will get the data\n            # correctly, let's just insert utcnow().\n            rec.issued_date = datetime.utcnow()\n        if update.date_modified:\n            rec.updated_date = update.date_modified\n        else:\n            rec.updated_date = datetime.utcnow()\n\n        col = cr.UpdateCollection()\n        col.name = update.release.long_name.encode('utf-8')\n        col.shortname = update.release.name.encode('utf-8')\n\n        koji = get_session()\n        for build in update.builds:\n            rpms = self.get_rpms(koji, build.nvr)\n            for rpm in rpms:\n                pkg = cr.UpdateCollectionPackage()\n                pkg.name = rpm['name']\n                pkg.version = rpm['version']\n                pkg.release = rpm['release']\n                if rpm['epoch'] is not None:\n                    pkg.epoch = str(rpm['epoch'])\n                else:\n                    pkg.epoch = '0'\n                pkg.arch = rpm['arch']\n\n                # TODO: how do we handle UpdateSuggestion.logout, etc?\n                pkg.reboot_suggested = update.suggest is UpdateSuggestion.reboot\n\n                filename = '%s.%s.rpm' % (rpm['nvr'], rpm['arch'])\n                pkg.filename = filename\n\n                # Build the URL\n                if rpm['arch'] == 'src':\n                    arch = 'SRPMS'\n                elif rpm['arch'] in ('noarch', 'i686'):\n                    arch = 'i386'\n                else:\n                    arch = rpm['arch']\n\n                pkg.src = os.path.join(\n                    config.get('file_url'),\n                    update.status is UpdateStatus.testing and 'testing' or '',\n                    str(update.release.version), arch, filename[0], filename)\n\n                col.append(pkg)\n\n        rec.append_collection(col)\n\n        # Create references for each bug\n        for bug in update.bugs:\n            ref = cr.UpdateReference()\n            ref.type = 'bugzilla'\n            ref.id = str(bug.bug_id).encode('utf-8')\n            ref.href = bug.url.encode('utf-8')\n            ref.title = bug.title.encode('utf-8') if bug.title else ''\n            rec.append_reference(ref)\n\n        self.uinfo.append(rec)\n\n    def insert_updateinfo(self, compose_path):\n        \"\"\"\n        Add the updateinfo.xml file to the repository.\n\n        Args:\n            compose_path (str): The path to the compose where the metadata will be inserted.\n        \"\"\"\n        fd, tmp_file_path = tempfile.mkstemp()\n        os.write(fd, self.uinfo.xml_dump().encode('utf-8'))\n        os.close(fd)\n        modifyrepo(self.comp_type, compose_path, 'updateinfo', 'xml', tmp_file_path)\n        os.unlink(tmp_file_path)\n", "path": "bodhi/server/metadata.py"}], "after_files": [{"content": "# Copyright 2007-2019 Red Hat, Inc. and others.\n#\n# This file is part of Bodhi.\n#\n# This program is free software; you can redistribute it and/or modify\n# it under the terms of the GNU General Public License as published by\n# the Free Software Foundation; either version 2 of the License, or\n# (at your option) any later version.\n#\n# This program is distributed in the hope that it will be useful,\n# but WITHOUT ANY WARRANTY; without even the implied warranty of\n# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n# GNU General Public License for more details.\n#\n# You should have received a copy of the GNU General Public License along\n# with this program; if not, write to the Free Software Foundation, Inc.,\n# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.\n\"\"\"Create metadata files when composing repositories.\"\"\"\nfrom datetime import datetime\nimport logging\nimport os\nimport shelve\nimport shutil\nimport tempfile\n\nimport createrepo_c as cr\n\nfrom bodhi.server import util\nfrom bodhi.server.buildsys import get_session\nfrom bodhi.server.config import config\nfrom bodhi.server.models import Build, UpdateStatus, UpdateRequest, UpdateSuggestion\n\n\n__version__ = '2.0'\nlog = logging.getLogger(__name__)\n\n\ndef insert_in_repo(comp_type, repodata, filetype, extension, source, zchunk):\n    \"\"\"\n    Inject a file into the repodata with the help of createrepo_c.\n\n    Args:\n        comp_type (int): createrepo_c compression type indication.\n        repodata (str): The path to the repo where the metadata will be inserted.\n        filetype (str): What type of metadata will be inserted by createrepo_c.\n            This does allow any string to be inserted (custom types). There are some\n            types which are used with dnf repos as primary, updateinfo, comps, filelist etc.\n        extension (str): The file extension (xml, sqlite).\n        source (str): A file path. File holds the dump of metadata until\n            copied to the repodata folder.\n        zchunk (bool): Whether zchunk data is supported for clients of this repo.\n    \"\"\"\n    log.info('Inserting %s.%s into %s', filetype, extension, repodata)\n    target_fname = os.path.join(repodata, '%s.%s' % (filetype, extension))\n    shutil.copyfile(source, target_fname)\n    repomd_xml = os.path.join(repodata, 'repomd.xml')\n    repomd = cr.Repomd(repomd_xml)\n    add_list = [(filetype, comp_type)]\n    if zchunk and hasattr(cr, 'ZCK_COMPRESSION') and comp_type != cr.ZCK_COMPRESSION:\n        add_list.append((filetype + \"_zck\", cr.ZCK_COMPRESSION))\n    for (ft, ct) in add_list:\n        # create a new record for our repomd.xml\n        rec = cr.RepomdRecord(ft, target_fname)\n        # compress our metadata file with the comp_type\n        rec_comp = rec.compress_and_fill(cr.SHA256, ct)\n        # add hash to the compressed metadata file\n        rec_comp.rename_file()\n        # set type of metadata\n        rec_comp.type = ft\n        # insert metadata about our metadata in repomd.xml\n        repomd.set_record(rec_comp)\n    with open(repomd_xml, 'w') as repomd_file:\n        repomd_file.write(repomd.xml_dump())\n    os.unlink(target_fname)\n\n\ndef modifyrepo(comp_type, compose_path, filetype, extension, source, zchunk):\n    \"\"\"\n    Inject a file into the repodata for each architecture with the help of createrepo_c.\n\n    Args:\n        compose_path (str): The path to the compose where the metadata will be inserted.\n        filetype (str): What type of metadata will be inserted by createrepo_c.\n            This does allow any string to be inserted (custom types). There are some\n            types which are used with dnf repos as primary, updateinfo, comps, filelist etc.\n        extension (str): The file extension (xml, sqlite).\n        source (str): A file path. File holds the dump of metadata until\n            copied to the repodata folder.\n        zchunk (bool): Whether zchunk data is supported for clients of this repo.\n    \"\"\"\n    repo_path = os.path.join(compose_path, 'compose', 'Everything')\n    for arch in os.listdir(repo_path):\n        if arch == 'source':\n            repodata = os.path.join(repo_path, arch, 'tree', 'repodata')\n        else:\n            repodata = os.path.join(repo_path, arch, 'os', 'repodata')\n        insert_in_repo(comp_type, repodata, filetype, extension, source, zchunk)\n\n\nclass UpdateInfoMetadata(object):\n    \"\"\"\n    This class represents the updateinfo.xml yum metadata.\n\n    It is generated during push time by the bodhi composer based on koji tags\n    and is injected into the yum repodata using the `modifyrepo_c` tool,\n    which is included in the `createrepo_c` package.\n    \"\"\"\n\n    def __init__(self, release, request, db, composedir, close_shelf=True):\n        \"\"\"\n        Initialize the UpdateInfoMetadata object.\n\n        Args:\n            release (bodhi.server.models.Release): The Release that is being composed.\n            request (bodhi.server.models.UpdateRequest): The Request that is being composed.\n            db (): A database session to be used for queries.\n            composedir (str): A path to the composedir.\n            close_shelf (bool): Whether to close the shelve, which is used to cache updateinfo\n                between composes.\n        \"\"\"\n        self.request = request\n        if request is UpdateRequest.stable:\n            self.tag = release.stable_tag\n        else:\n            self.tag = release.testing_tag\n\n        self.db = db\n        self.updates = set()\n        self.builds = {}\n        self._from = config.get('bodhi_email')\n        if config.get('cache_dir'):\n            self.shelf = shelve.open(os.path.join(config.get('cache_dir'), '%s.shelve' % self.tag))\n        else:\n            # If we have no cache dir, let's at least cache in-memory.\n            self.shelf = {}\n            close_shelf = False\n        self._fetch_updates()\n\n        self.uinfo = cr.UpdateInfo()\n\n        self.comp_type = cr.XZ\n\n        # Some repos such as FEDORA-EPEL, are primarily targeted at\n        # distributions that use the yum client, which does not support zchunk metadata\n        self.legacy_repos = ['FEDORA-EPEL']\n        self.zchunk = True\n\n        if release.id_prefix in self.legacy_repos:\n            # FIXME: I'm not sure which versions of RHEL support xz metadata\n            # compression, so use the lowest common denominator for now.\n            self.comp_type = cr.BZ2\n\n            log.warning(\n                'Zchunk data is disabled for repo {release.id_prefix} until it moves to a client'\n                ' with Zchunk support'\n            )\n            self.zchunk = False\n\n        self.uinfo = cr.UpdateInfo()\n        for update in self.updates:\n            self.add_update(update)\n\n        if close_shelf:\n            self.shelf.close()\n\n    def _fetch_updates(self):\n        \"\"\"Based on our given koji tag, populate a list of Update objects.\"\"\"\n        log.debug(\"Fetching builds tagged with '%s'\" % self.tag)\n        kojiBuilds = get_session().listTagged(self.tag, latest=True)\n        nonexistent = []\n        log.debug(\"%d builds found\" % len(kojiBuilds))\n        for build in kojiBuilds:\n            self.builds[build['nvr']] = build\n            build_obj = self.db.query(Build).filter_by(nvr=str(build['nvr'])).first()\n            if build_obj:\n                if build_obj.update:\n                    self.updates.add(build_obj.update)\n                else:\n                    log.warning('%s does not have a corresponding update' % build['nvr'])\n            else:\n                nonexistent.append(build['nvr'])\n        if nonexistent:\n            log.warning(\"Couldn't find the following koji builds tagged as \"\n                        \"%s in bodhi: %s\" % (self.tag, nonexistent))\n\n    def get_rpms(self, koji, nvr):\n        \"\"\"\n        Retrieve the given RPM nvr from the cache if available, or from Koji if not available.\n\n        Args:\n            koji (koji.ClientSession): An initialized Koji client.\n            nvr (str): The nvr for which you wish to retrieve Koji data.\n        Returns:\n            list: A list of dictionaries describing all the subpackages that are part of the given\n                nvr.\n        \"\"\"\n        if str(nvr) in self.shelf:\n            return self.shelf[str(nvr)]\n\n        if nvr in self.builds:\n            buildid = self.builds[nvr]['id']\n        else:\n            buildid = koji.getBuild(nvr)['id']\n\n        rpms = koji.listBuildRPMs(buildid)\n        self.shelf[str(nvr)] = rpms\n        return rpms\n\n    def add_update(self, update):\n        \"\"\"\n        Generate the extended metadata for a given update, adding it to self.uinfo.\n\n        Args:\n            update (bodhi.server.models.Update): The Update to be added to self.uinfo.\n        \"\"\"\n        rec = cr.UpdateRecord()\n        rec.version = __version__\n        rec.fromstr = config.get('bodhi_email')\n        rec.status = update.status.value\n        rec.type = update.type.value\n        rec.id = update.alias.encode('utf-8')\n        rec.title = update.title.encode('utf-8')\n        rec.severity = util.severity_updateinfo_str(update.severity.value)\n        rec.summary = ('%s %s update' % (update.get_title(),\n                                         update.type.value)).encode('utf-8')\n        rec.description = update.notes.encode('utf-8')\n        rec.release = update.release.long_name.encode('utf-8')\n        rec.rights = config.get('updateinfo_rights')\n\n        if update.date_pushed:\n            rec.issued_date = update.date_pushed\n        else:\n            # Sometimes we only set the date_pushed after it's pushed out, however,\n            # it seems that Satellite does not like update entries without issued_date.\n            # Since we know that we are pushing it now, and the next push will get the data\n            # correctly, let's just insert utcnow().\n            rec.issued_date = datetime.utcnow()\n        if update.date_modified:\n            rec.updated_date = update.date_modified\n        else:\n            rec.updated_date = datetime.utcnow()\n\n        col = cr.UpdateCollection()\n        col.name = update.release.long_name.encode('utf-8')\n        col.shortname = update.release.name.encode('utf-8')\n\n        koji = get_session()\n        for build in update.builds:\n            rpms = self.get_rpms(koji, build.nvr)\n            for rpm in rpms:\n                pkg = cr.UpdateCollectionPackage()\n                pkg.name = rpm['name']\n                pkg.version = rpm['version']\n                pkg.release = rpm['release']\n                if rpm['epoch'] is not None:\n                    pkg.epoch = str(rpm['epoch'])\n                else:\n                    pkg.epoch = '0'\n                pkg.arch = rpm['arch']\n\n                # TODO: how do we handle UpdateSuggestion.logout, etc?\n                pkg.reboot_suggested = update.suggest is UpdateSuggestion.reboot\n\n                filename = '%s.%s.rpm' % (rpm['nvr'], rpm['arch'])\n                pkg.filename = filename\n\n                # Build the URL\n                if rpm['arch'] == 'src':\n                    arch = 'SRPMS'\n                elif rpm['arch'] in ('noarch', 'i686'):\n                    arch = 'i386'\n                else:\n                    arch = rpm['arch']\n\n                pkg.src = os.path.join(\n                    config.get('file_url'),\n                    update.status is UpdateStatus.testing and 'testing' or '',\n                    str(update.release.version), arch, filename[0], filename)\n\n                col.append(pkg)\n\n        rec.append_collection(col)\n\n        # Create references for each bug\n        for bug in update.bugs:\n            ref = cr.UpdateReference()\n            ref.type = 'bugzilla'\n            ref.id = str(bug.bug_id).encode('utf-8')\n            ref.href = bug.url.encode('utf-8')\n            ref.title = bug.title.encode('utf-8') if bug.title else ''\n            rec.append_reference(ref)\n\n        self.uinfo.append(rec)\n\n    def insert_updateinfo(self, compose_path):\n        \"\"\"\n        Add the updateinfo.xml file to the repository.\n\n        Args:\n            compose_path (str): The path to the compose where the metadata will be inserted.\n        \"\"\"\n        fd, tmp_file_path = tempfile.mkstemp()\n        os.write(fd, self.uinfo.xml_dump().encode('utf-8'))\n        os.close(fd)\n        modifyrepo(self.comp_type,\n                   compose_path,\n                   'updateinfo',\n                   'xml',\n                   tmp_file_path,\n                   self.zchunk)\n        os.unlink(tmp_file_path)\n", "path": "bodhi/server/metadata.py"}]}