Support profile oriented style of config.
authorHuang Hao <hao.h.huang@intel.com>
Fri, 7 Sep 2012 07:47:18 +0000 (15:47 +0800)
committerHuang Hao <hao.h.huang@intel.com>
Mon, 10 Sep 2012 03:19:35 +0000 (11:19 +0800)
- support profile oriented style. It is compatible with subcommand
    oriented style.
- add a class Profile to store url/user/password of repos and api.
- add a class BizConfigManager extending from ConfigMgr. It
    handles profile info and does not care about multi-levels and
    passwdx issues. It returns current profile instance even
    config file has subcommand oriented style.
- make two functions decode_passwdx, encode_passwd to convert
    between passwd and passwdx.
- move get_repos_conf() from cmd_build.py to conf.py and split it
    into two parts
    _parse_build_repos() and _build_profile_by_subcommand()
- add test cases in test_profile.py

Change-Id: I81b2906456766f1061ccef9e7d51fea794c59409

12 files changed:
gitbuildsys/cmd_build.py
gitbuildsys/cmd_remotebuild.py
gitbuildsys/conf.py
gitbuildsys/safe_url.py
gitbuildsys/utils.py
tests/test_profile.py [new file with mode: 0644]
tests/test_safe_url.py
tests/testdata/ini/local_repo.ini [new file with mode: 0644]
tests/testdata/ini/no_such_profile_section_name.ini [new file with mode: 0644]
tests/testdata/ini/profile.ini [new file with mode: 0644]
tests/testdata/ini/profile_only_has_api.ini [new file with mode: 0644]
tests/testdata/ini/subcommand.ini [new file with mode: 0644]

index 4df2238..354c483 100644 (file)
@@ -25,7 +25,6 @@ import subprocess
 import tempfile
 import glob
 import shutil
-import base64
 import pwd
 
 from gitbuildsys import msger, utils, runner, errors
@@ -33,7 +32,6 @@ from gitbuildsys.conf import configmgr
 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
@@ -170,57 +168,6 @@ def get_env_proxies():
     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()
@@ -262,7 +209,7 @@ def do(opts, args):
 
     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
@@ -293,7 +240,7 @@ def do(opts, args):
         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 ])
@@ -316,7 +263,8 @@ def do(opts, args):
             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)
index ee49cdb..e7077f4 100644 (file)
@@ -24,12 +24,11 @@ import glob
 
 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
 
@@ -45,9 +44,6 @@ user=%(user)s
 passx=%(passwdx)s
 """
 
-APISERVER   = configmgr.get('build_server', 'remotebuild')
-USER        = configmgr.get('user', 'remotebuild')
-PASSWDX     = configmgr.get('passwdx', 'remotebuild')
 
 def do(opts, args):
 
@@ -69,7 +65,8 @@ 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')
 
@@ -109,7 +106,7 @@ def do(opts, args):
 
     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
 
@@ -117,9 +114,9 @@ def do(opts, args):
     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')
@@ -129,7 +126,7 @@ def do(opts, args):
     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:
@@ -173,9 +170,10 @@ def do(opts, args):
             # 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)
@@ -216,4 +214,4 @@ def do(opts, args):
     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))
index 49b7066..dc4bcfb 100644 (file)
@@ -15,6 +15,9 @@
 # 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
@@ -25,6 +28,29 @@ from ConfigParser import SafeConfigParser, NoSectionError, NoOptionError, \
                          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):
@@ -59,16 +85,9 @@ 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)
@@ -210,6 +229,9 @@ class BrainConfigParser(SafeConfigParser):
         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',
@@ -273,6 +295,7 @@ distconf = $build__distconf
         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 '\
@@ -312,6 +335,7 @@ distconf = $build__distconf
         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
@@ -324,6 +348,7 @@ distconf = $build__distconf
         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')
 
@@ -343,11 +368,10 @@ distconf = $build__distconf
         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))
@@ -358,8 +382,15 @@ distconf = $build__distconf
         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:
@@ -370,7 +401,7 @@ distconf = $build__distconf
                                 continue
                             cfgparser.set(sec,
                                      key + 'x',
-                                     base64.b64encode(plainpass.encode('bz2')),
+                                     encode_passwd(plainpass),
                                      key)
                             replaced_keys = True
 
@@ -380,6 +411,7 @@ distconf = $build__distconf
             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:
@@ -406,6 +438,7 @@ distconf = $build__distconf
             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:
@@ -425,12 +458,13 @@ distconf = $build__distconf
         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:
@@ -440,7 +474,7 @@ distconf = $build__distconf
 
     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:
@@ -456,7 +490,153 @@ distconf = $build__distconf
         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()
index 88e6afe..9e9acfe 100644 (file)
@@ -44,6 +44,9 @@ class SafeURL(str):
     @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)
 
@@ -56,6 +59,10 @@ class SafeURL(str):
         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)
index 9ac6bc4..613e417 100644 (file)
@@ -357,8 +357,11 @@ class RepoParser(object):
         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)
 
diff --git a/tests/test_profile.py b/tests/test_profile.py
new file mode 100644 (file)
index 0000000..bdf6f1d
--- /dev/null
@@ -0,0 +1,114 @@
+#!/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
index c33d6d1..4308835 100644 (file)
@@ -105,4 +105,11 @@ class SafeURLTest(unittest.TestCase):
         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)
diff --git a/tests/testdata/ini/local_repo.ini b/tests/testdata/ini/local_repo.ini
new file mode 100644 (file)
index 0000000..ffed290
--- /dev/null
@@ -0,0 +1,8 @@
+[build]
+repo1.url = https://repo1/path
+repo1.user = Alice
+repo1.passwdx = QlpoOTFBWSZTWYfNdxYAAAIBgAoAHAAgADDNAMNEA24u5IpwoSEPmu4s
+
+repo2.url = https://repo2/path
+repo2.user = Alice
+repo2.passwdx = QlpoOTFBWSZTWYfNdxYAAAIBgAoAHAAgADDNAMNEA24u5IpwoSEPmu4s
diff --git a/tests/testdata/ini/no_such_profile_section_name.ini b/tests/testdata/ini/no_such_profile_section_name.ini
new file mode 100644 (file)
index 0000000..a51818e
--- /dev/null
@@ -0,0 +1,2 @@
+[general]
+profile = test
\ No newline at end of file
diff --git a/tests/testdata/ini/profile.ini b/tests/testdata/ini/profile.ini
new file mode 100644 (file)
index 0000000..54b8284
--- /dev/null
@@ -0,0 +1,27 @@
+[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
diff --git a/tests/testdata/ini/profile_only_has_api.ini b/tests/testdata/ini/profile_only_has_api.ini
new file mode 100644 (file)
index 0000000..89035f0
--- /dev/null
@@ -0,0 +1,14 @@
+[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
diff --git a/tests/testdata/ini/subcommand.ini b/tests/testdata/ini/subcommand.ini
new file mode 100644 (file)
index 0000000..974abb3
--- /dev/null
@@ -0,0 +1,16 @@
+[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