--- # 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 (inline, no script needed) DEPS_HASH=$(sha256sum go.mod go.sum | sha256sum | cut -d' ' -f1 | head -c 12) echo "Dependency hash: $DEPS_HASH" echo "deps_hash=$DEPS_HASH" >> $GITHUB_OUTPUT - name: Check for existing cache id: check_cache run: | # Check if image exists in registry IMAGE_NAME="${{ env.CI_REGISTRY }}/${{ env.GITEA_ORG }}/${{ env.GITEA_REPO }}-build-cache:${{ steps.calculate_hash.outputs.deps_hash }}" # Try to pull the image to see if it exists if docker pull "$IMAGE_NAME" >/dev/null 2>&1; then echo "✅ Cache hit - using existing build cache" echo "cache_hit=true" >> $GITHUB_OUTPUT else echo "⚠️ Cache miss - will build new cache image" echo "cache_hit=false" >> $GITHUB_OUTPUT fi - name: Configure Docker to trust Gitea self-signed certificate if: steps.check_cache.outputs.cache_hit == 'false' run: | # Create Docker certs directory for our registry sudo mkdir -p /etc/docker/certs.d/${{ env.CI_REGISTRY }} # Copy the CA certificate that's already available in the runner sudo cp /usr/local/share/ca-certificates/root_ca.crt /etc/docker/certs.d/${{ env.CI_REGISTRY }}/ca.crt # Update CA certificates and restart Docker sudo update-ca-certificates sudo systemctl restart docker || echo "Docker restart may not be supported in this environment" echo "✅ Docker configured to trust Gitea registry certificate" - 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 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'" steps: - name: Checkout code uses: actions/checkout@v4 - name: Login to Gitea Container Registry uses: docker/login-action@v3 with: registry: ${{ env.CI_REGISTRY }} username: ${{ github.actor }} password: ${{ secrets.PACKAGES_TOKEN }} - name: Set up build environment run: | IMAGE_NAME="${{ env.CI_REGISTRY }}/${{ env.GITEA_ORG }}/${{ env.GITEA_REPO }}-build-cache:${{ needs.build-cache.outputs.deps_hash }}" echo "Build cache image: $IMAGE_NAME" # Try to use Docker cache if available if docker pull "$IMAGE_NAME" >/dev/null 2>&1; then echo "✅ Using Docker build cache" echo "CACHE_AVAILABLE=true" >> $GITHUB_ENV echo "CACHE_IMAGE=$IMAGE_NAME" >> $GITHUB_ENV else echo "⚠️ Building without cache (first run or new dependencies)" echo "CACHE_AVAILABLE=false" >> $GITHUB_ENV fi - name: Generate Swagger Docs using Docker cache run: | if [ "${{ env.CACHE_AVAILABLE }}" = "true" ]; then echo "Running in Docker cache..." docker run --rm -v "$(pwd):/workspace" -w /workspace ${{ env.CACHE_IMAGE }} sh -c "cd pkg/server && go generate" else echo "Running natively..." cd pkg/server && go generate fi - name: Build all packages using Docker cache run: | if [ "${{ env.CACHE_AVAILABLE }}" = "true" ]; then echo "Running in Docker cache..." docker run --rm -v "$(pwd):/workspace" -w /workspace ${{ env.CACHE_IMAGE }} sh -c "go build ./..." else echo "Running natively..." go build ./... fi - name: Run tests with coverage using Docker cache run: | if [ "${{ env.CACHE_AVAILABLE }}" = "true" ]; then echo "Running in Docker cache..." docker run --rm \ -v "$(pwd):/workspace" \ -w /workspace \ ${{ env.CACHE_IMAGE }} \ sh -c "go test ./... -coverprofile=coverage.out -v && go tool cover -func=coverage.out > coverage.txt" else echo "Running natively..." go test ./... -coverprofile=coverage.out -v go tool cover -func=coverage.out > coverage.txt fi # Extract coverage percentage COVERAGE=$(grep "total:" coverage.txt | grep -oP '\d+\.\d+' | head -1) echo "Coverage: ${COVERAGE}%" # Update coverage badge using script export PACKAGES_TOKEN="${{ secrets.PACKAGES_TOKEN }}" export GITHUB_REF_NAME="${{ github.ref_name }}" ./scripts/ci-update-coverage-badge.sh "$COVERAGE" - 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 # 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}" TAGS="$IMAGE_VERSION latest ${{ github.sha }}" echo "Building Docker image with tags: $TAGS" # Use the production Dockerfile that leverages the build cache docker build -t dance-lessons-coach -f 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 }}"