import tempfile
import glob
import shutil
-import base64
import pwd
from gitbuildsys import msger, utils, runner, errors
from gitbuildsys.safe_url import SafeURL
from gitbuildsys.cmd_export import export_sources
-from gbp.scripts.buildpackage_rpm import main as gbp_build
from gbp.rpm.git import GitRepositoryError, RpmGitRepository
import gbp.rpm as rpm
from gbp.errors import GbpError
return proxies
-def get_repos_conf():
- """
- Make list of urls using repox.url, repox.user and repox.passwd
- configuration file parameters from 'build' section.
- Validate configuration parameters.
- """
-
- repos = {}
- # get repo settings form build section
- for opt in configmgr.options('build'):
- if opt.startswith('repo'):
- try:
- key, name = opt.split('.')
- except ValueError:
- raise errors.ConfigError("invalid repo option: %s" % opt)
-
- if name not in ('url', 'user', 'passwdx'):
- raise errors.ConfigError("invalid repo option: %s" % opt)
-
- if key not in repos:
- repos[key] = {}
-
- if name in repos[key]:
- raise errors.ConfigError('Duplicate entry %s' % opt)
-
- value = configmgr.get(opt, 'build')
- if name == 'passwdx':
- try:
- value = base64.b64decode(value).decode('bz2')
- except (TypeError, IOError), err:
- raise errors.ConfigError('Error decoding %s: %s' % \
- (opt, err))
- repos[key]['passwd'] = value
- else:
- repos[key][name] = value
-
- result = []
- for key, item in repos.iteritems():
- if 'url' not in item:
- raise errors.ConfigError("Url is not specified for %s" % key)
-
- try:
- url = SafeURL(item['url'], item.get('user'), item.get('passwd'))
- except ValueError, e:
- raise errors.ConfigError('%s for %s' % (str(e), key))
-
- result.append(url)
-
- return result
-
-
def do(opts, args):
workdir = os.getcwd()
build_cmd = configmgr.get('build_cmd', 'build')
userid = pwd.getpwuid(os.getuid())[0]
- tmpdir = os.path.join(configmgr.get('tmpdir', 'general'), "%s-gbs" % userid)
+ tmpdir = os.path.join(configmgr.get('tmpdir', 'general'), "%s-gbs" % userid)
build_root = os.path.join(tmpdir, 'gbs-buildroot.%s' % buildarch)
if opts.buildroot:
build_root = opts.buildroot
if opts.skip_conf_repos:
repos = []
else:
- repos = get_repos_conf()
+ repos = configmgr.get_current_profile().get_repos()
if opts.repositories:
repos.extend([ SafeURL(i) for i in opts.repositories ])
distconf = opts.dist
else:
if repoparser.buildconf is None:
- msger.warning('failed to get build conf, use default build conf')
+ msger.warning('failed to get build conf, '
+ 'use default build conf')
distconf = configmgr.get('distconf', 'build')
else:
shutil.copy(repoparser.buildconf, tmpdir)
from gitbuildsys import msger, errors, utils
-from gitbuildsys.conf import configmgr
+from gitbuildsys.conf import configmgr, encode_passwd
from gitbuildsys.oscapi import OSC, OSCError
from gitbuildsys.cmd_export import export_sources
import gbp.rpm
-from gbp.scripts.buildpackage_rpm import main as gbp_build
from gbp.rpm.git import GitRepositoryError, RpmGitRepository
from gbp.errors import GbpError
passx=%(passwdx)s
"""
-APISERVER = configmgr.get('build_server', 'remotebuild')
-USER = configmgr.get('user', 'remotebuild')
-PASSWDX = configmgr.get('passwdx', 'remotebuild')
def do(opts, args):
else:
msger.error('Invalid arguments, see gbs remotebuild -h for more info')
- if not USER:
+ apiurl = configmgr.get_current_profile().get_api()
+ if not apiurl or not apiurl.user:
msger.error('empty user is not allowed for remotebuild, '\
'please add user/passwd to gbs conf, and try again')
if opts.target_obsprj is None:
target_prj = configmgr.get('target_prj', 'remotebuild') or \
- "home:%s:gbs:%s" % (USER, base_prj)
+ "home:%s:gbs:%s" % (apiurl.user, base_prj)
else:
target_prj = opts.target_obsprj
oscrc = OSCRC_TEMPLATE % {
"http_debug": 1 if msger.get_loglevel() == 'debug' else 0,
"debug": 1 if msger.get_loglevel() == 'verbose' else 0,
- "apiurl": APISERVER,
- "user": USER,
- "passwdx": PASSWDX,
+ "apiurl": apiurl,
+ "user": apiurl.user,
+ "passwdx": encode_passwd(apiurl.passwd),
}
tmpdir = configmgr.get('tmpdir', 'general')
tmpf = utils.Temp(dirn=exportdir, prefix='.oscrc', content=oscrc)
oscrcpath = tmpf.path
- api = OSC(APISERVER, oscrc=oscrcpath)
+ api = OSC(apiurl, oscrc=oscrcpath)
try:
if opts.buildlog:
# FIXME: How do you know that a certain user does not have
# permissions to create any project, anywhewre?
if opts.target_obsprj and \
- not target_prj.startswith('home:%s:' % USER):
- msger.error('no permission to create project %s, only sub '\
- 'projects of home:%s are allowed ' % (target_prj, USER))
+ not target_prj.startswith('home:%s:' % apiurl.user):
+ msger.error('no permission to create project %s, only sub '
+ 'projects of home:%s are '
+ 'allowed ' % (target_prj, apiurl.user))
msger.info('copying settings of %s to %s' % (base_prj, target_prj))
api.copy_project(base_prj, target_prj)
msger.info('local changes submitted to build server successfully')
msger.info('follow the link to monitor the build progress:\n'
' %s/package/show?package=%s&project=%s' \
- % (APISERVER.replace('api', 'build'), package, target_prj))
+ % (apiurl.replace('api', 'build'), package, target_prj))
# You should have received a copy of the GNU General Public License along
# with this program; if not, write to the Free Software Foundation, Inc., 59
# Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+'''
+Provides classes and functions to read and write gbs.conf.
+'''
from __future__ import with_statement
import os
MissingSectionHeaderError
from gitbuildsys import msger, errors
+from gitbuildsys.safe_url import SafeURL
+
+
+def decode_passwdx(passwdx):
+ '''decode passwdx into plain format'''
+ return base64.b64decode(passwdx).decode('bz2')
+
+
+def encode_passwd(passwd):
+ '''encode passwd by bz2 and base64'''
+ return base64.b64encode(passwd.encode('bz2'))
+
+
+def evalute_string(string):
+ '''safely evaluate string'''
+ if string.startswith('"') or string.startswith("'"):
+ return ast.literal_eval(string)
+ return string
+
+
+def split_and_evaluate_string(string, sep=None, maxsplit=-1):
+ '''split a string and evaluate each of them'''
+ return [ evalute_string(i.strip()) for i in string.split(sep, maxsplit) ]
class SectionPattern(object):
if not name:
return type_
- name = self.evalute_string(name)
+ name = evalute_string(name)
return type_, name
- @staticmethod
- def evalute_string(string):
- '''safely evaluate string'''
- if string.startswith('"') or string.startswith("'"):
- return ast.literal_eval(string)
- return string
-
def match(self, string):
'''return MatchObject if string match the pattern'''
match = self.SECTCRE.match(string)
fptr.close()
class ConfigMgr(object):
+ '''Support multi-levels of gbs.conf. Use this class to get and set
+ item value without caring about concrete ini format'''
+
DEFAULTS = {
'general': {
'tmpdir': '/var/tmp',
self.reset_from_conf(fpath)
def reset_from_conf(self, fpath):
+ 'reset all config values by files passed in'
if fpath:
if not os.path.exists(fpath):
raise errors.ConfigError('Configuration file %s does not '\
return paths
def get_default_conf(self, defaults=None):
+ 'returns ini template string contains default values'
from string import Template
if not defaults:
defaults = self.DEFAULTS
return Template(self.DEFAULT_CONF_TEMPLATE).safe_substitute(tmpl_keys)
def _new_conf(self, fpath=None):
+ 'generate a new conf file located by fpath'
if not fpath:
fpath = os.path.expanduser('~/.gbs.conf')
if defaults['remotebuild']['user']:
msger.info('Your password will be encoded before saving ...')
defaults['remotebuild']['passwdx'] = \
- base64.b64encode(getpass.getpass().encode('bz2'))
+ encode_passwd(getpass.getpass())
else:
defaults['remotebuild']['passwdx'] = \
- base64.b64encode(
- defaults['remotebuild']['passwd'].encode('bz2'))
+ encode_passwd(defaults['remotebuild']['passwd'])
with open(fpath, 'w') as wfile:
wfile.write(self.get_default_conf(defaults))
return True
def _check_passwd(self):
+ 'convert passwd item to passwdx and then update origin conf files'
replaced_keys = False
- for sec in self.DEFAULTS.keys():
+
+ all_sections = set()
+ for layer in self._cfgparsers:
+ for sec in layer.sections():
+ all_sections.add(sec)
+
+ for sec in all_sections:
for key in self.options(sec):
if key.endswith('passwd'):
for cfgparser in self._cfgparsers:
continue
cfgparser.set(sec,
key + 'x',
- base64.b64encode(plainpass.encode('bz2')),
+ encode_passwd(plainpass),
key)
replaced_keys = True
self.update()
def _get(self, opt, section='general'):
+ 'get value from multi-levels of config file'
sect_found = False
for cfgparser in self._cfgparsers:
try:
return False
def options(self, section='general'):
+ 'merge and return options of certain section from multi-levels'
sect_found = False
options = set()
for cfgparser in self._cfgparsers:
return options
def get(self, opt, section='general'):
+ 'get item value. return plain text of password if item is passwd'
if opt == 'passwd':
opt = 'passwdx'
val = self._get(opt, section)
if val:
try:
- return base64.b64decode(val).decode('bz2')
+ return decode_passwdx(val)
except (TypeError, IOError), err:
raise errors.ConfigError('passwdx:%s' % err)
else:
def set(self, opt, val, section='general'):
if opt.endswith('passwd'):
- val = base64.b64encode(val.encode('bz2'))
+ val = encode_passwd(val)
opt += 'x'
for cfgparser in self._cfgparsers:
raise errors.ConfigError('invalid section %s' % (section))
def update(self):
+ 'update changed values into files on disk'
for cfgparser in self._cfgparsers:
cfgparser.update()
-configmgr = ConfigMgr()
+
+class Profile(object):
+ '''Profile which contains all config values related to same domain'''
+
+ def __init__(self, user, password):
+ self.common_user = user
+ self.common_password = password
+ self.repos = []
+ self.api = None
+
+ def make_url(self, url, user, password):
+ '''make a safe url which contains auth info'''
+ user = user or self.common_user
+ password = password or self.common_password
+ try:
+ return SafeURL(url, user, password)
+ except ValueError, err:
+ raise errors.ConfigError('%s for %s' % (str(err), url))
+
+ def add_repo(self, url, user, password):
+ '''add a repo to repo list of the profile'''
+ self.repos.append(self.make_url(url, user, password))
+
+ def set_api(self, url, user, password):
+ '''set OBS api of the profile'''
+ self.api = self.make_url(url, user, password)
+
+ def get_repos(self):
+ '''get repo list of the profile'''
+ return self.repos
+
+ def get_api(self):
+ '''get OBS api of the profile'''
+ return self.api
+
+
+class BizConfigManager(ConfigMgr):
+ '''config manager which handles high level conception, such as profile info
+ '''
+
+ def is_profile_oriented(self):
+ '''return True if config file is profile oriented'''
+ return self.get_optional_item('general', 'profile') is not None
+
+ def get_current_profile(self):
+ '''get profile current used'''
+ if self.is_profile_oriented():
+ return self._build_profile_by_name(self.get('profile'))
+
+ msger.warning('subcommand oriented style of config is deprecated, '
+ 'please convert to profile oriented style.')
+ return self._build_profile_by_subcommand()
+
+ def get_optional_item(self, section, option, default=None):
+ '''return default if section.option does not exist'''
+ try:
+ return self.get(option, section)
+ except errors.ConfigError:
+ return default
+
+ def _get_url_section(self, section_id):
+ '''get url/user/passwd from a section'''
+ url = self.get('url', section_id)
+ user = self.get_optional_item(section_id, 'user')
+ password = self.get_optional_item(section_id, 'passwd')
+ return url, user, password
+
+ def _build_profile_by_name(self, name):
+ '''return profile object by a given section'''
+ profile_id = ('profile', name)
+ user = self.get_optional_item(profile_id, 'user')
+ password = self.get_optional_item(profile_id, 'passwd')
+
+ profile = Profile(user, password)
+
+ conf_api = self.get_optional_item(profile_id, 'api')
+ if conf_api:
+ api = self.get('api', profile_id)
+ api_id = ('obs', api)
+ profile.set_api(*self._get_url_section(api_id))
+
+ conf_repos = self.get_optional_item(profile_id, 'repos')
+ if conf_repos:
+ repos = split_and_evaluate_string(conf_repos, ',')
+ for repo in repos:
+ repo_id = ('repo', repo)
+ profile.add_repo(*self._get_url_section(repo_id))
+
+ return profile
+
+ def _parse_build_repos(self):
+ """
+ Make list of urls using repox.url, repox.user and repox.passwd
+ configuration file parameters from 'build' section.
+ Validate configuration parameters.
+ """
+ repos = {}
+ # get repo settings form build section
+ for opt in self.options('build'):
+ if opt.startswith('repo'):
+ try:
+ key, name = opt.split('.')
+ except ValueError:
+ raise errors.ConfigError("invalid repo option: %s" % opt)
+
+ if name not in ('url', 'user', 'passwdx'):
+ raise errors.ConfigError("invalid repo option: %s" % opt)
+
+ if key not in repos:
+ repos[key] = {}
+
+ if name in repos[key]:
+ raise errors.ConfigError('Duplicate entry %s' % opt)
+
+ value = self.get(opt, 'build')
+ if name == 'passwdx':
+ try:
+ value = decode_passwdx(value)
+ except (TypeError, IOError), err:
+ raise errors.ConfigError('Error decoding %s: %s' % \
+ (opt, err))
+ repos[key]['passwd'] = value
+ else:
+ repos[key][name] = value
+ return sorted(repos.items(), key=lambda i: i[0])
+
+ def _build_profile_by_subcommand(self):
+ '''return profile object from subcommand oriented style of config'''
+ profile = Profile(None, None)
+
+ section_id = 'remotebuild'
+ url = self.get('build_server', section_id)
+ user = self.get_optional_item(section_id, 'user')
+ password = self.get_optional_item(section_id, 'passwd')
+ profile.set_api(url, user, password)
+
+ repos = self._parse_build_repos()
+ for key, item in repos:
+ if 'url' not in item:
+ raise errors.ConfigError("Url is not specified for %s" % key)
+ profile.add_repo(item['url'], item.get('user'), item.get('passwd'))
+
+ return profile
+
+
+configmgr = BizConfigManager()
@property
def full(self):
'''return the full url with user and password'''
+ if self.is_local():
+ return self
+
userinfo = self._get_userinfo()
hostport = self._get_hostport(self.components)
new_components[1] = login
return urlparse.urlunsplit(new_components)
+ def is_local(self):
+ 'return True is it is local path'
+ return self.startswith('/')
+
def pathjoin(self, *args):
'''treat self as path and urljoin'''
new = urlparse.urljoin(self.rstrip('/') + '/', *args)
remotes = []
for repo in repos:
- if repo.startswith('/') and os.path.exists(repo):
- local_repos.append(repo)
+ if repo.is_local():
+ if os.path.exists(repo):
+ local_repos.append(repo)
+ else:
+ msger.warning('No such repo path:%s' % repo)
else:
remotes.append(repo)
--- /dev/null
+#!/usr/bin/python -tt
+# vim: ai ts=4 sts=4 et sw=4
+#
+# Copyright (c) 2012 Intel, Inc.
+#
+# This program is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by the Free
+# Software Foundation; version 2 of the License
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with this program; if not, write to the Free Software Foundation, Inc., 59
+# Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+"""Functional tests for profile style of config"""
+import unittest
+
+import gitbuildsys.conf
+from test_config import Fixture
+
+
+def get_profile():
+ '''get current profile to test'''
+ return gitbuildsys.conf.configmgr.get_current_profile()
+
+
+class ProfileStyleTest(unittest.TestCase):
+ '''Test for profile oriented config'''
+
+ @Fixture(home='profile.ini')
+ def test_profile_api(self):
+ 'test get obs api'
+ self.assertEquals('https://api.tz/path', get_profile().get_api())
+
+ @Fixture(home='profile.ini')
+ def test_api_inherit_auth(self):
+ 'test api can inherit auto from parent profile section'
+ self.assertEquals('https://Alice:secret@api.tz/path',
+ get_profile().get_api().full)
+
+ @Fixture(home='profile_only_has_api.ini')
+ def test_api_auth_can_be_overwrite(self):
+ 'test api auth can be overwrite'
+ self.assertEquals('https://Bob:classified@api.tz/path',
+ get_profile().get_api().full)
+
+ @Fixture(home='profile.ini')
+ def test_profile_repos_in_order(self):
+ 'repos must be in same order as they are write in config'
+ self.assertEquals(['https://repo/ia32/main',
+ 'https://repo/ia32/non-oss',
+ 'https://repo/ia32/base',
+ '/local/path'],
+ get_profile().get_repos())
+
+ @Fixture(home='profile.ini')
+ def test_repo_inherit_auth(self):
+ 'test repo can inherit auth from parent section'
+ self.assertEquals('https://Alice:secret@repo/ia32/main',
+ get_profile().get_repos()[0].full)
+
+ @Fixture(home='profile.ini')
+ def test_repo_overwrite_auth(self):
+ 'test repo auth can be overwrite'
+ self.assertEquals('https://Bob:classified@repo/ia32/base',
+ get_profile().get_repos()[2].full)
+
+ @Fixture(home='no_such_profile_section_name.ini')
+ def test_no_such_profile(self):
+ 'test get a empty profile when name does not exist'
+ profile = get_profile()
+ self.assertEquals(None, profile.get_api())
+ self.assertEquals([], profile.get_repos())
+
+ @Fixture(home='profile.ini')
+ def test_local_repo_need_not_auth(self):
+ '''test local path needn't auth info'''
+ self.assertEquals('/local/path', get_profile().get_repos()[3].full)
+
+
+class SubcommandStyleTest(unittest.TestCase):
+ '''test for subcommand oriented config'''
+
+ @Fixture(home='subcommand.ini')
+ def test_api(self):
+ 'test obs api'
+ self.assertEquals('https://api/build/server', get_profile().get_api())
+
+ @Fixture(home='subcommand.ini')
+ def test_api_auth(self):
+ 'test api auth'
+ self.assertEquals('https://Alice:secret@api/build/server',
+ get_profile().get_api().full)
+
+ @Fixture(home='subcommand.ini')
+ def test_repos_in_order(self):
+ 'repos list must be in the same order as they are write in config'
+ self.assertEquals(['https://repo1/path',
+ 'https://repo2/path',
+ '/local/path/repo'],
+ get_profile().get_repos())
+
+ @Fixture(home='subcommand.ini')
+ def test_repo_auth(self):
+ 'test repo auth'
+ self.assertEquals('https://Alice:secret@repo1/path',
+ get_profile().get_repos()[0].full)
+
+
+if __name__ == '__main__':
+ unittest.main()
\ No newline at end of file
url = SafeURL('/local/path')
self.assertEqual('/local/path', url)
- self.assertEqual(url, url.full)
\ No newline at end of file
+ self.assertEqual(url, url.full)
+
+ def test_local_path_need_not_auth(self):
+ '''local path should ignore user and password'''
+ url = SafeURL('/local/path', 'test', 'password')
+
+ self.assertEqual('/local/path', url)
+ self.assertEqual(url, url.full)
--- /dev/null
+[build]
+repo1.url = https://repo1/path
+repo1.user = Alice
+repo1.passwdx = QlpoOTFBWSZTWYfNdxYAAAIBgAoAHAAgADDNAMNEA24u5IpwoSEPmu4s
+
+repo2.url = https://repo2/path
+repo2.user = Alice
+repo2.passwdx = QlpoOTFBWSZTWYfNdxYAAAIBgAoAHAAgADDNAMNEA24u5IpwoSEPmu4s
--- /dev/null
+[general]
+profile = test
\ No newline at end of file
--- /dev/null
+[general]
+profile = tz
+
+[profile "tz"]
+user = Alice
+#passwd = secret
+passwdx = QlpoOTFBWSZTWYfNdxYAAAIBgAoAHAAgADDNAMNEA24u5IpwoSEPmu4s
+repos = ia32_main, ia32_non-oss, ia32_base, local
+api = tz
+
+[obs "tz"]
+url = https://api.tz/path
+
+[repo ia32_main]
+url = https://repo/ia32/main
+
+[repo ia32_non-oss]
+url = https://repo/ia32/non-oss
+
+[repo ia32_base]
+url = https://repo/ia32/base
+user = Bob
+#passwd = classified
+passwdx = QlpoOTFBWSZTWRwZil4AAACBgC8kCAAgADEMCCAPKGaQLT4u5IpwoSA4MxS8
+
+[repo local]
+url = /local/path
\ No newline at end of file
--- /dev/null
+[general]
+profile = test
+
+[profile "test"]
+user = Alice
+#passwd = secret
+passwdx = QlpoOTFBWSZTWYfNdxYAAAIBgAoAHAAgADDNAMNEA24u5IpwoSEPmu4s
+api = test_api
+
+[obs "test_api"]
+url = https://api.tz/path
+user = Bob
+#passwd = classified
+passwdx = QlpoOTFBWSZTWRwZil4AAACBgC8kCAAgADEMCCAPKGaQLT4u5IpwoSA4MxS8
--- /dev/null
+[remotebuild]
+build_server = https://api/build/server
+user = Alice
+#passwd = secret
+passwdx = QlpoOTFBWSZTWYfNdxYAAAIBgAoAHAAgADDNAMNEA24u5IpwoSEPmu4s
+
+[build]
+repo1.url = https://repo1/path
+repo1.user = Alice
+repo1.passwdx = QlpoOTFBWSZTWYfNdxYAAAIBgAoAHAAgADDNAMNEA24u5IpwoSEPmu4s
+
+repo2.url = https://repo2/path
+repo2.user = Alice
+repo2.passwdx = QlpoOTFBWSZTWYfNdxYAAAIBgAoAHAAgADDNAMNEA24u5IpwoSEPmu4s
+
+repo3.url = /local/path/repo
\ No newline at end of file