Skip to content

Commit 20740ba

Browse files
authored
Merge pull request #469 from nanotaboada/feat/named-releases-cicd
feat: implement tag-based releases with football coaches naming (#467)
2 parents 60d44f7 + 35860f6 commit 20740ba

File tree

4 files changed

+339
-42
lines changed

4 files changed

+339
-42
lines changed

.github/workflows/python-cd.yml

Lines changed: 168 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,168 @@
1+
# Building, testing, and publishing Python releases
2+
# https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-python
3+
4+
name: Python CD
5+
6+
on:
7+
push:
8+
tags:
9+
- 'v*.*.*-*'
10+
11+
env:
12+
PYTHON_VERSION_FILE: '.python-version'
13+
PACKAGE_NAME: nanotaboada/python-samples-fastapi-restful
14+
15+
jobs:
16+
release:
17+
runs-on: ubuntu-latest
18+
permissions:
19+
contents: write
20+
packages: write
21+
steps:
22+
- name: Checkout repository
23+
uses: actions/checkout@v6.0.1
24+
with:
25+
fetch-depth: 0
26+
27+
- name: Extract version from tag
28+
id: version
29+
run: |
30+
TAG_NAME=${GITHUB_REF#refs/tags/}
31+
# Extract semver (e.g., v1.0.0-ancelotti -> 1.0.0)
32+
SEMVER=$(echo $TAG_NAME | sed -E 's/^v([0-9]+\.[0-9]+\.[0-9]+)-.*/\1/')
33+
# Extract coach name (e.g., v1.0.0-ancelotti -> ancelotti)
34+
COACH=$(echo $TAG_NAME | sed -E 's/^v[0-9]+\.[0-9]+\.[0-9]+-//')
35+
36+
# Validate semver format (X.Y.Z)
37+
if ! echo "$SEMVER" | grep -Eq '^[0-9]+\.[0-9]+\.[0-9]+$'; then
38+
echo "❌ Error: Invalid semantic version '$SEMVER' extracted from tag '$TAG_NAME'"
39+
echo "Expected format: v{MAJOR}.{MINOR}.{PATCH}-{COACH} (e.g., v1.0.0-ancelotti)"
40+
exit 1
41+
fi
42+
43+
# Valid coach names (A-Z from CHANGELOG.md)
44+
VALID_COACHES="ancelotti bielsa capello delbosque eriksson ferguson guardiola heynckes inzaghi klopp kovac low mourinho nagelsmann ottmar pochettino queiroz ranieri simeone tuchel unai vangaal wenger xavi yozhef zeman"
45+
46+
# Validate coach name against the list
47+
if [ -z "$COACH" ]; then
48+
echo "❌ Error: Coach name is empty in tag '$TAG_NAME'"
49+
echo "Expected format: v{MAJOR}.{MINOR}.{PATCH}-{COACH} (e.g., v1.0.0-ancelotti)"
50+
exit 1
51+
fi
52+
53+
if ! echo "$VALID_COACHES" | grep -qw "$COACH"; then
54+
echo "❌ Error: Invalid coach name '$COACH' in tag '$TAG_NAME'"
55+
echo "Valid coaches (A-Z): $VALID_COACHES"
56+
echo "See CHANGELOG.md for the complete list"
57+
exit 1
58+
fi
59+
60+
# Export validated outputs
61+
echo "tag_name=$TAG_NAME" >> $GITHUB_OUTPUT
62+
echo "semver=$SEMVER" >> $GITHUB_OUTPUT
63+
echo "coach=$COACH" >> $GITHUB_OUTPUT
64+
65+
echo "📦 Release version: $SEMVER"
66+
echo "♟️ Coach name: $COACH"
67+
68+
- name: Set up Python
69+
uses: actions/setup-python@v6.1.0
70+
with:
71+
python-version-file: ${{ env.PYTHON_VERSION_FILE }}
72+
cache: 'pip'
73+
74+
- name: Install test dependencies
75+
run: |
76+
python -m pip install --upgrade pip
77+
pip install -r requirements-test.txt
78+
79+
- name: Run tests with pytest
80+
run: |
81+
pytest -v
82+
83+
- name: Generate coverage report
84+
run: |
85+
pytest --cov=./ --cov-report=xml --cov-report=term
86+
87+
- name: Log in to GitHub Container Registry
88+
uses: docker/login-action@v3.6.0
89+
with:
90+
registry: ghcr.io
91+
username: ${{ github.actor }}
92+
password: ${{ secrets.GITHUB_TOKEN }}
93+
94+
- name: Set up Docker Buildx
95+
uses: docker/setup-buildx-action@v3.12.0
96+
97+
- name: Build and push Docker image to GitHub Container Registry
98+
uses: docker/build-push-action@v6.18.0
99+
with:
100+
context: .
101+
push: true
102+
platforms: linux/amd64,linux/arm64
103+
provenance: false
104+
cache-from: type=gha
105+
cache-to: type=gha,mode=max
106+
tags: |
107+
ghcr.io/${{ env.PACKAGE_NAME }}:${{ steps.version.outputs.semver }}
108+
ghcr.io/${{ env.PACKAGE_NAME }}:${{ steps.version.outputs.coach }}
109+
ghcr.io/${{ env.PACKAGE_NAME }}:latest
110+
111+
- name: Generate changelog
112+
id: changelog
113+
run: |
114+
# Get the previous tag (second most recent tag matching the version pattern)
115+
PREVIOUS_TAG=$(git tag -l 'v*.*.*-*' --sort=-v:refname | grep -E '^v[0-9]+\.[0-9]+\.[0-9]+-' | sed -n '2p')
116+
117+
if [ -z "$PREVIOUS_TAG" ]; then
118+
echo "📝 First release - no previous tag found"
119+
CHANGELOG="Initial release"
120+
else
121+
echo "📝 Generating changelog from $PREVIOUS_TAG to ${{ steps.version.outputs.tag_name }}"
122+
CHANGELOG=$(git log --pretty=format:"- %s (%h)" ${PREVIOUS_TAG}..${{ steps.version.outputs.tag_name }})
123+
fi
124+
125+
# Write changelog to file
126+
echo "$CHANGELOG" > changelog.txt
127+
cat changelog.txt
128+
129+
# Set output for use in release body
130+
{
131+
echo "changelog<<EOF"
132+
echo "$CHANGELOG"
133+
echo "EOF"
134+
} >> $GITHUB_OUTPUT
135+
136+
- name: Create GitHub Release
137+
uses: softprops/action-gh-release@v2.2.0
138+
with:
139+
name: "v${{ steps.version.outputs.semver }} - ${{ steps.version.outputs.coach }} ♟️"
140+
tag_name: ${{ steps.version.outputs.tag_name }}
141+
body: |
142+
# ♟️ Release ${{ steps.version.outputs.semver }} - ${{ steps.version.outputs.coach }}
143+
144+
## Docker Images
145+
146+
Pull this release using any of the following tags:
147+
148+
```bash
149+
# By semantic version (recommended)
150+
docker pull ghcr.io/${{ env.PACKAGE_NAME }}:${{ steps.version.outputs.semver }}
151+
152+
# By coach name
153+
docker pull ghcr.io/${{ env.PACKAGE_NAME }}:${{ steps.version.outputs.coach }}
154+
155+
# Latest
156+
docker pull ghcr.io/${{ env.PACKAGE_NAME }}:latest
157+
```
158+
159+
## Changelog
160+
161+
${{ steps.changelog.outputs.changelog }}
162+
163+
---
164+
165+
📦 **Package:** [ghcr.io/${{ env.PACKAGE_NAME }}](https://github.com/${{ github.repository }}/pkgs/container/python-samples-fastapi-restful)
166+
draft: false
167+
prerelease: false
168+
generate_release_notes: true
Lines changed: 5 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -16,13 +16,13 @@ jobs:
1616
contents: read
1717
steps:
1818
- name: Checkout repository
19-
uses: actions/checkout@v6.0.2
19+
uses: actions/checkout@v6.0.1
2020

2121
- name: Lint commit messages
2222
uses: wagoid/commitlint-github-action@v6.2.1
2323

2424
- name: Set up Python
25-
uses: actions/setup-python@v6.2.0
25+
uses: actions/setup-python@v6.1.0
2626
with:
2727
python-version-file: '.python-version'
2828
cache: 'pip'
@@ -47,10 +47,10 @@ jobs:
4747
contents: read
4848
steps:
4949
- name: Checkout repository
50-
uses: actions/checkout@v6.0.2
50+
uses: actions/checkout@v6.0.1
5151

5252
- name: Set up Python
53-
uses: actions/setup-python@v6.2.0
53+
uses: actions/setup-python@v6.1.0
5454
with:
5555
python-version-file: '.python-version'
5656
cache: 'pip'
@@ -87,7 +87,7 @@ jobs:
8787
service: [codecov, codacy]
8888
steps:
8989
- name: Checkout repository
90-
uses: actions/checkout@v6.0.2
90+
uses: actions/checkout@v6.0.1
9191

9292
- name: Download coverage report artifact
9393
uses: actions/download-artifact@v7.0.0
@@ -107,39 +107,3 @@ jobs:
107107
with:
108108
project-token: ${{ secrets.CODACY_PROJECT_TOKEN }}
109109
coverage-reports: coverage.xml
110-
111-
container:
112-
needs: coverage
113-
if: ${{ github.event_name == 'push' && github.ref == 'refs/heads/master' }}
114-
runs-on: ubuntu-latest
115-
116-
permissions:
117-
contents: read
118-
packages: write
119-
120-
steps:
121-
- name: Checkout repository
122-
uses: actions/checkout@v6.0.2
123-
124-
- name: Log in to GitHub Container Registry
125-
uses: docker/login-action@v3.6.0
126-
with:
127-
registry: ghcr.io
128-
username: ${{ github.actor }}
129-
password: ${{ secrets.GITHUB_TOKEN }}
130-
131-
- name: Set up Docker Buildx
132-
uses: docker/setup-buildx-action@v3.12.0
133-
134-
- name: Build and push Docker image to GitHub Container Registry
135-
uses: docker/build-push-action@v6.18.0
136-
with:
137-
context: .
138-
push: true
139-
platforms: linux/amd64
140-
provenance: false
141-
cache-from: type=gha
142-
cache-to: type=gha,mode=max
143-
tags: |
144-
ghcr.io/${{ github.repository }}:latest
145-
ghcr.io/${{ github.repository }}:sha-${{ github.sha }}

CHANGELOG.md

Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
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 Naming Convention ♟️
9+
10+
This project uses famous football coaches as release codenames, following an A-Z naming pattern.
11+
12+
| Letter | Coach Name | Country/Notable Era | Tag Name |
13+
|--------|-----------|---------------------|----------|
14+
| A | Ancelotti (Carlo) | Italy | `ancelotti` |
15+
| B | Bielsa (Marcelo) | Argentina | `bielsa` |
16+
| C | Capello (Fabio) | Italy | `capello` |
17+
| D | Del Bosque (Vicente) | Spain | `delbosque` |
18+
| E | Eriksson (Sven-Göran) | Sweden | `eriksson` |
19+
| F | Ferguson (Alex) | Scotland | `ferguson` |
20+
| G | Guardiola (Pep) | Spain | `guardiola` |
21+
| H | Heynckes (Jupp) | Germany | `heynckes` |
22+
| I | Inzaghi (Simone) | Italy | `inzaghi` |
23+
| J | Klopp (Jürgen) | Germany | `klopp` |
24+
| K | Kovač (Niko) | Croatia | `kovac` |
25+
| L | Löw (Joachim) | Germany | `low` |
26+
| M | Mourinho (José) | Portugal | `mourinho` |
27+
| N | Nagelsmann (Julian) | Germany | `nagelsmann` |
28+
| O | Ottmar Hitzfeld | Germany/Switzerland | `ottmar` |
29+
| P | Pochettino (Mauricio) | Argentina | `pochettino` |
30+
| Q | Queiroz (Carlos) | Portugal | `queiroz` |
31+
| R | Ranieri (Claudio) | Italy | `ranieri` |
32+
| S | Simeone (Diego) | Argentina | `simeone` |
33+
| T | Tuchel (Thomas) | Germany | `tuchel` |
34+
| U | Unai Emery | Spain | `unai` |
35+
| V | Van Gaal (Louis) | Netherlands | `vangaal` |
36+
| W | Wenger (Arsène) | France | `wenger` |
37+
| X | Xavi Hernández | Spain | `xavi` |
38+
| Y | Yozhef Sabo | Ukraine | `yozhef` |
39+
| Z | Zeman (Zdeněk) | Czech Republic | `zeman` |
40+
41+
---
42+
43+
## [Unreleased]
44+
45+
### Added
46+
47+
### Changed
48+
49+
### Fixed
50+
51+
### Removed
52+
53+
---
54+
55+
## [1.0.0 - Ancelotti] - TBD
56+
57+
### Added
58+
59+
- Initial stable release
60+
- Player CRUD operations with FastAPI
61+
- SQLite database with SQLAlchemy (async)
62+
- Docker support with Docker Compose
63+
- In-memory caching with aiocache (10 min TTL)
64+
- Comprehensive pytest test suite with coverage reporting
65+
- Health check endpoint
66+
- CI/CD pipeline with tag-based releases
67+
- Famous coaches release naming convention ♟️
68+
69+
### Changed
70+
71+
- N/A
72+
73+
### Fixed
74+
75+
- N/A
76+
77+
### Removed
78+
79+
- N/A
80+
81+
---
82+
83+
## How to Release
84+
85+
1. Update this CHANGELOG.md with the new version details
86+
2. Create a tag with the format `v{MAJOR}.{MINOR}.{PATCH}-{COACH}`
87+
3. Push the tag to trigger the CD pipeline
88+
89+
```bash
90+
# Example: Creating the first release (Ancelotti)
91+
git tag -a v1.0.0-ancelotti -m "Release 1.0.0 - Ancelotti"
92+
git push origin v1.0.0-ancelotti
93+
```
94+
95+
The CD pipeline will automatically:
96+
97+
- Run tests and generate coverage reports
98+
- Build and push Docker images with multiple tags (`:1.0.0`, `:ancelotti`, `:latest`)
99+
- Generate a changelog from git commits
100+
- Create a GitHub Release with auto-generated notes
101+
102+
## Version History
103+
104+
<!--
105+
Add release summaries here as they are published:
106+
107+
### [1.0.0 - Ancelotti] (2026-XX-XX)
108+
- Initial stable release with core functionality
109+
-->

0 commit comments

Comments
 (0)