diff --git a/.git-hooks/pre_commit/gitleaks.rb b/.git-hooks/pre_commit/gitleaks.rb new file mode 100644 index 000000000..d3e1d7591 --- /dev/null +++ b/.git-hooks/pre_commit/gitleaks.rb @@ -0,0 +1,19 @@ +# frozen_string_literal: true + +module Overcommit + module Hook + module PreCommit + # Local script to run gitleaks + class Gitleaks < Base + # Runs the command. + def run + result = execute(['gitleaks', 'git', '--staged', '-v', '.']) + + return :pass if result.success? + + [:fail, result.stdout + result.stderr] + end + end + end + end +end diff --git a/.github/workflows/_static-analysis.yml b/.github/workflows/_static-analysis.yml index d35199286..b3969e7f7 100644 --- a/.github/workflows/_static-analysis.yml +++ b/.github/workflows/_static-analysis.yml @@ -15,6 +15,9 @@ jobs: - uses: actions/checkout@v5 with: submodules: recursive + # Gitleaks scans the commit range of the event, so it needs the full + # history rather than the default shallow clone. + fetch-depth: 0 - name: set up Ruby uses: ruby/setup-ruby@v1 @@ -22,31 +25,18 @@ jobs: ruby-version: "3.2.0" bundler-cache: true - - name: Set up Python - uses: actions/setup-python@v5 - with: - python-version: "3.x" - - - name: Install pre-commit - run: | - python -m pip install --upgrade pip - python -m pip install pre-commit - - - name: Run pre-commit checks (security + hygiene) + - name: Run Gitleaks + uses: gitleaks/gitleaks-action@v3 env: - SKIP: rubocop,steep - run: | - pre-commit run --all-files --show-diff-on-failure + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + GITLEAKS_LICENSE: ${{ secrets.GITLEAKS_LICENSE }} - name: Analyse the code with Rubocop run: | + git config user.email "ci@github-actions" + git config user.name "GitHub Actions" bundle install - bundle exec rubocop ./ - - - name: Attempt to generate the documentation - run: | - bundle exec rake doc - - - name: Check types using Steep - run: | - bundle exec steep check + bundle exec overcommit --install + bundle exec overcommit --sign + bundle exec overcommit --sign pre-commit + SKIP=Gitleaks bundle exec overcommit --run diff --git a/.github/workflows/pull-request.yml b/.github/workflows/pull-request.yml index 5f5edf043..556c5be23 100644 --- a/.github/workflows/pull-request.yml +++ b/.github/workflows/pull-request.yml @@ -10,6 +10,7 @@ permissions: jobs: static-analysis: uses: ./.github/workflows/_static-analysis.yml + secrets: inherit test-unit: uses: ./.github/workflows/_test-units.yml needs: static-analysis diff --git a/.github/workflows/push-main-branch.yml b/.github/workflows/push-main-branch.yml index 812b14568..c67828317 100644 --- a/.github/workflows/push-main-branch.yml +++ b/.github/workflows/push-main-branch.yml @@ -8,6 +8,7 @@ on: jobs: static-analysis: uses: mindee/mindee-api-ruby/.github/workflows/_static-analysis.yml@main + secrets: inherit test-units: uses: mindee/mindee-api-ruby/.github/workflows/_test-units.yml@main needs: static-analysis diff --git a/.overcommit.yml b/.overcommit.yml new file mode 100644 index 000000000..5e40e126b --- /dev/null +++ b/.overcommit.yml @@ -0,0 +1,46 @@ +PreCommit: + MergeConflicts: + enabled: true + + YamlSyntax: + enabled: true + + TrailingWhitespace: + enabled: true + + FileSize: + enabled: true + byte_limit: 1048576 + + RuboCop: + enabled: true + required_executable: 'bundle' + command: ['bundle', 'exec', 'rubocop', '--force-exclusion', '--no-server'] + + CustomScript: + enabled: true + description: 'Run Steep type checking' + required_executable: 'bundle' + command: ['bundle', 'exec', 'steep', 'check'] + + Gitleaks: + enabled: true + required_executable: 'gitleaks' + CommitMsg: + enabled: false + CapitalizedSubject: + enabled: false + TextWidth: + enabled: false + TrailingPeriod: + enabled: false + SingleLineSubject: + enabled: false + EmptyMessage: + enabled: false + +PrePush: + BundleAudit: + enabled: true + required_executable: 'bundle' + command: ['sh', '-c', 'bundle exec bundle-audit check'] diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml deleted file mode 100644 index e013967a1..000000000 --- a/.pre-commit-config.yaml +++ /dev/null @@ -1,36 +0,0 @@ -repos: - - repo: https://github.com/pre-commit/pre-commit-hooks - rev: v6.0.0 - hooks: - - id: check-merge-conflict - - id: check-yaml - - id: end-of-file-fixer - - id: trailing-whitespace - - id: check-added-large-files - args: ["--maxkb=1024"] - - - repo: https://github.com/gitleaks/gitleaks - rev: v8.30.0 - hooks: - - id: gitleaks - exclude: ^(spec/data/|docs/code_samples/|local_test/) - - - repo: local - hooks: - - id: rubocop - name: RuboCop - entry: bundle exec rubocop --force-exclusion --no-server - language: system - pass_filenames: false - - id: steep - name: Steep - entry: bundle exec steep check - language: system - pass_filenames: false - stages: [pre-commit, pre-push] - - id: bundle-audit - name: Bundle Audit - entry: bundle exec bundle-audit check - language: system - pass_filenames: false - stages: [pre-commit, pre-push] diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 776fa9dd0..5d571fa8b 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -68,6 +68,134 @@ Please follow these steps to have your contribution considered by the maintainer While the prerequisites above must be satisfied prior to having your pull request reviewed, the reviewer(s) may ask you to complete additional design work, tests, or other changes before your pull request can be ultimately accepted. +## Development Setup + +### 1. Ruby + +[`rbenv`](https://github.com/rbenv/rbenv) is the recommended way to manage Ruby versions: + +```shell +# Finalize the rbenv install (should work with both bash & zsh) +~/.rbenv/bin/rbenv init +# And then run +eval "$(~/.rbenv/bin/rbenv init - bash)" +``` + +### 2. Clone the repository + +Test data lives in a submodule, so make sure to clone recursively: + +```shell +git clone --recurse-submodules git@github.com:mindee/mindee-api-ruby.git ~/work/mindee/mindee-api-ruby +cd ~/work/mindee/mindee-api-ruby +``` + +If you already cloned without `--recurse-submodules`, run `git submodule update --init --recursive`. + +### 3. Install dependencies + +On Debian/Ubuntu: + +```shell +sudo apt install zlib1g-dev libssl-dev libreadline-dev libedit-dev libyaml-dev imagemagick +``` + +`imagemagick` is required at runtime by `mini_magick`, the rest are needed to build Ruby itself. + +```shell +# Ruby 3.3.0 is the minimum version supported by the SDK +rbenv install 3.3.0 +rbenv global 3.3.0 +``` + +### 4. Install code dependencies + +```shell +bundle config set --local path vendor +bundle install +``` + +### 5. Validate the install works by running unit tests + +```shell +bundle exec rake spec +``` + +## Local Quality Checks + +We use [`overcommit`](https://github.com/sds/overcommit) to run quality and security checks before +changes are committed and pushed. It is a development dependency of the gem, so `bundle install` +already installed it. The hooks are configured in [`.overcommit.yml`](.overcommit.yml), and +repository-local custom hooks live in [`.git-hooks`](.git-hooks). + +The same checks run in CI, see +[`.github/workflows/_static-analysis.yml`](.github/workflows/_static-analysis.yml). + +### Install `gitleaks` + +The `Gitleaks` pre-commit hook shells out to the +[`gitleaks`](https://github.com/gitleaks/gitleaks) binary, which is not a gem and must be installed +separately: + +```shell +brew install gitleaks +``` + +Otherwise, grab a binary from the +[releases page](https://github.com/gitleaks/gitleaks/releases) and put it on your `PATH`. + +### Install the hooks + +Run this once, after cloning: + +```shell +bundle exec overcommit --install +bundle exec overcommit --sign +bundle exec overcommit --sign pre-commit +``` + +`--install` writes the git hook stubs into `.git/hooks`, and the `--sign` calls tell overcommit you +trust the current contents of `.overcommit.yml` and of the custom hooks in `.git-hooks`. + +### What runs, and when + +* **pre-commit:** merge conflict markers, YAML syntax, trailing whitespace, file size, `rubocop`, + `steep check` (type checking) and `gitleaks` (secret scanning). +* **pre-push:** `bundle-audit` (dependency vulnerability check). + +### Run the hooks manually + +```shell +bundle exec overcommit --list-hooks # show which hooks are enabled +bundle exec overcommit --run # run the pre-commit hooks on all tracked files +bundle exec overcommit --run pre_push # run the pre-push hooks +bundle exec overcommit --diff main # run the pre-commit hooks on the diff against main +``` + +### Skipping hooks + +Skip one or more hooks for a single run, by name: + +```shell +SKIP=Gitleaks,Steep git commit +``` + +To bypass overcommit entirely (please use sparingly, CI runs the same checks): + +```shell +OVERCOMMIT_DISABLE=1 git commit +``` + +### Troubleshooting + +If overcommit refuses to run and warns that the configuration or the hooks have changed, review the +diff and then re-sign: + +```shell +bundle exec overcommit --sign +bundle exec overcommit --sign pre-commit +``` + ## Styleguides ### Git Commit Messages diff --git a/mindee.gemspec b/mindee.gemspec index ed46b531c..b5f84741f 100644 --- a/mindee.gemspec +++ b/mindee.gemspec @@ -37,6 +37,7 @@ Gem::Specification.new do |spec| spec.add_dependency 'pdf-reader', '~> 2.15' spec.add_development_dependency 'bundle-audit', '~> 0.2.0' + spec.add_development_dependency 'overcommit', '~> 0.71' spec.add_development_dependency 'rbs', '~> 3.10' spec.add_development_dependency 'rubocop', '~> 1.86' spec.add_development_dependency 'steep', '~> 1.10'