Skip to content

Commit 4b1ed66

Browse files
committed
ci: set fix versions when closing issues
When closing an issue, check if a pull request is linked to the issue. If so, extract labels to detect backport versions and include them when closing the jira issue.
1 parent fe53729 commit 4b1ed66

File tree

3 files changed

+112
-33
lines changed

3 files changed

+112
-33
lines changed

.github/actions/jira/transition/action.yml

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,16 @@ inputs:
1313
transition:
1414
required: true
1515
description: Transition name to apply to issue
16+
fixed-version:
17+
required: false
18+
description: Version fixed (only used if resolution set. Use 'auto' for detection in nomad.)
19+
timeline-url:
20+
required: false
21+
description: GitHub API issue timeline URL (required if fixed-version is set to 'auto')
22+
project:
23+
required: false
24+
description: JIRA project (required for setting fixed-version)
25+
default: NMD
1626

1727
runs:
1828
using: composite
@@ -24,3 +34,6 @@ runs:
2434
ISSUE: ${{ inputs.issue }}
2535
TRANSITION: ${{ inputs.transition }}
2636
RESOLUTION: ${{ inputs.resolution }}
37+
FIXED_VERSION: ${{ inputs.fixed-version }}
38+
TIMELINE_URL: ${{ inputs.timeline-url }}
39+
PROJECT: ${{ inputs.project }}

.github/actions/jira/transition/jira-transition.bash

Lines changed: 96 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,9 @@
44

55
source "$(dirname "${BASH_SOURCE[0]}")/../shared.bash"
66

7-
# Check for required inputs
7+
# Do all the checks up front so we just bail before
8+
# doing any actual work.
9+
810
if [ -z "${ISSUE}" ]; then
911
error "Missing 'issue' input value"
1012
exit 1
@@ -15,6 +17,21 @@ if [ -z "${TRANSITION}" ]; then
1517
exit 1
1618
fi
1719

20+
if [ -n "${FIXED_VERSION}" ] && [ -z "${RESOLUTION}" ]; then
21+
error "The 'fixed-version' value can only be set if 'resolution' is set"
22+
exit 1
23+
fi
24+
25+
if [ -n "${FIXED_VERSION}" ] && [ -z "${PROJECT}" ]; then
26+
error "Missing 'project' input value (must be set if 'fixed-version' is set)"
27+
exit 1
28+
fi
29+
30+
if [ "${FIXED_VERSION}" == "auto" ] && [ -z "${TIMELINE_URL}" ]; then
31+
error "Missing 'timeline-url' input value (must be set if 'fixed-version' is 'auto')"
32+
exit 1
33+
fi
34+
1835
# Grab the transition ID
1936
result="$(jira-request "${JIRA_BASE_URL}/rest/api/3/issue/${ISSUE}/transitions")" || exit
2037
query="$(printf '.transitions[] | select(.name == "%s").id' "${TRANSITION}")"
@@ -25,8 +42,15 @@ if [ -z "${transition_id}" ]; then
2542
exit 1
2643
fi
2744

28-
# If a resolution is set, find it and generate the template
29-
# with it included
45+
# Create the initial payload. This will be updated as needed prior
46+
# to sending the request.
47+
template='{transition: {id: $transition_id}}'
48+
payload="$(jq -n --arg transition_id "${transition_id}" "${template}")" || exit
49+
50+
info "Transitioning JIRA issue '%s' to %s (ID: %s)" "${ISSUE}" \
51+
"${TRANSITION}" "${transition_id}"
52+
53+
# If a resolution is set, find it
3054
if [ -n "${RESOLUTION}" ]; then
3155
# Grab the resolution ID
3256
result="$(jira-request "${JIRA_BASE_URL}/rest/api/3/resolution")" || exit
@@ -38,41 +62,80 @@ if [ -n "${RESOLUTION}" ]; then
3862
exit 1
3963
fi
4064

41-
template='
42-
{
43-
transition: {
44-
id: $transition
45-
},
46-
fields: {
47-
resolution: {
48-
id: $resolution
49-
}
50-
}
51-
}
52-
'
53-
issue_transition="$(jq -n --arg transition "${transition_id}" --arg resolution "${resolution_id}" "${template}")" || exit
54-
else
55-
# No resolution so the template only includes the transition
56-
template='
57-
{
58-
transition: {
59-
id: $transition
60-
}
61-
}
62-
'
63-
issue_transition="$(jq -n --arg transition "${transition_id}" "${template}")" || exit
65+
# Render the data structure for the resolution
66+
template='{fields: {resolution: {id: $resolution_id}}}'
67+
rendered="$(jq -n --arg resolution_id "${resolution_id}" "${template}")" || exit
68+
69+
# Add it to the payload
70+
payload="$(jq -s '.[0] * .[1]' <<< "${payload}${rendered}")"
71+
72+
info "Resolving JIRA issue '%s' to %s (ID: %s)" "${ISSUE}" "${RESOLUTION}" "${resolution_id}"
6473
fi
6574

66-
info "Transitioning JIRA issue '%s' to %s (ID: %s)" "${ISSUE}" \
67-
"${TRANSITION}" "${transition_id}"
68-
if [ -n "${resolution_id}" ]; then
69-
info "Resolving JIRA issue '%s' as %s (ID: %s)" "${ISSUE}" \
70-
"${RESOLUTION}" "${resolution_id}"
75+
# Handle setting fixed versions if set
76+
if [ -n "${FIXED_VERSION}" ]; then
77+
fixed_versions=()
78+
79+
# First pull the valid versions from jira
80+
project="$(jira-request "${JIRA_BASE_URL}/rest/api/3/project/${PROJECT}")" || exit
81+
jira_versions="$(jq -r '.versions' <<< "${project}")"
82+
83+
# If the fixed version value is auto, attempt to detect versions from pull linked pull request
84+
if [ "${FIXED_VERSION}" == "auto" ]; then
85+
timeline="$(curl -sL --show-error --fail-with-body -H "Accept: application/vnd.github+json" "${TIMELINE_URL}")" || exit
86+
filter='.[] | select(.event == "cross-referenced") | .source.issue.labels.[] | select(.name | startswith("backport")).name'
87+
readarray -t labels < <(jq -r "${filter}" <<< "${timeline}")
88+
89+
for label in "${labels[@]}"; do
90+
# start with stripping off the start of the label (backport/ or backport/ent/)
91+
version_prefix="${label##*/}"
92+
# then strip off the end of the label
93+
version_prefix="${version_prefix%.x*}"
94+
95+
filter="$(printf '.[] | select(.name | contains("%s")).name' "${version_prefix}")"
96+
readarray -t valid_versions < <(jq -r "${filter}" <<< "${jira_versions}")
97+
match="${version_prefix}.0"
98+
for v in "${valid_versions[@]}"; do
99+
if [ "${v##*.}" -gt "${match##*.}" ]; then
100+
match="${v}"
101+
fi
102+
done
103+
104+
filter="$(printf '.[] | select(.name | endswith("%s")).id' "${match}")"
105+
version_id="$(jq -r "${filter}" <<< "${jira_versions}")"
106+
if [ -z "${version_id}" ]; then
107+
printf "WARNING: Failed to find valid JIRA version to match label: '%s'\n" "${label}"
108+
continue
109+
fi
110+
fixed_versions+=(jq -n --arg version_id "${version_id}" '[{id: $version_id}]')
111+
done
112+
else
113+
# Match version directly
114+
filter="$(printf '.[] | select(.name | endswith("%s")).id' "${FIXED_VERSION}")"
115+
version_id="$(jq -r "${filter}" <<< "${jira_versions}")"
116+
if [ -z "${version_id}" ]; then
117+
printf "WARNING: Failed to find valid JIRA version to match provided version: '%s'\n" "${FIXED_VERSION}"
118+
else
119+
fixed_versions+=(jq -n --arg version_id "${version_id}" '[{id: $version_id}]')
120+
fi
121+
fi
122+
123+
# If fixed versions are availble, create the data structure
124+
if [ "${fixed_versions[#]}" -gt "0" ]; then
125+
template='{fields: {fixVersions: $versions}}'
126+
# Combine all the versions into a single array
127+
versions="$(jq -s 'add' <<< "${fixed_versions[*]}")"
128+
# Render the data structure for the fix versions
129+
rendered="$(jq -n --argjson versions "${versions}" "${template}")" || exit
130+
131+
# Add it to the payload
132+
payload="$(jq -s '.[0] * .[1]' <<< "${payload}${rendered}")"
133+
fi
71134
fi
72135

73-
info "Transition payload:\n%s" "${issue_transition}"
136+
info "Transition payload:\n%s" "${payload}"
74137

75-
jira-request --request "POST" --data "${issue_transition}" \
138+
jira-request --request "POST" --data "${payload}" \
76139
"${JIRA_BASE_URL}/rest/api/3/issue/${ISSUE}/transitions" || exit
77140

78141
info "JIRA issue '%s' transitioned to %s" "${ISSUE}" "${TRANSITION}"

.github/workflows/jira-sync.yml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,9 @@ jobs:
5959
issue: ${{ steps.search.outputs.issue }}
6060
transition: "Closed"
6161
resolution: "Done"
62+
fixed-version: "auto"
63+
project: NMD
64+
timeline-url: github.event.issue.timeline-url
6265
- name: Reopen ticket
6366
if: github.event.action == 'reopened' && steps.search.outputs.issue
6467
uses: ./.github/actions/jira/transition

0 commit comments

Comments
 (0)