Putting names at the top of files is is not recommended. Collective wisdom for
[opensuse:osc.git] / osc / fetch.py
1 #!/usr/bin/python
2
3 # Copyright (C) 2006 Novell Inc.  All rights reserved.
4 # This program is free software; it may be used, copied, modified
5 # and distributed under the terms of the GNU General Public Licence,
6 # either version 2, or (at your option) any later version.
7
8 import sys, os
9 import urllib2
10 from urlgrabber.grabber import URLGrabber, URLGrabError
11 from urlgrabber.mirror import MirrorGroup
12 try:
13     from meter import TextMeter
14 except:
15     TextMeter = None
16
17
18 def join_url(self, base_url, rel_url):
19     """to override _join_url of MirrorGroup, because we want to
20     pass full URLs instead of base URL where relative_url is added later...
21     IOW, we make MirrorGroup ignore relative_url""" 
22     return base_url
23
24
25 class Fetcher:
26     def __init__(self, cachedir = '/tmp', api_host_options = {}, urllist = [], http_debug = False, cookiejar = None):
27
28         __version__ = '0.1'
29         __user_agent__ = 'osbuild/%s' % __version__
30
31         # set up progress bar callback
32         if sys.stdout.isatty() and TextMeter:
33             self.progress_obj = TextMeter(fo=sys.stdout)
34         else:
35             self.progress_obj = None
36
37
38         self.cachedir = cachedir
39         self.urllist = urllist
40         self.http_debug = http_debug
41
42         passmgr = urllib2.HTTPPasswordMgrWithDefaultRealm()
43         for host in api_host_options.keys():
44             passmgr.add_password(None, host, api_host_options[host]['user'], api_host_options[host]['pass'])
45         openers = (urllib2.HTTPBasicAuthHandler(passmgr), )
46         if cookiejar:
47             openers += (urllib2.HTTPCookieProcessor(cookiejar), )
48         self.gr = URLGrabber(user_agent=__user_agent__,
49                             keepalive=1,
50                             opener = urllib2.build_opener(*openers),
51                             progress_obj=self.progress_obj,
52                             failure_callback=(self.failureReport,(),{}),
53                             )
54
55
56     def failureReport(self, errobj):
57         """failure output for failovers from urlgrabber"""
58
59         #log(0, '%s: %s' % (errobj.url, str(errobj.exception)))
60         #log(0, 'Trying other mirror.')
61         print 'Trying upstream server for %s (%s), since it is not on %s.' \
62                 % (self.curpac, self.curpac.project, errobj.url.split('/')[2])
63         raise errobj.exception
64
65
66     def fetch(self, pac):
67         # for use by the failure callback
68         self.curpac = pac
69
70         MirrorGroup._join_url = join_url
71         mg = MirrorGroup(self.gr, pac.urllist)
72
73         if self.http_debug:
74             print
75             print 'URLs to try for package \'%s\':' % pac
76             print '\n'.join(pac.urllist)
77             print
78
79         try:
80             # it returns the filename
81             ret = mg.urlgrab(pac.filename, 
82                              filename=pac.fullfilename, 
83                              text = '(%s) %s' %(pac.project, pac.filename))
84
85         except URLGrabError, e:
86             print
87             print >>sys.stderr, 'Error:', e.strerror
88             print >>sys.stderr, 'Failed to retrieve %s from the following locations (in order):' % pac.filename
89             print >>sys.stderr, '\n'.join(pac.urllist)
90
91             sys.exit(1)
92         
93
94     def dirSetup(self, pac):
95         dir = os.path.join(self.cachedir, pac.localdir)
96         if not os.path.exists(dir):
97             try:
98                 os.makedirs(dir, mode=0755)
99             except OSError, e:
100                 print >>sys.stderr, 'packagecachedir is not writable for you?'
101                 print >>sys.stderr, e
102                 sys.exit(1)
103
104
105     def run(self, buildinfo):
106         for i in buildinfo.deps:
107             i.makeurls(self.cachedir, self.urllist)
108
109             if os.path.exists(os.path.join(i.localdir, i.fullfilename)):
110                 #print 'cached:', i.fullfilename
111                 pass
112             else:
113                 self.dirSetup(i)
114
115                 try:
116                     # if there isn't a progress bar, there is no output at all
117                     if not self.progress_obj:
118                         print '(%s) %s' % (i.project, i.filename)
119                     self.fetch(i)
120
121                 except KeyboardInterrupt:
122                     print 'Cancelled by user (ctrl-c)'
123                     print 'Exiting.'
124                     if os.path.exists(i.fullfilename):
125                         print 'Cleaning up incomplete file', i.fullfilename
126                         os.unlink(i.fullfilename)
127                     sys.exit(0)
128
129
130
131 def verify_pacs(pac_list):
132     """Take a list of rpm filenames and run rpm -K on them. 
133
134        In case of failure, exit.
135
136        Check all packages in one go, since this takes only 6 seconds on my Athlon 700
137        instead of 20 when calling 'rpm -K' for each of them.
138        """
139     import subprocess
140
141     if not pac_list:
142         return
143         
144     # don't care about the return value because we check the
145     # output anyway, and rpm always writes to stdout.
146
147     # save locale first (we rely on English rpm output here)
148     saved_LC_ALL = os.environ.get('LC_ALL')
149     os.environ['LC_ALL'] = 'en_EN'
150
151     o = subprocess.Popen(['rpm', '-K'] + pac_list, stdout=subprocess.PIPE,
152                 stderr=subprocess.STDOUT, close_fds=True).stdout
153
154     # restore locale
155     if saved_LC_ALL: os.environ['LC_ALL'] = saved_LC_ALL;
156     else: os.environ.pop('LC_ALL')
157
158     for line in o.readlines():
159
160         if not 'OK' in line:
161             print 
162             print >>sys.stderr, 'The following package could not be verified:'
163             print >>sys.stderr, line
164             sys.exit(1)
165
166         if 'NOT OK' in line:
167             print 
168             print >>sys.stderr, 'The following package could not be verified:'
169             print >>sys.stderr, line
170
171             if 'MISSING KEYS' in line:
172                 missing_key = line.split('#')[-1].split(')')[0]
173
174                 print >>sys.stderr, """
175 - If the key is missing, install it first.
176   For example, do the following:
177     gpg --keyserver pgp.mit.edu --recv-keys %(name)s
178     gpg --armor --export %(name)s > %(dir)s/keyfile-%(name)s
179   and, as root:
180     rpm --import %(dir)s/keyfile-%(name)s
181
182   Then, just start the build again.
183
184 - If you do not trust the packages, you should configure osc build for XEN or KVM
185
186 - You may use --no-verify to skip the verification (which is a risk for your system).
187 """ % {'name': missing_key, 
188        'dir': os.path.expanduser('~')}
189
190             else:
191                 print >>sys.stderr, """
192 - If the signature is wrong, you may try deleting the package manually
193   and re-run this program, so it is fetched again.
194 """
195
196             sys.exit(1)
197
198