From ae4f3ebcf53debd749a8cac65600aba92ecdf492 Mon Sep 17 00:00:00 2001 From: Chao Wang Date: Mon, 17 Nov 2025 20:50:27 +0800 Subject: [PATCH] build: publish to maven central --- .github/workflows/maven/settings.xml | 14 ++ .github/workflows/release.yml | 198 +++++++++++++++------------ .github/workflows/stage.yml | 143 ------------------- CONTRIBUTING.md | 4 +- README.md | 15 ++ pom.xml | 86 ++++++++---- 6 files changed, 199 insertions(+), 261 deletions(-) create mode 100644 .github/workflows/maven/settings.xml delete mode 100644 .github/workflows/stage.yml diff --git a/.github/workflows/maven/settings.xml b/.github/workflows/maven/settings.xml new file mode 100644 index 00000000..a88734cf --- /dev/null +++ b/.github/workflows/maven/settings.xml @@ -0,0 +1,14 @@ + + + + + central + ${env.MAVEN_CENTRAL_USERNAME} + ${env.MAVEN_CENTRAL_TOKEN} + + + + \ No newline at end of file diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index d81b0226..8b3d690a 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -3,6 +3,11 @@ name: Release Version on: workflow_dispatch: + inputs: + version: + description: 'Custom version (optional)' + required: false + type: string pull_request_target: types: - closed @@ -14,123 +19,134 @@ on: - 'main' jobs: - deploy: + release: + if: github.repository_owner == 'guacsec' runs-on: ubuntu-latest - name: Deploy release - environment: staging -# only trigger the workflow on the base repository and if the merged branch name starts with release. - if: (github.repository_owner == 'guacsec' && github.event.pull_request.merged == true && startsWith(github.head_ref, 'release/') ) || (github.repository_owner == 'guacsec' && github.ref_name == 'main' && contains(github.event.commits[0].message, 'release/directly')) - outputs: - project_version: ${{ steps.project.outputs.version }} - last_release_tag: ${{ steps.last-release.outputs.tag-name }} + permissions: + contents: write + pull-requests: write + id-token: write steps: - - name: Checkout sources - uses: actions/checkout@v3 - with: - ssh-key: ${{ secrets.GITHUB_TOKEN }} - fetch-depth: 0 - + - name: Checkout + uses: actions/checkout@v5 - - name: Setup Java 17 - uses: actions/setup-java@v4 + - name: Set up Java 17 + uses: actions/setup-java@v5 with: - distribution: temurin - java-version: 17 - cache: maven + java-version: '17' + distribution: 'temurin' + cache: 'maven' + gpg-private-key: ${{ secrets.GPG_PRIVATE_KEY }} + gpg-passphrase: GPG_PASSPHRASE + env: + GPG_PASSPHRASE: ${{ secrets.GPG_PASSPHRASE }} - - name: create ssh agent - uses: webfactory/ssh-agent@v0.7.0 - with: - ssh-private-key: ${{ secrets.GITHUB_TOKEN }} + - name: Import GPG key for Maven + run: | + mkdir -p ~/.gnupg + echo "${{ secrets.GPG_PRIVATE_KEY }}" | gpg --batch --import + env: + GPG_PASSPHRASE: ${{ secrets.GPG_PASSPHRASE }} - name: Configure git run: | git config user.name "${{ github.actor }}" git config user.email "${{ github.actor }}@users.noreply.github.com" - - name: get previous released annotated tag id: last-release run: | echo "tag-name=$(git describe | awk -F '-' '{print $1}')" >> "$GITHUB_OUTPUT" - - name: Deploy release to GitHub - run: | - mvn release:prepare release:perform -B -ff + - name: Set version + if: github.event.inputs.version != '' + run: mvn -B versions:set -DnewVersion=${{ github.event.inputs.version }} -DgenerateBackupPoms=false - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + - name: Remove snapshot + if: github.event.inputs.version == '' + run: mvn -B versions:set -DremoveSnapshot -DgenerateBackupPoms=false - - name: Get pom version of released artifact - id: project + - name: Get version + id: get_version run: | - git checkout HEAD^ pom.xml echo "version=$(mvn help:evaluate -Dexpression=project.version -q -DforceStdout)" >> "$GITHUB_OUTPUT" - git restore pom.xml --staged --worktree + - name: Check if Maven artifact version exists + id: check_maven + run: | + VERSION="${{ steps.get_version.outputs.version }}" + GROUP_ID="io.github.guacsec" + ARTIFACT_ID="trustify-da-java-client" + echo "Checking if Maven artifact $GROUP_ID:$ARTIFACT_ID:$VERSION exists..." + + # Check Maven Central for the artifact + HTTP_CODE=$(curl -s -o /dev/null -w "%{http_code}" "https://repo1.maven.org/maven2/io/github/guacsec/trustify-da-java-client/$VERSION/trustify-da-java-client-$VERSION.pom") + if [ "$HTTP_CODE" = "200" ]; then + echo "maven_exists=true" >> $GITHUB_OUTPUT + echo "Maven artifact $GROUP_ID:$ARTIFACT_ID:$VERSION already exists, skipping Maven publish" + else + echo "maven_exists=false" >> $GITHUB_OUTPUT + echo "Maven artifact $GROUP_ID:$ARTIFACT_ID:$VERSION does not exist (HTTP $HTTP_CODE), will publish" + fi + continue-on-error: true + + - name: Show artifact check results + run: | + echo "=== Artifact Check Results ===" + echo "Maven artifact exists: ${{ steps.check_maven.outputs.maven_exists }}" + echo "Will publish to Maven Central: ${{ steps.check_maven.outputs.maven_exists == 'false' }}" - release: - runs-on: ubuntu-latest - name: Release - if: (github.repository_owner == 'guacsec' && startsWith(github.head_ref, 'release/')) || (github.repository_owner == 'guacsec' && github.ref_name == 'main' && contains(github.event.commits[0].message, 'release/directly')) - environment: staging - needs: deploy - steps: + - name: Compute Maven profiles + id: compute_profiles + run: | + PROFILES="gpg-sign" + if [ "${{ steps.check_maven.outputs.maven_exists }}" = "false" ]; then + PROFILES="${PROFILES},publish-maven" + fi + echo "profiles=$PROFILES" >> $GITHUB_OUTPUT + + - name: Build and publish to Maven Central + if: steps.check_maven.outputs.maven_exists == 'false' + run: | + mvn -B deploy -P${{ steps.compute_profiles.outputs.profiles }} --settings .github/workflows/maven/settings.xml - - name: Create release notes for ${{ needs.deploy.outputs.project_version }} release - uses: actions/github-script@v7 - id: release-notes - with: - github-token: ${{ secrets.GITHUB_TOKEN }} - script: | - const repo_name = context.payload.repository.full_name - const response = await github.request('POST /repos/' + repo_name + '/releases' + '/generate-notes', { - tag_name: '${{ needs.deploy.outputs.project_version }}', - previous_tag_name: '${{ needs.deploy.outputs.last_release_tag }}' - }) - return response.data.body - - - name: Create new ${{ needs.deploy.outputs.project_version }} release - uses: actions/github-script@v7 - with: - github-token: ${{ secrets.GITHUB_TOKEN }} - script: | - const repo_name = context.payload.repository.full_name - const response = await github.request('POST /repos/' + repo_name + '/releases', { - tag_name: '${{ needs.deploy.outputs.project_version }}', - name: '${{ needs.deploy.outputs.project_version }}', - body: ${{ steps.release-notes.outputs.result }}, - draft: false, - prerelease: false, - make_latest: 'true' - }) - - - name: Checkout sources - uses: actions/checkout@v3 - with: - ssh-key: ${{ secrets.GITHUB_TOKEN }} - fetch-depth: 0 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + MAVEN_GPG_PASSPHRASE: ${{ secrets.GPG_PASSPHRASE }} + MAVEN_CENTRAL_USERNAME: ${{ secrets.MAVEN_CENTRAL_USERNAME }} + MAVEN_CENTRAL_TOKEN: ${{ secrets.MAVEN_CENTRAL_TOKEN }} - - name: Configure git + - name: Skip publishing - artifact already exists + if: steps.check_maven.outputs.maven_exists == 'true' run: | - git config user.name "${{ github.actor }}" - git config user.email "${{ github.actor }}@users.noreply.github.com" + echo "Maven artifact already exists, skipping publish step" + echo "Maven exists: ${{ steps.check_maven.outputs.maven_exists }}" - - name: Get pom version of new snapshot artifact - id: project_snapshot - run: | - git pull - echo "version=$(mvn help:evaluate -Dexpression=project.version -q -DforceStdout)" >> "$GITHUB_OUTPUT" + - name: Create Release + id: create_release + uses: softprops/action-gh-release@v1 + with: + name: Release ${{ github.event.inputs.version || steps.get_version.outputs.version }} + tag_name: v${{ github.event.inputs.version || steps.get_version.outputs.version }} + generate_release_notes: true + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + - name: Update to next version + if: success() + run: | + mvn -B release:update-versions + # Run the phase that triggers README.md update + mvn -B validate - - name: Update readme usage section - run: > - sed -i - 's/.*<\/version>/${{ steps.project_snapshot.outputs.version }}<\/version>/g' - README.md + - name: Create Pull Request with next version + id: cpr + uses: peter-evans/create-pull-request@v5 + with: + commit-message: "build(release): update to next development version" + branch: chore/bump-version + title: "chore: bump to next development version" + signoff: true + body: | + This PR updates the project to the next development version after the release. - - name: Push modifications - run: | - git add README.md - git commit -m "docs: updated usage section with version ${{ steps.project_snapshot.outputs.version }} [skip ci]" - git push diff --git a/.github/workflows/stage.yml b/.github/workflows/stage.yml deleted file mode 100644 index 66a99cd4..00000000 --- a/.github/workflows/stage.yml +++ /dev/null @@ -1,143 +0,0 @@ ---- -name: Stage - -on: - pull_request_target: - types: - - closed - branches: - - main - paths: - - "src/main/**" - - "pom.xml" - - ".github/workflows/**" - -jobs: - deploy: - runs-on: ubuntu-latest - name: Deploy snapshot - env: - RUN_PYTHON_BIN: ${{ vars.RUN_PYTHON_BIN }} - if: github.repository_owner == 'guacsec' && github.event.pull_request.merged == true && !startsWith(github.head_ref, 'release/') - outputs: - project_version: ${{ steps.project.outputs.version }} - steps: - - name: Checkout sources - uses: actions/checkout@v3 - - - name: Setup Java 17 - uses: actions/setup-java@v4 - with: - distribution: temurin - java-version: 17 - cache: maven - - - name: Get pom specs - id: project - run: | - echo "version=$(mvn help:evaluate -Dexpression=project.version -q -DforceStdout)" >> "$GITHUB_OUTPUT" - - - name: Deploy snapshot to GitHub - if: | - contains(steps.project.outputs.version, 'SNAPSHOT') && - github.repository == 'guacsec/trustify-da-java-client' - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - run: mvn deploy -Pprepare-deployment,deploy-github -B -ff -DskipTests=true -Dskip.junit_platform=true - - release: - runs-on: ubuntu-latest - name: Release snapshot - environment: staging - needs: deploy - if: | - contains(needs.deploy.outputs.project_version, 'SNAPSHOT') && - github.repository_owner == 'guacsec' && github.event.pull_request.merged == true && !startsWith(github.head_ref, 'release/') - steps: - - name: Check for existing ${{ needs.deploy.outputs.project_version }} release - id: existing_release - uses: actions/github-script@v7 - env: - PROJECT_VERSION: ${{ needs.deploy.outputs.project_version }} - continue-on-error: true - with: - github-token: ${{ secrets.GITHUB_TOKEN }} - script: | - const repo = context.repo; - const tag = process.env.PROJECT_VERSION; - try { - const response = await github.rest.repos.getReleaseByTag({ - owner: repo.owner, - repo: repo.repo, - tag: tag - }); - core.setOutput('id', response.data.id); - } catch (error) { - if (error.status === 404) { - core.info(`Release for tag '${tag}' not found.`); - } else { - throw error; - } - } - - - name: Checkout sources - uses: actions/checkout@v3 - - - name: Delete ${{ needs.deploy.outputs.project_version }} release if exists - if: ${{ steps.existing_release.outputs.id }} - uses: actions/github-script@v6 - env: - RELEASE_ID: ${{ steps.existing_release.outputs.id }} - with: - github-token: ${{ secrets.GITHUB_TOKEN }} - script: | - const repo = context.repo; - const releaseId = process.env.RELEASE_ID; - - try { - console.log(`Deleting release ID: ${releaseId}`); - await github.rest.repos.deleteRelease({ - owner: repo.owner, - repo: repo.repo, - release_id: releaseId - }); - console.log(`Deleted release ID: ${releaseId}`); - } catch (error) { - if (error.status === 404) { - console.log(`Release ID: ${releaseId} not found. Skipping deletion.`); - } else { - throw error; - } - } - - name: Delete ${{ needs.deploy.outputs.project_version }} tag if exists - continue-on-error: true - run: git push --delete origin ${{ needs.deploy.outputs.project_version }} - - # Workaround for GitHub release cache issue — avoids ghost "draft" releases - - name: Sleep to allow release deletion to propagate - run: sleep 5 - - - name: Create new ${{ needs.deploy.outputs.project_version }} release - uses: actions/github-script@v7 - env: - PROJECT_VERSION: ${{ needs.deploy.outputs.project_version }} - with: - github-token: ${{ secrets.GITHUB_TOKEN }} - script: | - const repo = context.repo; - const tag = process.env.PROJECT_VERSION; - - console.log(`Creating release for tag: ${tag}`); - - const response = await github.rest.repos.createRelease({ - owner: repo.owner, - repo: repo.repo, - tag_name: tag, - name: tag, - draft: false, - prerelease: true, - generate_release_notes: true, - make_latest: 'false', - }); - - console.log(`Release created: ${response.data.html_url}`); diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index f0465012..15de1ed7 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -32,8 +32,8 @@ > The following are used strictly by CI workflows. Use `mvn install` to install the module locally. * `prepare-deployment` - use this profile for packaging of jars to deploy to artifact repository, it will create a *flatten pom* and a *sources*, and *javadoc* *jars* -* `deploy-github` - use this profile to include github registry distribution definition, - used for deploying and releasing +* `gpg-sign` - use this profile to GPG sign artifacts for Maven Central publishing +* `publish-maven` - use this profile to publish to Maven Central ### Good to know diff --git a/README.md b/README.md index 676bf4ad..c794da02 100644 --- a/README.md +++ b/README.md @@ -696,6 +696,21 @@ Customize image analysis optionally by using *Environment Variables* or *Java Pr | TRUSTIFY_DA_IMAGE_ARCH | Default Architecture used for multi-arch images when `TRUSTIFY_DA_IMAGE_PLATFORM` is not set | | | TRUSTIFY_DA_IMAGE_VARIANT | Default Variant used for multi-arch images when `TRUSTIFY_DA_IMAGE_PLATFORM` is not set | | +### Releases + +To create a new release: + +1. **Trigger Release Workflow**: Go to Actions → "Release Version" → "Run workflow" +2. **Choose Version**: + - Leave version empty to automatically release current snapshot (e.g., `0.0.9-SNAPSHOT` → `0.0.9`) + - Or specify custom version (e.g., `1.0.0`) +3. **Automatic Process**: The workflow will: + - Publish to Maven Central + - Create GitHub release with auto-generated notes + - Bump to next development version via pull request + +Released artifacts are available on [Maven Central](https://repo1.maven.org/maven2/io/github/guacsec/trustify-da-java-client/). + ### Known Issues - For pip requirements.txt - It's been observed that for python versions 3.11.x, there might be slowness for invoking the analysis. diff --git a/pom.xml b/pom.xml index e2ded2e2..760c9068 100644 --- a/pom.xml +++ b/pom.xml @@ -57,6 +57,7 @@ 2.15.0 3.5.3 2.44.4 + 0.9.0 @@ -103,6 +104,19 @@ https://github.com/guacsec/trustify-da-java-client/actions + + + central + Maven Central + https://central.sonatype.com/repository/maven-releases/ + + + central + Maven Central Snapshots + https://central.sonatype.com/repository/maven-snapshots/ + + + @@ -307,7 +321,7 @@ maven-release-plugin ${maven-release-plugin.version} - -DskipTests=true -Dskip.junit_platform=true -Pprepare-deployment,deploy-github + -DskipTests=true -Dskip.junit_platform=true -Pprepare-deployment v@{project.version} Build (Release): [skip ci] @@ -884,30 +898,6 @@ limitations under the License.]]> - - - - deploy-github - - - github - https://maven.pkg.github.com/guacsec/trustify-da-java-client - - - github - https://maven.pkg.github.com/guacsec/trustify-da-java-client - - - - - - de.sormuras.junit - junit-platform-maven-plugin - - - - - prepare-deployment @@ -953,5 +943,51 @@ limitations under the License.]]> + + + gpg-sign + + + + maven-gpg-plugin + + + sign-artifacts + verify + + sign + + + + --pinentry-mode + loopback + + + + + + + + + + + + publish-maven + + + + org.sonatype.central + central-publishing-maven-plugin + ${central-publishing-maven-plugin.version} + true + + central + true + + + + + +