blob: c13c5f296b76fd558aeb9c9bd0cc1d41cbd3442f [file] [log] [blame]
#!/usr/bin/env bash
# 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.
# no public APIs here
# SHELLDOC-IGNORE
# this bug system handles JIRA. Personalities
# can override the following variables:
# base JIRA URL
JIRA_URL=${JIRA_URL:-"https://issues.apache.org/jira"}
# Issue regex to help identify the project
JIRA_ISSUE_RE=''
# If the issue status is matched with this pattern, the attached patch is regarded as ready to be applied
JIRA_STATUS_RE='Patch Available'
add_bugsystem jira
# Simple function to set a default JIRA user after PROJECT_NAME has been set
function jira_set_jira_user
{
if [[ -n "${PROJECT_NAME}" && ! "${PROJECT_NAME}" = unknown ]]; then
JIRA_USER=${JIRA_USER:-"${PROJECT_NAME}qa"}
fi
}
function jira_usage
{
jira_set_jira_user
yetus_add_option "--jira-base-url=<url>" "The URL of the JIRA server (default:'${JIRA_URL}')"
yetus_add_option "--jira-issue-re=<expr>" "Bash regular expression to use when trying to find a jira ref in the patch name (default: '${JIRA_ISSUE_RE}')"
yetus_add_option "--jira-password=<pw>" "The password for accessing JIRA"
yetus_add_option "--jira-status-re=<expr>" "Grep regular expression representing the issue status whose patch is applicable to the codebase (default: '${JIRA_STATUS_RE}')"
yetus_add_option "--jira-user=<user>" "The user to access JIRA command (default: ${JIRA_USER})"
}
function jira_parse_args
{
declare i
jira_set_jira_user
for i in "$@"; do
case ${i} in
--jira-base-url=*)
JIRA_URL=${i#*=}
;;
--jira-issue-re=*)
JIRA_ISSUE_RE=${i#*=}
;;
--jira-password=*)
JIRA_PASSWD=${i#*=}
;;
--jira-status-re=*)
JIRA_STATUS_RE=${i#*=}
;;
--jira-user=*)
JIRA_USER=${i#*=}
;;
esac
done
}
## @description provides issue determination based upon the URL and more.
## @description WARNING: called from the github plugin!
function jira_determine_issue
{
declare input=$1
declare patchnamechunk
declare maybeissue
if [[ -n ${JIRA_ISSUE} ]]; then
return 0
fi
if [[ -z "${JIRA_ISSUE_RE}" ]]; then
return 1
fi
# shellcheck disable=SC2016
patchnamechunk=$(echo "${input}" | "${AWK}" -F/ '{print $NF}')
maybeissue=$(echo "${patchnamechunk}" | cut -f1,2 -d-)
if [[ ${maybeissue} =~ ${JIRA_ISSUE_RE} ]]; then
# shellcheck disable=SC2034
ISSUE=${maybeissue}
JIRA_ISSUE=${maybeissue}
add_footer_table "JIRA Issue" "${JIRA_ISSUE}"
return 0
fi
return 1
}
function jira_http_fetch
{
declare input=$1
declare output=$2
declare ec
yetus_debug "jira_http_fetch: ${JIRA_URL}/${input}"
if [[ -n "${JIRA_USER}"
&& -n "${JIRA_PASSWD}" ]]; then
"${CURL}" --silent --fail \
--user "${JIRA_USER}:${JIRA_PASSWD}" \
--output "${output}" \
--location \
"${JIRA_URL}/${input}"
else
"${CURL}" --silent --fail \
--output "${output}" \
--location \
"${JIRA_URL}/${input}"
fi
ec=$?
case "${ec}" in
"0")
;;
"1")
yetus_debug "jira_http_fetch: Unsupported protocol. Maybe misspelled jira's url?"
;;
"3")
yetus_debug "jira_http_fetch: ${JIRA_URL}/${input} url is malformed."
;;
"6")
yetus_debug "jira_http_fetch: Could not resolve host in URL ${JIRA_URL}."
;;
"22")
yetus_debug "jira_http_fetch: ${JIRA_URL}/${input} returned 4xx status code. Maybe incorrect username/password?"
;;
*)
yetus_debug "jira_http_fetch: ${JIRA_URL}/${input} returned $ec error code. See https://ec.haxx.se/usingcurl-returns.html for details."
;;
esac
return ${ec}
}
function jira_locate_patch
{
declare input=$1
declare patchout=$2
declare diffout=$3
declare jsonloc
declare relativeurl
declare retval
declare found=false
yetus_debug "jira_locate_patch: trying ${JIRA_URL}/browse/${input}"
if [[ "${OFFLINE}" == true ]]; then
yetus_debug "jira_locate_patch: offline, skipping"
return 1
fi
if ! jira_http_fetch "browse/${input}" "${PATCH_DIR}/jira"; then
yetus_debug "jira_locate_patch: not a JIRA."
return 1
fi
# if github is configured check to see if there is a URL in the text
# that is a github patch file or pull request
if [[ -n "${GITHUB_BASE_URL}" ]]; then
jira_determine_issue "${input}"
# Download information via REST API
jsonloc="${PATCH_DIR}/jira-json"
jira_http_fetch "rest/api/2/issue/${input}" "${jsonloc}"
# Parse the downloaded information to check if the issue is
# just a pointer to GitHub.
if github_jira_bridge "${jsonloc}" "${patchout}" "${diffout}"; then
echo "${input} appears to be a Github PR. Switching Modes."
return 0
fi
yetus_debug "jira_locate_patch: ${input} seemed like a Github PR, but there was a failure."
fi
# Not reached if there is a successful github plugin return
if [[ $("${GREP}" -c "${JIRA_STATUS_RE}" "${PATCH_DIR}/jira") == 0 ]]; then
if [[ ${ROBOT} == true ]]; then
yetus_error "ERROR: ${input} issue status is not matched with \"${JIRA_STATUS_RE}\"."
cleanup_and_exit 1
else
yetus_error "WARNING: ${input} issue status is not matched with \"${JIRA_STATUS_RE}\"."
fi
fi
# See https://jira.atlassian.com/browse/JRA-27637 as why we can't use
# the REST interface here. :(
# the assumption here is that attachment id's are given in an
# ascending order. so bigger # == newer file
#shellcheck disable=SC2016
tr '>' '\n' < "${PATCH_DIR}/jira" \
| "${AWK}" 'match($0,"/secure/attachment/[0-9]*/[^\"]*"){print substr($0,RSTART,RLENGTH)}' \
| "${GREP}" -v -e 'htm[l]*$' \
| "${SED}" -e 's,[ ]*$,,g' \
| sort -n -r -k4 -t/ \
| uniq \
> "${PATCH_DIR}/jira-attachments.txt"
echo "${input} patch is being downloaded at $(date) from"
while read -r relativeurl && [[ ${found} = false ]]; do
PATCHURL="${JIRA_URL}${relativeurl}"
printf " %s -> " "${PATCHURL}"
jira_http_fetch "${relativeurl}" "${patchout}"
retval=$?
if [[ ${retval} == 0 ]]; then
found=true
echo "Downloaded"
elif [[ ${retval} == 22 ]]; then
echo "404"
yetus_debug "Presuming the attachment was deleted, trying the next one (see YETUS-298)"
else
echo "Error (curl returned ${retval})"
break
fi
done < <(cat "${PATCH_DIR}/jira-attachments.txt")
if [[ "${found}" = false ]]; then
yetus_error "ERROR: ${input} could not be downloaded."
cleanup_and_exit 1
fi
if [[ ! ${PATCHURL} =~ \.patch$ ]]; then
if guess_patch_file "${patchout}"; then
yetus_debug "The patch ${PATCHURL} was not named properly, but it looks like a patch file. Proceeding, but issue/branch matching might go awry."
add_vote_table 0 patch "The patch file was not named according to ${PROJECT_NAME}'s naming conventions. Please see ${PATCH_NAMING_RULE} for instructions."
else
# this definitely isn't a patch so just bail out.
return 1
fi
fi
add_footer_table "JIRA Patch URL" "${PATCHURL}"
return 0
}
## @description Try to guess the branch being tested using a variety of heuristics
## @audience private
## @stability evolving
## @replaceable no
## @return 0 on success, with PATCH_BRANCH updated appropriately
function jira_determine_branch
{
declare patchnamechunk
declare total
declare count
declare hinttype
for hinttype in "${PATCHURL}" "${PATCH_OR_ISSUE}"; do
if [[ -z "${hinttype}" ]]; then
continue
fi
# If one of these matches the JIRA issue regex
# then we don't want it to trigger the branch
# detection since that's almost certainly not
# intended. In other words, if ISSUE-99 is the
# name of a branch, you want to test ISSUE-99
# against master, not ISSUE-99's branch
if [[ ${hinttype} =~ ${JIRA_ISSUE_RE} ]]; then
continue
fi
yetus_debug "Determine branch: starting with ${hinttype}"
patchnamechunk=$(echo "${hinttype}" \
| "${SED}" -e 's,.*/\(.*\)$,\1,' \
-e 's,\.txt,.,' \
-e 's,.patch,.,g' \
-e 's,.diff,.,g' \
-e 's,\.\.,.,g' \
-e 's,\.$,,g' )
# ISSUE-branch-##
PATCH_BRANCH=$(echo "${patchnamechunk}" | cut -f3- -d- | cut -f1,2 -d-)
yetus_debug "Determine branch: ISSUE-branch-## = ${PATCH_BRANCH}"
if [[ -n "${PATCH_BRANCH}" ]]; then
if verify_valid_branch "${PATCH_BRANCH}"; then
return 0
fi
fi
# ISSUE-##[.##].branch
PATCH_BRANCH=$(echo "${patchnamechunk}" | cut -f3- -d. )
count="${PATCH_BRANCH//[^.]}"
total=${#count}
((total = total + 3 ))
until [[ ${total} -lt 3 ]]; do
PATCH_BRANCH=$(echo "${patchnamechunk}" | cut -f3-${total} -d.)
yetus_debug "Determine branch: ISSUE[.##].branch = ${PATCH_BRANCH}"
((total=total-1))
if [[ -n "${PATCH_BRANCH}" ]]; then
if verify_valid_branch "${PATCH_BRANCH}"; then
return 0
fi
fi
done
# ISSUE.branch.##
PATCH_BRANCH=$(echo "${patchnamechunk}" | cut -f2- -d. )
count="${PATCH_BRANCH//[^.]}"
total=${#count}
((total = total + 3 ))
until [[ ${total} -lt 2 ]]; do
PATCH_BRANCH=$(echo "${patchnamechunk}" | cut -f2-${total} -d.)
yetus_debug "Determine branch: ISSUE.branch[.##] = ${PATCH_BRANCH}"
((total=total-1))
if [[ -n "${PATCH_BRANCH}" ]]; then
if verify_valid_branch "${PATCH_BRANCH}"; then
return 0
fi
fi
done
# ISSUE-branch.##
PATCH_BRANCH=$(echo "${patchnamechunk}" | cut -f3- -d- | cut -f1- -d. )
count="${PATCH_BRANCH//[^.]}"
total=${#count}
((total = total + 1 ))
until [[ ${total} -lt 1 ]]; do
PATCH_BRANCH=$(echo "${patchnamechunk}" | cut -f3- -d- | cut -f1-${total} -d. )
yetus_debug "Determine branch: ISSUE-branch[.##] = ${PATCH_BRANCH}"
((total=total-1))
if [[ -n "${PATCH_BRANCH}" ]]; then
if verify_valid_branch "${PATCH_BRANCH}"; then
return 0
fi
fi
done
done
return 1
}
## @description Write the contents of a file to JIRA
## @param filename
## @stability stable
## @audience public
## @return exit code from posting to jira
function jira_write_comment
{
declare -r commentfile=${1}
declare retval=0
if [[ "${OFFLINE}" == true ]]; then
echo "JIRA Plugin: Running in offline, comment skipped."
return 0
fi
if [[ -n ${JIRA_PASSWD}
&& -n ${JIRA_USER} ]]; then
# RESTify the comment
{
echo "{\"body\":\""
"${SED}" -e 's,\\,\\\\,g' \
-e 's,\",\\\",g' \
-e 's,$,\\r\\n,g' "${commentfile}" \
| tr -d '\n'
echo "\"}"
} > "${PATCH_DIR}/jiracomment.$$"
"${CURL}" -X POST \
-H "Accept: application/json" \
-H "Content-Type: application/json" \
-u "${JIRA_USER}:${JIRA_PASSWD}" \
-d @"${PATCH_DIR}/jiracomment.$$" \
--silent --location \
"${JIRA_URL}/rest/api/2/issue/${JIRA_ISSUE}/comment" \
>/dev/null
retval=$?
rm "${PATCH_DIR}/jiracomment.$$"
else
echo "JIRA Plugin: no credentials provided to write a comment."
fi
return ${retval}
}
## @description Print out the finished details to the JIRA issue
## @audience private
## @stability evolving
## @replaceable no
## @param runresult
function jira_finalreport
{
declare result=$1
declare i
declare commentfile=${PATCH_DIR}/jiracommentfile
declare comment
declare vote
declare ourstring
declare ela
declare subs
declare color
declare comment
declare calctime
declare url
rm "${commentfile}" 2>/dev/null
if [[ ${ROBOT} == "false"
|| ${OFFLINE} == true ]] ; then
return 0
fi
if [[ -z "${JIRA_ISSUE}" ]]; then
return 0
fi
big_console_header "Adding comment to JIRA"
if [[ ${result} == 0 ]]; then
echo "| (/) *{color:green}+1 overall{color}* |" >> "${commentfile}"
else
echo "| (x) *{color:red}-1 overall{color}* |" >> "${commentfile}"
fi
echo "\\\\" >> "${commentfile}"
i=0
until [[ $i -eq ${#TP_HEADER[@]} ]]; do
printf '%s\n' "${TP_HEADER[${i}]}" >> "${commentfile}"
((i=i+1))
done
echo "\\\\" >> "${commentfile}"
echo "|| Vote || Subsystem || Runtime || Comment ||" >> "${commentfile}"
i=0
until [[ $i -eq ${#TP_VOTE_TABLE[@]} ]]; do
ourstring=$(echo "${TP_VOTE_TABLE[${i}]}" | tr -s ' ')
vote=$(echo "${ourstring}" | cut -f2 -d\| | tr -d ' ')
subs=$(echo "${ourstring}" | cut -f3 -d\|)
ela=$(echo "${ourstring}" | cut -f4 -d\|)
calctime=$(clock_display "${ela}")
comment=$(echo "${ourstring}" | cut -f5 -d\|)
if [[ "${vote}" = "H" ]]; then
echo "|| || || || {color:brown}${comment}{color} ||" >> "${commentfile}"
((i=i+1))
continue
fi
# summary line
if [[ -z ${vote}
&& -n ${ela} ]]; then
color="black"
elif [[ -z ${vote} ]]; then
# keep same color
true
else
# new vote line
case ${vote} in
1|"+1")
color="green"
;;
-1)
color="red"
;;
0)
color="blue"
;;
-0)
color="orange"
;;
H)
# this never gets called (see above) but this is here so others know the color is taken
color="brown"
;;
*)
color="black"
;;
esac
fi
printf '| {color:%s}%s{color} | {color:%s}%s{color} | {color:%s}%s{color} | {color:%s}%s{color} |\n' \
"${color}" "${vote}" \
"${color}" "${subs}" \
"${color}" "${calctime}" \
"${color}" "${comment}" \
>> "${commentfile}"
((i=i+1))
done
if [[ ${#TP_TEST_TABLE[@]} -gt 0 ]]; then
{ echo "\\\\" ; echo "\\\\"; } >> "${commentfile}"
echo "|| Reason || Tests ||" >> "${commentfile}"
i=0
until [[ $i -eq ${#TP_TEST_TABLE[@]} ]]; do
printf '%s\n' "${TP_TEST_TABLE[${i}]}" >> "${commentfile}"
((i=i+1))
done
fi
{ echo "\\\\" ; echo "\\\\"; } >> "${commentfile}"
url=$(get_artifact_url)
echo "|| Subsystem || Report/Notes ||" >> "${commentfile}"
i=0
until [[ $i -eq ${#TP_FOOTER_TABLE[@]} ]]; do
comment=$(echo "${TP_FOOTER_TABLE[${i}]}" |
"${SED}" -e "s,@@BASE@@,${url},g")
printf '%s\n' "${comment}" >> "${commentfile}"
((i=i+1))
done
printf '\n\nThis message was automatically generated.\n\n' >> "${commentfile}"
jira_write_comment "${commentfile}"
}