normalize apiurl
[opensuse:osc.git] / osc / conf.py
1 # Copyright (C) 2006-2009 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 version 3 (at your option).
5
6 """Read osc configuration and store it in a dictionary
7
8 This module reads and parses ~/.oscrc. The resulting configuration is stored
9 for later usage in a dictionary named 'config'.
10 The .oscrc is kept mode 0600, so that it is not publically readable.
11 This gives no real security for storing passwords.
12 If in doubt, use your favourite keyring.
13 Password is stored on ~/.oscrc as bz2 compressed and base64 encoded, so that is fairly
14 large and not to be recognized or remembered easily by an occasional spectator.
15
16 If information is missing, it asks the user questions.
17
18 After reading the config, urllib2 is initialized.
19
20 The configuration dictionary could look like this:
21
22 {'apisrv': 'https://api.opensuse.org/',
23  'user': 'joe',
24  'api_host_options': {'api.opensuse.org': {'user': 'joe', 'pass': 'secret'},
25                       'apitest.opensuse.org': {'user': 'joe', 'pass': 'secret',
26                                                'http_headers':(('Host','api.suse.de'),
27                                                                ('User','faye'))},
28                       'foo.opensuse.org': {'user': 'foo', 'pass': 'foo'}},
29  'build-cmd': '/usr/bin/build',
30  'build-root': '/abuild/oscbuild-%(repo)s-%(arch)s',
31  'packagecachedir': '/var/cache/osbuild',
32  'su-wrapper': 'sudo',
33  }
34
35 """
36
37 import OscConfigParser
38 from osc import oscerr
39 from oscsslexcp import NoSecureSSLError
40 import os
41
42 GENERIC_KEYRING = False
43 GNOME_KEYRING = False
44
45 try:
46     import keyring
47     GENERIC_KEYRING = True
48
49 except:
50     try:
51         import gobject
52         gobject.set_application_name('osc')
53         import gnomekeyring
54         if os.environ['GNOME_DESKTOP_SESSION_ID']:
55             # otherwise gnome keyring bindings spit out errors, when you have
56             # it installed, but you are not under gnome
57             # (even though hundreds of gnome-keyring daemons got started in parallel)
58             # another option would be to support kwallet here
59             GNOME_KEYRING = gnomekeyring.is_available()
60     except:
61         pass
62
63 DEFAULTS = { 'apiurl': 'https://api.opensuse.org',
64              'user': 'your_username',
65              'pass': 'your_password',
66              'passx': '',
67              'packagecachedir': '/var/tmp/osbuild-packagecache',
68              'su-wrapper': 'su -c',
69
70              # build type settings
71              'build-cmd': '/usr/bin/build',
72              'build-type': '', # may be empty for chroot, kvm or xen
73              'build-root': '/var/tmp/build-root',
74              'build-uid': '', # use the default provided by build
75              'build-device': '', # required for VM builds
76              'build-memory': '',# required for VM builds
77              'build-swap': '',  # optional for VM builds
78              'build-vmdisk-rootsize': '', # optional for VM builds
79              'build-vmdisk-swapsize': '', # optional for VM builds
80
81              'build-jobs': os.sysconf('SC_NPROCESSORS_ONLN'), # compile with N jobs
82              'icecream': '0',
83
84              'debug': '0',
85              'http_debug': '0',
86              'verbose': '1',
87              'traceback': '0',
88              'post_mortem': '0',
89              'use_keyring': '1',
90              'gnome_keyring': '1',
91              'cookiejar': '~/.osc_cookiejar',
92              # enable project tracking by default
93              'do_package_tracking': '1',
94              # default for osc build
95              'extra-pkgs': '',
96              # default repository
97              'build_repository': 'openSUSE_Factory',
98              # default project for branch or bco
99              'getpac_default_project': 'openSUSE:Factory',
100              # alternate filesystem layout: have multiple subdirs, where colons were.
101              'checkout_no_colon': '0',
102              # local files to ignore with status, addremove, ....
103              # local files to ignore with status, addremove, ....
104              'exclude_glob': '.osc CVS .svn .* _linkerror *~ #*# *.orig *.bak *.changes.*',
105              # keep passwords in plaintext. If you see this comment, your osc
106              # already uses the encrypted password, and only keeps them in plain text
107              # for backwards compatibility. Default will change to 0 in future releases.
108              'plaintext_passwd': '1',
109              # limit the age of requests shown with 'osc req list'.
110              # this is a default only, can be overridden by 'osc req list -D NNN'
111              # Use 0 for unlimted.
112              'request_list_days': 30,
113              # check for unversioned/removed files before commit
114              'check_filelist': '1',
115              # External scripts to validate sources, esp before commit. This is a directory
116              'source_validator_directory': '/usr/lib/osc/source_validators',
117              # check for pending requests after executing an action (e.g. checkout, update, commit)
118              'check_for_request_on_action': '0',
119              # what to do with the source package if the submitrequest has been accepted
120              'submitrequest_on_accept_action': '',
121              'request_show_interactive': '0',
122              'linkcontrol': '0',
123
124              # Maintenance defaults to OBS instance defaults
125              'maintained_attribute': 'OBS:Maintained',
126              'maintained_update_project_attribute': 'OBS:UpdateProject',
127              'show_download_progress': '0',
128 }
129
130 # being global to this module, this dict can be accessed from outside
131 # it will hold the parsed configuration
132 config = DEFAULTS.copy()
133
134 boolean_opts = ['debug', 'do_package_tracking', 'http_debug', 'post_mortem', 'traceback', 'check_filelist', 'plaintext_passwd',
135     'checkout_no_colon', 'check_for_request_on_action', 'linkcontrol', 'show_download_progress', 'request_show_interactive',
136     'use_keyring', 'gnome_keyring']
137
138 api_host_options = ['user', 'pass', 'passx', 'aliases', 'http_headers', 'email', 'sslcertck', 'cafile', 'capath', 'trusted_prj']
139
140 new_conf_template = """
141 [general]
142
143 # URL to access API server, e.g. %(apiurl)s
144 # you also need a section [%(apiurl)s] with the credentials
145 apiurl = %(apiurl)s
146
147 # Downloaded packages are cached here. Must be writable by you.
148 #packagecachedir = %(packagecachedir)s
149
150 # Wrapper to call build as root (sudo, su -, ...)
151 #su-wrapper = %(su-wrapper)s
152
153 # rootdir to setup the chroot environment
154 # can contain %%(repo)s, %%(arch)s, %%(project)s and %%(package)s for replacement, e.g.
155 # /srv/oscbuild/%%(repo)s-%%(arch)s or
156 # /srv/oscbuild/%%(repo)s-%%(arch)s-%%(project)s-%%(package)s
157 #build-root = %(build-root)s
158
159 # compile with N jobs (default: "getconf _NPROCESSORS_ONLN")
160 #build-jobs = N
161
162 # build-type to use - values can be (depending on the capabilities of the 'build' script)
163 # empty    -  chroot build
164 # kvm      -  kvm VM build  (needs build-device, build-swap, build-memory)
165 # xen      -  xen VM build  (needs build-device, build-swap, build-memory)
166 #   experimental:
167 #     qemu -  qemu VM build
168 #     lxc  -  lxc build
169 #build-type =
170
171 # build-device is the disk-image file to use as root for VM builds
172 # e.g. /var/tmp/FILE.root
173 #build-device = /var/tmp/FILE.root
174
175 # build-swap is the disk-image to use as swap for VM builds
176 # e.g. /var/tmp/FILE.swap
177 #build-swap = /var/tmp/FILE.swap
178
179 # build-memory is the amount of memory used in the VM
180 # value in MB - e.g. 512
181 #build-memory = 512
182
183 # build-vmdisk-rootsize is the size of the disk-image used as root in a VM build
184 # values in MB - e.g. 4096
185 #build-vmdisk-rootsize = 4096
186
187 # build-vmdisk-swapsize is the size of the disk-image used as swap in a VM build
188 # values in MB - e.g. 1024
189 #build-vmdisk-swapsize = 1024
190
191 # Numeric uid:gid to assign to the "abuild" user in the build-root
192 # or "caller" to use the current users uid:gid
193 # This is convenient when sharing the buildroot with ordinary userids
194 # on the host.
195 # This should not be 0
196 # build-uid =
197
198 # extra packages to install when building packages locally (osc build)
199 # this corresponds to osc build's -x option and can be overridden with that
200 # -x '' can also be given on the command line to override this setting, or
201 # you can have an empty setting here.
202 #extra-pkgs = vim gdb strace
203
204 # build platform is used if the platform argument is omitted to osc build
205 #build_repository = %(build_repository)s
206
207 # default project for getpac or bco
208 #getpac_default_project = %(getpac_default_project)s
209
210 # alternate filesystem layout: have multiple subdirs, where colons were.
211 #checkout_no_colon = %(checkout_no_colon)s
212
213 # local files to ignore with status, addremove, ....
214 #exclude_glob = %(exclude_glob)s
215
216 # keep passwords in plaintext. If you see this comment, your osc
217 # already uses the encrypted password, and only keeps them in plain text
218 # for backwards compatibility. Default will change to 0 in future releases.
219 # You can remove the plaintext password without harm, if you do not need
220 # backwards compatibility.
221 #plaintext_passwd = %(plaintext_passwd)s
222
223 # limit the age of requests shown with 'osc req list'.
224 # this is a default only, can be overridden by 'osc req list -D NNN'
225 # Use 0 for unlimted.
226 #request_list_days = %(request_list_days)s
227
228 # show info useful for debugging
229 #debug = 1
230
231 # show HTTP traffic useful for debugging
232 #http_debug = 1
233
234 # jump into the debugger in case of errors
235 #post_mortem = 1
236
237 # print call traces in case of errors
238 #traceback = 1
239
240 # use KDE/Gnome/MacOS/Windows keyring for credentials if available
241 #use_keyring = 1
242
243 # check for unversioned/removed files before commit
244 #check_filelist = 1
245
246 # check for pending requests after executing an action (e.g. checkout, update, commit)
247 #check_for_request_on_action = 0
248
249 # what to do with the source package if the submitrequest has been accepted. If
250 # nothing is specified the API default is used
251 #submitrequest_on_accept_action = cleanup|update|noupdate
252
253 #review requests interactively (default: off)
254 #request_show_review = 1
255
256 # Directory with executables to validate sources, esp before committing
257 #source_validator_directory = /usr/lib/osc/source_validators
258
259 [%(apiurl)s]
260 user = %(user)s
261 pass = %(pass)s
262 passx = %(passx)s
263 # set aliases for this apiurl
264 # aliases = foo, bar
265 # email used in .changes, unless the one from osc meta prj <user> will be used
266 # email =
267 # additional headers to pass to a request, e.g. for special authentication
268 #http_headers = Host: foofoobar,
269 #       User: mumblegack
270 # Force using of keyring for this API
271 #keyring = 1
272 """
273
274
275 account_not_configured_text ="""
276 Your user account / password are not configured yet.
277 You will be asked for them below, and they will be stored in
278 %s for future use.
279 """
280
281 config_incomplete_text = """
282
283 Your configuration file %s is not complete.
284 Make sure that it has a [general] section.
285 (You can copy&paste the below. Some commented defaults are shown.)
286
287 """
288
289 config_missing_apiurl_text = """
290 the apiurl \'%s\' does not exist in the config file. Please enter
291 your credentials for this apiurl.
292 """
293
294 cookiejar = None
295
296 def parse_apisrv_url(scheme, apisrv):
297     import urlparse
298     if apisrv.startswith('http://') or apisrv.startswith('https://'):
299         return urlparse.urlsplit(apisrv)[0:2]
300     elif scheme != None:
301         # the split/join is needed to get a proper url (e.g. without a trailing slash)
302         return urlparse.urlsplit(urljoin(scheme, apisrv))[0:2]
303     else:
304         from urllib2 import URLError
305         msg = 'invalid apiurl \'%s\' (specify the protocol (http:// or https://))' % apisrv
306         raise URLError(msg)
307
308 def urljoin(scheme, apisrv):
309     return '://'.join([scheme, apisrv])
310
311 def get_apiurl_api_host_options(apiurl):
312     """
313     Returns all apihost specific options for the given apiurl, None if
314     no such specific optiosn exist.
315     """
316     # FIXME: in A Better World (tm) there was a config object which
317     # knows this instead of having to extract it from a url where it
318     # had been mingled into before.  But this works fine for now.
319
320     apiurl = urljoin(*parse_apisrv_url(None, apiurl))
321     try:
322         return config['api_host_options'][apiurl]
323     except KeyError:
324         raise oscerr.ConfigMissingApiurl('missing credentials for apiurl: \'%s\'' % apiurl,
325                                          '', apiurl)
326
327 def get_apiurl_usr(apiurl):
328     """
329     returns the user for this host - if this host does not exist in the
330     internal api_host_options the default user is returned.
331     """
332     # FIXME: maybe there should be defaults not just for the user but
333     # for all apihost specific options.  The ConfigParser class
334     # actually even does this but for some reason we don't use it
335     # (yet?).
336
337     import sys
338     try:
339         return get_apiurl_api_host_options(apiurl)['user']
340     except KeyError:
341         print >>sys.stderr, 'no specific section found in config file for host of [\'%s\'] - using default user: \'%s\'' \
342             % (apiurl, config['user'])
343         return config['user']
344
345
346
347 def init_basicauth(config):
348     """initialize urllib2 with the credentials for Basic Authentication"""
349
350     from osc.core import __version__
351     import cookielib
352
353     if config['api_host_options'][config['apiurl']]['sslcertck']:
354         try:
355             import oscssl
356             from M2Crypto import m2urllib2
357         except ImportError, e:
358             print e
359             raise NoSecureSSLError("M2Crypto is needed to access %s in a secure way.\nPlease install python-m2crypto." % config['apiurl'])
360
361     import urllib2
362
363
364     global cookiejar
365
366     import sys
367     if sys.version_info < (2, 6):
368         # HTTPS proxy is not supported in old urllib2. It only leads to an error
369         # or, at best, a warning.
370         if 'https_proxy' in os.environ:
371             del os.environ['https_proxy']
372         if 'HTTPS_PROXY' in os.environ:
373             del os.environ['HTTPS_PROXY']
374
375     if config['http_debug']:
376         # brute force
377         def urllib2_debug_init(self, debuglevel=0):
378             self._debuglevel = 1
379         urllib2.AbstractHTTPHandler.__init__ = urllib2_debug_init
380
381     authhandler = urllib2.HTTPBasicAuthHandler( \
382         urllib2.HTTPPasswordMgrWithDefaultRealm())
383
384     cookie_file = os.path.expanduser(config['cookiejar'])
385     cookiejar = cookielib.LWPCookieJar(cookie_file)
386     try:
387         cookiejar.load(ignore_discard=True)
388     except IOError:
389         try:
390             open(cookie_file, 'w').close()
391             os.chmod(cookie_file, 0600)
392         except:
393             #print 'Unable to create cookiejar file: \'%s\'. Using RAM-based cookies.' % cookie_file
394             cookiejar = cookielib.CookieJar()
395
396
397     if config['api_host_options'][config['apiurl']]['sslcertck']:
398         cafile = capath = None
399         if 'capath' in config['api_host_options'][config['apiurl']]:
400             capath = config['api_host_options'][config['apiurl']]['capath']
401         if 'cafile' in config['api_host_options'][config['apiurl']]:
402             cafile = config['api_host_options'][config['apiurl']]['cafile']
403         if not cafile and not capath:
404             for i in ['/etc/pki/tls/cert.pem', '/etc/ssl/certs' ]:
405                 if os.path.isfile(i):
406                     cafile = i
407                     break
408                 elif os.path.isdir(i):
409                     capath = i
410                     break
411         ctx = oscssl.mySSLContext()
412         if ctx.load_verify_locations(capath=capath, cafile=cafile) != 1: raise Exception('No CA certificates found')
413         opener = m2urllib2.build_opener(ctx, oscssl.myHTTPSHandler(ssl_context = ctx, appname = 'osc'), urllib2.HTTPCookieProcessor(cookiejar), authhandler)
414     else:
415         import sys
416         print >>sys.stderr, "WARNING: SSL certificate checks disabled. Connection is insecure!\n"
417         opener = urllib2.build_opener(urllib2.HTTPCookieProcessor(cookiejar), authhandler)
418
419     urllib2.install_opener(opener)
420
421     opener.addheaders = [('User-agent', 'osc/%s' % __version__)]
422
423     # with None as first argument, it will always use this username/password
424     # combination for urls for which arg2 (apisrv) is a super-url
425     for host, auth in config['api_host_options'].iteritems():
426         authhandler.add_password(None, host, auth['user'], auth['pass'])
427
428
429 def get_configParser(conffile=None, force_read=False):
430     """
431     Returns an ConfigParser() object. After its first invocation the
432     ConfigParser object is stored in a method attribute and this attribute
433     is returned unless you pass force_read=True.
434     """
435     conffile = conffile or os.environ.get('OSC_CONFIG', '~/.oscrc')
436     conffile = os.path.expanduser(conffile)
437     if not get_configParser.__dict__.has_key('conffile'):
438         get_configParser.conffile = conffile
439     if force_read or not get_configParser.__dict__.has_key('cp') or conffile != get_configParser.conffile:
440         get_configParser.cp = OscConfigParser.OscConfigParser(DEFAULTS)
441         get_configParser.cp.read(conffile)
442         get_configParser.conffile = conffile
443     return get_configParser.cp
444
445 def config_set_option(section, opt, val=None, delete=False, update=True, **kwargs):
446     """
447     Sets a config option. If val is not specified the current/default value is
448     returned. If val is specified, opt is set to val and the new value is returned.
449     If an option was modified get_config is called with **kwargs unless update is set
450     to False (override_conffile defaults to config['conffile']).
451     If val is not specified and delete is True then the option is removed from the
452     config/reset to the default value.
453     """
454     def write_config(fname, cp):
455         """write new configfile in a safe way"""
456         try:
457             f = open(fname + '.new', 'w')
458             cp.write(f, comments=True)
459             f.close()
460             os.rename(fname + '.new', fname)
461         except:
462             if os.path.exists(fname + '.new'):
463                 os.unlink(fname + '.new')
464             raise
465
466     cp = get_configParser(config['conffile'])
467     # don't allow "internal" options
468     general_opts = [i for i in DEFAULTS.keys() if not i in ['user', 'pass', 'passx']]
469     if section != 'general':
470         section = config['apiurl_aliases'].get(section, section)
471         scheme, host = \
472             parse_apisrv_url(config.get('scheme', 'https'), section)
473         section = urljoin(scheme, host)
474
475     sections = {}
476     for url in cp.sections():
477         if url == 'general':
478             sections[url] = url
479         else:
480             scheme, host = \
481                 parse_apisrv_url(config.get('scheme', 'https'), url)
482             apiurl = urljoin(scheme, host)
483             sections[apiurl] = url
484
485     section = sections.get(section.rstrip('/'), section)
486     if not section in cp.sections():
487         raise oscerr.ConfigError('unknown section \'%s\'' % section, config['conffile'])
488     if section == 'general' and not opt in general_opts or \
489        section != 'general' and not opt in api_host_options:
490         raise oscerr.ConfigError('unknown config option \'%s\'' % opt, config['conffile'])
491     run = False
492     if val:
493         cp.set(section, opt, val)
494         write_config(config['conffile'], cp)
495         run = True
496     elif delete and cp.has_option(section, opt):
497         cp.remove_option(section, opt)
498         write_config(config['conffile'], cp)
499         run = True
500     if run and update:
501         kw = {'override_conffile': config['conffile']}
502         kw.update(kwargs)
503         get_config(**kw)
504     if cp.has_option(section, opt):
505         return (opt, cp.get(section, opt, raw=True))
506     return (opt, None)
507
508 def write_initial_config(conffile, entries, custom_template = ''):
509     """
510     write osc's intial configuration file. entries is a dict which contains values
511     for the config file (e.g. { 'user' : 'username', 'pass' : 'password' } ).
512     custom_template is an optional configuration template.
513     """
514     import StringIO, sys, base64
515     conf_template = custom_template or new_conf_template
516     config = DEFAULTS.copy()
517     config.update(entries)
518     config['passx'] = base64.b64encode(config['pass'].encode('bz2'))
519     # at this point use_keyring and gnome_keyring are str objects
520     if config['use_keyring'] == '1' and GENERIC_KEYRING:
521         protocol, host = \
522             parse_apisrv_url(None, config['apiurl'])
523         keyring.set_password(host, config['user'], config['pass'])
524         config['pass'] = ''
525         config['passx'] = ''
526     elif config['gnome_keyring'] == '1' and GNOME_KEYRING:
527         protocol, host = \
528             parse_apisrv_url(None, config['apiurl'])
529         gnomekeyring.set_network_password_sync(
530             user = config['user'],
531             password = config['pass'],
532             protocol = protocol,
533             server = host)
534         config['user'] = ''
535         config['pass'] = ''
536         config['passx'] = ''
537     if not config['plaintext_passwd']:
538         config['pass'] = ''
539     sio = StringIO.StringIO(conf_template.strip() % config)
540     cp = OscConfigParser.OscConfigParser(DEFAULTS)
541     cp.readfp(sio)
542
543     file = None
544     try:
545         file = open(conffile, 'w')
546     except IOError, e:
547         raise oscerr.OscIOError(e, 'cannot open configfile \'%s\'' % conffile)
548     try:
549         try:
550             os.chmod(conffile, 0600)
551             cp.write(file, True)
552         except IOError, e:
553             raise oscerr.OscIOError(e, 'cannot write configfile \'s\'' % conffile)
554     finally:
555         if file: file.close()
556
557 def add_section(filename, url, user, passwd):
558     """
559     Add a section to config file for new api url.
560     """
561     import base64
562     global config
563     cp = get_configParser(filename)
564     try:
565         cp.add_section(url)
566     except OscConfigParser.ConfigParser.DuplicateSectionError:
567         # Section might have existed, but was empty
568         pass
569     if config['use_keyring'] and GENERIC_KEYRING:
570         protocol, host = \
571             parse_apisrv_url(None, url)
572         keyring.set_password(host, user, passwd)
573         cp.set(url, 'keyring', '1')
574         cp.set(url, 'user', user)
575         cp.remove_option(url, 'pass')
576         cp.remove_option(url, 'passx')
577     elif config['gnome_keyring'] and GNOME_KEYRING:
578         protocol, host = \
579             parse_apisrv_url(None, url)
580         gnomekeyring.set_network_password_sync(
581             user = user,
582             password = passwd,
583             protocol = protocol,
584             server = host)
585         cp.set(url, 'keyring', '1')
586         cp.remove_option(url, 'pass')
587         cp.remove_option(url, 'passx')
588     else:
589         cp.set(url, 'user', user)
590         if not config['plaintext_passwd']:
591             cp.remove_option(url, 'pass')
592         cp.set(url, 'passx', base64.b64encode(passwd.encode('bz2')))
593     file = open(filename, 'w')
594     cp.write(file, True)
595     if file: file.close()
596
597
598 def get_config(override_conffile = None,
599                override_apiurl = None,
600                override_debug = None,
601                override_http_debug = None,
602                override_traceback = None,
603                override_post_mortem = None,
604                override_no_keyring = None,
605                override_no_gnome_keyring = None,
606                override_verbose = None):
607     """do the actual work (see module documentation)"""
608     import sys
609     import re
610     global config
611
612     conffile = override_conffile or os.environ.get('OSC_CONFIG', '~/.oscrc')
613     conffile = os.path.expanduser(conffile)
614
615     if not os.path.exists(conffile):
616         raise oscerr.NoConfigfile(conffile, \
617                                   account_not_configured_text % conffile)
618
619     # okay, we made sure that .oscrc exists
620
621     # make sure it is not world readable, it may contain a password.
622     os.chmod(conffile, 0600)
623
624     cp = get_configParser(conffile)
625
626     if not cp.has_section('general'):
627         # FIXME: it might be sufficient to just assume defaults?
628         msg = config_incomplete_text % conffile
629         msg += new_conf_template % DEFAULTS
630         raise oscerr.ConfigError(msg, conffile)
631
632     config = dict(cp.items('general', raw=1))
633     config['conffile'] = conffile
634
635     for i in boolean_opts:
636         try:
637             config[i] = cp.getboolean('general', i)
638         except ValueError, e:
639             raise oscerr.ConfigError('cannot parse \'%s\' setting: ' % i + str(e), conffile)
640
641     config['packagecachedir'] = os.path.expanduser(config['packagecachedir'])
642     config['exclude_glob'] = config['exclude_glob'].split()
643
644     re_clist = re.compile('[, ]+')
645     config['extra-pkgs'] = [ i.strip() for i in re_clist.split(config['extra-pkgs'].strip()) if i ]
646
647     # collect the usernames, passwords and additional options for each api host
648     api_host_options = {}
649
650     # Regexp to split extra http headers into a dictionary
651     # the text to be matched looks essentially looks this:
652     # "Attribute1: value1, Attribute2: value2, ..."
653     # there may be arbitray leading and intermitting whitespace.
654     # the following regexp does _not_ support quoted commas within the value.
655     http_header_regexp = re.compile(r"\s*(.*?)\s*:\s*(.*?)\s*(?:,\s*|\Z)")
656
657     # override values which we were called with
658     # This needs to be done before processing API sections as it might be already used there
659     if override_no_keyring:
660         config['use_keyring'] = False
661     if override_no_gnome_keyring:
662         config['gnome_keyring'] = False
663
664     aliases = {}
665     for url in [ x for x in cp.sections() if x != 'general' ]:
666         # backward compatiblity
667         scheme, host = \
668             parse_apisrv_url(config.get('scheme', 'https'), url)
669         apiurl = urljoin(scheme, host)
670         user = None
671         if config['use_keyring'] and GENERIC_KEYRING:
672             try:
673                 # Read from keyring lib if available
674                 user = cp.get(url, 'user', raw=True)
675                 password = keyring.get_password(host, user)
676             except:
677                 # Fallback to file based auth.
678                 pass
679         elif config['gnome_keyring'] and GNOME_KEYRING:
680             # Read from gnome keyring if available
681             try:
682                 gk_data = gnomekeyring.find_network_password_sync(
683                     protocol = scheme,
684                     server = host)
685                 password = gk_data[0]['password']
686                 user = gk_data[0]['user']
687             except gnomekeyring.NoMatchError:
688                 # Fallback to file based auth.
689                 pass
690         # Read credentials from config
691         if user is None:
692             #FIXME: this could actually be the ideal spot to take defaults
693             #from the general section.
694             user         = cp.get(url, 'user', raw=True) # need to set raw to prevent '%' expansion
695             password     = cp.get(url, 'pass', raw=True) # especially on password!
696             passwordx    = cp.get(url, 'passx', raw=True) # especially on password!
697             if password is None or password == 'your_password':
698                 try:
699                     password = passwordx.decode('base64').decode('bz2')
700                 except:
701                     print "%s: no credentials known" % url
702                     password = 'your_password'
703             else:
704                 if not passwordx:
705                     print "%s: rewriting from plain pass to encoded pass\n" % url
706                     add_section(conffile, url, user, password)
707
708         if cp.has_option(url, 'http_headers'):
709             http_headers = cp.get(url, 'http_headers')
710             http_headers = http_header_regexp.findall(http_headers)
711         else:
712             http_headers = []
713         if cp.has_option(url, 'aliases'):
714             for i in cp.get(url, 'aliases').split(','):
715                 key = i.strip()
716                 if key == '':
717                     continue
718                 if aliases.has_key(key):
719                     msg = 'duplicate alias entry: \'%s\' is already used for another apiurl' % key
720                     raise oscerr.ConfigError(msg, conffile)
721                 aliases[key] = url
722
723         api_host_options[apiurl] = { 'user': user,
724                                      'pass': password,
725                                      'http_headers': http_headers}
726
727         optional = ('email', 'sslcertck', 'cafile', 'capath')
728         for key in optional:
729             if cp.has_option(url, key):
730                 if key == 'sslcertck':
731                     api_host_options[apiurl][key] = cp.getboolean(url, key)
732                 else:
733                     api_host_options[apiurl][key] = cp.get(url, key)
734
735         if not 'sslcertck' in api_host_options[apiurl]:
736             api_host_options[apiurl]['sslcertck'] = True
737
738         if cp.has_option(url, 'trusted_prj'):
739             api_host_options[apiurl]['trusted_prj'] = cp.get(url, key).split(' ')
740         else:
741             api_host_options[apiurl]['trusted_prj'] = []
742
743     # add the auth data we collected to the config dict
744     config['api_host_options'] = api_host_options
745     config['apiurl_aliases'] = aliases
746
747     apiurl = aliases.get(config['apiurl'], config['apiurl'])
748     config['apiurl'] = urljoin(*parse_apisrv_url(None, apiurl))
749     # backward compatibility
750     if config.has_key('apisrv'):
751         apisrv = config['apisrv'].lstrip('http://')
752         apisrv = apisrv.lstrip('https://')
753         scheme = config.get('scheme', 'https')
754         config['apiurl'] = urljoin(scheme, apisrv)
755     if config.has_key('apisrv') or config.has_key('scheme'):
756         print >>sys.stderr, 'Warning: Use of the \'scheme\' or \'apisrv\' in ~/.oscrc is deprecated!\n' \
757                             'Warning: See README for migration details.'
758     if config.has_key('build_platform'):
759         print >>sys.stderr, 'Warning: Use of \'build_platform\' config option is deprecated! (use \'build_repository\' instead)'
760         config['build_repository'] = config['build_platform']
761
762     config['verbose'] = int(config['verbose'])
763     # override values which we were called with
764     if override_verbose:
765         config['verbose'] = override_verbose + 1
766
767     if override_debug:
768         config['debug'] = override_debug
769     if override_http_debug:
770         config['http_debug'] = override_http_debug
771     if override_traceback:
772         config['traceback'] = override_traceback
773     if override_post_mortem:
774         config['post_mortem'] = override_post_mortem
775     if override_apiurl:
776         apiurl = aliases.get(override_apiurl, override_apiurl)
777         # check if apiurl is a valid url
778         config['apiurl'] = urljoin(*parse_apisrv_url(None, apiurl))
779
780     # XXX unless config['user'] goes away (and is replaced with a handy function, or
781     # config becomes an object, even better), set the global 'user' here as well,
782     # provided that there _are_ credentials for the chosen apiurl:
783     try:
784         config['user'] = get_apiurl_usr(config['apiurl'])
785     except oscerr.ConfigMissingApiurl, e:
786         e.msg = config_missing_apiurl_text % config['apiurl']
787         e.file = conffile
788         raise e
789
790     # finally, initialize urllib2 for to use the credentials for Basic Authentication
791     init_basicauth(config)
792
793 # vim: sw=4 et