diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 81cbd1bd..1f1cdb2b 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -12,7 +12,6 @@ permissions: env: RAILS_ENV: test - CC_TEST_REPORTER_ID: ${{ secrets.CC_TEST_REPORTER_ID }} CI: true GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} REPO: ${{ github.repository }} @@ -132,11 +131,6 @@ jobs: uses: ruby/setup-ruby@v1 with: bundler-cache: true - - name: Setup Code Climate test-reporter - run: | - curl -L https://codeclimate.com/downloads/test-reporter/test-reporter-latest-linux-amd64 > ./cc-test-reporter - chmod +x ./cc-test-reporter - ./cc-test-reporter before-build - name: Let Rails generate the secret_key_base run: bundle exec rails runner 'puts Rails.application.secret_key_base' - name: Setup Database @@ -164,13 +158,13 @@ jobs: uses: rootstrap/check_untracked_changes@v1 with: path: "./app/ ./spec/" - - name: Report to CodeClimate - run: ./cc-test-reporter format-coverage --output "coverage/coverage.${{ matrix.ci_node_index }}.json" - - name: Upload partial coverage + - name: Rename coverage files + run: mv coverage/coverage.json coverage/coverage-${{ matrix.ci_node_index }}.json + - name: Upload coverage report uses: actions/upload-artifact@v4 with: - name: coverage - path: "coverage/coverage.${{ matrix.ci_node_index }}.json" + name: "coverage-${{ matrix.ci_node_index }}" + path: coverage/coverage-${{ matrix.ci_node_index }}.json - name: Merge API Docs from threads env: CI_NODE_INDEX: ${{ matrix.ci_node_index }} @@ -192,32 +186,27 @@ jobs: with: fetch-depth: 0 ssh-key: "${{ secrets.PUSH_KEY }}" - - name: Set ENV for CodeClimate - if: github.event_name == 'pull_request' - run: | - git fetch --no-tags --prune --depth=1 origin +refs/heads/$GITHUB_HEAD_REF:refs/remotes/origin/$GITHUB_HEAD_REF - echo "GIT_BRANCH=$GITHUB_HEAD_REF" >> $GITHUB_ENV - echo "GIT_COMMIT_SHA=$(git rev-parse origin/$GITHUB_HEAD_REF)" >> $GITHUB_ENV - echo "PULL_REQUEST_NUMBER=${{ github.event.pull_request.number }}" >> $GITHUB_ENV - name: Set ENV for API Docs if: github.event_name == 'push' && github.ref == 'refs/heads/main' run: | if [ $(git diff ${{ github.event.before }} ${{ github.event.after }} --name-only | grep 'spec/requests/api' | wc -l) -gt 0 ]; then echo "OPENAPI=true" >> $GITHUB_ENV fi - - name: Setup Code Climate test-reporter - run: | - curl -L https://codeclimate.com/downloads/test-reporter/test-reporter-latest-linux-amd64 > ./cc-test-reporter - chmod +x ./cc-test-reporter - - name: Download coverage reports - uses: actions/download-artifact@v5 + - name: Download coverage report + uses: actions/download-artifact@v4 with: - name: coverage - path: coverage/coverage.*.json - - name: Report coverage + pattern: coverage-* + path: coverage/ + merge-multiple: true + - name: Set ENV for SonarQube run: | - ./cc-test-reporter sum-coverage coverage/**/*.json - ./cc-test-reporter upload-coverage + echo "SONAR_PROJECT_VERSION=$(echo $GITHUB_SHA | cut -c1-8)" >> $GITHUB_ENV + echo "SONAR_REPORT_PATHS=$(ls coverage/coverage-*.json | paste -sd "," -)" >> $GITHUB_ENV + - name: SonarQube Scanner + uses: sonarsource/sonarqube-scan-action@v5.3.0 + env: + SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} + SONAR_HOST_URL: ${{ secrets.SONAR_HOST_URL }} - name: Enable Corepack run: corepack enable - name: Setup Node diff --git a/README.md b/README.md index f32796b7..5780c70b 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,8 @@ # Rails API Template [![Github Actions CI](https://github.com/rootstrap/rails_api_base/actions/workflows/ci.yml/badge.svg?event=push)](https://github.com/rootstrap/rails_api_base/actions) -[![Code Climate](https://codeclimate.com/github/rootstrap/rails_api_base/badges/gpa.svg)](https://codeclimate.com/github/rootstrap/rails_api_base) -[![Test Coverage](https://api.codeclimate.com/v1/badges/63de7f82c79f5fe82f46/test_coverage)](https://codeclimate.com/github/rootstrap/rails_api_base/test_coverage) +[![Quality Gate Status](${SONAR_HOST_URL}/api/project_badges/measure?project=rails-api-base&metric=alert_status)](${SONAR_HOST_URL}/dashboard?id=rails-api-base) +[![Coverage](${SONAR_HOST_URL}/api/project_badges/measure?project=rails-api-base&metric=coverage)](${SONAR_HOST_URL}/dashboard?id=rails-api-base) Rails API Base is a boilerplate project for JSON RESTful APIs. It follows the community best practices in terms of standards, security and maintainability, integrating a variety of testing and code quality tools. It's based on Rails 8.0 and Ruby 3.4. @@ -134,6 +134,26 @@ With `bundle exec rails code:analysis` you can run the code analysis tool, you c - [Rails Best Practices](https://github.com/flyerhzm/rails_best_practices#custom-configuration) Edit `config/rails_best_practices.yml` - [Brakeman](https://github.com/presidentbeef/brakeman) Run `brakeman -I` to generate `config/brakeman.ignore` +## Code quality and test coverage + +The project uses SonarQube Community Edition to ensure code quality and monitor test coverage. The configuration can be found in `sonar-project.properties`. + +To set up SonarQube: + +1. Install SonarQube locally (see [CI documentation](docs/ci.md#setting-up-sonarqube) for details) +2. Configure your environment variables: + ```bash + export SONAR_TOKEN=your_token + export SONAR_HOST_URL=http://localhost:9000 + ``` +3. Run the analysis: + ```bash + bundle exec rspec + sonar-scanner + ``` + +For more details about the CI process and SonarQube setup, check the [CI documentation](docs/ci.md). + ## More linters - [Hadolint](https://github.com/hadolint/hadolint) Install with `brew install hadolint` and run `hadolint Dockerfile*`. Edit `.hadolint.yml` to omit additional rules. @@ -148,12 +168,6 @@ See [Impersonation docs](./docs/impersonation.md) for more info In order to use [New Relic](https://newrelic.com) to monitor your application requests and metrics, you must setup `NEW_RELIC_API_KEY` and `NEW_RELIC_APP_NAME` environment variables. To obtain an API key you must create an account in the platform. -## Configuring Code Climate - -1. After adding the project to CC, go to `Repo Settings` -1. On the `Test Coverage` tab, copy the `Test Reporter ID` -1. Set the current value of `CC_TEST_REPORTER_ID` in the [GitHub secrets and variables](https://docs.github.com/en/actions/security-guides/using-secrets-in-github-actions#creating-secrets-for-a-repository) - ## Code Owners You can use [CODEOWNERS](https://help.github.com/en/articles/about-code-owners) file to define individuals or teams that are responsible for code in the repository. diff --git a/docs/ci.md b/docs/ci.md index 0effdfea..c613c5bc 100644 --- a/docs/ci.md +++ b/docs/ci.md @@ -1,6 +1,31 @@ -# CI +# Continuous Integration + +This project uses GitHub Actions for continuous integration. The workflow is defined in `.github/workflows/ci.yml`. + +## What's being tested and analyzed? + +The CI process includes: +1. Running all RSpec tests +2. Running code style checks via RuboCop +3. Running security checks via Brakeman +4. Running best practices checks via Rails Best Practices +5. Running code quality and test coverage analysis via SonarQube +6. Checking for missing annotations +7. Linting Dockerfiles +8. Building Docker images +9. Generating and updating API documentation + +## CI Setup Requirements + +### SonarQube Configuration +Configure these secrets in your GitHub repository (Settings → Secrets and variables → Actions): +- `SONAR_TOKEN`: Your SonarQube token +- `SONAR_HOST_URL`: Your SonarQube server URL + +The CI will automatically run tests, generate coverage reports, and upload results to SonarQube. ## Parallelization with Parallel Tests & Knapsack + Knapsack and Parallel Tests gems allow us to run tests in several nodes at the same time, benefiting us in the execution time. Knapsack parallelizes them at node level while Parallel Tests does it at CPU level. Knapsack splits tests based on an execution time report. In case there are files that were not added in the report, they will all run on the same node and may overload it, so it is strongly recommended to update the report frequently. diff --git a/sonar-project.properties b/sonar-project.properties new file mode 100644 index 00000000..6149eaa1 --- /dev/null +++ b/sonar-project.properties @@ -0,0 +1,5 @@ +sonar.sources=. +sonar.projectBaseDir=. +sonar.projectKey=rootstrap_rails_api_base +sonar.organization=rootstrap +sonar.projectVersion=1.0.0 diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 55441f5b..71626173 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -11,6 +11,7 @@ require 'shoulda/matchers' require 'pundit/rspec' require 'capybara/rspec' +require 'simplecov_json_formatter' Knapsack.tracker.config(enable_time_offset_warning: false) Knapsack::Adapters::RSpecAdapter.bind @@ -68,3 +69,10 @@ ActionMailer::Base.deliveries.clear end end + +SimpleCov.formatter = SimpleCov::Formatter::MultiFormatter.new( + [ + SimpleCov::Formatter::JSONFormatter, + SimpleCov::Formatter::HTMLFormatter + ] +)