Skip to content

Instantly share code, notes, and snippets.

@gulrich1
Last active May 7, 2021 13:16
Show Gist options
  • Save gulrich1/91d284a2444af32a41c609735fdf3203 to your computer and use it in GitHub Desktop.
Save gulrich1/91d284a2444af32a41c609735fdf3203 to your computer and use it in GitHub Desktop.
#!/bin/bash
############################
# Dependencies: jq, recode #
############################
# PARAMETERS:
# 1 - project name
# 2 - pull request
########################
# Environent variable: #
# GITHUB_TOKEN #
# SONAR_TOKEN #
########################
################################################
# Constants depending on the project or team: #
GITHUB_ORG=despegar #
SONAR_URL=http://jenkins-loyalty-cross-00:9000 #
################################################
CURL_OPTS=-s
DEBUG=1
BLOCKER_IMG="![severity: BLOCKER](https://sonarsource.github.io/sonar-github/severity-blocker.png)"
CRITICAL_IMG="![severity: CRITICAL](https://sonarsource.github.io/sonar-github/severity-critical.png)"
MAJOR_IMG="![severity: MAYOR](https://sonarsource.github.io/sonar-github/severity-major.png)"
MINOR_IMG="![severity: MINOR](https://sonarsource.github.io/sonar-github/severity-minor.png)"
INFO_IMG="![severity: INFO](https://sonarsource.github.io/sonar-github/severity-info.png)"
RULE_IMG="![details](https://sonarsource.github.io/sonar-github/rule.png)"
# Get last commit id for pull request
get_commit_id () {
TOKEN=$1
ORG=$2
REPO=$3
PR=$4
curl $CURL_OPTS -H "Authorization: token $TOKEN" -H "Accept: application/vnd.github.v3+json" \
https://api.github.com/repos/$ORG/$REPO/pulls/$PR | jq -r '.head.sha'
}
get_total_violations() {
TOKEN=$1
PROJECT=$2
PR=$3
curl $CURL_OPTS -u "${TOKEN}:" --location --request POST \
"$SONAR_URL/api/measures/component?component=$PROJECT&pullRequest=$PR&metricKeys=violations" \
| jq -r '.component.measures[] | select (.metric== "violations") | .value'
}
get_violations_list() {
TOKEN=$1
PROJECT=$2
PR=$3
curl $CURL_OPTS -u "${TOKEN}:" --location --request POST \
"$SONAR_URL/api/issues/search?componentKeys=$PROJECT&ps=100&p=1&pullRequest=$PR&metricKeys=violations&resolved=false" \
| jq ".issues | [.[] | {\
id: .key,
severity: (if .severity == null then \"MINOR\" else .severity end),\
rule: .rule,\
line: .line,\
component: .component,\
message: .message,\
type: .type\
}]"
}
get_source_code_line() {
TOKEN=$1
PROJECT=$2
PR=$3
LINE=$4
FILE=$5
curl $CURL_OPTS -u "${TOKEN}:" --location --request POST \
"$SONAR_URL/api/sources/lines?pullRequest=$PR&from=$LINE&to=$LINE&key=$FILE" \
| jq -r '.sources[0].code' | sed -e 's/<[^>]*>//g' | recode html..ascii
}
get_pull_request_diff() {
TOKEN=$1
ORG=$2
PROJECT=$3
PR=$4
curl $CURL_OPTS -H "Authorization: token $TOKEN" -H "Accept: application/vnd.github.v3.diff" \
"https://api.github.com/repos/$GITHUB_ORG/$PROJECT/pulls/$PR"
}
get_line_in_diff() {
PR=$1
FILE=$(basename $2) # File name
SOURCE=$(echo $3 | sed 's/"/\\"/g') # Source code line
cat /tmp/${PR}.diff | awk "BEGIN{ found=0} /$FILE/{found=1} {if (found) print }" \
| awk 'BEGIN{ found=0} /@@.*/{found=1} {if (found) print }' \
| awk '{print} /diff --git.*/ {exit}' | nl -v 0 | grep "$SOURCE" | awk '{print $1}'
}
get_github_reviews() {
TOKEN=$1
ORG=$2
PROJECT=$3
PR=$4
curl $CURL_OPTS -H "Authorization: token $TOKEN" \
-H "Accept: application/vnd.github.v3+json" \
https://api.github.com/repos/$ORG/$PROJECT/pulls/$PR/comments \
| jq "[.[] | { \
rp_id: .pull_request_review_id,\
id: .id,\
path: .path,\
original_position:\
.original_position,\
original_commit_id:\
.original_commit_id,\
body: .body\
}]"
}
get_github_comments() {
TOKEN=$1
ORG=$2
PROJECT=$3
PR=$4
curl $CURL_OPTS -H "Authorization: token $TOKEN" \
-H "Accept: application/vnd.github.v3+json" \
https://api.github.com/repos/$ORG/$PROJECT/issues/$PR/comments | jq '[.[] | {id: .id, body: .body}]'
}
update_github_status_pending() {
TOKEN=$1
ORG=$2
PROJECT=$3
COMMIT=$4
curl $CURL_OPTS -H "Authorization: token $TOKEN" -X POST -H "Accept: application/vnd.github.v3+json" \
https://api.github.com/repos/$ORG/$PROJECT/statuses/$COMMIT \
-d '{"state":"pending", "context": "Sonarqube"}'
}
update_github_status_error() {
TOKEN=$1
ORG=$2
PROJECT=$3
COMMIT=$4
ISSUES="$5"
curl $CURL_OPTS -H "Authorization: token $TOKEN" -X POST -H "Accept: application/vnd.github.v3+json" \
https://api.github.com/repos/$ORG/$PROJECT/statuses/$COMMIT \
-d '{"state" : "error", "context": "Sonarqube", "description": "Sonarqube reported '$ISSUES' issues. Quality gate is ERROR"}'
}
update_github_status_success() {
TOKEN=$1
ORG=$2
PROJECT=$3
COMMIT=$4
ISSUES="$5"
curl $CURL_OPTS -H "Authorization: token $TOKEN" -X POST -H "Accept: application/vnd.github.v3+json" \
https://api.github.com/repos/$ORG/$PROJECT/statuses/$COMMIT \
-d '{"state" : "success", "context": "Sonarqube", "description": "Sonarqube reported '$ISSUES' issues, no criticals or blockers"}'
}
get_qualitygate_status_from_sonar() {
TOKEN=$1
PROJECT=$2
PR=$3
curl $CURL_OPTS -u "${TOKEN}:" --location \
--request POST "$SONAR_URL/api/qualitygates/project_status?projectKey=$PROJECT&pullRequest=$PR" \
| jq -r .projectStatus.status
}
get_qualitygate_error_from_sonar() {
TOKEN=$1
PROJECT=$2
PR=$3
curl $CURL_OPTS -u "${TOKEN}:" --location \
--request POST "$SONAR_URL/api/qualitygates/project_status?projectKey=$PROJECT&pullRequest=$PR" \
| jq -r .projectStatus.status
}
add_github_comment_summary() {
TOKEN=$1
ORG=$2
PROJECT=$3
PR=$4
curl -s -H "Authorization: token $TOKEN" -X POST -d @/tmp/${PR}.sumary.json \
"https://api.github.com/repos/$ORG/$PROJECT/issues/$PR/comments"
}
usage() {
echo "usage: sonar-github-integration.sh <project_name> <pull-request>"
echo "set this environment variables: GITHUB_TOKEN, SONAR_SONAR"
echo "edit variables SONAR_URL and GITHUB_ORG according to the project"
}
if [ "$#" -ne 2 ] || [ -z "$GITHUB_TOKEN" ] || [ -z "$SONAR_TOKEN" ]; then
usage
exit 1
fi
PROJECT_NAME=$1
PULL_REQUEST=$(echo $2 | sed 's/PR-\(.*\)/\1/')
COMMIT_ID=$(get_commit_id $GITHUB_TOKEN $GITHUB_ORG $PROJECT_NAME $PULL_REQUEST)
echo "commit_id: $COMMIT_ID"
if [ "$ACTION" == "PENDING" ]; then
update_github_status_pending $GITHUB_TOKEN $GITHUB_ORG $PROJECT_NAME $COMMIT_ID > /dev/null
exit 0
fi
# Get number of violations
violations=$(get_total_violations $SONAR_TOKEN $PROJECT_NAME $PULL_REQUEST)
if [ -z "$violations" ] || [ "$violations" -eq 0 ]; then
echo "No violations found for PR: $PULL_REQUEST"
update_github_status_success $GITHUB_TOKEN $GITHUB_ORG $PROJECT_NAME $COMMIT_ID 0
exit 0
else
echo "Found $violations violations for PR: $PULL_REQUEST"
fi
list=$(get_violations_list $SONAR_TOKEN $PROJECT_NAME $PULL_REQUEST)
count=$(echo $list | jq length)
echo "Issues to process: $count"
get_pull_request_diff $GITHUB_TOKEN $GITHUB_ORG $PROJECT_NAME $PULL_REQUEST > /tmp/${PULL_REQUEST}.diff
# Create json with all coments to send to github
REVIEWS='[]'
ADDITIONAL_COMMENTS='[]'
declare -A issue_severity
if [ $count -gt 0 ]; then
count=$((count-1))
for idx in $(seq 0 $count);
do
found=0
if [ $DEBUG -eq 1 ]; then
echo "Processing issue: $idx"
fi
# Get issue properties
issue=$(echo $list | jq ".[$idx]")
id=$(echo $issue | jq -r .id) # line number
line=$(echo $issue | jq '.line // empty') # line number
severity=$(echo $issue | jq -r .severity)
rule=$(echo $issue | jq -r .rule)
component=$(echo $issue | jq -r .component)
message=$(echo $issue | jq -r .message | sed 's/\"/\\\"/g' | tr -d '\n')
type=$(echo $issue | jq -r .type)
file=$(echo $component | sed 's/.*:\(.*\)/\1/')
if [ -n "$line" ]; then
source=$(get_source_code_line $SONAR_TOKEN $PROJECT_NAME $PULL_REQUEST $line $component)
diff_position=$(get_line_in_diff $PULL_REQUEST "$file" "$source")
fi
url="$SONAR_URL/project/issues?id=$PROJECT_NAME&open=$id&pullRequest=$PULL_REQUEST&resolved=false&types=$type"
if [ -n "$diff_position" ]; then
#increment severity counter
((issue_severity["$severity"]++))
found=1
case $severity in
"BLOCKER") comment="$CRITICAL_IMG $message [$RULE_IMG]($url)" ;;
"CRITICAL") comment="$CRITICAL_IMG $message [$RULE_IMG]($url)" ;;
"MAYOR") comment="$MAYOR_IMG $message [$RULE_IMG]($url)" ;;
"MINOR") comment="$MINOR_IMG $message [$RULE_IMG]($url)" ;;
*) comment="- $INFO_IMG $message [$RULE_IMG]($url)" ;;
esac
REVIEWS=$(echo $REVIEWS | jq ". + [{\"commit_id\": \"$COMMIT_ID\", \"body\":\"$comment\", \"path\": \"$file\", \"position\": $diff_position}]")
else
if [ -n "$line" ]; then
filename="$(basename $file)#L${line}:"
else
filename="$(basename $file):"
fi
case $severity in
"BLOCKER") comment="- $CRITICAL_IMG $filename $message [$RULE_IMG]($url)\n" ;;
"CRITICAL") comment="- $CRITICAL_IMG $filename $message [$RULE_IMG]($url)\n" ;;
"MAYOR") comment="- $MAYOR_IMG $filename $message [$RULE_IMG]($url)\n" ;;
"MINOR") comment="- $MINOR_IMG $filename $message [$RULE_IMG]($url)\n" ;;
*) comment="- $INFO_IMG $filename $message [$RULE_IMG]($url)\n" ;;
esac
ADDITIONAL_COMMENTS=$(echo "$ADDITIONAL_COMMENTS" | jq --compact-output ". + [\"$comment\"]")
fi
if [ $DEBUG -eq 1 ]; then
echo -e "\tseverity: $severity"
echo -e "\tline: $line"
echo -e "\trule: $rule"
echo -e "\tcomponent: $component"
echo -e "\tmessage: $message"
echo -e "\ttype: $type"
echo -e "\tfile: $file"
echo -e "\tsource code: '$source'"
echo -e "\tdiff position: $diff_position"
echo -e "\turl: $url"
echo -e "\tfound: $found"
if [ $idx -ne $count ]; then
echo "" # new line
fi
fi
done
fi
# Save coments in file grouping by file and posiion
echo $REVIEWS | jq " . | group_by(.path, .position, .commit_id) | map({\
path: .[0].path,\
position: .[0].position,\
commit_id: .[0].commit_id,\
body: map(.body) | join(\"\n\")\
})" > "/tmp/${PULL_REQUEST}.reviews.json"
#START Create comment summary in /tmp/$PULL_REQUEST.summary.json
global_comment="[]"
total=0
for i in "${!issue_severity[@]}"
do
total=$((total+${issue_severity[$i]}))
done
global_comment=$(echo $global_comment | jq ". + [\"SonarQube analysis reported $total issues\n\n\"]")
for i in "${!issue_severity[@]}"
do
case $i in
"BLOCKER") comment="$CRITICAL_IMG ${issue_severity[$i]}" ;;
"CRITICAL") comment="$CRITICAL_IMG ${issue_severity[$i]}" ;;
"MAYOR") comment="$MAYOR_IMG ${issue_severity[$i]}" ;;
"MINOR") comment="$MINOR_IMG ${issue_severity[$i]}" ;;
*) comment="$INFO_IMG ${issue_severity[$i]}" ;;
esac
global_comment=$(echo $global_comment | jq ". + [\"- $comment ${i,,}\n\"]")
done
additionals=$(echo $ADDITIONAL_COMMENTS | jq -r length)
if [ "$additionals" -gt 0 ]; then
if [ "$additionals" -gt 1 ]; then
global_comment=$(echo $global_comment | jq ". + [\"\n**$additionals extra issues**\n\"]")
fi
global_comment=$(echo $global_comment | jq ". + [\"\nNote: The following issues were found on lines that were not modified in the pull request. Because these issues can't be reported as line comments, they are summarized here:\n\"]")
global_comment=$(echo $global_comment | jq ". + $ADDITIONAL_COMMENTS")
fi
echo $global_comment | jq '. | join("") | { body: .}' > "/tmp/${PULL_REQUEST}.sumary.json"
# End Create comment summary
get_github_reviews $GITHUB_TOKEN $GITHUB_ORG $PROJECT_NAME $PULL_REQUEST > /tmp/${PULL_REQUEST}.github.reviews.json
get_github_comments $GITHUB_TOKEN $GITHUB_ORG $PROJECT_NAME $PULL_REQUEST > /tmp/${PULL_REQUEST}.github.comments.json
# ******************************************
# at this point we have the following files
# - /tmp/${PULL_REQUEST}.diff -> pull request diff
# - /tmp/${PULL_REQUEST}.reviews.json -> json with all reviews
# - /tmp/${PULL_REQUEST}.sumary.json -> json with comment summary
# - /tmp/${PULL_REQUEST}.github.reviews.json -> json with all reviews in github
# - /tmp/${PULL_REQUEST}.github.sumary.json -> json with comments in github
# check if reviews already exists
reviews=$(cat /tmp/${PULL_REQUEST}.reviews.json | jq '.')
count=$(echo $reviews | jq length)
if [ $count -gt 0 ]; then
count=$((count-1))
for idx in $(seq 0 $count);
do
echo "Check if comment $idx exists in github"
review=$(echo $reviews | jq ".[$idx]")
position=$(echo $review | jq -r '.position')
path=$(echo $review | jq -r '.path')
body=$(echo $review | jq -r '.body')
q=$(cat "/tmp/${PULL_REQUEST}.github.reviews.json" | jq ".| [.[] | select (.original_position == $position) \
| select(.path == \"$path\")] | length")
if [ "$q" -eq 0 ]; then
echo "Review not found in github"
curl -s -H "Authorization: token $GITHUB_TOKEN" \
-X POST \
-H "Accept: application/vnd.github.v3+json" \
https://api.github.com/repos/$GITHUB_ORG/$PROJECT_NAME/pulls/$PULL_REQUEST/comments \
-d ''"$review"''
else
body_comment=$(cat "/tmp/${PULL_REQUEST}.github.reviews.json" | jq -r ".| [.[] | select (.original_position == $position) \
| select(.path == \"$path\")] | .[0].body")
if [ "$body" == "$body_comment" ]; then
echo "Review found in github, skiping"
else
echo "Review found in github, but body is diferent"
fi
fi
done
fi
# filter comment
echo "Check if summary exists in github:"
has_comment=$(cat /tmp/${PULL_REQUEST}.github.comments.json | jq '.| [.[] | select(.body | contains("SonarQube analysis reported"))] | length')
if [ "$has_comment" -gt 0 ]; then
echo "Found."
else
echo "Not found."
add_github_comment_summary $GITHUB_TOKEN $GITHUB_ORG $PROJECT_NAME $PULL_REQUEST
fi
status=$(get_qualitygate_status_from_sonar $SONAR_TOKEN $PROJECT_NAME $PULL_REQUEST)
if [ "$status" == 'ERROR' ]; then
update_github_status_error $GITHUB_TOKEN $GITHUB_ORG $PROJECT_NAME $COMMIT_ID "$violations"
else
update_github_status_success $GITHUB_TOKEN $GITHUB_ORG $PROJECT_NAME $COMMIT_ID "$violations"
fi
# Remove all temp files
rm "/tmp/${PULL_REQUEST}.reviews.json"
rm "/tmp/${PULL_REQUEST}.diff"
rm "/tmp/${PULL_REQUEST}.sumary.json"
rm "/tmp/${PULL_REQUEST}.github.comments.json"
rm "/tmp/${PULL_REQUEST}.github.reviews.json"
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment