diff --git a/dev/create_jira_and_branch.py b/dev/create_jira_and_branch.py index f3b94b6e..1b373af6 100755 --- a/dev/create_jira_and_branch.py +++ b/dev/create_jira_and_branch.py @@ -17,27 +17,12 @@ # limitations under the License. # -import os -import re +import argparse import subprocess import sys import traceback -try: - import jira.client - - JIRA_IMPORTED = True -except ImportError: - JIRA_IMPORTED = False - -# ASF JIRA access token -JIRA_ACCESS_TOKEN = os.environ.get("JIRA_ACCESS_TOKEN") -JIRA_API_BASE = "https://issues.apache.org/jira" - - -def fail(msg): - print(msg) - sys.exit(-1) +from spark_jira_utils import create_jira_issue, fail, get_jira_client def run_cmd(cmd): @@ -48,48 +33,6 @@ def run_cmd(cmd): return subprocess.check_output(cmd.split(" ")).decode("utf-8") -import argparse - -def create_jira_issue(title, parent_jira_id=None, issue_type=None, version=None): - asf_jira = jira.client.JIRA( - {"server": JIRA_API_BASE}, - token_auth=JIRA_ACCESS_TOKEN, - timeout=(3.05, 30) - ) - - if version is None: - versions = asf_jira.project_versions("SPARK") - # Consider only kubernetes-operator-x.y.z, unreleased, unarchived versions - versions = [ - x for x in versions - if not x.raw["released"] and not x.raw["archived"] and re.match(r"kubernetes-operator-\d+\.\d+\.\d+", x.name) - ] - versions = sorted(versions, key=lambda x: x.name, reverse=True) - affected_version = versions[0].name - else: - affected_version = version - - issue_dict = { - 'project': {'key': 'SPARK'}, - 'summary': title, - 'description': '', - 'components': [{'name': 'Kubernetes'}], - 'versions': [{'name': affected_version}], - } - - if parent_jira_id: - issue_dict['issuetype'] = {'name': 'Sub-task'} - issue_dict['parent'] = {'key': parent_jira_id} - else: - issue_dict['issuetype'] = {'name': issue_type if issue_type else 'Improvement'} - - try: - new_issue = asf_jira.create_issue(fields=issue_dict) - return new_issue.key - except Exception as e: - fail("Failed to create JIRA issue: %s" % e) - - def create_and_checkout_branch(jira_id): try: run_cmd("git checkout -b %s" % jira_id) @@ -107,12 +50,6 @@ def create_commit(jira_id, title): def main(): - if not JIRA_IMPORTED: - fail("Could not find jira-python library. Run 'sudo pip3 install jira' to install.") - - if not JIRA_ACCESS_TOKEN: - fail("The env-var JIRA_ACCESS_TOKEN is not set.") - parser = argparse.ArgumentParser(description="Create a Spark JIRA issue.") parser.add_argument("title", help="Title of the JIRA issue") parser.add_argument("-p", "--parent", help="Parent JIRA ID for subtasks") @@ -125,7 +62,10 @@ def main(): else: print("Creating JIRA issue with title: %s" % args.title) - jira_id = create_jira_issue(args.title, args.parent, args.type, args.version) + asf_jira = get_jira_client() + jira_id = create_jira_issue( + asf_jira, args.title, "Kubernetes", args.parent, args.type, args.version + ) print("Created JIRA issue: %s" % jira_id) create_and_checkout_branch(jira_id) diff --git a/dev/spark_jira_utils.py b/dev/spark_jira_utils.py new file mode 100644 index 00000000..a341f083 --- /dev/null +++ b/dev/spark_jira_utils.py @@ -0,0 +1,107 @@ +#!/usr/bin/env python3 + +# +# 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. +# + +import os +import re +import sys + +try: + import jira.client + + JIRA_IMPORTED = True +except ImportError: + JIRA_IMPORTED = False + +# ASF JIRA access token +JIRA_ACCESS_TOKEN = os.environ.get("JIRA_ACCESS_TOKEN") +JIRA_API_BASE = "https://issues.apache.org/jira" + + +def fail(msg): + print(msg) + sys.exit(-1) + + +def get_jira_client(): + """Create and return a JIRA client, or exit with a helpful message.""" + errors = [] + if not JIRA_IMPORTED: + errors.append("jira-python library not installed, run 'pip install jira'") + if not JIRA_ACCESS_TOKEN: + errors.append("JIRA_ACCESS_TOKEN env-var not set") + if errors: + fail( + "Cannot create JIRA ticket automatically (%s). " + "Please create the ticket manually at %s" % ("; ".join(errors), JIRA_API_BASE) + ) + return jira.client.JIRA( + {"server": JIRA_API_BASE}, token_auth=JIRA_ACCESS_TOKEN, timeout=(3.05, 30) + ) + + +def detect_affected_version(asf_jira): + """Return the latest unreleased kubernetes-operator-x.y.z version, or exit.""" + versions = asf_jira.project_versions("SPARK") + versions = [ + x + for x in versions + if not x.raw["released"] + and not x.raw["archived"] + and re.match(r"kubernetes-operator-\d+\.\d+\.\d+", x.name) + ] + versions = sorted(versions, key=lambda x: x.name, reverse=True) + if not versions: + fail( + "Cannot detect affected version. " + "Please create the ticket manually at %s" % JIRA_API_BASE + ) + return versions[0].name + + +def list_components(asf_jira): + """Print all non-archived Spark JIRA components.""" + components = asf_jira.project_components("SPARK") + components = [c for c in components if not c.raw.get("archived", False)] + for c in sorted(components, key=lambda x: x.name): + print(c.name) + + +def create_jira_issue(asf_jira, title, component, parent=None, issue_type=None, version=None): + """Create a JIRA issue and return the issue key (e.g. SPARK-12345).""" + affected_version = version if version else detect_affected_version(asf_jira) + + issue_dict = { + "project": {"key": "SPARK"}, + "summary": title, + "description": "", + "versions": [{"name": affected_version}], + "components": [{"name": component}], + } + + if parent: + issue_dict["issuetype"] = {"name": "Sub-task"} + issue_dict["parent"] = {"key": parent} + else: + issue_dict["issuetype"] = {"name": issue_type if issue_type else "Improvement"} + + try: + new_issue = asf_jira.create_issue(fields=issue_dict) + return new_issue.key + except Exception as e: + fail("Failed to create JIRA issue: %s" % e)