Упрощаем подключение линтеров в пайплайны Gitlab CI

от автора

Всем привет! Меня зовут Юра, я Python-разработчик в Точке. В статье я покажу, как написать шаблон с линтером для Gitlab CI, чтобы при старте нового проекта (или уже запущенного) было легко добавить линтер в пайплайны.

Мотивация

В своих проектах я активно использую flakeheaven (современная обёртка над flake8) с большим количеством плагинов — это позволяет избежать бесполезных дискуссий на код-ревью, поддерживать порядок в проектах и предотвращать некоторые ошибки и уязвимости. За решение этих проблем я плачу временем на прекоммит-хуки и задачи в CI.

Когда плагинов становится слишком много, проверка занимает больше времени и, соответственно, сильнее вырывает из потока. Поэтому я обратил внимание на ruff  — реализацию правил из flake8 и нескольких самых популярных плагинов на Rust. С помощью ruff проверка объёмных проектов проходит очень быстро: например, в моём проекте запуск flakeheaven занимает минуту, а ruff — около секунды.

В Точке довольно большое Python-сообщество, которое занимается не только поддержкой внутренних библиотек, но и инструментов для разработчиков, таких как шаблоны для сборки и анализа кода. Поэтому, когда я настроил ruff в своём проекте, я решил сделать шаблон для Gitlab CI, который можно было бы использовать в других проектах.

Gitlab CI

У нас используется плагин для Gitlab CI, который позволяет запускать джобы в кластере k8s, поэтому сделаем проект с образом Docker и в шаблоне будем использовать его. 

Дисклеймер

Как у любой приличной компании, у нас внутри развёрнут docker registry, но для простоты дальше в коде я вместо него буду использовать dockerhub и прятать его под переменной окружения $CI_REGISTRY.

В Dockerfile возьмём последнюю версию python, установим линтер и загрузим его в хранилище:

# Dockerfile FROM python:3.12-slim RUN python3 -m pip install ruff==0.1.0

Для запуска ruff требуется версия python 3.7, но для анализа используются значение requires-python в pyproject.toml и аргумент запуска --target-version, поэтому мы используем самый свежий образ python. Для версионирования используем следующую схему: когда обновляем Dockerfile с новой версией линтера, помечаем коммит тегом, совпадающим с версией ruff. В gitlab-ci при появлении нового тега будем загружать новую версию образа в хранилище

# gitlab-ci.yaml variables:   CI_REGISTRY: ...  stages:  - build  build:  stage: build  rules:    - if: $CI_COMMIT_TAG  # before_script: docker login  script:    - docker build -t "ruff:$CI_COMMIT_TAG"    - docker tag "ruff:$CI_COMMIT_TAG" "$CI_REGISTRY/ruff-template/ruff:$CI_COMMIT_TAG"    - docker push "$CI_REGISTRY/ruff-template/ruff:$CI_COMMIT_TAG"

Теперь, когда у нас появился образ с линтером в хранилище, пришло время написать шаблон, в котором мы будем его запускать:

# linters.yaml .lint-ruff:  variables:    RUFF_VERSION: latest    RUFF_SOURCE: .    CI_REGISTRY: ...  image: $CI_REGISTRY/ruff-template/ruff:$RUFF_VERSION  script:    - |      set -ex      RUFF_ARGS=""      if [[ -n $RUFF_TARGET_VERSION ]]; then        RUFF_ARGS="$RUFF_ARGS --target-version $RUFF_TARGET_VERSION"      fi      echo "ruff args: $RUFF_ARGS"      echo "ruff source: $RUFF_SOURCE"      ruff $RUFF_ARGS $RUFF_SOURCE

С помощью переменной RUFF_TARGET_VERSION пользователь сможет указывать целевую версию языка для запуска. Аналогично можно добавить поддержку остальных параметров запуска, если появится необходимость. Теперь, когда у нас есть образ и шаблон, мы можем его использовать в своих проектах:

# .gitlab-ci.yaml include: - project: ruff-template   file: linters.yaml    stages: - test    lint-ruff:     extends:       .lint-ruff     stage: test     variables:       RUFF_TARGET_VERSION: py312

У меня в проекте используется Python 3.12, поэтому я указываю переменную окружения, чтобы правильно работали проверки импортов из future и новые фишки языка.

Настройка

По умолчанию ruff проверяет код на соответствие стандарту PEP8 и очевидных проблем вроде неиспользованных импортов с помощью правил pyflakes и pycodestyle. В отличие от flake8, ruff не поддерживает 3rd party плагины, поэтому все дополнительные правила реализованы в проекте. На данный момент реализовано более 700 правил, поэтому с высокой долей вероятности ваш любимый плагин уже в их числе. Например:

  • DTZ не даёт создать объект timestamp без таймзоны

  •  PT помогает поддерживать одинаковый стиль в тестах

  • TRY нужен для соблюдения лучших практик в работе с исключениями

Полный список правил можно посмотреть в документации

Ниже готовый конфиг без «вкусовщины»: его можно скопировать в pyproject.toml и начать использовать ruff на полную катушку.

[tool.ruff]  src = ["src", "tests"] target-version = "py311" line-length = 90  extend-select = [    "C90",    # mccabe    "N",      # pep8-naming    "UP",     # pyupgrade    "S",      # bandit    "BLE",    # flake8-blind-except    "B",      # bugbear    "C4",     # comprehensions    "DTZ",    # datetimez    "EM",     # error-messages    "FA",     # future-annotations    "ISC",    # implicit string concat    "PIE",    # flake8-pie    "PT",     # flake8-pytest-style    "SLF",    # flake8-self    "SIM",    # flake8-simplify    "ARG",    # flake8-unused-argument    "ERA",    # eradicate commented out code    "TRY",    # tryceratops ]  [tool.ruff.extend-per-file-ignores] "migrations/**.py" = ["E501"]   # line length "tests/**.py" = ["S101"]  # use of `assert`

Итоги

Создание шаблона — это объёмная задача, которую можно разбить на несколько этапов, и на каждом из них можно извлечь пользу:

  1. Добавление линтера упрощает поддержку единого стиля в проекте и предотвращает часть ошибок. 

  2. Отдельный образ можно использовать в нескольких проектах, и задача на линт будет занимать меньше времени. 

  3. Шаблон для запуска образа позволяет экономить время на поддержке для нескольких команд. 

Я надеюсь, что благодаря статье вы увидели, что общие инструменты для разработчиков — это не привилегия мегакорпораций, и их можно начинать использовать, когда у вас становится больше одного репозитория.


ссылка на оригинал статьи https://habr.com/ru/articles/768292/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *