diff --git a/obs-mismatching-source-requests/Dockerfile b/obs-mismatching-source-requests/Dockerfile new file mode 100644 index 0000000000000000000000000000000000000000..d9e6068ce5ba5f89c656532f33f12f510caa126b --- /dev/null +++ b/obs-mismatching-source-requests/Dockerfile @@ -0,0 +1,8 @@ +FROM debian:buster-slim + +RUN apt-get update && apt-get install -y \ + ca-certificates \ + osc \ + python-jinja2 \ + --no-install-recommends \ + && rm -rf /var/lib/apt/lists/* diff --git a/obs-mismatching-source-requests/Jenkinsfile b/obs-mismatching-source-requests/Jenkinsfile new file mode 100644 index 0000000000000000000000000000000000000000..68380539a6240bbb20e8e041f1e6a5c5e8a22c81 --- /dev/null +++ b/obs-mismatching-source-requests/Jenkinsfile @@ -0,0 +1,26 @@ +pipeline { + agent { + dockerfile { + filename 'obs-mismatching-source-requests/Dockerfile' + } + } + environment { + RELEASE = "v2020dev0" + OSCRC = credentials("349d474d-a6d0-4ab0-987a-a3843916bdff") + } + stages { + stage('Test') { + steps { + sh ''' + export HOME=/tmp # switch to a writable folder so OSC can create the `~/.osc_cookiejar` file + ./obs-mismatching-source-requests/obs-mismatching-source-requests --oscrc "$OSCRC" --release $RELEASE --junit junit.xml + ''' + } + } + } + post { + success { + junit 'junit.xml' + } + } +} diff --git a/obs-mismatching-source-requests/obs-mismatching-source-requests b/obs-mismatching-source-requests/obs-mismatching-source-requests new file mode 100755 index 0000000000000000000000000000000000000000..cd146f74ff3a31d233bef24e28a3f5c97662d2c0 --- /dev/null +++ b/obs-mismatching-source-requests/obs-mismatching-source-requests @@ -0,0 +1,125 @@ +#!/usr/bin/env python + +from __future__ import print_function + +import argparse +import sys +import textwrap + +import jinja2 +import osc.conf +import osc.core + +try: + from xml.etree import cElementTree as ET +except ImportError: + import cElementTree as ET + +class File: + def __init__(self, name, md5): + self.name = name + self.md5 = md5 + +class Mismatch: + def __init__(self, url, src_project, dsc, rev, tgt_project, src_md5, tgt_md5): + self.url = url + self.src_project = src_project + self.dsc = dsc + self.rev = rev + self.tgt_project = tgt_project + self.tgt_md5 = tgt_md5 + self.src_md5 = src_md5 + +def get_xpath(state, projectprefix): + xpath = 'state/@name="{}"'.format(state) + if projectprefix: + xpath += ' and (starts-with(source/@project, "{0}") or starts-with(target/@project, "{0}"))'.format(projectprefix) + return xpath + +def get_requests(apiurl, xpath): + res = osc.core.search(apiurl, request=xpath) + collection = res['request'] + requests = [] + for root in collection.findall('request'): + r = osc.core.Request() + r.read(root) + requests.append(r) + return requests + +def get_files(apiurl, project, package, revision=None): + xml = osc.core.show_files_meta(apiurl, project, package, revision=revision) + filelist = ET.fromstring(xml) + ret = [] + for entry in filelist.findall('entry'): + if entry.get('name').startswith('_service:'): + continue + f = File(entry.get('name'), entry.get('md5')) + ret.append(f) + return ret + +def requesturl(apiurl, reqid): + return apiurl+"/request/show/"+reqid + +def get_mismatches(oscrc=None, projectprefix=None): + osc.conf.get_config(override_conffile = oscrc) + apiurl = osc.conf.config['apiurl'] + xpath = get_xpath('review', projectprefix) + requests = get_requests(apiurl, xpath) + mismatches = [] + for r in requests: + requrl = requesturl(apiurl, r.reqid) + try: + submit = next(iter(r.get_actions('submit')), None) + if not submit: + continue + src_filelist = get_files(apiurl, submit.src_project, submit.src_package, revision=submit.src_rev) + tgt_filelist = get_files(apiurl, submit.tgt_project, submit.tgt_package) + src_dsc = next((f for f in src_filelist if f.name.endswith('.dsc')), None) + tgt_dsc = next((f for f in tgt_filelist if f.name.endswith('.dsc')), None) + if tgt_dsc and src_dsc.name == tgt_dsc.name and src_dsc.md5 != tgt_dsc.md5: + mismatches.append(Mismatch(requrl, submit.src_project, src_dsc.name, submit.src_rev, submit.tgt_project, src_dsc.md5, tgt_dsc.md5)) + except: + print('error while fetching data for request {}'.format(requrl), file=sys.stderr) + raise + return mismatches + +def render_junit(project, release, mismatches): + junit_template = textwrap.dedent("""\ + <?xml version="1.0" encoding="UTF-8"?> + <testsuites tests="{{mismatches|length}}" failures="{{mismatches|length}}"> + <testsuite name="{{project}}:{{release}}"> + {%- for m in mismatches %} + <testcase name="{{m.dsc|replace(".dsc", "")}}"> + <failure type="failed"> + {{m.url}} {{m.src_project}} {{m.dsc}} rev{{m.rev}} ({{m.src_md5}}) -> {{m.tgt_project}} ({{m.tgt_md5}}) + </failure> + </testcase> + {%- endfor %} + </testsuite> + </testsuites> + """) + environment = jinja2.Environment(autoescape=True) + template = environment.from_string(junit_template) + result = template.render( + project=project, + release=release, + mismatches=mismatches, + ) + return result + +def main(): + parser = argparse.ArgumentParser(description='List OBS request with conflicting .dsc files') + parser.add_argument('--release', type=str, metavar='RELEASE', + help='restrict the search to requests sourceing or targeting projects in this release') + parser.add_argument('--oscrc', type=str, help='the OSC configuration with the OBS credentials') + parser.add_argument('--junit', type=argparse.FileType('w'), help='file to store results in JUnit format') + args = parser.parse_args() + projectprefix = 'apertis:'+args.release if args.release else None + mismatches = get_mismatches(args.oscrc, projectprefix) + for m in mismatches: + print("{m.url} {m.src_project} {m.dsc} rev{m.rev} ({m.src_md5}) -> {m.tgt_project} ({m.tgt_md5})".format(m=m)) + if args.junit: + args.junit.write(render_junit('apertis', args.release, mismatches)) + +if __name__ == '__main__': + main()