DXLTTC-785 First SR acceptance problem.
[tools/repa.git] / repa / obs.py
1 #!/usr/bin/env python
2 #
3 # This file is part of REPA: Release Engineering Process Assistant.
4 #
5 # Copyright (C) 2013 Intel Corporation
6 #
7 # REPA is free software; you can redistribute it and/or
8 # modify it under the terms of the GNU General Public License
9 # version 2 as published by the Free Software Foundation.
10 #
11 # This program is distributed in the hope that it will be useful,
12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14 # GNU General Public License for more details.
15 #
16 # You should have received a copy of the GNU General Public License
17 # along with this program; if not, write to the Free Software
18 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
19 # MA 02110-1301, USA.
20
21 """
22 REPA: Release Engineering Process Assistant.
23
24 Copyright (C) Intel Corporation 2013
25 Licence: GPL version 2
26 Author: Ed Bartosh <eduard.bartosh@intel.com>
27
28 OBS module.
29 Interface module to access Open Build System.
30
31 This module is a intermediate step for new APIs
32 to oscapi. As soon as APIs are mature enough they
33 should be contributed to oscapi.
34 """
35
36 import sys
37 import re
38 import cgi
39 import locale
40
41 from base64 import b64encode
42 from xml.etree import cElementTree as ET
43 from StringIO import StringIO
44 from urllib2 import HTTPError
45
46 from osc import core
47
48 from gitbuildsys.oscapi import OSC, OSCError
49 from gitbuildsys.utils import Temp
50
51 from repa.common import RepaException, retry
52
53
54 OSCRC_TEMPLATE = """[general]
55 apiurl = %(apiurl)s
56 plaintext_passwd=0
57 use_keyring=0
58 http_debug = %(http_debug)s
59 debug = %(debug)s
60 gnome_keyring=0
61 [%(apiurl)s]
62 user=%(user)s
63 passx=%(passwdx)s
64 """
65
66 # pylint: disable=too-many-public-methods
67 class OBS(OSC):
68     """Interface to OBS API."""
69
70     def __init__(self, apiurl, apiuser, apipasswd):
71         oscrc = OSCRC_TEMPLATE % {
72             'http_debug': 0,
73             "debug": 0,
74             "apiurl": apiurl,
75             "user": apiuser,
76             "passwdx": b64encode(apipasswd.encode('bz2'))}
77
78         self.apiurl = apiurl
79         tmpf = Temp(prefix='.oscrc', content=oscrc)
80         self.oscrcpath = tmpf.path
81         OSC.__init__(self, apiurl, self.oscrcpath)
82
83
84     @retry((OSCError, HTTPError))
85     def get_descr(self, project):
86         """Wrapper around get_description to be able to use @retry."""
87         return self.get_description(project)
88
89     def get_project_list(self, regexp=''):
90         """Get list of projects matching regexp."""
91         try:
92             projects = core.meta_get_project_list(self.apiurl)
93         except OSCError as err:
94             raise RepaException("cat't get list of projects from %s: %s" %
95                                 (self.apiurl, err))
96
97         for proj in projects:
98             if regexp and re.match(regexp, proj):
99                 yield proj
100
101
102     def get_projects(self, regexp='', processes=0):
103         """List projects with attributes."""
104         projects = list(self.get_project_list(regexp))
105
106         if processes > 1:
107             from multiprocessing.pool import ThreadPool
108             pool = ThreadPool(processes=processes)
109             processes = {}
110             for project in projects:
111                 processes[project] = (
112                     pool.apply_async(self.get_descr, [project]),
113                     pool.apply_async(self.get_build_results, [project]))
114
115         for project in projects:
116             if processes > 1:
117                 yield (project,
118                        processes[project][0].get(),
119                        processes[project][1].get())
120             else:
121                 yield (project,
122                        self.get_descr(project),
123                        self.get_build_results(project))
124
125
126     @retry((OSCError, HTTPError))
127     def get_build_results(self, prj):
128         """Get project build results."""
129         meta = core.show_prj_results_meta(self.apiurl, prj)
130         root = ET.fromstring(''.join(meta))
131         buildres = {}
132         if not root.find('result'):
133             return buildres
134         for results in root.findall('result'):
135             key = (results.get('repository'), results.get('arch'))
136             buildres[key] = dict([(field, results.get(field)) \
137                                       for field in ('code', 'state')])
138             pkgresults = []
139             for node in results:
140                 code = node.get('code')
141                 if node.get('code') != 'excluded':
142                     pkgresults.append((node.get('package'), code))
143             buildres[key]['packages'] = pkgresults
144
145         return buildres
146
147     @retry((OSCError, HTTPError))
148     def get_build_time(self, prj, repo, arch):
149         """Get build time for the project/repo/arch."""
150         url = core.makeurl(self.apiurl,
151                            ['build', prj, repo, arch, '_jobhistory'])
152         history = self.core_http(core.http_GET, url)
153         history_root = ET.parse(history).getroot()
154         seconds = 0
155         for node in history_root.findall('jobhist'):
156             seconds += int(node.get('endtime')) - int(node.get('starttime'))
157         return seconds
158
159     def get_source_packages(self, prj):
160         """Get list of binary packages in the project."""
161         return iter(core.meta_get_packagelist(self.apiurl, prj))
162
163
164     def get_binary_packages(self, prj):
165         """Get list of binary packages in the project."""
166         for repo in core.get_repos_of_project(self.apiurl, prj):
167             yield (repo.name, repo.arch), core.get_binarylist(self.apiurl,
168                                                               prj,
169                                                               repo.name,
170                                                               repo.arch)
171
172     @staticmethod
173     @retry((OSCError, HTTPError))
174     def aggregate_package(src_project, src_package, dst_project,
175                           dst_package):
176         """Aggregate package. Wraps core.aggregate_pack."""
177         saved = sys.stdout
178         sys.stdout = StringIO()
179         try:
180             core.aggregate_pac(src_project, src_package,
181                                dst_project, dst_package)
182         finally:
183             sys.stdout = saved
184
185         return src_project, src_package, dst_project, dst_package
186
187     def create_sr(self, src_project, packages, tgt_project, message=''):
188         """Create submit request for the project."""
189
190         content = '<request><description>%s</description>' % \
191                   cgi.escape(message.encode('utf-8'))
192         for package in packages:
193             content += '<action type="submit">'
194             content += '<source project="%s" package="%s"/>' % \
195                        (src_project, package)
196             content += '<target project="%s" package="%s" />' % \
197                        (tgt_project, package)
198             content += '</action>'
199         content += '</request>\n'
200         url = core.makeurl(self.apiurl, ['request'], query='cmd=create')
201         reply = self.core_http(core.http_POST, url, data=content)
202         return ET.parse(reply).getroot().get('id')
203
204     def set_sr_state(self, reqid, state, message='', force=False):
205         """Set SR state."""
206         return core.change_request_state(self.apiurl, reqid, state,
207                                          message=message, force=force)
208
209     def get_srs(self, prj, states=None, pkg=None):
210         """
211         Get SRs for the project (and package).
212
213         Args:
214             prj (str): OBS project name
215             states (str): comma-separated list of states, e.g. 'new,accepted'
216             pkg (str): package name
217
218         Yields:
219             id, state, description of found SRs
220         """
221         query = 'view=collection&types=submit&project=%s' % prj
222         if states:
223             query += '&states=%s' % states
224         if pkg:
225             query += '&package=%s' % pkg
226         url = core.makeurl(self.apiurl, ['request'], query)
227
228         root = ET.parse(self.core_http(core.http_GET, url))
229         for req in root.findall('request'):
230             yield req.get('id'), req.find('state').get('name'), \
231                   req.find('description').text
232
233     def set_global_flag(self, flag, value, prj, pkg=None):
234         """
235         Set global flag in meta
236         Supported flag: publish,build,useforbuild,debuginfo
237         Supported values: enable,disable
238         """
239         supported_flags = ('publish', 'build', 'useforbuild', 'debuginfo')
240         if flag not in supported_flags:
241             raise RepaException("flag %s is not supported. "
242                                 "supported flags: %s" % \
243                                     (flag, ', '.join(supported_flags)))
244         supported_vals = ('enable', 'disable')
245         if value not in supported_vals:
246             raise RepaException("value %s is not supported. "
247                                 "supported values: %s" % \
248                                 (value, ', '.join(supported_vals)))
249         meta = self.get_meta(prj, pkg)
250         root = ET.fromstring(meta)
251         elem = root.find(flag)
252         if elem is None:
253             elem = ET.SubElement(root, flag)
254         elem.clear()
255         ET.SubElement(elem, value)
256         self.set_meta(ET.tostring(root), prj, pkg)
257
258     def get_file_list(self, prj, pkg):
259         """Get file list from OBS."""
260         url = core.makeurl(self.apiurl,
261                            ['source', prj, pkg]);
262         files=[]
263         try:
264             xml = self.core_http(core.http_GET, url).read()
265             root = ET.fromstring(''.join(xml))
266             for entry in root.findall("entry"):
267                 files.append(entry.get('name'))
268             return files
269         except OSCError as error:
270             if error.message == "HTTP Error 404: Not Found":
271                 return None
272             raise
273
274     def get_file(self, prj, pkg, fname, rev=None):
275         """Get file content from OBS."""
276         query = {'expand': 1}
277         if rev:
278             query['rev'] = rev
279         encoded_fname = core.pathname2url(
280             fname.encode(locale.getpreferredencoding(), 'replace'))
281         url = core.makeurl(self.apiurl,
282                            ['source', prj, pkg, encoded_fname],
283                            query=query)
284         try:
285             return self.core_http(core.http_GET, url).read()
286         except OSCError as error:
287             if error.message == "HTTP Error 404: Not Found":
288                 return None
289             raise
290
291     def get_package_list(self, prj, deleted=None):
292         """Get package list of the project"""
293         query = {}
294         if deleted:
295             query['deleted'] = 1
296         else:
297             query['deleted'] = 0
298
299         url = core.makeurl(self.apiurl, ['source', prj], query)
300         _file = core.http_GET(url)
301         root = ET.parse(_file).getroot()
302         return [node.get('name') for node in root.findall('entry')]
303