Skip to content

Commit 1a8fab2

Browse files
authored
Merge pull request #287 from nanotaboada/ci/named-releases-tag-based-deployment
ci(release): implement named releases with tag-based deployment (#252)
2 parents 925f137 + ba641ac commit 1a8fab2

File tree

4 files changed

+281
-38
lines changed

4 files changed

+281
-38
lines changed

.github/workflows/maven-cd.yml

Lines changed: 145 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,145 @@
1+
# Deploying Java with Maven to GitHub Container Registry
2+
# https://docs.github.com/en/actions/use-cases-and-examples/publishing-packages/publishing-docker-images
3+
4+
name: Java CD
5+
6+
on:
7+
push:
8+
tags:
9+
- "v*.*.*-*"
10+
11+
env:
12+
JAVA_VERSION: 25
13+
14+
jobs:
15+
test:
16+
runs-on: ubuntu-latest
17+
permissions:
18+
contents: read
19+
steps:
20+
- name: Checkout repository
21+
uses: actions/checkout@v6
22+
23+
- name: Set up OpenJDK ${{ env.JAVA_VERSION }}
24+
uses: actions/setup-java@v5.2.0
25+
with:
26+
java-version: ${{ env.JAVA_VERSION }}
27+
distribution: "temurin"
28+
cache: "maven"
29+
30+
- name: Compile and verify with Maven
31+
run: ./mvnw clean verify
32+
33+
release:
34+
needs: test
35+
runs-on: ubuntu-latest
36+
permissions:
37+
contents: write
38+
packages: write
39+
40+
steps:
41+
- name: Checkout repository
42+
uses: actions/checkout@v6
43+
with:
44+
fetch-depth: 0
45+
46+
- name: Extract and validate tag components
47+
id: tag
48+
run: |
49+
TAG="${GITHUB_REF#refs/tags/}"
50+
echo "Full tag: $TAG"
51+
52+
SEMVER=$(echo "$TAG" | sed -E 's/^v([0-9]+\.[0-9]+\.[0-9]+)-.+$/\1/')
53+
CLUB=$(echo "$TAG" | sed -E 's/^v[0-9]+\.[0-9]+\.[0-9]+-(.+)$/\1/')
54+
55+
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"
56+
57+
if ! echo "$SEMVER" | grep -qE '^[0-9]+\.[0-9]+\.[0-9]+$'; then
58+
echo "❌ Invalid semver: $SEMVER"
59+
exit 1
60+
fi
61+
62+
if ! echo "$VALID_CLUBS" | grep -qw "$CLUB"; then
63+
echo "❌ Invalid club name: $CLUB"
64+
echo "Valid clubs: $VALID_CLUBS"
65+
exit 1
66+
fi
67+
68+
echo "semver=$SEMVER" >> "$GITHUB_OUTPUT"
69+
echo "club=$CLUB" >> "$GITHUB_OUTPUT"
70+
echo "✅ Tag: v$SEMVER - $CLUB"
71+
72+
- name: Log in to GitHub Container Registry
73+
uses: docker/login-action@v4.0.0
74+
with:
75+
registry: ghcr.io
76+
username: ${{ github.actor }}
77+
password: ${{ secrets.GITHUB_TOKEN }}
78+
79+
- name: Set up Docker Buildx
80+
uses: docker/setup-buildx-action@v4.0.0
81+
82+
- name: Set image name
83+
id: image
84+
run: echo "name=$(echo '${{ github.repository }}' | tr '[:upper:]' '[:lower:]')" >> "$GITHUB_OUTPUT"
85+
86+
- name: Build and push Docker image to GitHub Container Registry
87+
uses: docker/build-push-action@v7.0.0
88+
with:
89+
context: .
90+
push: true
91+
platforms: linux/amd64,linux/arm64
92+
provenance: false
93+
cache-from: type=gha
94+
cache-to: type=gha,mode=max
95+
tags: |
96+
ghcr.io/${{ steps.image.outputs.name }}:latest
97+
ghcr.io/${{ steps.image.outputs.name }}:${{ steps.tag.outputs.semver }}
98+
ghcr.io/${{ steps.image.outputs.name }}:${{ steps.tag.outputs.club }}
99+
100+
- name: Generate changelog
101+
id: changelog
102+
run: |
103+
CURRENT_TAG="${GITHUB_REF#refs/tags/}"
104+
PREVIOUS_TAG=$(git tag --sort=-version:refname | grep -Fxv "$CURRENT_TAG" | head -n 1)
105+
if [ -n "$PREVIOUS_TAG" ]; then
106+
CHANGELOG=$(git log "$PREVIOUS_TAG"..HEAD --pretty=format:"- %s" --no-merges)
107+
else
108+
CHANGELOG=$(git log --pretty=format:"- %s" --no-merges)
109+
fi
110+
{
111+
echo "content<<EOF"
112+
echo "$CHANGELOG"
113+
echo "EOF"
114+
} >> "$GITHUB_OUTPUT"
115+
116+
- name: Create GitHub Release
117+
uses: softprops/action-gh-release@v2.6.1
118+
with:
119+
name: "v${{ steps.tag.outputs.semver }} - ${{ steps.tag.outputs.club }} 🏆"
120+
body: |
121+
## What's Changed
122+
123+
${{ steps.changelog.outputs.content }}
124+
125+
## Docker
126+
127+
```bash
128+
# By semantic version (recommended)
129+
docker pull ghcr.io/${{ steps.image.outputs.name }}:${{ steps.tag.outputs.semver }}
130+
131+
# By club name
132+
docker pull ghcr.io/${{ steps.image.outputs.name }}:${{ steps.tag.outputs.club }}
133+
134+
# Latest
135+
docker pull ghcr.io/${{ steps.image.outputs.name }}:latest
136+
```
137+
138+
## Quick Start
139+
140+
```bash
141+
docker run -p 9000:9000 ghcr.io/${{ steps.image.outputs.name }}:${{ steps.tag.outputs.semver }}
142+
```
143+
144+
API available at `http://localhost:9000` · Swagger UI at `http://localhost:9000/swagger/index.html`
145+
generate_release_notes: false
Lines changed: 1 addition & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ jobs:
2929
cache: "maven"
3030

3131
- name: Compile and verify with Maven
32-
run: mvn verify --file pom.xml
32+
run: ./mvnw clean verify
3333

3434
- name: Upload JaCoCo coverage report artifact
3535
uses: actions/upload-artifact@v7.0.0
@@ -56,39 +56,3 @@ jobs:
5656
with:
5757
token: ${{ secrets.CODECOV_TOKEN }}
5858
files: jacoco.xml
59-
60-
container:
61-
needs: coverage
62-
runs-on: ubuntu-latest
63-
if: ${{ github.event_name == 'push' && github.ref == 'refs/heads/master' }}
64-
65-
permissions:
66-
contents: read
67-
packages: write
68-
69-
steps:
70-
- name: Checkout repository
71-
uses: actions/checkout@v6
72-
73-
- name: Log in to GitHub Container Registry
74-
uses: docker/login-action@v4.0.0
75-
with:
76-
registry: ghcr.io
77-
username: ${{ github.actor }}
78-
password: ${{ secrets.GITHUB_TOKEN }}
79-
80-
- name: Set up Docker Buildx
81-
uses: docker/setup-buildx-action@v4.0.0
82-
83-
- name: Build and push Docker image to GitHub Container Registry
84-
uses: docker/build-push-action@v7.0.0
85-
with:
86-
context: .
87-
push: true
88-
platforms: linux/amd64
89-
provenance: false
90-
cache-from: type=gha
91-
cache-to: type=gha,mode=max
92-
tags: |
93-
ghcr.io/${{ github.repository }}:latest
94-
ghcr.io/${{ github.repository }}:sha-${{ github.sha }}

CHANGELOG.md

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
# Changelog
2+
3+
All notable changes to this project will be documented in this file.
4+
5+
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
6+
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7+
8+
Release names follow the **historic football clubs** naming convention (A–Z):
9+
10+
| Tag | Club | Country | Founded |
11+
| --- | ---- | ------- | ------- |
12+
| `arsenal` | Arsenal | England | 1886 |
13+
| `barcelona` | Barcelona | Spain | 1899 |
14+
| `chelsea` | Chelsea | England | 1905 |
15+
| `dortmund` | Borussia Dortmund | Germany | 1909 |
16+
| `everton` | Everton | England | 1878 |
17+
| `flamengo` | Flamengo | Brazil | 1895 |
18+
| `galatasaray` | Galatasaray | Turkey | 1905 |
19+
| `hamburg` | Hamburg SV | Germany | 1887 |
20+
| `inter` | Internazionale | Italy | 1908 |
21+
| `juventus` | Juventus | Italy | 1897 |
22+
| `kaiserslautern` | Kaiserslautern | Germany | 1900 |
23+
| `liverpool` | Liverpool | England | 1892 |
24+
| `manchesterutd` | Manchester United | England | 1878 |
25+
| `napoli` | Napoli | Italy | 1926 |
26+
| `olympique` | Olympique Marseille | France | 1899 |
27+
| `psg` | Paris Saint-Germain | France | 1970 |
28+
| `qpr` | Queens Park Rangers | England | 1882 |
29+
| `realmadrid` | Real Madrid | Spain | 1902 |
30+
| `sevilla` | Sevilla | Spain | 1890 |
31+
| `tottenham` | Tottenham Hotspur | England | 1882 |
32+
| `union` | Union Berlin | Germany | 1966 |
33+
| `valencia` | Valencia | Spain | 1919 |
34+
| `werder` | Werder Bremen | Germany | 1899 |
35+
| `xerez` | Xerez CD | Spain | 1947 |
36+
| `youngboys` | Young Boys | Switzerland | 1898 |
37+
| `zenit` | Zenit | Russia | 1925 |
38+
39+
---
40+
41+
## [Unreleased]
42+
43+
### Added
44+
45+
### Changed
46+
47+
### Fixed

README.md

Lines changed: 88 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# 🧪 RESTful API with Java and Spring Boot
22

3-
[![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)
3+
[![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)
44
[![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)
55
[![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)
66
[![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
3232
- [Reset Database](#reset-database)
3333
- [Environment Variables](#environment-variables)
3434
- [Command Summary](#command-summary)
35+
- [Releases](#releases)
3536
- [Contributing](#contributing)
3637
- [Legal](#legal)
3738

@@ -391,6 +392,92 @@ spring.jpa.hibernate.ddl-auto=create-drop
391392

392393
> 💡 **Note:** Always use the Maven wrapper (`./mvnw`) instead of system Maven to ensure consistent builds.
393394
395+
## Releases
396+
397+
This project uses **historic football clubs** as release codenames 🏆 (inspired by Ubuntu, Android, and macOS naming conventions).
398+
399+
### Release Naming Convention
400+
401+
Releases follow the pattern: `v{SEMVER}-{CLUB}` (e.g., `v1.0.0-arsenal`)
402+
403+
- **Semantic Version**: Standard versioning (MAJOR.MINOR.PATCH)
404+
- **Club Name**: Alphabetically ordered codename from the [historic club list](CHANGELOG.md)
405+
406+
### Create a Release
407+
408+
To create a new release, follow this workflow:
409+
410+
#### 1. Create a Release Branch
411+
412+
Branch protection prevents direct pushes to `master`, so all release prep goes through a PR:
413+
414+
```bash
415+
git checkout master && git pull
416+
git checkout -b release/v1.0.0-arsenal
417+
```
418+
419+
#### 2. Update CHANGELOG.md
420+
421+
Move items from `[Unreleased]` to a new release section in [CHANGELOG.md](CHANGELOG.md), then commit and push the branch:
422+
423+
```bash
424+
# Move items from [Unreleased] to new release section
425+
# Example: [1.0.0 - Arsenal] - 2026-XX-XX
426+
git add CHANGELOG.md
427+
git commit -m "docs(changelog): prepare release notes for v1.0.0-arsenal"
428+
git push origin release/v1.0.0-arsenal
429+
```
430+
431+
#### 3. Merge the Release PR
432+
433+
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`.
434+
435+
#### 4. Create and Push Tag
436+
437+
After the PR is merged, pull `master` and create the annotated tag:
438+
439+
```bash
440+
git checkout master && git pull
441+
git tag -a v1.0.0-arsenal -m "Release 1.0.0 - Arsenal"
442+
git push origin v1.0.0-arsenal
443+
```
444+
445+
#### 5. Automated CD Workflow
446+
447+
This triggers the CD workflow which automatically:
448+
449+
1. Validates the club name
450+
2. Builds and tests the project with Maven
451+
3. Publishes Docker images to GitHub Container Registry with three tags
452+
4. Creates a GitHub Release with auto-generated changelog from commits
453+
454+
#### Pre-Release Checklist
455+
456+
- [ ] Release branch created from `master`
457+
- [ ] `CHANGELOG.md` updated with release notes
458+
- [ ] Changes committed and pushed on the release branch
459+
- [ ] Release PR merged into `master`
460+
- [ ] Tag created with correct format: `vX.Y.Z-club`
461+
- [ ] Club name is valid (A-Z from the [historic club list](CHANGELOG.md))
462+
- [ ] Tag pushed to trigger CD workflow
463+
464+
### Pull Docker Images
465+
466+
Each release publishes multiple tags for flexibility:
467+
468+
```bash
469+
# By semantic version (recommended for production)
470+
docker pull ghcr.io/nanotaboada/java-samples-spring-boot:1.0.0
471+
472+
# By club name (memorable alternative)
473+
docker pull ghcr.io/nanotaboada/java-samples-spring-boot:arsenal
474+
475+
# Latest release
476+
docker pull ghcr.io/nanotaboada/java-samples-spring-boot:latest
477+
```
478+
479+
> 💡 See [CHANGELOG.md](CHANGELOG.md) for the complete club list (A-Z) and release history.
480+
394481
## Contributing
395482

396483
Contributions are welcome! Please see [CONTRIBUTING.md](CONTRIBUTING.md) for details on:

0 commit comments

Comments
 (0)