Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
187 changes: 187 additions & 0 deletions .github/workflows/create-jira-issue.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,187 @@
name: Create Jira issue

on:
issues:
types: [opened]

permissions:
issues: write

concurrency:
group: create-jira-issue-${{ github.event.issue.number }}
cancel-in-progress: false

jobs:
create-jira-issue:
runs-on: ubuntu-latest
timeout-minutes: 5
steps:
- name: Create ES incident
env:
GITHUB_TOKEN: ${{ github.token }}
JIRA_API_TOKEN: ${{ secrets.JIRA_API_TOKEN }}
shell: python
run: |

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔵 Low — JIRA_EMAIL is hardcoded to an individual's personal address (vu.phung@databricks.com). The Basic-auth credential is email:token, so if this person's account is deprovisioned or their email changes, the automation silently breaks (all Jira calls start returning 401) with no obvious owner. Consider using a shared/service Jira account email, or sourcing it from a repository secret/variable alongside JIRA_API_TOKEN, so the automation isn't tied to one employee's identity.

import base64
import json
import os
import urllib.error
import urllib.parse
import urllib.request

JIRA_URL = "https://databricks.atlassian.net"
JIRA_EMAIL = "vu.phung@databricks.com"
JIRA_PROJECT = "ES"

with open(os.environ["GITHUB_EVENT_PATH"], encoding="utf-8") as event_file:
event = json.load(event_file)

issue = event["issue"]
issue_number = issue["number"]
jira_label = f"github-databricks-jdbc-{issue_number}"
jira_token = os.environ.get("JIRA_API_TOKEN")
if not jira_token:
raise RuntimeError("The JIRA_API_TOKEN repository secret is not configured")

jira_auth = base64.b64encode(
f"{JIRA_EMAIL}:{jira_token}".encode("utf-8")
).decode("ascii")


def jira_request(path, *, method="GET", payload=None):
data = None if payload is None else json.dumps(payload).encode("utf-8")
request = urllib.request.Request(
f"{JIRA_URL}{path}",
data=data,
method=method,
headers={
"Accept": "application/json",
"Authorization": f"Basic {jira_auth}",
"Content-Type": "application/json",
},
)
try:
with urllib.request.urlopen(request, timeout=30) as response:
return json.load(response)
except urllib.error.HTTPError as error:
detail = error.read().decode("utf-8", errors="replace")
raise RuntimeError(
f"Jira API request failed ({error.code}): {detail}"
) from error


jql = f'project = {JIRA_PROJECT} AND labels = "{jira_label}"'
query = urllib.parse.urlencode(
{"jql": jql, "fields": "key", "maxResults": 1}
)
search_result = jira_request(f"/rest/api/3/search/jql?{query}")
existing_issues = search_result.get("issues", [])

if existing_issues:
jira_key = existing_issues[0]["key"]
else:
title = issue["title"].strip()
summary = f"[GitHub #{issue_number}] {title}"[:255]
body = (issue.get("body") or "No description provided.")[:30000]
reporter = issue["user"]["login"]
github_url = issue["html_url"]
created_at = issue["created_at"]

description = {
"type": "doc",
"version": 1,
"content": [
{
"type": "heading",
"attrs": {"level": 2},
"content": [{"type": "text", "text": "GitHub issue"}],

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔵 Low — The duplicate-ticket search (project = ES AND labels = "...") does not scope by status. If a matching ES incident was previously created and later closed/resolved, a re-run (or a future trigger) will reuse that closed ticket and only re-post the tracking comment, rather than opening a fresh incident. If reopening a tracking ticket for renewed activity is desired, this is worth handling; if reuse-forever is intended, a brief comment noting that would help future maintainers.

},
{
"type": "paragraph",
"content": [
{
"type": "text",
"text": f"databricks/databricks-jdbc#{issue_number}",
"marks": [
{
"type": "link",
"attrs": {"href": github_url},
}
],
}
],
},
{
"type": "paragraph",
"content": [
{
"type": "text",
"text": f"Opened by @{reporter} at {created_at}",
}
],
},
{
"type": "heading",
"attrs": {"level": 2},
"content": [{"type": "text", "text": "Description"}],
},
{
"type": "codeBlock",
"attrs": {"language": "text"},
"content": [{"type": "text", "text": body}],
},
],
}

result = jira_request(
"/rest/api/3/issue",
method="POST",
payload={
"fields": {
"project": {"key": JIRA_PROJECT},
"issuetype": {"name": "Incident"},
"summary": summary,
"description": description,
"customfield_18150": {"id": "24323"},
"customfield_14677": {"id": "13344"},
"customfield_11500": {"id": "10602"},
"versions": [{"id": "20000"}],
"labels": ["github", "databricks-jdbc", jira_label],
}
},
)
jira_key = result["key"]

jira_issue_url = f"{JIRA_URL}/browse/{jira_key}"
comment_marker = "<!-- jira-issue-automation -->"
comments_url = f"{issue['comments_url']}?per_page=100"
comment_request = urllib.request.Request(
comments_url,
headers={
"Accept": "application/vnd.github+json",
"Authorization": f"Bearer {os.environ['GITHUB_TOKEN']}",
"X-GitHub-Api-Version": "2022-11-28",
},
)
with urllib.request.urlopen(comment_request, timeout=30) as response:
comments = json.load(response)

if not any(comment_marker in comment.get("body", "") for comment in comments):
comment_payload = json.dumps(
{"body": f"{comment_marker}\nTracking Jira issue: {jira_issue_url}"}
).encode("utf-8")
comment_request = urllib.request.Request(
issue["comments_url"],
data=comment_payload,
method="POST",
headers={
"Accept": "application/vnd.github+json",
"Authorization": f"Bearer {os.environ['GITHUB_TOKEN']}",
"Content-Type": "application/json",
"X-GitHub-Api-Version": "2022-11-28",
},
)
with urllib.request.urlopen(comment_request, timeout=30):
pass

print(f"Tracking GitHub issue #{issue_number} in {jira_issue_url}")
Loading