diff options
author | SoonKyu Park <sk7.park@samsung.com> | 2016-04-11 16:49:41 +0900 |
---|---|---|
committer | SoonKyu Park <sk7.park@samsung.com> | 2016-04-11 16:49:41 +0900 |
commit | c4c242e3cc074ac77967d84b16801f84cf6911c7 (patch) | |
tree | 924e21b662c7e73dbff5f5b5894c4539d5fcdaa2 | |
parent | a502320c9f0887c913b3e9f7013a9b67a3a738cb (diff) | |
parent | a0e5dec9a6c7da7165933998aa69a095362dcdb3 (diff) | |
download | repa-c4c242e3cc074ac77967d84b16801f84cf6911c7.tar.gz repa-c4c242e3cc074ac77967d84b16801f84cf6911c7.tar.bz2 repa-c4c242e3cc074ac77967d84b16801f84cf6911c7.zip |
Merge remote-tracking branch 'origin/release-20160315' into release-20160411
-rw-r--r-- | RELEASE_NOTES | 1 | ||||
-rw-r--r-- | debian/changelog | 13 | ||||
-rw-r--r-- | debian/control | 2 | ||||
-rw-r--r-- | examples/namespace/setup.py | 3 | ||||
-rw-r--r-- | examples/plugin/setup.py | 3 | ||||
-rw-r--r-- | packaging/repa.changes | 10 | ||||
-rw-r--r-- | packaging/repa.spec | 7 | ||||
-rw-r--r-- | repa.1 | 244 | ||||
-rw-r--r-- | repa/accept.py | 24 | ||||
-rw-r--r-- | repa/common.py | 85 | ||||
-rw-r--r-- | repa/diff.py | 28 | ||||
-rwxr-xr-x | repa/group.py | 16 | ||||
-rw-r--r-- | repa/jenkins.py | 62 | ||||
-rwxr-xr-x | repa/list.py | 42 | ||||
-rw-r--r-- | repa/lock.py | 74 | ||||
-rwxr-xr-x | repa/main.py | 10 | ||||
-rw-r--r-- | repa/obs.py | 22 | ||||
-rw-r--r-- | repa/rebuild.py | 76 | ||||
-rw-r--r-- | repa/reject.py | 23 | ||||
-rw-r--r-- | repa/remove.py | 74 | ||||
-rw-r--r-- | repa/rmgroup.py | 2 | ||||
-rwxr-xr-x | repa/show.py (renamed from repa/info.py) | 14 | ||||
-rw-r--r-- | repa/unlock.py | 74 | ||||
-rwxr-xr-x | setup.py | 13 |
24 files changed, 818 insertions, 104 deletions
diff --git a/RELEASE_NOTES b/RELEASE_NOTES index 7e45238..c216ddd 100644 --- a/RELEASE_NOTES +++ b/RELEASE_NOTES @@ -21,7 +21,6 @@ Release notes for repa 0.3 - Bugfixes: * create_sr: Fix unicode issue (Fixes: #1979, #TINF-618) - Release notes for repa 0.2 ============================ diff --git a/debian/changelog b/debian/changelog index 403323f..164a7c6 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,3 +1,16 @@ +repa (0.4) unstable; urgency=low + + * fix pylint error + * group: remove --force option + * Rename command info -> show + * Implement rebuild subcommand + * Implement lock/unlock subcommand + * Implement remove subcommand + * list: add build time + * Implement --edit command line option + + -- Ed Bartosh <eduard.bartosh@intel.com> Thu, 05 Feb 2015 22:21:17 +0200 + repa (0.3) unstable; urgency=low * group: disable publishing when aggregating packages diff --git a/debian/control b/debian/control index 5bdedf2..1563322 100644 --- a/debian/control +++ b/debian/control @@ -13,6 +13,8 @@ Architecture: all Depends: python (>=2.6), osc, gbs-api, + python-setuptools, + python-jenkinsapi, ${misc:Depends}, ${python:Depends} Description: tool to help release engineers to maintain code submissions diff --git a/examples/namespace/setup.py b/examples/namespace/setup.py index 5ce86e5..182a3dc 100644 --- a/examples/namespace/setup.py +++ b/examples/namespace/setup.py @@ -36,5 +36,4 @@ setup(name="repa_namespace_package", author='Ed Bartosh', author_email='eduard.bartosh@intel.com', packages=['repa'], - namespace_packages=['repa'] -) + namespace_packages=['repa']) diff --git a/examples/plugin/setup.py b/examples/plugin/setup.py index 11a1df4..36684fd 100644 --- a/examples/plugin/setup.py +++ b/examples/plugin/setup.py @@ -37,5 +37,4 @@ setup(name="repa_plugin", packages=['repa_plugin'], entry_points={ 'repa_commands': ['repa_plugin = repa_plugin.plugin:Test'] - } -) + }) diff --git a/packaging/repa.changes b/packaging/repa.changes index ee2d08e..c375886 100644 --- a/packaging/repa.changes +++ b/packaging/repa.changes @@ -1,3 +1,13 @@ +* Fri Mar 4 2016 Ed Bartosh <ed@ed.fi.intel.com> 0.4 +- fix pylint error +- group: remove --force option +- Rename command info -> show +- Implement rebuild subcommand +- Implement lock/unlock subcommand +- Implement remove subcommand +- list: add build time +- Implement --edit command line option + * Sun Dec 22 2014 Ed Bartosh <ed@ed.fi.intel.com> 0.3 - group: disable publishing when aggregating packages - Skip conflicting submissions when creating a group diff --git a/packaging/repa.spec b/packaging/repa.spec index 5d0c956..c52c738 100644 --- a/packaging/repa.spec +++ b/packaging/repa.spec @@ -1,5 +1,5 @@ Name: repa -Version: 0.3 +Version: 0.4 Summary: Release Engineering Process Assistant %if 0%{?opensuse_bs} Release: 1.<CI_CNT>.<B_CNT> @@ -18,6 +18,7 @@ Requires: gbs-api Requires: osc Requires: python >= 2.6 Requires: python-setuptools +Requires: python-jenkinsapi %description This tool is to assist release engineers to operate with submissions @@ -27,10 +28,10 @@ in easy and flexible manner %setup -q %build -python ./setup.py build +%{__python} ./setup.py build %install -python ./setup.py install --root=%{buildroot} --prefix=%{_prefix} +%{__python} ./setup.py install --root=%{buildroot} --prefix=%{_prefix} %clean rm -rf %{buildroot} @@ -29,7 +29,7 @@ Submission group is a temporary group of submissions, created for testing purpos 1. \fBlist\fR - list submissions and submission groups .RE .RS 2 -2. \fBinfo\fR - show detailed info about submission or submission group +2. \fBshow\fR - show detailed info about submission or submission group .RE .RS 2 3. \fBaccept\fR - accept submissions @@ -46,6 +46,18 @@ Submission group is a temporary group of submissions, created for testing purpos .RS 2 7. \fBdiff\fR - show the difference between projects .RE +.RS 2 +8. \fBrebuild\fR - rebuild submission +.RE +.RS 2 +9. \fBlock\fR - lock submission +.RE +.RS 2 +10. \fBunlock\fR - unlock submission +.RE +.RS 2 +11. \fBremove\fR - remove submission +.RE .\" =========================================================================== .\" Global options @@ -90,7 +102,7 @@ Turn on colorized output .\" .\" The "list" command description .\" -.SS \fBlist\fR [\-\-help] [\-\-processes <processes>] [\-\-showurls] [\-\-ignore <regexp>] [\-\-base <project>] +.SS \fBlist\fR [\-\-help] [\-\-processes <processes>] [\-\-showurls] [\-\-ignore <regexp>] [\-\-base <project>] [\-\-showtime] .RS 2 List submissions in the following format: @@ -107,9 +119,9 @@ submit/tizen/20140327.055105 package building libav zlib .RS 0 submit/tizen/20140327.080733 image building bluez .RS 0 -submit/tizen/20140328.063916 ready connman +submit/tizen/20140328.063916 ready 01:20:14 connman .RS 0 -submit/tizen/20140328.080409 ready sdbd +submit/tizen/20140328.080409 ready 00:14:30 sdbd .RS 0 .RE @@ -155,10 +167,18 @@ Note, that this parameter can also be specified in \fIrepa\fR configuration file .RE .PP +.B \-\-showtime +.RS 2 +Show submission build time for the submissions in 'ready' status. It's switched off by default. +Note, that this parameter can also be specified in \fIrepa\fR configuration file. +.RE + + +.PP .\" -.\" The "info" command description +.\" The "show" command description .\" -.SS \fBinfo\fR [\-\-help] \-\-project <project> <submission or group> +.SS \fBshow\fR [\-\-help] \-\-project <project> <submission or group> .RS 2 Show detailed information about submission @@ -193,14 +213,14 @@ Package build failures: .RE .\" -.\" The "info" command's options +.\" The "show" command's options .\" .RS 2 \fBOPTIONS\fR .RS 2 \-h, \-\-help .RS 2 -Print short help text about the "info" command and exit. +Print short help text about the "show" command and exit. .RE .\" @@ -229,6 +249,18 @@ Print short help text about the "accept" command and exit. Add acceptance comment for created SR. .RE +.PP +\-j \-\-jenkins +.RS 2 +Trigger Jenkins job to accept submission. +.RE + +.PP +\-e \-\-edit +.RS 2 +Run editor to edit comment. Editor is taken from EDITOR environment variable. +.RE + .\" .\" The "reject" command description .\" @@ -255,6 +287,18 @@ Print short help text about the "reject" command and exit. Add rejection comment for created SR. .RE +.PP +\-j \-\-jenkins +.RS 2 +Trigger Jenkins job to reject submission. +.RE + +.PP +\-e \-\-edit +.RS 2 +Run editor to edit comment. Editor is taken from EDITOR environment variable. +.RE + .\" .\" The "group" command description .\" @@ -287,12 +331,6 @@ Add comment to created submit group. It will be shown by list command. .RE .PP -\-f, \-\-force -.RS 2 -Force group creation for submissions without binary packages. Useful when grouping failed submissions for rejection. -.RE - -.PP \--noaggregate <regexp> .RS 2 Do not aggregate binary packages matching regexp. This is useful to skip aggregates, propagated by OBS from target project to prerlease projects, e.g. qemu-accel-*, mic-bootstrap, etc. @@ -365,6 +403,137 @@ Print short help text about the "diff" command and exit. .RE +.\" +.\" The "rebuild" command description +.\" +.SS \fBrebuild\fR [options] <submision> + +.RS 2 +Rebuild submission by triggering 're' Jenkins job. 're' job triggering rebuild by re-exports packages in submission. By default it re-exports all packages. It's also possible to re-export only one package using --package command line option. + +.\" +.\" The "rebuild" command's options +.\" +.RS 2 +\fBOPTIONS\fR +.RS 2 +.B \-h, \-\-help +.RS 2 +Print short help text about the "rebuild" command and exit. +.RE + +.PP +.B \-c \-\-comment COMMENT +.RS 2 +Provide a comment with the explanation of a reason for rebuild. Mandatory option. +.RE + +.PP +.B \-p \-\-package <package name> +.RS 2 +Rebuild only one package. +.RE + +.PP +\-e \-\-edit +.RS 2 +Run editor to edit comment. Editor is taken from EDITOR environment variable. +.RE + +.\" +.\" The "lock" command description +.\" +.SS \fBlock\fR [options] <submision> + +.RS 2 +Lock submission by triggering 're' Jenkins job. 're' job locks submission by disabling build in prerelease OBS project. + +.\" +.\" The "lock" command's options +.\" +.RS 2 +\fBOPTIONS\fR +.RS 2 +.B \-h, \-\-help +.RS 2 +Print short help text about the "lock" command and exit. +.RE + +.PP +.B \-c \-\-comment COMMENT +.RS 2 +Provide a comment with the explanation of a reason for lock. Mandatory option. +.RE + +.PP +\-e \-\-edit +.RS 2 +Run editor to edit comment. Editor is taken from EDITOR environment variable. +.RE + +.\" +.\" The "unlock" command description +.\" +.SS \fBunlock\fR [options] <submision> + +.RS 2 +Unlock submission by triggering 're' Jenkins job. 're' job unlocks submission by re-enabling build in prerelease OBS project. + +.\" +.\" The "unlock" command's options +.\" +.RS 2 +\fBOPTIONS\fR +.RS 2 +.B \-h, \-\-help +.RS 2 +Print short help text about the "unlock" command and exit. +.RE + +.PP +.B \-c \-\-comment COMMENT +.RS 2 +Provide a comment with the explanation of a reason for unlock. +.RE + +.PP +\-e \-\-edit +.RS 2 +Run editor to edit comment. Editor is taken from EDITOR environment variable. +.RE + +.\" +.\" The "remove" command description +.\" +.SS \fBremove\fR [options] <submision> + +.RS 2 +Remove submission by triggering 're' Jenkins job. 're' job remove prerelease OBS project. + +.\" +.\" The "remove" command's options +.\" +.RS 2 +\fBOPTIONS\fR +.RS 2 +.B \-h, \-\-help +.RS 2 +Print short help text about the "remove" command and exit. +.RE + +.PP +.B \-c \-\-comment COMMENT +.RS 2 +Provide a comment with the explanation of a reason for remove. This is mandatory option. +.RE + +.PP +\-e \-\-edit +.RS 2 +Run editor to edit comment. Editor is taken from EDITOR environment variable. +.RE + + .SH CONFIGURATION FILE .RS 2 @@ -388,6 +557,15 @@ apiuser = your_user_name apipasswd = your_password .RE .RS 2 +jenkins_url = https://build.tizen.org/robot +.RE +.RS 2 +jenkins_user = your_jenkins_user_name +.RE +.RS 2 +jenkins_passwd = your_jenkins_password +.RE +.RS 2 processes = 20 .RE .RS 2 @@ -408,14 +586,46 @@ ignore = arm-.*/armv7./.*_aggregate$ .RS 2 noaggregate = mic-bootstrap-x86-arm.rpm|mic-bootstrap.rpm|mic-bootstrap-debugsource.rpm|qemu-accel-armv7l.rpm|qemu-accel-armv7l-cross-arm.rpm|qemu-linux-user-cross-arm.rpm .RE - +.RS 2 +showtime = off +.RE +.RS 2 +accept_comment = default comment + for accept + command +.RE +.RS 2 +reject_comment = default comment + for reject + command +.RE +.RS 2 +rebuild_comment = default comment + for rebuild + command +.RE +.RS 2 +lock_comment = default comment + for lock + command +.RE +.RS 2 +unlock_comment = default comment + for unlock + command +.RE +.RS 2 +remove_comment = default comment + for remove + command +.RE .RS 2 -Mandatory options: apiurl, apiuser, apipasswd and project +Mandatory options: apiurl, apiuser, apipasswd, jenkins_url, jenkins_user, jenkins_passwd and project .RE .RS 2 -Some options (project, processes, colorize, showurls, ignore, noaggregate, base) can be overridden by commandline options (--project, --processes, --colorize, --showurls, --ignore, --noaggregate, --base) +Some options (project, processes, colorize, showurls, ignore, noaggregate, base, showtime) can be overridden by commandline options (--project, --processes, --colorize, --showurls, --ignore, --noaggregate, --base, --showtime) .RE .SH BUGS diff --git a/repa/accept.py b/repa/accept.py index 8292f24..45be50a 100644 --- a/repa/accept.py +++ b/repa/accept.py @@ -30,10 +30,11 @@ Accept submissions. """ import sys +from collections import namedtuple from repa.obs import OBS from repa.main import sub_main -from repa.common import accept_or_reject +from repa.common import accept_or_reject, edit class Accept(object): @@ -44,17 +45,32 @@ class Accept(object): help = description @staticmethod - def add_arguments(parser, _config): + def add_arguments(parser, config): """Adds arguments to the parser. Called from [sub_]main.""" + parser.add_argument('submission', help='submission or group') - parser.add_argument('-c', '--comment', help='comment', default='') + parser.add_argument('-c', '--comment', help='comment', + default=config.get('accept_comment', '')) + parser.add_argument('-j', '--jenkins', action='store_true', + help='trigger Jenkins job') + parser.add_argument('-e', '--edit', action='store_true', + help='run editor to edit comment') @staticmethod def run(argv): """Command line entry point. Called from [sub_]main.""" + if argv.edit: + argv.comment = edit(argv.comment) + obs = OBS(argv.apiurl, argv.apiuser, argv.apipasswd) + + cred = None + if argv.jenkins: + cred = namedtuple('cred', ['url', 'username', 'password'])(\ + argv.jenkins_url, argv.jenkins_user, argv.jenkins_passwd) + return accept_or_reject(obs, argv.submission, 'accepted', - argv.project, argv.comment) + argv.project, argv.comment, cred) if __name__ == '__main__': sys.exit(sub_main(sys.argv[1:], Accept())) diff --git a/repa/common.py b/repa/common.py index 90b5640..e63f073 100644 --- a/repa/common.py +++ b/repa/common.py @@ -33,7 +33,12 @@ import sys import os import time import json +import tempfile +import subprocess from functools import wraps, partial +from distutils.spawn import find_executable + +from repa.jenkins import trigger_build OBS_PROJECT_PREFIX = "home:prerelease:" @@ -67,10 +72,10 @@ def get_project_by_name(obs, name, target): def _resolve_submissions(obs, name, target): """Get list of submissions with meta. Resolves submitgroups.""" - project, meta, _bresults = get_project_by_name(obs, name, target) + project, meta = get_project_by_name(obs, name, target)[:2] if name.startswith('submitgroup'): for subm in meta['submissions']: - sprj, smeta, _bresults = get_project_by_name(obs, subm, target) + sprj, smeta = get_project_by_name(obs, subm, target)[:2] yield subm, sprj, smeta else: yield name, project, meta @@ -82,8 +87,14 @@ def delete_project(obs, name, target): obs.delete_project(project) -def accept_or_reject(obs, submission, state, target, comment=''): - """Create SRs and set their state for one submission or for a group.""" +def accept_or_reject(obs, submission, state, target, comment='', + jenkins_cred=None): + """ + Create SRs and set their state for one submission or for a group. + + This can be done 2 ways - directly using OBS API and by triggering + jenkins job + """ for name, project, meta in _resolve_submissions(obs, submission, target): # osc submitreq [OPTIONS] SOURCEPRJ SOURCEPKG DESTPRJ [DESTPKG] # osc request accept [-m TEXT] ID @@ -97,22 +108,32 @@ def accept_or_reject(obs, submission, state, target, comment=''): message += "Comments: %s \nGit project: %s\nTag: %s" \ % (comment or "submission %s" % str(name), - projects, - meta['git_tag']) - - # Create SR - reqid = obs.create_sr(project, obs.get_source_packages(project), - str(meta['obs_target_prj']), message=message) + projects, meta['git_tag']) + + target_prj = str(meta['obs_target_prj']) + + if jenkins_cred: + build, status, out = \ + trigger_build('re', {'action': state, + 'submission': str(name), + 'target_project': target_prj, + 'comment': comment}, jenkins_cred) + print "Jenkins job: re, build #%s, status: %s" % (build, status) + print out + else: + # Create SR + reqid = obs.create_sr(project, obs.get_source_packages(project), + target_prj, message=message) - print 'created SR %s' % reqid + print 'created SR %s' % reqid - # and immediately set its state - message = "SR %s is set to %s" % (reqid, state) - if comment: - message += comment - obs.set_sr_state(reqid, state=state, + # and immediately set its state + message = "SR %s is set to %s" % (reqid, state) + if comment: + message += comment + obs.set_sr_state(reqid, state=state, message=str(message), force=True) - print 'set SR state to', state + print 'set SR state to', state # delete submit group if submission.startswith('submitgroup'): @@ -201,3 +222,33 @@ def get_obs_url(meta, buildurl='https://build.tizen.org'): return os.path.join(buildurl, 'project/show?project=home:prerelease:%s:%s' % (meta['obs_target_prj'], name.replace('/', ':'))) + +def edit(content): + """ + Launch an editor to get input from user. + Returns: content of user input. + """ + editor = os.getenv('EDITOR') or 'vi' + + if not find_executable(editor): + raise RepaException("editor %s not found. Please set EDITOR " + "environment variable or install vi" % editor) + + fds, path = tempfile.mkstemp('.tmp', 'repa-', text=True) + try: + if content: + os.write(fds, content) + os.close(fds) + + try: + subprocess.call([editor, path]) + except OSError as error: + raise RepaException("Can't run %s %s: %s" % (editor, path, error)) + + with open(path) as fobj: + result = fobj.read() + finally: + os.unlink(path) + + return result + diff --git a/repa/diff.py b/repa/diff.py index 613ec01..954321b 100644 --- a/repa/diff.py +++ b/repa/diff.py @@ -27,13 +27,14 @@ import sys import os import json +from collections import namedtuple + import xml.etree.ElementTree as ET from repa.common import RepaException, Colorizer, get_prerelease from repa.obs import OBS from repa.main import sub_main - def gen_data(manifest): """Parse manifest. Yield git path and revision for every entry.""" try: @@ -58,16 +59,16 @@ def get_tag(obs, project, package): return None -def diff(obs, cmp_project, target_project, cmp_manifest, - target_manifest, is_colorize=False): +def diff(obs, cmpinfo, targetinfo, is_colorize=False): """ Show the difference between two projects in terms of revisions and tags. + cmpinfo and target info are named tuples: (project, manifest) """ colorizer = Colorizer(is_colorize) - target_data = dict(gen_data(target_manifest)) - cmp_data = dict(gen_data(cmp_manifest)) + target_data = dict(gen_data(targetinfo.manifest)) + cmp_data = dict(gen_data(cmpinfo.manifest)) for path in sorted(cmp_data): rev_target = target_data.get(path) @@ -76,12 +77,12 @@ def diff(obs, cmp_project, target_project, cmp_manifest, package = os.path.basename(path) # Get tag, accepted to cmp_project - cmp_tag = get_tag(obs, cmp_project, package) + cmp_tag = get_tag(obs, cmpinfo.project, package) status = '' # Colorize tag if it's pending in target project if cmp_tag: try: - prerelease = get_prerelease(cmp_tag, target_project) + prerelease = get_prerelease(cmp_tag, targetinfo.project) if obs.exists(prerelease): if is_colorize: cmp_tag = colorizer.green(cmp_tag) @@ -90,9 +91,9 @@ def diff(obs, cmp_project, target_project, cmp_manifest, except RepaException: pass - print "%-55s %-12s %-12s %-40s %s" % (path, rev_cmp[:10], - str(rev_target)[:10], - cmp_tag, status) + print "%-55s %-12s %-12s %-40s %s" % \ + (path, rev_cmp[:10], str(rev_target)[:10], cmp_tag, status) + class Diff(object): """Subcommand: diff projects.""" @@ -102,7 +103,7 @@ class Diff(object): help = description @staticmethod - def add_arguments(parser, _config): + def add_arguments(parser, _): """Adds arguments to the parser. Called from [sub_]main.""" parser.add_argument('cmp_project', help='Compared OBS project') parser.add_argument('cmp_manifest', help='Manifest of compared project') @@ -112,8 +113,9 @@ class Diff(object): def run(argv): """Command line entry point. Called from [sub_]main.""" obs = OBS(argv.apiurl, argv.apiuser, argv.apipasswd) - return diff(obs, argv.cmp_project, argv.project, - argv.cmp_manifest, argv.manifest, argv.colorize) + difftype = namedtuple('diff', ['project', 'manifest']) + return diff(obs, difftype(argv.cmp_project, argv.cmp_manifest), + difftype(argv.project, argv.manifest), argv.colorize) if __name__ == '__main__': sys.exit(sub_main(sys.argv[1:], Diff())) diff --git a/repa/group.py b/repa/group.py index 10d6a0a..9f34f2d 100755 --- a/repa/group.py +++ b/repa/group.py @@ -57,7 +57,7 @@ def check_target_prj(submissions): def check_build_results(bresults): """Check if build targets are published.""" - for subm, _prj, results in bresults: + for subm, _, results in bresults: for target, res in results.iteritems(): if res['state'] != 'published' or res['code'] != 'published': if res['packages']: @@ -67,7 +67,7 @@ def check_build_results(bresults): # target project: for pkg, status in res['packages'] ... -def check_binary_pkgs(obs, submissions, force=False, noaggregate=''): +def check_binary_pkgs(obs, submissions, noaggregate=''): """ Check if submissions have common binary packages. Check if binary packages exist. @@ -107,7 +107,7 @@ def create_group_project(obs, submissions, meta, comment): gmeta = {'name': name, 'obs_target_prj': target_prj, 'submissions': submissions, 'comment': comment} project = '%s%s:%s-group' % (OBS_PROJECT_PREFIX, str(target_prj), - name.replace('/', ':')) + name.replace('/', ':')) saved = sys.stdout sys.stdout = StringIO() @@ -152,7 +152,7 @@ def aggregate(obs, bresults, gproject, processes): def group_submissions(obs, submissions, target, comment, - force=False, processes=0, noaggregate=''): + processes=0, noaggregate=''): """Group multiple submissions into one group.""" # find correspondent prerelease projects info = {} @@ -170,7 +170,7 @@ def group_submissions(obs, submissions, target, comment, check_build_results(bresults) # filter out conflicting submissions - filtered = check_binary_pkgs(obs, info, force, noaggregate) + filtered = check_binary_pkgs(obs, info, noaggregate) bresults = [item for item in bresults if item[0] in filtered] info = dict(item for item in info.iteritems() if item[0] in filtered) @@ -196,13 +196,11 @@ class Group(object): def add_arguments(parser, config): """Adds arguments to the parser. Called from [sub_]main.""" parser.add_argument('submission', nargs='+', - help='space separated list of submissions') + help='space separated list of submissions') parser.add_argument('--processes', type=int, help='amount of parallel processes to use', default=config.get('processes')) parser.add_argument('-c', '--comment', help='comment', default='') - parser.add_argument('-f', '--force', action='store_true', - help='force group creation') parser.add_argument('--noaggregate', default=config.get('noaggregate', ''), help='do not aggregate packages matching regexp') @@ -212,7 +210,7 @@ class Group(object): """Command line entry point. Called from [sub_]main.""" obs = OBS(argv.apiurl, argv.apiuser, argv.apipasswd) return group_submissions(obs, argv.submission, argv.project, - argv.comment, argv.force, argv.processes, + argv.comment, argv.processes, argv.noaggregate) diff --git a/repa/jenkins.py b/repa/jenkins.py new file mode 100644 index 0000000..29759e8 --- /dev/null +++ b/repa/jenkins.py @@ -0,0 +1,62 @@ +#!/usr/bin/env python +# +# This file is part of REPA: Release Engineering Process Assistant. +# +# Copyright (C) 2015 Intel Corporation +# +# REPA is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# version 2 as published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, +# MA 02110-1301, USA. + +""" +REPA: Release Engineering Process Assistant. + +Copyright (C) Intel Corporation 2015 +Licence: GPL version 2 +Author: Ed Bartosh <eduard.bartosh@intel.com> + +Jenkins module. Triggering Jenkins builds. +""" + +from jenkinsapi.jenkins import Jenkins +from requests import HTTPError, ConnectionError + +class JenkinsError(Exception): + """Local exception.""" + pass + +def trigger_build(job, parameters, cred, block=True): + """ + Trigger Jenkins build. + + :param job: job name + :type job: string + :param parameters: job parameters + :type parameters: dictionary + :param cred: credentials + :type cred: named tuple cred.url, cred.username, cred.password + :param block: block the call until build is finished + :type block: bool + :returns: build number, build status, console output + """ + try: + jenkins = Jenkins(cred.url, cred.username, cred.password) + except (HTTPError, ConnectionError) as error: + raise JenkinsError("Can't connect to jenkins: %s" % str(error)) + + if job not in jenkins: + raise JenkinsError("Job %s doesn't exist" % job) + + qitem = jenkins[job].invoke(block=block, build_params=parameters) + build = qitem.get_build() + return build.get_number(), build.get_status(), build.get_console() diff --git a/repa/list.py b/repa/list.py index 4a43e18..0e0251b 100755 --- a/repa/list.py +++ b/repa/list.py @@ -38,7 +38,8 @@ from repa.common import (OBS_PROJECT_PREFIX, Colorizer, from repa.obs import OBS from repa.main import sub_main -def get_status(meta, colorizer, build_results=None, ignore=''): +def get_status(meta, colorizer, build_results=None, ignore='', + obs=None, showtime=False): """Get overall status by analyzing package and image build status.""" if build_results: codes = set() @@ -71,7 +72,19 @@ def get_status(meta, colorizer, build_results=None, ignore=''): else: return colorizer.blue('waiting for images') - return colorizer.green('ready') + status = 'ready' + if obs and showtime: + # Add build time to the status + project = get_prerelease(meta['git_tag'], meta['obs_target_prj']) + btime = 0 + for (repo, arch), target in build_results.iteritems(): + btime = max(btime, obs.get_build_time(str(project), str(repo), + str(arch))) + minutes, seconds = divmod(btime, 60) + hours, minutes = divmod(minutes, 60) + status += " %02d:%02d:%02d" % (hours, minutes, seconds) + + return colorizer.green(status) def show_urls(meta): @@ -93,7 +106,7 @@ def get_sr(obs, project, package, tag, status): return sreq[0] def list_submissions(obs, target, processes, base, is_colorize=False, - showurls=False, ignore=''): + showurls=False, ignore='', showtime=False): """List submissions and groups.""" colorizer = Colorizer(is_colorize) # submissions @@ -123,14 +136,17 @@ def list_submissions(obs, target, processes, base, is_colorize=False, rsr = get_sr(obs, base, projects[0], tag, 'revoked') if rsr: base_status = colorizer.red('rejected. SR %s' % rsr) - - print '%-37s %-37s %-37s %s' % (meta['git_tag'], \ - get_status(meta, colorizer, build_results, ignore), - base_status, ','.join(projects)) + print '%-37s %-37s %-37s %s' % \ + (meta['git_tag'], + get_status(meta, colorizer, build_results, + ignore, obs, showtime), + base_status, ','.join(projects)) else: - print '%-37s %-37s %s' % (meta['git_tag'], \ - get_status(meta, colorizer, build_results, ignore), - ','.join(projects)) + print '%-37s %-37s %s' % \ + (meta['git_tag'], \ + get_status(meta, colorizer, build_results, + ignore, obs, showtime), + ','.join(projects)) if showurls: show_urls(meta) @@ -166,13 +182,17 @@ class List(object): help='ignore package failures by regexp') parser.add_argument('--base', default=config.get('base', ''), help='Show submission status in base project') + parser.add_argument('--showtime', action='store_true', + help='show build time', + default=config.get('showtime', '').lower() == 'on') @staticmethod def run(argv): """Command line entry point. Called from [sub_]main""" obs = OBS(argv.apiurl, argv.apiuser, argv.apipasswd) return list_submissions(obs, argv.project, argv.processes, argv.base, - argv.colorize, argv.showurls, argv.ignore) + argv.colorize, argv.showurls, argv.ignore, + argv.showtime) if __name__ == '__main__': diff --git a/repa/lock.py b/repa/lock.py new file mode 100644 index 0000000..a9d9c04 --- /dev/null +++ b/repa/lock.py @@ -0,0 +1,74 @@ +#!/usr/bin/env python +# +# This file is part of REPA: Release Engineering Process Assistant. +# +# Copyright (C) 2015 Intel Corporation +# +# REPA is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# version 2 as published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, +# MA 02110-1301, USA. + +""" +REPA: Release Engineering Process Assistant. + +Copyright (C) Intel Corporation 2015 +Licence: GPL version 2 +Author: Ed Bartosh <eduard.bartosh@intel.com> + +Lock module. +Lock submissions. +""" + +import sys + +from collections import namedtuple + +from repa.main import sub_main +from repa.jenkins import trigger_build +from repa.common import edit + +class Lock(object): + """Subcommand: lock submissions.""" + + name = 'lock' + description = 'Lock submission' + help = description + + @staticmethod + def add_arguments(parser, config): + """Adds arguments to the parser. Called from [sub_]main.""" + parser.add_argument('submission', help='submission') + parser.add_argument('-c', '--comment', help='comment', + default=config.get('lock_comment', '')) + parser.add_argument('-e', '--edit', action='store_true', + help='run editor to edit comment') + + @staticmethod + def run(argv): + """Command line entry point. Called from [sub_]main.""" + if argv.edit: + argv.comment = edit(argv.comment) + job = 're' + cred = namedtuple('cred', ['url', 'username', 'password'])(\ + argv.jenkins_url, argv.jenkins_user, argv.jenkins_passwd) + build, status, out = \ + trigger_build(job, {'action': 'lock', + 'submission': argv.submission, + 'target_project': argv.project, + 'comment': argv.comment}, cred) + print "Jenkins job: %s, build #%s, status: %s" % (job, build, status) + print out + return status == 'SUCCESS' + +if __name__ == '__main__': + sys.exit(sub_main(sys.argv[1:], Lock())) diff --git a/repa/main.py b/repa/main.py index 3f62252..b27945c 100755 --- a/repa/main.py +++ b/repa/main.py @@ -53,10 +53,11 @@ def parse_args(argv): config = read_config(section=parsed.section) # recreate parser to parse rest of the command line - parser = ArgumentParser(prog='repa', - description='Release Engineering Process Assistant') + parser = \ + ArgumentParser(prog='repa', + description='Release Engineering Process Assistant') parser.add_argument('--version', action='version', - version='%(prog)s version 0.3') + version='%(prog)s version 0.4') parser.add_argument('-s', '--section', default='general', help='config section to use') parser.add_argument('-p', '--project', help='target project', @@ -82,7 +83,8 @@ def parse_args(argv): def read_config(paths=('/etc/repa.conf', expanduser('~/.repa.conf')), section='general', - mandatory=('apiurl', 'apiuser', 'apipasswd', 'project')): + mandatory=('apiurl', 'apiuser', 'apipasswd', 'project', + 'jenkins_url', 'jenkins_user', 'jenkins_passwd')): """ Read repa config. Configuration is read from the set of files provided. diff --git a/repa/obs.py b/repa/obs.py index 6cfd810..d25017f 100644 --- a/repa/obs.py +++ b/repa/obs.py @@ -63,6 +63,7 @@ user=%(user)s passx=%(passwdx)s """ +# pylint: disable=too-many-public-methods class OBS(OSC): """Interface to OBS API.""" @@ -113,11 +114,13 @@ class OBS(OSC): for project in projects: if processes > 1: - yield (project, processes[project][0].get(), - processes[project][1].get()) + yield (project, + processes[project][0].get(), + processes[project][1].get()) else: - yield (project, self.get_descr(project), - self.get_build_results(project)) + yield (project, + self.get_descr(project), + self.get_build_results(project)) @retry((OSCError, HTTPError)) @@ -141,6 +144,17 @@ class OBS(OSC): return buildres + @retry((OSCError, HTTPError)) + def get_build_time(self, prj, repo, arch): + """Get build time for the project/repo/arch.""" + url = core.makeurl(self.apiurl, + ['build', prj, repo, arch, '_jobhistory']) + history = self.core_http(core.http_GET, url) + history_root = ET.parse(history).getroot() + seconds = 0 + for node in history_root.findall('jobhist'): + seconds += int(node.get('endtime')) - int(node.get('starttime')) + return seconds def get_source_packages(self, prj): """Get list of binary packages in the project.""" diff --git a/repa/rebuild.py b/repa/rebuild.py new file mode 100644 index 0000000..898658f --- /dev/null +++ b/repa/rebuild.py @@ -0,0 +1,76 @@ +#!/usr/bin/env python +# +# This file is part of REPA: Release Engineering Process Assistant. +# +# Copyright (C) 2015 Intel Corporation +# +# REPA is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# version 2 as published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, +# MA 02110-1301, USA. + +""" +REPA: Release Engineering Process Assistant. + +Copyright (C) Intel Corporation 2015 +Licence: GPL version 2 +Author: Ed Bartosh <eduard.bartosh@intel.com> + +Rebuild module. +Rebuild submissions. +""" + +import sys + +from collections import namedtuple + +from repa.main import sub_main +from repa.jenkins import trigger_build +from repa.common import edit + +class Rebuild(object): + """Subcommand: rebuild submissions.""" + + name = 'rebuild' + description = 'Rebuild submission' + help = description + + @staticmethod + def add_arguments(parser, config): + """Adds arguments to the parser. Called from [sub_]main.""" + parser.add_argument('submission', help='submission') + parser.add_argument('-p', '--package', help='package') + parser.add_argument('-c', '--comment', help='comment', + default=config.get('rebuild_comment', '')) + parser.add_argument('-e', '--edit', action='store_true', + help='run editor to edit comment') + + @staticmethod + def run(argv): + """Command line entry point. Called from [sub_]main.""" + if argv.edit: + argv.comment = edit(argv.comment) + job = 're' + cred = namedtuple('cred', ['url', 'username', 'password'])(\ + argv.jenkins_url, argv.jenkins_user, argv.jenkins_passwd) + build, status, out = \ + trigger_build(job, {'action': 'rebuild', + 'submission': argv.submission, + 'package': argv.package, + 'target_project': argv.project, + 'comment': argv.comment}, cred) + print "Jenkins job: %s, build #%s, status: %s" % (job, build, status) + print out + return status == 'SUCCESS' + +if __name__ == '__main__': + sys.exit(sub_main(sys.argv[1:], Rebuild())) diff --git a/repa/reject.py b/repa/reject.py index a907740..8866e3f 100644 --- a/repa/reject.py +++ b/repa/reject.py @@ -30,10 +30,11 @@ Accept submissions. """ import sys +from collections import namedtuple from repa.obs import OBS from repa.main import sub_main -from repa.common import accept_or_reject +from repa.common import accept_or_reject, edit class Reject(object): @@ -44,17 +45,31 @@ class Reject(object): help = description @staticmethod - def add_arguments(parser, _config): + def add_arguments(parser, config): """Adds arguments to the parser. Called from [sub_]main.""" parser.add_argument('submission', help='submission or group') - parser.add_argument('-c', '--comment', help='comment', default='') + parser.add_argument('-c', '--comment', help='comment', + default=config.get('reject_comment', '')) + parser.add_argument('-j', '--jenkins', action='store_true', + help='trigger Jenkins job') + parser.add_argument('-e', '--edit', action='store_true', + help='run editor to edit comment') @staticmethod def run(argv): """Command line entry point. Called from [sub_]main.""" + if argv.edit: + argv.comment = edit(argv.comment) + obs = OBS(argv.apiurl, argv.apiuser, argv.apipasswd) + + cred = None + if argv.jenkins: + cred = namedtuple('cred', ['url', 'username', 'password'])(\ + argv.jenkins_url, argv.jenkins_user, argv.jenkins_passwd) + return accept_or_reject(obs, argv.submission, 'declined', - argv.project, argv.comment) + argv.project, argv.comment, cred) if __name__ == '__main__': sys.exit(sub_main(sys.argv[1:], Reject())) diff --git a/repa/remove.py b/repa/remove.py new file mode 100644 index 0000000..5bfe05b --- /dev/null +++ b/repa/remove.py @@ -0,0 +1,74 @@ +#!/usr/bin/env python +# +# This file is part of REPA: Release Engineering Process Assistant. +# +# Copyright (C) 2015 Intel Corporation +# +# REPA is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# version 2 as published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, +# MA 02110-1301, USA. + +""" +REPA: Release Engineering Process Assistant. + +Copyright (C) Intel Corporation 2015 +Licence: GPL version 2 +Author: Ed Bartosh <eduard.bartosh@intel.com> + +Remove module. +Remove submission. +""" + +import sys + +from collections import namedtuple + +from repa.main import sub_main +from repa.jenkins import trigger_build +from repa.common import edit + +class Remove(object): + """Subcommand: remove submissions.""" + + name = 'remove' + description = 'Remove submission' + help = description + + @staticmethod + def add_arguments(parser, config): + """Adds arguments to the parser. Called from [sub_]main.""" + parser.add_argument('submission', help='submission') + parser.add_argument('-c', '--comment', help='comment', + default=config.get('remove_comment', '')) + parser.add_argument('-e', '--edit', action='store_true', + help='run editor to edit comment') + + @staticmethod + def run(argv): + """Command line entry point. Called from [sub_]main.""" + if argv.edit: + argv.comment = edit(argv.comment) + job = 're' + cred = namedtuple('cred', ['url', 'username', 'password'])(\ + argv.jenkins_url, argv.jenkins_user, argv.jenkins_passwd) + build, status, out = \ + trigger_build(job, {'action': 'remove', + 'submission': argv.submission, + 'target_project': argv.project, + 'comment': argv.comment}, cred) + print "Jenkins job: %s, build #%s, status: %s" % (job, build, status) + print out + return status == 'SUCCESS' + +if __name__ == '__main__': + sys.exit(sub_main(sys.argv[1:], Remove())) diff --git a/repa/rmgroup.py b/repa/rmgroup.py index c3eac2a..8c3a901 100644 --- a/repa/rmgroup.py +++ b/repa/rmgroup.py @@ -53,7 +53,7 @@ class RmGroup(object): help = description @staticmethod - def add_arguments(parser, _config): + def add_arguments(parser, _): """Adds arguments to the parser. Called from [sub_]main.""" parser.add_argument('group', help='group of submissions') diff --git a/repa/info.py b/repa/show.py index be4693a..bc479a4 100755 --- a/repa/info.py +++ b/repa/show.py @@ -51,10 +51,10 @@ def get_status(results): return status -def info(obs, name, target): +def show(obs, name, target): """Print detailed info about submission or submitgroup.""" is_group = name.startswith('submitgroup/') - _project, meta, build_results = get_project_by_name(obs, name, target) + _, meta, build_results = get_project_by_name(obs, name, target) print if is_group: @@ -103,15 +103,15 @@ def info(obs, name, target): print ' %-40s %s' % (pkg, status) -class Info(object): +class Show(object): """Subcommand: Print detailed information about submission.""" - name = 'info' + name = 'show' description = 'Information about submission' help = description @staticmethod - def add_arguments(parser, _config): + def add_arguments(parser, _): """ Add arguments to the parser. Called from [sub_]main. Set defaults for arguments from config. @@ -122,8 +122,8 @@ class Info(object): def run(argv): """Command line entry point. Called from [sub_]main""" obs = OBS(argv.apiurl, argv.apiuser, argv.apipasswd) - return info(obs, argv.submission, argv.project) + return show(obs, argv.submission, argv.project) if __name__ == '__main__': - sys.exit(sub_main(sys.argv[1:], Info())) + sys.exit(sub_main(sys.argv[1:], Show())) diff --git a/repa/unlock.py b/repa/unlock.py new file mode 100644 index 0000000..7e90330 --- /dev/null +++ b/repa/unlock.py @@ -0,0 +1,74 @@ +#!/usr/bin/env python +# +# This file is part of REPA: Release Engineering Process Assistant. +# +# Copyright (C) 2015 Intel Corporation +# +# REPA is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# version 2 as published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, +# MA 02110-1301, USA. + +""" +REPA: Release Engineering Process Assistant. + +Copyright (C) Intel Corporation 2015 +Licence: GPL version 2 +Author: Ed Bartosh <eduard.bartosh@intel.com> + +Unlock module. +Unlock submissions. +""" + +import sys + +from collections import namedtuple + +from repa.main import sub_main +from repa.jenkins import trigger_build +from repa.common import edit + +class Unlock(object): + """Subcommand: unlock submissions.""" + + name = 'unlock' + description = 'Unlock submission' + help = description + + @staticmethod + def add_arguments(parser, config): + """Adds arguments to the parser. Called from [sub_]main.""" + parser.add_argument('submission', help='submission') + parser.add_argument('-c', '--comment', help='comment', + default=config.get('unlock_comment', '')) + parser.add_argument('-e', '--edit', action='store_true', + help='run editor to edit comment') + + @staticmethod + def run(argv): + """Command line entry point. Called from [sub_]main.""" + if argv.edit: + argv.comment = edit(argv.comment) + job = 're' + cred = namedtuple('cred', ['url', 'username', 'password'])(\ + argv.jenkins_url, argv.jenkins_user, argv.jenkins_passwd) + build, status, out = \ + trigger_build(job, {'action': 'unlock', + 'submission': argv.submission, + 'target_project': argv.project, + 'comment': argv.comment}, cred) + print "Jenkins job: %s, build #%s, status: %s" % (job, build, status) + print out + return status == 'SUCCESS' + +if __name__ == '__main__': + sys.exit(sub_main(sys.argv[1:], Unlock())) @@ -31,7 +31,7 @@ import os from setuptools import setup setup(name="repa", - version='0.3', + version='0.4', author='Ed Bartosh', author_email='eduard.bartosh@intel.com', packages=['repa'], @@ -47,7 +47,10 @@ setup(name="repa", 'accept = repa.accept:Accept', 'reject = repa.reject:Reject', 'rmgroup = repa.rmgroup:RmGroup', - 'info = repa.info:Info', - 'diff = repa.diff:Diff'] - } -) + 'show = repa.show:Show', + 'diff = repa.diff:Diff', + 'rebuild = repa.rebuild:Rebuild', + 'lock = repa.lock:Lock', + 'unlock = repa.unlock:Unlock', + 'remove = repa.remove:Remove'] + }) |