diff --git a/.github/README.md b/.github/README.md new file mode 100644 index 0000000..cfc91e1 --- /dev/null +++ b/.github/README.md @@ -0,0 +1,118 @@ +# GitHub Actions CI/CD Setup + +This repository includes comprehensive GitHub Actions workflows for continuous integration, security scanning, and automated releases. + +## Workflows + +### ๐Ÿ”„ CI Workflow (`.github/workflows/ci.yml`) + +**Triggers:** Push or Pull Request to `main` or `develop` branches + +**What it does:** +- Sets up PostgreSQL 15 database service +- Installs Elixir 1.15.7 with OTP 26.1 +- Caches dependencies for faster builds +- Runs code formatting checks +- Performs static analysis with Credo +- Compiles with warnings treated as errors +- Executes tests with coverage reporting +- Runs quality checks using `mix quality` + +### ๐Ÿš€ Release Workflow (`.github/workflows/release.yml`) + +**Triggers:** Git tags starting with `v*` (e.g., `v1.0.0`) + +**What it does:** +- Builds production release +- Generates documentation +- Creates GitHub release with auto-generated notes +- Optionally publishes to Hex.pm (requires `HEX_API_KEY` secret) + +### ๐Ÿ”’ Security Workflow (`.github/workflows/security.yml`) + +**Triggers:** +- Push or Pull Request to `main` +- Weekly schedule (Mondays at 10:00 UTC) + +**What it does:** +- Scans for security vulnerabilities using Trivy +- Uploads results to GitHub Security tab +- Runs automatically on a weekly basis + +### ๐Ÿงช Matrix Testing (`.github/workflows/matrix.yml`) + +**Triggers:** +- Push or Pull Request to `main` +- Weekly schedule (Sundays at 6:00 UTC) + +**What it does:** +- Tests across multiple Elixir/OTP version combinations +- Ensures compatibility with minimum and latest supported versions +- Includes Elixir 1.15.0-1.16.0 with OTP 26.0-26.2 + +## Configuration + +### Required Secrets + +For full functionality, add these secrets to your repository: + +- `HEX_API_KEY` - Required for automatic Hex.pm publishing on releases +- `GITHUB_TOKEN` - Automatically provided by GitHub Actions + +### Database Setup + +All workflows use PostgreSQL with these credentials (matching `config/test.exs`): +- **Username:** `postgres` +- **Password:** `postgres` +- **Database:** `postgres` +- **Port:** `5432` + +### Coverage Reporting + +The CI workflow generates coverage reports using ExCoveralls and uploads them to GitHub. The project requires a minimum of 85% test coverage as configured in `.coveralls.exs`. + +## Status Badges + +Add these badges to your README.md: + +```markdown +[![CI](https://github.com/agoodway/tango/actions/workflows/ci.yml/badge.svg)](https://github.com/agoodway/tango/actions/workflows/ci.yml) +[![Security](https://github.com/agoodway/tango/actions/workflows/security.yml/badge.svg)](https://github.com/agoodway/tango/actions/workflows/security.yml) +``` + +## Local Development + +To run the same checks locally: + +```bash +# Install dependencies +mix deps.get + +# Run formatting check +mix format --check-formatted + +# Run static analysis +mix credo + +# Run tests with coverage +mix coveralls + +# Run all quality checks +mix quality +``` + +## Troubleshooting + +### Common Issues + +1. **Database Connection Errors**: Ensure PostgreSQL is running locally with the credentials in `config/test.exs` +2. **Coverage Failures**: Check that tests meet the 85% minimum coverage requirement +3. **Formatting Errors**: Run `mix format` to auto-fix formatting issues +4. **Credo Warnings**: Address static analysis issues highlighted by Credo + +### Workflow Debugging + +- Check the Actions tab for detailed logs +- Failed steps will show specific error messages +- Use the re-run feature to retry failed workflows +- Check the matrix workflow for compatibility issues across Elixir versions diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..a1bad03 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,79 @@ +name: CI + +on: + push: + branches: [ main, develop ] + pull_request: + branches: [ main, develop ] + +env: + MIX_ENV: test + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + +jobs: + test: + name: Test & Quality + runs-on: ubuntu-latest + + services: + postgres: + image: postgres:15 + env: + POSTGRES_USER: postgres + POSTGRES_PASSWORD: postgres + POSTGRES_DB: postgres + options: >- + --health-cmd pg_isready + --health-interval 10s + --health-timeout 5s + --health-retries 5 + ports: + - 5432:5432 + + strategy: + matrix: + elixir: ['1.15.7'] + otp: ['26.1'] + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Set up Elixir + uses: erlef/setup-beam@v1 + with: + elixir-version: ${{ matrix.elixir }} + otp-version: ${{ matrix.otp }} + + - name: Cache dependencies + uses: actions/cache@v3 + with: + path: | + deps + _build + key: ${{ runner.os }}-mix-${{ hashFiles('**/mix.lock') }} + restore-keys: | + ${{ runner.os }}-mix- + + - name: Install dependencies + run: | + mix local.hex --force + mix local.rebar --force + mix deps.get + + - name: Check formatting + run: mix format --check-formatted + + - name: Run static analysis (Credo) + run: mix credo + + - name: Compile + run: mix compile --warnings-as-errors + + - name: Run tests with coverage + run: mix coveralls.github + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + - name: Run quality checks + run: mix quality diff --git a/.github/workflows/matrix.yml b/.github/workflows/matrix.yml new file mode 100644 index 0000000..1a8ce07 --- /dev/null +++ b/.github/workflows/matrix.yml @@ -0,0 +1,79 @@ +name: Matrix Tests + +on: + push: + branches: [ main ] + pull_request: + branches: [ main ] + schedule: + # Run every week on Sunday at 6:00 UTC + - cron: '0 6 * * 0' + +env: + MIX_ENV: test + +jobs: + test-matrix: + name: Test (Elixir ${{ matrix.elixir }}, OTP ${{ matrix.otp }}) + runs-on: ubuntu-latest + + services: + postgres: + image: postgres:15 + env: + POSTGRES_USER: postgres + POSTGRES_PASSWORD: postgres + POSTGRES_DB: postgres + options: >- + --health-cmd pg_isready + --health-interval 10s + --health-timeout 5s + --health-retries 5 + ports: + - 5432:5432 + + strategy: + fail-fast: false + matrix: + include: + # Minimum supported versions + - elixir: '1.15.0' + otp: '26.0' + # Current stable versions + - elixir: '1.15.7' + otp: '26.1' + # Latest versions + - elixir: '1.16.0' + otp: '26.2' + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Set up Elixir + uses: erlef/setup-beam@v1 + with: + elixir-version: ${{ matrix.elixir }} + otp-version: ${{ matrix.otp }} + + - name: Cache dependencies + uses: actions/cache@v3 + with: + path: | + deps + _build + key: ${{ runner.os }}-mix-${{ matrix.elixir }}-${{ matrix.otp }}-${{ hashFiles('**/mix.lock') }} + restore-keys: | + ${{ runner.os }}-mix-${{ matrix.elixir }}-${{ matrix.otp }}- + + - name: Install dependencies + run: | + mix local.hex --force + mix local.rebar --force + mix deps.get + + - name: Compile + run: mix compile + + - name: Run tests + run: mix test \ No newline at end of file diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..bb50c67 --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,82 @@ +name: Release + +on: + push: + tags: + - 'v*' + +env: + MIX_ENV: prod + +jobs: + release: + name: Build and Release + runs-on: ubuntu-latest + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Set up Elixir + uses: erlef/setup-beam@v1 + with: + elixir-version: '1.15.7' + otp-version: '26.1' + + - name: Cache dependencies + uses: actions/cache@v3 + with: + path: | + deps + _build + key: ${{ runner.os }}-mix-prod-${{ hashFiles('**/mix.lock') }} + restore-keys: | + ${{ runner.os }}-mix-prod- + + - name: Install dependencies + run: | + mix local.hex --force + mix local.rebar --force + mix deps.get --only=prod + + - name: Compile + run: mix compile + + - name: Build documentation + run: mix docs + + - name: Create GitHub Release + uses: softprops/action-gh-release@v1 + with: + files: | + doc/**/* + generate_release_notes: true + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + publish: + name: Publish to Hex + runs-on: ubuntu-latest + needs: release + if: startsWith(github.ref, 'refs/tags/v') + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Set up Elixir + uses: erlef/setup-beam@v1 + with: + elixir-version: '1.15.7' + otp-version: '26.1' + + - name: Install dependencies + run: | + mix local.hex --force + mix local.rebar --force + mix deps.get + + - name: Publish to Hex + run: mix hex.publish --yes + env: + HEX_API_KEY: ${{ secrets.HEX_API_KEY }} \ No newline at end of file diff --git a/.github/workflows/security.yml b/.github/workflows/security.yml new file mode 100644 index 0000000..540291c --- /dev/null +++ b/.github/workflows/security.yml @@ -0,0 +1,33 @@ +name: Security + +on: + push: + branches: [ main ] + pull_request: + branches: [ main ] + schedule: + # Run every Monday at 10:00 UTC + - cron: '0 10 * * 1' + +jobs: + secrets-scan: + name: Security Scan + runs-on: ubuntu-latest + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Run Trivy vulnerability scanner + uses: aquasecurity/trivy-action@master + with: + scan-type: 'fs' + scan-ref: '.' + format: 'sarif' + output: 'trivy-results.sarif' + + - name: Upload Trivy scan results to GitHub Security tab + uses: github/codeql-action/upload-sarif@v3 + if: always() + with: + sarif_file: 'trivy-results.sarif' \ No newline at end of file diff --git a/mix.exs b/mix.exs index 2213d64..325bff9 100644 --- a/mix.exs +++ b/mix.exs @@ -11,7 +11,12 @@ defmodule Tango.MixProject do elixirc_paths: elixirc_paths(Mix.env()), aliases: aliases(), test_coverage: [tool: ExCoveralls], - preferred_cli_coverages: ["coveralls", "coveralls.detail", "coveralls.html"], + preferred_cli_coverages: [ + "coveralls", + "coveralls.detail", + "coveralls.html", + "coveralls.github" + ], dialyzer: [ plt_file: {:no_warn, "priv/plts/dialyzer.plt"} ]