248 lines
8.6 KiB
YAML
248 lines
8.6 KiB
YAML
name: Publish Snapshot to Maven Central
|
|
permissions:
|
|
contents: read
|
|
|
|
on:
|
|
push:
|
|
branches:
|
|
- master
|
|
workflow_dispatch:
|
|
inputs:
|
|
sourceReleaseVersion:
|
|
description: "Optional release version that requested this snapshot publication."
|
|
required: false
|
|
type: string
|
|
sourceReleaseCommit:
|
|
description: "Optional release commit SHA that requested this snapshot publication."
|
|
required: false
|
|
type: string
|
|
dryRun:
|
|
description: "Run snapshot workflow in dry-run mode (build/test only; no deploy)."
|
|
required: false
|
|
default: true
|
|
type: boolean
|
|
|
|
concurrency:
|
|
group: snapshot-${{ github.repository }}
|
|
cancel-in-progress: true
|
|
|
|
jobs:
|
|
snapshot:
|
|
name: Publish Snapshot to Maven Central
|
|
runs-on: ubuntu-latest
|
|
|
|
steps:
|
|
- name: Checkout repository
|
|
uses: actions/checkout@v6
|
|
|
|
- name: Normalize dry run input
|
|
id: dry_run
|
|
run: |
|
|
if [ "${GITHUB_EVENT_NAME}" = "workflow_dispatch" ]; then
|
|
raw="${{ github.event.inputs.dryRun }}"
|
|
if [ -z "$raw" ]; then
|
|
raw=true
|
|
fi
|
|
normalized=$(printf '%s' "$raw" | tr '[:upper:]' '[:lower:]')
|
|
|
|
if [ "$normalized" = "false" ] || [ "$normalized" = "0" ] || [ "$normalized" = "no" ]; then
|
|
dry_run=false
|
|
else
|
|
dry_run=true
|
|
fi
|
|
else
|
|
raw=""
|
|
dry_run=false
|
|
fi
|
|
|
|
echo "audit:dry_run_input_raw='${raw}'"
|
|
echo "audit:dry_run_normalized=$dry_run"
|
|
echo "dryRun=$dry_run" >> $GITHUB_OUTPUT
|
|
|
|
- name: Read snapshot version
|
|
id: version
|
|
run: |
|
|
set -euo pipefail
|
|
snapshot_version=$(python3 - <<'PY'
|
|
import sys
|
|
import xml.etree.ElementTree as ET
|
|
|
|
def find_version(path):
|
|
tree = ET.parse(path)
|
|
root = tree.getroot()
|
|
def find_first(elem):
|
|
if elem is None:
|
|
return None
|
|
for child in elem:
|
|
if child.tag.endswith("version"):
|
|
text = (child.text or "").strip()
|
|
if text:
|
|
return text
|
|
return None
|
|
return find_first(root) or find_first(root.find("./{*}parent"))
|
|
|
|
ver = find_version("pom.xml")
|
|
if not ver:
|
|
sys.exit(1)
|
|
print(ver)
|
|
PY
|
|
) || { echo "Could not read pom.xml version" >&2; exit 1; }
|
|
echo "audit:snapshot_version=${snapshot_version}"
|
|
echo "snapshotVersion=${snapshot_version}" >> "$GITHUB_OUTPUT"
|
|
|
|
- name: Cache Maven packages
|
|
uses: actions/cache@v5
|
|
with:
|
|
path: ~/.m2
|
|
key: ${{ runner.os }}-maven-${{ hashFiles('**/pom.xml') }}
|
|
restore-keys: |
|
|
${{ runner.os }}-maven-
|
|
|
|
- name: Set up JDK 25 (dry-run without publishing secrets)
|
|
if: steps.dry_run.outputs.dryRun == 'true'
|
|
uses: actions/setup-java@v5
|
|
with:
|
|
distribution: temurin
|
|
java-version: 25
|
|
cache: maven
|
|
|
|
- name: Set up JDK 25 with publishing secrets
|
|
if: steps.dry_run.outputs.dryRun != 'true'
|
|
uses: actions/setup-java@v5
|
|
with:
|
|
distribution: temurin
|
|
java-version: 25
|
|
cache: maven
|
|
server-id: central
|
|
server-username: ${{ secrets.MAVEN_CENTRAL_TOKEN_USER }}
|
|
server-password: ${{ secrets.MAVEN_CENTRAL_TOKEN_PASS }}
|
|
gpg-private-key: ${{ secrets.GPG_PRIVATE_KEY }}
|
|
gpg-passphrase: ${{ secrets.GPG_PASSPHRASE }}
|
|
|
|
- name: Write Maven settings
|
|
if: steps.dry_run.outputs.dryRun != 'true'
|
|
run: |
|
|
echo "::group::Write Maven settings"
|
|
mkdir -p ~/.m2
|
|
printf '%s\n' \
|
|
'<settings>' \
|
|
' <servers>' \
|
|
' <server>' \
|
|
' <id>central</id>' \
|
|
" <username>${{ secrets.MAVEN_CENTRAL_TOKEN_USER }}</username>" \
|
|
" <password>${{ secrets.MAVEN_CENTRAL_TOKEN_PASS }}</password>" \
|
|
' </server>' \
|
|
' </servers>' \
|
|
'</settings>' > ~/.m2/settings.xml
|
|
echo "settings.xml created"
|
|
echo "::endgroup::"
|
|
|
|
- name: Write Maven security settings
|
|
if: steps.dry_run.outputs.dryRun != 'true'
|
|
shell: bash
|
|
env:
|
|
MAVEN_SECURITY_MASTER: ${{ secrets.MAVEN_MASTER_PASSPHRASE }}
|
|
run: |
|
|
set -euo pipefail
|
|
if [[ -z "${MAVEN_SECURITY_MASTER:-}" ]]; then
|
|
echo "MAVEN_MASTER_PASSPHRASE not set; skipping settings-security.xml generation"
|
|
exit 0
|
|
fi
|
|
mkdir -p ~/.m2
|
|
printf '%s\n' \
|
|
'<?xml version="1.0" encoding="UTF-8"?>' \
|
|
'<settingsSecurity>' \
|
|
" <master>${MAVEN_SECURITY_MASTER}</master>" \
|
|
'</settingsSecurity>' > ~/.m2/settings-security.xml
|
|
chmod 600 ~/.m2/settings-security.xml
|
|
echo "settings-security.xml created"
|
|
|
|
- name: Configure GPG for signing
|
|
if: steps.dry_run.outputs.dryRun != 'true'
|
|
shell: bash
|
|
run: |
|
|
set -euo pipefail
|
|
mkdir -p ~/.gnupg
|
|
chmod 700 ~/.gnupg
|
|
echo "pinentry-mode loopback" >> ~/.gnupg/gpg.conf
|
|
chmod 600 ~/.gnupg/gpg.conf
|
|
touch ~/.gnupg/gpg-agent.conf
|
|
echo "allow-loopback-pinentry" >> ~/.gnupg/gpg-agent.conf
|
|
chmod 600 ~/.gnupg/gpg-agent.conf
|
|
gpgconf --kill gpg-agent 2>/dev/null || true
|
|
gpgconf --launch gpg-agent 2>/dev/null || true
|
|
echo "gpg.conf and gpg-agent.conf created"
|
|
|
|
- name: Dry-run mode notice
|
|
if: steps.dry_run.outputs.dryRun == 'true'
|
|
run: |
|
|
echo "DRY RUN ENABLED: running snapshot build/test only; Maven Central deployment is skipped."
|
|
|
|
- name: Build and Test
|
|
run: |
|
|
set -euo pipefail
|
|
echo "::group::Build and test snapshot candidate"
|
|
mvn -B clean test
|
|
echo "::endgroup::"
|
|
|
|
- name: Deploy Snapshot
|
|
if: steps.dry_run.outputs.dryRun != 'true'
|
|
env:
|
|
MAVEN_GPG_PASSPHRASE: ${{ secrets.GPG_PASSPHRASE }}
|
|
GPG_PASSPHRASE: ${{ secrets.GPG_PASSPHRASE }}
|
|
run: |
|
|
set -euo pipefail
|
|
echo "::group::Deploy snapshot to Maven Central"
|
|
mvn -B deploy -Psign-snapshots 2>&1 | tee snapshot-deploy.log
|
|
echo "::endgroup::"
|
|
|
|
- name: Snapshot publication summary
|
|
if: always()
|
|
env:
|
|
DRY_RUN: ${{ steps.dry_run.outputs.dryRun }}
|
|
SNAPSHOT_VERSION: ${{ steps.version.outputs.snapshotVersion }}
|
|
SOURCE_RELEASE_VERSION: ${{ github.event.inputs.sourceReleaseVersion }}
|
|
SOURCE_RELEASE_COMMIT: ${{ github.event.inputs.sourceReleaseCommit }}
|
|
run: |
|
|
if [ "${DRY_RUN:-false}" = "true" ]; then
|
|
mutation_plan="would configure publishing secrets and deploy the snapshot to Maven Central"
|
|
rerun_guidance="rerun snapshot.yml with dryRun=false, or let the master push/publish handoff path run"
|
|
else
|
|
mutation_plan="completed snapshot deployment"
|
|
rerun_guidance="not needed for this non-dry-run snapshot run"
|
|
fi
|
|
cat > snapshot-audit.json <<EOF
|
|
{
|
|
"event": "${GITHUB_EVENT_NAME}",
|
|
"dryRun": "${DRY_RUN:-}",
|
|
"snapshotVersion": "${SNAPSHOT_VERSION:-}",
|
|
"sourceReleaseVersion": "${SOURCE_RELEASE_VERSION:-}",
|
|
"sourceReleaseCommit": "${SOURCE_RELEASE_COMMIT:-}",
|
|
"mutationPlan": "${mutation_plan}",
|
|
"rerunGuidance": "${rerun_guidance}",
|
|
"runUrl": "https://github.com/${GITHUB_REPOSITORY}/actions/runs/${GITHUB_RUN_ID}"
|
|
}
|
|
EOF
|
|
{
|
|
echo "## Snapshot Publication"
|
|
echo ""
|
|
echo "- event: ${GITHUB_EVENT_NAME}"
|
|
echo "- dry run: ${DRY_RUN:-unknown}"
|
|
echo "- snapshot version: ${SNAPSHOT_VERSION:-unknown}"
|
|
echo "- source release version: ${SOURCE_RELEASE_VERSION:-none}"
|
|
echo "- source release commit: ${SOURCE_RELEASE_COMMIT:-none}"
|
|
echo "- mutation plan: ${mutation_plan}"
|
|
echo "- rerun guidance: ${rerun_guidance}"
|
|
} >> "$GITHUB_STEP_SUMMARY"
|
|
|
|
- name: Upload snapshot audit artifacts
|
|
if: always()
|
|
uses: actions/upload-artifact@v7
|
|
with:
|
|
name: snapshot-audit-${{ github.run_id }}
|
|
path: |
|
|
snapshot-audit.json
|
|
snapshot-deploy.log
|
|
if-no-files-found: ignore
|
|
retention-days: 14
|