ci: fix github to jira issue sync (#26747)

Add local actions for JIRA interactions to replace github actions
that have been archived.
This commit is contained in:
Chris Roberts
2025-09-12 13:40:11 -07:00
committed by GitHub
parent ac86225e09
commit 10be73c081
10 changed files with 384 additions and 35 deletions

43
.github/actions/jira/create/action.yml vendored Normal file
View File

@@ -0,0 +1,43 @@
# Copyright (c) HashiCorp, Inc.
# SPDX-License-Identifier: MPL-2.0
name: jira-create
description: Create a JIRA issue
inputs:
project:
required: true
description: JIRA project
default: NMD
issuetype:
required: true
description: Type of JIRA issue
default: "GH Issue"
summary:
required: true
description: Title of the issue
description:
required: false
description: Description of the issue
extraFields:
required: false
description: Extra fields to add to issue
outputs:
issue:
description: JIRA issue ID of created issue
value: ${{ steps.create.outputs.issue }}
issue-key:
description: JIRA issue key of created issue
value: ${{ steps.create.outputs.issue-key }}
runs:
using: composite
steps:
- name: Create JIRA issue
id: create
shell: bash
run: ./.github/actions/jira/create/jira-create.bash
env:
PROJECT: ${{ inputs.project }}
ISSUE_TYPE: ${{ inputs.issueType }}
SUMMARY: ${{ inputs.summary }}
DESCRIPTION: ${{ inputs.description }}
EXTRA_FIELDS: ${{ inputs.extraFields }}

75
.github/actions/jira/create/jira-create.bash vendored Executable file
View File

@@ -0,0 +1,75 @@
#!/usr/bin/env bash
# Copyright (c) HashiCorp, Inc.
# SPDX-License-Identifier: MPL-2.0
source "$(dirname "${BASH_SOURCE[0]}")/../shared.bash"
# Check for required input values
if [ -z "${ISSUE_TYPE}" ]; then
error "Missing 'issueType' input value"
exit 1
fi
if [ -z "${PROJECT}" ]; then
error "Missing 'project' input value"
exit 1
fi
if [ -z "${SUMMARY}" ]; then
error "Missing 'summary' input value"
exit 1
fi
# Grab the issue type ID
result="$(jira-request "${JIRA_BASE_URL}/rest/api/3/issuetype")" || exit
query="$(printf '.[] | select(.name == "%s").id' "${ISSUE_TYPE}")"
type_id="$(jq -r "${query}" <<< "${result}")"
if [ -z "${type_id}" ]; then
error "Could not find issue type with name '%s'" "${ISSUE_TYPE}"
exit 1
fi
info "Issue type ID for '%s': %s" "${ISSUE_TYPE}" "${type_id}"
if [ -n "${DESCRIPTION}" ]; then
description="$(convert-gfm-to-jira "${DESCRIPTION}")" || exit
fi
# Base template for issue creation
template='
{
description: $description,
issuetype: {
id: $issuetype
},
project: {
key: $project
},
summary: $summary
}'
new_issue="$(jq -n --arg description "${description}" --arg issuetype "${type_id}" --arg project "${PROJECT}" --arg summary "${SUMMARY}" "${template}")" || exit
# If there are extra fields provided, merge them in
if [ -n "${EXTRA_FIELDS}" ]; then
new_issue="$(printf "%s %s" "${new_issue}" "${EXTRA_FIELDS}" | jq -s add)"
fi
# Wrap the payload for submission
template='{fields: $fields}'
new_issue="$(jq -n --argjson fields "${new_issue}" "${template}")" || exit
info "JIRA new issue payload:\n%s" "${new_issue}"
# Create the issue
# NOTE: The v2 API is used here for creating the issue. This is because
# the v3 API only supports the Atlassian Document Format for which pandoc
# currently does not have support (https://github.com/jgm/pandoc/issues/9898)
result="$(jira-request --request "POST" --data "${new_issue}" "${JIRA_BASE_URL}/rest/api/2/issue")" || exit
key="$(jq -r '.key' <<< "${result}")"
id="$(jq -r '.id' <<< "${result}")"
printf "issue=%s\n" "${id}" >> "${GITHUB_OUTPUT}"
printf "issue-key=%s\n" "${key}" >> "${GITHUB_OUTPUT}"
info ">> New JIRA issue created: %s/browse/%s" "${JIRA_BASE_URL}" "${key}"

22
.github/actions/jira/search/action.yml vendored Normal file
View File

@@ -0,0 +1,22 @@
# Copyright (c) HashiCorp, Inc.
# SPDX-License-Identifier: MPL-2.0
name: jira-search
description: Search for a JIRA issue
inputs:
jql:
required: true
description: JQL used to search for issue
outputs:
issue:
description: JIRA issue ID matching JQL
value: ${{ steps.search.outputs.issue }}
runs:
using: composite
steps:
- name: Search for JIRA issue
id: search
shell: bash
run: ./.github/actions/jira/search/jira-search.bash
env:
JQL: ${{ inputs.jql }}

28
.github/actions/jira/search/jira-search.bash vendored Executable file
View File

@@ -0,0 +1,28 @@
#!/usr/bin/env bash
# Copyright (c) HashiCorp, Inc.
# SPDX-License-Identifier: MPL-2.0
source "$(dirname "${BASH_SOURCE[0]}")/../shared.bash"
# Check for required inputs
if [ -z "${JQL}" ]; then
error "Missing 'jql' input value"
exit 1
fi
info "Searching for existing JIRA issue..."
info "JQL: %s" "${JQL}"
template='{jql: $jql}'
search="$(jq -n --arg jql "${JQL}" "${template}")" || exit
result="$(jira-request --request "POST" --data "${search}" "${JIRA_BASE_URL}/rest/api/3/search/jql")" || exit
issue="$(jq -r '.issues[].id' <<< "${result}")"
if [ -z "${issue}" ]; then
info "No existing issue found in JIRA"
exit
fi
info "Existing JIRA issue found: %s" "${issue}"
# Make issue available in output
printf "issue=%s\n" "${issue}" >> "${GITHUB_OUTPUT}"

67
.github/actions/jira/shared.bash vendored Normal file
View File

@@ -0,0 +1,67 @@
#!/usr/bin/env bash
# Copyright (c) HashiCorp, Inc.
# SPDX-License-Identifier: MPL-2.0
TEXT_RED='\e[31m'
TEXT_CLEAR='\e[0m'
# Make a request to the Jira API
function jira-request() {
curl --show-error --location --fail \
--user "${JIRA_USER_EMAIL}:${JIRA_API_TOKEN}" \
--header "Accept: application/json" \
--header "Content-Type: application/json" \
"${@}"
}
# Write an informational message
function info() {
local msg_template="${1}\n"
local i=$(( ${#} - 1 ))
local msg_args=("${@:2:$i}")
#shellcheck disable=SC2059
printf ">> ${msg_template}" "${msg_args[@]}" >&2
}
# Write an error message
function error() {
local msg_template="${1}\n"
local i=$(( ${#} - 1 ))
local msg_args=("${@:2:$i}")
#shellcheck disable=SC2059
printf "%b!! ERROR:%b ${msg_template}%b" "${TEXT_RED}" "${TEXT_CLEAR}" "${msg_args[@]}" >&2
}
# Convert content from GitHub format to Jira format
function convert-gfm-to-jira() {
local content="${1?Content value is required}"
local src
src="$(mktemp)" ||
return 1
printf "%s" "${content}" > "${src}"
# NOTE: Using docker here instead of installing the pandoc package directly.
# This is because when installing the pandoc package in CI the post install
# tasks take multiple minutes to complete.
docker run --rm -v "$(dirname "${src}"):/data" pandoc/core --from=gfm --to=jira "/data/$(basename "${src}")" ||
return 1
rm -f "${src}"
return 0
}
# Check for environment variables that must always be set
if [ -z "${JIRA_BASE_URL}" ]; then
error "Missing JIRA_BASE_URL environment variable"
exit 1
fi
if [ -z "${JIRA_USER_EMAIL}" ]; then
error "Missing JIRA_USER_EMAIL environment variable"
exit 1
fi
if [ -z "${JIRA_API_TOKEN}" ]; then
error "Missing JIRA_API_TOKEN environment variable"
exit 1
fi

View File

@@ -0,0 +1,22 @@
# Copyright (c) HashiCorp, Inc.
# SPDX-License-Identifier: MPL-2.0
name: jira-sync-comment
description: Sync GitHub comment to JIRA issue
inputs:
issue:
required: true
description: JIRA issue to sync comment
comment:
required: true
description: Comment to add to JIRA issue
runs:
using: composite
steps:
- name: Sync comment to JIRA
shell: bash
run: ./.github/actions/jira/sync-comment/jira-sync-comment.bash
env:
ISSUE: ${{ inputs.issue }}
COMMENT: ${{ inputs.comment }}

View File

@@ -0,0 +1,38 @@
#!/usr/bin/env bash
# Copyright (c) HashiCorp, Inc.
# SPDX-License-Identifier: MPL-2.0
source "$(dirname "${BASH_SOURCE[0]}")/../shared.bash"
# Check for required inputs
if [ -z "${ISSUE}" ]; then
error "Missing 'issue' input value"
exit 1
fi
if [ -z "${COMMENT}" ]; then
error "Missing 'comment' input value"
exit 1
fi
comment="$(convert-gfm-to-jira "${COMMENT}")" || exit
template='
{
body: $comment
}
'
issue_comment="$(jq -n --arg comment "${comment}" "${template}")"
info "Adding comment to JIRA issue %s" "${ISSUE}"
info "Comment payload: %s" "${issue_comment}"
# Create the comment
# NOTE: The v2 API is used here for creating the comment. This is because
# the v3 API only supports the Atlassian Document Format for which pandoc
# currently does not have support (https://github.com/jgm/pandoc/issues/9898)
result="$(jira-request --request "POST" --data "${issue_comment}" "${JIRA_BASE_URL}/rest/api/2/issue/${ISSUE}/comment")" || exit
comment_id="$(jq -r .id <<< "${result}")"
info "JIRA issue ID %s updated with new comment ID %s" "${ISSUE}" "${comment_id}"
printf "comment-id=%s\n" "${comment_id}" >> "${GITHUB_OUTPUT}"

View File

@@ -0,0 +1,22 @@
# Copyright (c) HashiCorp, Inc.
# SPDX-License-Identifier: MPL-2.0
name: jira-transition
description: Transition state of JIRA issue
inputs:
issue:
required: true
description: JIRA issue to transition
transition:
required: true
description: Transition name to apply to issue
runs:
using: composite
steps:
- name: Transition JIRA issue
shell: bash
run: ./.github/actions/jira/transition/jira-transition.bash
env:
ISSUE: ${{ inputs.issue }}
TRANSITION: ${{ inputs.transition }}

View File

@@ -0,0 +1,44 @@
#!/usr/bin/env bash
# Copyright (c) HashiCorp, Inc.
# SPDX-License-Identifier: MPL-2.0
source "$(dirname "${BASH_SOURCE[0]}")/../shared.bash"
# Check for required inputs
if [ -z "${ISSUE}" ]; then
error "Missing 'issue' input value"
exit 1
fi
if [ -z "${TRANSITION}" ]; then
error "Missing 'transition' input value"
exit 1
fi
# Grab the transition ID
result="$(jira-request "${JIRA_BASE_URL}/rest/api/3/issue/${ISSUE}/transitions")" || exit
query="$(printf '.transitions[] | select(.name == "%s").id' "${TRANSITION}")"
transition_id="$(jq -r "${query}" <<< "${result}")"
if [ -z "${transition_id}" ]; then
error "Could not find matching transition with name matching '%s'" "${TRANSITION}"
exit 1
fi
template='
{
transition: {
id: $transition
}
}
'
issue_transition="$(jq -n --arg transition "${transition_id}" "${template}")" || exit
info "Transitioning JIRA issue '%s' to %s (ID: %s)" "${ISSUE}" \
"${TRANSITION}" "${transition_id}"
info "Transition payload:\n%s" "${issue_transition}"
jira-request --request "POST" --data "${issue_transition}" \
"${JIRA_BASE_URL}/rest/api/3/issue/${ISSUE}/transitions" || exit
info "JIRA issue '%s' transitioned to %s" "${ISSUE}" "${TRANSITION}"

View File

@@ -10,30 +10,31 @@ on:
name: Jira Issue Sync
env:
JIRA_BASE_URL: ${{ secrets.JIRA_BASE_URL }}
JIRA_USER_EMAIL: ${{ secrets.JIRA_USER_EMAIL }}
JIRA_API_TOKEN: ${{ secrets.JIRA_API_TOKEN }}
jobs:
sync:
runs-on: ubuntu-22.04
runs-on: ubuntu-latest
name: Jira Issue sync
steps:
- name: Login
uses: atlassian/gajira-login@45fd029b9f1d6d8926c6f04175aa80c0e42c9026 # v3.0.1
env:
JIRA_BASE_URL: ${{ secrets.JIRA_BASE_URL }}
JIRA_USER_EMAIL: ${{ secrets.JIRA_USER_EMAIL }}
JIRA_API_TOKEN: ${{ secrets.JIRA_API_TOKEN }}
- name: Set ticket type
id: set-ticket-type
run: |
echo "TYPE=GH Issue" >> "$GITHUB_OUTPUT"
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- name: Search
if: github.event.action != 'opened'
id: search
uses: ./.github/actions/jira/search
with:
# cf[10089] is Issue Link (use JIRA API to retrieve)
jql: 'cf[10089] = "${{ github.event.issue.html_url || github.event.pull_request.html_url }}"'
- name: Create ticket if an issue is labeled with hcc/jira
if: github.event.action == 'labeled' && github.event.label.name == 'hcc/jira'
uses: tomhjp/gh-action-jira-create@3ed1789cad3521292e591a7cfa703215ec1348bf # v0.2.1
if: github.event.action == 'labeled' && github.event.label.name == 'hcc/jira' && !steps.search.outputs.issue
uses: ./.github/actions/jira/create
with:
project: NMD
issuetype: "${{ steps.set-ticket-type.outputs.TYPE }}"
summary: "${{ github.event.repository.name }} [${{ steps.set-ticket-type.outputs.TYPE }} #${{ github.event.issue.number }}]: ${{ github.event.issue.title }}"
issuetype: "GH Issue"
summary: "${{ github.event.repository.name }} [GH Issue #${{ github.event.issue.number }}]: ${{ github.event.issue.title }}"
description: "${{ github.event.issue.body || github.event.pull_request.body }}\n\n_Created in GitHub by ${{ github.actor }}._"
# customfield_10089 is "Issue Link"
# customfield_10371 is "Source" (use JIRA API to retrieve)
@@ -41,38 +42,25 @@ jobs:
# customfield_10001 is Team (jira default teams?)
extraFields: '{ "customfield_10089": "${{ github.event.issue.html_url || github.event.pull_request.html_url }}",
"customfield_10001": "72e166fb-d26c-4a61-b0de-7a290d91708f",
"customfield_10371": { "value": "GitHub" },
"customfield_10091": ["NomadMinor"],
"components": [{ "name": "nomad" }],
"labels": ["community", "GitHub"] }'
env:
JIRA_BASE_URL: ${{ secrets.JIRA_BASE_URL }}
JIRA_USER_EMAIL: ${{ secrets.JIRA_USER_EMAIL }}
JIRA_API_TOKEN: ${{ secrets.JIRA_API_TOKEN }}
- name: Search
if: github.event.action != 'opened'
id: search
uses: tomhjp/gh-action-jira-search@04700b457f317c3e341ce90da5a3ff4ce058f2fa # v0.2.2
with:
# cf[10089] is Issue Link (use JIRA API to retrieve)
jql: 'cf[10089] = "${{ github.event.issue.html_url || github.event.pull_request.html_url }}"'
- name: Sync comment
if: github.event.action == 'created' && steps.search.outputs.issue
uses: tomhjp/gh-action-jira-comment@6eb6b9ead70221916b6badd118c24535ed220bd9 # v0.2.0
uses: ./.github/actions/jira/sync-comment
with:
issue: ${{ steps.search.outputs.issue }}
comment: "${{ github.actor }} ${{ github.event.review.state || 'commented' }}:\n\n${{ github.event.comment.body || github.event.review.body }}\n\n${{ github.event.comment.html_url || github.event.review.html_url }}"
- name: Close ticket
if: ( github.event.action == 'closed' || github.event.action == 'deleted' ) && steps.search.outputs.issue
uses: atlassian/gajira-transition@38fc9cd61b03d6a53dd35fcccda172fe04b36de3 # v3.0.1
uses: ./.github/actions/jira/transition
with:
issue: ${{ steps.search.outputs.issue }}
transition: "Closed"
- name: Reopen ticket
if: github.event.action == 'reopened' && steps.search.outputs.issue
uses: atlassian/gajira-transition@38fc9cd61b03d6a53dd35fcccda172fe04b36de3 # v3.0.1
uses: ./.github/actions/jira/transition
with:
issue: ${{ steps.search.outputs.issue }}
transition: "To Do"