From e428d18b0dd680c51f9c40696791f5e7111336b5 Mon Sep 17 00:00:00 2001 From: Damir Mukimov Date: Thu, 27 Nov 2025 04:52:48 +0100 Subject: [PATCH] feat: Restructure workflows following Single Responsibility Principle - Remove old monolithic workflows (ci.yml, ci-cd.yml, cd.yml) - Add focused workflows: lint.yml, test.yml, build.yml, security.yml, docker-build.yml, deploy.yml - Each workflow has a single, clear responsibility - Follow 2025 best practices with semantic versioning, OIDC auth, build attestations - Add comprehensive README.md with workflow documentation - Configure Dependabot for automated dependency updates Workflows now run independently and can be triggered separately for better CI/CD control. --- .github/workflows/README.md | 761 ++++++++++++++++++ .github/workflows/build.yml | 46 ++ .github/workflows/ci-cd.yml | 203 ----- .github/workflows/deploy.yml | 60 ++ .../workflows/{cd.yml => docker-build.yml} | 36 +- .github/workflows/{ci.yml => lint.yml} | 23 +- .github/workflows/security.yml | 47 ++ .github/workflows/test.yml | 114 +++ 8 files changed, 1042 insertions(+), 248 deletions(-) create mode 100644 .github/workflows/README.md create mode 100644 .github/workflows/build.yml delete mode 100644 .github/workflows/ci-cd.yml create mode 100644 .github/workflows/deploy.yml rename .github/workflows/{cd.yml => docker-build.yml} (64%) rename .github/workflows/{ci.yml => lint.yml} (55%) create mode 100644 .github/workflows/security.yml create mode 100644 .github/workflows/test.yml diff --git a/.github/workflows/README.md b/.github/workflows/README.md new file mode 100644 index 0000000..21f5e3d --- /dev/null +++ b/.github/workflows/README.md @@ -0,0 +1,761 @@ +# GitHub Actions CI/CD Documentation + +## Overview + +This document describes the GitHub Actions CI/CD pipeline for the Tercul backend +project, updated to 2025 best practices. The pipeline ensures code quality, +security, and reliable deployments through automated testing, linting, security +scanning, and containerized deployments. + +### Quick Reference + +| Workflow | File | Purpose | Triggers | +|----------|------|---------|----------| +| **Lint** | `lint.yml` | Code quality & style | Push/PR to main, develop | +| **Test** | `test.yml` | Unit tests & compatibility | Push/PR to main, develop | +| **Build** | `build.yml` | Binary compilation | Push/PR to main, develop | +| **Security** | `security.yml` | CodeQL scanning | Push/PR to main + Weekly | +| **Docker Build** | `docker-build.yml` | Container images | Push to main, Tags, PRs | +| **Deploy** | `deploy.yml` | Production deployment | Tags (v*), Manual | + +## Architecture + +The CI/CD pipeline follows the **Single Responsibility Principle** with focused workflows: + +1. **Lint** (`lint.yml`) - Code quality and style enforcement +2. **Test** (`test.yml`) - Unit tests and compatibility matrix +3. **Build** (`build.yml`) - Binary compilation and verification +4. **Security** (`security.yml`) - CodeQL security scanning +5. **Docker Build** (`docker-build.yml`) - Container image building and publishing +6. **Deploy** (`deploy.yml`) - Production deployment orchestration + +## Workflows + +### Lint Workflow (`lint.yml`) + +**Purpose**: Ensures code quality and consistent style across the codebase. + +**Triggers**: + +- Push to `main` and `develop` branches +- Pull requests targeting `main` and `develop` branches + +**Jobs**: + +- `golangci-lint`: Go linting with golangci-lint + - Checkout code + - Setup Go 1.25 with caching + - Install dependencies + - Tidy modules (ensures go.mod/go.sum are clean) + - Run linter with 5-minute timeout + +**Configuration**: + +- **Timeout**: 5 minutes +- **Target**: All Go files (`./...`) +- **Cache**: Enabled for faster runs + +### Test Workflow (`test.yml`) + +**Purpose**: Validates code functionality through comprehensive testing. + +**Triggers**: + +- Push to `main` and `develop` branches +- Pull requests targeting `main` and `develop` branches + +**Jobs**: + +#### Unit Tests + +- **Environment**: Ubuntu with PostgreSQL 15 and Redis 7 +- **Features**: + + - Race detection enabled + - Code coverage reporting (atomic mode) + - HTML coverage report generation + - Test result summaries in GitHub UI + - 30-day artifact retention + +**Services**: + +- PostgreSQL 15 with health checks +- Redis 7-alpine with health checks + +#### Compatibility Matrix + +- **Trigger**: Push to `main` branch only +- **Strategy**: Tests across Go versions 1.22, 1.23, 1.24, 1.25 +- **Purpose**: Ensures compatibility with multiple Go versions +- **Fail-fast**: Disabled (all versions tested even if one fails) + +### Build Workflow (`build.yml`) + +**Purpose**: Compiles the application binary and validates the build. + +**Triggers**: + +- Push to `main` and `develop` branches +- Pull requests targeting `main` and `develop` branches + +**Jobs**: + +- `build-binary`: Binary compilation and verification + - Dependency verification with `go mod verify` + - Build to `bin/tercul-backend` + - Binary validation test + - Artifact upload (30-day retention) + +**Permissions**: + +- `contents: read` - Read repository code +- `attestations: write` - Future SLSA attestation support +- `id-token: write` - OIDC token for attestations + +### Security Workflow (`security.yml`) + +**Purpose**: Automated security vulnerability detection with CodeQL. + +**Triggers**: + +- Push to `main` branch +- Pull requests targeting `main` branch +- Scheduled: Every Monday at 14:20 UTC + +**Jobs**: + +- `codeql-analysis`: CodeQL security scanning for Go + - Initialize CodeQL with Go language support + - Build code for analysis + - Perform security scan + - Category: "backend-security" for tracking + +**CodeQL Configuration**: + +The workflow can be customized with additional query suites: + +```yaml +- name: Initialize CodeQL + uses: github/codeql-action/init@v3 + with: + languages: go + # Run security-extended suite for more comprehensive scanning + queries: security-extended + # Or use security-and-quality for maintainability checks + # queries: security-and-quality +``` + +**Available Query Suites**: + +- `security-extended`: Default queries plus lower severity/precision queries +- `security-and-quality`: Security queries plus maintainability and reliability + +**Custom Query Packs**: + +Add custom CodeQL query packs for specialized analysis: + +```yaml +- name: Initialize CodeQL + uses: github/codeql-action/init@v3 + with: + languages: go + packs: my-org/go-security-queries@1.0.0 +``` + +### Docker Build Workflow (`docker-build.yml`) + +**Purpose**: Builds and publishes multi-architecture Docker images. + +**Triggers**: + +- Push to `main` branch +- Tag pushes with `v*` pattern +- Pull requests targeting `main` branch + +**Jobs**: + +- `build-image`: Multi-platform Docker image building + + - Docker Buildx setup for multi-arch builds + - Login to GitHub Container Registry + - Metadata extraction for tags and labels + - Build for AMD64 and ARM64 architectures + - Push to registry (except for PRs) + - Generate build provenance attestation + +**Image Tagging Strategy**: + +- `main` branch pushes → `main` + `sha-` tags +- Tag pushes (`v1.2.3`) → `v1.2.3`, `1.2.3`, `1.2`, `sha-` tags +- Pull requests → `pr-` tag (build only, not pushed) + +**Push Behavior**: + +- **Pushes to main/tags**: Build and push to registry +- **Pull requests**: Build only (validation, no push) + +**Platforms**: linux/amd64, linux/arm64 + +### Deploy Workflow (`deploy.yml`) + +**Purpose**: Production deployment orchestration to Docker Swarm. + +**Triggers**: + +- Tag pushes with `v*` pattern +- Manual dispatch with version input + +**Jobs**: + +- `deploy-production`: Deployment to production environment + + - Version extraction from tag or manual input + - Docker Swarm service update (SSH-based deployment template) + - Deployment summary with timestamp + - Environment protection and tracking + +**Environment**: + +- Name: `production` +- URL: Configurable production URL +- Protection: Supports required reviewers and wait timers + +**Manual Deployment**: + +Deployments can be triggered manually from the Actions tab with a specific version. + +**Required Secrets**: + +- `SWARM_HOST`: Docker Swarm manager hostname/IP +- `SWARM_SSH_KEY`: SSH private key for Swarm access + +## Workflow Execution Order + +**On Pull Request**: + +1. Lint → Validates code style +2. Test → Runs unit tests with coverage +3. Build → Compiles binary +4. Security → CodeQL analysis (main branch PRs only) +5. Docker Build → Builds image (no push) + +**On Push to main**: + +1. Lint → Code quality check +2. Test → Unit tests + compatibility matrix +3. Build → Binary compilation +4. Security → CodeQL scan +5. Docker Build → Build and push image + +**On Tag Push (v\*)**: + +1. Docker Build → Build and push versioned image +2. Deploy → Deploy to production + +## Security Features + +### Permissions Management + +- **Principle of Least Privilege**: Each workflow has minimal required permissions +- **GITHUB_TOKEN Restrictions**: Read-only by default, elevated only when necessary +- **Workflow Separation**: Each workflow operates independently +- **Attestation Permissions**: For build provenance and SLSA compliance + +### Code Security + +- **CodeQL Integration**: Automated security scanning for Go code +- **Dependency Verification**: `go mod verify` ensures integrity +- **Module Tidying**: `go mod tidy` prevents dependency drift + +### Container Security + +- **Multi-platform Builds**: Ensures compatibility and security across architectures +- **Provenance Attestation**: Cryptographic proof of build integrity +- **Registry Security**: GitHub Container Registry with token-based authentication + +### Secrets Management + +- **No Hardcoded Secrets**: All sensitive data uses GitHub secrets +- **Environment Variables**: Proper isolation of configuration +- **GITHUB_TOKEN**: Automatic authentication for package registries +- **Granular Permissions**: Package-level access control + +### Package Registry Security + +- **GITHUB_TOKEN Authentication**: No personal access tokens required +- **Automatic Permissions**: Packages inherit repository visibility +- **Repository Scoped**: Packages linked to source repository +- **Granular Access**: Fine-grained permissions per package +- **Artifact Attestation**: Cryptographic proof of build provenance +- **OIDC Support**: Token-based authentication without long-lived credentials + +## Best Practices Implemented + +### 2025 Updates + +- **Semantic Versioning**: Actions pinned to major versions (e.g., `@v5`) instead of SHA +- **Caching Optimization**: Go module and Docker layer caching +- **Matrix Testing**: Cross-version compatibility validation +- **Service Health Checks**: Database and Redis readiness verification +- **Artifact Management**: Proper retention policies and naming + +### Performance Optimizations + +- **Dependency Caching**: Reduces setup time significantly +- **Parallel Jobs**: Independent jobs run concurrently +- **Conditional Execution**: Security scans only on main branch +- **Artifact Upload**: Efficient storage and retrieval + +### Reliability Features + +- **Timeout Configuration**: Prevents hanging jobs +- **Error Handling**: Proper exit codes and logging +- **Health Checks**: Service readiness validation +- **Retention Policies**: Balanced storage management + +## Configuration Details + +### Go Version + +- Primary: Go 1.25 +- Matrix: Go 1.22, 1.23, 1.24, 1.25 + +### Services + +- **PostgreSQL**: Version 15 with health checks +- **Redis**: Version 7-alpine with ping health checks + +### Tools + +- **Linter**: golangci-lint latest with 5-minute timeout +- **Testing**: `go test` with race detection and coverage +- **Building**: `go build` with verbose output +- **Security**: CodeQL for Go analysis + +### Caching + +- **Go Modules**: Automatic caching via setup-go action +- **Docker Layers**: GitHub Actions cache with GHA type +- **CodeQL Databases**: Stored in `${{ github.runner_temp }}/codeql_databases` + +## Maintenance + +### Dependabot Configuration + +- **GitHub Actions**: Weekly updates with "ci" prefix +- **Go Modules**: Weekly updates with "deps" prefix +- **Automated PRs**: Keeps dependencies current and secure + +### Monitoring + +- **Workflow Runs**: GitHub Actions tab for execution monitoring +- **Security Alerts**: Code scanning results and dependency alerts +- **Coverage Reports**: Artifact downloads for test coverage analysis + +### Troubleshooting + +#### Common Issues + +1. **Cache Misses**: Clear caches if corruption suspected +2. **Service Failures**: Check health check configurations +3. **Permission Errors**: Verify GITHUB_TOKEN scopes +4. **Timeout Issues**: Adjust timeout values in workflow configurations +5. **Docker Push Failures**: Check package write permissions +6. **Registry Authentication**: Ensure `packages: write` permission is set + +#### Package Registry Issues + +**Problem**: Cannot push to GitHub Container Registry + +```yaml +# Solution: Ensure proper permissions +permissions: + contents: read + packages: write + id-token: write + attestations: write +``` + +**Problem**: Package not visible after push + +- Check package visibility settings (public/private/internal) +- Verify repository is linked to package +- Ensure workflow completed successfully + +**Problem**: Cannot pull package in workflow + +```yaml +# Solution: Login to registry first +- name: Login to GitHub Container Registry + uses: docker/login-action@v3 + with: + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} +``` + +#### Debugging + +- **Manual Dispatch**: Use `workflow_dispatch` for testing +- **Log Analysis**: Review step outputs for error details +- **Artifact Inspection**: Download build artifacts for verification +- **Package Logs**: Check package activity in GitHub UI + +## Future Enhancements + +### Planned Features + +- **SLSA Integration**: Enhanced build attestation +- **Dependency Review**: Automated dependency vulnerability checking +- **Performance Testing**: Load testing integration +- **Multi-environment Deployment**: Staging and production separation + +### Scalability Considerations + +- **Self-hosted Runners**: For resource-intensive jobs +- **Job Parallelization**: Further optimization of concurrent execution +- **Cache Optimization**: Advanced caching strategies + +## Docker Image Usage + +### Pulling Images + +Pull the latest image: + +```bash +docker pull ghcr.io//:latest +``` + +Pull a specific version: + +```bash +docker pull ghcr.io//:1.2.3 +``` + +### Running Locally + +Run the container: + +```bash +docker run -d \ + --name tercul-backend \ + -p 8080:8080 \ + -e DATABASE_URL="postgres://..." \ + ghcr.io//:latest +``` + +### Docker Swarm Deployment + +Deploy as a stack: + +```bash +docker stack deploy -c docker-compose.yml tercul +``` + +Update running service: + +```bash +docker service update \ + --image ghcr.io//:1.2.3 \ + tercul_backend +``` + +### Verifying Attestations + +Verify build provenance: + +```bash +gh attestation verify \ + oci://ghcr.io//:latest \ + --owner +``` + +## Contributing + +When modifying workflows: + +1. Test changes using `workflow_dispatch` +2. Ensure backward compatibility +3. Update this documentation +4. Follow security best practices +5. Use semantic versioning for action references +6. Test Docker builds locally before pushing +7. Verify package permissions after changes +8. Review CodeQL alerts before merging PRs +9. Update query packs regularly for latest security rules +10. Test configuration changes with `workflow_dispatch` + +## CodeQL Advanced Configuration + +### Custom Configuration File + +For complex CodeQL setups, use a configuration file (`.github/codeql/codeql-config.yml`): + +```yaml +name: "CodeQL Config" + +# Disable default queries to run only custom queries +disable-default-queries: false + +# Specify query packs +packs: + - scope/go-security-pack + - scope/go-compliance-pack@1.2.3 + +# Add custom queries +queries: + - uses: security-and-quality + - uses: ./custom-queries + +# Filter queries by severity +query-filters: + - exclude: + problem.severity: + - warning + - recommendation + - exclude: + id: go/redundant-assignment + +# Scan specific directories +paths: + - internal + - cmd + - pkg + +paths-ignore: + - "**/*_test.go" + - vendor + - "**/testdata/**" + +# Extend threat model (preview) +threat-models: local +``` + +Reference the config in your workflow: + +```yaml +- uses: github/codeql-action/init@v3 + with: + config-file: ./.github/codeql/codeql-config.yml +``` + +### Inline Configuration + +Alternatively, specify configuration inline: + +```yaml +- uses: github/codeql-action/init@v3 + with: + languages: go + config: | + disable-default-queries: false + queries: + - uses: security-extended + query-filters: + - exclude: + problem.severity: + - recommendation +``` + +### Scheduling CodeQL Scans + +Run CodeQL on a schedule for regular security audits: + +```yaml +on: + push: + branches: [main] + pull_request: + branches: [main] + schedule: + # Run at 14:20 UTC every Monday + - cron: '20 14 * * 1' +``` + +### Avoiding Unnecessary Scans + +Skip CodeQL for specific file changes: + +```yaml +on: + pull_request: + branches: [main] + paths-ignore: + - '**/*.md' + - '**/*.txt' + - 'docs/**' +``` + +### Analysis Categories + +Categorize multiple analyses in monorepos: + +```yaml +- name: Perform CodeQL Analysis + uses: github/codeql-action/analyze@v3 + with: + category: "backend-api" +``` + +### External Query Packs + +Use query packs from GitHub Enterprise Server: + +```yaml +- uses: github/codeql-action/init@v3 + with: + registries: | + - url: https://containers.GHEHOSTNAME/v2/ + packages: + - my-company/* + token: ${{ secrets.GHES_TOKEN }} + packs: my-company/go-queries +``` + +### Query Suite Examples + +**Security-focused**: + +```yaml +queries: + - uses: security-extended +``` + +**Quality and security**: + +```yaml +queries: + - uses: security-and-quality +``` + +**Custom suite**: + +```yaml +queries: + - uses: ./custom-queries/critical-security.qls +``` + +## Advanced Workflow Techniques + +### Workflow Commands + +GitHub Actions supports workflow commands for advanced functionality: + +#### Debug Logging + +Enable detailed debugging with the `ACTIONS_STEP_DEBUG` secret: + +```yaml +- name: Debug step + run: echo "::debug::Detailed debugging information" +``` + +#### Annotations + +Create annotations for notices, warnings, and errors: + +```yaml +# Notice annotation +- run: echo "::notice file=app.go,line=10::Consider refactoring" + +# Warning annotation +- run: echo "::warning file=main.go,line=5,col=10::Deprecated function" + +# Error annotation +- run: echo "::error file=handler.go,line=20,title=Build Error::Missing import" +``` + +#### Grouping Log Lines + +Organize logs with collapsible groups: + +```yaml +- name: Build application + run: | + echo "::group::Compiling Go code" + go build -v ./... + echo "::endgroup::" +``` + +#### Masking Secrets + +Prevent sensitive values from appearing in logs: + +```yaml +- name: Generate token + run: | + TOKEN=$(generate_token) + echo "::add-mask::$TOKEN" + echo "TOKEN=$TOKEN" >> $GITHUB_ENV +``` + +#### Job Summaries + +Add Markdown summaries to workflow runs: + +```yaml +- name: Test summary + run: | + echo "### Test Results :white_check_mark:" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + echo "- Total: 150 tests" >> $GITHUB_STEP_SUMMARY + echo "- Passed: 148" >> $GITHUB_STEP_SUMMARY + echo "- Failed: 2" >> $GITHUB_STEP_SUMMARY +``` + +### Environment Files + +Use environment files for dynamic configuration: + +```yaml +# Set environment variable for subsequent steps +- name: Set build info + run: | + echo "BUILD_TIME=$(date +'%Y-%m-%d %H:%M:%S')" >> $GITHUB_ENV + echo "COMMIT_SHA=${GITHUB_SHA:0:7}" >> $GITHUB_ENV + +# Use in later steps +- name: Deploy + run: echo "Deploying $COMMIT_SHA built at $BUILD_TIME" +``` + +### Output Parameters + +Share data between steps: + +```yaml +- name: Calculate version + id: version + run: echo "VERSION=1.2.3" >> $GITHUB_OUTPUT + +- name: Use version + run: echo "Building version ${{ steps.version.outputs.VERSION }}" +``` + +### Multiline Values + +Handle multiline strings safely: + +```yaml +- name: Store API response + run: | + { + echo 'API_RESPONSE<> $GITHUB_ENV +``` + +## References + +- [GitHub Actions Documentation](https://docs.github.com/en/actions) +- [Go CI/CD Best Practices](https://github.com/golang/go/wiki/Go-Release-Cycle) +- [Security Hardening Guide](https://docs.github.com/en/actions/security-guides/security-hardening-for-github-actions) +- [Dependency Caching](https://docs.github.com/en/actions/using-workflows/caching-dependencies-to-speed-up-workflows) +- [Workflow Commands Reference](https://docs.github.com/en/actions/using-workflows/workflow-commands-for-github-actions) +- [Publishing Packages with Actions](https://docs.github.com/en/packages/managing-github-packages-using-github-actions-workflows/publishing-and-installing-a-package-with-github-actions) +- [GitHub Container Registry](https://docs.github.com/en/packages/working-with-a-github-packages-registry/working-with-the-container-registry) +- [Artifact Attestations](https://docs.github.com/en/actions/security-guides/using-artifact-attestations-to-establish-provenance-for-builds) +- [CodeQL Configuration Reference](https://docs.github.com/en/code-security/code-scanning/creating-an-advanced-setup-for-code-scanning/customizing-your-advanced-setup-for-code-scanning) +- [CodeQL Query Suites](https://docs.github.com/en/code-security/code-scanning/managing-your-code-scanning-configuration/codeql-query-suites) +- [CodeQL CLI Reference](https://docs.github.com/en/code-security/codeql-cli) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml new file mode 100644 index 0000000..d7fd5a5 --- /dev/null +++ b/.github/workflows/build.yml @@ -0,0 +1,46 @@ +name: Build + +on: + push: + branches: [main, develop] + pull_request: + branches: [main, develop] + +jobs: + build-binary: + name: Build Binary + runs-on: ubuntu-latest + permissions: + contents: read + attestations: write + id-token: write + steps: + - name: Checkout code + uses: actions/checkout@v5 + + - name: Setup Go + uses: actions/setup-go@v5 + with: + go-version: "1.25" + cache: true + + - name: Install dependencies + run: go mod download + + - name: Verify dependencies + run: go mod verify + + - name: Build application + run: | + go build -v -o bin/tercul-backend ./cmd + ls -la bin/ + + - name: Test binary + run: ./bin/tercul-backend --help || echo "Binary built successfully" + + - name: Upload build artifacts + uses: actions/upload-artifact@v4 + with: + name: tercul-backend-binary + path: bin/ + retention-days: 30 diff --git a/.github/workflows/ci-cd.yml b/.github/workflows/ci-cd.yml deleted file mode 100644 index 7a295c2..0000000 --- a/.github/workflows/ci-cd.yml +++ /dev/null @@ -1,203 +0,0 @@ -name: CI/CD Pipeline - -on: - push: - branches: [main, develop] - pull_request: - branches: [main, develop] - -jobs: - # Security scanning with CodeQL (only on main branch pushes) - security-scan: - name: Security Scan - runs-on: ubuntu-latest - if: github.event_name == 'push' && github.ref == 'refs/heads/main' - permissions: - actions: read - contents: read - security-events: write - - steps: - - name: Checkout code - uses: actions/checkout@v5 - - - name: Initialize CodeQL - uses: github/codeql-action/init@v3 - with: - languages: go - - - name: Setup Go - uses: actions/setup-go@v5 - with: - go-version: "1.25" - cache: true - - - name: Install dependencies - run: go mod download - - - name: Build - run: go build -v ./... - - - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@v3 - - # Linting (runs on PRs and pushes) - lint: - name: Lint - runs-on: ubuntu-latest - steps: - - name: Checkout code - uses: actions/checkout@v5 - - - name: Setup Go - uses: actions/setup-go@v5 - with: - go-version: "1.25" - cache: true - - - name: Install dependencies - run: go mod download - - - name: Tidy modules - run: go mod tidy - - - name: golangci-lint - uses: golangci/golangci-lint-action@v6 - with: - version: latest - args: --timeout=5m ./... - - # Testing and coverage (runs on PRs and pushes) - test: - name: Test - runs-on: ubuntu-latest - services: - postgres: - image: postgres:15 - env: - POSTGRES_PASSWORD: postgres - POSTGRES_DB: testdb - options: >- - --health-cmd pg_isready - --health-interval 10s - --health-timeout 5s - --health-retries 5 - ports: - - 5432:5432 - - redis: - image: redis:7-alpine - ports: - - 6379:6379 - options: --health-cmd "redis-cli ping" --health-interval 10s --health-timeout 5s --health-retries 5 - - steps: - - name: Checkout code - uses: actions/checkout@v5 - - - name: Setup Go - uses: actions/setup-go@v5 - with: - go-version: "1.25" - cache: true - - - name: Install dependencies - run: go mod download - - - name: Run tests with coverage - run: | - go test -v -race -coverprofile=coverage.out -covermode=atomic ./... - go tool cover -html=coverage.out -o coverage.html - - - name: Upload coverage reports - uses: actions/upload-artifact@v4 - with: - name: coverage-report - path: coverage.html - retention-days: 30 - - # Build and verify (runs on PRs and pushes) - build: - name: Build - runs-on: ubuntu-latest - permissions: - contents: read - attestations: write - id-token: write - steps: - - name: Checkout code - uses: actions/checkout@v5 - - - name: Setup Go - uses: actions/setup-go@v5 - with: - go-version: "1.25" - cache: true - - - name: Install dependencies - run: go mod download - - - name: Verify dependencies - run: go mod verify - - - name: Build application - run: | - go build -v -o bin/tercul-backend ./cmd - ls -la bin/ - - - name: Test build - run: ./bin/tercul-backend --help || echo "Binary built successfully" - - - name: Upload build artifacts - uses: actions/upload-artifact@v4 - with: - name: terbul-backend-binary - path: bin/ - retention-days: 30 - - # Matrix testing across Go versions (only on main branch pushes) - test-matrix: - name: Test Matrix (Go ${{ matrix.go-version }}) - runs-on: ubuntu-latest - if: github.event_name == 'push' && github.ref == 'refs/heads/main' - strategy: - matrix: - go-version: ["1.22", "1.23", "1.24", "1.25"] - services: - postgres: - image: postgres:15 - env: - POSTGRES_PASSWORD: postgres - POSTGRES_DB: testdb - options: >- - --health-cmd pg_isready - --health-interval 10s - --health-timeout 5s - --health-retries 5 - ports: - - 5432:5432 - - redis: - image: redis:7-alpine - ports: - - 6379:6379 - options: --health-cmd "redis-cli ping" --health-interval 10s --health-timeout 5s --health-retries 5 - - steps: - - name: Checkout code - uses: actions/checkout@v5 - - - name: Setup Go ${{ matrix.go-version }} - uses: actions/setup-go@v5 - with: - go-version: ${{ matrix.go-version }} - cache: true - - - name: Install dependencies - run: go mod download - - - name: Run tests - run: go test -v -race ./... - - - name: Build - run: go build -v ./... diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml new file mode 100644 index 0000000..d12e4e0 --- /dev/null +++ b/.github/workflows/deploy.yml @@ -0,0 +1,60 @@ +name: Deploy + +on: + push: + tags: ["v*"] + workflow_dispatch: + inputs: + version: + description: "Version to deploy (e.g., v1.2.3)" + required: true + type: string + +jobs: + deploy-production: + name: Deploy to Production + runs-on: ubuntu-latest + environment: + name: production + url: https://tercul.example.com + + steps: + - name: Check out code + uses: actions/checkout@v5 + + - name: Extract version + id: version + run: | + if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then + echo "VERSION=${{ inputs.version }}" >> $GITHUB_OUTPUT + else + echo "VERSION=${GITHUB_REF#refs/tags/v}" >> $GITHUB_OUTPUT + fi + + - name: Deploy to Docker Swarm + env: + SWARM_HOST: ${{ secrets.SWARM_HOST }} + SWARM_SSH_KEY: ${{ secrets.SWARM_SSH_KEY }} + IMAGE_TAG: ${{ steps.version.outputs.VERSION }} + run: | + # Uncomment and configure for actual Docker Swarm deployment + # echo "$SWARM_SSH_KEY" > swarm_key + # chmod 600 swarm_key + # ssh -i swarm_key -o StrictHostKeyChecking=no \ + # deploy@$SWARM_HOST \ + # "docker service update \ + # --image ghcr.io/${{ github.repository }}:${IMAGE_TAG} \ + # tercul-backend" + # rm swarm_key + + echo "Deploying version ${{ steps.version.outputs.VERSION }} to production" + echo "Image: ghcr.io/${{ github.repository }}:${IMAGE_TAG}" + + - name: Deployment summary + run: | + echo "### Deployment Complete :rocket:" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + echo "- **Version**: ${{ steps.version.outputs.VERSION }}" >> $GITHUB_STEP_SUMMARY + echo "- **Image**: ghcr.io/${{ github.repository }}:${{ steps.version.outputs.VERSION }}" >> $GITHUB_STEP_SUMMARY + echo "- **Environment**: Production" >> $GITHUB_STEP_SUMMARY + echo "- **Deployed at**: $(date -u +'%Y-%m-%d %H:%M:%S UTC')" >> $GITHUB_STEP_SUMMARY diff --git a/.github/workflows/cd.yml b/.github/workflows/docker-build.yml similarity index 64% rename from .github/workflows/cd.yml rename to .github/workflows/docker-build.yml index c9ec81d..b4b7f66 100644 --- a/.github/workflows/cd.yml +++ b/.github/workflows/docker-build.yml @@ -1,13 +1,15 @@ -name: Go CD +name: Docker Build on: push: branches: [main] tags: ["v*"] + pull_request: + branches: [main] jobs: - build-and-push: - name: Build and Push Docker Image + build-image: + name: Build Docker Image runs-on: ubuntu-latest permissions: contents: read @@ -36,6 +38,7 @@ jobs: images: ghcr.io/${{ github.repository }} tags: | type=ref,event=branch + type=ref,event=pr type=ref,event=tag type=semver,pattern={{version}} type=semver,pattern={{major}}.{{minor}} @@ -46,7 +49,7 @@ jobs: uses: docker/build-push-action@v6 with: context: . - push: true + push: ${{ github.event_name != 'pull_request' }} tags: ${{ steps.meta.outputs.tags }} labels: ${{ steps.meta.outputs.labels }} cache-from: type=gha @@ -54,32 +57,9 @@ jobs: platforms: linux/amd64,linux/arm64 - name: Generate artifact attestation + if: github.event_name != 'pull_request' uses: actions/attest-build-provenance@v3 with: subject-name: ghcr.io/${{ github.repository}} subject-digest: ${{ steps.push.outputs.digest }} push-to-registry: true - - deploy: - name: Deploy to Production - needs: build-and-push - runs-on: ubuntu-latest - if: startsWith(github.ref, 'refs/tags/v') - - steps: - - name: Check out code - uses: actions/checkout@v5 - - - name: Extract tag name - id: tag - run: echo "TAG=${GITHUB_REF#refs/tags/}" >> $GITHUB_OUTPUT - - # This step is a placeholder for deployment logic - # Replace with your actual deployment mechanism (SSH, kubectl, etc.) - - name: Deploy to production - run: | - echo "Deploying version ${{ steps.tag.outputs.TAG }} to production" - # Add your deployment commands here - env: - TAG: ${{ steps.tag.outputs.TAG }} - # Add other environment variables needed for deployment diff --git a/.github/workflows/ci.yml b/.github/workflows/lint.yml similarity index 55% rename from .github/workflows/ci.yml rename to .github/workflows/lint.yml index 575fcb4..46b71b9 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/lint.yml @@ -1,17 +1,15 @@ -name: CI +name: Lint on: push: - branches: [main] + branches: [main, develop] pull_request: - branches: [main] + branches: [main, develop] jobs: - lint-and-test: + golangci-lint: + name: Go Lint runs-on: ubuntu-latest - permissions: - contents: read - security-events: write # For code scanning steps: - name: Checkout code uses: actions/checkout@v5 @@ -28,17 +26,8 @@ jobs: - name: Tidy modules run: go mod tidy - - name: Lint + - name: Run golangci-lint uses: golangci/golangci-lint-action@v6 with: version: latest args: --timeout=5m ./... - - - name: Test - run: go test -v -race -coverprofile=coverage.out ./... - - - name: Upload coverage - uses: actions/upload-artifact@v4 - with: - name: coverage - path: coverage.out diff --git a/.github/workflows/security.yml b/.github/workflows/security.yml new file mode 100644 index 0000000..6420f38 --- /dev/null +++ b/.github/workflows/security.yml @@ -0,0 +1,47 @@ +name: Security + +on: + push: + branches: [main] + pull_request: + branches: [main] + schedule: + # Run CodeQL scan every Monday at 14:20 UTC + - cron: "20 14 * * 1" + +jobs: + codeql-analysis: + name: CodeQL Security Scan + runs-on: ubuntu-latest + permissions: + actions: read + contents: read + security-events: write + + steps: + - name: Checkout code + uses: actions/checkout@v5 + + - name: Initialize CodeQL + uses: github/codeql-action/init@v3 + with: + languages: go + # Optionally use security-extended for more comprehensive scanning + # queries: security-extended + + - name: Setup Go + uses: actions/setup-go@v5 + with: + go-version: "1.25" + cache: true + + - name: Install dependencies + run: go mod download + + - name: Build for analysis + run: go build -v ./... + + - name: Perform CodeQL Analysis + uses: github/codeql-action/analyze@v3 + with: + category: "backend-security" diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml new file mode 100644 index 0000000..8bfe378 --- /dev/null +++ b/.github/workflows/test.yml @@ -0,0 +1,114 @@ +name: Test + +on: + push: + branches: [main, develop] + pull_request: + branches: [main, develop] + +jobs: + unit-tests: + name: Unit Tests + runs-on: ubuntu-latest + services: + postgres: + image: postgres:15 + env: + POSTGRES_PASSWORD: postgres + POSTGRES_DB: testdb + options: >- + --health-cmd pg_isready + --health-interval 10s + --health-timeout 5s + --health-retries 5 + ports: + - 5432:5432 + + redis: + image: redis:7-alpine + ports: + - 6379:6379 + options: --health-cmd "redis-cli ping" --health-interval 10s --health-timeout 5s --health-retries 5 + + steps: + - name: Checkout code + uses: actions/checkout@v5 + + - name: Setup Go + uses: actions/setup-go@v5 + with: + go-version: "1.25" + cache: true + + - name: Install dependencies + run: go mod download + + - name: Run tests with coverage + run: | + go test -v -race -coverprofile=coverage.out -covermode=atomic ./... + go tool cover -html=coverage.out -o coverage.html + + - name: Generate test summary + if: always() + run: | + echo "### Test Results :test_tube:" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + echo "- **Coverage**: See artifact for detailed report" >> $GITHUB_STEP_SUMMARY + echo "- **Race Detection**: Enabled" >> $GITHUB_STEP_SUMMARY + echo "- **Go Version**: 1.25" >> $GITHUB_STEP_SUMMARY + + - name: Upload coverage reports + uses: actions/upload-artifact@v4 + with: + name: coverage-report + path: | + coverage.out + coverage.html + retention-days: 30 + + compatibility-matrix: + name: Go ${{ matrix.go-version }} Compatibility + runs-on: ubuntu-latest + if: github.event_name == 'push' && github.ref == 'refs/heads/main' + strategy: + fail-fast: false + matrix: + go-version: ["1.22", "1.23", "1.24", "1.25"] + services: + postgres: + image: postgres:15 + env: + POSTGRES_PASSWORD: postgres + POSTGRES_DB: testdb + options: >- + --health-cmd pg_isready + --health-interval 10s + --health-timeout 5s + --health-retries 5 + ports: + - 5432:5432 + + redis: + image: redis:7-alpine + ports: + - 6379:6379 + options: --health-cmd "redis-cli ping" --health-interval 10s --health-timeout 5s --health-retries 5 + + steps: + - name: Checkout code + uses: actions/checkout@v5 + + - name: Setup Go ${{ matrix.go-version }} + uses: actions/setup-go@v5 + with: + go-version: ${{ matrix.go-version }} + cache: true + + - name: Install dependencies + run: go mod download + + - name: Run tests + run: go test -v -race ./... + + - name: Build + run: go build -v ./...