This is an automated email from the ASF dual-hosted git repository. jdaugherty pushed a commit to branch 7.0.x in repository https://gitbox.apache.org/repos/asf/grails-core.git
commit 508bfa1a270201137a0fb7f6afd7c736b3472fbf Author: James Daugherty <jdaughe...@jdresources.net> AuthorDate: Wed Sep 10 13:55:26 2025 -0400 chore: add scripts to help automate post-vote processes --- .asf.yaml | 7 ++ .github/scripts/releaseDistributions.sh | 83 +++++++++++++++++++ .github/scripts/releaseJarFiles.sh | 141 ++++++++++++++++++++++++++++++++ .github/vote_templates/groovy_pmc.txt | 1 + .github/vote_templates/staged.txt | 7 +- .github/workflows/release.yml | 108 ++++++++++++++++++------ 6 files changed, 317 insertions(+), 30 deletions(-) diff --git a/.asf.yaml b/.asf.yaml index 92fe9f6c4a..60f332e399 100644 --- a/.asf.yaml +++ b/.asf.yaml @@ -18,6 +18,13 @@ github: # deployment environments to delay releasing until the necessary steps have completed environments: + close: + required_reviewers: + - id: grails-committers + type: Team + - id: jdaugherty + type: User + wait_timer: 0 docs: required_reviewers: - id: grails-committers diff --git a/.github/scripts/releaseDistributions.sh b/.github/scripts/releaseDistributions.sh new file mode 100755 index 0000000000..d935db6ea6 --- /dev/null +++ b/.github/scripts/releaseDistributions.sh @@ -0,0 +1,83 @@ +#!/bin/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 +# +# https://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. +# + +# ./releaseDistributions.sh <tag> <username> <password> + +set -euo pipefail + +if [[ $# -ne 3 ]]; then + echo "Usage: $0 <tag> <username> <password>" >&2 + exit 1 +fi + +RELEASE_TAG="$1" +RELEASE_VERSION="${RELEASE_TAG#v}" +SVN_USER="$2" +SVN_PASS="$3" +RELEASE_ROOT="https://dist.apache.org/repos/dist/release/incubator/grails/core" +DEV_ROOT="https://dist.apache.org/repos/dist/dev/incubator/grails/core" + +if [[ -z "${RELEASE_TAG}" ]]; then + echo "â ERROR: Release Tag must not be empty." >&2 + exit 1 +fi +if [[ -z "${SVN_USER}" ]]; then + echo "â ERROR: Username must not be empty." >&2 + exit 1 +fi +if [[ -z "${SVN_PASS}" ]]; then + echo "â ERROR: Password must not be empty." >&2 + exit 1 +fi + +svn_flags=(--non-interactive --trust-server-cert --username "${SVN_USER}" --password "${SVN_PASS}") + +svn_exists() { + local url="$1" + svn ls "${svn_flags[@]}" --depth=empty "${url}" >/dev/null 2>&1 +} + +old_release_folder="$(svn ls "${svn_flags[@]}" "${RELEASE_ROOT}" | awk -F/ 'NF{print $1; exit}')" +if [[ -n "${old_release_folder}" ]]; then + PRIOR_RELEASE_URL="${RELEASE_ROOT}/${old_release_folder}" + echo "đī¸ Deleting old release folder: ${PRIOR_RELEASE_URL}" + svn rm "${svn_flags[@]}" -m "Remove previous release ${old_release_folder}" "${PRIOR_RELEASE_URL}" + echo "â Deleted old release folder" +else + echo "âšī¸ No existing release subfolder found under ${RELEASE_ROOT}" +fi + +DEV_VERSION_URL="$DEV_ROOT/${RELEASE_VERSION}" +RELEASE_VERSION_URL="$RELEASE_ROOT/${RELEASE_VERSION}" + +if ! svn_exists "${DEV_VERSION_URL}"; then + echo "â ERROR: dev folder for ${RELEASE_VERSION} does not exist at: ${DEV_VERSION_URL}" >&2 + exit 2 +fi + +if svn_exists "${RELEASE_VERSION_URL}"; then + echo "â ERROR: release folder for ${RELEASE_VERSION} already exists at: ${RELEASE_VERSION_URL}" >&2 + exit 3 +fi + +echo "đ Promoting ${DEV_VERSION_URL} -> ${RELEASE_VERSION_URL}" +svn mv "${svn_flags[@]}" -m "Promote Apache Grails (incubating) ${RELEASE_VERSION} from dev to release" "${DEV_VERSION_URL}" "${RELEASE_VERSION_URL}" +echo "â Promoted" diff --git a/.github/scripts/releaseJarFiles.sh b/.github/scripts/releaseJarFiles.sh new file mode 100755 index 0000000000..8421782d74 --- /dev/null +++ b/.github/scripts/releaseJarFiles.sh @@ -0,0 +1,141 @@ +#!/bin/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 +# +# https://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. +# + +# ./releaseJarFiles.sh <staging repo description> <username> <password> + +set -euo pipefail + +if [[ $# -ne 3 ]]; then + echo "Usage: $0 <staging repo description> <username> <password>" >&2 + exit 1 +fi + +NEXUS_URL="https://repository.apache.org" +STAGING_DESCRIPTION="$1" +NEXUS_USER="$2" +NEXUS_PASS="$3" + +if [[ -z "${STAGING_DESCRIPTION}" ]]; then + echo "ERROR: Staging Description must not be empty." >&2 + exit 1 +fi +if [[ -z "${NEXUS_USER}" ]]; then + echo "ERROR: Username must not be empty." >&2 + exit 1 +fi +if [[ -z "${NEXUS_PASS}" ]]; then + echo "ERROR: Password must not be empty." >&2 + exit 1 +fi + +nexusApi() { + local request_method="$1"; shift + local path="$1"; shift + curl -fsS -u "${NEXUS_USER}:${NEXUS_PASS}" \ + -H 'Accept: application/json' \ + -H 'Content-Type: application/json' \ + -X "${request_method}" "${NEXUS_URL}/service/local/${path}" "$@" +} + +wait_for_promotion() { + local repoId="$1" + local timeout_s="${2:-600}" # default 10 minutes + local interval_s="${3:-3}" + local started + started="$(date +%s)" + + echo "Waiting for release promotion to complete (timeout ${timeout_s}s)âĻ" + + while :; do + # 1) If any ERROR appears in activity, fail fast + act="$(nexusApi GET "/staging/repository/${repoId}/activity" || true)" + if [[ -n "$act" ]]; then + err_count="$(jq -r '[.. | objects? | select(has("severity")) | select(.severity=="ERROR")] | length' <<<"$act" 2>/dev/null || echo 0)" + if [[ "$err_count" != "0" ]]; then + echo "ERROR: Staging activity contains failure(s). Aborting." >&2 + # Optionally dump recent relevant lines: + jq -r '.. | objects? | select(has("severity")) | "\(.severity): \(.name // "event") - \(.message // "")"' <<<"$act" || true + return 1 + fi + fi + + # 2) Check transitioning flag â when false after promote, action is done + trans="$(nexusApi GET '/staging/profile_repositories' \ + | jq -r --arg r "$repoId" '.data[]? | select(.repositoryId==$r) | .transitioning' 2>/dev/null || echo "true")" + + if [[ "$trans" == "false" ]]; then + # sanity: make sure we actually saw some "release/promote" activity; otherwise keep waiting a bit + if [[ -n "$act" ]]; then + # did we see any promote/release-ish step? + saw_promote="$(jq -r ' + [ .. | objects? | .name? // empty | ascii_downcase + | select(test("release|promote|finish")) ] | length' <<<"$act" 2>/dev/null || echo 0)" + if [[ "$saw_promote" -gt 0 ]]; then + echo "Promotion appears complete." + return 0 + fi + fi + fi + + # timeout? + now="$(date +%s)" + if (( now - started >= timeout_s )); then + echo "ERROR: Timed out waiting for promotion to complete." >&2 + # Show a short summary to aid debugging + if [[ -n "$act" ]]; then + echo "--- Recent activity snapshot ---" + jq -r '.. | objects? | select(has("severity") or has("name")) | "\(.severity // "")\t\(.name // "")\t\(.message // "")"' <<<"$act" | tail -n 20 || true + fi + return 1 + fi + + sleep "$interval_s" + done +} + +repos_json="$(nexusApi GET '/staging/profile_repositories')" +repoId="$(jq -r --arg d "${STAGING_DESCRIPTION}" '.data[] | select(.description==$d) | .repositoryId' <<<"${repos_json}")" +profileId="$(jq -r --arg d "${STAGING_DESCRIPTION}" '.data[] | select(.description==$d) | .profileId' <<<"${repos_json}")" +state="$(jq -r --arg d "${STAGING_DESCRIPTION}" '.data[] | select(.description==$d) | .type' <<<"${repos_json}")" + +if [[ -z "${repoId}" || -z "${profileId}" ]]; then + echo "ERROR: No staged repository found with description: ${STAGING_DESCRIPTION}" >&2 + exit 2 +fi +echo "Found staged repo: ${repoId} (profile: ${profileId}, state: ${state})" +if [[ "${state}" == "open" ]]; then + echo "ERROR: Staged Repo is not closed: ${STAGING_DESCRIPTION}" >&2 + exit 3 +fi + +if [[ "${state}" == "closed" ]]; then + echo "Promoting (release) ${repoId}âĻ" + nexusApi POST "/staging/profiles/${profileId}/promote" \ + --data "$(jq -n --arg r "${repoId}" --arg d "${STAGING_DESCRIPTION}" '{data:{stagedRepositoryId:$r,description:$d}}')" +fi + +wait_for_promotion "$repoId" 600 3 + +echo "Dropping staging repository ${repoId}âĻ" +nexusApi POST "/staging/profiles/${profileId}/drop" \ + --data "$(jq -n --arg r "$repoId" --arg d "${STAGING_DESCRIPTION}" '{data:{stagedRepositoryId:$r,description:$d}}')" + +echo "Done. Released ${repoId}." \ No newline at end of file diff --git a/.github/vote_templates/groovy_pmc.txt b/.github/vote_templates/groovy_pmc.txt index 14a16d2ad5..b205127d9c 100644 --- a/.github/vote_templates/groovy_pmc.txt +++ b/.github/vote_templates/groovy_pmc.txt @@ -1,4 +1,5 @@ Hi Everyone, + The Apache Grails community has voted to approve the release of Apache Grails ${VERSION}. As the incubation host, we now kindly request the Groovy PMC to review & approve our ASF release. diff --git a/.github/vote_templates/staged.txt b/.github/vote_templates/staged.txt index 63f2f1e575..af21ff9f42 100644 --- a/.github/vote_templates/staged.txt +++ b/.github/vote_templates/staged.txt @@ -4,12 +4,7 @@ I am happy to start the VOTE thread for an Apache Grails (incubating) release of Please note the following build reproducibility issues for this release: -1. grails-cli-7.0.0-RC1-all.jar # caused by extension module merge from the shadowJar (need to strip date from property file) & the plugin.xml signature is being included -- Created https://github.com/apache/grails-core/issues/14951 to address this in a future release -2. grails-converters-7.0.0-RC1.jar # contents identical on decompile -3. grails-datamapping-core-test-7.0.0-RC1-tests.jar # contents identical on decompile -4. grails-datamapping-tck-7.0.0-RC1.jar # contents identical on decompile -5. grails-forge-core-7.0.0-RC1-javadoc.jar # due to groovy 3 in forge, we need to update to groovy 4 to fix this +1. grails-forge-core-${VERSION}-javadoc.jar # due to groovy 3 in forge, we need to update to groovy 4 to fix this Given that these differences are known, explainable, and immaterial, we are moving forward with a release vote. diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 9b8dc39731..9f7f35ecf1 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -23,7 +23,7 @@ env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} jobs: publish: - name: "Stage Files" + name: "Stage Jar Files" permissions: contents: write # to create release & upload wrapper issues: write # to modify milestones @@ -361,6 +361,7 @@ jobs: set -e gh release --repo ${{ github.repository }} delete-asset v${{ needs.publish.outputs.release_version }} PUBLISHED_ARTIFACTS.txt --yes upload: + name: "Upload Distributions to dist.apache.org" needs: [ publish, source ] runs-on: ubuntu-24.04 permissions: @@ -470,7 +471,7 @@ jobs: VERSION_COMMIT_ID: ${{ needs.publish.outputs.commit_hash }} run: | export DIST_SVN_REVISION=$(awk '/Last Changed Rev:/ {print $4}' dev-repo/DIST_SVN_REVISION.txt) - + echo "::group::Grails PPMC Vote Email" echo "*************************************************" echo "Subject: [VOTE] Release Apache Grails (incubating) ${VERSION}" echo "*************************************************" @@ -478,6 +479,7 @@ jobs: echo "*************************************************" cat grails-core/.github/vote_templates/staged.txt | envsubst echo "*************************************************" + echo "::endgroup::" - name: 'Groovy Vote Email' env: VERSION: ${{ needs.publish.outputs.release_version }} @@ -485,6 +487,7 @@ jobs: run: | export DIST_SVN_REVISION=$(awk '/Last Changed Rev:/ {print $4}' dev-repo/DIST_SVN_REVISION.txt) + echo "::group::Groovy PMC Vote Email" echo "*************************************************" echo "Subject: [VOTE] Approval of Apache Grails (incubating) ${VERSION} release by Groovy PMC" echo "*************************************************" @@ -492,25 +495,60 @@ jobs: echo "*************************************************" cat grails-core/.github/vote_templates/groovy_pmc.txt | envsubst echo "*************************************************" - - name: 'Announcement Email' - env: - VERSION: ${{ needs.publish.outputs.release_version }} - VERSION_COMMIT_ID: ${{ needs.publish.outputs.commit_hash }} - PREVIOUS_VERSION: 'TODO_PREVIOUS_VERSION' - run: | - export DIST_SVN_REVISION=$(awk '/Last Changed Rev:/ {print $4}' dev-repo/DIST_SVN_REVISION.txt) - - echo "*************************************************" - echo "Subject: [ANNOUNCE] Apache Grails (incubating) ${VERSION}" - echo "*************************************************" - echo "Body:" - echo "*************************************************" - cat grails-core/.github/vote_templates/announce.txt | envsubst - echo "*************************************************" + echo "::endgroup::" + release: + environment: release + name: "VOTE SUCCEEDED - Release Artifacts" + needs: [ publish, source, upload ] + runs-on: ubuntu-24.04 + steps: + - name: "Output Agent IP" # in the event RAO blocks this agent, this can be used to debug it + run: curl -s https://api.ipify.org + - name: "Setup SVN and Tools" + run: sudo apt-get install -y subversion subversion-tools tree gettext-base + - name: "đŗ Grails PPMC Vote Confirmation - MANUAL" + run: | + echo "::group::Manual Confirmation" + echo "đ This step is a placeholder that the vote confirmation on d...@grails.apache.org completed successfully." + echo "::endgroup::" + - name: "đŗ Groovy PMC Vote Confirmation - MANUAL" + run: | + echo "::group::Manual Confirmation" + echo "đ This step is a placeholder that the vote confirmation on d...@grails.apache.org completed successfully." + echo "::endgroup::" + - name: "đ Release JAR files - MANUAL" + run: | + echo "::group::Manual Jar Promotion" + echo "Run .github/scripts/releaseJarFiles.sh 'v${{ needs.publish.outputs.release_version }}' '${{ needs.publish.outputs.extract_repository_name }}:${{ needs.publish.outputs.release_version }}' ASF_USER ASF_PASS" + echo "::endgroup::" + - name: "đ Release distribution artifacts - MANUAL" + run: | + echo "::group::Manual ASF Artifact Promotion" + echo "Run github/scripts/releaseDistributions.sh 'v${{ needs.publish.outputs.release_version }}' ASF_USER ASF_PASS" + echo "::endgroup::" + - name: "â Update ASF Reporter - MANUAL" + run: | + echo "::group::Manual ASF Reporter Update" + TODAY=$(date +"%Y-%m-%d") + echo "Check email & update https://reporter.apache.org to mark the release ${{ needs.publish.outputs.release_version }} as complete as of ${TODAY}" + echo "Note: this is a place holder; currently this is not possible since Groovy sponsors us instead of the incubator PMC" + echo "::endgroup::" + - name: "â Deploy grails-forge - MANUAL" + run: | + echo "::group::Manual grails-forge deployment" + echo "Kick off Forge-* workflow for version ${{ needs.publish.outputs.release_version }} based on the below rules:" + cat <<'EOF' + * RELEASE - GA releases only + * NEXT - Milestones and Release Candidate + * SNAPSHOT - current or next version snapshot + * PREV - previous release version + * PREV-SNAPSHOT - previous version snapshot + EOF + echo "::endgroup::" docs: environment: docs - name: "Publish Documentation" - needs: [ publish, source, upload ] + name: "VOTE SUCCEEDED - Publish Documentation" + needs: [ publish, source, upload ] # TODO Once we have confirmed release won't fail, add it as a dependency here runs-on: ubuntu-24.04 steps: - name: "đĨ Checkout repository" @@ -544,9 +582,9 @@ jobs: VERSION: ${{ needs.publish.outputs.release_version }} sdkman: environment: sdkman - name: "Release to SDKMAN!" + name: "VOTE SUCCEEDED - Release to SDKMAN!" runs-on: ubuntu-24.04 - needs: [ publish, source, upload ] + needs: [ publish, source, upload ] # TODO Once we have confirmed release won't fail, add it as a dependency here steps: - name: "đĨ Checkout repository" uses: actions/checkout@v4 @@ -576,10 +614,10 @@ jobs: env: GVM_SDKVENDOR_KEY: ${{ secrets.GVM_SDKVENDOR_KEY }} GVM_SDKVENDOR_TOKEN: ${{ secrets.GVM_SDKVENDOR_TOKEN }} - release: - name: "Close Release" + close: + name: "VOTE SUCCEEDED - Close Release" environment: release - needs: [ publish, source, upload, docs, sdkman ] + needs: [ publish, source, upload, docs, sdkman ] # TODO Once we have confirmed release won't fail, add it as a dependency here runs-on: ubuntu-24.04 permissions: contents: write # required for gradle.properties revert @@ -606,3 +644,25 @@ jobs: uses: apache/grails-github-actions/post-release@asf env: RELEASE_SCRIPT_PATH: '.github/scripts/setSnapshotGrailsVersion.sh' + - name: 'đ Create Blog Post - MANUAL' + run: | + echo "::group::Blog Post Creation - MANUAL" + echo "Publish a blog post on https://grails.apache.org/blog/ about the new release ${{ needs.publish.outputs.release_version }} using the repo https://github.com/apache/grails-static-website" + echo "::endgroup::" + - name: 'Announcement Email' + env: + VERSION: ${{ needs.publish.outputs.release_version }} + VERSION_COMMIT_ID: ${{ needs.publish.outputs.commit_hash }} + PREVIOUS_VERSION: 'TODO_PREVIOUS_VERSION' + run: | + export DIST_SVN_REVISION=$(awk '/Last Changed Rev:/ {print $4}' dev-repo/DIST_SVN_REVISION.txt) + + echo "::group::Announcement Email" + echo "*************************************************" + echo "Subject: [ANNOUNCE] Apache Grails (incubating) ${VERSION}" + echo "*************************************************" + echo "Body:" + echo "*************************************************" + cat grails-core/.github/vote_templates/announce.txt | envsubst + echo "*************************************************" + echo "::endgroup::"