diff --git a/.github/workflows/dotnet-cd.yml b/.github/workflows/dotnet-cd.yml new file mode 100644 index 0000000..059b349 --- /dev/null +++ b/.github/workflows/dotnet-cd.yml @@ -0,0 +1,166 @@ +# Building, testing, and publishing .NET releases +# https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-net + +name: .NET CD + +on: + push: + tags: + - 'v*.*.*-*' + +env: + DOTNET_VERSION: 8.0.x + PACKAGE_NAME: nanotaboada/dotnet-samples-aspnetcore-webapi + +jobs: + release: + runs-on: ubuntu-latest + permissions: + contents: write + packages: write + + steps: + - name: Checkout repository + uses: actions/checkout@v6.0.1 + with: + fetch-depth: 0 + + - name: Extract version from tag + id: version + run: | + TAG_NAME=${GITHUB_REF#refs/tags/} + + # Extract semver (e.g., v1.0.0-azteca -> 1.0.0) + SEMVER=$(echo $TAG_NAME | sed -E 's/^v([0-9]+\.[0-9]+\.[0-9]+)-.*/\1/') + + # Extract stadium name (e.g., v1.0.0-azteca -> azteca) + STADIUM=$(echo $TAG_NAME | sed -E 's/^v[0-9]+\.[0-9]+\.[0-9]+-//') + + # Validate semver format (X.Y.Z) + if ! echo "$SEMVER" | grep -Eq '^[0-9]+\.[0-9]+\.[0-9]+$'; then + echo "❌ Error: Invalid semantic version '$SEMVER' extracted from tag '$TAG_NAME'" + echo "Expected format: v{MAJOR}.{MINOR}.{PATCH}-{STADIUM} (e.g., v1.0.0-azteca)" + exit 1 + fi + + # Valid stadium names (A-Z from CHANGELOG.md) + VALID_STADIUMS="azteca bernabeu centenario dusseldorf ekaterinburg frankfurt gelsenkirchen hardrock ibnbatouta johannesburg kazan lusail maracana nantes olympiastadion parcdesprinces qatar974 rosebowl sansiro toronto ullevi volgograd wembley xiamen yokohama zentralstadion" + + # Validate stadium name against the list + if [ -z "$STADIUM" ]; then + echo "❌ Error: Stadium name is empty in tag '$TAG_NAME'" + echo "Expected format: v{MAJOR}.{MINOR}.{PATCH}-{STADIUM} (e.g., v1.0.0-azteca)" + exit 1 + fi + + if ! echo "$VALID_STADIUMS" | grep -qw "$STADIUM"; then + echo "❌ Error: Invalid stadium name '$STADIUM' in tag '$TAG_NAME'" + echo "Valid stadiums (A-Z): $VALID_STADIUMS" + echo "See CHANGELOG.md for the complete list" + exit 1 + fi + + # Export validated outputs + echo "tag_name=$TAG_NAME" >> $GITHUB_OUTPUT + echo "semver=$SEMVER" >> $GITHUB_OUTPUT + echo "stadium=$STADIUM" >> $GITHUB_OUTPUT + + echo "📦 Release version: $SEMVER" + echo "🏟️ Stadium name: $STADIUM" + + - name: Set up .NET ${{ env.DOTNET_VERSION }} + uses: actions/setup-dotnet@v5.1.0 + with: + dotnet-version: ${{ env.DOTNET_VERSION }} + cache: true + cache-dependency-path: | + src/Dotnet.Samples.AspNetCore.WebApi/packages.lock.json + test/Dotnet.Samples.AspNetCore.WebApi.Tests/packages.lock.json + + - name: Restore dependencies + run: dotnet restore + + - name: Build projects (Release configuration) + run: dotnet build --configuration Release --no-restore + + - name: Run tests + run: dotnet test --configuration Release --no-build --verbosity normal + + - name: Log in to GitHub Container Registry + uses: docker/login-action@v3.6.0 + with: + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3.12.0 + + - name: Build and push Docker image to GitHub Container Registry + uses: docker/build-push-action@v6.18.0 + with: + context: . + push: true + platforms: linux/amd64 + provenance: false + cache-from: type=gha + cache-to: type=gha,mode=max + tags: | + ghcr.io/${{ env.PACKAGE_NAME }}:latest + ghcr.io/${{ env.PACKAGE_NAME }}:${{ steps.version.outputs.semver }} + ghcr.io/${{ env.PACKAGE_NAME }}:${{ steps.version.outputs.stadium }} + + - name: Generate changelog + id: changelog + run: | + # Get previous tag + PREVIOUS_TAG=$(git tag --sort=-version:refname | grep -E '^v[0-9]+\.[0-9]+\.[0-9]+-' | sed -n '2p') + + if [ -z "$PREVIOUS_TAG" ]; then + echo "📝 First release - no previous tag found" + CHANGELOG="Initial release" + else + echo "📝 Generating changelog from $PREVIOUS_TAG to ${{ steps.version.outputs.tag_name }}" + CHANGELOG=$(git log --pretty=format:"- %s (%h)" ${PREVIOUS_TAG}..${{ steps.version.outputs.tag_name }}) + fi + + # Write changelog to file + echo "$CHANGELOG" > changelog.txt + cat changelog.txt + + # Set output for use in release body + { + echo "changelog<> $GITHUB_OUTPUT + + - name: Create GitHub Release + uses: softprops/action-gh-release@v2.2.0 + with: + name: "v${{ steps.version.outputs.semver }} - ${{ steps.version.outputs.stadium }}" + tag_name: ${{ steps.version.outputs.tag_name }} + body: | + # 🏟️ Release ${{ steps.version.outputs.semver }} - ${{ steps.version.outputs.stadium }} + + ## Docker Images + + Pull this release using any of the following tags: + + ```bash + # By semantic version (recommended) + docker pull ghcr.io/${{ env.PACKAGE_NAME }}:${{ steps.version.outputs.semver }} + + # By stadium name + docker pull ghcr.io/${{ env.PACKAGE_NAME }}:${{ steps.version.outputs.stadium }} + + # Latest + docker pull ghcr.io/${{ env.PACKAGE_NAME }}:latest + ``` + + --- + + 📦 **Package:** [ghcr.io/${{ env.PACKAGE_NAME }}](https://github.com/${{ github.repository }}/pkgs/container/dotnet-samples-aspnetcore-webapi) + draft: false + prerelease: false + generate_release_notes: true diff --git a/.github/workflows/dotnet.yml b/.github/workflows/dotnet-ci.yml similarity index 76% rename from .github/workflows/dotnet.yml rename to .github/workflows/dotnet-ci.yml index ec30faf..12db348 100644 --- a/.github/workflows/dotnet.yml +++ b/.github/workflows/dotnet-ci.yml @@ -11,7 +11,6 @@ on: env: DOTNET_VERSION: 8.0.x - PACKAGE_NAME: nanotaboada/dotnet-samples-aspnetcore-webapi jobs: build: @@ -108,39 +107,3 @@ jobs: with: project-token: ${{ secrets.CODACY_PROJECT_TOKEN }} coverage-reports: cobertura.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.0.1 - - - name: Log in to GitHub Container Registry - uses: docker/login-action@v3.6.0 - with: - registry: ghcr.io - username: ${{ github.actor }} - password: ${{ secrets.GITHUB_TOKEN }} - - - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v3.12.0 - - - name: Build and push Docker image to GitHub Container Registry - uses: docker/build-push-action@v6.18.0 - with: - context: . - push: true - platforms: linux/amd64 - provenance: false - cache-from: type=gha - cache-to: type=gha,mode=max - tags: | - ghcr.io/${{ env.PACKAGE_NAME }}:latest - ghcr.io/${{ env.PACKAGE_NAME }}:sha-${{ github.sha }} diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..b66de58 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,90 @@ +# 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.0.0/), +and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +## Stadium Release Names + +This project uses famous football stadiums (A-Z) that hosted FIFA World Cup matches (with notable fallbacks where necessary): + +| Letter | Stadium Name | Location | Tag Name | +| ------ | ----------- | -------- | -------- | +| A | Azteca | Mexico (1970, 1986, 2026) | `azteca` | +| B | Bernabéu | Spain (1982) | `bernabeu` | +| C | Centenario | Uruguay (1930) | `centenario` | +| D | Düsseldorf (Merkur Spiel-Arena) | Germany (2006) | `dusseldorf` | +| E | Ekaterinburg Arena | Russia (2018) | `ekaterinburg` | +| F | Frankfurt Waldstadion | Germany (1974, 2006) | `frankfurt` | +| G | Gelsenkirchen | Germany (2006) | `gelsenkirchen` | +| H | Hard Rock Stadium | USA (2026) | `hardrock` | +| I | Ibn Batouta Stadium | Morocco (2030) | `ibnbatouta` | +| J | Johannesburg Soccer City | South Africa (2010) | `johannesburg` | +| K | Kazan Arena | Russia (2018) | `kazan` | +| L | Lusail | Qatar (2022) | `lusail` | +| M | Maracanã | Brazil (1950, 2014) | `maracana` | +| N | Nantes Beaujoire | France (1998) | `nantes` | +| O | Olympiastadion Berlin | Germany (1974, 2006) | `olympiastadion` | +| P | Parc des Princes | France (1938, 1998) | `parcdesprinces` | +| Q | Qatar 974 | Qatar (2022) | `qatar974` | +| R | Rose Bowl | USA (1994) | `rosebowl` | +| S | San Siro | Italy (1934, 1990) | `sansiro` | +| T | Toronto BMO Field | Canada (2026) | `toronto` | +| U | Ullevi | Sweden (1958) | `ullevi` | +| V | Volgograd Arena | Russia (2018) | `volgograd` | +| W | Wembley | England (1966) | `wembley` | +| X | Xiamen Egret Stadium | (famous fallback) | `xiamen` | +| Y | Yokohama International Stadium | Japan (2002) | `yokohama` | +| Z | Zentralstadion Leipzig | Germany (1974, 2006) | `zentralstadion` | + +--- + +## [Unreleased] + +### Added + +### Changed + +### Fixed + +### Removed + +--- + +## How to Release + +To create a new release: + +1. Update this CHANGELOG with release notes under the appropriate version heading +2. Create and push a version tag with stadium name: + + ```bash + git tag -a v1.0.0-azteca -m "Release 1.0.0 - Azteca" + git push origin v1.0.0-azteca + ``` + +3. The CD workflow will automatically: + - Build and test the project + - Publish Docker images to GHCR with three tags (`:1.0.0`, `:azteca`, `:latest`) + - Create a GitHub Release with auto-generated notes + +--- + + diff --git a/README.md b/README.md index 37551d3..3127324 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # 🧪 RESTful API with .NET and ASP.NET Core -[![.NET CI](https://github.com/nanotaboada/Dotnet.Samples.AspNetCore.WebApi/actions/workflows/dotnet.yml/badge.svg)](https://github.com/nanotaboada/Dotnet.Samples.AspNetCore.WebApi/actions/workflows/dotnet.yml) +[![.NET CI](https://github.com/nanotaboada/Dotnet.Samples.AspNetCore.WebApi/actions/workflows/dotnet-ci.yml/badge.svg)](https://github.com/nanotaboada/Dotnet.Samples.AspNetCore.WebApi/actions/workflows/dotnet-ci.yml) [![Quality Gate Status](https://sonarcloud.io/api/project_badges/measure?project=nanotaboada_Dotnet.Samples.AspNetCore.WebApi&metric=alert_status)](https://sonarcloud.io/summary/new_code?id=nanotaboada_Dotnet.Samples.AspNetCore.WebApi) [![Build Status](https://dev.azure.com/nanotaboada/Dotnet.Samples.AspNetCore.WebApi/_apis/build/status%2FDotnet.Samples.AspNetCore.WebApi?branchName=master)](https://dev.azure.com/nanotaboada/Dotnet.Samples.AspNetCore.WebApi/_build/latest?definitionId=14&branchName=master) [![Codacy Badge](https://app.codacy.com/project/badge/Grade/ac7b7e22f1cd4d9d9233b36982b0d6a9)](https://app.codacy.com/gh/nanotaboada/Dotnet.Samples.AspNetCore.WebApi/dashboard?utm_source=gh&utm_medium=referral&utm_content=&utm_campaign=Badge_grade) @@ -21,6 +21,7 @@ Proof of Concept for a RESTful API built with .NET 8 (LTS) and ASP.NET Core. Man - [Quick Start](#quick-start) - [Testing](#testing) - [Docker](#docker) +- [Releases](#releases) - [Environment Variables](#environment-variables) - [Command Summary](#command-summary) - [Contributing](#contributing) @@ -289,6 +290,49 @@ docker compose down -v The containerized application runs on port 9000 and includes health checks that monitor the `/health` endpoint. +## Releases + +This project uses **stadium-themed release names** inspired by famous football stadiums that hosted FIFA World Cup matches. Each release is named after a stadium (A-Z alphabetically), making versions memorable and fun. + +### Release Naming Convention + +Releases follow the pattern: `v{SEMVER}-{STADIUM}` (e.g., `v1.0.0-azteca`) + +- **Semantic Version**: Standard versioning (MAJOR.MINOR.PATCH) +- **Stadium Name**: Alphabetically ordered codename from the [stadium list](CHANGELOG.md#stadium-release-names) + +### Create a Release + +To create a new release, tag a commit and push the tag: + +```bash +git tag -a v1.0.0-azteca -m "Release 1.0.0 - Azteca" +git push origin v1.0.0-azteca +``` + +This triggers the CD workflow which automatically: + +1. Builds and tests the project in Release configuration +2. Publishes Docker images to GitHub Container Registry with three tags +3. Creates a GitHub Release with auto-generated changelog + +### Pull Docker Images + +Each release publishes multiple tags for flexibility: + +```bash +# By semantic version (recommended for production) +docker pull ghcr.io/nanotaboada/dotnet-samples-aspnetcore-webapi:1.0.0 + +# By stadium name (memorable alternative) +docker pull ghcr.io/nanotaboada/dotnet-samples-aspnetcore-webapi:azteca + +# Latest release +docker pull ghcr.io/nanotaboada/dotnet-samples-aspnetcore-webapi:latest +``` + +> 💡 **Note:** See [CHANGELOG.md](CHANGELOG.md) for the complete stadium list (A-Z) and release history. + ## Environment Variables The application can be configured using environment variables for different scenarios: