Skip to content
Merged
Show file tree
Hide file tree
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
145 changes: 145 additions & 0 deletions .github/workflows/maven-cd.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
# 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: 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:
context: .
push: true
platforms: linux/amd64,linux/arm64
provenance: false
cache-from: type=gha
cache-to: type=gha,mode=max
tags: |
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
run: |
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
CHANGELOG=$(git log --pretty=format:"- %s" --no-merges)
fi
{
echo "content<<EOF"
echo "$CHANGELOG"
echo "EOF"
} >> "$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/${{ steps.image.outputs.name }}:${{ steps.tag.outputs.semver }}

# By club name
docker pull ghcr.io/${{ steps.image.outputs.name }}:${{ steps.tag.outputs.club }}

# Latest
docker pull ghcr.io/${{ steps.image.outputs.name }}:latest
```

## Quick Start

```bash
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`
generate_release_notes: false
38 changes: 1 addition & 37 deletions .github/workflows/maven.yml → .github/workflows/maven-ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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 }}
47 changes: 47 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -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
89 changes: 88 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -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)
Expand Down Expand Up @@ -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)

Expand Down Expand Up @@ -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:
Expand Down
Loading