
Одной из самых нужных функций, которой нет в бесплатной версии GitLab, является возможность контролировать Merge request (MR), используя обязательный code review.
Сделаем минимальный функционал сами запретим Merge, пока несколько разработчиков не поставят палец вверх на MR.
Зачем это вообще?
Наша организация вполне может позволить себе купить лицензию GitLab. Но, так как разработка ведется в закрытом контуре без доступа в интернет, и есть жесткое планирование бюджета, закупка лицензий self-managed с нужным функционалом может затянуться на многие месяцы, а работать нужно уже сейчас.
В итоге приходится:
- либо совсем запрещать Merge в защищенные ветки для части разработчиков, но тогда разработчики, имеющие право на Merge, получают конфликты при слиянии чужих MR как бонус;
- либо давать возможность делать бесконтрольные слияния с вашей мастер-веткой без code review, даже если это Junior, устроившийся только вчера.
Первым же делом отправился гуглить, полагая, что уж точно кто-то что-то подобное уже сделал (без доработки кода), но оказалось, что подобной реализации в community версии еще не было.
Общая схема работы
В качестве примера настроим Merge request approvals на тестовом репозитории myapp
gitlab.com/gitlab-ce-mr-approvals/myapp
- Создадим токен для доступа к API GitLab (через него будем получать информацию о количестве голосов за и против)
- Добавим токен в переменные GitLab
- Запретим Merge при ошибках в пайплайне (если голосов за недостаточно)
- Настроим проверку голосов как часть пайплайна CI/CD
- Запретим делать коммиты в защищенные ветки, все изменения проводим только через MR
- Проверим, что получилось в итоге
1. Создаем токен для доступа к API
Заходим в Настройки пользователя Токены доступа и записываем токен

2. Добавляем токен в переменные Gitlab
Например, на предыдущем шаге мы получили токен QmN2Y0NOUFlfeXhvd21ZS01aQzgK
Открываем Настройки CI/CD Переменные Добавить переменную GITLAB_TOKEN_FOR_CI

В итоге получим:

Это можно сделать как на одном репозитории, так и на группе репозиториев.
3. Ставим запрет на Merge, если не получены одобрения коллег после проведенного code review
В нашем случае запретом на Merge будет являться то, что сборочный конвейер вернет ошибку при недостаточном количестве голосов.
Заходим в Настройки Основные Запросы на слияние Проверки слияния и включаем опцию Сборочные линии должны успешно выполниться

4. Настраиваем пайплайн
stages: - build - testvariables: NEED_VOTES: 1include: - remote: "https://gitlab.com/gitlab-ce-mr-approvals/ci/-/raw/master/check-approve.gitlab-ci.yml"run-myapp: stage: build script: echo "Hello world"
Расположение нового файла конвейера нужно будет указать, зайдя в репозиторий myapp Настройки CI/CD Сборочные линии Пользовательский путь конфигурации CI указать новый файл, например myapp-ci.gitlab-ci.yml@gitlab-ce-mr-approvals/ci
Пример контейнеров с линтерами, которые вы можете встроить в ваш пайплайн:
hub.docker.com/r/gableroux/gitlab-ci-lint
hub.docker.com/r/sebiwi/gitlab-ci-validate
И пример стадии проверки:
stages: - lintlint: image: sebiwi/gitlab-ci-validate:1.3.0 variables: GITLAB_HOST: https://gitlab.com script: - CI_FILES=(./*.yml) - for f in "${CI_FILES[@]}"; do gitlab-ci-validate $f; done;
Осталось добавить в ваш пайплайн несколько параметров, чтобы все заработало:
stages:
- test
variables:
NEED_VOTES: 1
include:
- remote:
"https://gitlab.com/gitlab-ce-mr-approvals/ci/-/raw/master/check-approve.gitlab-ci.yml"
Переменная NEED_VOTES определяет сколько пальцев вверх должно быть у MR, чтобы был доступен Merge. Значение, равное единице, означает, что вы сами можете одобрить свой MR, лайкнув его.
include подключает стадию test, проверяющую количество лайков.
stages:
- build
- test
variables:
NEED_VOTES: 0
include:
- remote:
"https://gitlab.com/gitlab-ce-mr-approvals/ci/-/raw/master/check-approve.gitlab-ci.yml"
run-myapp:
stage: build
image: openjdk
script:
- echo CI_MERGE_REQUEST_TARGET_BRANCH_NAME
$CI_MERGE_REQUEST_TARGET_BRANCH_NAME
- java HelloWorld.java
ci-mr:
stage: test
script:
- echo ${CI_API_V4_URL}
- echo "CI_PROJECT_ID ${CI_PROJECT_ID}"
- echo "CI_COMMIT_SHA ${CI_COMMIT_SHA}"
- "export MR_ID=$(curl --silent --request GET --header
\"PRIVATE-TOKEN: $GITLAB_TOKEN_FOR_CI\"
${CI_API_V4_URL}/projects/${CI_PROJECT_ID}/merge_requests | jq
\".[] | if .sha == \\\"${CI_COMMIT_SHA}\\\" then .id else {} end\"
| grep --invert-match {})"
- "export MR_TITLE=$(curl --silent --request GET --header
\"PRIVATE-TOKEN: $GITLAB_TOKEN_FOR_CI\"
${CI_API_V4_URL}/projects/${CI_PROJECT_ID}/merge_requests | jq
\".[] | if .sha == \\\"${CI_COMMIT_SHA}\\\" then .title else {}
end\" | grep --invert-match {})"
- "export MR_WIP=$(curl --silent --request GET --header
\"PRIVATE-TOKEN: $GITLAB_TOKEN_FOR_CI\"
${CI_API_V4_URL}/projects/${CI_PROJECT_ID}/merge_requests | jq
\".[] | if .sha == \\\"${CI_COMMIT_SHA}\\\" then .work_in_progress
else {} end\" | grep --invert-match {})"
- "export MR_UPVOTES=$(curl --silent --request GET --header
\"PRIVATE-TOKEN: $GITLAB_TOKEN_FOR_CI\"
${CI_API_V4_URL}/projects/${CI_PROJECT_ID}/merge_requests | jq
\".[] | if .sha == \\\"${CI_COMMIT_SHA}\\\" then .upvotes else {}
end\" | grep --invert-match {})"
- "export MR_DOWNVOTES=$(curl --silent --request GET --header
\"PRIVATE-TOKEN: $GITLAB_TOKEN_FOR_CI\"
${CI_API_V4_URL}/projects/${CI_PROJECT_ID}/merge_requests | jq
\".[] | if .sha == \\\"${CI_COMMIT_SHA}\\\" then .downvotes else {}
end\" | grep --invert-match {})"
- MR_VOTES=$(expr ${MR_UPVOTES} - ${MR_DOWNVOTES})
- NEED_VOTES_REAL=${NEED_VOTES:-1}
- echo "MR_ID ${MR_ID} MR_TITLE ${MR_TITLE} MR_WIP ${MR_WIP}
MR_UPVOTES ${MR_UPVOTES} MR_DOWNVOTES ${MR_DOWNVOTES}"
- echo "MR_VOTES ${MR_VOTES} Up vote = 1, down vote = -1, MR OK if
votes >=${NEED_VOTES_REAL}"
- if [ "${MR_VOTES}" -ge "$(expr ${NEED_VOTES_REAL})" ];
then
echo "MR OK";
else
echo "MR ERROR Need more votes";
exit 1;
fi
image: laptevss/gitlab-api-util
rules:
- if: '$CI_MERGE_REQUEST_TARGET_BRANCH_NAME == "master" ||
$CI_MERGE_REQUEST_TARGET_BRANCH_NAME =~
/^release\/.*$/'
Подробнее о том, что происходит при проверке:
- установлено ограничение, что проверка будет только при создании MR в ветки master или release/*
- используя API GitLab, получаем количество лайков и дизлайков
- вычисляем разность между положительными и отрицательными откликами
- если разность меньше заданного нами значения в NEED_VOTES, то блокируем возможность сделать слияние
5. Запрещаем коммиты в защищенные ветки
Определяем ветки, для которых мы должны проводить code review и указываем, что работать с ними можно только через MR.
Для этого заходим в Настройки Репозиторий Protected Branches

6. Проверяем
Зададим NEED_VOTES: 0
Делаем MR и ставим дизлайк.

В логах сборки:

Теперь ставим лайк и запускаем повторную проверку:
