#!/usr/bin/env python # # Licensed to the Apache Software Foundation (ASF) under one # or more contributor license agreements. See the NOTICE file # distributed with this work for additional information # regarding copyright ownership. The ASF licenses this file # to you under the Apache License, Version 2.0 (the # "License"); you may not use this file except in compliance # with the License. You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, # software distributed under the License is distributed on an # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY # KIND, either express or implied. See the License for the # specific language governing permissions and limitations # under the License. # # # Pre Commit Hook for running tests and updating JIRA # # Original version was copied from FLUME project. # import sys, os, re, urllib2, base64, subprocess, tempfile, shutil import json from optparse import OptionParser tmp_dir = None BASE_JIRA_URL = 'https://issues.apache.org/jira' # Write output to file def write_file(filename, content): with open(filename, "w") as text_file: text_file.write(content) # Guess branch for given versions # # Return None if detects that JIRA belongs to more than one branch def sqoop_guess_branch(versions): branch = None for v in versions: tmp_branch = None if v.startswith("1.99") or v.startswith("2.0"): tmp_branch = "SQOOP-1082" else: tmp_branch = "trunk" if not branch: branch = tmp_branch else: if branch != tmp_branch: return None return branch # Verify supported branch def sqoop_verify_branch(branch): return branch in {"sqoop2", "SQOOP-1082"} def execute(cmd, log=True): if log: print "INFO: Executing %s" % (cmd) return subprocess.call(cmd, shell=True) def jira_request(result, url, username, password, data, headers): request = urllib2.Request(url, data, headers) print "INFO: URL = %s, Username = %s, data = %s, headers = %s" % (url, username, data, str(headers)) if username and password: base64string = base64.encodestring('%s:%s' % (username, password)).replace('\n', '') request.add_header("Authorization", "Basic %s" % base64string) return urllib2.urlopen(request) def jira_get_defect_html(result, defect, username, password): url = "%s/browse/%s" % (BASE_JIRA_URL, defect) return jira_request(result, url, username, password, None, {}).read() def jira_get_defect(result, defect, username, password): url = "%s/rest/api/2/issue/%s" % (BASE_JIRA_URL, defect) return jira_request(result, url, username, password, None, {}).read() def jira_generate_comment(result, branch): body = [ "Here are the results of testing the latest attachment" ] body += [ "%s against branch %s." % (result.attachment, branch) ] body += [ "" ] if result._fatal: result._error = [ result._fatal ] + result._error if result._error: count = len(result._error) if count == 1: body += [ "{color:red}Overall:{color} -1 due to an error" ] else: body += [ "{color:red}Overall:{color} -1 due to %d errors" % (count) ] else: body += [ "{color:green}Overall:{color} +1 all checks pass" ] body += [ "" ] for error in result._error: body += [ "{color:red}ERROR:{color} %s" % (error.replace("\n", "\\n")) ] for info in result._info: body += [ "INFO: %s" % (info.replace("\n", "\\n")) ] for success in result._success: body += [ "{color:green}SUCCESS:{color} %s" % (success.replace("\n", "\\n")) ] if "BUILD_URL" in os.environ: body += [ "" ] body += [ "Console output: %sconsole" % (os.environ['BUILD_URL']) ] body += [ "" ] body += [ "This message is automatically generated." ] return "\\n".join(body) def jira_post_comment(result, defect, branch, username, password): url = "%s/rest/api/2/issue/%s/comment" % (BASE_JIRA_URL, defect) # Generate body for the comment and save it to a file body = jira_generate_comment(result, branch) write_file("%s/jira-comment.txt" % output_dir, body.replace("\\n", "\n")) # Send the comment to the JIRA body = "{\"body\": \"%s\"}" % body headers = {'Content-Type' : 'application/json'} response = jira_request(result, url, username, password, body, headers) body = response.read() if response.code != 201: msg = """Request for %s failed: URL = '%s' Code = '%d' Comment = '%s' Response = '%s' """ % (defect, url, response.code, comment, body) print "FATAL: %s" % (msg) sys.exit(1) # hack (from hadoop) but REST api doesn't list attachments? def jira_get_attachment(result, defect, username, password): html = jira_get_defect_html(result, defect, username, password) pattern = "(/secure/attachment/[0-9]+/(bug)?%s[0-9\-]*\.(patch|txt|patch\.txt))" % (re.escape(defect)) matches = [] for match in re.findall(pattern, html, re.IGNORECASE): matches += [ match[0] ] if matches: matches.sort() return "%s%s" % (BASE_JIRA_URL, matches.pop()) return None # Get versions from JIRA JSON object def json_get_version(json): versions = [] for version in json.get("fields").get("versions"): versions = versions + [version.get("name")] return versions def git_cleanup(): rc = execute("git clean -d -f", False) if rc != 0: print "ERROR: git clean failed" rc = execute("git reset --hard HEAD", False) if rc != 0: print "ERROR: git reset failed" def git_checkout(result, branch): if not branch: result.fatal("Branch wasn't specified nor was correctly guessed") return if execute("git checkout %s" % (branch)) != 0: result.fatal("git checkout %s failed" % branch) if execute("git clean -d -f") != 0: result.fatal("git clean failed") if execute("git reset --hard HEAD") != 0: result.fatal("git reset failed") if execute("git fetch origin") != 0: result.fatal("git fetch failed") if execute("git merge --ff-only origin/sqoop2"): result.fatal("git merge failed") def git_apply(result, cmd, patch_file, strip, output_dir): output_file = "%s/apply.txt" % (output_dir) rc = execute("%s -p%s < %s 1>%s 2>&1" % (cmd, strip, patch_file, output_file)) output = "" if os.path.exists(output_file): with open(output_file) as fh: output = fh.read() if rc == 0: result.success("Patch applied correctly") if output: print output else: result.fatal("failed to apply patch (exit code %d):\n{code}%s{code}\n" % (rc, output)) def mvn_clean(result, output_dir): rc = execute("mvn clean 1>%s/clean.txt 2>&1" % output_dir) if rc == 0: result.success("Clean was successful") else: result.fatal("failed to clean project (exit code %d)" % (rc)) def mvn_install(result, output_dir): rc = execute("mvn install -DskipTests 1>%s/install.txt 2>&1" % output_dir) if rc == 0: result.success("Patch compiled") else: result.fatal("failed to build with patch (exit code %d)" % (rc)) def find_all_files(top): for root, dirs, files in os.walk(top): for f in files: yield os.path.join(root, f) def mvn_test(result, output_dir): rc = execute("mvn test 1>%s/test.txt 2>&1" % output_dir) if rc == 0: result.success("All tests passed") else: result.error("mvn test exited %d" % (rc)) failed_tests = [] for path in list(find_all_files(".")): file_name = os.path.basename(path) if file_name.startswith("TEST-") and file_name.endswith(".xml"): fd = open(path) for line in fd: if "