From dcfbc30f5e9d36c4661023c260a1d0d45625665c Mon Sep 17 00:00:00 2001 From: Nano Taboada <87288+nanotaboada@users.noreply.github.com> Date: Sat, 28 Mar 2026 22:43:06 -0300 Subject: [PATCH 1/3] ci(release): implement named releases with tag-based deployment (#252) Co-authored-by: Claude Sonnet 4.6 --- .github/workflows/maven-cd.yml | 140 ++++++++++++++++++ .github/workflows/{maven.yml => maven-ci.yml} | 38 +---- CHANGELOG.md | 47 ++++++ README.md | 89 ++++++++++- 4 files changed, 276 insertions(+), 38 deletions(-) create mode 100644 .github/workflows/maven-cd.yml rename .github/workflows/{maven.yml => maven-ci.yml} (55%) create mode 100644 CHANGELOG.md diff --git a/.github/workflows/maven-cd.yml b/.github/workflows/maven-cd.yml new file mode 100644 index 0000000..00cb9c9 --- /dev/null +++ b/.github/workflows/maven-cd.yml @@ -0,0 +1,140 @@ +# Deploying Java with Maven to GitHub Container Registry +# https://docs.github.com/en/actions/use-cases-and-examples/publishing-packages/publishing-docker-images + +name: Java CD + +on: + push: + tags: + - "v*.*.*-*" + +env: + JAVA_VERSION: 25 + +jobs: + test: + runs-on: ubuntu-latest + permissions: + contents: read + steps: + - name: Checkout repository + uses: actions/checkout@v6 + + - name: Set up OpenJDK ${{ env.JAVA_VERSION }} + uses: actions/setup-java@v5.2.0 + with: + java-version: ${{ env.JAVA_VERSION }} + distribution: "temurin" + cache: "maven" + + - name: Compile and verify with Maven + run: ./mvnw clean verify + + release: + needs: test + runs-on: ubuntu-latest + permissions: + contents: write + packages: write + + steps: + - name: Checkout repository + uses: actions/checkout@v6 + with: + fetch-depth: 0 + + - name: Extract and validate tag components + id: tag + run: | + TAG="${GITHUB_REF#refs/tags/}" + echo "Full tag: $TAG" + + SEMVER=$(echo "$TAG" | sed -E 's/^v([0-9]+\.[0-9]+\.[0-9]+)-.+$/\1/') + CLUB=$(echo "$TAG" | sed -E 's/^v[0-9]+\.[0-9]+\.[0-9]+-(.+)$/\1/') + + VALID_CLUBS="arsenal barcelona chelsea dortmund everton flamengo galatasaray hamburg inter juventus kaiserslautern liverpool manchesterutd napoli olympique psg qpr realmadrid sevilla tottenham union valencia werder xerez youngboys zenit" + + if ! echo "$SEMVER" | grep -qE '^[0-9]+\.[0-9]+\.[0-9]+$'; then + echo "โŒ Invalid semver: $SEMVER" + exit 1 + fi + + if ! echo "$VALID_CLUBS" | grep -qw "$CLUB"; then + echo "โŒ Invalid club name: $CLUB" + echo "Valid clubs: $VALID_CLUBS" + exit 1 + fi + + echo "semver=$SEMVER" >> "$GITHUB_OUTPUT" + echo "club=$CLUB" >> "$GITHUB_OUTPUT" + echo "โœ… Tag: v$SEMVER - $CLUB" + + - name: Log in to GitHub Container Registry + uses: docker/login-action@v4.0.0 + with: + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v4.0.0 + + - name: Build and push Docker image to GitHub Container Registry + uses: docker/build-push-action@v7.0.0 + with: + context: . + push: true + platforms: linux/amd64,linux/arm64 + provenance: false + cache-from: type=gha + cache-to: type=gha,mode=max + tags: | + ghcr.io/${{ github.repository }}:latest + ghcr.io/${{ github.repository }}:${{ steps.tag.outputs.semver }} + ghcr.io/${{ github.repository }}:${{ steps.tag.outputs.club }} + + - name: Generate changelog + id: changelog + run: | + PREVIOUS_TAG=$(git tag --sort=-version:refname | grep -v "^${GITHUB_REF#refs/tags/}$" | head -n 1) + if [ -n "$PREVIOUS_TAG" ]; then + CHANGELOG=$(git log "$PREVIOUS_TAG"..HEAD --pretty=format:"- %s" --no-merges) + else + CHANGELOG=$(git log --pretty=format:"- %s" --no-merges) + fi + { + echo "content<> "$GITHUB_OUTPUT" + + - name: Create GitHub Release + uses: softprops/action-gh-release@v2.6.1 + with: + name: "v${{ steps.tag.outputs.semver }} - ${{ steps.tag.outputs.club }} ๐Ÿ†" + body: | + ## What's Changed + + ${{ steps.changelog.outputs.content }} + + ## Docker + + ```bash + # By semantic version (recommended) + docker pull ghcr.io/${{ github.repository }}:${{ steps.tag.outputs.semver }} + + # By club name + docker pull ghcr.io/${{ github.repository }}:${{ steps.tag.outputs.club }} + + # Latest + docker pull ghcr.io/${{ github.repository }}:latest + ``` + + ## Quick Start + + ```bash + docker run -p 9000:9000 ghcr.io/${{ github.repository }}:${{ steps.tag.outputs.semver }} + ``` + + API available at `http://localhost:9000` ยท Swagger UI at `http://localhost:9000/swagger/index.html` + generate_release_notes: false diff --git a/.github/workflows/maven.yml b/.github/workflows/maven-ci.yml similarity index 55% rename from .github/workflows/maven.yml rename to .github/workflows/maven-ci.yml index dabf6e0..34eba87 100644 --- a/.github/workflows/maven.yml +++ b/.github/workflows/maven-ci.yml @@ -29,7 +29,7 @@ jobs: cache: "maven" - name: Compile and verify with Maven - run: mvn verify --file pom.xml + run: ./mvnw clean verify - name: Upload JaCoCo coverage report artifact uses: actions/upload-artifact@v7.0.0 @@ -56,39 +56,3 @@ jobs: with: token: ${{ secrets.CODECOV_TOKEN }} files: jacoco.xml - - container: - needs: coverage - runs-on: ubuntu-latest - if: ${{ github.event_name == 'push' && github.ref == 'refs/heads/master' }} - - permissions: - contents: read - packages: write - - steps: - - name: Checkout repository - uses: actions/checkout@v6 - - - name: Log in to GitHub Container Registry - uses: docker/login-action@v4.0.0 - with: - registry: ghcr.io - username: ${{ github.actor }} - password: ${{ secrets.GITHUB_TOKEN }} - - - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v4.0.0 - - - name: Build and push Docker image to GitHub Container Registry - uses: docker/build-push-action@v7.0.0 - with: - context: . - push: true - platforms: linux/amd64 - provenance: false - cache-from: type=gha - cache-to: type=gha,mode=max - tags: | - ghcr.io/${{ github.repository }}:latest - ghcr.io/${{ github.repository }}:sha-${{ github.sha }} diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..632be5c --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,47 @@ +# Changelog + +All notable changes to this project will be documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), +and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +Release names follow the **historic football clubs** naming convention (Aโ€“Z): + +| Tag | Club | Country | Founded | +| --- | ---- | ------- | ------- | +| `arsenal` | Arsenal | England | 1886 | +| `barcelona` | Barcelona | Spain | 1899 | +| `chelsea` | Chelsea | England | 1905 | +| `dortmund` | Borussia Dortmund | Germany | 1909 | +| `everton` | Everton | England | 1878 | +| `flamengo` | Flamengo | Brazil | 1895 | +| `galatasaray` | Galatasaray | Turkey | 1905 | +| `hamburg` | Hamburg SV | Germany | 1887 | +| `inter` | Internazionale | Italy | 1908 | +| `juventus` | Juventus | Italy | 1897 | +| `kaiserslautern` | Kaiserslautern | Germany | 1900 | +| `liverpool` | Liverpool | England | 1892 | +| `manchesterutd` | Manchester United | England | 1878 | +| `napoli` | Napoli | Italy | 1926 | +| `olympique` | Olympique Marseille | France | 1899 | +| `psg` | Paris Saint-Germain | France | 1970 | +| `qpr` | Queens Park Rangers | England | 1882 | +| `realmadrid` | Real Madrid | Spain | 1902 | +| `sevilla` | Sevilla | Spain | 1890 | +| `tottenham` | Tottenham Hotspur | England | 1882 | +| `union` | Union Berlin | Germany | 1966 | +| `valencia` | Valencia | Spain | 1919 | +| `werder` | Werder Bremen | Germany | 1899 | +| `xerez` | Xerez CD | Spain | 1947 | +| `youngboys` | Young Boys | Switzerland | 1898 | +| `zenit` | Zenit | Russia | 1925 | + +--- + +## [Unreleased] + +### Added + +### Changed + +### Fixed diff --git a/README.md b/README.md index 046d4ec..58e30ea 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # ๐Ÿงช RESTful API with Java and Spring Boot -[![Java CI with Maven](https://github.com/nanotaboada/java.samples.spring.boot/actions/workflows/maven.yml/badge.svg)](https://github.com/nanotaboada/java.samples.spring.boot/actions/workflows/maven.yml) +[![Java CI with Maven](https://github.com/nanotaboada/java.samples.spring.boot/actions/workflows/maven-ci.yml/badge.svg)](https://github.com/nanotaboada/java.samples.spring.boot/actions/workflows/maven-ci.yml) [![CodeQL Advanced](https://github.com/nanotaboada/java.samples.spring.boot/actions/workflows/codeql.yml/badge.svg)](https://github.com/nanotaboada/java.samples.spring.boot/actions/workflows/codeql.yml) [![Quality Gate Status](https://sonarcloud.io/api/project_badges/measure?project=nanotaboada_java.samples.spring.boot&metric=alert_status)](https://sonarcloud.io/summary/new_code?id=nanotaboada_java.samples.spring.boot) [![codecov](https://codecov.io/gh/nanotaboada/java.samples.spring.boot/branch/master/graph/badge.svg?token=D3FMNG0WOI)](https://codecov.io/gh/nanotaboada/java.samples.spring.boot) @@ -32,6 +32,7 @@ Proof of Concept for a RESTful Web Service built with **Spring Boot 4** targetin - [Reset Database](#reset-database) - [Environment Variables](#environment-variables) - [Command Summary](#command-summary) +- [Releases](#releases) - [Contributing](#contributing) - [Legal](#legal) @@ -391,6 +392,92 @@ spring.jpa.hibernate.ddl-auto=create-drop > ๐Ÿ’ก **Note:** Always use the Maven wrapper (`./mvnw`) instead of system Maven to ensure consistent builds. +## Releases + +This project uses **historic football clubs** as release codenames ๐Ÿ† (inspired by Ubuntu, Android, and macOS naming conventions). + +### Release Naming Convention + +Releases follow the pattern: `v{SEMVER}-{CLUB}` (e.g., `v1.0.0-arsenal`) + +- **Semantic Version**: Standard versioning (MAJOR.MINOR.PATCH) +- **Club Name**: Alphabetically ordered codename from the [historic club list](CHANGELOG.md) + +### Create a Release + +To create a new release, follow this workflow: + +#### 1. Create a Release Branch + +Branch protection prevents direct pushes to `master`, so all release prep goes through a PR: + +```bash +git checkout master && git pull +git checkout -b release/v1.0.0-arsenal +``` + +#### 2. Update CHANGELOG.md + +Move items from `[Unreleased]` to a new release section in [CHANGELOG.md](CHANGELOG.md), then commit and push the branch: + +```bash +# Move items from [Unreleased] to new release section +# Example: [1.0.0 - Arsenal] - 2026-XX-XX +git add CHANGELOG.md +git commit -m "docs(changelog): prepare release notes for v1.0.0-arsenal" +git push origin release/v1.0.0-arsenal +``` + +#### 3. Merge the Release PR + +Open a pull request from `release/v1.0.0-arsenal` into `master` and merge it. The tag must be created **after** the merge so it points to the correct commit on `master`. + +#### 4. Create and Push Tag + +After the PR is merged, pull `master` and create the annotated tag: + +```bash +git checkout master && git pull +git tag -a v1.0.0-arsenal -m "Release 1.0.0 - Arsenal" +git push origin v1.0.0-arsenal +``` + +#### 5. Automated CD Workflow + +This triggers the CD workflow which automatically: + +1. Validates the club name +2. Builds and tests the project with Maven +3. Publishes Docker images to GitHub Container Registry with three tags +4. Creates a GitHub Release with auto-generated changelog from commits + +#### Pre-Release Checklist + +- [ ] Release branch created from `master` +- [ ] `CHANGELOG.md` updated with release notes +- [ ] Changes committed and pushed on the release branch +- [ ] Release PR merged into `master` +- [ ] Tag created with correct format: `vX.Y.Z-club` +- [ ] Club name is valid (A-Z from the [historic club list](CHANGELOG.md)) +- [ ] Tag pushed to trigger CD workflow + +### Pull Docker Images + +Each release publishes multiple tags for flexibility: + +```bash +# By semantic version (recommended for production) +docker pull ghcr.io/nanotaboada/java-samples-spring-boot:1.0.0 + +# By club name (memorable alternative) +docker pull ghcr.io/nanotaboada/java-samples-spring-boot:arsenal + +# Latest release +docker pull ghcr.io/nanotaboada/java-samples-spring-boot:latest +``` + +> ๐Ÿ’ก See [CHANGELOG.md](CHANGELOG.md) for the complete club list (A-Z) and release history. + ## Contributing Contributions are welcome! Please see [CONTRIBUTING.md](CONTRIBUTING.md) for details on: From e1d3848984b7ade1f5a727601b381fa78780e657 Mon Sep 17 00:00:00 2001 From: Nano Taboada <87288+nanotaboada@users.noreply.github.com> Date: Sat, 28 Mar 2026 22:53:13 -0300 Subject: [PATCH 2/3] fix(cd): ensure lowercase image name for GHCR compatibility Co-authored-by: Claude Sonnet 4.6 --- .github/workflows/maven-cd.yml | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/.github/workflows/maven-cd.yml b/.github/workflows/maven-cd.yml index 00cb9c9..fad16b8 100644 --- a/.github/workflows/maven-cd.yml +++ b/.github/workflows/maven-cd.yml @@ -79,6 +79,10 @@ jobs: - name: Set up Docker Buildx uses: docker/setup-buildx-action@v4.0.0 + - name: Set image name + id: image + run: echo "name=$(echo '${{ github.repository }}' | tr '[:upper:]' '[:lower:]')" >> "$GITHUB_OUTPUT" + - name: Build and push Docker image to GitHub Container Registry uses: docker/build-push-action@v7.0.0 with: @@ -89,9 +93,9 @@ jobs: cache-from: type=gha cache-to: type=gha,mode=max tags: | - ghcr.io/${{ github.repository }}:latest - ghcr.io/${{ github.repository }}:${{ steps.tag.outputs.semver }} - ghcr.io/${{ github.repository }}:${{ steps.tag.outputs.club }} + ghcr.io/${{ steps.image.outputs.name }}:latest + ghcr.io/${{ steps.image.outputs.name }}:${{ steps.tag.outputs.semver }} + ghcr.io/${{ steps.image.outputs.name }}:${{ steps.tag.outputs.club }} - name: Generate changelog id: changelog @@ -121,19 +125,19 @@ jobs: ```bash # By semantic version (recommended) - docker pull ghcr.io/${{ github.repository }}:${{ steps.tag.outputs.semver }} + docker pull ghcr.io/${{ steps.image.outputs.name }}:${{ steps.tag.outputs.semver }} # By club name - docker pull ghcr.io/${{ github.repository }}:${{ steps.tag.outputs.club }} + docker pull ghcr.io/${{ steps.image.outputs.name }}:${{ steps.tag.outputs.club }} # Latest - docker pull ghcr.io/${{ github.repository }}:latest + docker pull ghcr.io/${{ steps.image.outputs.name }}:latest ``` ## Quick Start ```bash - docker run -p 9000:9000 ghcr.io/${{ github.repository }}:${{ steps.tag.outputs.semver }} + docker run -p 9000:9000 ghcr.io/${{ steps.image.outputs.name }}:${{ steps.tag.outputs.semver }} ``` API available at `http://localhost:9000` ยท Swagger UI at `http://localhost:9000/swagger/index.html` From ba641ac8e5703ae3c0e01b75ed706f9d69fe47e0 Mon Sep 17 00:00:00 2001 From: Nano Taboada <87288+nanotaboada@users.noreply.github.com> Date: Sat, 28 Mar 2026 22:59:44 -0300 Subject: [PATCH 3/3] fix(cd): use fixed-string matching when excluding current tag Co-authored-by: Claude Sonnet 4.6 --- .github/workflows/maven-cd.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/maven-cd.yml b/.github/workflows/maven-cd.yml index fad16b8..ac773f4 100644 --- a/.github/workflows/maven-cd.yml +++ b/.github/workflows/maven-cd.yml @@ -100,7 +100,8 @@ jobs: - name: Generate changelog id: changelog run: | - PREVIOUS_TAG=$(git tag --sort=-version:refname | grep -v "^${GITHUB_REF#refs/tags/}$" | head -n 1) + CURRENT_TAG="${GITHUB_REF#refs/tags/}" + PREVIOUS_TAG=$(git tag --sort=-version:refname | grep -Fxv "$CURRENT_TAG" | head -n 1) if [ -n "$PREVIOUS_TAG" ]; then CHANGELOG=$(git log "$PREVIOUS_TAG"..HEAD --pretty=format:"- %s" --no-merges) else