From 7f5f740e1f99ba488f810f41320d8f0524c4bc58 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Yoann=20Rodi=C3=A8re?= Date: Mon, 26 May 2025 17:37:42 +0200 Subject: [PATCH 1/2] Update jira-release.sh to call a webhook It's simpler than doing everything directly in our script. And before you ask, we just cannot have the webhook called from GitHub directly, because Jira requires a custom header. --- jira-release.sh | 217 +++++++++++++----------------------------------- 1 file changed, 57 insertions(+), 160 deletions(-) diff --git a/jira-release.sh b/jira-release.sh index 18178c9..f832d7f 100755 --- a/jira-release.sh +++ b/jira-release.sh @@ -1,16 +1,13 @@ #!/usr/bin/env -S bash -e -JIRA_CLOSE_TRANSITION_ID=2 -JIRA_REOPEN_TRANSITION_ID=3 - function usage() { echo "Usage:" echo - echo " $0 [options] " + echo " $0 [options] " echo - echo " The Jira project key (e.g. HHH)" + echo " One of [search,validator,ogm,orm]" echo " The version to release (e.g. 6.0.1)" - echo " The new version to create (e.g. 6.0.2)" + echo " The new version after the release (e.g. 6.0.2-SNAPSHOT)" echo echo " Options" echo @@ -18,6 +15,8 @@ function usage() { echo " -d Dry run; do not push, deploy or publish anything." } +function needs_arg() { if [ -z "$OPTARG" ]; then die "No arg for --$opt option"; fi; } + #-------------------------------------------- # Option parsing @@ -26,7 +25,17 @@ function exec_or_dry_run() { } PUSH_CHANGES=true -while getopts 'dhb:' opt; do +notesFile="" +while getopts 'dh-:' opt; do + if [ "$opt" = "-" ]; then + # long option: reformulate opt and OPTARG + # - extract long option name + opt="${OPTARG%%=*}" + # - extract long option argument (may be empty) + OPTARG="${OPTARG#"$opt"}" + # - remove assigning `=` + OPTARG="${OPTARG#=}" + fi case "$opt" in h) usage @@ -40,7 +49,7 @@ while getopts 'dhb:' opt; do echo "DRY RUN; would have executed:" "${@}" } ;; - \?) + *) usage exit 1 ;; @@ -51,171 +60,59 @@ shift $((OPTIND - 1)) SCRIPTS_DIR="$(readlink -f ${BASH_SOURCE[0]} | xargs dirname)" -JIRA_KEY=$1 +PROJECT=$1 RELEASE_VERSION=$2 -NEXT_VERSION=$3 +DEVELOPMENT_VERSION=$3 -if [ -z "$JIRA_KEY" ]; then - echo "ERROR: Jira key not supplied" +if [ -z "$PROJECT" ]; then + echo "ERROR: Project not supplied" + usage exit 1 fi - if [ -z "$RELEASE_VERSION" ]; then - echo "ERROR: Release version not supplied" - exit 1 -fi - -if [ -z "$NEXT_VERSION" ]; then - echo "ERROR: Next version not supplied" + echo "ERROR: Release version argument not supplied" + usage exit 1 fi - -if [ "$PUSH_CHANGES" == 'true' ] && [ -z "$JIRA_API_TOKEN" ]; then - echo "ERROR: Environment variable JIRA_API_TOKEN must not be empty" +if [ -z "$DEVELOPMENT_VERSION" ]; then + echo "ERROR: Development version argument not supplied" exit 1 fi -JIRA_VERSION_ID=$($SCRIPTS_DIR/determine-jira-version-id.sh $JIRA_KEY $RELEASE_VERSION) - -response=$(exec_or_dry_run curl -L -s -w "\n%{http_code}" -X PUT \ - -u "\$JIRA_API_TOKEN" \ - -H 'Accept: application/json' \ - -H 'Content-Type: application/json' \ - -d '{"released": true}' \ - "https://hibernate.atlassian.net/rest/api/2/version/$JIRA_VERSION_ID") - -if [ "$PUSH_CHANGES" == true ]; then - jiraReleaseResponseCode=$(tail -n1 <<< "$response") # get the last line - jiraReleaseResponse=$(sed '$ d' <<< "$response") # get all but the last line which contains the status code - - if [ "$jiraReleaseResponseCode" != 200 ]; then - echo "$jiraReleaseResponse" - echo "ERROR: Release failed because Jira version ${RELEASE_VERSION} could not be released" - exit 1 - fi +if [ "$PROJECT" == "search" ]; then + JIRA_KEY="HSEARCH" + STRIPPED_SUFFIX_FOR_JIRA="" +elif [ "$PROJECT" == "validator" ]; then + JIRA_KEY="HV" + STRIPPED_SUFFIX_FOR_JIRA="" +elif [ "$PROJECT" == "ogm" ]; then + JIRA_KEY="OGM" + STRIPPED_SUFFIX_FOR_JIRA="" +elif [ "$PROJECT" == "orm" ]; then + JIRA_KEY="HHH" + STRIPPED_SUFFIX_FOR_JIRA=".Final" +elif [ "$PROJECT" == "tools" ]; then + JIRA_KEY="HBX" + STRIPPED_SUFFIX_FOR_JIRA="" else - echo $response + echo "ERROR: Unknown project name $PROJECT" + usage + exit 1 fi -response=$(exec_or_dry_run curl -L -s -w "\n%{http_code}" -X POST \ - -u "\$JIRA_API_TOKEN" \ - -H 'Accept: application/json' \ - -H 'Content-Type: application/json' \ - -d '{"name": "'${NEXT_VERSION}'", "projectId": '${JIRA_PROJECT_ID}} \ - 'https://hibernate.atlassian.net/rest/api/2/version') - -if [ "$PUSH_CHANGES" == true ]; then - jiraCreateVersionResponseCode=$(tail -n1 <<< "$response") # get the last line - jiraCreateVersionResponse=$(sed '$ d' <<< "$response") # get all but the last line which contains the status code - - if [ "$jiraCreateVersionResponseCode" != 201 ]; then - echo "$jiraCreateVersionResponse" - echo "ERROR: Release failed because Jira version ${NEXT_VERSION} could not be created" - exit 1 - fi -else - echo $response +DEVELOPMENT_VERSION=${DEVELOPMENT_VERSION%-SNAPSHOT} +if [ -n "$STRIPPED_SUFFIX_FOR_JIRA" ]; then + RELEASE_VERSION=${RELEASE_VERSION%$STRIPPED_SUFFIX_FOR_JIRA} + DEVELOPMENT_VERSION=${DEVELOPMENT_VERSION%$STRIPPED_SUFFIX_FOR_JIRA} fi +if [ "$PUSH_CHANGES" == 'true' ] && [ -z "$JIRA_WEBHOOK_SECRET" ]; then + echo "ERROR: Environment variable JIRA_WEBHOOK_SECRET must not be empty" + exit 1 +fi -# REST URL used for getting all issues of given release - see https://docs.atlassian.com/jira/REST/latest/#d2e2450 -jiraIssuesResponse=$(curl -sL "https://hibernate.atlassian.net/rest/api/2/search/?jql=project%20%3D%20${JIRA_KEY}%20AND%20fixVersion%20%3D%20${RELEASE_VERSION}%20ORDER%20BY%20issuetype%20ASC&fields=issuetype,summary&maxResults=200") -jiraIssueUrls=$(echo "$jiraIssuesResponse" | sed -nE 's/(https:\/\/hibernate\.atlassian\.net\/rest\/api\/2\/issue\/[0-9]+)/\n\1\n/gp' | grep https://hibernate.atlassian.net/rest/api/2/issue/) - -# Close resolved issues. Remove fix version from Done non-resolved issues. -# Move issues Undone non-resolved issues to next version. -while IFS= read -r jiraIssueUrl; do - jiraIssueResponse=$(curl -sL "$jiraIssueUrl") - jiraIssueId=$(echo "$jiraIssueResponse" | sed -nE 's/^\{[^{]+"id":"([^"]+)".+/\1/p' ) - jiraIssueKey=$(echo "$jiraIssueResponse" | sed -nE 's/^\{[^{]+"key":"([^"]+)".+/\1/p' ) - jiraIssueStatus=$(echo "$jiraIssueResponse" | sed -nE 's/.*"status":\{([^{]+)\,"statusCategory".+/\1/p' | sed -nE 's/.*"name":"([^"]+)".+/\1/p' ) - if [ "$jiraIssueStatus" == "Resolved" ]; then - response=$(exec_or_dry_run curl -L -s -w "\n%{http_code}" -X POST \ - -u "\$JIRA_API_TOKEN" \ - -H 'Accept: application/json' \ - -H 'Content-Type: application/json' \ - -d '{"transition":{"id":"'${JIRA_CLOSE_TRANSITION_ID}'"}}' \ - "https://hibernate.atlassian.net/rest/api/2/issue/${jiraIssueId}/transitions") - - if [ "$PUSH_CHANGES" == true ]; then - jiraCloseIssueResponseCode=$(tail -n1 <<< "$response") # get the last line - jiraCloseIssueResponse=$(sed '$ d' <<< "$response") # get all but the last line which contains the status code - - if [ "$jiraCloseIssueResponseCode" != 201 ]; then - echo "$jiraCloseIssueResponse" - echo "ERROR: Release failed because Jira issue ${jiraIssueKey} could not be closed" - exit 1 - fi - else - echo $response - fi - elif [ "$jiraIssueStatus" == "Closed" ]; then - - if [ "$jiraIssueResolution" != "Fixed" ]; then - response=$(exec_or_dry_run curl -L -s -w "\n%{http_code}" -X POST \ - -u "\$JIRA_API_TOKEN" \ - -H 'Accept: application/json' \ - -H 'Content-Type: application/json' \ - -d '{"transition":{"id":"'${JIRA_REOPEN_TRANSITION_ID}'"}}' \ - "https://hibernate.atlassian.net/rest/api/2/issue/${jiraIssueId}/transitions") - - if [ "$PUSH_CHANGES" == true ]; then - jiraReopenIssueResponseCode=$(tail -n1 <<< "$response") # get the last line - jiraReopenIssueResponse=$(sed '$ d' <<< "$response") # get all but the last line which contains the status code - - if [ "$jiraReopenIssueResponseCode" != 201 ]; then - echo "$jiraReopenIssueResponse" - echo "ERROR: Release failed because Jira issue ${jiraIssueKey} could not be reopened" - exit 1 - fi - else - echo $response - fi - - response=$(exec_or_dry_run curl -L -s -w "\n%{http_code}" -X POST \ - -u "\$JIRA_API_TOKEN" \ - -H 'Accept: application/json' \ - -H 'Content-Type: application/json' \ - -d '{"transition":{"id":"'${JIRA_CLOSE_TRANSITION_ID}'"},"update":{"fixVersions":[{"remove":"'${RELEASE_VERSION}'"}]},"fields":{"resolution":{"name":"'${jiraIssueResolution}'"}}}' \ - "https://hibernate.atlassian.net/rest/api/2/issue/${jiraIssueId}/transitions") - - if [ "$PUSH_CHANGES" == true ]; then - jiraCloseIssueResponseCode=$(tail -n1 <<< "$response") # get the last line - jiraCloseIssueResponse=$(sed '$ d' <<< "$response") # get all but the last line which contains the status code - - if [ "$jiraCloseIssueResponseCode" != 201 ]; then - echo "$jiraCloseIssueResponse" - echo "ERROR: Release failed because Jira issue ${jiraIssueKey} could not be closed" - exit 1 - fi - else - echo $response - fi - else - echo "Ignoring already closed Jira issue: ${jiraIssueKey}" - fi - else - response=$(exec_or_dry_run curl -L -s -w "\n%{http_code}" -X POST \ - -u "\$JIRA_API_TOKEN" \ - -H 'Accept: application/json' \ - -H 'Content-Type: application/json' \ - -d '{"update":{"fixVersions":[{"remove":"'${RELEASE_VERSION}'"},{"add":"'${NEXT_VERSION}'"}]}}' \ - "https://hibernate.atlassian.net/rest/api/2/issue/${jiraIssueId}") - - if [ "$PUSH_CHANGES" == true ]; then - jiraMoveIssueResponseCode=$(tail -n1 <<< "$response") # get the last line - jiraMoveIssueResponse=$(sed '$ d' <<< "$response") # get all but the last line which contains the status code - - if [ "$jiraMoveIssueResponseCode" != 201 ]; then - echo "$jiraMoveIssueResponse" - echo "ERROR: Release failed because Jira issue ${jiraIssueKey} could not be moved to next version" - exit 1 - fi - else - echo $response - fi - fi -done <<< "$jiraIssueUrls" - - - \ No newline at end of file +exec_or_dry_run curl -L --fail-with-body -X POST \ + -H "X-Automation-Webhook-Token: $JIRA_WEBHOOK_SECRET" \ + -H 'Content-Type: application/json' \ + -d '{"data":{"projectKey": "'${JIRA_KEY}'", "releaseVersion":"'${RELEASE_VERSION}'","nextVersion":"'${DEVELOPMENT_VERSION}'"}}' \ + https://api-private.atlassian.com/automation/webhooks/jira/a/1fec2f23-3f8e-486e-b8fe-85159188d8c8/01970d1d-d1fa-756b-98f0-53c1d7cfd676 From b01de24cf2418b4f67c9b6c6fbde902e0ddee815 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Yoann=20Rodi=C3=A8re?= Date: Wed, 28 May 2025 12:15:55 +0200 Subject: [PATCH 2/2] Add count-releasable-commits.sh --- count-releasable-commits.sh | 52 +++++++++++++++++++++++++++++++++++++ 1 file changed, 52 insertions(+) create mode 100755 count-releasable-commits.sh diff --git a/count-releasable-commits.sh b/count-releasable-commits.sh new file mode 100755 index 0000000..271a4e0 --- /dev/null +++ b/count-releasable-commits.sh @@ -0,0 +1,52 @@ +#!/usr/bin/env -S bash -e + +function log() { + echo 1>&2 "$@" +} + +function usage() { + log "Usage:" + log + log " $0 " + log + log " One of [search,validator,ogm,orm,reactive]" +} + +SCRIPTS_DIR="$(readlink -f ${BASH_SOURCE[0]} | xargs dirname)" + +PROJECT=$1 +WORKSPACE=${WORKSPACE:-'.'} + +if [ "$PROJECT" == "search" ]; then + MESSAGE_PATTERN='^HSEARCH-|^\[HSEARCH-' +elif [ "$PROJECT" == "validator" ]; then + MESSAGE_PATTERN='^HV-|^\[HV-' +elif [ "$PROJECT" == "ogm" ]; then + MESSAGE_PATTERN='^OGM-|^\[OGM-' +elif [ "$PROJECT" == "orm" ]; then + MESSAGE_PATTERN='^HHH-|^\[HHH-' +elif [ "$PROJECT" == "tools" ]; then + MESSAGE_PATTERN='^HBX-|^\[HBX-' +elif [ "$PROJECT" == "reactive" ]; then + MESSAGE_PATTERN='^#[[:digit:]]+|^\[#[[:digit:]]+' +else + log "ERROR: Unknown project name $PROJECT" + usage + exit 1 +fi + + +LAST_RELEASE_COMMIT=$(git log --max-count=1 --author=Hibernate-CI --format='%H') +log "Last release-related commit: ${LAST_RELEASE_COMMIT}." +if [ -z "$LAST_RELEASE_COMMIT" ] +then + log "No release-related commit. Assuming no releasable commits." + log "Note: the very first release must be triggered manually." + echo "0" + exit 0 +fi + +# Displays the last few commits on stderr (thanks to tee) and just the count on stdout (thanks to wc) +git log $LAST_RELEASE_COMMIT..HEAD -E "--grep=${MESSAGE_PATTERN}" --format=' %H %s' \ + | tee >(echo 1>&2 "Releasable commits (max 10 displayed):"; tail -n 10 1>&2) \ + | wc -l