diff --git a/osc-staging.py b/osc-staging.py index 5d451860..640ab214 100644 --- a/osc-staging.py +++ b/osc-staging.py @@ -35,6 +35,7 @@ from osclib.obslock import OBSLock from osclib.select_command import SelectCommand from osclib.stagingapi import StagingAPI from osclib.unselect_command import UnselectCommand +from osclib.adi_command import AdiCommand OSC_STAGING_VERSION = '0.0.1' @@ -121,6 +122,8 @@ def do_staging(self, subcmd, opts, *args): min_args = 2 elif cmd == 'unselect': min_args, max_args = 1, None + elif cmd == 'adi': + min_args, max_args = None, None elif cmd in ('list', 'cleanup_rings'): min_args, max_args = 0, 0 else: @@ -162,9 +165,11 @@ def do_staging(self, subcmd, opts, *args): if opts.add: api.mark_additional_packages(tprj, [opts.add]) else: - SelectCommand(api).perform(tprj, args[2:], opts.move, + SelectCommand(api, tprj).perform(args[2:], opts.move, opts.from_, opts.no_freeze) elif cmd == 'cleanup_rings': CleanupRings(api).perform() elif cmd == 'list': ListCommand(api).perform() + elif cmd == 'adi': + AdiCommand(api).perform(args[1:]) diff --git a/osclib/adi_command.py b/osclib/adi_command.py new file mode 100644 index 00000000..4da03cbb --- /dev/null +++ b/osclib/adi_command.py @@ -0,0 +1,95 @@ +import json + +from osc import oscerr +from osc.core import delete_project + +from osclib.select_command import SelectCommand +from osclib.request_finder import RequestFinder + +class AdiCommand: + def __init__(self, api): + self.api = api + + def check_adi_project(self, project): + query_project = 'adi:' + project.split(':adi:')[1] + query = {'format': 'json'} + url = self.api.makeurl(('project', 'staging_projects', self.api.project, + query_project), query=query) + info = json.load(self.api.retried_GET(url)) + if len(info['building_repositories']): + print project, "still building" + return + if len(info['broken_packages']): + print "https://build.opensuse.org/project/show/{}".format(project), "has broken packages" + return + for review in info['missing_reviews']: + print project, "has at least one missing review by", review['by'], "in", review['request'] + return + if len(info['untracked_requests']) or len(info['obsolete_requests']): + print project, "has inconsistent requests" + return + print project, "is ready" + for req in info['selected_requests']: + print req['id'] + self.api.rm_from_prj(project, request_id=req['id'], msg='ready to accept') + delete_project(self.api.apiurl, project) + + def check_adi_projects(self): + for p in self.api.get_adi_projects(): + self.check_adi_project(p) + + def create_new_adi(self, wanted_requests): + all_requests = self.api.get_open_requests() + + non_ring_packages = [] + non_ring_requests = [] + + for request in all_requests: + # Consolidate all data from request + request_id = int(request.get('id')) + if len(wanted_requests) and request_id not in wanted_requests: + continue + action = request.findall('action') + if not action: + msg = 'Request {} has no action'.format(request_id) + raise oscerr.WrongArgs(msg) + # we care only about first action + action = action[0] + + # Where are we targeting the package + target_package = action.find('target').get('package') + + if not self.api.ring_packages.get(target_package): + non_ring_packages.append(target_package) + non_ring_requests.append(request_id) + + if len(non_ring_packages): + print "Not in a ring:", ' '.join(sorted(non_ring_packages)) + else: + return + + name = self.api.create_adi_project(None) + + sc = SelectCommand(self.api, name) + + for request in non_ring_requests: + if not self.api.rq_to_prj(request, name): + return False + + # Notify everybody about the changes + self.api.update_status_comments(name, 'select') + + + def perform(self, packages): + """ + Perform the list command + """ + if len(packages): + requests = set() + for request, request_project in RequestFinder.find_sr(packages, + self.api).items(): + requests.add(request) + self.create_new_adi(requests) + else: + self.check_adi_projects() + self.create_new_adi(()) diff --git a/osclib/select_command.py b/osclib/select_command.py index b04fb705..1ed0ff7b 100644 --- a/osclib/select_command.py +++ b/osclib/select_command.py @@ -14,9 +14,10 @@ MOVE = 'move' class SelectCommand(object): - def __init__(self, api): + def __init__(self, api, target_project): self.api = api self.affected_projects = set() + self.target_project = target_project def _package(self, request): """ @@ -103,7 +104,7 @@ class SelectCommand(object): else: raise oscerr.WrongArgs('Arguments for select are not correct.') - def perform(self, target_project, requests, move=False, + def perform(self, requests, move=False, from_=None, no_freeze=False): """ Select package and move it accordingly by arguments @@ -113,23 +114,25 @@ class SelectCommand(object): :param from_: location where from move the requests """ + if self.api.is_adi_project(self.target_project): + no_freeze = True + # If the project is not frozen enough yet freeze it - if not (no_freeze or self.api.prj_frozen_enough(target_project)): + if not (no_freeze or self.api.prj_frozen_enough(self.target_project)): print('Freeze the prj first') return False - # FreezeCommand(self.api).perform(target_project) - self.target_project = target_project + # FreezeCommand(self.api).perform(self.target_project) for request in RequestFinder.find_sr(requests, self.api): if not self.select_request(request, move, from_): return False # Notify everybody about the changes - self.api.update_status_comments(target_project, 'select') + self.api.update_status_comments(self.target_project, 'select') for fprj in self.affected_projects: self.api.update_status_comments(fprj, 'select') # now make sure we enable the prj if the prj contains any ringed package - self.api.build_switch_staging_project(target_project) + self.api.build_switch_staging_project(self.target_project) return True diff --git a/osclib/stagingapi.py b/osclib/stagingapi.py index 44fae307..3cca7c45 100644 --- a/osclib/stagingapi.py +++ b/osclib/stagingapi.py @@ -246,14 +246,21 @@ class StagingAPI(object): projects.append(val.get('name')) return projects + def is_adi_project(self, p): + return ':adi:' in p + + # this function will crash if given a non-adi project name + def extract_adi_number(self, p): + return int(p.split(':adi:')[1]) + def get_adi_projects(self): """ Get all current running ADI projects :return list of known ADI projects """ - projects = [p for p in self.get_staging_project() if ':adi:' in p] - return projects + projects = [p for p in self.get_staging_projects() if self.is_adi_project(p) ] + return sorted(projects, key=lambda project: self.extract_adi_number(project)) def do_change_review_state(self, request_id, newstate, message=None, by_group=None, by_user=None, by_project=None): @@ -745,7 +752,7 @@ class StagingAPI(object): # The force_enable_build will avoid the # map_ring_package_to_subproject if not force_enable_build: - if self.crings and not self.ring_packages.get(tar_pkg): + if self.crings and not self.ring_packages.get(tar_pkg) and not self.is_adi_project(project): disable_build = True else: project = self.map_ring_package_to_subject(project, tar_pkg) @@ -1092,10 +1099,14 @@ class StagingAPI(object): def _candidate_adi_project(self): """Decide a candidate name for an ADI project.""" - adi_projects = sorted(self.get_adi_projects()) + adi_projects = self.get_adi_projects() + adi_index = 1 for i, project in enumerate(adi_projects): - if not project.endswith(i): - return self.adi_prj_from_number(i) + adi_index = i + 1 + if not project.endswith(str(adi_index)): + return self.adi_prj_from_number(adi_index) + adi_index = i + 2 + return self.adi_prj_from_number(adi_index) def create_adi_project(self, name): """Create an ADI project.""" @@ -1123,5 +1134,9 @@ class StagingAPI(object): x86_64 """.format(name, self.project) - url = "" + url = make_meta_url('prj', name, self.apiurl) http_PUT(url, data=meta) + # put twice because on first put, the API adds useless maintainer + http_PUT(url, data=meta) + + return name diff --git a/tests/select_tests.py b/tests/select_tests.py index 0a101bc5..cdcc3cb2 100644 --- a/tests/select_tests.py +++ b/tests/select_tests.py @@ -38,7 +38,7 @@ class TestSelect(unittest.TestCase): def test_old_frozen(self): self.assertEqual(self.api.prj_frozen_enough('openSUSE:Factory:Staging:A'), False) # check it won't allow selecting - self.assertEqual(False, SelectCommand(self.api).perform('openSUSE:Factory:Staging:A', ['gcc'])) + self.assertEqual(False, SelectCommand(self.api, 'openSUSE:Factory:Staging:A').perform(['gcc'])) def test_select_comments(self): c_api = CommentAPI(self.api.apiurl) @@ -46,7 +46,7 @@ class TestSelect(unittest.TestCase): comments = c_api.get_comments(project_name=staging_b) # First select - self.assertEqual(True, SelectCommand(self.api).perform(staging_b, ['gcc', 'wine'])) + self.assertEqual(True, SelectCommand(self.api, staging_b).perform(['gcc', 'wine'])) first_select_comments = c_api.get_comments(project_name=staging_b) last_id = sorted(first_select_comments.keys())[-1] first_select_comment = first_select_comments[last_id] @@ -56,7 +56,7 @@ class TestSelect(unittest.TestCase): self.assertTrue('Request#123 for package gcc submitted by @Admin' in first_select_comment['comment']) # Second select - self.assertEqual(True, SelectCommand(self.api).perform(staging_b, ['puppet'])) + self.assertEqual(True, SelectCommand(self.api, staging_b).perform(['puppet'])) second_select_comments = c_api.get_comments(project_name=staging_b) last_id = sorted(second_select_comments.keys())[-1] second_select_comment = second_select_comments[last_id] @@ -70,11 +70,11 @@ class TestSelect(unittest.TestCase): def test_no_matches(self): # search for requests with self.assertRaises(oscerr.WrongArgs) as cm: - SelectCommand(self.api).perform('openSUSE:Factory:Staging:B', ['bash']) + SelectCommand(self.api, 'openSUSE:Factory:Staging:B').perform(['bash']) self.assertEqual(str(cm.exception), "No SR# found for: bash") def test_selected(self): # make sure the project is frozen recently for other tests - ret = SelectCommand(self.api).perform('openSUSE:Factory:Staging:B', ['wine']) + ret = SelectCommand(self.api, 'openSUSE:Factory:Staging:B').perform(['wine']) self.assertEqual(True, ret)