fix typo
[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',
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     section = config['apiurl_aliases'].get(section, section)
470     sections = dict([[i.rstrip('/'), i] for i in cp.sections()])
471     section = sections.get(section.rstrip('/'), section)
472     if not section in cp.sections():
473         raise oscerr.ConfigError('unknown section \'%s\'' % section, config['conffile'])
474     if section == 'general' and not opt in general_opts or \
475        section != 'general' and not opt in api_host_options:
476         raise oscerr.ConfigError('unknown config option \'%s\'' % opt, config['conffile'])
477     run = False
478     if val:
479         cp.set(section, opt, val)
480         write_config(config['conffile'], cp)
481         run = True
482     elif delete and cp.has_option(section, opt):
483         cp.remove_option(section, opt)
484         write_config(config['conffile'], cp)
485         run = True
486     if run and update:
487         kw = {'override_conffile': config['conffile']}
488         kw.update(kwargs)
489         get_config(**kw)
490     if cp.has_option(section, opt):
491         return (opt, cp.get(section, opt, raw=True))
492     return (opt, None)
493
494 def write_initial_config(conffile, entries, custom_template = ''):
495     """
496     write osc's intial configuration file. entries is a dict which contains values
497     for the config file (e.g. { 'user' : 'username', 'pass' : 'password' } ).
498     custom_template is an optional configuration template.
499     """
500     import StringIO, sys, base64
501     conf_template = custom_template or new_conf_template
502     config = DEFAULTS.copy()
503     config.update(entries)
504     config['passx'] = base64.b64encode(config['pass'].encode('bz2'))
505     # at this point use_keyring and gnome_keyring are str objects
506     if config['use_keyring'] == '1' and GENERIC_KEYRING:
507         protocol, host = \
508             parse_apisrv_url(None, config['apiurl'])
509         keyring.set_password(host, config['user'], config['pass'])
510         config['pass'] = ''
511         config['passx'] = ''
512     elif config['gnome_keyring'] == '1' and GNOME_KEYRING:
513         protocol, host = \
514             parse_apisrv_url(None, config['apiurl'])
515         gnomekeyring.set_network_password_sync(
516             user = config['user'],
517             password = config['pass'],
518             protocol = protocol,
519             server = host)
520         config['user'] = ''
521         config['pass'] = ''
522         config['passx'] = ''
523     if not config['plaintext_passwd']:
524         config['pass'] = ''
525     sio = StringIO.StringIO(conf_template.strip() % config)
526     cp = OscConfigParser.OscConfigParser(DEFAULTS)
527     cp.readfp(sio)
528
529     file = None
530     try:
531         file = open(conffile, 'w')
532     except IOError, e:
533         raise oscerr.OscIOError(e, 'cannot open configfile \'%s\'' % conffile)
534     try:
535         try:
536             os.chmod(conffile, 0600)
537             cp.write(file, True)
538         except IOError, e:
539             raise oscerr.OscIOError(e, 'cannot write configfile \'s\'' % conffile)
540     finally:
541         if file: file.close()
542
543 def add_section(filename, url, user, passwd):
544     """
545     Add a section to config file for new api url.
546     """
547     import base64
548     global config
549     cp = get_configParser(filename)
550     try:
551         cp.add_section(url)
552     except OscConfigParser.ConfigParser.DuplicateSectionError:
553         # Section might have existed, but was empty
554         pass
555     if config['use_keyring'] and GENERIC_KEYRING:
556         protocol, host = \
557             parse_apisrv_url(None, url)
558         keyring.set_password(host, user, passwd)
559         cp.set(url, 'keyring', '1')
560         cp.set(url, 'user', user)
561         cp.remove_option(url, 'pass')
562         cp.remove_option(url, 'passx')
563     elif config['gnome_keyring'] and GNOME_KEYRING:
564         protocol, host = \
565             parse_apisrv_url(None, url)
566         gnomekeyring.set_network_password_sync(
567             user = user,
568             password = passwd,
569             protocol = protocol,
570             server = host)
571         cp.set(url, 'keyring', '1')
572         cp.remove_option(url, 'pass')
573         cp.remove_option(url, 'passx')
574     else:
575         cp.set(url, 'user', user)
576         if not config['plaintext_passwd']:
577             cp.remove_option(url, 'pass')
578         cp.set(url, 'passx', base64.b64encode(passwd.encode('bz2')))
579     file = open(filename, 'w')
580     cp.write(file, True)
581     if file: file.close()
582
583
584 def get_config(override_conffile = None,
585                override_apiurl = None,
586                override_debug = None,
587                override_http_debug = None,
588                override_traceback = None,
589                override_post_mortem = None,
590                override_no_keyring = None,
591                override_no_gnome_keyring = None,
592                override_verbose = None):
593     """do the actual work (see module documentation)"""
594     import sys
595     import re
596     global config
597
598     conffile = override_conffile or os.environ.get('OSC_CONFIG', '~/.oscrc')
599     conffile = os.path.expanduser(conffile)
600
601     if not os.path.exists(conffile):
602         raise oscerr.NoConfigfile(conffile, \
603                                   account_not_configured_text % conffile)
604
605     # okay, we made sure that .oscrc exists
606
607     # make sure it is not world readable, it may contain a password.
608     os.chmod(conffile, 0600)
609
610     cp = get_configParser(conffile)
611
612     if not cp.has_section('general'):
613         # FIXME: it might be sufficient to just assume defaults?
614         msg = config_incomplete_text % conffile
615         msg += new_conf_template % DEFAULTS
616         raise oscerr.ConfigError(msg, conffile)
617
618     config = dict(cp.items('general', raw=1))
619     config['conffile'] = conffile
620
621     for i in boolean_opts:
622         try:
623             config[i] = cp.getboolean('general', i)
624         except ValueError, e:
625             raise oscerr.ConfigError('cannot parse \'%s\' setting: ' % i + str(e), conffile)
626
627     config['packagecachedir'] = os.path.expanduser(config['packagecachedir'])
628     config['exclude_glob'] = config['exclude_glob'].split()
629
630     re_clist = re.compile('[, ]+')
631     config['extra-pkgs'] = [ i.strip() for i in re_clist.split(config['extra-pkgs'].strip()) if i ]
632
633     # collect the usernames, passwords and additional options for each api host
634     api_host_options = {}
635
636     # Regexp to split extra http headers into a dictionary
637     # the text to be matched looks essentially looks this:
638     # "Attribute1: value1, Attribute2: value2, ..."
639     # there may be arbitray leading and intermitting whitespace.
640     # the following regexp does _not_ support quoted commas within the value.
641     http_header_regexp = re.compile(r"\s*(.*?)\s*:\s*(.*?)\s*(?:,\s*|\Z)")
642
643     # override values which we were called with
644     # This needs to be done before processing API sections as it might be already used there
645     if override_no_keyring:
646         config['use_keyring'] = False
647     if override_no_gnome_keyring:
648         config['gnome_keyring'] = False
649
650     aliases = {}
651     for url in [ x for x in cp.sections() if x != 'general' ]:
652         # backward compatiblity
653         scheme, host = \
654             parse_apisrv_url(config.get('scheme', 'https'), url)
655         apiurl = urljoin(scheme, host)
656         user = None
657         if config['use_keyring'] and GENERIC_KEYRING:
658             try:
659                 # Read from keyring lib if available
660                 user = cp.get(url, 'user', raw=True)
661                 password = keyring.get_password(host, user)
662             except:
663                 # Fallback to file based auth.
664                 pass
665         elif config['gnome_keyring'] and GNOME_KEYRING:
666             # Read from gnome keyring if available
667             try:
668                 gk_data = gnomekeyring.find_network_password_sync(
669                     protocol = scheme,
670                     server = host)
671                 password = gk_data[0]['password']
672                 user = gk_data[0]['user']
673             except gnomekeyring.NoMatchError:
674                 # Fallback to file based auth.
675                 pass
676         # Read credentials from config
677         if user is None:
678             #FIXME: this could actually be the ideal spot to take defaults
679             #from the general section.
680             user         = cp.get(url, 'user', raw=True) # need to set raw to prevent '%' expansion
681             password     = cp.get(url, 'pass', raw=True) # especially on password!
682             passwordx    = cp.get(url, 'passx', raw=True) # especially on password!
683             if password is None or password == 'your_password':
684                 try:
685                     password = passwordx.decode('base64').decode('bz2')
686                 except:
687                     print "%s: no credentials known" % url
688                     password = 'your_password'
689             else:
690                 if not passwordx:
691                     print "%s: rewriting from plain pass to encoded pass\n" % url
692                     add_section(conffile, url, user, password)
693
694         if cp.has_option(url, 'http_headers'):
695             http_headers = cp.get(url, 'http_headers')
696             http_headers = http_header_regexp.findall(http_headers)
697         else:
698             http_headers = []
699         if cp.has_option(url, 'aliases'):
700             for i in cp.get(url, 'aliases').split(','):
701                 key = i.strip()
702                 if key == '':
703                     continue
704                 if aliases.has_key(key):
705                     msg = 'duplicate alias entry: \'%s\' is already used for another apiurl' % key
706                     raise oscerr.ConfigError(msg, conffile)
707                 aliases[key] = url
708
709         api_host_options[apiurl] = { 'user': user,
710                                      'pass': password,
711                                      'http_headers': http_headers}
712
713         optional = ('email', 'sslcertck', 'cafile', 'capath')
714         for key in optional:
715             if cp.has_option(url, key):
716                 if key == 'sslcertck':
717                     api_host_options[apiurl][key] = cp.getboolean(url, key)
718                 else:
719                     api_host_options[apiurl][key] = cp.get(url, key)
720
721         if not 'sslcertck' in api_host_options[apiurl]:
722             api_host_options[apiurl]['sslcertck'] = True
723
724         if cp.has_option(url, 'trusted_prj'):
725             api_host_options[apiurl]['trusted_prj'] = cp.get(url, key).split(' ')
726         else:
727             api_host_options[apiurl]['trusted_prj'] = []
728
729     # add the auth data we collected to the config dict
730     config['api_host_options'] = api_host_options
731     config['apiurl_aliases'] = aliases
732
733     apiurl = aliases.get(config['apiurl'], config['apiurl'])
734     config['apiurl'] = urljoin(*parse_apisrv_url(None, apiurl))
735     # backward compatibility
736     if config.has_key('apisrv'):
737         apisrv = config['apisrv'].lstrip('http://')
738         apisrv = apisrv.lstrip('https://')
739         scheme = config.get('scheme', 'https')
740         config['apiurl'] = urljoin(scheme, apisrv)
741     if config.has_key('apisrv') or config.has_key('scheme'):
742         print >>sys.stderr, 'Warning: Use of the \'scheme\' or \'apisrv\' in ~/.oscrc is deprecated!\n' \
743                             'Warning: See README for migration details.'
744     if config.has_key('build_platform'):
745         print >>sys.stderr, 'Warning: Use of \'build_platform\' config option is deprecated! (use \'build_repository\' instead)'
746         config['build_repository'] = config['build_platform']
747
748     config['verbose'] = int(config['verbose'])
749     # override values which we were called with
750     if override_verbose:
751         config['verbose'] = override_verbose + 1
752
753     if override_debug:
754         config['debug'] = override_debug
755     if override_http_debug:
756         config['http_debug'] = override_http_debug
757     if override_traceback:
758         config['traceback'] = override_traceback
759     if override_post_mortem:
760         config['post_mortem'] = override_post_mortem
761     if override_apiurl:
762         apiurl = aliases.get(override_apiurl, override_apiurl)
763         # check if apiurl is a valid url
764         config['apiurl'] = urljoin(*parse_apisrv_url(None, apiurl))
765
766     # XXX unless config['user'] goes away (and is replaced with a handy function, or
767     # config becomes an object, even better), set the global 'user' here as well,
768     # provided that there _are_ credentials for the chosen apiurl:
769     try:
770         config['user'] = get_apiurl_usr(config['apiurl'])
771     except oscerr.ConfigMissingApiurl, e:
772         e.msg = config_missing_apiurl_text % config['apiurl']
773         e.file = conffile
774         raise e
775
776     # finally, initialize urllib2 for to use the credentials for Basic Authentication
777     init_basicauth(config)
778
779 # vim: sw=4 et