mirror of
https://github.com/openSUSE/osc.git
synced 2024-12-29 03:06:15 +01:00
3018460596
https://forgesvn1.novell.com/svn/opensuse/branches/buildservice/osc-exception-handling ........ r3774 | poeml | 2008-04-22 17:39:45 +0200 (Tue, 22 Apr 2008) | 3 lines A first draft on implement systematic exception handling: Add errors.py with some exceptions defined, and babysitter.py to handle them ........ r3775 | poeml | 2008-04-22 22:34:12 +0200 (Tue, 22 Apr 2008) | 9 lines - new global options: --debugger jump into the debugger before executing anything --post-mortem jump into the debugger in case of errors -t, --traceback print call trace in case of errors - traceback and post_mortem can also be set in .oscrc. - catch more errors (HTTPError). - make config accessible from outside of the Osc instance, by making it a class attribute ........ r3779 | poeml | 2008-04-23 00:55:49 +0200 (Wed, 23 Apr 2008) | 5 lines - new global option: -d, --debug print info useful for debugging - catch some more errors (HTTPError), with OscHTTPError which isn't very advanced yet. ........ r3781 | Marcus_H | 2008-04-23 01:02:00 +0200 (Wed, 23 Apr 2008) | 2 lines - added OscConfigError class (just for testing). - small change in the signature of the OscBaseError constructor (actually we should get rid of the 'args' tuple because it will be deprecated sooner or later ........ r3782 | Marcus_H | 2008-04-23 02:02:13 +0200 (Wed, 23 Apr 2008) | 2 lines - access the prg.conf.config dict in a save way - this way we avoid AttributeErrors if the prg.conf.config dict doesn't exist - in case of an configparser error we have to use the prg.options object directly (this was removed in r3781 by accident) ........ r3787 | poeml | 2008-04-23 09:23:56 +0200 (Wed, 23 Apr 2008) | 4 lines - remove local exception handling from do_req - for HTTPError, print details (headers and response) when in debug mode - catch AttributeError ........ r3789 | poeml | 2008-04-23 16:23:14 +0200 (Wed, 23 Apr 2008) | 4 lines - errors: add two new classes named OscWrongOptionsError and OscWrongArgsError - commandline: raise instances of the new errors in a number of places - commandline: add get_cmd_help() to Osc instance which returns the formatted help of a subcommand ........ r3790 | Marcus_H | 2008-04-23 16:48:28 +0200 (Wed, 23 Apr 2008) | 4 lines - added 2 new exception classes: OscNoConfigfileError and OscIOError - added new method write_config() to the conf.py module: This method writes osc's configuration file - minor cleanups in the conf module ........ r3791 | poeml | 2008-04-23 17:11:07 +0200 (Wed, 23 Apr 2008) | 3 lines small compatibility fix for r3790: try-except-finally isn't supported in python-2.4.2, thus do the same as try-except inside a try-finally. ........ r3792 | poeml | 2008-04-23 17:37:53 +0200 (Wed, 23 Apr 2008) | 2 lines fix up the remaining places regarding handling of errors related to commandline parsing ........ r3793 | poeml | 2008-04-23 17:40:34 +0200 (Wed, 23 Apr 2008) | 3 lines raise a NoWorkingCopyError in osc.core.store_read_project() in case of an IOError ........ r3798 | Marcus_H | 2008-04-23 23:55:24 +0200 (Wed, 23 Apr 2008) | 1 line ported -r3797 from trunk ........ r3802 | Marcus_H | 2008-04-24 11:00:55 +0200 (Thu, 24 Apr 2008) | 1 line ported -r3801 from trunk ........ r3805 | poeml | 2008-04-24 12:52:30 +0200 (Thu, 24 Apr 2008) | 2 lines raise OscHTTPError in show_pattern_meta(), replacing local error handling ........ r3817 | poeml | 2008-04-24 20:21:32 +0200 (Thu, 24 Apr 2008) | 9 lines - remove errors.OscHTTPError again. it seems simpler to use urllib2.HTTPError instead (and just add a specific error text message where appropriate, and re-raise) - for 404s, check out _which_ part was not found it is very ugly, but may be considered Good for pragmatic reasons - removed local exception handling and workaround for returned 500's from delete_package() and delete_project(), thereby getting rid of 4 possible exit points. ........ r3818 | Marcus_H | 2008-04-24 22:36:17 +0200 (Thu, 24 Apr 2008) | 1 line - this check is superfluous because every HTTPError instance has a code attribute ........ r3819 | poeml | 2008-04-25 00:39:39 +0200 (Fri, 25 Apr 2008) | 1 line remove a forgotten debug line from core.delete_project() ........ r3820 | poeml | 2008-04-25 10:07:58 +0200 (Fri, 25 Apr 2008) | 2 lines - ditch local error handling from wipebinaries(), rebuild(), and abortbuild() ........ r3821 | poeml | 2008-04-25 10:56:38 +0200 (Fri, 25 Apr 2008) | 2 lines It is never needed to import the exception module. ........ r3822 | poeml | 2008-04-25 11:13:39 +0200 (Fri, 25 Apr 2008) | 4 lines - when going into the debugger with --post-mortem, always print a traceback before (thus implying -t) - do not jump into the debugger if not on a TTY, or working in interactive mode ........ r3827 | poeml | 2008-04-25 13:07:46 +0200 (Fri, 25 Apr 2008) | 9 lines - add errors.OscWorkingCopyOutdated, which takes a tuple with three args: path to working copy, current rev, expected rev - add handler for urllib2.URLError errors to the babysitter - simplify the OscWrongArgsError and OscWrongOptionsError handlers, by removing the extra line "Sorry, wrong ..." that was printed before the messages given when the error was raised. - remove one more errors.OscHTTPError which was still there, and raise urllib2.HTTPError instead (show_package_meta()) ........ r3829 | poeml | 2008-04-25 14:19:10 +0200 (Fri, 25 Apr 2008) | 11 lines - comment some methods in osc.core which are used by nearly all do_* methods in osc.commandline - improve "is not a package/project dir" error messages, by printing the absolute path tried, instead of '.' for the cwd - make core.store_read_package() raise a proper NoWorkingCopyError instead of terminating - give attribution to things in babysitter.py copied from mercurial - prefix HTTPError exceptions with 'Server returned an error:' - remove obsolete local error handling from do_prjresults(), do_importsrcpkg(), do_repos() ........ r3830 | poeml | 2008-04-25 14:29:35 +0200 (Fri, 25 Apr 2008) | 1 line catch IOError exceptions in the babysitter ........ r3837 | poeml | 2008-04-25 17:27:36 +0200 (Fri, 25 Apr 2008) | 5 lines - do_remotebuildlog: raise errors for wrong arguments, remove exits - raise AttributeError in make_meta_url() instead of exiting - delete unused method core.delete_server_files() - replace exit call inside make_meta_url() with an AttributeError ........ r3838 | poeml | 2008-04-25 17:49:18 +0200 (Fri, 25 Apr 2008) | 1 line simplify the check in do_checkout if a project exists, by using show_project_meta() instead of meta_exists ........ r3839 | poeml | 2008-04-25 18:31:26 +0200 (Fri, 25 Apr 2008) | 6 lines - commandline do_checkout(): change the order of the two checks, first do the (cheaper) check for existing directory - core.core checkout_package(): simplify the check in if the package exists, by using show_package_meta() instead of meta_exists Let it throw an exception, instead of using sys.exit(). ........ r3841 | Marcus_H | 2008-04-27 15:48:06 +0200 (Sun, 27 Apr 2008) | 5 lines - added 2 new exception classes: PackageError() and PackageExistsError. The PackageError() class is meant to be the base class for all subsequent package exceptions. - get rid of 2 sys.exit(1) calls - make the update() method of the Project() class safer: in any case we have to write the _packages file otherwise the package tracking will be screwed up. - minor fix in delPackage(): use getTransActPath() when printing out the filename ........ r3842 | Marcus_H | 2008-04-27 16:52:55 +0200 (Sun, 27 Apr 2008) | 3 lines - make the commit() method safe: in any case we have to write the _packages file otherwise the package tracking will be screwed up. - removed another sys.exit(1): raise an exception if a package is missing when doing a commit. For now we use the PackageExistsError() exception but this might change in the future (updated description of PackageExistsError() according to this change) ........ r3848 | poeml | 2008-04-28 12:46:45 +0200 (Mon, 28 Apr 2008) | 3 lines rename several error classes, dropping the "Osc" prefix, and "Error" suffix in cases where they don't really make sense. ........ r3849 | poeml | 2008-04-28 12:57:32 +0200 (Mon, 28 Apr 2008) | 3 lines - rename osc.errors module to osc.oscerr, to make it easier to import it from other programs and have a crystal clear namespace ........ r3850 | poeml | 2008-04-28 13:26:12 +0200 (Mon, 28 Apr 2008) | 2 lines split PackageExists exception class into PackageExists and PackageMissing ........
341 lines
12 KiB
Python
341 lines
12 KiB
Python
#!/usr/bin/python
|
|
|
|
# Copyright (C) 2006 Peter Poeml / Novell Inc. All rights reserved.
|
|
# This program is free software; it may be used, copied, modified
|
|
# and distributed under the terms of the GNU General Public Licence,
|
|
# either version 2, or (at your option) any later version.
|
|
|
|
"""Read osc configuration and store it in a dictionary
|
|
|
|
This module reads and parses ~/.oscrc. The resulting configuration is stored
|
|
for later usage in a dictionary named 'config'.
|
|
|
|
In the absence of .oscrc, it tries .netrc.
|
|
If information is missing, it asks the user questions.
|
|
|
|
After reading the config, urllib2 is initialized.
|
|
|
|
The configuration dictionary could look like this:
|
|
|
|
{'apisrv': 'https://api.opensuse.org/',
|
|
'user': 'poeml',
|
|
'auth_dict': {'api.opensuse.org': {'user': 'poeml', 'pass': 'secret'},
|
|
'apitest.opensuse.org': {'user': 'poeml', 'pass': 'secret'},
|
|
'foo.opensuse.org': {'user': 'foo', 'pass': 'foo'}},
|
|
'build-cmd': '/usr/bin/build',
|
|
'build-root': '/abuild/oscbuild-%(repo)s-%(arch)s',
|
|
'packagecachedir': '/var/cache/osbuild',
|
|
'su-wrapper': 'sudo',
|
|
'urllist': ['http://download.opensuse.org/repositories/%(project)s/%(repository)s/%(arch)s/%(filename)s',
|
|
'http://api.opensuse.org/rpm/%(project)s/%(repository)s/_repository/%(buildarch)s/%(name)s'],
|
|
}
|
|
|
|
"""
|
|
|
|
import ConfigParser
|
|
from osc import oscerr
|
|
|
|
# being global to this module, this dict can be accessed from outside
|
|
# it will hold the parsed configuration
|
|
config = { }
|
|
|
|
DEFAULTS = { 'apisrv': 'https://api.opensuse.org/',
|
|
'scheme': 'https',
|
|
'user': 'your_username',
|
|
'pass': 'your_password',
|
|
'packagecachedir': '/var/tmp/osbuild-packagecache',
|
|
'su-wrapper': 'su -c',
|
|
'build-cmd': '/usr/bin/build',
|
|
'build-root': '/var/tmp/build-root',
|
|
|
|
# default list of download URLs, which will be tried in order
|
|
'urllist': [
|
|
# the normal repo server, redirecting to mirrors
|
|
'http://download.opensuse.org/repositories/%(project)s/%(repository)s/%(arch)s/%(filename)s',
|
|
# direct access to "full" tree
|
|
'%(scheme)s://%(apisrv)s/build/%(project)s/%(repository)s/%(buildarch)s/_repository/%(name)s',
|
|
],
|
|
|
|
'debug': '0',
|
|
'http_debug': '0',
|
|
'traceback': '0',
|
|
'post_mortem': '0',
|
|
'cookiejar': '~/.osc_cookiejar',
|
|
# disable project tracking by default
|
|
'do_package_tracking': '0',
|
|
}
|
|
boolean_opts = ['debug', 'do_package_tracking', 'http_debug', 'post_mortem', 'traceback']
|
|
|
|
new_conf_template = """
|
|
[general]
|
|
|
|
# URL to access API server, e.g. %(apisrv)s
|
|
# you also need a section [%(apisrv)s] with the credentials
|
|
#apisrv = %(apisrv)s
|
|
|
|
# Downloaded packages are cached here. Must be writable by you.
|
|
#packagecachedir = %(packagecachedir)s
|
|
|
|
# Wrapper to call build as root (sudo, su -, ...)
|
|
#su-wrapper = %(su-wrapper)s
|
|
|
|
# rootdir to setup the chroot environment
|
|
# can contain %%(repo)s and/or %%(arch)s for replacement, e.g.
|
|
# /srv/oscbuild/%%(repo)s-%%(arch)s
|
|
#build-root = %(build-root)s
|
|
|
|
# show info useful for debugging
|
|
#debug = 1
|
|
|
|
# show HTTP traffic useful for debugging
|
|
#http_debug = 1
|
|
|
|
# jump into the debugger in case of errors
|
|
#post_mortem = 1
|
|
|
|
# print call traces in case of errors
|
|
#traceback = 1
|
|
|
|
[%(apisrv)s]
|
|
user = %(user)s
|
|
pass = %(pass)s
|
|
"""
|
|
|
|
|
|
account_not_configured_text ="""
|
|
Your user account / password are not configured yet.
|
|
You will be asked for them below, and they will be stored in
|
|
%s for future use.
|
|
"""
|
|
|
|
|
|
config_incomplete_text = """
|
|
|
|
Your configuration file %s is not complete.
|
|
Make sure that it has a [general] section.
|
|
(You can copy&paste the below. Some commented defaults are shown.)
|
|
|
|
"""
|
|
|
|
cookiejar = None
|
|
|
|
def parse_apisrv_url(scheme, apisrv):
|
|
import urlparse
|
|
if apisrv.startswith('http://') or apisrv.startswith('https://'):
|
|
return urlparse.urlsplit(apisrv)[0:2]
|
|
else:
|
|
return scheme, apisrv
|
|
|
|
def get_apiurl_usr(apiurl):
|
|
"""
|
|
returns the user for this host - if this host does not exist in the
|
|
internal auth_dict the default user is returned.
|
|
"""
|
|
import sys
|
|
scheme, apisrv = parse_apisrv_url(None, apiurl)
|
|
if config['auth_dict'].has_key(apisrv):
|
|
return config['auth_dict'][apisrv]['user']
|
|
else:
|
|
print >>sys.stderr, 'section [\'%s\'] does not exist - using default user: \'%s\'' \
|
|
% (apisrv, config['user'])
|
|
return config['user']
|
|
|
|
def init_basicauth(config):
|
|
"""initialize urllib2 with the credentials for Basic Authentication"""
|
|
|
|
from osc.core import __version__
|
|
import os, urllib2
|
|
import cookielib
|
|
|
|
global cookiejar
|
|
|
|
# HTTPS proxy is not supported by urllib2. It only leads to an error
|
|
# or, at best, a warning.
|
|
# https://bugzilla.novell.com/show_bug.cgi?id=214983
|
|
# https://bugzilla.novell.com/show_bug.cgi?id=298378
|
|
if 'https_proxy' in os.environ:
|
|
del os.environ['https_proxy']
|
|
if 'HTTPS_PROXY' in os.environ:
|
|
del os.environ['HTTPS_PROXY']
|
|
|
|
if config['http_debug']:
|
|
# brute force
|
|
def urllib2_debug_init(self, debuglevel=0):
|
|
self._debuglevel = 1
|
|
urllib2.AbstractHTTPHandler.__init__ = urllib2_debug_init
|
|
|
|
authhandler = urllib2.HTTPBasicAuthHandler( \
|
|
urllib2.HTTPPasswordMgrWithDefaultRealm())
|
|
|
|
cookie_file = os.path.expanduser(config['cookiejar'])
|
|
cookiejar = cookielib.LWPCookieJar(cookie_file)
|
|
try:
|
|
cookiejar.load(ignore_discard=True)
|
|
except IOError:
|
|
try:
|
|
open(cookie_file, 'w').close()
|
|
os.chmod(cookie_file, 0600)
|
|
except:
|
|
#print 'Unable to create cookiejar file: \'%s\'. Using RAM-based cookies.' % cookie_file
|
|
cookiejar = cookielib.CookieJar()
|
|
|
|
opener = urllib2.build_opener(urllib2.HTTPCookieProcessor(cookiejar), authhandler)
|
|
urllib2.install_opener(opener)
|
|
|
|
opener.addheaders = [('User-agent', 'osc/%s' % __version__)]
|
|
|
|
# with None as first argument, it will always use this username/password
|
|
# combination for urls for which arg2 (apisrv) is a super-url
|
|
for host, auth in config['auth_dict'].iteritems():
|
|
authhandler.add_password(None, host, auth['user'], auth['pass'])
|
|
|
|
|
|
def get_configParser(conffile=None, force_read=False):
|
|
"""
|
|
Returns an ConfigParser() object. After its first invocation the
|
|
ConfigParser object is stored in a method attribute and this attribute
|
|
is returned unless you pass force_read=True.
|
|
"""
|
|
import os
|
|
conffile = conffile or os.environ.get('OSC_CONFIG', '~/.oscrc')
|
|
conffile = os.path.expanduser(conffile)
|
|
if force_read or not get_configParser.__dict__.has_key('cp'):
|
|
get_configParser.cp = ConfigParser.SafeConfigParser(DEFAULTS)
|
|
get_configParser.cp.read(conffile)
|
|
return get_configParser.cp
|
|
|
|
|
|
def write_config(conffile, entries, custom_template = None, force = False):
|
|
"""
|
|
write osc's configuration file. entries is a dict which contains values
|
|
for the config file (e.g. { 'user' : 'username', 'pass' : 'password' } ).
|
|
custom_template is an optional configuration template. Use force=True if you
|
|
want to overwrite an existing configuration file.
|
|
"""
|
|
import os
|
|
conf_template = custom_template or new_conf_template
|
|
config = DEFAULTS.copy()
|
|
config.update(entries)
|
|
if force or not os.path.exists(conffile):
|
|
file = None
|
|
try:
|
|
file = open(conffile, 'w')
|
|
except IOError, e:
|
|
raise oscerr.OscIOError(e, 'cannot open configfile \'%s\'' % conffile)
|
|
try:
|
|
try:
|
|
os.chmod(conffile, 0600)
|
|
file.write(conf_template % config)
|
|
except IOError, e:
|
|
raise oscerr.OscIOError(e, 'cannot write configfile \'s\'' % conffile)
|
|
finally:
|
|
if file: file.close()
|
|
return True
|
|
else:
|
|
return False
|
|
|
|
|
|
def get_config(override_conffile = None,
|
|
override_apisrv = None,
|
|
override_debug = None,
|
|
override_http_debug = None,
|
|
override_traceback = None,
|
|
override_post_mortem = None):
|
|
"""do the actual work (see module documentation)"""
|
|
import os
|
|
import sys
|
|
global config
|
|
|
|
conffile = override_conffile or os.environ.get('OSC_CONFIG', '~/.oscrc')
|
|
conffile = os.path.expanduser(conffile)
|
|
|
|
if not os.path.exists(conffile):
|
|
|
|
# okay, let's create a fresh config file
|
|
# if credentials are found in .netrc, use those
|
|
# otherwise ask
|
|
|
|
config = DEFAULTS.copy()
|
|
|
|
# try .netrc
|
|
# the needed entry needs to look like this:
|
|
# machine api.opensuse.org login your_login password your_pass
|
|
# note that it is not suited for credentials containing spaces
|
|
import netrc
|
|
try:
|
|
# XXX: apisrv is a URL now, thus requiring the "scheme" setting if https is to be used
|
|
netrc_host = parse_apisrv_url(None, DEFAULTS['apisrv'])[1]
|
|
config['user'], account, config['pass'] = \
|
|
netrc.netrc().authenticators(netrc_host)
|
|
print >>sys.stderr, 'Read credentials from %s.' % os.path.expanduser('~/.netrc')
|
|
except (IOError, TypeError, netrc.NetrcParseError):
|
|
raise oscerr.NoConfigfile(conffile, \
|
|
account_not_configured_text % conffile)
|
|
|
|
# okay, we made sure that .oscrc exists
|
|
|
|
cp = get_configParser(conffile)
|
|
|
|
if not cp.has_section('general'):
|
|
# FIXME: it might be sufficient to just assume defaults?
|
|
msg = config_incomplete_text % conffile
|
|
msg += new_conf_template % DEFAULTS
|
|
raise oscerr.ConfigError(msg)
|
|
|
|
config = dict(cp.items('general', raw=1))
|
|
|
|
config['scheme'], config['apisrv'] = \
|
|
parse_apisrv_url(config['scheme'], config['apisrv'])
|
|
|
|
for i in boolean_opts:
|
|
try:
|
|
if int(config.get(i)):
|
|
config[i] = True
|
|
else:
|
|
config[i] = False
|
|
except:
|
|
raise oscerr.ConfigError('option %s requires an integer value' % i)
|
|
|
|
packagecachedir = os.path.expanduser(config['packagecachedir'])
|
|
|
|
# transform 'url1, url2, url3' form into a list
|
|
if type(config['urllist']) == str:
|
|
config['urllist'] = [ i.strip() for i in config['urllist'].split(',') ]
|
|
|
|
# holds multiple usernames and passwords
|
|
auth_dict = { }
|
|
for url in [ x for x in cp.sections() if x != 'general' ]:
|
|
dummy, host = \
|
|
parse_apisrv_url(config['scheme'], url)
|
|
auth_dict[host] = { 'user': cp.get(url, 'user'),
|
|
'pass': cp.get(url, 'pass') }
|
|
|
|
# add the auth data we collected to the config dict
|
|
config['auth_dict'] = auth_dict
|
|
|
|
# override values which we were called with
|
|
if override_debug:
|
|
config['debug'] = override_debug
|
|
if override_http_debug:
|
|
config['http_debug'] = override_http_debug
|
|
if override_traceback:
|
|
config['traceback'] = override_traceback
|
|
if override_post_mortem:
|
|
config['post_mortem'] = override_post_mortem
|
|
if override_apisrv:
|
|
config['scheme'], config['apisrv'] = \
|
|
parse_apisrv_url(config['scheme'], override_apisrv)
|
|
|
|
# to make the mess complete, set up the more convenient api url which we'll rather use
|
|
config['apiurl'] = config['scheme'] + '://' + config['apisrv']
|
|
|
|
# XXX unless config['user'] goes away (and is replaced with a handy function, or
|
|
# config becomes an object, even better), set the global 'user' here as well,
|
|
# provided that there _are_ credentials for the chosen apisrv:
|
|
if config['apisrv'] in config['auth_dict'].keys():
|
|
config['user'] = config['auth_dict'][config['apisrv']]['user']
|
|
|
|
# finally, initialize urllib2 for to use the credentials for Basic Authentication
|
|
init_basicauth(config)
|
|
|