David Caro has uploaded a new change for review. Change subject: Added the hok dispatcher ......................................................................
Added the hok dispatcher This script is in charge of selecting which hooks will run and managing the execution flow Change-Id: Iada3d1bef5357366d5f319561efac8ee85a614f0 Signed-off-by: David Caro <dcaro...@redhat.com> --- A hooks/hook-dispatcher 1 file changed, 326 insertions(+), 0 deletions(-) git pull ssh://gerrit.ovirt.org:29418/gerrit-admin refs/changes/86/15386/1 diff --git a/hooks/hook-dispatcher b/hooks/hook-dispatcher new file mode 100755 index 0000000..b68d863 --- /dev/null +++ b/hooks/hook-dispatcher @@ -0,0 +1,326 @@ +#!/usr/bin/env python +""" +This dispatcher handles the hook execution + +Allowed tags: + +Ignore-Hooks +----------- + Ignore-Hooks: hook1, hook2, ... + Do not run the given hooks + +Run-Only-Hooks +----------- + Run-Only-Hooks: hook1, hook2 ... + Run only the given hooks if they exist + +""" + +import os +import sys +import re +import subprocess +import argparse +import logging +from collections import OrderedDict +from dulwich.repo import Repo + + +def ignore(ignored_hooks, current_hooks, args): + """ + Return the hooks list without the given hooks + + :param ignored_hooks; csv lis tof the hooks to ignore + :param current_hooks: array with the currently available hooks + """ + ignored_hooks = set([hook.strip() for hook in ignored_hooks.split(',')]) + logging.info(" Ignoring the hooks %s", ignored_hooks) + for hook in ignored_hooks: + match = filter(lambda h: re.match(hook, h), current_hooks) + if match: + for mhook in match: + current_hooks.pop(current_hooks.index(mhook)) + return current_hooks + + +def run_only(to_run_hooks, current_hooks, args): + """ + Handles the Run-Only tag, removes all the hooks not macching the given + hooks + + :param to_run_hooks: array with the csv string passed to the tag + :param current_hooks: array with the current available hooks + """ + to_run_hooks = set([hook.strip() for hook in to_run_hooks.split(',')]) + logging.info(" Running only the hooks %s", to_run_hooks) + to_run = [] + for hook in to_run_hooks: + if hook in current_hooks: + to_run.append(hook) + return to_run + + +def rerun(to_rerun_hooks, current_hooks, args): + """ + Handles the Rerun-Hooks tag, reruns the given hooks, if all is given it + will rerun all the hooks + + :param to_run_hooks: array with the csv string passed to the tag + :param current_hooks: array with the current available hooks + """ + ## When rerunning use only the patchset-created hooks + current_hooks = get_hooks(os.path.join(os.environ['GIT_DIR'], 'hooks'), + 'patchset-created') + to_rerun_hooks = set([hook.strip() for hook in to_rerun_hooks.split(',')]) + logging.info(" Rerunning the hooks %s", to_rerun_hooks) + to_rerun = [] + args.rerun = True + for hook in to_rerun_hooks: + if not hook.startswith('patchset-created') and hook != 'all': + hook = 'patchset-created.' + hook + if hook in current_hooks: + to_rerun.append(hook) + elif hook == 'all': + return current_hooks + return to_rerun + + +#Flags to look for specified like this: +# +# { +# tag_name: ( +# Regexp_to_match, +# function_to_run +# ) +# } +COMMIT_TAGS = { + 'ignore': ( + 'Ignore-Hooks: ', + ignore + ), + 'run_only': ( + 'Run-Only-Hooks: ', + run_only + ), +} +COMMENT_TAGS = { + 'rerun': ( + 'Rerun-Hooks: ', + rerun + ), +} + + +def get_commit_tags(commit): + """ + Get the tags that were specified in the comit message + + :param commit: Commit to get the message from + """ + repo = Repo(os.environ['GIT_DIR']) + message = repo[commit].message + logging.debug("Got commit message:\n" + message) + result = {} + for line in message.splitlines(): + for tname, toptions in COMMIT_TAGS.iteritems(): + if line.startswith(toptions[0]): + result[tname] = (line[len(toptions[0]):], toptions[1]) + return result + + +def get_comment_tags(comment): + """ + Get the tags that were specified in gerrit message + + :param comment: Gerrit comment + """ + comment = str(comment) + logging.debug("Got gerrit comment:\n" + comment) + result = {} + for line in comment.splitlines(): + for tname, toptions in COMMENT_TAGS.iteritems(): + if line.startswith(toptions[0]): + result[tname] = (line[len(toptions[0]):], toptions[1]) + return result + + +def get_parser(): + """ + Build the parser for any hook type + """ + parser = argparse.ArgumentParser( + description=('This dispatcher handles the' + ' special tags and hook' + ' execution'), + ) + for arg in ('change', 'project', 'branch'): + parser.add_argument('--' + arg, + action='store', + required=True) + for arg in ('commit', 'patchset', 'author', 'comment', 'submitter', + 'abandoner', 'reason', 'rerun'): + parser.add_argument('--' + arg, + action='store', + default=False, + required=False) + return parser + + +def flatten(array, elems): + """ + Function that appends to the array the options given by elems, to build a + new command line array. + + :param array: Command line array as ['ls', '-l', '/tmp'] + :param elems: Command line options as parsed by arpargse, like + [('author', 'dcaro'), ('email', 'dcaro...@redhat.com')] + """ + option, value = elems + if value: + array.extend(('--' + option, value)) + return array + + +def get_hooks(path, hook_type): + """" + Get all the hooks that match the given type in the given path + + :param path: Path to look the hooks in + :param hook_type: Hook type to look for + """ + logging.debug("get_hooks::%s on %s" % (hook_type, path)) + hooks = [] + for hook in os.listdir(path): + if os.access(os.path.join(path, hook), os.X_OK) \ + and hook.startswith(hook_type): + logging.debug("get_hooks::got hook " + hook) + hooks.append(hook) + return hooks + + +def get_chains(hooks): + chains = OrderedDict() + hooks.sort() + for hook in hooks: + if '.' not in hook: + chain = hook + elif hook.count('.') < 2: + chain = hook.split('.')[1] + else: + chain = hook.split('.', 2)[1] + if chain in chains: + chains[chain].append(hook) + else: + chains[chain] = [hook] + return chains + + +def run_hooks(path, hooks, chain='NONE'): + """" + Run the given hooks form the given path + + :param path: Path where the hooks are located + :param hooks: hook names to run + """ + ## add the hooks lib dir to the path + os.environ["PATH"] = os.environ["PATH"] + ':' \ + + os.path.dirname(os.path.realpath(__file__)) + '/lib' + hooks.sort() + params = sys.argv[1:] + for hook in hooks: + cmd = [os.path.join(path, hook)] + cmd.extend(params) + logstr = chain + "::" + hook + "::" + logging.info(logstr + "RUNNING::" + ' '.join(cmd)) + pipe = subprocess.Popen(cmd, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE) + output, error = pipe.communicate() + if pipe.returncode == 0: + logging.info(logstr + '\n' + output) + error and logging.warn(logstr + '\n' + error) + else: + logging.warn(logstr + '\n' + output) + error and logging.error(logstr + '\n' + error) + logging.warn(chain + "::BROKEN WITH " + str(pipe.returncode)) + return pipe.returncode + + +def run_chains(path, hooks): + """" + Run the given hooks form the given path + + :param path: Path where the hooks are located + :param hooks: hook names to run + """ + ## add the hooks lib dir to the path + logging.info("::RUNNING HOOKS::%s", hooks) + chains = get_chains(hooks) + for c_name, c_hooks in chains.iteritems(): + logging.info(c_name + "::STARTING") + ret_code = run_hooks(path, c_hooks, c_name) + if ret_code == 1: + logging.error("ABORTING::got return code 1.") + return ret_code + elif ret_code == 0: + logging.info(c_name + "::FINISHED OK") + + +def get_hook_type(opts): + """ + Guess the right hook type, gerrit sometimes resolves the real path + + :param opts: options given to the script, so it can guess the hook type + """ + if opts.patchset: + return 'patchset-created' + elif opts.author: + return 'comment-added' + elif opts.submitter: + return 'change-merged' + elif opts.abandoner: + return 'change-abandoned' + else: + return os.path.basename(__file__) + + +def main(): + logpath = os.path.join(os.path.dirname(__file__), '..', 'logs') + logging.basicConfig(filename=os.path.join(logpath, 'gerrit.hooks.log'), + level=logging.INFO, + format='%(asctime)s::' + + str(os.getpid()) + + '::%(levelname)s::%(message)s') + parser = get_parser() + known_args, rest = parser.parse_known_args() + res = reduce(flatten, vars(known_args).items(), []) + ## This second time is to force the existence of the required args + parser.parse_args(res) + if 'GIT_DIR' not in os.environ: + logging.error("Set the GIT_DIR to the repository path") + raise Exception("Set the GIT_DIR to the repository path") + logging.debug("STARTING::PARAMS %s" % sys.argv) + hook_type = get_hook_type(known_args) + logging.info("STARTING::" + hook_type) + hooks = get_hooks(os.path.join(os.environ['GIT_DIR'], 'hooks'), + hook_type) + logging.info("::AVAILABLE HOOKS::%s", hooks) + tags = {} + if known_args.commit: + ## get tags from the commit message + tags.update(get_commit_tags(known_args.commit)) + if known_args.comment: + ## and from the gerrit comment + tags.update(get_comment_tags(known_args.comment)) + for tag_val, fun in tags.itervalues(): + ## Execute the functions with the tag value, the current available + ## hooks, and the args so they can be modified + hooks = fun(tag_val, hooks, known_args) + ## keep them sorted (will be sorted again later though) + hooks.sort() + run_chains(os.path.join(os.environ['GIT_DIR'], 'hooks'), hooks) + logging.info("FINISHED") + + +if __name__ == '__main__': + main() -- To view, visit http://gerrit.ovirt.org/15386 To unsubscribe, visit http://gerrit.ovirt.org/settings Gerrit-MessageType: newchange Gerrit-Change-Id: Iada3d1bef5357366d5f319561efac8ee85a614f0 Gerrit-PatchSet: 1 Gerrit-Project: gerrit-admin Gerrit-Branch: master Gerrit-Owner: David Caro <dcaro...@redhat.com> _______________________________________________ Engine-patches mailing list Engine-patches@ovirt.org http://lists.ovirt.org/mailman/listinfo/engine-patches