Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
110 changes: 84 additions & 26 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,7 @@ jobs:
publish:
needs: release
runs-on: ubuntu-latest
timeout-minutes: 15
timeout-minutes: 30
environment: maven-publish
permissions:
id-token: write
Expand Down Expand Up @@ -180,36 +180,94 @@ jobs:
exit 1
fi

- name: Deploy and release to Maven Central
id: deploy
- name: Build and sign artifacts
id: build
run: |
# Build + GPG-sign the publishable modules locally (no deploy to staging).
./mvnw -s settings.xml \
-Ddevelocity.storage.directory=$HOME/.develocity-root \
-Dmaven.repo.local=$HOME/.m2/spring-data-valkey \
deploy -DskipTests \
-pl spring-data-valkey,spring-boot-starter-data-valkey \
-Dmaven.deploy.skip=false \
-DautoReleaseAfterClose=true \
clean install javadoc:jar -DskipTests \
-pl spring-data-valkey,spring-boot-starter-data-valkey -am \
-U -B

- name: Drop staging repository on failure
if: failure() && steps.deploy.outcome == 'failure'
- name: Assemble Central bundle
id: bundle
run: |
echo "Deploy failed - attempting to drop staging repository..."
AUTH_HEADER="Bearer $(echo -n "${OSSRH_USERNAME}:${OSSRH_PASSWORD}" | base64)"
REPOS=$(curl -s --header "Authorization: ${AUTH_HEADER}" \
"https://ossrh-staging-api.central.sonatype.com/manual/search/repositories?profile_id=io.valkey.springframework&state=open")
REPO_COUNT=$(echo "$REPOS" | jq '.repositories | length')
if [ "$REPO_COUNT" = "1" ]; then
REPO_KEY=$(echo "$REPOS" | jq -r '.repositories[0].key')
echo "Dropping repository: $REPO_KEY"
curl --fail --request DELETE \
--header "Authorization: ${AUTH_HEADER}" \
"https://ossrh-staging-api.central.sonatype.com/manual/drop/repository/${REPO_KEY}"
echo "Staging repository dropped - safe to retry"
elif [ "$REPO_COUNT" = "0" ]; then
echo "No open staging repository found - nothing to clean up"
else
echo "Multiple open staging repositories found ($REPO_COUNT) - skipping automatic cleanup to avoid dropping wrong repo"
echo "$REPOS" | jq '.repositories'
set -euo pipefail
VERSION="$(./mvnw -s settings.xml help:evaluate -Dexpression=project.version -q -DforceStdout -N)"
echo "version=$VERSION" >> "$GITHUB_OUTPUT"

# module_dir|group_path|artifact_id
MODULES=(
"spring-data-valkey|io/valkey/springframework/data|spring-data-valkey"
"spring-boot-starter-data-valkey|io/valkey/springframework/boot|spring-boot-starter-data-valkey"
)

rm -rf bundle bundle.zip
for entry in "${MODULES[@]}"; do
IFS='|' read -r DIR GROUP ARTIFACT <<< "$entry"
OUT="bundle/${GROUP}/${ARTIFACT}/${VERSION}"
mkdir -p "$OUT"

# Published POM = the flattened pom.
cp "${DIR}/.flattened-pom.xml" "${OUT}/${ARTIFACT}-${VERSION}.pom"

for f in "${ARTIFACT}-${VERSION}.jar" "${ARTIFACT}-${VERSION}-sources.jar" "${ARTIFACT}-${VERSION}-javadoc.jar"; do
cp "${DIR}/target/${f}" "${OUT}/"
cp "${DIR}/target/${f}.asc" "${OUT}/" # signatures from maven-gpg-plugin
done

# Sign the (flattened) POM, which target/*.asc does not cover.
gpg --batch --yes --pinentry-mode loopback --passphrase "${GPG_PASSPHRASE}" \
-u "${GPG_KEYNAME}" --armor --detach-sign "${OUT}/${ARTIFACT}-${VERSION}.pom"

( cd "$OUT"
for file in *.jar *.pom; do
md5sum "$file" | cut -d' ' -f1 > "${file}.md5"
sha1sum "$file" | cut -d' ' -f1 > "${file}.sha1"
done )
done

( cd bundle && zip -qr ../bundle.zip . )
echo "Bundle contents:"; unzip -l bundle.zip

- name: Upload and release to Maven Central
id: release
run: |
set -euo pipefail
PORTAL="https://central.sonatype.com/api/v1/publisher"
AUTH="Bearer $(printf '%s:%s' "$OSSRH_USERNAME" "$OSSRH_PASSWORD" | base64 | tr -d '\n')"

# Upload the bundle; AUTOMATIC releases to Central after validation passes.
# Upload returns 201 with the deployment id as the plain-text body.
RESP="$(curl -s --fail-with-body -H "Authorization: $AUTH" \
--form bundle=@bundle.zip "${PORTAL}/upload?publishingType=AUTOMATIC")"
DEPLOYMENT_ID="$(echo "$RESP" | tail -n1)"
echo "Deployment: $DEPLOYMENT_ID"
# Guard against a malformed upload response (empty, whitespace, or an error
# body) rather than assuming a specific id format. The status poll below is
# the real validator of the id.
if [ -z "${DEPLOYMENT_ID// }" ] || echo "$DEPLOYMENT_ID" | grep -q '[[:space:]<]'; then
echo "ERROR: upload did not return a usable deployment id. Response was:"
echo "$RESP"
exit 1
fi

# Confirm it reaches PUBLISHED. Fail fast on FAILED; tolerate transient
# unknown states but give up if they persist (likely a bad id or API outage).
unknown=0
for i in $(seq 1 40); do
Comment thread
coderabbitai[bot] marked this conversation as resolved.
STATE="$(curl -s -X POST -H "Authorization: $AUTH" "${PORTAL}/status?id=${DEPLOYMENT_ID}" | jq -r '.deploymentState')"
echo " [$i] $STATE"
case "$STATE" in
PUBLISHED) echo "Released to Maven Central."; exit 0 ;;
FAILED) echo "ERROR: deployment FAILED."; exit 1 ;;
PENDING|VALIDATING|VALIDATED|PUBLISHING) unknown=0 ;;
*) unknown=$((unknown+1));
[ "$unknown" -ge 3 ] && { echo "ERROR: repeated unexpected state '$STATE' (bad id or API error)."; exit 1; } ;;
esac
sleep 20
done
echo "ERROR: timed out (last: $STATE)."
exit 1
Loading