- Fix step pattern escaping in pkg/bdd/steps/steps.go:80 - Update CI/CD workflow to use run-bdd-tests.sh script - Enhance run-bdd-tests.sh for both local and CI environments - Add strict validation for undefined/pending/skipped steps - Update BDD testing documentation with pattern requirements The CI/CD pipeline now properly validates BDD tests and fails on any undefined, pending, or skipped steps. All 22 BDD scenarios are passing with correct step pattern registration.
374 lines
13 KiB
Plaintext
374 lines
13 KiB
Plaintext
---
|
|
# 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: 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'"
|
|
|
|
steps:
|
|
- name: Checkout code
|
|
uses: actions/checkout@v4
|
|
|
|
- name: Install Docker Compose
|
|
run: sudo apt-get update && sudo apt-get install -y docker-compose-plugin
|
|
|
|
- name: Start PostgreSQL with Docker Compose
|
|
run: docker compose -f docker-compose.yml up -d postgres
|
|
|
|
- name: Wait for PostgreSQL to be ready
|
|
run: |
|
|
echo "Waiting for PostgreSQL to be ready..."
|
|
for i in {1..30}; do
|
|
if docker exec dance-lessons-coach-postgres pg_isready -U postgres; then
|
|
echo "✅ PostgreSQL is ready!"
|
|
break
|
|
fi
|
|
echo "Waiting for PostgreSQL... ($i/30)"
|
|
sleep 2
|
|
done
|
|
|
|
# Verify PostgreSQL is accessible
|
|
if ! docker exec dance-lessons-coach-postgres pg_isready -U postgres; then
|
|
echo "❌ PostgreSQL failed to start"
|
|
exit 1
|
|
fi
|
|
|
|
steps:
|
|
- name: Checkout code
|
|
uses: actions/checkout@v4
|
|
|
|
- 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: Check dependencies
|
|
run: |
|
|
if [ "${{ env.CACHE_AVAILABLE }}" = "true" ]; then
|
|
echo "✅ Using pre-installed dependencies from Docker cache"
|
|
# No need to run go mod tidy - dependencies are already in the cache
|
|
else
|
|
echo "Running natively - ensuring dependencies are up to date..."
|
|
go mod tidy
|
|
fi
|
|
|
|
- name: Start build cache container with Docker Compose
|
|
run: |
|
|
if [ "${{ env.CACHE_AVAILABLE }}" = "true" ]; then
|
|
echo "Starting build cache container..."
|
|
export DEPS_HASH="${{ needs.build-cache.outputs.deps_hash }}"
|
|
docker compose -f docker-compose.build.yml up -d build-cache
|
|
fi
|
|
|
|
- name: Generate Swagger Docs using Docker Compose
|
|
run: |
|
|
if [ "${{ env.CACHE_AVAILABLE }}" = "true" ]; then
|
|
echo "Running in Docker Compose container..."
|
|
docker compose -f docker-compose.build.yml exec -w /workspace/pkg/server build-cache sh -c "go generate"
|
|
else
|
|
echo "Running natively..."
|
|
cd pkg/server && go generate
|
|
fi
|
|
|
|
- name: Build all packages using Docker Compose
|
|
run: |
|
|
if [ "${{ env.CACHE_AVAILABLE }}" = "true" ]; then
|
|
echo "Running in Docker Compose container..."
|
|
docker compose -f docker-compose.build.yml exec -w /workspace build-cache sh -c "go build ./..."
|
|
else
|
|
echo "Running natively..."
|
|
go build ./...
|
|
fi
|
|
|
|
- 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 localhost -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 localhost -p 5432 -U postgres -d dance_lessons_coach_bdd_test; then
|
|
echo "❌ PostgreSQL failed to start"
|
|
exit 1
|
|
fi
|
|
|
|
- name: Run tests with coverage using Docker Compose
|
|
run: |
|
|
if [ "${{ env.CACHE_AVAILABLE }}" = "true" ]; then
|
|
echo "Running in Docker Compose container with PostgreSQL..."
|
|
docker compose -f docker-compose.build.yml exec \
|
|
-e PGHOST=dance-lessons-coach-postgres \
|
|
-e PGPORT=5432 \
|
|
-e PGUSER=postgres \
|
|
-e PGPASSWORD=postgres \
|
|
-e PGDATABASE=dance_lessons_coach_bdd_test \
|
|
-w /workspace \
|
|
build-cache \
|
|
sh -c "go test ./... -coverprofile=coverage.out -v && go tool cover -func=coverage.out > coverage.txt"
|
|
else
|
|
echo "Running natively with Docker Compose PostgreSQL..."
|
|
export PGHOST=dance-lessons-coach-postgres
|
|
export PGPORT=5432
|
|
export PGUSER=postgres
|
|
export PGPASSWORD=postgres
|
|
export PGDATABASE=dance_lessons_coach_bdd_test
|
|
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: |
|
|
if [ "${{ env.CACHE_AVAILABLE }}" = "true" ]; then
|
|
echo "Running in Docker Compose container..."
|
|
docker compose -f docker-compose.build.yml exec -w /workspace build-cache sh -c "./scripts/build.sh"
|
|
else
|
|
echo "Running natively..."
|
|
./scripts/build.sh
|
|
fi
|
|
|
|
# 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}"
|
|
|
|
# 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 }}"
|