1 # Copyright (C) 2006 Novell Inc. All rights reserved.
2 # This program is free software; it may be used, copied, modified
3 # and distributed under the terms of the GNU General Public Licence,
4 # either version 2, or (at your option) any later version.
8 from urlgrabber.grabber import URLGrabError
9 from urlgrabber.mirror import MirrorGroup
10 from core import makeurl, streamfile
11 from util import packagequery, cpio
16 from meter import TextMeter
21 def join_url(self, base_url, rel_url):
22 """to override _join_url of MirrorGroup, because we want to
23 pass full URLs instead of base URL where relative_url is added later...
24 IOW, we make MirrorGroup ignore relative_url"""
28 def __init__(self, progress_obj = None):
29 self.progress_obj = progress_obj
31 def urlgrab(self, url, filename, text = None, **kwargs):
32 if url.startswith('file://'):
33 file = url.replace('file://', '', 1)
34 if os.path.isfile(file):
37 raise URLGrabError(2, 'Local file \'%s\' does not exist' % file)
38 f = open(filename, 'wb')
41 for i in streamfile(url, progress_obj=self.progress_obj, text=text):
43 except urllib2.HTTPError, e:
44 exc = URLGrabError(14, str(e))
50 raise URLGrabError(4, str(e))
56 def __init__(self, cachedir = '/tmp', api_host_options = {}, urllist = [], http_debug = False,
57 cookiejar = None, offline = False, enable_cpio = True):
58 # set up progress bar callback
59 if sys.stdout.isatty() and TextMeter:
60 self.progress_obj = TextMeter(fo=sys.stdout)
62 self.progress_obj = None
64 self.cachedir = cachedir
65 self.urllist = urllist
66 self.http_debug = http_debug
67 self.offline = offline
69 self.enable_cpio = enable_cpio
71 passmgr = urllib2.HTTPPasswordMgrWithDefaultRealm()
72 for host in api_host_options.keys():
73 passmgr.add_password(None, host, api_host_options[host]['user'], api_host_options[host]['pass'])
74 openers = (urllib2.HTTPBasicAuthHandler(passmgr), )
76 openers += (urllib2.HTTPCookieProcessor(cookiejar), )
77 self.gr = OscFileGrabber(progress_obj=self.progress_obj)
79 def failureReport(self, errobj):
80 """failure output for failovers from urlgrabber"""
81 if errobj.url.startswith('file://'):
83 print 'Trying openSUSE Build Service server for %s (%s), not found at %s.' \
84 % (self.curpac, self.curpac.project, errobj.url.split('/')[2])
87 def __add_cpio(self, pac):
88 prpap = '%s/%s/%s/%s' % (pac.project, pac.repository, pac.repoarch, pac.repopackage)
89 self.cpio.setdefault(prpap, {})[pac.repofilename] = pac
91 def __fetch_cpio(self, apiurl):
92 from urllib import quote_plus
93 for prpap, pkgs in self.cpio.iteritems():
94 project, repo, arch, package = prpap.split('/', 3)
95 query = ['binary=%s' % quote_plus(i) for i in pkgs.keys()]
96 query.append('view=cpio')
97 tmparchive = tmpfile = None
99 (fd, tmparchive) = tempfile.mkstemp(prefix='osc_build_cpio')
100 (fd, tmpfile) = tempfile.mkstemp(prefix='osc_build')
101 url = makeurl(apiurl, ['build', project, repo, arch, package], query=query)
102 sys.stdout.write("preparing download ...\r")
104 self.gr.urlgrab(url, filename = tmparchive, text = 'fetching cpio for \'%s\'' % project)
105 archive = cpio.CpioRead(tmparchive)
108 # XXX: we won't have an .errors file because we're using
109 # getbinarylist instead of the public/... route (which is
110 # routed to getbinaries (but that won't work for kiwi products))
111 if hdr.filename == '.errors':
112 archive.copyin_file(hdr.filename)
113 raise oscerr.APIError('CPIO archive is incomplete (see .errors file)')
114 if package == '_repository':
115 pac = pkgs[hdr.filename.rsplit('.', 1)[0]]
117 # this is a kiwi product
118 pac = pkgs[hdr.filename]
119 archive.copyin_file(hdr.filename, os.path.dirname(tmpfile), os.path.basename(tmpfile))
120 self.move_package(tmpfile, pac.localdir, pac)
121 # check if we got all packages... (because we've no .errors file)
122 for pac in pkgs.itervalues():
123 if not os.path.isfile(pac.fullfilename):
124 raise oscerr.APIError('failed to fetch file \'%s\': ' \
125 'does not exist in CPIO archive' % pac.repofilename)
127 if not tmparchive is None and os.path.exists(tmparchive):
128 os.unlink(tmparchive)
129 if not tmpfile is None and os.path.exists(tmpfile):
132 def fetch(self, pac, prefix=''):
133 # for use by the failure callback
136 MirrorGroup._join_url = join_url
137 mg = MirrorGroup(self.gr, pac.urllist, failure_callback=(self.failureReport,(),{}))
140 print >>sys.stderr, '\nURLs to try for package \'%s\':' % pac
141 print >>sys.stderr, '\n'.join(pac.urllist)
144 (fd, tmpfile) = tempfile.mkstemp(prefix='osc_build')
147 mg.urlgrab(pac.filename,
149 text = '%s(%s) %s' %(prefix, pac.project, pac.filename))
150 self.move_package(tmpfile, pac.localdir, pac)
151 except URLGrabError, e:
152 if self.enable_cpio and e.errno == 256:
156 print >>sys.stderr, 'Error:', e.strerror
157 print >>sys.stderr, 'Failed to retrieve %s from the following locations (in order):' % pac.filename
158 print >>sys.stderr, '\n'.join(pac.urllist)
162 if os.path.exists(tmpfile):
165 def move_package(self, tmpfile, destdir, pac_obj = None):
167 pkgq = packagequery.PackageQuery.query(tmpfile, extra_rpmtags=(1044, 1051, 1052))
168 canonname = pkgq.canonname()
169 fullfilename = os.path.join(destdir, canonname)
170 if pac_obj is not None:
171 pac_obj.filename = canonname
172 pac_obj.fullfilename = fullfilename
173 shutil.move(tmpfile, fullfilename)
175 def dirSetup(self, pac):
176 dir = os.path.join(self.cachedir, pac.localdir)
177 if not os.path.exists(dir):
179 os.makedirs(dir, mode=0755)
181 print >>sys.stderr, 'packagecachedir is not writable for you?'
182 print >>sys.stderr, e
185 def run(self, buildinfo):
187 all = len(buildinfo.deps)
188 for i in buildinfo.deps:
189 i.makeurls(self.cachedir, self.urllist)
190 if os.path.exists(i.fullfilename):
193 needed = all - cached
195 miss = 100.0 * needed / all
196 print "%.1f%% cache miss. %d/%d dependencies cached.\n" % (miss, cached, all)
198 for i in buildinfo.deps:
199 i.makeurls(self.cachedir, self.urllist)
200 if not os.path.exists(i.fullfilename):
202 raise oscerr.OscIOError(None, 'Missing package \'%s\' in cache: --offline not possible.' % i.fullfilename)
205 # if there isn't a progress bar, there is no output at all
206 if not self.progress_obj:
207 print '%d/%d (%s) %s' % (done, needed, i.project, i.filename)
209 if self.progress_obj:
210 print " %d/%d\r" % (done, needed),
213 except KeyboardInterrupt:
214 print 'Cancelled by user (ctrl-c)'
219 self.__fetch_cpio(buildinfo.apiurl)
221 prjs = buildinfo.projects.keys()
223 dest = "%s/%s" % (self.cachedir, i)
224 if not os.path.exists(dest):
225 os.makedirs(dest, mode=0755)
228 url = "%s/source/%s/_pubkey" % (buildinfo.apiurl, i)
230 OscFileGrabber().urlgrab(url, dest)
231 if not i in buildinfo.prjkeys: # not that many keys usually
232 buildinfo.keys.append(dest)
233 buildinfo.prjkeys.append(i)
234 except KeyboardInterrupt:
235 print 'Cancelled by user (ctrl-c)'
237 if os.path.exists(dest):
240 except URLGrabError, e:
242 print >>sys.stderr, "can't fetch key for %s: %s" %(i, e.strerror)
243 print >>sys.stderr, "url: %s" % url
245 if os.path.exists(dest):
249 # try key from parent project
250 if len(l) > 1 and l[1] and not l[0] in buildinfo.projects:
253 def verify_pacs_old(pac_list):
254 """Take a list of rpm filenames and run rpm -K on them.
256 In case of failure, exit.
258 Check all packages in one go, since this takes only 6 seconds on my Athlon 700
259 instead of 20 when calling 'rpm -K' for each of them.
266 # don't care about the return value because we check the
267 # output anyway, and rpm always writes to stdout.
269 # save locale first (we rely on English rpm output here)
270 saved_LC_ALL = os.environ.get('LC_ALL')
271 os.environ['LC_ALL'] = 'en_EN'
273 o = subprocess.Popen(['rpm', '-K'] + pac_list, stdout=subprocess.PIPE,
274 stderr=subprocess.STDOUT, close_fds=True).stdout
277 if saved_LC_ALL: os.environ['LC_ALL'] = saved_LC_ALL
278 else: os.environ.pop('LC_ALL')
280 for line in o.readlines():
284 print >>sys.stderr, 'The following package could not be verified:'
285 print >>sys.stderr, line
290 print >>sys.stderr, 'The following package could not be verified:'
291 print >>sys.stderr, line
293 if 'MISSING KEYS' in line:
294 missing_key = line.split('#')[-1].split(')')[0]
296 print >>sys.stderr, """
297 - If the key (%(name)s) is missing, install it first.
298 For example, do the following:
299 osc signkey PROJECT > file
301 rpm --import %(dir)s/keyfile-%(name)s
303 Then, just start the build again.
305 - If you do not trust the packages, you should configure osc build for XEN or KVM
307 - You may use --no-verify to skip the verification (which is a risk for your system).
308 """ % {'name': missing_key,
309 'dir': os.path.expanduser('~')}
312 print >>sys.stderr, """
313 - If the signature is wrong, you may try deleting the package manually
314 and re-run this program, so it is fetched again.
321 """Take a list of rpm filenames and verify their signatures.
323 In case of failure, exit.
326 pac_list = [ i.fullfilename for i in bi.deps ]
327 if not conf.config['builtin_signature_check']:
328 return verify_pacs_old(pac_list)
334 raise oscerr.APIError("can't verify packages due to lack of GPG keys")
336 print "using keys from", ', '.join(bi.prjkeys)
340 checker = checker.Checker()
342 checker.readkeys(bi.keys)