We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
Add a log statement to masher when reusing cached updateinfo, and change generating statement to info
Basically, in https://github.com/fedora-infra/bodhi/blob/2.2.0/bodhi/server/metadata.py#L79-L81 change

```
    self._load_cached_updateinfo()
else:
    log.debug("Generating new updateinfo.xml")
```

to

```
    log.info('Restoring cached updateinfo.xml')
    self._load_cached_updateinfo()
else:
    log.info("Generating new updateinfo.xml")
```


--- 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 # This program is free software; you can redistribute it and/or modify
2 # it under the terms of the GNU General Public License as published by
3 # the Free Software Foundation; either version 2 of the License, or
4 # (at your option) any later version.
5 #
6 # This program is distributed in the hope that it will be useful,
7 # but WITHOUT ANY WARRANTY; without even the implied warranty of
8 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
9 # GNU General Public License for more details.
10 #
11 # You should have received a copy of the GNU General Public License along
12 # with this program; if not, write to the Free Software Foundation, Inc.,
13 # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
14 
15 __version__ = '2.0'
16 
17 import os
18 import logging
19 import shutil
20 import tempfile
21 
22 from urlgrabber.grabber import urlgrab
23 from kitchen.text.converters import to_bytes
24 
25 import createrepo_c as cr
26 
27 from bodhi.server.config import config
28 from bodhi.server.models import Build, UpdateStatus, UpdateRequest, UpdateSuggestion
29 from bodhi.server.buildsys import get_session
30 
31 log = logging.getLogger(__name__)
32 
33 
34 class ExtendedMetadata(object):
35     """This class represents the updateinfo.xml yum metadata.
36 
37     It is generated during push time by the bodhi masher based on koji tags
38     and is injected into the yum repodata using the `modifyrepo_c` tool,
39     which is included in the `createrepo_c` package.
40 
41     """
42     def __init__(self, release, request, db, path):
43         self.repo = path
44         log.debug('repo = %r' % self.repo)
45         self.request = request
46         if request is UpdateRequest.stable:
47             self.tag = release.stable_tag
48         else:
49             self.tag = release.testing_tag
50         self.repo_path = os.path.join(self.repo, self.tag)
51 
52         self.db = db
53         self.updates = set()
54         self.builds = {}
55         self.missing_ids = []
56         self._from = config.get('bodhi_email')
57         self.koji = get_session()
58         self._fetch_updates()
59 
60         self.uinfo = cr.UpdateInfo()
61 
62         self.hash_type = cr.SHA256
63         self.comp_type = cr.XZ
64 
65         if release.id_prefix == u'FEDORA-EPEL':
66             # yum on py2.4 doesn't support sha256 (#1080373)
67             if 'el5' in self.repo or '5E' in self.repo:
68                 self.hash_type = cr.SHA1
69                 self.comp_type = cr.GZ
70             else:
71                 # FIXME: I'm not sure which versions of RHEL support xz metadata
72                 # compression, so use the lowest common denominator for now.
73                 self.comp_type = cr.BZ2
74 
75         # Load from the cache if it exists
76         self.cached_repodata = os.path.join(self.repo, '..', self.tag +
77                                             '.repocache', 'repodata/')
78         if os.path.isdir(self.cached_repodata):
79             self._load_cached_updateinfo()
80         else:
81             log.debug("Generating new updateinfo.xml")
82             self.uinfo = cr.UpdateInfo()
83             for update in self.updates:
84                 if update.alias:
85                     self.add_update(update)
86                 else:
87                     self.missing_ids.append(update.title)
88 
89         if self.missing_ids:
90             log.error("%d updates with missing ID: %r" % (
91                 len(self.missing_ids), self.missing_ids))
92 
93     def _load_cached_updateinfo(self):
94         """
95         Load the cached updateinfo.xml from '../{tag}.repocache/repodata'
96         """
97         seen_ids = set()
98         from_cache = set()
99         existing_ids = set()
100 
101         # Parse the updateinfo out of the repomd
102         updateinfo = None
103         repomd_xml = os.path.join(self.cached_repodata, 'repomd.xml')
104         repomd = cr.Repomd()
105         cr.xml_parse_repomd(repomd_xml, repomd)
106         for record in repomd.records:
107             if record.type == 'updateinfo':
108                 updateinfo = os.path.join(os.path.dirname(
109                     os.path.dirname(self.cached_repodata)),
110                     record.location_href)
111                 break
112 
113         assert updateinfo, 'Unable to find updateinfo'
114 
115         # Load the metadata with createrepo_c
116         log.info('Loading cached updateinfo: %s', updateinfo)
117         uinfo = cr.UpdateInfo(updateinfo)
118 
119         # Determine which updates are present in the cache
120         for update in uinfo.updates:
121             existing_ids.add(update.id)
122 
123         # Generate metadata for any new builds
124         for update in self.updates:
125             seen_ids.add(update.alias)
126             if not update.alias:
127                 self.missing_ids.append(update.title)
128                 continue
129             if update.alias in existing_ids:
130                 notice = None
131                 for value in uinfo.updates:
132                     if value.title == update.title:
133                         notice = value
134                         break
135                 if not notice:
136                     log.warn('%s ID in cache but notice cannot be found', update.title)
137                     self.add_update(update)
138                     continue
139                 if notice.updated_date:
140                     if notice.updated_date < update.date_modified:
141                         log.debug('Update modified, generating new notice: %s' % update.title)
142                         self.add_update(update)
143                     else:
144                         log.debug('Loading updated %s from cache' % update.title)
145                         from_cache.add(update.alias)
146                 elif update.date_modified:
147                     log.debug('Update modified, generating new notice: %s' % update.title)
148                     self.add_update(update)
149                 else:
150                     log.debug('Loading %s from cache' % update.title)
151                     from_cache.add(update.alias)
152             else:
153                 log.debug('Adding new update notice: %s' % update.title)
154                 self.add_update(update)
155 
156         # Add all relevant notices from the cache to this document
157         for notice in uinfo.updates:
158             if notice.id in from_cache:
159                 log.debug('Keeping existing notice: %s', notice.title)
160                 self.uinfo.append(notice)
161             else:
162                 # Keep all security notices in the stable repo
163                 if self.request is not UpdateRequest.testing:
164                     if notice.type == 'security':
165                         if notice.id not in seen_ids:
166                             log.debug('Keeping existing security notice: %s',
167                                       notice.title)
168                             self.uinfo.append(notice)
169                         else:
170                             log.debug('%s already added?', notice.title)
171                     else:
172                         log.debug('Purging cached stable notice %s', notice.title)
173                 else:
174                     log.debug('Purging cached testing update %s', notice.title)
175 
176     def _fetch_updates(self):
177         """Based on our given koji tag, populate a list of Update objects"""
178         log.debug("Fetching builds tagged with '%s'" % self.tag)
179         kojiBuilds = self.koji.listTagged(self.tag, latest=True)
180         nonexistent = []
181         log.debug("%d builds found" % len(kojiBuilds))
182         for build in kojiBuilds:
183             self.builds[build['nvr']] = build
184             build_obj = self.db.query(Build).filter_by(nvr=unicode(build['nvr'])).first()
185             if build_obj:
186                 if build_obj.update:
187                     self.updates.add(build_obj.update)
188                 else:
189                     log.warn('%s does not have a corresponding update' % build['nvr'])
190             else:
191                 nonexistent.append(build['nvr'])
192         if nonexistent:
193             log.warning("Couldn't find the following koji builds tagged as "
194                         "%s in bodhi: %s" % (self.tag, nonexistent))
195 
196     def add_update(self, update):
197         """Generate the extended metadata for a given update"""
198         rec = cr.UpdateRecord()
199         rec.version = __version__
200         rec.fromstr = config.get('bodhi_email')
201         rec.status = update.status.value
202         rec.type = update.type.value
203         rec.id = to_bytes(update.alias)
204         rec.title = to_bytes(update.title)
205         rec.summary = to_bytes('%s %s update' % (update.get_title(),
206                                                  update.type.value))
207         rec.description = to_bytes(update.notes)
208         rec.release = to_bytes(update.release.long_name)
209         rec.rights = config.get('updateinfo_rights')
210 
211         if update.date_pushed:
212             rec.issued_date = update.date_pushed
213         if update.date_modified:
214             rec.updated_date = update.date_modified
215 
216         col = cr.UpdateCollection()
217         col.name = to_bytes(update.release.long_name)
218         col.shortname = to_bytes(update.release.name)
219 
220         for build in update.builds:
221             try:
222                 kojiBuild = self.builds[build.nvr]
223             except:
224                 kojiBuild = self.koji.getBuild(build.nvr)
225 
226             rpms = self.koji.listBuildRPMs(kojiBuild['id'])
227             for rpm in rpms:
228                 pkg = cr.UpdateCollectionPackage()
229                 pkg.name = rpm['name']
230                 pkg.version = rpm['version']
231                 pkg.release = rpm['release']
232                 if rpm['epoch'] is not None:
233                     pkg.epoch = str(rpm['epoch'])
234                 else:
235                     pkg.epoch = '0'
236                 pkg.arch = rpm['arch']
237 
238                 # TODO: how do we handle UpdateSuggestion.logout, etc?
239                 pkg.reboot_suggested = update.suggest is UpdateSuggestion.reboot
240 
241                 filename = '%s.%s.rpm' % (rpm['nvr'], rpm['arch'])
242                 pkg.filename = filename
243 
244                 # Build the URL
245                 if rpm['arch'] == 'src':
246                     arch = 'SRPMS'
247                 elif rpm['arch'] in ('noarch', 'i686'):
248                     arch = 'i386'
249                 else:
250                     arch = rpm['arch']
251 
252                 pkg.src = os.path.join(config.get('file_url'), update.status is
253                         UpdateStatus.testing and 'testing' or '',
254                         str(update.release.version), arch, filename[0], filename)
255 
256                 col.append(pkg)
257 
258         rec.append_collection(col)
259 
260         # Create references for each bug
261         for bug in update.bugs:
262             ref = cr.UpdateReference()
263             ref.type = 'bugzilla'
264             ref.id = to_bytes(bug.bug_id)
265             ref.href = to_bytes(bug.url)
266             ref.title = to_bytes(bug.title)
267             rec.append_reference(ref)
268 
269         # Create references for each CVE
270         for cve in update.cves:
271             ref = cr.UpdateReference()
272             ref.type = 'cve'
273             ref.id = to_bytes(cve.cve_id)
274             ref.href = to_bytes(cve.url)
275             rec.append_reference(ref)
276 
277         self.uinfo.append(rec)
278 
279     def insert_updateinfo(self):
280         fd, name = tempfile.mkstemp()
281         os.write(fd, self.uinfo.xml_dump())
282         os.close(fd)
283         self.modifyrepo(name)
284         os.unlink(name)
285 
286     def modifyrepo(self, filename):
287         """Inject a file into the repodata for each architecture"""
288         for arch in os.listdir(self.repo_path):
289             repodata = os.path.join(self.repo_path, arch, 'repodata')
290             log.info('Inserting %s into %s', filename, repodata)
291             uinfo_xml = os.path.join(repodata, 'updateinfo.xml')
292             shutil.copyfile(filename, uinfo_xml)
293             repomd_xml = os.path.join(repodata, 'repomd.xml')
294             repomd = cr.Repomd(repomd_xml)
295             uinfo_rec = cr.RepomdRecord('updateinfo', uinfo_xml)
296             uinfo_rec_comp = uinfo_rec.compress_and_fill(self.hash_type, self.comp_type)
297             uinfo_rec_comp.rename_file()
298             uinfo_rec_comp.type = 'updateinfo'
299             repomd.set_record(uinfo_rec_comp)
300             with file(repomd_xml, 'w') as repomd_file:
301                 repomd_file.write(repomd.xml_dump())
302             os.unlink(uinfo_xml)
303 
304     def insert_pkgtags(self):
305         """Download and inject the pkgtags sqlite from fedora-tagger"""
306         if config.get('pkgtags_url'):
307             try:
308                 tags_url = config.get('pkgtags_url')
309                 tempdir = tempfile.mkdtemp('bodhi')
310                 local_tags = os.path.join(tempdir, 'pkgtags.sqlite')
311                 log.info('Downloading %s' % tags_url)
312                 urlgrab(tags_url, filename=local_tags)
313                 self.modifyrepo(local_tags)
314             except:
315                 log.exception("There was a problem injecting pkgtags")
316             finally:
317                 shutil.rmtree(tempdir)
318 
319     def cache_repodata(self):
320         arch = os.listdir(self.repo_path)[0]  # Take the first arch
321         repodata = os.path.join(self.repo_path, arch, 'repodata')
322         if not os.path.isdir(repodata):
323             log.warning('Cannot find repodata to cache: %s' % repodata)
324             return
325         cache = self.cached_repodata
326         if os.path.isdir(cache):
327             shutil.rmtree(cache)
328         shutil.copytree(repodata, cache)
329         log.info('%s cached to %s' % (repodata, cache))
330
```
--- 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
@@ -76,9 +76,10 @@
         self.cached_repodata = os.path.join(self.repo, '..', self.tag +
                                             '.repocache', 'repodata/')
         if os.path.isdir(self.cached_repodata):
+            log.info('Loading cached updateinfo.xml')
             self._load_cached_updateinfo()
         else:
-            log.debug("Generating new updateinfo.xml")
+            log.info("Generating new updateinfo.xml")
             self.uinfo = cr.UpdateInfo()
             for update in self.updates:
                 if update.alias:

{"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@@ -76,9 +76,10 @@\n         self.cached_repodata = os.path.join(self.repo, '..', self.tag +\n                                             '.repocache', 'repodata/')\n         if os.path.isdir(self.cached_repodata):\n+            log.info('Loading cached updateinfo.xml')\n             self._load_cached_updateinfo()\n         else:\n-            log.debug(\"Generating new updateinfo.xml\")\n+            log.info(\"Generating new updateinfo.xml\")\n             self.uinfo = cr.UpdateInfo()\n             for update in self.updates:\n                 if update.alias:\n", "issue": "Add a log statement to masher when reusing cached updateinfo, and change generating statement to info\nBasically, in https://github.com/fedora-infra/bodhi/blob/2.2.0/bodhi/server/metadata.py#L79-L81 change\n\n```\n    self._load_cached_updateinfo()\nelse:\n    log.debug(\"Generating new updateinfo.xml\")\n```\n\nto\n\n```\n    log.info('Restoring cached updateinfo.xml')\n    self._load_cached_updateinfo()\nelse:\n    log.info(\"Generating new updateinfo.xml\")\n```\n\n", "before_files": [{"content": "# 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\n__version__ = '2.0'\n\nimport os\nimport logging\nimport shutil\nimport tempfile\n\nfrom urlgrabber.grabber import urlgrab\nfrom kitchen.text.converters import to_bytes\n\nimport createrepo_c as cr\n\nfrom bodhi.server.config import config\nfrom bodhi.server.models import Build, UpdateStatus, UpdateRequest, UpdateSuggestion\nfrom bodhi.server.buildsys import get_session\n\nlog = logging.getLogger(__name__)\n\n\nclass ExtendedMetadata(object):\n    \"\"\"This class represents the updateinfo.xml yum metadata.\n\n    It is generated during push time by the bodhi masher 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, path):\n        self.repo = path\n        log.debug('repo = %r' % self.repo)\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        self.repo_path = os.path.join(self.repo, self.tag)\n\n        self.db = db\n        self.updates = set()\n        self.builds = {}\n        self.missing_ids = []\n        self._from = config.get('bodhi_email')\n        self.koji = get_session()\n        self._fetch_updates()\n\n        self.uinfo = cr.UpdateInfo()\n\n        self.hash_type = cr.SHA256\n        self.comp_type = cr.XZ\n\n        if release.id_prefix == u'FEDORA-EPEL':\n            # yum on py2.4 doesn't support sha256 (#1080373)\n            if 'el5' in self.repo or '5E' in self.repo:\n                self.hash_type = cr.SHA1\n                self.comp_type = cr.GZ\n            else:\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        # Load from the cache if it exists\n        self.cached_repodata = os.path.join(self.repo, '..', self.tag +\n                                            '.repocache', 'repodata/')\n        if os.path.isdir(self.cached_repodata):\n            self._load_cached_updateinfo()\n        else:\n            log.debug(\"Generating new updateinfo.xml\")\n            self.uinfo = cr.UpdateInfo()\n            for update in self.updates:\n                if update.alias:\n                    self.add_update(update)\n                else:\n                    self.missing_ids.append(update.title)\n\n        if self.missing_ids:\n            log.error(\"%d updates with missing ID: %r\" % (\n                len(self.missing_ids), self.missing_ids))\n\n    def _load_cached_updateinfo(self):\n        \"\"\"\n        Load the cached updateinfo.xml from '../{tag}.repocache/repodata'\n        \"\"\"\n        seen_ids = set()\n        from_cache = set()\n        existing_ids = set()\n\n        # Parse the updateinfo out of the repomd\n        updateinfo = None\n        repomd_xml = os.path.join(self.cached_repodata, 'repomd.xml')\n        repomd = cr.Repomd()\n        cr.xml_parse_repomd(repomd_xml, repomd)\n        for record in repomd.records:\n            if record.type == 'updateinfo':\n                updateinfo = os.path.join(os.path.dirname(\n                    os.path.dirname(self.cached_repodata)),\n                    record.location_href)\n                break\n\n        assert updateinfo, 'Unable to find updateinfo'\n\n        # Load the metadata with createrepo_c\n        log.info('Loading cached updateinfo: %s', updateinfo)\n        uinfo = cr.UpdateInfo(updateinfo)\n\n        # Determine which updates are present in the cache\n        for update in uinfo.updates:\n            existing_ids.add(update.id)\n\n        # Generate metadata for any new builds\n        for update in self.updates:\n            seen_ids.add(update.alias)\n            if not update.alias:\n                self.missing_ids.append(update.title)\n                continue\n            if update.alias in existing_ids:\n                notice = None\n                for value in uinfo.updates:\n                    if value.title == update.title:\n                        notice = value\n                        break\n                if not notice:\n                    log.warn('%s ID in cache but notice cannot be found', update.title)\n                    self.add_update(update)\n                    continue\n                if notice.updated_date:\n                    if notice.updated_date < update.date_modified:\n                        log.debug('Update modified, generating new notice: %s' % update.title)\n                        self.add_update(update)\n                    else:\n                        log.debug('Loading updated %s from cache' % update.title)\n                        from_cache.add(update.alias)\n                elif update.date_modified:\n                    log.debug('Update modified, generating new notice: %s' % update.title)\n                    self.add_update(update)\n                else:\n                    log.debug('Loading %s from cache' % update.title)\n                    from_cache.add(update.alias)\n            else:\n                log.debug('Adding new update notice: %s' % update.title)\n                self.add_update(update)\n\n        # Add all relevant notices from the cache to this document\n        for notice in uinfo.updates:\n            if notice.id in from_cache:\n                log.debug('Keeping existing notice: %s', notice.title)\n                self.uinfo.append(notice)\n            else:\n                # Keep all security notices in the stable repo\n                if self.request is not UpdateRequest.testing:\n                    if notice.type == 'security':\n                        if notice.id not in seen_ids:\n                            log.debug('Keeping existing security notice: %s',\n                                      notice.title)\n                            self.uinfo.append(notice)\n                        else:\n                            log.debug('%s already added?', notice.title)\n                    else:\n                        log.debug('Purging cached stable notice %s', notice.title)\n                else:\n                    log.debug('Purging cached testing update %s', notice.title)\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 = self.koji.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=unicode(build['nvr'])).first()\n            if build_obj:\n                if build_obj.update:\n                    self.updates.add(build_obj.update)\n                else:\n                    log.warn('%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 add_update(self, update):\n        \"\"\"Generate the extended metadata for a given update\"\"\"\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 = to_bytes(update.alias)\n        rec.title = to_bytes(update.title)\n        rec.summary = to_bytes('%s %s update' % (update.get_title(),\n                                                 update.type.value))\n        rec.description = to_bytes(update.notes)\n        rec.release = to_bytes(update.release.long_name)\n        rec.rights = config.get('updateinfo_rights')\n\n        if update.date_pushed:\n            rec.issued_date = update.date_pushed\n        if update.date_modified:\n            rec.updated_date = update.date_modified\n\n        col = cr.UpdateCollection()\n        col.name = to_bytes(update.release.long_name)\n        col.shortname = to_bytes(update.release.name)\n\n        for build in update.builds:\n            try:\n                kojiBuild = self.builds[build.nvr]\n            except:\n                kojiBuild = self.koji.getBuild(build.nvr)\n\n            rpms = self.koji.listBuildRPMs(kojiBuild['id'])\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(config.get('file_url'), update.status is\n                        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 = to_bytes(bug.bug_id)\n            ref.href = to_bytes(bug.url)\n            ref.title = to_bytes(bug.title)\n            rec.append_reference(ref)\n\n        # Create references for each CVE\n        for cve in update.cves:\n            ref = cr.UpdateReference()\n            ref.type = 'cve'\n            ref.id = to_bytes(cve.cve_id)\n            ref.href = to_bytes(cve.url)\n            rec.append_reference(ref)\n\n        self.uinfo.append(rec)\n\n    def insert_updateinfo(self):\n        fd, name = tempfile.mkstemp()\n        os.write(fd, self.uinfo.xml_dump())\n        os.close(fd)\n        self.modifyrepo(name)\n        os.unlink(name)\n\n    def modifyrepo(self, filename):\n        \"\"\"Inject a file into the repodata for each architecture\"\"\"\n        for arch in os.listdir(self.repo_path):\n            repodata = os.path.join(self.repo_path, arch, 'repodata')\n            log.info('Inserting %s into %s', filename, repodata)\n            uinfo_xml = os.path.join(repodata, 'updateinfo.xml')\n            shutil.copyfile(filename, uinfo_xml)\n            repomd_xml = os.path.join(repodata, 'repomd.xml')\n            repomd = cr.Repomd(repomd_xml)\n            uinfo_rec = cr.RepomdRecord('updateinfo', uinfo_xml)\n            uinfo_rec_comp = uinfo_rec.compress_and_fill(self.hash_type, self.comp_type)\n            uinfo_rec_comp.rename_file()\n            uinfo_rec_comp.type = 'updateinfo'\n            repomd.set_record(uinfo_rec_comp)\n            with file(repomd_xml, 'w') as repomd_file:\n                repomd_file.write(repomd.xml_dump())\n            os.unlink(uinfo_xml)\n\n    def insert_pkgtags(self):\n        \"\"\"Download and inject the pkgtags sqlite from fedora-tagger\"\"\"\n        if config.get('pkgtags_url'):\n            try:\n                tags_url = config.get('pkgtags_url')\n                tempdir = tempfile.mkdtemp('bodhi')\n                local_tags = os.path.join(tempdir, 'pkgtags.sqlite')\n                log.info('Downloading %s' % tags_url)\n                urlgrab(tags_url, filename=local_tags)\n                self.modifyrepo(local_tags)\n            except:\n                log.exception(\"There was a problem injecting pkgtags\")\n            finally:\n                shutil.rmtree(tempdir)\n\n    def cache_repodata(self):\n        arch = os.listdir(self.repo_path)[0]  # Take the first arch\n        repodata = os.path.join(self.repo_path, arch, 'repodata')\n        if not os.path.isdir(repodata):\n            log.warning('Cannot find repodata to cache: %s' % repodata)\n            return\n        cache = self.cached_repodata\n        if os.path.isdir(cache):\n            shutil.rmtree(cache)\n        shutil.copytree(repodata, cache)\n        log.info('%s cached to %s' % (repodata, cache))\n", "path": "bodhi/server/metadata.py"}], "after_files": [{"content": "# 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\n__version__ = '2.0'\n\nimport os\nimport logging\nimport shutil\nimport tempfile\n\nfrom urlgrabber.grabber import urlgrab\nfrom kitchen.text.converters import to_bytes\n\nimport createrepo_c as cr\n\nfrom bodhi.server.config import config\nfrom bodhi.server.models import Build, UpdateStatus, UpdateRequest, UpdateSuggestion\nfrom bodhi.server.buildsys import get_session\n\nlog = logging.getLogger(__name__)\n\n\nclass ExtendedMetadata(object):\n    \"\"\"This class represents the updateinfo.xml yum metadata.\n\n    It is generated during push time by the bodhi masher 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, path):\n        self.repo = path\n        log.debug('repo = %r' % self.repo)\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        self.repo_path = os.path.join(self.repo, self.tag)\n\n        self.db = db\n        self.updates = set()\n        self.builds = {}\n        self.missing_ids = []\n        self._from = config.get('bodhi_email')\n        self.koji = get_session()\n        self._fetch_updates()\n\n        self.uinfo = cr.UpdateInfo()\n\n        self.hash_type = cr.SHA256\n        self.comp_type = cr.XZ\n\n        if release.id_prefix == u'FEDORA-EPEL':\n            # yum on py2.4 doesn't support sha256 (#1080373)\n            if 'el5' in self.repo or '5E' in self.repo:\n                self.hash_type = cr.SHA1\n                self.comp_type = cr.GZ\n            else:\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        # Load from the cache if it exists\n        self.cached_repodata = os.path.join(self.repo, '..', self.tag +\n                                            '.repocache', 'repodata/')\n        if os.path.isdir(self.cached_repodata):\n            log.info('Loading cached updateinfo.xml')\n            self._load_cached_updateinfo()\n        else:\n            log.info(\"Generating new updateinfo.xml\")\n            self.uinfo = cr.UpdateInfo()\n            for update in self.updates:\n                if update.alias:\n                    self.add_update(update)\n                else:\n                    self.missing_ids.append(update.title)\n\n        if self.missing_ids:\n            log.error(\"%d updates with missing ID: %r\" % (\n                len(self.missing_ids), self.missing_ids))\n\n    def _load_cached_updateinfo(self):\n        \"\"\"\n        Load the cached updateinfo.xml from '../{tag}.repocache/repodata'\n        \"\"\"\n        seen_ids = set()\n        from_cache = set()\n        existing_ids = set()\n\n        # Parse the updateinfo out of the repomd\n        updateinfo = None\n        repomd_xml = os.path.join(self.cached_repodata, 'repomd.xml')\n        repomd = cr.Repomd()\n        cr.xml_parse_repomd(repomd_xml, repomd)\n        for record in repomd.records:\n            if record.type == 'updateinfo':\n                updateinfo = os.path.join(os.path.dirname(\n                    os.path.dirname(self.cached_repodata)),\n                    record.location_href)\n                break\n\n        assert updateinfo, 'Unable to find updateinfo'\n\n        # Load the metadata with createrepo_c\n        log.info('Loading cached updateinfo: %s', updateinfo)\n        uinfo = cr.UpdateInfo(updateinfo)\n\n        # Determine which updates are present in the cache\n        for update in uinfo.updates:\n            existing_ids.add(update.id)\n\n        # Generate metadata for any new builds\n        for update in self.updates:\n            seen_ids.add(update.alias)\n            if not update.alias:\n                self.missing_ids.append(update.title)\n                continue\n            if update.alias in existing_ids:\n                notice = None\n                for value in uinfo.updates:\n                    if value.title == update.title:\n                        notice = value\n                        break\n                if not notice:\n                    log.warn('%s ID in cache but notice cannot be found', update.title)\n                    self.add_update(update)\n                    continue\n                if notice.updated_date:\n                    if notice.updated_date < update.date_modified:\n                        log.debug('Update modified, generating new notice: %s' % update.title)\n                        self.add_update(update)\n                    else:\n                        log.debug('Loading updated %s from cache' % update.title)\n                        from_cache.add(update.alias)\n                elif update.date_modified:\n                    log.debug('Update modified, generating new notice: %s' % update.title)\n                    self.add_update(update)\n                else:\n                    log.debug('Loading %s from cache' % update.title)\n                    from_cache.add(update.alias)\n            else:\n                log.debug('Adding new update notice: %s' % update.title)\n                self.add_update(update)\n\n        # Add all relevant notices from the cache to this document\n        for notice in uinfo.updates:\n            if notice.id in from_cache:\n                log.debug('Keeping existing notice: %s', notice.title)\n                self.uinfo.append(notice)\n            else:\n                # Keep all security notices in the stable repo\n                if self.request is not UpdateRequest.testing:\n                    if notice.type == 'security':\n                        if notice.id not in seen_ids:\n                            log.debug('Keeping existing security notice: %s',\n                                      notice.title)\n                            self.uinfo.append(notice)\n                        else:\n                            log.debug('%s already added?', notice.title)\n                    else:\n                        log.debug('Purging cached stable notice %s', notice.title)\n                else:\n                    log.debug('Purging cached testing update %s', notice.title)\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 = self.koji.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=unicode(build['nvr'])).first()\n            if build_obj:\n                if build_obj.update:\n                    self.updates.add(build_obj.update)\n                else:\n                    log.warn('%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 add_update(self, update):\n        \"\"\"Generate the extended metadata for a given update\"\"\"\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 = to_bytes(update.alias)\n        rec.title = to_bytes(update.title)\n        rec.summary = to_bytes('%s %s update' % (update.get_title(),\n                                                 update.type.value))\n        rec.description = to_bytes(update.notes)\n        rec.release = to_bytes(update.release.long_name)\n        rec.rights = config.get('updateinfo_rights')\n\n        if update.date_pushed:\n            rec.issued_date = update.date_pushed\n        if update.date_modified:\n            rec.updated_date = update.date_modified\n\n        col = cr.UpdateCollection()\n        col.name = to_bytes(update.release.long_name)\n        col.shortname = to_bytes(update.release.name)\n\n        for build in update.builds:\n            try:\n                kojiBuild = self.builds[build.nvr]\n            except:\n                kojiBuild = self.koji.getBuild(build.nvr)\n\n            rpms = self.koji.listBuildRPMs(kojiBuild['id'])\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(config.get('file_url'), update.status is\n                        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 = to_bytes(bug.bug_id)\n            ref.href = to_bytes(bug.url)\n            ref.title = to_bytes(bug.title)\n            rec.append_reference(ref)\n\n        # Create references for each CVE\n        for cve in update.cves:\n            ref = cr.UpdateReference()\n            ref.type = 'cve'\n            ref.id = to_bytes(cve.cve_id)\n            ref.href = to_bytes(cve.url)\n            rec.append_reference(ref)\n\n        self.uinfo.append(rec)\n\n    def insert_updateinfo(self):\n        fd, name = tempfile.mkstemp()\n        os.write(fd, self.uinfo.xml_dump())\n        os.close(fd)\n        self.modifyrepo(name)\n        os.unlink(name)\n\n    def modifyrepo(self, filename):\n        \"\"\"Inject a file into the repodata for each architecture\"\"\"\n        for arch in os.listdir(self.repo_path):\n            repodata = os.path.join(self.repo_path, arch, 'repodata')\n            log.info('Inserting %s into %s', filename, repodata)\n            uinfo_xml = os.path.join(repodata, 'updateinfo.xml')\n            shutil.copyfile(filename, uinfo_xml)\n            repomd_xml = os.path.join(repodata, 'repomd.xml')\n            repomd = cr.Repomd(repomd_xml)\n            uinfo_rec = cr.RepomdRecord('updateinfo', uinfo_xml)\n            uinfo_rec_comp = uinfo_rec.compress_and_fill(self.hash_type, self.comp_type)\n            uinfo_rec_comp.rename_file()\n            uinfo_rec_comp.type = 'updateinfo'\n            repomd.set_record(uinfo_rec_comp)\n            with file(repomd_xml, 'w') as repomd_file:\n                repomd_file.write(repomd.xml_dump())\n            os.unlink(uinfo_xml)\n\n    def insert_pkgtags(self):\n        \"\"\"Download and inject the pkgtags sqlite from fedora-tagger\"\"\"\n        if config.get('pkgtags_url'):\n            try:\n                tags_url = config.get('pkgtags_url')\n                tempdir = tempfile.mkdtemp('bodhi')\n                local_tags = os.path.join(tempdir, 'pkgtags.sqlite')\n                log.info('Downloading %s' % tags_url)\n                urlgrab(tags_url, filename=local_tags)\n                self.modifyrepo(local_tags)\n            except:\n                log.exception(\"There was a problem injecting pkgtags\")\n            finally:\n                shutil.rmtree(tempdir)\n\n    def cache_repodata(self):\n        arch = os.listdir(self.repo_path)[0]  # Take the first arch\n        repodata = os.path.join(self.repo_path, arch, 'repodata')\n        if not os.path.isdir(repodata):\n            log.warning('Cannot find repodata to cache: %s' % repodata)\n            return\n        cache = self.cached_repodata\n        if os.path.isdir(cache):\n            shutil.rmtree(cache)\n        shutil.copytree(repodata, cache)\n        log.info('%s cached to %s' % (repodata, cache))\n", "path": "bodhi/server/metadata.py"}]}