diff --git a/osc-staging.py b/osc-staging.py index 8e6da0bc..b4f30cc4 100644 --- a/osc-staging.py +++ b/osc-staging.py @@ -267,7 +267,6 @@ def _staging_get_rings(self, opts): ret[entry.attrib['name']] = prj return ret - def _staging_one_request(self, rq, opts): if (opts.verbose): ET.dump(rq) @@ -309,59 +308,6 @@ def _staging_one_request(self, rq, opts): self._staging_change_review_state(opts, id, 'accepted', by_group='factory-staging', message=msg) - -def _staging_check_one_source(self, flink, si, opts): - package = si.get('package') - # we have to check if its a link within the staging project - # in this case we need to keep the link as is, and not freezing - # the target. Otherwise putting kernel-source into staging prj - # won't get updated kernel-default (and many other cases) - for linked in si.findall('linked'): - if linked.get('project') in self.projectlinks: - # take the unexpanded md5 from Factory link - url = makeurl(opts.apiurl, ['source', 'openSUSE:Factory', package], { 'view': 'info', 'nofilename': '1' }) - #print package, linked.get('package'), linked.get('project') - f = http_GET(url) - proot = ET.parse(f).getroot() - ET.SubElement(flink, 'package', { 'name': package, 'srcmd5': proot.get('lsrcmd5'), 'vrev': si.get('vrev') }) - return package - ET.SubElement(flink, 'package', { 'name': package, 'srcmd5': si.get('srcmd5'), 'vrev': si.get('vrev') }) - return package - - -def _staging_receive_sources(self, prj, sources, flink, opts): - url = makeurl(opts.apiurl, ['source', prj], { 'view': 'info', 'nofilename': '1' } ) - f = http_GET(url) - root = ET.parse(f).getroot() - - for si in root.findall('sourceinfo'): - package = self._staging_check_one_source(flink, si, opts) - sources[package] = 1 - return sources - - -def _staging_freeze_prjlink(self, prj, opts): - url = makeurl(opts.apiurl, ['source', prj, '_meta']) - f = http_GET(url) - root = ET.parse(f).getroot() - sources = dict() - flink = ET.Element('frozenlinks') - links = root.findall('link') - links.reverse() - self.projectlinks = [] - for link in links: - self.projectlinks.append(link.get('project')) - - for lprj in self.projectlinks: - fl = ET.SubElement(flink, 'frozenlink', { 'project': lprj } ) - sources = self._staging_receive_sources(lprj, sources, fl, opts) - - url = makeurl(opts.apiurl, ['source', prj, '_project', '_frozenlinks'], { 'meta': '1' } ) - f = http_PUT(url, data=ET.tostring(flink)) - root = ET.parse(f).getroot() - print ET.tostring(root) - - @cmdln.option('-e', '--everything', action='store_true', help='during check do not stop on first first issue and show them all') @cmdln.option('-p', '--parent', metavar='TARGETPROJECT', @@ -442,7 +388,8 @@ def do_staging(self, subcmd, opts, *args): project = args[1] self._staging_submit_devel(project, opts) elif cmd in ['freeze']: - self._staging_freeze_prjlink(args[1], opts) + import osclib.freeze_command + osclib.freeze_command.FreezeCommand(opts.apiurl).perform(args[1]) elif cmd in ['select']: # TODO: have an api call for that stprj = 'openSUSE:Factory:Staging:%s' % args[1] diff --git a/osclib/freeze_command.py b/osclib/freeze_command.py new file mode 100644 index 00000000..7901839a --- /dev/null +++ b/osclib/freeze_command.py @@ -0,0 +1,192 @@ +import osc +from osc import cmdln +from osc.core import * +import time + +class FreezeCommand: + + def __init__(self, apiurl): + self.apiurl = apiurl + + def set_links(self): + url = makeurl(self.apiurl, ['source', self.prj, '_meta']) + f = http_GET(url) + root = ET.parse(f).getroot() + sources = dict() + flink = ET.Element('frozenlinks') + links = root.findall('link') + links.reverse() + self.projectlinks = [] + for link in links: + self.projectlinks.append(link.get('project')) + + def set_bootstrap_copy(self): + url = makeurl(self.apiurl, ['source', self.prj, '_meta']) + meta = self.prj_meta_for_bootstrap_copy(self.prj) + http_PUT(url, data=meta) + + def create_bootstrap_aggregate(self): + self.create_bootstrap_aggregate_meta() + self.create_bootstrap_aggregate_file() + + def bootstrap_packages(self): + url = makeurl(self.apiurl, ['source', 'openSUSE:Factory:Rings:0-Bootstrap']) + f = http_GET(url) + root = ET.parse(f).getroot() + l = list() + for e in root.findall('entry'): + name = e.get('name') + if name in ['rpmlint-mini-AGGR']: continue + l.append(name) + l.sort() + return l + + def create_bootstrap_aggregate_file(self): + url = makeurl(self.apiurl, ['source', self.prj, 'bootstrap-copy', '_aggregate']) + + root = ET.Element('aggregatelist') + a = ET.SubElement(root, 'aggregate', { 'project': "openSUSE:Factory:Rings:0-Bootstrap" } ) + + for package in self.bootstrap_packages(): + p = ET.SubElement(a, 'package') + p.text = package + + ET.SubElement(a, 'repository', { 'target': 'bootstrap_copy', 'source': 'standard' } ) + ET.SubElement(a, 'repository', { 'target': 'standard', 'source': 'nothing' } ) + ET.SubElement(a, 'repository', { 'target': 'images', 'source': 'nothing' } ) + + http_PUT(url, data=ET.tostring(root)) + + def create_bootstrap_aggregate_meta(self): + url = makeurl(self.apiurl, ['source', self.prj, 'bootstrap-copy', '_meta']) + + root = ET.Element('package', { 'project': self.prj, 'name': 'bootstrap-copy' }) + ET.SubElement(root, 'title') + ET.SubElement(root, 'description') + f = ET.SubElement(root, 'build') + # this one is to toggle + ET.SubElement(f, 'disable', { 'repository': 'bootstrap_copy' }) + # this one is the global toggle + ET.SubElement(f, 'disable') + + http_PUT(url, data=ET.tostring(root)) + + def build_switch_bootstrap_copy(self, state): + url = makeurl(self.apiurl, ['source', self.prj, 'bootstrap-copy', '_meta']) + pkgmeta = ET.parse(http_GET(url)).getroot() + + for f in pkgmeta.find('build'): + if f.get('repository', None) == 'bootstrap_copy': + f.tag = state + pass + http_PUT(url, data=ET.tostring(pkgmeta)) + + def verify_bootstrap_copy_code(self, code): + url = makeurl(self.apiurl, ['build', self.prj, '_result'], { 'package': 'bootstrap-copy' }) + + root = ET.parse(http_GET(url)).getroot() + for result in root.findall('result'): + if result.get('repository') == 'bootstrap_copy': + if not result.get('code') in ['published', 'unpublished']: + print ET.tostring(result) + return False + + if result.find('status').get('code') != code: + print ET.tostring(result) + return False + return True + + def perform(self, prj): + self.prj = prj + self.set_links() + + self.freeze_prjlinks() + + if 'openSUSE:Factory:Rings:1-MinimalX' in self.projectlinks \ + and not 'openSUSE:Factory:Rings:0-Bootstrap' in self.projectlinks: + self.set_bootstrap_copy() + self.create_bootstrap_aggregate() + print "waiting for scheduler to disable..." + while not self.verify_bootstrap_copy_code('disabled'): + time.sleep(1) + self.build_switch_bootstrap_copy('enable') + print "waiting for scheduler to copy..." + while not self.verify_bootstrap_copy_code('succeeded'): + time.sleep(1) + self.build_switch_bootstrap_copy('disable') + + def prj_meta_for_bootstrap_copy(self, prj): + root = ET.Element('project', { 'name': prj }) + ET.SubElement(root, 'title') + ET.SubElement(root, 'description') + ET.SubElement(root, 'link', { 'project': 'openSUSE:Factory:Rings:1-MinimalX' }) + f = ET.SubElement(root, 'build') + # this one stays + ET.SubElement(f, 'disable', { 'repository': 'bootstrap_copy' }) + # this one is the global toggle + ET.SubElement(f, 'disable') + f = ET.SubElement(root, 'publish') + ET.SubElement(f, 'disable') + f = ET.SubElement(root, 'debuginfo') + ET.SubElement(f, 'enable') + + r = ET.SubElement(root, 'repository', { 'name': 'bootstrap_copy' }) + ET.SubElement(r, 'path', { 'project': 'openSUSE:Factory', 'repository': 'ports' }) + a = ET.SubElement(r, 'arch') + a.text = 'i586' + a = ET.SubElement(r, 'arch') + a.text = 'x86_64' + + r = ET.SubElement(root, 'repository', { 'name': 'standard', 'linkedbuild': 'all', 'rebuild': 'direct' }) + ET.SubElement(r, 'path', { 'project': prj, 'repository': 'bootstrap_copy' }) + a = ET.SubElement(r, 'arch') + a.text = 'i586' + a = ET.SubElement(r, 'arch') + a.text = 'x86_64' + + r = ET.SubElement(root, 'repository', { 'name': 'images', 'linkedbuild': 'all', 'rebuild': 'direct' }) + ET.SubElement(r, 'path', { 'project': prj, 'repository': 'standard' }) + a = ET.SubElement(r, 'arch') + a.text = 'x86_64' + + return ET.tostring(root) + + def freeze_prjlinks(self): + sources = dict() + flink = ET.Element('frozenlinks') + + for lprj in self.projectlinks: + fl = ET.SubElement(flink, 'frozenlink', { 'project': lprj } ) + sources = self.receive_sources(lprj, sources, fl) + + url = makeurl(self.apiurl, ['source', self.prj, '_project', '_frozenlinks'], { 'meta': '1' } ) + http_PUT(url, data=ET.tostring(flink)) + + def receive_sources(self, prj, sources, flink): + url = makeurl(self.apiurl, ['source', prj], { 'view': 'info', 'nofilename': '1' } ) + f = http_GET(url) + root = ET.parse(f).getroot() + + for si in root.findall('sourceinfo'): + package = self.check_one_source(flink, si) + sources[package] = 1 + return sources + + def check_one_source(self, flink, si): + package = si.get('package') + # we have to check if its a link within the staging project + # in this case we need to keep the link as is, and not freezing + # the target. Otherwise putting kernel-source into staging prj + # won't get updated kernel-default (and many other cases) + for linked in si.findall('linked'): + if linked.get('project') in self.projectlinks: + # take the unexpanded md5 from Factory link + url = makeurl(self.apiurl, ['source', 'openSUSE:Factory', package], { 'view': 'info', 'nofilename': '1' }) + #print package, linked.get('package'), linked.get('project') + f = http_GET(url) + proot = ET.parse(f).getroot() + ET.SubElement(flink, 'package', { 'name': package, 'srcmd5': proot.get('lsrcmd5'), 'vrev': si.get('vrev') }) + return package + ET.SubElement(flink, 'package', { 'name': package, 'srcmd5': si.get('srcmd5'), 'vrev': si.get('vrev') }) + return package + diff --git a/tests/api_tests.py b/tests/api_tests.py index 09743747..c8596cfe 100644 --- a/tests/api_tests.py +++ b/tests/api_tests.py @@ -9,6 +9,9 @@ import sys import contextlib import unittest import httpretty +import difflib +import subprocess +import tempfile # mock is part of python3.3 try: import unittest.mock @@ -30,6 +33,15 @@ class TestApiCalls(unittest.TestCase): """ return os.path.join(os.getcwd(), 'tests/fixtures') + def _get_fixture_path(self, filename): + return os.path.join(self._get_fixtures_dir(), filename) + + def _get_fixture_content(self, filename): + response = open(self._get_fixture_path(filename), 'r') + content = response.read() + response.close() + return content + def _register_pretty_url_get(self, url, filename): """ Register specified get url with specific filename in fixtures @@ -37,9 +49,7 @@ class TestApiCalls(unittest.TestCase): :param filename: name of the fixtures file """ - response = open(os.path.join(self._get_fixtures_dir(), filename), 'r') - content = response.read() - response.close() + content = self._get_fixture_content(filename) httpretty.register_uri(httpretty.GET, url, @@ -274,6 +284,22 @@ class TestApiCalls(unittest.TestCase): data = f.read().strip() self.assertEqual(sys.stdout.getvalue().strip(), data) + def test_bootstrap_copy(self): + import osclib.freeze_command + fc = osclib.freeze_command.FreezeCommand('http://localhost') + + fp = self._get_fixture_path('staging-meta-for-bootstrap-copy.xml') + fixture = subprocess.check_output('/usr/bin/xmllint --format %s' % fp, shell=True) + + f = tempfile.NamedTemporaryFile(delete=False) + f.write(fc.prj_meta_for_bootstrap_copy('openSUSE:Factory:Staging:A')) + f.close() + + output = subprocess.check_output('/usr/bin/xmllint --format %s' % f.name, shell=True) + + for line in difflib.unified_diff(fixture.split("\n"), output.split("\n")): + print line + self.assertEqual(output, fixture) # Here place all mockable functions @contextlib.contextmanager diff --git a/tests/fixtures/staging-meta-for-bootstrap-copy.xml b/tests/fixtures/staging-meta-for-bootstrap-copy.xml new file mode 100644 index 00000000..bc97014a --- /dev/null +++ b/tests/fixtures/staging-meta-for-bootstrap-copy.xml @@ -0,0 +1,29 @@ + + + + + + + + + + + + + + + + + i586 + x86_64 + + + + i586 + x86_64 + + + + x86_64 + +