Exemple #1
0
class StagingWorkflow(object):
    def __init__(self, project=PROJECT):
        """
        Initialize the configuration
        """
        THIS_DIR = os.path.dirname(os.path.abspath(__file__))
        oscrc = os.path.join(THIS_DIR, 'test.oscrc')

        # set to None so we return the destructor early in case of exceptions
        self.api = None
        self.apiurl = APIURL
        self.project = project
        self.projects = {}
        self.requests = []
        self.groups = []
        self.users = []
        logging.basicConfig()

        # clear cache from other tests - otherwise the VCR is replayed depending
        # on test order, which can be harmful
        memoize_session_reset()

        osc.core.conf.get_config(override_conffile=oscrc,
                                 override_no_keyring=True,
                                 override_no_gnome_keyring=True)
        os.environ['OSC_CONFIG'] = oscrc

        if os.environ.get('OSC_DEBUG'):
            osc.core.conf.config['debug'] = 1

        CacheManager.test = True
        # disable caching, the TTLs break any reproduciblity
        Cache.CACHE_DIR = None
        Cache.PATTERNS = {}
        Cache.init()
        self.setup_remote_config()
        self.load_config()
        self.api = StagingAPI(APIURL, project)

    def load_config(self, project=None):
        if project is None:
            project = self.project
        self.config = Config(APIURL, project)

    def create_attribute_type(self, namespace, name, values=None):
        meta = """
        <namespace name='{}'>
            <modifiable_by user='******'/>
        </namespace>""".format(namespace)
        url = osc.core.makeurl(APIURL, ['attribute', namespace, '_meta'])
        osc.core.http_PUT(url, data=meta)

        meta = "<definition name='{}' namespace='{}'><description/>".format(
            name, namespace)
        if values:
            meta += "<count>{}</count>".format(values)
        meta += "<modifiable_by role='maintainer'/></definition>"
        url = osc.core.makeurl(APIURL, ['attribute', namespace, name, '_meta'])
        osc.core.http_PUT(url, data=meta)

    def setup_remote_config(self):
        self.create_target()
        self.create_attribute_type('OSRT', 'Config', 1)

        config = {
            'overridden-by-local': 'remote-nope',
            'staging-group': 'factory-staging',
            'remote-only': 'remote-indeed',
        }
        self.remote_config_set(config, replace_all=True)

    def remote_config_set(self, config, replace_all=False):
        if not replace_all:
            config_existing = Config.get(self.apiurl, self.project)
            config_existing.update(config)
            config = config_existing

        config_lines = []
        for key, value in config.items():
            config_lines.append(f'{key} = {value}')

        attribute_value_save(APIURL, self.project, 'Config',
                             '\n'.join(config_lines))

    def create_group(self, name, users=[]):

        meta = """
        <group>
          <title>{}</title>
        </group>
        """.format(name)

        if len(users):
            root = ET.fromstring(meta)
            persons = ET.SubElement(root, 'person')
            for user in users:
                ET.SubElement(persons, 'person', {'userid': user})
            meta = ET.tostring(root)

        if not name in self.groups:
            self.groups.append(name)
        url = osc.core.makeurl(APIURL, ['group', name])
        osc.core.http_PUT(url, data=meta)

    def create_user(self, name):
        if name in self.users: return
        meta = """
        <person>
          <login>{}</login>
          <email>{}@example.com</email>
          <state>confirmed</state>
        </person>
        """.format(name, name)
        self.users.append(name)
        url = osc.core.makeurl(APIURL, ['person', name])
        osc.core.http_PUT(url, data=meta)
        url = osc.core.makeurl(APIURL, ['person', name],
                               {'cmd': 'change_password'})
        osc.core.http_POST(url, data='opensuse')
        home_project = 'home:' + name
        self.projects[home_project] = Project(home_project, create=False)

    def create_target(self):
        if self.projects.get('target'): return
        self.create_user('staging-bot')
        self.create_group('factory-staging', users=['staging-bot'])
        p = Project(name=self.project,
                    reviewer={'groups': ['factory-staging']})
        self.projects['target'] = p
        self.projects[self.project] = p

        url = osc.core.makeurl(APIURL, ['staging', self.project, 'workflow'])
        data = "<workflow managers='factory-staging'/>"
        osc.core.http_POST(url, data=data)
        # creates A and B as well
        self.projects['staging:A'] = Project(self.project + ':Staging:A',
                                             create=False)
        self.projects['staging:B'] = Project(self.project + ':Staging:B',
                                             create=False)

    def setup_rings(self):
        self.create_target()
        self.projects['ring0'] = Project(name=self.project +
                                         ':Rings:0-Bootstrap')
        self.projects['ring1'] = Project(name=self.project +
                                         ':Rings:1-MinimalX')
        target_wine = Package(name='wine', project=self.projects['target'])
        target_wine.create_commit()
        self.create_link(target_wine, self.projects['ring1'])

    def create_package(self, project, package):
        project = self.create_project(project)
        return Package(name=package, project=project)

    def create_link(self, source_package, target_project, target_package=None):
        if not target_package:
            target_package = source_package.name
        target_package = Package(name=target_package, project=target_project)
        url = self.api.makeurl(
            ['source', target_project.name, target_package.name, '_link'])
        osc.core.http_PUT(url,
                          data='<link project="{}" package="{}"/>'.format(
                              source_package.project.name,
                              source_package.name))
        return target_package

    def create_project(self,
                       name,
                       reviewer={},
                       maintainer={},
                       project_links=[]):
        if isinstance(name, Project):
            return name
        if name in self.projects:
            return self.projects[name]
        self.projects[name] = Project(name,
                                      reviewer=reviewer,
                                      maintainer=maintainer,
                                      project_links=project_links)
        return self.projects[name]

    def submit_package(self, package=None, project=None):
        if not project:
            project = self.project
        request = Request(source_package=package, target_project=project)
        self.requests.append(request)
        return request

    def request_package_delete(self, package, project=None):
        if not project:
            project = package.project
        request = Request(target_package=package,
                          target_project=project,
                          type='delete')
        self.requests.append(request)
        return request

    def create_submit_request(self, project, package, text=None):
        project = self.create_project(project)
        package = Package(name=package, project=project)
        package.create_commit(text=text)
        return self.submit_package(package)

    def create_staging(self,
                       suffix,
                       freeze=False,
                       rings=None,
                       with_repo=False):
        staging_key = 'staging:{}'.format(suffix)
        # do not reattach if already present
        if not staging_key in self.projects:
            staging_name = self.project + ':Staging:' + suffix
            staging = Project(staging_name, create=False, with_repo=with_repo)
            url = osc.core.makeurl(
                APIURL, ['staging', self.project, 'staging_projects'])
            data = '<workflow><staging_project>{}</staging_project></workflow>'
            osc.core.http_POST(url, data=data.format(staging_name))
            self.projects[staging_key] = staging
        else:
            staging = self.projects[staging_key]

        project_links = []
        if rings == 0:
            project_links.append(self.project + ":Rings:0-Bootstrap")
        if rings == 1 or rings == 0:
            project_links.append(self.project + ":Rings:1-MinimalX")
        staging.update_meta(project_links=project_links,
                            maintainer={'groups': ['factory-staging']},
                            with_repo=with_repo)

        if freeze:
            FreezeCommand(self.api).perform(staging.name)

        return staging

    def __del__(self):
        if not self.api:
            return
        try:
            self.remove()
        except:
            # normally exceptions in destructors are ignored but a info
            # message is displayed. Make this a little more useful by
            # printing it into the capture log
            traceback.print_exc(None, sys.stdout)

    def remove(self):
        print('deleting staging workflow')
        for project in self.projects.values():
            project.remove()
        for request in self.requests:
            request.revoke()
        for group in self.groups:
            url = osc.core.makeurl(APIURL, ['group', group])
            try:
                osc.core.http_DELETE(url)
            except HTTPError:
                pass
        print('done')
        if hasattr(self.api, '_invalidate_all'):
            self.api._invalidate_all()
class TestApiCalls(unittest.TestCase):
    """
    Tests for various api calls to ensure we return expected content
    """

    def setUp(self):
        """
        Initialize the configuration
        """

        self.obs = OBS()
        Config('openSUSE:Factory')
        self.api = StagingAPI(APIURL, 'openSUSE:Factory')

    def tearDown(self):
        """Clean internal cache"""
        if hasattr(self.api, '_invalidate_all'):
            self.api._invalidate_all()

    def test_ring_packages(self):
        """
        Validate the creation of the rings.
        """
        # our content in the XML files
        # test content for listonly ie. list command
        ring_packages = {
            'elem-ring-0': 'openSUSE:Factory:Rings:0-Bootstrap',
            'elem-ring-1': 'openSUSE:Factory:Rings:0-Bootstrap',
            'elem-ring-mini': 'openSUSE:Factory:Rings:0-Bootstrap',
            'elem-ring-2': 'openSUSE:Factory:Rings:2-TestDVD',
            'git': 'openSUSE:Factory:Rings:2-TestDVD',
            'wine': 'openSUSE:Factory:Rings:1-MinimalX',
        }
        self.assertEqual(ring_packages, self.api.ring_packages_for_links)

        # test content for real usage
        ring_packages = {
            'elem-ring-0': 'openSUSE:Factory:Rings:0-Bootstrap',
            'elem-ring-1': 'openSUSE:Factory:Rings:1-MinimalX',
            'elem-ring-mini': 'openSUSE:Factory:Rings:0-Bootstrap',
            'elem-ring-2': 'openSUSE:Factory:Rings:2-TestDVD',
            'git': 'openSUSE:Factory:Rings:2-TestDVD',
            'wine': 'openSUSE:Factory:Rings:1-MinimalX',
        }
        self.assertEqual(ring_packages, self.api.ring_packages)

    @unittest.skip("no longer approving non-ring packages")
    def test_dispatch_open_requests(self):
        """
        Test dispatching and closure of non-ring packages
        """

        # Get rid of open requests
        self.api.dispatch_open_requests()
        # Check that we tried to close it
        self.assertEqual(httpretty.last_request().method, 'POST')
        self.assertEqual(httpretty.last_request().querystring[u'cmd'], [u'changereviewstate'])
        # Try it again
        self.api.dispatch_open_requests()
        # This time there should be nothing to close
        self.assertEqual(httpretty.last_request().method, 'GET')

    def test_pseudometa_get_prj(self):
        """
        Test getting project metadata from YAML in project description
        """

        # Try to get data from project that has no metadata
        data = self.api.get_prj_pseudometa('openSUSE:Factory:Staging:A')
        # Should be empty, but contain structure to work with
        self.assertEqual(data, {'requests': []})
        # Add some sample data
        rq = {'id': '123', 'package': 'test-package'}
        data['requests'].append(rq)
        # Save them and read them back
        self.api.set_prj_pseudometa('openSUSE:Factory:Staging:A', data)
        test_data = self.api.get_prj_pseudometa('openSUSE:Factory:Staging:A')
        # Verify that we got back the same data
        self.assertEqual(data, test_data)

    def test_list_projects(self):
        """
        List projects and their content
        """

        # Prepare expected results
        data = []
        for prj in self.obs.staging_project:
            data.append('openSUSE:Factory:Staging:' + prj)

        # Compare the results
        self.assertEqual(data, self.api.get_staging_projects())

    def test_open_requests(self):
        """
        Test searching for open requests
        """

        requests = []

        # get the open requests
        requests = self.api.get_open_requests()

        # Compare the results, we only care now that we got 1 of them not the content
        self.assertEqual(1, len(requests))

    def test_get_package_information(self):
        """
        Test if we get proper project, name and revision from the staging informations
        """

        package_info = {
            'dir_srcmd5': '751efeae52d6c99de48164088a33d855',
            'project': 'home:Admin',
            'rev': '7b98ac01b8071d63a402fa99dc79331c',
            'srcmd5': '7b98ac01b8071d63a402fa99dc79331c',
            'package': 'wine'
        }

        # Compare the results, we only care now that we got 2 of them not the content
        self.assertEqual(
            package_info,
            self.api.get_package_information('openSUSE:Factory:Staging:B', 'wine'))

    def test_request_id_package_mapping(self):
        """
        Test whether we can get correct id for sr in staging project
        """

        prj = 'openSUSE:Factory:Staging:B'
        # Get rq
        num = self.api.get_request_id_for_package(prj, 'wine')
        self.assertEqual(333, num)
        # Get package name
        self.assertEqual('wine', self.api.get_package_for_request_id(prj, num))

    def test_rm_from_prj(self):
        prj = 'openSUSE:Factory:Staging:B'
        pkg = 'wine'

        full_name = prj + '/' + pkg

        # Verify package is there
        self.assertTrue(full_name in self.obs.links)

        # Get rq number
        num = self.api.get_request_id_for_package(prj, pkg)

        # Delete the package
        self.api.rm_from_prj(prj, package='wine')

        # Verify package is not there
        self.assertTrue(full_name not in self.obs.links)

        # RQ is gone
        self.assertEqual(None, self.api.get_request_id_for_package(prj, pkg))
        self.assertEqual(None, self.api.get_package_for_request_id(prj, num))

        # Verify that review is closed
        self.assertEqual('accepted', self.obs.requests[str(num)]['review'])
        self.assertEqual('new', self.obs.requests[str(num)]['request'])

    def test_rm_from_prj_2(self):
        # Try the same with request number
        prj = 'openSUSE:Factory:Staging:B'
        pkg = 'wine'

        full_name = prj + '/' + pkg

        # Get rq number
        num = self.api.get_request_id_for_package(prj, pkg)

        # Delete the package
        self.api.rm_from_prj(prj, request_id=num)

        # Verify package is not there
        self.assertTrue(full_name not in self.obs.links)

        # RQ is gone
        self.assertEqual(None, self.api.get_request_id_for_package(prj, pkg))
        self.assertEqual(None, self.api.get_package_for_request_id(prj, num))

        # Verify that review is closed
        self.assertEqual('accepted', self.obs.requests[str(num)]['review'])
        self.assertEqual('new', self.obs.requests[str(num)]['request'])

    def test_add_sr(self):
        prj = 'openSUSE:Factory:Staging:A'
        rq = '123'

        # Running it twice shouldn't change anything
        for i in range(2):
            # Add rq to the project
            self.api.rq_to_prj(rq, prj)
            # Verify that review is there
            self.assertEqual('new', self.obs.requests[rq]['review'])
            self.assertEqual('review', self.obs.requests[rq]['request'])
            self.assertEqual(self.api.get_prj_pseudometa('openSUSE:Factory:Staging:A'),
                             {'requests': [{'id': 123, 'package': 'gcc', 'author': 'Admin'}]})

    def test_create_package_container(self):
        """Test if the uploaded _meta is correct."""

        self.api.create_package_container('openSUSE:Factory:Staging:B', 'wine')
        self.assertEqual(httpretty.last_request().method, 'PUT')
        self.assertEqual(httpretty.last_request().body, '<package name="wine"><title/><description/></package>')
        self.assertEqual(httpretty.last_request().path, '/source/openSUSE:Factory:Staging:B/wine/_meta')

        self.api.create_package_container('openSUSE:Factory:Staging:B', 'wine', disable_build=True)
        self.assertEqual(httpretty.last_request().method, 'PUT')
        self.assertEqual(httpretty.last_request().body, '<package name="wine"><title /><description /><build><disable /></build></package>')
        self.assertEqual(httpretty.last_request().path, '/source/openSUSE:Factory:Staging:B/wine/_meta')

    def test_review_handling(self):
        """Test whether accepting/creating reviews behaves correctly."""

        # Add review
        self.api.add_review('123', by_project='openSUSE:Factory:Staging:A')
        self.assertEqual(httpretty.last_request().method, 'POST')
        self.assertEqual(httpretty.last_request().querystring[u'cmd'], [u'addreview'])
        # Try to readd, should do anything
        self.api.add_review('123', by_project='openSUSE:Factory:Staging:A')
        self.assertEqual(httpretty.last_request().method, 'GET')
        # Accept review
        self.api.set_review('123', 'openSUSE:Factory:Staging:A')
        self.assertEqual(httpretty.last_request().method, 'POST')
        self.assertEqual(httpretty.last_request().querystring[u'cmd'], [u'changereviewstate'])
        # Try to accept it again should do anything
        self.api.set_review('123', 'openSUSE:Factory:Staging:A')
        self.assertEqual(httpretty.last_request().method, 'GET')
        # But we should be able to reopen it
        self.api.add_review('123', by_project='openSUSE:Factory:Staging:A')
        self.assertEqual(httpretty.last_request().method, 'POST')
        self.assertEqual(httpretty.last_request().querystring[u'cmd'], [u'addreview'])

    def test_prj_from_letter(self):

        # Verify it works
        self.assertEqual(self.api.prj_from_letter('openSUSE:Factory'), 'openSUSE:Factory')
        self.assertEqual(self.api.prj_from_letter('A'), 'openSUSE:Factory:Staging:A')

    def test_frozen_mtime(self):
        """Test frozen mtime."""

        # Testing frozen mtime
        self.assertTrue(self.api.days_since_last_freeze('openSUSE:Factory:Staging:A') > 8)
        self.assertTrue(self.api.days_since_last_freeze('openSUSE:Factory:Staging:B') < 1)

        # U == unfrozen
        self.assertTrue(self.api.days_since_last_freeze('openSUSE:Factory:Staging:U') > 1000)

    def test_frozen_enough(self):
        """Test frozen enough."""

        # Testing frozen mtime
        self.assertEqual(self.api.prj_frozen_enough('openSUSE:Factory:Staging:B'), True)
        self.assertEqual(self.api.prj_frozen_enough('openSUSE:Factory:Staging:A'), False)

        # U == unfrozen
        self.assertEqual(self.api.prj_frozen_enough('openSUSE:Factory:Staging:U'), False)

    def test_move(self):
        """Test package movement."""

        init_data = self.api.get_package_information('openSUSE:Factory:Staging:B', 'wine')
        self.api.move_between_project('openSUSE:Factory:Staging:B', 333, 'openSUSE:Factory:Staging:A')
        test_data = self.api.get_package_information('openSUSE:Factory:Staging:A', 'wine')
        self.assertEqual(init_data, test_data)
class TestApiCalls(unittest.TestCase):
    """
    Tests for various api calls to ensure we return expected content
    """

    def setUp(self):
        """
        Initialize the configuration
        """

        self.obs = OBS()
        Config('openSUSE:Factory')
        self.api = StagingAPI(APIURL, 'openSUSE:Factory')

    def tearDown(self):
        """Clean internal cache"""
        if hasattr(self.api, '_invalidate_all'):
            self.api._invalidate_all()

    def test_ring_packages(self):
        """
        Validate the creation of the rings.
        """
        # our content in the XML files
        # test content for listonly ie. list command
        ring_packages = {
            'apparmor': 'openSUSE:Factory:Rings:1-MinimalX',
            'elem-ring-0': 'openSUSE:Factory:Rings:0-Bootstrap',
            'elem-ring-1': 'openSUSE:Factory:Rings:0-Bootstrap',
            'elem-ring-mini': 'openSUSE:Factory:Rings:0-Bootstrap',
            'wine': 'openSUSE:Factory:Rings:1-MinimalX',
        }
        self.assertEqual(ring_packages, self.api.ring_packages_for_links)

        # test content for real usage
        ring_packages = {
            'apparmor': 'openSUSE:Factory:Rings:1-MinimalX',
            'elem-ring-0': 'openSUSE:Factory:Rings:0-Bootstrap',
            'elem-ring-1': 'openSUSE:Factory:Rings:1-MinimalX',
            'elem-ring-mini': 'openSUSE:Factory:Rings:0-Bootstrap',
            'wine': 'openSUSE:Factory:Rings:1-MinimalX',
        }
        self.assertEqual(ring_packages, self.api.ring_packages)

    @unittest.skip("no longer approving non-ring packages")
    def test_dispatch_open_requests(self):
        """
        Test dispatching and closure of non-ring packages
        """

        # Get rid of open requests
        self.api.dispatch_open_requests()
        # Check that we tried to close it
        self.assertEqual(httpretty.last_request().method, 'POST')
        self.assertEqual(httpretty.last_request().querystring[u'cmd'], [u'changereviewstate'])
        # Try it again
        self.api.dispatch_open_requests()
        # This time there should be nothing to close
        self.assertEqual(httpretty.last_request().method, 'GET')

    def test_pseudometa_get_prj(self):
        """
        Test getting project metadata from YAML in project description
        """

        # Try to get data from project that has no metadata
        data = self.api.get_prj_pseudometa('openSUSE:Factory:Staging:A')
        # Should be empty, but contain structure to work with
        self.assertEqual(data, {'requests': []})
        # Add some sample data
        rq = {'id': '123', 'package': 'test-package'}
        data['requests'].append(rq)
        # Save them and read them back
        self.api.set_prj_pseudometa('openSUSE:Factory:Staging:A', data)
        test_data = self.api.get_prj_pseudometa('openSUSE:Factory:Staging:A')
        # Verify that we got back the same data
        self.assertEqual(data, test_data)

    def test_list_projects(self):
        """
        List projects and their content
        """

        # Prepare expected results
        data = []
        for prj in self.obs.staging_project:
            data.append('openSUSE:Factory:Staging:' + prj)

        # Compare the results
        self.assertEqual(data, self.api.get_staging_projects())

    def test_open_requests(self):
        """
        Test searching for open requests
        """

        requests = []

        # get the open requests
        requests = self.api.get_open_requests()

        # Compare the results, we only care now that we got 1 of them not the content
        self.assertEqual(1, len(requests))

    def test_get_package_information(self):
        """
        Test if we get proper project, name and revision from the staging informations
        """

        package_info = {
            'dir_srcmd5': '751efeae52d6c99de48164088a33d855',
            'project': 'home:Admin',
            'rev': '7b98ac01b8071d63a402fa99dc79331c',
            'srcmd5': '7b98ac01b8071d63a402fa99dc79331c',
            'package': 'wine'
        }

        # Compare the results, we only care now that we got 2 of them not the content
        self.assertEqual(
            package_info,
            self.api.get_package_information('openSUSE:Factory:Staging:B', 'wine'))

    def test_request_id_package_mapping(self):
        """
        Test whether we can get correct id for sr in staging project
        """

        prj = 'openSUSE:Factory:Staging:B'
        # Get rq
        num = self.api.get_request_id_for_package(prj, 'wine')
        self.assertEqual(333, num)
        # Get package name
        self.assertEqual('wine', self.api.get_package_for_request_id(prj, num))

    def test_rm_from_prj(self):
        prj = 'openSUSE:Factory:Staging:B'
        pkg = 'wine'

        full_name = prj + '/' + pkg

        # Verify package is there
        self.assertTrue(full_name in self.obs.links)

        # Get rq number
        num = self.api.get_request_id_for_package(prj, pkg)

        # Delete the package
        self.api.rm_from_prj(prj, package='wine')

        # Verify package is not there
        self.assertTrue(full_name not in self.obs.links)

        # RQ is gone
        self.assertEqual(None, self.api.get_request_id_for_package(prj, pkg))
        self.assertEqual(None, self.api.get_package_for_request_id(prj, num))

        # Verify that review is closed
        self.assertEqual('accepted', self.obs.requests[str(num)]['review'])
        self.assertEqual('new', self.obs.requests[str(num)]['request'])

    def test_rm_from_prj_2(self):
        # Try the same with request number
        prj = 'openSUSE:Factory:Staging:B'
        pkg = 'wine'

        full_name = prj + '/' + pkg

        # Get rq number
        num = self.api.get_request_id_for_package(prj, pkg)

        # Delete the package
        self.api.rm_from_prj(prj, request_id=num)

        # Verify package is not there
        self.assertTrue(full_name not in self.obs.links)

        # RQ is gone
        self.assertEqual(None, self.api.get_request_id_for_package(prj, pkg))
        self.assertEqual(None, self.api.get_package_for_request_id(prj, num))

        # Verify that review is closed
        self.assertEqual('accepted', self.obs.requests[str(num)]['review'])
        self.assertEqual('new', self.obs.requests[str(num)]['request'])

    def test_add_sr(self):
        prj = 'openSUSE:Factory:Staging:A'
        rq = '123'

        # Running it twice shouldn't change anything
        for i in range(2):
            # Add rq to the project
            self.api.rq_to_prj(rq, prj)
            # Verify that review is there
            self.assertEqual('new', self.obs.requests[rq]['review'])
            self.assertEqual('review', self.obs.requests[rq]['request'])
            self.assertEqual(self.api.get_prj_pseudometa('openSUSE:Factory:Staging:A'),
                    {'requests': [{'id': 123, 'package': 'gcc', 'author': 'Admin', 'type': 'submit'}]})

    def test_create_package_container(self):
        """Test if the uploaded _meta is correct."""

        self.api.create_package_container('openSUSE:Factory:Staging:B', 'wine')
        self.assertEqual(httpretty.last_request().method, 'PUT')
        self.assertEqual(httpretty.last_request().body, '<package name="wine"><title/><description/></package>')
        self.assertEqual(httpretty.last_request().path, '/source/openSUSE:Factory:Staging:B/wine/_meta')

        self.api.create_package_container('openSUSE:Factory:Staging:B', 'wine', disable_build=True)
        self.assertEqual(httpretty.last_request().method, 'PUT')
        self.assertEqual(httpretty.last_request().body, '<package name="wine"><title/><description/><build><disable/></build></package>')
        self.assertEqual(httpretty.last_request().path, '/source/openSUSE:Factory:Staging:B/wine/_meta')

    def test_review_handling(self):
        """Test whether accepting/creating reviews behaves correctly."""

        # Add review
        self.api.add_review('123', by_project='openSUSE:Factory:Staging:A')
        self.assertEqual(httpretty.last_request().method, 'POST')
        self.assertEqual(httpretty.last_request().querystring[u'cmd'], [u'addreview'])
        # Try to readd, should do anything
        self.api.add_review('123', by_project='openSUSE:Factory:Staging:A')
        self.assertEqual(httpretty.last_request().method, 'GET')
        # Accept review
        self.api.set_review('123', 'openSUSE:Factory:Staging:A')
        self.assertEqual(httpretty.last_request().method, 'POST')
        self.assertEqual(httpretty.last_request().querystring[u'cmd'], [u'changereviewstate'])
        # Try to accept it again should do anything
        self.api.set_review('123', 'openSUSE:Factory:Staging:A')
        self.assertEqual(httpretty.last_request().method, 'GET')
        # But we should be able to reopen it
        self.api.add_review('123', by_project='openSUSE:Factory:Staging:A')
        self.assertEqual(httpretty.last_request().method, 'POST')
        self.assertEqual(httpretty.last_request().querystring[u'cmd'], [u'addreview'])

    def test_prj_from_letter(self):

        # Verify it works
        self.assertEqual(self.api.prj_from_letter('openSUSE:Factory'), 'openSUSE:Factory')
        self.assertEqual(self.api.prj_from_letter('A'), 'openSUSE:Factory:Staging:A')

    def test_frozen_mtime(self):
        """Test frozen mtime."""

        # Testing frozen mtime
        self.assertTrue(self.api.days_since_last_freeze('openSUSE:Factory:Staging:A') > 8)
        self.assertTrue(self.api.days_since_last_freeze('openSUSE:Factory:Staging:B') < 1)

        # U == unfrozen
        self.assertTrue(self.api.days_since_last_freeze('openSUSE:Factory:Staging:U') > 1000)

    def test_frozen_enough(self):
        """Test frozen enough."""

        # Testing frozen mtime
        self.assertEqual(self.api.prj_frozen_enough('openSUSE:Factory:Staging:B'), True)
        self.assertEqual(self.api.prj_frozen_enough('openSUSE:Factory:Staging:A'), False)

        # U == unfrozen
        self.assertEqual(self.api.prj_frozen_enough('openSUSE:Factory:Staging:U'), False)

    def test_move(self):
        """Test package movement."""

        init_data = self.api.get_package_information('openSUSE:Factory:Staging:B', 'wine')
        self.api.move_between_project('openSUSE:Factory:Staging:B', 333, 'openSUSE:Factory:Staging:A')
        test_data = self.api.get_package_information('openSUSE:Factory:Staging:A', 'wine')
        self.assertEqual(init_data, test_data)
Exemple #4
0
class StagingWorkflow(ABC):
    """This abstract base class is intended to setup and manipulate the environment (projects,
    users, etc.) in the local OBS instance used to tests the release tools. Thus, the derivative
    classes make easy to setup scenarios similar to the ones used during the real (open)SUSE
    development.
    """
    def __init__(self, project=PROJECT):
        """Initializes the configuration

        Note this constructor calls :func:`create_target`, which implies several projects and users
        are created right away.

        :param project: default target project
        :type project: str
        """
        THIS_DIR = os.path.dirname(os.path.abspath(__file__))
        oscrc = os.path.join(THIS_DIR, 'test.oscrc')

        # set to None so we return the destructor early in case of exceptions
        self.api = None
        self.apiurl = APIURL
        self.project = project
        self.projects = {}
        self.requests = []
        self.groups = []
        self.users = []
        self.attr_types = {}
        logging.basicConfig()

        # clear cache from other tests - otherwise the VCR is replayed depending
        # on test order, which can be harmful
        memoize_session_reset()

        osc.core.conf.get_config(override_conffile=oscrc,
                                 override_no_keyring=True,
                                 override_no_gnome_keyring=True)
        os.environ['OSC_CONFIG'] = oscrc

        if os.environ.get('OSC_DEBUG'):
            osc.core.conf.config['debug'] = 1

        CacheManager.test = True
        # disable caching, the TTLs break any reproduciblity
        Cache.CACHE_DIR = None
        Cache.PATTERNS = {}
        Cache.init()
        # Note this implicitly calls create_target()
        self.setup_remote_config()
        self.load_config()
        self.api = StagingAPI(APIURL, project)

    @abstractmethod
    def initial_config(self):
        """Values to use to initialize the 'Config' attribute at :func:`setup_remote_config`"""
        pass

    @abstractmethod
    def staging_group_name(self):
        """Name of the group in charge of the staging workflow"""
        pass

    def load_config(self, project=None):
        """Loads the corresponding :class:`osclib.Config` object into the attribute ``config``

        Such an object represents the set of values stored on the attribute 'Config' of the
        target project. See :func:`remote_config_set`.

        :param project: target project name
        :type project: str
        """

        if project is None:
            project = self.project
        self.config = Config(APIURL, project)

    def create_attribute_type(self, namespace, name, values=None):
        """Creates a new attribute type in the OBS instance."""

        if namespace not in self.attr_types: self.attr_types[namespace] = []

        if name not in self.attr_types[namespace]:
            self.attr_types[namespace].append(name)

        meta = """
        <namespace name='{}'>
            <modifiable_by user='******'/>
        </namespace>""".format(namespace)
        url = osc.core.makeurl(APIURL, ['attribute', namespace, '_meta'])
        osc.core.http_PUT(url, data=meta)

        meta = "<definition name='{}' namespace='{}'><description/>".format(
            name, namespace)
        if values:
            meta += "<count>{}</count>".format(values)
        meta += "<modifiable_by role='maintainer'/></definition>"
        url = osc.core.makeurl(APIURL, ['attribute', namespace, name, '_meta'])
        osc.core.http_PUT(url, data=meta)

    def setup_remote_config(self):
        """Creates the attribute 'Config' for the target project, with proper initial content.

        See :func:`remote_config_set` for more information about that attribute.

        Note this calls :func:`create_target` to ensure the target project exists.
        """
        # First ensure the existence of both the target project and the 'Config' attribute type
        self.create_target()
        self.create_attribute_type('OSRT', 'Config', 1)

        self.remote_config_set(self.initial_config(), replace_all=True)

    def remote_config_set(self, config, replace_all=False):
        """Sets the values of the 'Config' attribute for the target project.

        That attribute stores a set of values that are useful to influence the behavior of several
        tools and bots in the context of the given project. For convenience, such a collection of
        values is usually accessed using a :class:`osclib.Config` object. See :func:`load_config`.

        :param config: values to write into the attribute
        :type config: dict[str, str]
        :param replace_all: whether the previous content of 'Config' should be cleared up
        :type replace_all: bool
        """

        if not replace_all:
            config_existing = Config.get(self.apiurl, self.project)
            config_existing.update(config)
            config = config_existing

        config_lines = []
        for key, value in config.items():
            config_lines.append(f'{key} = {value}')

        attribute_value_save(APIURL, self.project, 'Config',
                             '\n'.join(config_lines))

    def create_group(self, name, users=[]):
        """Creates a group and assigns users to it.

        If the group already exists then it just updates it users.

        :param name: name of group
        :type name: str
        :param users: list of users to be in group
        :type users: list(str)
        """
        meta = """
        <group>
          <title>{}</title>
        </group>
        """.format(name)

        if len(users):
            root = ET.fromstring(meta)
            persons = ET.SubElement(root, 'person')
            for user in users:
                ET.SubElement(persons, 'person', {'userid': user})
            meta = ET.tostring(root)

        if name not in self.groups:
            self.groups.append(name)
        url = osc.core.makeurl(APIURL, ['group', name])
        osc.core.http_PUT(url, data=meta)

    def create_user(self, name):
        """Creates a user and their home project.

        Do nothing if the user already exists.
        Password is always "opensuse".

        The home project is not really created in the OBS instance, but :func:`Project.update_meta`
        can be used to create it.

        :param name: name of the user
        :type name: str
        """
        if name in self.users: return
        meta = """
        <person>
          <login>{}</login>
          <email>{}@example.com</email>
          <state>confirmed</state>
        </person>
        """.format(name, name)
        self.users.append(name)
        url = osc.core.makeurl(APIURL, ['person', name])
        osc.core.http_PUT(url, data=meta)
        url = osc.core.makeurl(APIURL, ['person', name],
                               {'cmd': 'change_password'})
        osc.core.http_POST(url, data='opensuse')
        home_project = 'home:' + name
        self.projects[home_project] = Project(home_project, create=False)

    def create_target(self):
        """Creates the main project that represents the product being developed and, as such, is
        expected to be the target for requests. It also creates all the associated projects, users
        and groups involved in the development workflow.

        In the base implementation, that includes:

            - The target project (see :func:`create_target_project`)
            - A group of staging managers including the "staging-bot" user
              (see :func:`create_staging_users`)
            - A couple of staging projects for the target one
            - The ProductVersion attribute type, that is used by the staging tools

        After the execution, the target project is indexed in the projects dictionary twice,
        by its name and as 'target'.
        """
        if self.projects.get('target'): return

        self.create_target_project()
        self.create_staging_users()

        self.projects['staging:A'] = Project(self.project + ':Staging:A',
                                             create=False)
        self.projects['staging:B'] = Project(self.project + ':Staging:B',
                                             create=False)

        # The ProductVersion is required for some actions, like accepting a staging project
        self.create_attribute_type('OSRT', 'ProductVersion', 1)

    def create_package(self, project, package):
        project = self.create_project(project)
        return Package(name=package, project=project)

    def create_link(self, source_package, target_project, target_package=None):
        if not target_package:
            target_package = source_package.name
        target_package = Package(name=target_package, project=target_project)
        url = self.api.makeurl(
            ['source', target_project.name, target_package.name, '_link'])
        osc.core.http_PUT(url,
                          data='<link project="{}" package="{}"/>'.format(
                              source_package.project.name,
                              source_package.name))
        return target_package

    def create_project(self,
                       name,
                       reviewer={},
                       maintainer={},
                       project_links=[]):
        """Creates project if it does not already exist.

        For params see the constructor of :class:`Project`

        :return: the project instance representing the given project
        :rtype: Project
        """
        if isinstance(name, Project):
            return name
        if name in self.projects:
            return self.projects[name]
        self.projects[name] = Project(name,
                                      reviewer=reviewer,
                                      maintainer=maintainer,
                                      project_links=project_links)
        return self.projects[name]

    def submit_package(self, package, project=None):
        """Creates submit request from package to target project.

        Both have to exist (Use :func:`create_submit_request` otherwise).

        :param package: package to submit
        :type package: Package
        :param project: project where to send submit request, None means use the default.
        :type project: Project or str or None
        :return: created request.
        :rtype: Request
        """
        if not project:
            project = self.project
        request = Request(source_package=package, target_project=project)
        self.requests.append(request)
        return request

    def request_package_delete(self, package, project=None):
        if not project:
            project = package.project
        request = Request(target_package=package,
                          target_project=project,
                          type='delete')
        self.requests.append(request)
        return request

    def create_submit_request(self,
                              project,
                              package,
                              text=None,
                              add_commit=True):
        """Creates submit request from package in specified project to default project.

        It creates project if not exist and also package.
        Package is commited with optional text.
        Note different parameters than submit_package.

        :param project: project where package will live
        :type project: Project or str
        :param package: package name to create
        :type package: str
        :param text: commit message for initial package creation
        :type text: str
        :param add_commit: whether add initial package commit. Useful to disable
               if package already exists
        :type add_commit: bool
        :return: created request.
        :rtype: Request
        """
        project = self.create_project(project)
        package = Package(name=package, project=project)
        if add_commit:
            package.create_commit(text=text)
        return self.submit_package(package)

    def __del__(self):
        if not self.api:
            return
        try:
            self.remove()
        except:
            # normally exceptions in destructors are ignored but a info
            # message is displayed. Make this a little more useful by
            # printing it into the capture log
            traceback.print_exc(None, sys.stdout)

    def remove(self):
        print('deleting staging workflow')

        for project in self.projects.values():
            project.remove()
        for request in self.requests:
            request.revoke()
        for group in self.groups:
            self.remove_group(group)
        for namespace in self.attr_types:
            self.remove_attribute_types(namespace)

        print('done')

        if hasattr(self.api, '_invalidate_all'):
            self.api._invalidate_all()

    def remove_group(self, group):
        """Removes a group from the OBS instance

        :param group: name of the group to remove
        :type group: str
        """
        print('deleting group', group)
        url = osc.core.makeurl(APIURL, ['group', group])
        self._safe_delete(url)

    def remove_attribute_types(self, namespace):
        """Removes an attributes namespace and all the attribute types it contains

        :param namespace: attributes namespace to remove
        :type namespace: str
        """
        for name in self.attr_types[namespace]:
            print('deleting attribute type {}:{}'.format(namespace, name))
            url = osc.core.makeurl(APIURL,
                                   ['attribute', namespace, name, '_meta'])
            self._safe_delete(url)
        print('deleting namespace', namespace)
        url = osc.core.makeurl(APIURL, ['attribute', namespace, '_meta'])
        self._safe_delete(url)

    def _safe_delete(self, url):
        """Performs a delete request to the OBS instance, ignoring possible http errors

        :param url: url to use for the http delete request
        :type url: str
        """
        try:
            osc.core.http_DELETE(url)
        except HTTPError:
            pass

    def create_target_project(self):
        """Creates the main target project (see :func:`create_target`)"""
        p = Project(name=self.project)
        self.projects['target'] = p
        self.projects[self.project] = p

    def create_staging_users(self):
        """Creates users and groups for the staging workflow for the target project
        (see :func:`create_target`)
        """
        group = self.staging_group_name()

        self.create_user('staging-bot')
        self.create_group(group, users=['staging-bot'])
        self.projects['target'].add_reviewers(groups=[group])

        url = osc.core.makeurl(APIURL, ['staging', self.project, 'workflow'])
        data = f"<workflow managers='{group}'/>"
        osc.core.http_POST(url, data=data)
class TestApiCalls(unittest.TestCase):
    """
    Tests for various api calls to ensure we return expected content
    """

    def setUp(self):
        """
        Initialize the configuration
        """

        self.obs = OBS()
        Config("openSUSE:Factory")
        self.api = StagingAPI(APIURL, "openSUSE:Factory")

    def tearDown(self):
        """Clean internal cache"""
        self.api._invalidate_all()

    def test_ring_packages(self):
        """
        Validate the creation of the rings.
        """
        # our content in the XML files
        ring_packages = {
            "elem-ring-0": "openSUSE:Factory:Rings:0-Bootstrap",
            "elem-ring-1": "openSUSE:Factory:Rings:1-MinimalX",
            "elem-ring-2": "openSUSE:Factory:Rings:2-TestDVD",
            "git": "openSUSE:Factory:Rings:2-TestDVD",
            "wine": "openSUSE:Factory:Rings:1-MinimalX",
        }
        self.assertEqual(ring_packages, self.api.ring_packages)

    @unittest.skip("no longer approving non-ring packages")
    def test_dispatch_open_requests(self):
        """
        Test dispatching and closure of non-ring packages
        """

        # Get rid of open requests
        self.api.dispatch_open_requests()
        # Check that we tried to close it
        self.assertEqual(httpretty.last_request().method, "POST")
        self.assertEqual(httpretty.last_request().querystring[u"cmd"], [u"changereviewstate"])
        # Try it again
        self.api.dispatch_open_requests()
        # This time there should be nothing to close
        self.assertEqual(httpretty.last_request().method, "GET")

    def test_pseudometa_get_prj(self):
        """
        Test getting project metadata from YAML in project description
        """

        # Try to get data from project that has no metadata
        data = self.api.get_prj_pseudometa("openSUSE:Factory:Staging:A")
        # Should be empty, but contain structure to work with
        self.assertEqual(data, {"requests": []})
        # Add some sample data
        rq = {"id": "123", "package": "test-package"}
        data["requests"].append(rq)
        # Save them and read them back
        self.api.set_prj_pseudometa("openSUSE:Factory:Staging:A", data)
        test_data = self.api.get_prj_pseudometa("openSUSE:Factory:Staging:A")
        # Verify that we got back the same data
        self.assertEqual(data, test_data)

    def test_list_projects(self):
        """
        List projects and their content
        """

        # Prepare expected results
        data = []
        for prj in self.obs.staging_project:
            data.append("openSUSE:Factory:Staging:" + prj)

        # Compare the results
        self.assertEqual(data, self.api.get_staging_projects())

    def test_open_requests(self):
        """
        Test searching for open requests
        """

        requests = []

        # get the open requests
        requests = self.api.get_open_requests()

        # Compare the results, we only care now that we got 1 of them not the content
        self.assertEqual(1, len(requests))

    def test_get_package_information(self):
        """
        Test if we get proper project, name and revision from the staging informations
        """

        package_info = {
            "dir_srcmd5": "751efeae52d6c99de48164088a33d855",
            "project": "home:Admin",
            "rev": "7b98ac01b8071d63a402fa99dc79331c",
            "srcmd5": "7b98ac01b8071d63a402fa99dc79331c",
            "package": "wine",
        }

        # Compare the results, we only care now that we got 2 of them not the content
        self.assertEqual(package_info, self.api.get_package_information("openSUSE:Factory:Staging:B", "wine"))

    def test_request_id_package_mapping(self):
        """
        Test whether we can get correct id for sr in staging project
        """

        prj = "openSUSE:Factory:Staging:B"
        # Get rq
        num = self.api.get_request_id_for_package(prj, "wine")
        self.assertEqual(333, num)
        # Get package name
        self.assertEqual("wine", self.api.get_package_for_request_id(prj, num))

    def test_rm_from_prj(self):
        prj = "openSUSE:Factory:Staging:B"
        pkg = "wine"

        full_name = prj + "/" + pkg

        # Verify package is there
        self.assertTrue(full_name in self.obs.links)

        # Get rq number
        num = self.api.get_request_id_for_package(prj, pkg)

        # Delete the package
        self.api.rm_from_prj(prj, package="wine")

        # Verify package is not there
        self.assertTrue(full_name not in self.obs.links)

        # RQ is gone
        self.assertEqual(None, self.api.get_request_id_for_package(prj, pkg))
        self.assertEqual(None, self.api.get_package_for_request_id(prj, num))

        # Verify that review is closed
        self.assertEqual("accepted", self.obs.requests[str(num)]["review"])
        self.assertEqual("new", self.obs.requests[str(num)]["request"])

    def test_rm_from_prj_2(self):
        # Try the same with request number
        prj = "openSUSE:Factory:Staging:B"
        pkg = "wine"

        full_name = prj + "/" + pkg

        # Get rq number
        num = self.api.get_request_id_for_package(prj, pkg)

        # Delete the package
        self.api.rm_from_prj(prj, request_id=num)

        # Verify package is not there
        self.assertTrue(full_name not in self.obs.links)

        # RQ is gone
        self.assertEqual(None, self.api.get_request_id_for_package(prj, pkg))
        self.assertEqual(None, self.api.get_package_for_request_id(prj, num))

        # Verify that review is closed
        self.assertEqual("accepted", self.obs.requests[str(num)]["review"])
        self.assertEqual("new", self.obs.requests[str(num)]["request"])

    def test_add_sr(self):
        prj = "openSUSE:Factory:Staging:A"
        rq = "123"

        # Running it twice shouldn't change anything
        for i in range(2):
            # Add rq to the project
            self.api.rq_to_prj(rq, prj)
            # Verify that review is there
            self.assertEqual("new", self.obs.requests[rq]["review"])
            self.assertEqual("review", self.obs.requests[rq]["request"])
            self.assertEqual(
                self.api.get_prj_pseudometa("openSUSE:Factory:Staging:A"),
                {"requests": [{"id": 123, "package": "gcc", "author": "Admin"}]},
            )

    def test_create_package_container(self):
        """Test if the uploaded _meta is correct."""

        self.api.create_package_container("openSUSE:Factory:Staging:B", "wine")
        self.assertEqual(httpretty.last_request().method, "PUT")
        self.assertEqual(httpretty.last_request().body, '<package name="wine"><title/><description/></package>')
        self.assertEqual(httpretty.last_request().path, "/source/openSUSE:Factory:Staging:B/wine/_meta")

        self.api.create_package_container("openSUSE:Factory:Staging:B", "wine", disable_build=True)
        self.assertEqual(httpretty.last_request().method, "PUT")
        self.assertEqual(
            httpretty.last_request().body,
            '<package name="wine"><title /><description /><build><disable /></build></package>',
        )
        self.assertEqual(httpretty.last_request().path, "/source/openSUSE:Factory:Staging:B/wine/_meta")

    def test_review_handling(self):
        """Test whether accepting/creating reviews behaves correctly."""

        # Add review
        self.api.add_review("123", by_project="openSUSE:Factory:Staging:A")
        self.assertEqual(httpretty.last_request().method, "POST")
        self.assertEqual(httpretty.last_request().querystring[u"cmd"], [u"addreview"])
        # Try to readd, should do anything
        self.api.add_review("123", by_project="openSUSE:Factory:Staging:A")
        self.assertEqual(httpretty.last_request().method, "GET")
        # Accept review
        self.api.set_review("123", "openSUSE:Factory:Staging:A")
        self.assertEqual(httpretty.last_request().method, "POST")
        self.assertEqual(httpretty.last_request().querystring[u"cmd"], [u"changereviewstate"])
        # Try to accept it again should do anything
        self.api.set_review("123", "openSUSE:Factory:Staging:A")
        self.assertEqual(httpretty.last_request().method, "GET")
        # But we should be able to reopen it
        self.api.add_review("123", by_project="openSUSE:Factory:Staging:A")
        self.assertEqual(httpretty.last_request().method, "POST")
        self.assertEqual(httpretty.last_request().querystring[u"cmd"], [u"addreview"])

    def test_prj_from_letter(self):

        # Verify it works
        self.assertEqual(self.api.prj_from_letter("openSUSE:Factory"), "openSUSE:Factory")
        self.assertEqual(self.api.prj_from_letter("A"), "openSUSE:Factory:Staging:A")

    def test_frozen_mtime(self):
        """Test frozen mtime."""

        # Testing frozen mtime
        self.assertTrue(self.api.days_since_last_freeze("openSUSE:Factory:Staging:A") > 8)
        self.assertTrue(self.api.days_since_last_freeze("openSUSE:Factory:Staging:B") < 1)

        # U == unfrozen
        self.assertTrue(self.api.days_since_last_freeze("openSUSE:Factory:Staging:U") > 1000)

    def test_frozen_enough(self):
        """Test frozen enough."""

        # Testing frozen mtime
        self.assertEqual(self.api.prj_frozen_enough("openSUSE:Factory:Staging:B"), True)
        self.assertEqual(self.api.prj_frozen_enough("openSUSE:Factory:Staging:A"), False)

        # U == unfrozen
        self.assertEqual(self.api.prj_frozen_enough("openSUSE:Factory:Staging:U"), False)

    def test_move(self):
        """Test package movement."""

        init_data = self.api.get_package_information("openSUSE:Factory:Staging:B", "wine")
        self.api.move_between_project("openSUSE:Factory:Staging:B", 333, "openSUSE:Factory:Staging:A")
        test_data = self.api.get_package_information("openSUSE:Factory:Staging:A", "wine")
        self.assertEqual(init_data, test_data)
class StagingWorkflow(object):
    def __init__(self, project=PROJECT):
        """
        Initialize the configuration
        """
        THIS_DIR = os.path.dirname(os.path.abspath(__file__))
        oscrc = os.path.join(THIS_DIR, 'test.oscrc')

        self.apiurl = APIURL
        logging.basicConfig()

        # clear cache from other tests - otherwise the VCR is replayed depending
        # on test order, which can be harmful
        memoize_session_reset()

        osc.core.conf.get_config(override_conffile=oscrc,
                                 override_no_keyring=True,
                                 override_no_gnome_keyring=True)
        if os.environ.get('OSC_DEBUG'):
            osc.core.conf.config['debug'] = 1
        self.project = project
        self.projects = {}
        self.requests = []
        self.groups = []
        self.users = []
        CacheManager.test = True
        # disable caching, the TTLs break any reproduciblity
        Cache.CACHE_DIR = None
        Cache.PATTERNS = {}
        Cache.init()
        self.setup_remote_config()
        self.load_config()
        self.api = StagingAPI(APIURL, project)

    def load_config(self, project=None):
        if project is None:
            project = self.project
        self.config = Config(APIURL, project)

    def create_attribute_type(self, namespace, name, values=None):
        meta="""
        <namespace name='{}'>
            <modifiable_by user='******'/>
        </namespace>""".format(namespace)
        url = osc.core.makeurl(APIURL, ['attribute', namespace, '_meta'])
        osc.core.http_PUT(url, data=meta)

        meta="<definition name='{}' namespace='{}'><description/>".format(name, namespace)
        if values:
            meta += "<count>{}</count>".format(values)
        meta += "<modifiable_by role='maintainer'/></definition>"
        url = osc.core.makeurl(APIURL, ['attribute', namespace, name, '_meta'])
        osc.core.http_PUT(url, data=meta)

    def setup_remote_config(self):
        self.create_target()
        self.create_attribute_type('OSRT', 'Config', 1)
        attribute_value_save(APIURL, self.project, 'Config', 'overridden-by-local = remote-nope\n'
                                                        'remote-only = remote-indeed\n')

    def create_group(self, name, users=[]):

        meta = """
        <group>
          <title>{}</title>
        </group>
        """.format(name)

        if len(users):
            root = ET.fromstring(meta)
            persons = ET.SubElement(root, 'person')
            for user in users:
                ET.SubElement(persons, 'person', { 'userid': user } )
            meta = ET.tostring(root)

        if not name in self.groups:
            self.groups.append(name)
        url = osc.core.makeurl(APIURL, ['group', name])
        osc.core.http_PUT(url, data=meta)

    def create_user(self, name):
        if name in self.users: return
        meta = """
        <person>
          <login>{}</login>
          <email>{}@example.com</email>
          <state>confirmed</state>
        </person>
        """.format(name, name)
        self.users.append(name)
        url = osc.core.makeurl(APIURL, ['person', name])
        osc.core.http_PUT(url, data=meta)
        url = osc.core.makeurl(APIURL, ['person', name], {'cmd': 'change_password'})
        osc.core.http_POST(url, data='opensuse')
        home_project = 'home:' + name
        self.projects[home_project] = Project(home_project, create=False)

    def create_target(self):
        if self.projects.get('target'): return
        self.create_group('factory-staging')
        self.projects['target'] = Project(name=self.project, reviewer={'groups': ['factory-staging']})

    def setup_rings(self):
        self.create_target()
        self.projects['ring0'] = Project(name=self.project + ':Rings:0-Bootstrap')
        self.projects['ring1'] = Project(name=self.project + ':Rings:1-MinimalX')
        target_wine = Package(name='wine', project=self.projects['target'])
        target_wine.create_commit()
        self.create_link(target_wine, self.projects['ring1'])

    def create_package(self, project, package):
        project = self.create_project(project)
        return Package(name=package, project=project)

    def create_link(self, source_package, target_project, target_package=None):
        if not target_package:
            target_package = source_package.name
        target_package = Package(name=target_package, project=target_project)
        url = self.api.makeurl(['source', target_project.name, target_package.name, '_link'])
        osc.core.http_PUT(url, data='<link project="{}" package="{}"/>'.format(source_package.project.name,
                                                                               source_package.name))
        return target_package

    def create_project(self, name,  reviewer={}, maintainer={}, project_links=[]):
        if isinstance(name, Project):
            return name
        if name in self.projects:
            return self.projects[name]
        self.projects[name] = Project(name, reviewer=reviewer,
                                      maintainer=maintainer,
                                      project_links=project_links)
        return self.projects[name]

    def submit_package(self, package=None):
        request = Request(source_package=package, target_project=self.project)
        self.requests.append(request)
        return request

    def create_submit_request(self, project, package, text=None):
        project = self.create_project(project)
        package = Package(name=package, project=project)
        package.create_commit(text=text)
        return self.submit_package(package)

    def create_staging(self, suffix, freeze=False, rings=None):
        project_links = []
        if rings == 0:
            project_links.append(self.project + ":Rings:0-Bootstrap")
        if rings == 1 or rings == 0:
            project_links.append(self.project + ":Rings:1-MinimalX")
        staging = Project(self.project + ':Staging:' + suffix, project_links=project_links)
        if freeze:
            FreezeCommand(self.api).perform(staging.name)
        self.projects['staging:{}'.format(suffix)] = staging
        return staging

    def __del__(self):
        try:
            self.remove()
        except:
            # normally exceptions in destructors are ignored but a info
            # message is displayed. Make this a little more useful by
            # printing it into the capture log
            traceback.print_exc(None, sys.stdout)

    def remove(self):
        print('deleting staging workflow')
        for project in self.projects.values():
            project.remove()
        for request in self.requests:
            request.revoke()
        for group in self.groups:
            url = osc.core.makeurl(APIURL, ['group', group])
            try:
                osc.core.http_DELETE(url)
            except HTTPError:
                pass
        print('done')
        if hasattr(self.api, '_invalidate_all'):
            self.api._invalidate_all()