This is an automated email from the ASF dual-hosted git repository.

dongjoon-hyun pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/spark-docker.git


The following commit(s) were added to refs/heads/master by this push:
     new c09e3c8  [SPARK-57301] Support `create_spark_jira.py` and 
`create_jira_and_branch.py` (#123)
c09e3c8 is described below

commit c09e3c86421b410dbea596d31d33f045e16c7c3a
Author: Dongjoon Hyun <[email protected]>
AuthorDate: Sun Jun 7 12:00:33 2026 -0700

    [SPARK-57301] Support `create_spark_jira.py` and 
`create_jira_and_branch.py` (#123)
    
    ### What changes were proposed in this pull request?
    
    This PR aims to support `create_spark_jira.py` and 
`create_jira_and_branch.py` like the other Spark projects. 
`spark_jira_utils.py` is also added because it's the shared module for both 
scripts.
    
    ### Why are the changes needed?
    
    To help users and agents by providing seamless experiences:
    - https://github.com/apache/spark/blob/master/dev/create_spark_jira.py
    - https://github.com/apache/spark/blob/master/dev/create_jira_and_branch.py
    - https://github.com/apache/spark/blob/master/dev/spark_jira_utils.py
    
    ### Does this PR introduce _any_ user-facing change?
    
    No behavior change.
    
    ### How was this patch tested?
    
    Manual review.
    
    ### Was this patch authored or co-authored using generative AI tooling?
    
    Generated-by: Claude Code (Claude Opus 4.8)
---
 dev/create_jira_and_branch.py | 102 ++++++++++++++++++++++++++++++++++++++++
 dev/create_spark_jira.py      |  77 +++++++++++++++++++++++++++++++
 dev/spark_jira_utils.py       | 105 ++++++++++++++++++++++++++++++++++++++++++
 3 files changed, 284 insertions(+)

diff --git a/dev/create_jira_and_branch.py b/dev/create_jira_and_branch.py
new file mode 100755
index 0000000..cd78677
--- /dev/null
+++ b/dev/create_jira_and_branch.py
@@ -0,0 +1,102 @@
+#!/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 argparse
+import subprocess
+import sys
+import traceback
+
+from spark_jira_utils import DEFAULT_COMPONENT, create_jira_issue, fail, 
get_jira_client
+
+
+def run_cmd(cmd):
+    print(cmd)
+    if isinstance(cmd, list):
+        return subprocess.check_output(cmd).decode("utf-8")
+    else:
+        return subprocess.check_output(cmd.split(" ")).decode("utf-8")
+
+
+def create_and_checkout_branch(jira_id):
+    try:
+        run_cmd("git checkout -b %s" % jira_id)
+        print("Created and checked out branch: %s" % jira_id)
+    except subprocess.CalledProcessError as e:
+        fail("Failed to create branch %s: %s" % (jira_id, e))
+
+
+def create_commit(jira_id, title):
+    try:
+        run_cmd(["git", "commit", "-a", "-m", "[%s] %s" % (jira_id, title)])
+        print("Created a commit with message: [%s] %s" % (jira_id, title))
+    except subprocess.CalledProcessError as e:
+        fail("Failed to create commit: %s" % e)
+
+
+def main():
+    parser = argparse.ArgumentParser(description="Create a Spark JIRA issue.")
+    parser.add_argument("title", nargs="?", help="Title of the JIRA issue")
+    parser.add_argument("-p", "--parent", help="Parent JIRA ID for subtasks")
+    parser.add_argument(
+        "-t",
+        "--type",
+        help="Issue type to create when no parent is specified (e.g. Bug). 
Defaults to Improvement.",
+    )
+    parser.add_argument("-v", "--version", help="Version to use for the issue")
+    parser.add_argument(
+        "-c",
+        "--component",
+        default=DEFAULT_COMPONENT,
+        help="Component for the issue (default: %(default)s)",
+    )
+    args = parser.parse_args()
+
+    asf_jira = get_jira_client()
+
+    if args.parent:
+        parent_issue = asf_jira.issue(args.parent)
+        print("Parent issue title: %s" % parent_issue.fields.summary)
+        print("Creating a subtask of %s with title: %s" % (args.parent, 
args.title))
+    else:
+        print("Creating JIRA issue with title: %s" % args.title)
+
+    if not args.title:
+        parser.error("the following arguments are required: title")
+
+    jira_id = create_jira_issue(
+        asf_jira,
+        args.title,
+        args.component,
+        parent=args.parent,
+        issue_type=args.type,
+        version=args.version,
+    )
+    print("Created JIRA issue: %s" % jira_id)
+
+    create_and_checkout_branch(jira_id)
+
+    create_commit(jira_id, args.title)
+
+
+if __name__ == "__main__":
+    try:
+        main()
+    except Exception:
+        traceback.print_exc()
+        sys.exit(-1)
diff --git a/dev/create_spark_jira.py b/dev/create_spark_jira.py
new file mode 100755
index 0000000..ff66d01
--- /dev/null
+++ b/dev/create_spark_jira.py
@@ -0,0 +1,77 @@
+#!/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 argparse
+import sys
+import traceback
+
+from spark_jira_utils import DEFAULT_COMPONENT, create_jira_issue, 
get_jira_client, list_components
+
+
+def main():
+    parser = argparse.ArgumentParser(description="Create a Spark JIRA issue.")
+    parser.add_argument("title", nargs="?", help="Title of the JIRA issue")
+    parser.add_argument(
+        "-p",
+        "--parent",
+        help="Parent JIRA ID to create a subtask (e.g. SPARK-12345).",
+    )
+    parser.add_argument(
+        "-t",
+        "--type",
+        help="Issue type (e.g. Bug, Improvement)",
+    )
+    parser.add_argument(
+        "-c",
+        "--component",
+        default=DEFAULT_COMPONENT,
+        help="Component for the issue (default: %(default)s)",
+    )
+    parser.add_argument(
+        "--list-components", action="store_true", help="List available 
components and exit"
+    )
+    args = parser.parse_args()
+
+    if args.list_components:
+        asf_jira = get_jira_client()
+        list_components(asf_jira)
+        return
+
+    if not args.title:
+        parser.error("the following arguments are required: title")
+
+    if args.parent and args.type:
+        parser.error("--parent and --type cannot be used together")
+
+    if not args.parent and not args.type:
+        parser.error("-t/--type is required when not creating a subtask")
+
+    asf_jira = get_jira_client()
+    jira_id = create_jira_issue(
+        asf_jira, args.title, args.component, parent=args.parent, 
issue_type=args.type
+    )
+    print(jira_id)
+
+
+if __name__ == "__main__":
+    try:
+        main()
+    except Exception:
+        traceback.print_exc()
+        sys.exit(-1)
diff --git a/dev/spark_jira_utils.py b/dev/spark_jira_utils.py
new file mode 100644
index 0000000..1405175
--- /dev/null
+++ b/dev/spark_jira_utils.py
@@ -0,0 +1,105 @@
+#
+# 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";
+# Default JIRA component for Apache Spark Docker issues
+DEFAULT_COMPONENT = "Spark Docker"
+
+
+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 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"\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)


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to