Implemented --showurls option
[tools/repa.git] / repa / common.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 Common module.
29 Common functions, classes, exceptions.
30 """
31
32 import sys
33 import os
34 import time
35 import json
36 from functools import wraps, partial
37
38 OBS_PROJECT_PREFIX = "home:prerelease:"
39
40 class RepaException(Exception):
41     """Custom repa exception. All repa modules should use it."""
42     pass
43
44 def get_project_by_name(obs, name, target):
45     """Lookup for a project in OBS by submission or group name."""
46     projects = list(obs.get_projects('^%s%s:%s$' % (OBS_PROJECT_PREFIX, target,
47                                                     name.replace('/', ':'))))
48     if not projects:
49         raise RepaException('OBS project not found for %s %s' % \
50                             (target, name))
51     if len(projects) > 1:
52
53         plist = '\n  '.join(prj for prj, _desc, _build_results in projects)
54         raise RepaException('%s %s resolves into multiple projects:\n  %s' % \
55                             (target, name, plist))
56
57     return projects[0][0], json.loads(projects[0][1]), projects[0][2]
58
59
60 def _resolve_submissions(obs, name, target):
61     """Get list of submissions with meta. Resolves submitgroups."""
62     project, meta, _bresults = get_project_by_name(obs, name, target)
63     if name.startswith('submitgroup'):
64         for subm in meta['submissions']:
65             sprj, smeta, _bresults = get_project_by_name(obs, subm, target)
66             yield subm, sprj, smeta
67     else:
68         yield name, project, meta
69
70
71 def delete_project(obs, name, target):
72     """Delete OBS project related to submission or submit group."""
73     project = get_project_by_name(obs, name, target)[0]
74     obs.delete_project(project)
75
76
77 def accept_or_reject(obs, submission, state, target, comment=''):
78     """Create SRs and set their state for one submission or for a group."""
79     for name, project, meta in _resolve_submissions(obs, submission, target):
80         # osc submitreq [OPTIONS] SOURCEPRJ SOURCEPKG DESTPRJ [DESTPKG]
81         # osc request accept [-m TEXT] ID
82         print "submission %s" % str(name)
83
84         commit = meta.get('git_commit') or meta['git_tag']
85         submitter = meta.get('submitter')
86         message = ''
87         if submitter:
88             message = "Submitter: %s\n" % submitter
89
90         message += "Comments: %s \nGit project: %s\nTag: %s\nCommit: %s" \
91             % (comment or "submission %s" % str(name),
92               meta['projects'][0],
93               meta['git_tag'],
94               commit)
95
96         for pkg in obs.get_source_packages(project):
97             # Create SR
98             try:
99                 reqid = obs.create_sr(project, pkg, str(meta['obs_target_prj']),
100                                       message=str(message))
101             except RepaException:
102                 if state == 'declined':
103                     # Broken sources. Try to avoid queryng source by
104                     # providing revision 1. It always exists and it doesn't
105                     # matter which revision to reject.
106                     reqid = obs.create_sr(project, pkg,
107                                           str(meta['obs_target_prj']),
108                                           message=str(message), revision=1)
109                 else:
110                     raise
111
112             print 'package %s: created SR %s' % (pkg, reqid)
113
114             # and immediately set its state
115             message = "SR %s is set to %s" % (reqid, state)
116             if comment:
117                 message += comment
118             obs.set_sr_state(reqid, state=state,
119                              message=str(message), force=True)
120             print 'set SR state to', state
121
122     # delete submit group
123     if submission.startswith('submitgroup'):
124         delete_project(obs, submission, target)
125
126 class CancelRetryError(Exception):
127     """Exception for handling cancelling of the retry loop. Needed for
128     transparently re-raising the previous exception."""
129     def __init__(self):
130         Exception.__init__(self)
131         self.typ, self.val, self.backtrace = sys.exc_info()
132
133 def retry(exceptions, tries=10, sleep=1):
134     """Decorator for re-trying function calls"""
135     def decorator(func):
136         """The "real" decorator function"""
137         @wraps(func)
138         def wrap(*args, **kwargs):
139             """Wrapper for re-trying func"""
140             for attempt in range(1, tries + 1):
141                 try:
142                     return func(*args, **kwargs)
143                 except CancelRetryError as err:
144                     raise err.typ, err.val, err.backtrace
145                 except exceptions as err:
146                     if attempt >= tries:
147                         raise
148                     elif sleep:
149                         time.sleep(sleep)
150         return wrap
151     return decorator
152
153 class Colorizer:
154     """Colorize text with ANSI colors."""
155     colors = {'black': '\033[90m', 'red':     '\033[91m',
156               'green': '\033[92m', 'yellow':  '\033[93m',
157               'blue':  '\033[94m', 'magenta': '\033[95m',
158               'cyan':  '\033[96m', 'white':   '\033[97m'
159              }
160     reset = '\033[0m'
161
162     def __init__(self, enable=True):
163         self.enabled = enable
164
165     def __getattr__(self, name):
166         if name in self.colors:
167             return partial(self.colorize, color=name)
168
169     def disable(self):
170         """Disable colorizing."""
171         self.enabled = False
172
173     def enable(self):
174         """Enable colorizing."""
175         self.enabled = True
176
177     def colorize(self, text, color):
178         """Colorize text."""
179         if self.enabled:
180             return self.reset + self.colors[color] + text + self.reset
181         else:
182             return text
183
184 def get_download_url(meta):
185     """Get download url from meta."""
186     if 'download_url' in meta:
187         return meta['download_url']
188     # Guess url from image url if download_url is not in the meta
189     if 'images' not in meta:
190         return
191     for img in meta['images']:
192         if 'url' in img:
193             return img['url'].split('images')[0]
194
195
196 def get_obs_url(meta, buildurl='https://build.tizen.org'):
197     """Get obs project url from meta."""
198     if 'obs_url' in meta:
199         return meta['obs_url']
200     # Make it from git tag and obs_target_prj if obs_url is not in the meta
201     if 'obs_target_prj' not in meta:
202         return
203     if 'name' not in meta and 'git_tag' not in meta:
204         return
205     name = meta.get('git_tag') or meta.get('name')
206     return os.path.join(buildurl, 'project/show?project=home:prerelease:%s:%s'
207                         % (meta['obs_target_prj'], name.replace('/', ':')))
208