Skip to content

Commit 10be73c

Browse files
authored
ci: fix github to jira issue sync (#26747)
Add local actions for JIRA interactions to replace github actions that have been archived.
1 parent ac86225 commit 10be73c

File tree

10 files changed

+384
-35
lines changed

10 files changed

+384
-35
lines changed
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
# Copyright (c) HashiCorp, Inc.
2+
# SPDX-License-Identifier: MPL-2.0
3+
4+
name: jira-create
5+
description: Create a JIRA issue
6+
inputs:
7+
project:
8+
required: true
9+
description: JIRA project
10+
default: NMD
11+
issuetype:
12+
required: true
13+
description: Type of JIRA issue
14+
default: "GH Issue"
15+
summary:
16+
required: true
17+
description: Title of the issue
18+
description:
19+
required: false
20+
description: Description of the issue
21+
extraFields:
22+
required: false
23+
description: Extra fields to add to issue
24+
outputs:
25+
issue:
26+
description: JIRA issue ID of created issue
27+
value: ${{ steps.create.outputs.issue }}
28+
issue-key:
29+
description: JIRA issue key of created issue
30+
value: ${{ steps.create.outputs.issue-key }}
31+
runs:
32+
using: composite
33+
steps:
34+
- name: Create JIRA issue
35+
id: create
36+
shell: bash
37+
run: ./.github/actions/jira/create/jira-create.bash
38+
env:
39+
PROJECT: ${{ inputs.project }}
40+
ISSUE_TYPE: ${{ inputs.issueType }}
41+
SUMMARY: ${{ inputs.summary }}
42+
DESCRIPTION: ${{ inputs.description }}
43+
EXTRA_FIELDS: ${{ inputs.extraFields }}
Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
#!/usr/bin/env bash
2+
# Copyright (c) HashiCorp, Inc.
3+
# SPDX-License-Identifier: MPL-2.0
4+
5+
source "$(dirname "${BASH_SOURCE[0]}")/../shared.bash"
6+
7+
# Check for required input values
8+
if [ -z "${ISSUE_TYPE}" ]; then
9+
error "Missing 'issueType' input value"
10+
exit 1
11+
fi
12+
13+
if [ -z "${PROJECT}" ]; then
14+
error "Missing 'project' input value"
15+
exit 1
16+
fi
17+
18+
if [ -z "${SUMMARY}" ]; then
19+
error "Missing 'summary' input value"
20+
exit 1
21+
fi
22+
23+
# Grab the issue type ID
24+
result="$(jira-request "${JIRA_BASE_URL}/rest/api/3/issuetype")" || exit
25+
query="$(printf '.[] | select(.name == "%s").id' "${ISSUE_TYPE}")"
26+
type_id="$(jq -r "${query}" <<< "${result}")"
27+
28+
if [ -z "${type_id}" ]; then
29+
error "Could not find issue type with name '%s'" "${ISSUE_TYPE}"
30+
exit 1
31+
fi
32+
33+
info "Issue type ID for '%s': %s" "${ISSUE_TYPE}" "${type_id}"
34+
35+
if [ -n "${DESCRIPTION}" ]; then
36+
description="$(convert-gfm-to-jira "${DESCRIPTION}")" || exit
37+
fi
38+
39+
# Base template for issue creation
40+
template='
41+
{
42+
description: $description,
43+
issuetype: {
44+
id: $issuetype
45+
},
46+
project: {
47+
key: $project
48+
},
49+
summary: $summary
50+
}'
51+
new_issue="$(jq -n --arg description "${description}" --arg issuetype "${type_id}" --arg project "${PROJECT}" --arg summary "${SUMMARY}" "${template}")" || exit
52+
53+
# If there are extra fields provided, merge them in
54+
if [ -n "${EXTRA_FIELDS}" ]; then
55+
new_issue="$(printf "%s %s" "${new_issue}" "${EXTRA_FIELDS}" | jq -s add)"
56+
fi
57+
58+
# Wrap the payload for submission
59+
template='{fields: $fields}'
60+
new_issue="$(jq -n --argjson fields "${new_issue}" "${template}")" || exit
61+
62+
info "JIRA new issue payload:\n%s" "${new_issue}"
63+
64+
# Create the issue
65+
# NOTE: The v2 API is used here for creating the issue. This is because
66+
# the v3 API only supports the Atlassian Document Format for which pandoc
67+
# currently does not have support (https://github.com/jgm/pandoc/issues/9898)
68+
result="$(jira-request --request "POST" --data "${new_issue}" "${JIRA_BASE_URL}/rest/api/2/issue")" || exit
69+
key="$(jq -r '.key' <<< "${result}")"
70+
id="$(jq -r '.id' <<< "${result}")"
71+
72+
printf "issue=%s\n" "${id}" >> "${GITHUB_OUTPUT}"
73+
printf "issue-key=%s\n" "${key}" >> "${GITHUB_OUTPUT}"
74+
75+
info ">> New JIRA issue created: %s/browse/%s" "${JIRA_BASE_URL}" "${key}"
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
# Copyright (c) HashiCorp, Inc.
2+
# SPDX-License-Identifier: MPL-2.0
3+
4+
name: jira-search
5+
description: Search for a JIRA issue
6+
inputs:
7+
jql:
8+
required: true
9+
description: JQL used to search for issue
10+
outputs:
11+
issue:
12+
description: JIRA issue ID matching JQL
13+
value: ${{ steps.search.outputs.issue }}
14+
runs:
15+
using: composite
16+
steps:
17+
- name: Search for JIRA issue
18+
id: search
19+
shell: bash
20+
run: ./.github/actions/jira/search/jira-search.bash
21+
env:
22+
JQL: ${{ inputs.jql }}
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
#!/usr/bin/env bash
2+
# Copyright (c) HashiCorp, Inc.
3+
# SPDX-License-Identifier: MPL-2.0
4+
5+
source "$(dirname "${BASH_SOURCE[0]}")/../shared.bash"
6+
7+
# Check for required inputs
8+
if [ -z "${JQL}" ]; then
9+
error "Missing 'jql' input value"
10+
exit 1
11+
fi
12+
13+
info "Searching for existing JIRA issue..."
14+
info "JQL: %s" "${JQL}"
15+
template='{jql: $jql}'
16+
search="$(jq -n --arg jql "${JQL}" "${template}")" || exit
17+
result="$(jira-request --request "POST" --data "${search}" "${JIRA_BASE_URL}/rest/api/3/search/jql")" || exit
18+
issue="$(jq -r '.issues[].id' <<< "${result}")"
19+
20+
if [ -z "${issue}" ]; then
21+
info "No existing issue found in JIRA"
22+
exit
23+
fi
24+
25+
info "Existing JIRA issue found: %s" "${issue}"
26+
27+
# Make issue available in output
28+
printf "issue=%s\n" "${issue}" >> "${GITHUB_OUTPUT}"

.github/actions/jira/shared.bash

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
#!/usr/bin/env bash
2+
# Copyright (c) HashiCorp, Inc.
3+
# SPDX-License-Identifier: MPL-2.0
4+
5+
TEXT_RED='\e[31m'
6+
TEXT_CLEAR='\e[0m'
7+
8+
# Make a request to the Jira API
9+
function jira-request() {
10+
curl --show-error --location --fail \
11+
--user "${JIRA_USER_EMAIL}:${JIRA_API_TOKEN}" \
12+
--header "Accept: application/json" \
13+
--header "Content-Type: application/json" \
14+
"${@}"
15+
}
16+
17+
# Write an informational message
18+
function info() {
19+
local msg_template="${1}\n"
20+
local i=$(( ${#} - 1 ))
21+
local msg_args=("${@:2:$i}")
22+
23+
#shellcheck disable=SC2059
24+
printf ">> ${msg_template}" "${msg_args[@]}" >&2
25+
}
26+
27+
# Write an error message
28+
function error() {
29+
local msg_template="${1}\n"
30+
local i=$(( ${#} - 1 ))
31+
local msg_args=("${@:2:$i}")
32+
33+
#shellcheck disable=SC2059
34+
printf "%b!! ERROR:%b ${msg_template}%b" "${TEXT_RED}" "${TEXT_CLEAR}" "${msg_args[@]}" >&2
35+
}
36+
37+
# Convert content from GitHub format to Jira format
38+
function convert-gfm-to-jira() {
39+
local content="${1?Content value is required}"
40+
local src
41+
src="$(mktemp)" ||
42+
return 1
43+
printf "%s" "${content}" > "${src}"
44+
# NOTE: Using docker here instead of installing the pandoc package directly.
45+
# This is because when installing the pandoc package in CI the post install
46+
# tasks take multiple minutes to complete.
47+
docker run --rm -v "$(dirname "${src}"):/data" pandoc/core --from=gfm --to=jira "/data/$(basename "${src}")" ||
48+
return 1
49+
rm -f "${src}"
50+
return 0
51+
}
52+
53+
# Check for environment variables that must always be set
54+
if [ -z "${JIRA_BASE_URL}" ]; then
55+
error "Missing JIRA_BASE_URL environment variable"
56+
exit 1
57+
fi
58+
59+
if [ -z "${JIRA_USER_EMAIL}" ]; then
60+
error "Missing JIRA_USER_EMAIL environment variable"
61+
exit 1
62+
fi
63+
64+
if [ -z "${JIRA_API_TOKEN}" ]; then
65+
error "Missing JIRA_API_TOKEN environment variable"
66+
exit 1
67+
fi
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
# Copyright (c) HashiCorp, Inc.
2+
# SPDX-License-Identifier: MPL-2.0
3+
4+
name: jira-sync-comment
5+
description: Sync GitHub comment to JIRA issue
6+
inputs:
7+
issue:
8+
required: true
9+
description: JIRA issue to sync comment
10+
comment:
11+
required: true
12+
description: Comment to add to JIRA issue
13+
14+
runs:
15+
using: composite
16+
steps:
17+
- name: Sync comment to JIRA
18+
shell: bash
19+
run: ./.github/actions/jira/sync-comment/jira-sync-comment.bash
20+
env:
21+
ISSUE: ${{ inputs.issue }}
22+
COMMENT: ${{ inputs.comment }}
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
#!/usr/bin/env bash
2+
# Copyright (c) HashiCorp, Inc.
3+
# SPDX-License-Identifier: MPL-2.0
4+
5+
source "$(dirname "${BASH_SOURCE[0]}")/../shared.bash"
6+
7+
# Check for required inputs
8+
if [ -z "${ISSUE}" ]; then
9+
error "Missing 'issue' input value"
10+
exit 1
11+
fi
12+
13+
if [ -z "${COMMENT}" ]; then
14+
error "Missing 'comment' input value"
15+
exit 1
16+
fi
17+
18+
comment="$(convert-gfm-to-jira "${COMMENT}")" || exit
19+
template='
20+
{
21+
body: $comment
22+
}
23+
'
24+
issue_comment="$(jq -n --arg comment "${comment}" "${template}")"
25+
26+
info "Adding comment to JIRA issue %s" "${ISSUE}"
27+
info "Comment payload: %s" "${issue_comment}"
28+
29+
# Create the comment
30+
# NOTE: The v2 API is used here for creating the comment. This is because
31+
# the v3 API only supports the Atlassian Document Format for which pandoc
32+
# currently does not have support (https://github.com/jgm/pandoc/issues/9898)
33+
result="$(jira-request --request "POST" --data "${issue_comment}" "${JIRA_BASE_URL}/rest/api/2/issue/${ISSUE}/comment")" || exit
34+
comment_id="$(jq -r .id <<< "${result}")"
35+
36+
info "JIRA issue ID %s updated with new comment ID %s" "${ISSUE}" "${comment_id}"
37+
38+
printf "comment-id=%s\n" "${comment_id}" >> "${GITHUB_OUTPUT}"
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
# Copyright (c) HashiCorp, Inc.
2+
# SPDX-License-Identifier: MPL-2.0
3+
4+
name: jira-transition
5+
description: Transition state of JIRA issue
6+
inputs:
7+
issue:
8+
required: true
9+
description: JIRA issue to transition
10+
transition:
11+
required: true
12+
description: Transition name to apply to issue
13+
14+
runs:
15+
using: composite
16+
steps:
17+
- name: Transition JIRA issue
18+
shell: bash
19+
run: ./.github/actions/jira/transition/jira-transition.bash
20+
env:
21+
ISSUE: ${{ inputs.issue }}
22+
TRANSITION: ${{ inputs.transition }}
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
#!/usr/bin/env bash
2+
# Copyright (c) HashiCorp, Inc.
3+
# SPDX-License-Identifier: MPL-2.0
4+
5+
source "$(dirname "${BASH_SOURCE[0]}")/../shared.bash"
6+
7+
# Check for required inputs
8+
if [ -z "${ISSUE}" ]; then
9+
error "Missing 'issue' input value"
10+
exit 1
11+
fi
12+
13+
if [ -z "${TRANSITION}" ]; then
14+
error "Missing 'transition' input value"
15+
exit 1
16+
fi
17+
18+
# Grab the transition ID
19+
result="$(jira-request "${JIRA_BASE_URL}/rest/api/3/issue/${ISSUE}/transitions")" || exit
20+
query="$(printf '.transitions[] | select(.name == "%s").id' "${TRANSITION}")"
21+
transition_id="$(jq -r "${query}" <<< "${result}")"
22+
23+
if [ -z "${transition_id}" ]; then
24+
error "Could not find matching transition with name matching '%s'" "${TRANSITION}"
25+
exit 1
26+
fi
27+
28+
template='
29+
{
30+
transition: {
31+
id: $transition
32+
}
33+
}
34+
'
35+
issue_transition="$(jq -n --arg transition "${transition_id}" "${template}")" || exit
36+
37+
info "Transitioning JIRA issue '%s' to %s (ID: %s)" "${ISSUE}" \
38+
"${TRANSITION}" "${transition_id}"
39+
info "Transition payload:\n%s" "${issue_transition}"
40+
41+
jira-request --request "POST" --data "${issue_transition}" \
42+
"${JIRA_BASE_URL}/rest/api/3/issue/${ISSUE}/transitions" || exit
43+
44+
info "JIRA issue '%s' transitioned to %s" "${ISSUE}" "${TRANSITION}"

0 commit comments

Comments
 (0)