Files
dance-lessons-coach/.gitea/workflows/ci-cd.yaml
Gabriel Radureau 9f89132a1f 🤖 feat: enable badge updates on all branches
Separate concerns properly:
- Badge updates: run on all branches to keep documentation accurate
- Docker builds: only on main branch for production deployments

This follows best practices where documentation should be updated
regardless of branch, but deployments only happen from main.
2026-04-08 15:32:08 +02:00

353 lines
13 KiB
YAML
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
---
# dance-lessons-coach Unified CI/CD Workflow
# Single, optimized workflow that replaces all previous workflows
# Fast execution with minimal repetition and maximum artifact sharing
name: CI/CD Pipeline
on:
workflow_dispatch: {}
push:
branches:
- main
- 'ci/**'
- 'feature/**'
- 'fix/**'
- 'refactor/**'
paths-ignore:
- 'README.md'
- 'doc/**'
- 'adr/**'
- '.gitea/**'
- 'documentation/**'
- '*.md'
- '.vibe/**'
- 'features/**'
pull_request:
branches:
- main
types: [opened, synchronize, reopened, labeled]
# Only run PR CI if the commit doesn't already have passing branch CI
if: |
github.event_name == 'pull_request' &&
(github.event.action == 'opened' ||
github.event.action == 'synchronize' ||
github.event.action == 'reopened')
paths-ignore:
- 'README.md'
- 'doc/**'
- 'adr/**'
- '.gitea/**'
- 'documentation/**'
- '*.md'
- '.vibe/**'
- 'features/**'
# cancel any previously-started runs of this workflow on the same branch
concurrency:
group: ${{ github.ref }}-${{ github.workflow }}
cancel-in-progress: true
# Arcodange-specific environment variables
env:
GITEA_INTERNAL: "https://gitea.arcodange.lab/"
GITEA_EXTERNAL: "https://gitea.arcodange.fr/"
GITEA_ORG: "arcodange"
GITEA_REPO: "dance-lessons-coach"
CI_REGISTRY: "gitea.arcodange.lab"
jobs:
build-cache:
name: Build Docker Cache
runs-on: ubuntu-latest-ca
if: "!contains(github.event.head_commit.message, '[skip ci]') && github.actor != 'ci-bot'"
outputs:
deps_hash: ${{ steps.calculate_hash.outputs.deps_hash }}
cache_hit: ${{ steps.check_cache.outputs.cache_hit }}
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Calculate dependency hash
id: calculate_hash
run: |
# Calculate hash of go.mod + go.sum + Dockerfile.build (inline, no script needed)
DEPS_HASH=$(sha256sum go.mod go.sum docker/Dockerfile.build | sha256sum | cut -d' ' -f1 | head -c 12)
echo "Dependency hash: $DEPS_HASH"
echo "deps_hash=$DEPS_HASH" >> $GITHUB_OUTPUT
- name: Check for existing cache (optimized with fallback)
id: check_cache
run: |
# Check if image exists in registry using optimized approach with fallback
IMAGE_NAME="${{ env.CI_REGISTRY }}/${{ env.GITEA_ORG }}/${{ env.GITEA_REPO }}-build-cache:${{ steps.calculate_hash.outputs.deps_hash }}"
# Fast check using docker manifest inspect (lighter than pull)
echo "🔍 Checking cache: $IMAGE_NAME"
# Try manifest inspect first (fastest method, but experimental)
if docker manifest inspect "$IMAGE_NAME" >/dev/null 2>&1; then
echo "✅ Cache hit - using existing build cache (manifest inspect)"
echo "cache_hit=true" >> $GITHUB_OUTPUT
else
# Fallback to docker pull if manifest inspect fails (more reliable)
echo "⚠️ Manifest inspect failed, falling back to docker pull..."
if docker pull "$IMAGE_NAME" >/dev/null 2>&1; then
echo "✅ Cache hit - using existing build cache (fallback: docker pull)"
echo "cache_hit=true" >> $GITHUB_OUTPUT
else
echo "⚠️ Cache miss - will build new cache image"
echo "cache_hit=false" >> $GITHUB_OUTPUT
fi
fi
- name: Login to Gitea Container Registry
if: steps.check_cache.outputs.cache_hit == 'false'
uses: docker/login-action@v3
with:
registry: ${{ env.CI_REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.PACKAGES_TOKEN }}
- name: Build and push Docker cache image
if: steps.check_cache.outputs.cache_hit == 'false'
run: |
IMAGE_NAME="${{ env.CI_REGISTRY }}/${{ env.GITEA_ORG }}/${{ env.GITEA_REPO }}-build-cache:${{ steps.calculate_hash.outputs.deps_hash }}"
echo "Building cache image: $IMAGE_NAME"
# Build the image using traditional docker build
docker build \
--file docker/Dockerfile.build \
--tag "$IMAGE_NAME" \
.
# Push the image
docker push "$IMAGE_NAME"
echo "✅ Build cache image pushed successfully"
ci-pipeline:
name: CI Pipeline
needs: build-cache
runs-on: ubuntu-latest-ca
if: "!contains(github.event.head_commit.message, '[skip ci]') && github.actor != 'ci-bot'"
container:
image: ${{ env.CI_REGISTRY }}/${{ env.GITEA_ORG }}/${{ env.GITEA_REPO }}-build-cache:${{ needs.build-cache.outputs.deps_hash }}
services:
postgres:
image: postgres:15
env:
POSTGRES_USER: postgres
POSTGRES_PASSWORD: postgres
POSTGRES_DB: dance_lessons_coach_bdd_test
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Set database environment variables
run: |
echo "DLC_DATABASE_HOST=postgres" >> $GITHUB_ENV
echo "DLC_DATABASE_PORT=5432" >> $GITHUB_ENV
echo "DLC_DATABASE_USER=postgres" >> $GITHUB_ENV
echo "DLC_DATABASE_PASSWORD=postgres" >> $GITHUB_ENV
echo "DLC_DATABASE_NAME=dance_lessons_coach_bdd_test" >> $GITHUB_ENV
echo "DLC_DATABASE_SSL_MODE=disable" >> $GITHUB_ENV
- name: Generate Swagger Docs
run: go generate ./pkg/server
- name: Build all packages
run: go build ./...
- name: Wait for PostgreSQL to be ready
run: |
echo "Waiting for PostgreSQL to be ready..."
for i in {1..30}; do
if pg_isready -h postgres -p 5432 -U postgres -d dance_lessons_coach_bdd_test; then
echo "✅ PostgreSQL is ready!"
break
fi
echo "Waiting for PostgreSQL... ($i/30)"
sleep 2
done
# Verify PostgreSQL is accessible
if ! pg_isready -h postgres -p 5432 -U postgres -d dance_lessons_coach_bdd_test; then
echo "❌ PostgreSQL failed to start"
exit 1
fi
- name: Run BDD tests with strict validation and coverage
run: |
echo "Running BDD tests with strict validation and coverage..."
# Use the run-bdd-tests.sh script which fails on undefined/pending steps
# In CI environment, PostgreSQL is already running as a service
export DLC_DATABASE_HOST=postgres
export DLC_DATABASE_PORT=5432
export DLC_DATABASE_USER=postgres
export DLC_DATABASE_PASSWORD=postgres
export DLC_DATABASE_NAME=dance_lessons_coach_bdd_test
export DLC_DATABASE_SSL_MODE=disable
./scripts/run-bdd-tests.sh
# Generate BDD coverage report
go tool cover -func=coverage.out > bdd_coverage.txt
# Extract BDD coverage percentage and set as environment variable
BDD_COVERAGE=$(grep "total:" bdd_coverage.txt | grep -oP '\d+\.\d+' | head -1)
echo "BDD Coverage: ${BDD_COVERAGE}%"
echo "DLC_BDD_COVERAGE=${BDD_COVERAGE}%" >> $GITHUB_ENV
- name: Run unit tests with coverage
run: |
echo "Running unit tests with PostgreSQL service..."
# Run unit tests excluding BDD tests (already run above)
go test ./pkg/... ./cmd/... -coverprofile=unit_coverage.out -v
# Generate unit coverage report
go tool cover -func=unit_coverage.out > unit_coverage.txt
# Extract unit test coverage percentage and set as environment variable
UNIT_COVERAGE=$(grep "total:" unit_coverage.txt | grep -oP '\d+\.\d+' | head -1)
echo "Unit Coverage: ${UNIT_COVERAGE}%"
echo "DLC_UNIT_COVERAGE=${UNIT_COVERAGE}%" >> $GITHUB_ENV
- name: Run go fmt
run: go fmt ./...
- name: Run swag fmt
run: swag fmt
- name: Build binaries
run: ./scripts/build.sh
# NOTE: Artifact upload disabled - actions/upload-artifact@v4 not available on Gitea
# TODO: Replace with Gitea-specific upload action when available
# - name: Upload Swagger documentation
# uses: actions/upload-artifact@v4
# with:
# name: swagger-docs
# path: pkg/server/docs/swagger.json
# retention-days: 1
# Single badge update step using environment variables (KISS approach)
# Badge updates should run on all branches to keep documentation accurate
- name: Update all badges (single commit)
if: always() && github.actor != 'ci-bot'
run: |
echo "🎯 Updating all badges using environment variables..."
echo "BDD Coverage: ${DLC_BDD_COVERAGE:-Not set}"
echo "Unit Coverage: ${DLC_UNIT_COVERAGE:-Not set}"
# Extract coverage values (remove % sign)
BDD_COV=${DLC_BDD_COVERAGE%"%"}
UNIT_COV=${DLC_UNIT_COVERAGE%"%"}
# Update badges only if values are set
if [ -n "$BDD_COV" ] || [ -n "$UNIT_COV" ]; then
./scripts/update-all-badges.sh "$BDD_COV" "$UNIT_COV"
# Configure git
git config user.name "CI Bot"
git config user.email "ci@arcodange.fr"
git add README.md
# Commit only if there are changes
if git commit -m "🤖 chore: update coverage badges [skip ci]"; then
echo "✅ Badges updated, committing changes..."
git push
echo "🎉 Successfully pushed badge updates"
else
echo " No badge changes to commit"
fi
else
echo "⚠️ No coverage data available to update badges"
fi
# Docker build and push (main branch only)
- name: Login to Gitea Container Registry
if: github.ref == 'refs/heads/main'
uses: docker/login-action@v3
with:
registry: ${{ env.CI_REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.PACKAGES_TOKEN }}
- name: Build and push Docker image
if: github.ref == 'refs/heads/main'
run: |
source VERSION
IMAGE_VERSION="$MAJOR.$MINOR.$PATCH${PRERELEASE:+-$PRERELEASE}"
# Generate Dockerfile.prod with correct dependency hash
DEPS_HASH="${{ needs.build-cache.outputs.deps_hash }}"
echo "Using dependency hash: $DEPS_HASH"
# Create Dockerfile.prod with the correct cache image tag
cat > docker/Dockerfile.prod << EOF
# dance-lessons-coach Production Docker Image
# Generated by CI/CD pipeline with dependency hash: $DEPS_HASH
# Use the build cache image as base
FROM gitea.arcodange.lab/arcodange/dance-lessons-coach-build-cache:$DEPS_HASH AS builder
# Final minimal image
FROM alpine:3.18
WORKDIR /app
# Install minimal dependencies
RUN apk add --no-cache ca-certificates tzdata
# Copy binary from builder
COPY --from=builder /workspace/dance-lessons-coach /app/dance-lessons-coach
# Copy configuration
COPY config.yaml /app/config.yaml
# Set permissions
RUN chmod +x /app/dance-lessons-coach
# Set timezone
ENV TZ=UTC
# Expose port
EXPOSE 8080
# Health check
HEALTHCHECK --interval=30s --timeout=3s \
CMD wget -q --spider http://localhost:8080/api/health || exit 1
# Entry point
ENTRYPOINT ["/app/dance-lessons-coach"]
EOF
TAGS="$IMAGE_VERSION latest ${{ github.sha }}"
echo "Building Docker image with tags: $TAGS"
# Build the production image
docker build -t dance-lessons-coach -f docker/Dockerfile.prod .
for TAG in $TAGS; do
IMAGE_NAME="${{ env.CI_REGISTRY }}/${{ env.GITEA_ORG }}/${{ env.GITEA_REPO }}:$TAG"
echo "Tagging and pushing: $IMAGE_NAME"
docker tag dance-lessons-coach "$IMAGE_NAME"
docker push "$IMAGE_NAME"
done
- name: Show published images
if: github.ref == 'refs/heads/main'
run: |
source VERSION
IMAGE_VERSION="$MAJOR.$MINOR.$PATCH${PRERELEASE:+-$PRERELEASE}"
echo "📦 Published Docker images:"
echo " - ${{ env.CI_REGISTRY }}/${{ env.GITEA_ORG }}/${{ env.GITEA_REPO }}:$IMAGE_VERSION"
echo " - ${{ env.CI_REGISTRY }}/${{ env.GITEA_ORG }}/${{ env.GITEA_REPO }}:latest"
echo " - ${{ env.CI_REGISTRY }}/${{ env.GITEA_ORG }}/${{ env.GITEA_REPO }}:${{ github.sha }}"