diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml new file mode 100644 index 0000000..f0d5c23 --- /dev/null +++ b/.github/workflows/ci.yaml @@ -0,0 +1,157 @@ +name: CI + +on: + push: + branches: + - master + - main + pull_request: + branches: + - master + - main + workflow_dispatch: ~ + +concurrency: + group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }} + cancel-in-progress: true + +jobs: + tests: + name: Tests + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v5 + + - name: Set up PHP + uses: shivammathur/setup-php@v2 + with: + php-version: '8.5' + coverage: none + + - name: Install Composer dependencies + run: composer install --no-interaction --no-progress --prefer-dist + env: + COMPOSER_MEMORY_LIMIT: -1 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v4 + + - name: Build test image + run: docker compose -f compose.test.yaml build + + - name: Start services + run: docker compose -f compose.test.yaml up -d + + - name: Print logs on failure + if: failure() + run: docker compose -f compose.test.yaml logs + + - name: Create test database + run: docker compose -f compose.test.yaml exec -T app php bin/console -e test doctrine:database:create --if-not-exists + + - name: Run migrations + run: docker compose -f compose.test.yaml exec -T app php bin/console -e test doctrine:migrations:migrate --no-interaction + + - name: Run PHPUnit + run: docker compose -f compose.test.yaml exec -T app vendor/bin/phpunit + + - name: Run PHPUnit with coverage + run: docker compose -f compose.test.yaml exec -T app php -d pcov.enabled=1 vendor/bin/phpunit --testsuite=unit --coverage-clover var/coverage/clover.xml --coverage-text + + - name: Check coverage threshold (80%) + run: | + COVERAGE=$(docker compose -f compose.test.yaml exec -T app php -r " + \$xml = simplexml_load_file('var/coverage/clover.xml'); + \$metrics = \$xml->project->metrics; + \$statements = (int)\$metrics['statements']; + \$covered = (int)\$metrics['coveredstatements']; + echo \$statements > 0 ? round((\$covered / \$statements) * 100, 2) : 0; + ") + echo "Line coverage: ${COVERAGE}%" + if [ "$(echo "${COVERAGE} < 80" | bc)" -eq 1 ]; then + echo "::error::Coverage ${COVERAGE}% is below the 80% threshold" + exit 1 + fi + + - name: Run Behat + run: docker compose -f compose.test.yaml exec -T app vendor/bin/behat --no-interaction --colors + + - name: Audit PHP dependencies + # Exit 1 = vulnerabilities (blocking), exit 2 = abandoned packages (non-blocking) + run: | + docker compose -f compose.test.yaml exec -T app composer audit || { code=$?; [ "$code" -eq 2 ] || exit "$code"; } + + - name: Run CS Fixer (dry-run) + run: docker compose -f compose.test.yaml exec -T app vendor/bin/php-cs-fixer fix --dry-run --diff + + - name: Run PHPStan + run: docker compose -f compose.test.yaml exec -T app vendor/bin/phpstan analyse + + - name: Run Rector (dry-run) + run: docker compose -f compose.test.yaml exec -T app vendor/bin/rector process --dry-run + + - name: YAML linter + run: docker compose -f compose.test.yaml exec -T app php bin/console lint:yaml config + + - name: Doctrine Schema Validator + run: docker compose -f compose.test.yaml exec -T app php bin/console -e test doctrine:schema:validate --skip-sync || true + + secret-scan: + name: Secret Scan + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v5 + with: + fetch-depth: 0 + + - name: Scan for secrets + uses: trufflesecurity/trufflehog@main + with: + extra_args: --only-verified + + trivy: + name: Trivy Security Scan + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v5 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v4 + + - name: Build prod image + run: docker build -f .docker/api/Dockerfile --target frankenphp_prod -t fall-guardian-api:ci . + + - name: Scan image for vulnerabilities + uses: aquasecurity/trivy-action@v0.35.0 + with: + image-ref: fall-guardian-api:ci + format: table + exit-code: 0 + ignore-unfixed: true + severity: CRITICAL,HIGH + + - name: Scan repository for misconfigurations + uses: aquasecurity/trivy-action@v0.35.0 + with: + scan-type: fs + scan-ref: . + format: table + exit-code: 0 + ignore-unfixed: true + severity: CRITICAL,HIGH + + lint: + name: Docker Lint + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v5 + + - name: Lint Dockerfile + uses: hadolint/hadolint-action@v3.1.0 + with: + dockerfile: .docker/api/Dockerfile + failure-threshold: error diff --git a/.gitignore b/.gitignore index 3e523a9..5926c10 100644 --- a/.gitignore +++ b/.gitignore @@ -18,3 +18,4 @@ /phpunit.xml /.phpunit.cache/ ###< phpunit/phpunit ### +phpstan.neon diff --git a/phpstan.neon b/phpstan.neon deleted file mode 100644 index d96f73f..0000000 --- a/phpstan.neon +++ /dev/null @@ -1,2 +0,0 @@ -includes: - - phpstan.dist.neon diff --git a/phpstan.dist.neon b/phpstan.neon.dist similarity index 100% rename from phpstan.dist.neon rename to phpstan.neon.dist