ci/trunk-based-development #1

Merged
arcodange merged 11 commits from ci/trunk-based-development into main 2026-04-06 13:20:01 +02:00
9 changed files with 621 additions and 80 deletions
Showing only changes of commit 7c6075e836 - Show all commits

View File

@@ -0,0 +1,83 @@
---
# DanceLessonsCoach Docker Image Build Workflow
# Based on Arcodange webapp conventions
# Builds and publishes Docker images to Gitea Container Registry
name: Docker Build and Publish
on:
workflow_dispatch: {}
push:
branches:
- main
tags:
- 'v*.*.*'
paths-ignore:
- 'README.md'
- 'doc/**'
- 'adr/**'
- '.gitea/**'
# cancel any previously-started, yet still active 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: "DanceLessonsCoach"
CI_REGISTRY: "gitea.arcodange.lab"
jobs:
build-and-push-image:
name: Build and Push Docker Image
runs-on: ubuntu-latest
steps:
- name: Login to Gitea Container Registry
uses: docker/login-action@v3
with:
registry: ${{ env.CI_REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.PACKAGES_TOKEN }}
- name: Checkout code
uses: actions/checkout@v4
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Build and push image to Gitea Container Registry
run: |-
# Determine tags based on event type
if [[ "${{ github.ref }}" == refs/tags/* ]]; then
# For tags, use the tag name and latest
TAGS="${{ github.ref_name }} latest"
else
# For main branch, use commit SHA and latest
TAGS="latest ${{ github.sha }}"
fi
echo "Building Docker image with tags: $TAGS"
docker build -t dance-lessons-coach .
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
run: |-
echo "📦 Published Docker images:"
if [[ "${{ github.ref }}" == refs/tags/* ]]; then
echo " - ${{ env.CI_REGISTRY }}/${{ env.GITEA_ORG }}/${{ env.GITEA_REPO }}:${{ github.ref_name }}"
echo " - ${{ env.CI_REGISTRY }}/${{ env.GITEA_ORG }}/${{ env.GITEA_REPO }}:latest"
else
echo " - ${{ env.CI_REGISTRY }}/${{ env.GITEA_ORG }}/${{ env.GITEA_REPO }}:latest"
echo " - ${{ env.CI_REGISTRY }}/${{ env.GITEA_ORG }}/${{ env.GITEA_REPO }}:${{ github.sha }}"
fi

View File

@@ -3,10 +3,10 @@
# Follows Arcodange conventions from webapp workflow
# Uses .gitea/workflows/ directory and internal registry
name: DanceLessonsCoach CI/CD
name: Go CI/CD Pipeline
on:
workflow_dispatch: true
workflow_dispatch: {}
push:
branches:
- main
@@ -18,10 +18,16 @@ on:
- 'README.md'
- 'doc/**'
- 'adr/**'
- '.gitea/**'
pull_request:
branches:
- main
types: [opened, synchronize, reopened, labeled]
paths-ignore:
- 'README.md'
- 'doc/**'
- 'adr/**'
- '.gitea/**'
# cancel any previously-started runs of this workflow on the same branch
concurrency:
@@ -94,7 +100,7 @@ jobs:
echo "✅ Code is properly formatted"
workflow-validation:
name: Workflow Validation
name: Arcodange Workflow Validation
runs-on: ubuntu-latest
if: github.event_name == 'pull_request' || contains(github.ref, 'ci/')
@@ -102,25 +108,16 @@ jobs:
- name: Checkout code
uses: actions/checkout@v4
- name: Validate workflow syntax
run: |
if command -v yq >/dev/null 2>&1; then
yq eval '.' .gitea/workflows/ci-cd.yaml > /dev/null
echo "✅ Workflow YAML syntax is valid"
else
echo "⚠️ yq not installed, skipping YAML validation"
fi
- name: Run workflow validation script
- name: Run Arcodange workflow validation
run: ./scripts/cicd/validate-workflow.sh
- name: Check for breaking changes
- name: Check for workflow changes in PR
if: github.event_name == 'pull_request'
run: |
echo "🔍 Checking workflow changes..."
changes=$(git diff origin/main -- .gitea/workflows/ci-cd.yaml | grep -q "^-")
changes=$(git diff origin/main -- .gitea/workflows/ | grep -q "^-")
if [ $changes ]; then
echo "⚠️ Changes detected - review recommended"
echo "⚠️ Workflow changes detected - review recommended"
else
echo "✅ No workflow changes"
fi

47
Dockerfile Normal file
View File

@@ -0,0 +1,47 @@
# DanceLessonsCoach Docker Image
# Multi-stage build for production deployment
# Stage 1: Build binary
FROM golang:1.26.1-alpine AS builder
WORKDIR /app
# Copy go mod files
COPY go.mod go.sum ./
RUN go mod download
# Copy source code
COPY . ./
# Build binary
RUN CGO_ENABLED=0 GOOS=linux go build -o /dance-lessons-coach ./cmd/server
# Stage 2: Final image
FROM alpine:3.18
WORKDIR /app
# Install dependencies
RUN apk add --no-cache ca-certificates tzdata
# Copy binary from builder
COPY --from=builder /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"]

286
scripts/cicd/README.md Normal file
View File

@@ -0,0 +1,286 @@
# CI/CD Scripts for DanceLessonsCoach
## 🚀 Quick Start for Contributors
### You Only Need These Commands
```bash
# 1. Run tests (this is what matters most!)
go test ./...
# 2. Build binaries
./scripts/build.sh
# 3. Check formatting
go fmt ./...
# That's it! The CI/CD pipeline will handle the rest when you create a PR.
```
## 📖 Understanding the CI/CD Pipeline
### What Happens Automatically
When you push code or create a PR, GitHub Actions runs:
1. **Go CI/CD Pipeline** (`.gitea/workflows/go-ci-cd.yaml`)
- Builds all Go packages
- Runs tests with coverage
- Checks code formatting
- Validates workflow structure
2. **Docker Image Pipeline** (`.gitea/workflows/dockerimage.yaml`)
- Builds Docker image (on main branch only)
- Publishes to Gitea Container Registry
- Tags with version and commit SHA
### When Does It Run?
| Event | Go CI/CD | Docker Image |
|-------|---------|--------------|
| Push to `main` | ✅ Yes | ✅ Yes |
| Push to `feature/*` | ✅ Yes | ❌ No |
| Push to `fix/*` | ✅ Yes | ❌ No |
| Push to `ci/*` | ✅ Yes | ❌ No |
| Pull Request | ✅ Yes | ❌ No |
| Manual trigger | ✅ Yes | ✅ Yes |
## 🧪 Local Testing Options
### Option 1: Simple Validation (No Docker Required)
```bash
# Just run the essentials
./scripts/cicd/contributor-quickstart.sh
```
This checks:
- ✅ Go installation
- ✅ All tests pass
- ✅ Code formatting
- ✅ Go vet analysis
- ✅ Workflow structure
### Option 2: Docker-Based Testing (Recommended)
```bash
# Test workflow compatibility with GitHub Actions
./scripts/cicd/test-act-local.sh
```
**Requirements:**
- Docker installed and running
- Internet connection (to pull images)
**What it does:**
- Validates YAML syntax
- Checks workflow structure
- Simulates GitHub Actions execution
- Tests both workflow files
### Option 3: Full CI/CD Simulation
```bash
# Complete local simulation
./scripts/cicd/test-cicd-simple.sh
```
**Requirements:**
- Docker installed and running
- More time (pulls multiple images)
**What it does:**
- YAML linting
- YAML validation
- Workflow structure validation
- Simulates build job
- Runs actual Go tests in containers
## 🐳 Docker Setup Guide
### For Windows Users
1. **Install Docker Desktop**
- Download: https://www.docker.com/products/docker-desktop/
- Enable WSL 2 backend (recommended)
- Allocate at least 4GB RAM
2. **Verify Installation**
```powershell
docker --version
docker run hello-world
```
### For macOS Users
1. **Install Docker Desktop**
- Download: https://www.docker.com/products/docker-desktop/
- Grant necessary permissions
2. **Verify Installation**
```bash
docker --version
docker run hello-world
```
### For Linux Users
1. **Install Docker Engine**
```bash
# Ubuntu/Debian
sudo apt-get update
sudo apt-get install docker.io docker-compose
sudo systemctl enable docker
sudo systemctl start docker
# Add user to docker group (avoid sudo)
sudo usermod -aG docker $USER
newgrp docker # Reload group membership
```
2. **Verify Installation**
```bash
docker --version
docker run hello-world
```
## 🔧 Troubleshooting
### Docker Permission Issues
**Symptom:** `Got permission denied while trying to connect to the Docker daemon socket`
**Solution:**
```bash
# Linux/macOS
sudo usermod -aG docker $USER
newgrp docker
# Windows
Right-click Docker Desktop → Settings → Resources → WSL Integration → Enable
```
### Docker Not Running
**Symptom:** `Cannot connect to the Docker daemon`
**Solution:**
- Windows/macOS: Open Docker Desktop app
- Linux: `sudo systemctl start docker`
### Network Issues
**Symptom:** `Cannot pull Docker images`
**Solution:**
```bash
# Check internet connection
ping google.com
# Try pulling manually first
docker pull mikefarah/yq:latest
docker pull pipelinecomponents/yamllint:latest
```
### act Not Installed
**Symptom:** `act not found` in `test-act-local.sh`
**Solution:**
```bash
# Install act (optional - only needed for test-act-local.sh)
# macOS
brew install act
# Linux
curl https://raw.githubusercontent.com/nektos/act/master/install.sh | sudo bash
# Windows (WSL)
curl https://raw.githubusercontent.com/nektos/act/master/install.sh | sudo bash
```
## 📚 Script Reference
| Script | Purpose | Docker Required? | act Required? |
|--------|---------|------------------|---------------|
| `contributor-quickstart.sh` | Basic validation | ❌ No | ❌ No |
| `validate-workflow.sh` | Workflow structure | ❌ No | ❌ No |
| `test-act-local.sh` | GitHub Actions compatibility | ✅ Yes | ✅ Yes |
| `test-cicd-simple.sh` | Full CI/CD simulation | ✅ Yes | ❌ No |
## 🎯 Best Practices
### Before Submitting a PR
1. **Run tests locally**
```bash
go test ./...
```
2. **Check formatting**
```bash
go fmt ./...
```
3. **Build binaries**
```bash
./scripts/build.sh
```
4. **Validate workflows** (optional)
```bash
./scripts/cicd/validate-workflow.sh
```
### Working with the CI/CD Pipeline
- **Don't worry about Docker images** - The pipeline builds them automatically
- **Focus on tests** - If tests pass locally, they'll pass in CI/CD
- **Check PR status** - GitHub will show CI/CD results automatically
- **Fix failures** - If CI/CD fails, check the logs and fix issues
## 🔗 Useful Links
- **GitHub Actions Docs**: https://docs.github.com/en/actions
- **Docker Docs**: https://docs.docker.com/
- **act GitHub**: https://github.com/nektos/act
- **DanceLessonsCoach CI/CD**: See `.gitea/workflows/` directory
## 💡 Pro Tips
### Speed Up Local Testing
```bash
# Pull Docker images in advance
docker pull mikefarah/yq:latest
docker pull pipelinecomponents/yamllint:latest
docker pull node:16-buster-slim
```
### Test Specific Workflows
```bash
# Test Go CI/CD workflow only
act -W .gitea/workflows/go-ci-cd.yaml
# Test Docker workflow only
act -W .gitea/workflows/dockerimage.yaml
```
### Dry Run (No Execution)
```bash
# Check workflow syntax without running
echo 'm' | act -n -W .gitea/workflows/go-ci-cd.yaml
```
## 📞 Need Help?
If you're stuck with CI/CD setup:
1. **Check this documentation** - Most issues are covered here
2. **Run contributor-quickstart.sh** - It validates the essentials
3. **Ask in the PR** - We'll help you resolve any issues
4. **Check CI/CD logs** - GitHub shows detailed error messages
Remember: **You don't need to run CI/CD locally to contribute!** The pipeline runs automatically when you push code.

View File

@@ -0,0 +1,78 @@
#!/bin/bash
# Simple CI/CD validation for new contributors
# Works without Docker - just validates the essentials
set -e
echo "🚀 DanceLessonsCoach Contributor Quick Start"
echo "=========================================="
echo ""
echo "This script helps you validate your changes before submitting a PR."
echo "It doesn't require Docker or complex setup."
echo ""
# 1. Check Go is installed
echo "1. Checking Go installation..."
if ! command -v go >/dev/null 2>&1; then
echo "❌ Go is not installed. Please install Go 1.26.1+"
echo " Download: https://go.dev/dl/"
exit 1
fi
go_version=$(go version | grep -o 'go[0-9.]*')
echo "✅ Go $go_version found"
# 2. Run Go tests
echo ""
echo "2. Running Go tests..."
if go test ./...; then
echo "✅ All Go tests passed"
else
echo "❌ Some tests failed. Please fix and try again."
exit 1
fi
# 3. Check formatting
echo ""
echo "3. Checking code formatting..."
if [ -n "$(go fmt ./...)" ]; then
echo "❌ Code formatting issues found"
echo " Run: go fmt ./..."
exit 1
fi
echo "✅ Code is properly formatted"
# 4. Run Go vet
echo ""
echo "4. Running Go vet..."
if go vet ./...; then
echo "✅ Go vet passed"
else
echo "❌ Go vet found issues"
exit 1
fi
# 5. Validate workflows (no Docker required)
echo ""
echo "5. Validating CI/CD workflows..."
if [ -f "scripts/cicd/validate-workflow.sh" ]; then
if ./scripts/cicd/validate-workflow.sh; then
echo "✅ Workflow validation passed"
else
echo "⚠️ Workflow validation issues (not critical)"
fi
else
echo " Workflow validation script not found"
fi
echo ""
echo "🎉 All checks passed!"
echo "=========================================="
echo ""
echo "Your changes are ready to submit! 🚀"
echo ""
echo "Next steps:"
echo " 1. Commit your changes: git commit -m 'feat: your feature'"
echo " 2. Push to your branch: git push origin your-branch"
echo " 3. Create a Pull Request"
echo ""
echo "The CI/CD pipeline will run automatically on your PR!"

View File

@@ -15,37 +15,61 @@ if ! command -v act >/dev/null 2>&1; then
exit 1
fi
# Check if workflow file exists
if [ ! -f ".gitea/workflows/ci-cd.yaml" ]; then
echo "❌ Workflow file not found: .gitea/workflows/ci-cd.yaml"
exit 1
fi
# Check if workflow files exist
WORKFLOW_FILES=(
".gitea/workflows/go-ci-cd.yaml"
".gitea/workflows/dockerimage.yaml"
)
for file in "${WORKFLOW_FILES[@]}"; do
if [ ! -f "$file" ]; then
echo "❌ Workflow file not found: $file"
exit 1
fi
done
echo "✅ act installed and workflow file found"
echo ""
# 1. Dry run (syntax check only)
echo "1. Running dry run (syntax validation)..."
if echo 'm' | act -n -W .gitea/workflows/ci-cd.yaml --container-architecture linux/amd64; then
echo "✅ Dry run completed successfully"
ALL_PASSED=true
for file in "${WORKFLOW_FILES[@]}"; do
echo " Testing: $file"
if echo 'm' | act -n -W "$file" --container-architecture linux/amd64; then
echo " ✅ Dry run completed for $file"
else
echo " ❌ Dry run failed for $file"
ALL_PASSED=false
fi
done
if [ "$ALL_PASSED" = true ]; then
echo "✅ All dry runs completed successfully"
else
echo "❌ Dry run failed"
echo "❌ Some dry runs failed"
exit 1
fi
echo ""
echo "🎉 Gitea workflow is compatible with GitHub Actions!"
echo "================================================"
echo "🎉 Gitea workflows are compatible with GitHub Actions!"
echo "=================================================="
echo ""
echo "📋 Summary:"
echo " ✅ Syntax validation passed"
echo " ✅ Syntax validation passed for all workflows"
echo " ✅ All jobs parsed correctly"
echo " ✅ Job dependencies resolved"
echo " ✅ Conditional execution working"
echo " ✅ Gitea/GitHub Actions compatibility confirmed"
echo ""
echo "🚀 You can now test locally without Gitea instance:"
echo " act -n -W .gitea/workflows/ci-cd.yaml # Dry run"
echo " act -W .gitea/workflows/ci-cd.yaml # Full execution"
for file in "${WORKFLOW_FILES[@]}"; do
workflow_name=$(basename "$file" .yaml)
echo " act -n -W $file # Dry run $workflow_name"
echo " act -W $file # Full execution $workflow_name"
done
echo ""
echo "💡 Tip: Add this to your pre-commit hook to validate workflows automatically!"

View File

@@ -17,7 +17,7 @@ fi
# 2. Validate workflow structure
echo "2. Validating workflow structure..."
./scripts/validate-workflow.sh
./scripts/cicd/validate-workflow.sh
# 3. Check docker-compose configuration
echo "3. Checking docker-compose configuration..."

View File

@@ -20,7 +20,10 @@ echo "✅ YAML linting passed"
# 2. YAML Validation
echo "2. Running YAML validation..."
docker run --rm -v $(pwd):/workspace -w /workspace mikefarah/yq:latest eval '.' .gitea/workflows/ci-cd.yaml > /dev/null
WORKFLOW_FILES=(".gitea/workflows/go-ci-cd.yaml" ".gitea/workflows/dockerimage.yaml")
for file in "${WORKFLOW_FILES[@]}"; do
docker run --rm -v $(pwd):/workspace -w /workspace mikefarah/yq:latest eval '.' "$file" > /dev/null
done
echo "✅ YAML validation passed"
# 3. Workflow Structure Validation

View File

@@ -6,69 +6,85 @@ set -e
echo "🔍 Validating CI/CD Workflow"
echo "================================"
# 1. Check workflow file exists
if [ ! -f ".gitea/workflows/ci-cd.yaml" ]; then
echo "❌ Workflow file not found: .gitea/workflows/ci-cd.yaml"
exit 1
fi
# 1. Check workflow files exist
WORKFLOW_FILES=(
".gitea/workflows/go-ci-cd.yaml"
".gitea/workflows/dockerimage.yaml"
)
echo "✅ Workflow file found"
# 2. Validate YAML syntax
if command -v yq >/dev/null 2>&1; then
if ! yq eval '.' .gitea/workflows/ci-cd.yaml > /dev/null 2>&1; then
echo "❌ Invalid YAML syntax"
yq eval '.' .gitea/workflows/ci-cd.yaml || true
for file in "${WORKFLOW_FILES[@]}"; do
if [ ! -f "$file" ]; then
echo "❌ Workflow file not found: $file"
exit 1
fi
echo "✅ YAML syntax valid"
echo "✅ Workflow file found: $file"
done
# 2. Validate YAML syntax for all workflows
if command -v yq >/dev/null 2>&1; then
for file in "${WORKFLOW_FILES[@]}"; do
if ! yq eval '.' "$file" > /dev/null 2>&1; then
echo "❌ Invalid YAML syntax in: $file"
yq eval '.' "$file" || true
exit 1
fi
echo "✅ YAML syntax valid: $file"
done
else
echo "⚠️ yq not installed, skipping YAML validation"
fi
# 3. YAML Linting with custom config
# 3. YAML Linting with custom config for all workflows
if command -v yamllint >/dev/null 2>&1; then
if [ -f ".yamllint.yaml" ]; then
yamllint -c .yamllint.yaml .gitea/workflows/ci-cd.yaml
else
yamllint .gitea/workflows/ci-cd.yaml
fi
for file in "${WORKFLOW_FILES[@]}"; do
if [ -f ".yamllint.yaml" ]; then
yamllint -c .yamllint.yaml "$file"
else
yamllint "$file"
fi
done
elif docker info >/dev/null 2>&1; then
if [ -f ".yamllint.yaml" ]; then
docker run --rm -v $(pwd):/workspace -w /workspace pipelinecomponents/yamllint:latest \
yamllint -c .yamllint.yaml .gitea/workflows/ci-cd.yaml
else
docker run --rm -v $(pwd):/workspace -w /workspace pipelinecomponents/yamllint:latest \
yamllint .gitea/workflows/ci-cd.yaml
fi
for file in "${WORKFLOW_FILES[@]}"; do
if [ -f ".yamllint.yaml" ]; then
docker run --rm -v $(pwd):/workspace -w /workspace pipelinecomponents/yamllint:latest \
yamllint -c .yamllint.yaml "$file"
else
docker run --rm -v $(pwd):/workspace -w /workspace pipelinecomponents/yamllint:latest \
yamllint "$file"
fi
done
else
echo "⚠️ Neither yamllint nor docker available, skipping linting"
fi
# 3. Check required fields
MISSING_FIELDS=()
# 3. Check required fields for all workflows
for file in "${WORKFLOW_FILES[@]}"; do
MISSING_FIELDS=()
if command -v yq >/dev/null 2>&1; then
workflow_name=$(basename "$file" .yaml)
if [ -z "$(yq eval '.name' "$file" 2>/dev/null)" ]; then
MISSING_FIELDS+=("name")
fi
if command -v yq >/dev/null 2>&1; then
if [ -z "$(yq eval '.name' .gitea/workflows/ci-cd.yaml 2>/dev/null)" ]; then
MISSING_FIELDS+=("name")
fi
if [ -z "$(yq eval '.on' "$file" 2>/dev/null)" ]; then
MISSING_FIELDS+=("on")
fi
if [ -z "$(yq eval '.on' .gitea/workflows/ci-cd.yaml 2>/dev/null)" ]; then
MISSING_FIELDS+=("on")
fi
if [ -z "$(yq eval '.jobs' "$file" 2>/dev/null)" ]; then
MISSING_FIELDS+=("jobs")
fi
if [ -z "$(yq eval '.jobs' .gitea/workflows/ci-cd.yaml 2>/dev/null)" ]; then
MISSING_FIELDS+=("jobs")
if [ ${#MISSING_FIELDS[@]} -gt 0 ]; then
echo "❌ Missing required fields in $workflow_name: ${MISSING_FIELDS[*]}"
exit 1
fi
echo "✅ All required fields present in $workflow_name"
else
echo "⚠️ yq not installed, skipping field validation for $file"
fi
if [ ${#MISSING_FIELDS[@]} -gt 0 ]; then
echo "❌ Missing required fields: ${MISSING_FIELDS[*]}"
exit 1
fi
echo "✅ All required fields present"
else
echo "⚠️ yq not installed, skipping field validation"
fi
done
# 4. Check jobs structure
if command -v yq >/dev/null 2>&1; then
@@ -118,10 +134,17 @@ fi
echo ""
echo "🎉 Workflow Validation Successful!"
echo "================================"
echo "📁 Location: .gitea/workflows/ci-cd.yaml"
echo "📁 Workflows validated:"
for file in "${WORKFLOW_FILES[@]}"; do
echo " - $file"
done
if command -v yq >/dev/null 2>&1; then
JOBS=$(yq eval '.jobs | keys | join(", ")' .gitea/workflows/ci-cd.yaml 2>/dev/null || echo 'Unable to parse')
echo "🔧 Jobs: $JOBS"
echo "🔧 Summary:"
for file in "${WORKFLOW_FILES[@]}"; do
workflow_name=$(basename "$file" .yaml)
JOBS=$(yq eval '.jobs | keys | join(", ")' "$file" 2>/dev/null || echo 'Unable to parse')
echo " - $workflow_name: $JOBS"
done
else
echo "🔧 Jobs: yq not installed"
fi