diff --git a/osc-staging-workflow.dot b/osc-staging-workflow.dot new file mode 100644 index 00000000..912c1f7a --- /dev/null +++ b/osc-staging-workflow.dot @@ -0,0 +1,23 @@ +digraph staging { + + nodesep=1.5; + + graph [ sep="+2" ] + + node [ shape=rectangle ] + + devel [ label="Devel project" ]; + review [ label="Review team", shape=ellipse, style=dashed ]; + factory [ label="openSUSE Factory" ]; + staging [ label="Staging project" ]; + + devel -> review [ label="Developer submits fixes from staging repo" ]; + devel -> review [ label="Developer submits packages" ]; + review -> review [ label="Initial grouping of requests" ]; + review -> staging [ label="Review team creates staging project from GR/SR" ]; + staging -> devel [ label="Developer updates staging and fixes stuff" ]; + review -> factory [ label=(verified to produce same result as staging repo)> ]; + factory -> staging [ label="Factory maintainers have rights in staging", style=dotted ]; + devel -> staging [ label="Devel project maintainers have rights in staging", style=dotted ]; + staging -> staging [ label="Developer does changes" ]; +} diff --git a/osc-staging.py b/osc-staging.py new file mode 100644 index 00000000..cbd25e29 --- /dev/null +++ b/osc-staging.py @@ -0,0 +1,282 @@ +#!/usr/bin/env python +# -*- coding: UTF-8 -*- +# +# (C) 2013 mhrusecky@suse.cz, openSUSE.org +# (C) 2013 tchvatal@suse.cz, openSUSE.org +# Distribute under GPLv2 or GPLv3 + +from __future__ import print_function + +import osc +import osc.core + +from osc import cmdln +from osc import conf + +OSC_STAGING_VERSION='0.0.1' + +def _print_version(self): + """ Print version information about this extension. """ + print('{0}'.format(self.OSC_STAGING_VERSION)) + quit(0) + +def _staging_check(self, project, check_everything, opts): + """ + Checks whether project does not contain local changes + and whether it contains only links + :param project: staging project to check + :param everything: do not stop on first verification failure + :param opts: pointer to options + """ + + ret = 0 + for pkg in osc.core.meta_get_packagelist(opts.apiurl, project): + if ret == 1 and not check_everything: + break + f = http_GET(makeurl(apiurl, ['source', project, pkg])) + linkinfo = ET.parse(f).getroot().find('linkinfo') + if linkinfo is None: + print('Error: Not a source link: {0}/{1}'.format(project,pkg), file=sys.stderr) + ret = 1 + continue + if linkinfo.get('error'): + print('Error: Broken source link: {0}/{1}'.format(project, pkg), file=sys.stderr) + ret = 1 + continue + t = linkinfo.get('project') + p = linkinfo.get('package') + r = linkinfo.get('revision') + if len(server_diff(opts.apiurl, t, p, r, project, pkg, None, True)) > 0: + print('Error: Has local modifications: {0}/{1}'.format(project, pkg), file=sys.stderr) + ret = 1 + continue + return ret + +def _staging_create(self, sr, opts): + """ + Creates new staging project based on the submit request. + :param sr: submit request containing package to test directed for openSUSE:Factory + :param opts: pointer to options + """ + + # read info from sr + req = get_request(opts.apiurl, sr) + act = req.get_actions("submit")[0] + + trg_prj = act.tgt_project + trg_pkg = act.tgt_package + src_prj = act.src_project + src_pkg = act.src_package + stg_prj = trg_prj + ":Staging:" + trg_pkg + + # test if staging project exists + found = 1 + url = make_meta_url('prj', stg_prj, opts.apiurl) + try: + data = http_GET(url).readlines() + except HTTPError as e: + if e.code == 404: + found = 0 + else: + raise e + if found == 1: + print('Such a staging project already exists, overwrite? (Y/n)') + answer = sys.stdin.readline() + if re.search("^\s*[Nn]", answer): + print('Aborting...') + exit(1) + + # parse metadata from parent project + trg_meta_url = make_meta_url("prj", trg_prj, opts.apiurl) + data = http_GET(trg_meta_url).readlines() + + dis_repo = [] + en_repo = [] + repos = [] + perm ='' + in_build = 0 + for line in data: + # what repositories are disabled + if in_build == 1: + if re.search("^\s+", line): + in_build = 0 + elif re.search("^\s+", line): + in_build=1 + # what are the rights + elif re.search("^\s+( {2}/{3}...'.format(src_pkg,src_prj,stg_prj,trg_pkg)) + link_pac(src_prj, src_pkg, stg_prj, trg_pkg, True) + print + + return + +def _staging_remove(self, project, opts): + """ + Remove staging project. + :param project: staging project to delete + :param opts: pointer to options + """ + delete_project(opts.apiurl, project, force=True, msg=None) + print("Deleted.") + return + +def _staging_push(self, project, opts): + """ + Generate new submit requests group based on staging project. + :param project: staging project to submit + :param opts: pointer to options + """ + if not self._staging_check(opts.apiurl, project): + raise oscerr.ServiceRuntimeError('Verification of staging repo failed.') + + # loop over packages + for pkg in osc.core.meta_get_packagelist(opts.apiurl, project): + # decompose symlinks + u = makeurl(apiurl, ['source', project, pkg]) + f = http_GET(u) + root = ET.parse(f).getroot() + linkinfo = root.find('linkinfo') + if linkinfo == None: + print("Not a source link: {0}".format(pkg), file=sys.stderr) + quit(1) + if linkinfo.get('error'): + print("Broken source link: {0}".format(pkg), file=sys.stderr) + quit(1) + t = linkinfo.get('project') + p = linkinfo.get('package') + r = linkinfo.get('revision') + # Get rid of old requests + for rq in get_exact_request_list(opts.apiurl, t, project, pkg, pkg, ('new', 'review')): + # obsolete submit requests that contain the package (notify!) + print('.') + # sent new submitrequest for the package + +def _staging_submit_devel(self, project, opts): + """ + Generate new review requests for devel-projects based on our staging changes. + :param apiurl: pointer to obs api url link + :param project: staging project to submit into devel projects + """ + print("Not implemented.") + return + + +@cmdln.option('-e', '--everything', action='store_true', dest='everything', + help='during check do not stop on first first issue and show them all') +@cmdln.option('-v', '--version', action='store_true', + dest='version', + help='show version of the plugin') +def do_staging(self, subcmd, opts, *args): + """${cmd_name}: Commands to work with staging projects + + "check" will check if all packages are links without changes + + "create" (or "c") will create staging repo from specified submit request + + "push" (or "p") will push the staging project into grouped submit requests for openSUSE:Factory + + "remove" (or "r") will delete the staging project into submit requests for openSUSE:Factory + + "submit-devel" (or "s") will create review requests for changed packages in staging project + into their respective devel projects to obtain approval from maitnainers for pushing the + changes to openSUSE:Factory + + Usage: + osc staging check [--everything] REPO + osc staging create SR# + osc staging push REPO + osc staging remove REPO + osc stating submit-devel REPO + """ + if opts.version: + self._print_version() + + # available commands + cmds = ['check', 'push', 'p', 'create', 'c', 'remove', 'r'] + if not args or args[0] not in cmds: + raise oscerr.WrongArgs('Unknown stagings action. Choose one of the {0}.'.format(', '.join(cmds))) + + # verify the argument counts match the commands + cmd = args[0] + if cmd in ['push', 'p', 'submit-devel', 's', 'remove', 'r']: + min_args, max_args = 1, 1 + elif cmd in ['check']: + min_args, max_args = 1, 2 + elif cmd in ['create', 'c']: + min_args, max_args = 1, 1 + else: + raise RuntimeError('Unknown command: {0}'.format(cmd)) + if len(args) - 1 < min_args: + raise oscerr.WrongArgs('Too few arguments.') + if not max_args is None and len(args) - 1 > max_args: + raise oscerr.WrongArgs('Too many arguments.') + + # init the obs access + opts.apiurl = conf.config['apiurl'] + + # check for the opts + staging_check_everything = False + if opts.everything: + staging_check_everything = True + + # call the respective command and parse args by need + if cmd in ['push', 'p']: + project = args[1] + self._staging_push(project, opts) + elif cmd in ['create', 'c']: + sr = args[1] + self._staging_create(sr, opts) + elif cmd in ['check']: + project = args[1] + self._staging_check(project, staging_check_everything, opts) + elif cmd in ['remove', 'r']: + project = args[1] + self._staging_remove(project, opts) + elif cmd in ['submit-devel', 's']: + project = args[1] + self._staging_submit_devel(project, opts)