
Jedną z najważniejszych funkcji, której brakuje w darmowej wersji GitLab, jest możliwość głosowania przeciwko resetowaniu repozytoriów w procesie kontroli Merge request (MR) z wykorzystaniem obowiązkowego przeglądu kodu.
Stworzymy minimalną funkcjonalność sami — zablokujemy Merge, dopóki kilku deweloperów nie poda „zielonej karty” dla MR.
Dlaczego to w ogóle jest potrzebne?
Nasza organizacja całkiem dobrze może sobie pozwolić na zakup licencji GitLab. Jednakże, ponieważ rozwój odbywa się w zamkniętej sieci bez dostępu do internetu i istnieje sztywne planowanie budżetu, zakup licencji self-managed z potrzebnymi funkcjami może się wydłużyć na wiele miesięcy, a praca musi być realizowana już teraz.
W rezultacie musimy:
- albo całkowicie zablokować Merge w chronionych gałęziach dla części deweloperów, ale wtedy deweloperzy mający prawo do Merge będą otrzymywać konflikty przy łączeniu cudzych MR jako bonus;
- albo dawać możliwość dokonywania niekontrolowanych połączeń z twoją główną gałęzią bez przeglądu kodu, nawet jeśli jest to junior, który dopiero co zaczął.
Najpierw udałem się do Google, myśląc, że na pewno ktoś coś podobnego już zrobił (bez modyfikacji kodu), ale okazało się, że takiej implementacji w wersji community jeszcze nie było.
Ogólna schema pracy
Jako przykład skonfigurujemy zatwierdzenia merge request w testowym repozytorium :
- Stworzymy token dostępu do API GitLab (przez który uzyskamy informacje o liczbie głosów „za” i „przeciw”)
- Dodamy token do zmiennych GitLab
- Zabronimy Merge w przypadku błędów w pipeline (jeśli liczba głosów „za” jest niewystarczająca)
- Skonfigurujemy sprawdzanie głosów jako część pipeline CI/CD
- Zabronimy dokonywania commitów w chronione gałęzie, wszystkie zmiany wprowadzamy tylko poprzez MR
- Sprawdzimy, co wyszło na końcu
1. Tworzymy token dostępu do API
Wchodzimy w Ustawienia użytkownika → Tokeny dostępu i zapisujemy token:

Konto do uzyskania tokena
Dostęp do API pozwala na praktycznie wszystko z twoimi repozytoriami, dlatego zalecam stworzenie osobnego konta GitLab, nadanie mu minimalnych uprawnień do twoich repozytoriów (na przykład Reporter) i uzyskanie tokena dla tego konta.
2. Dodajemy token do zmiennych GitLab
Na przykład, na poprzednim kroku uzyskaliśmy token QmN2Y0NOUFlfeXhvd21ZS01aQzgK
Otwieramy Ustawienia → CI/CD → Zmienne → Dodaj zmienną → GITLAB_TOKEN_FOR_CI

Ostatecznie uzyskamy:

Można to zrobić zarówno w jednym repozytorium, jak i w grupie repozytoriów.
3. Ustawiamy zakaz Merge, jeśli nie uzyskano zgód od kolegów po przeprowadzeniu przeglądu kodu
W naszym przypadku zakazem Merge będzie to, że proces budowy zwróci błąd przy niewystarczającej liczbie głosów.
Przechodzimy do Ustawienia → Główne → Żądania scalania → Sprawdzanie scalania i włączamy opcję Procesy budowlane muszą zakończyć się pomyślnie.

4. Ustawiamy pipeline
Jeśli jeszcze nie stworzyłeś procesu CI/CD dla swojej aplikacji
Tworzymy w głównym katalogu repozytorium plik .gitlab-ci.yml o najprostszym zawartości:
stages:
- build
- test
variables:
NEED_VOTES: 1
include:
- remote: "https://gitlab.com/gitlab-ce-mr-approvals/ci/-/raw/master/check-approve.gitlab-ci.yml"
run-myapp:
stage: build
script: echo "Hello world"
Oddzielne repozytorium dla konfiguracji CI/CD
Zalecałbym stworzenie oddzielnego repozytorium, w którym powinieneś utworzyć plik myapp.gitlab-ci.yml do konfiguracji pipeline. Dzięki temu będziesz mógł lepiej kontrolować dostęp uczestników, którzy mogą zmieniać proces budowy i uzyskać token dostępu.
Lokalizację nowego pliku pipeline będziesz musiał wskazać, wchodząc do repozytorium myapp — Ustawienia — CI/CD — Procesy budowlane — Własna ścieżka konfiguracji CI — wskaż nowy plik, na przykład myapp.gitlab-ci.yml@gitlab-ce-mr-approvals/Ci
Porada: używaj lintera do wprowadzania zmian w plikach GitLab CI
Nawet jeśli pracujesz sam, dobrym wsparciem będzie praca przez MR, przepuszczając wszystkie swoje zmiany plików pipeline przez linter. Jeśli popełnisz błąd w składni pliku YAML, nie zepsuje to działającego pipeline'a, a jedynie zablokuje Merge.
Przykład kontenerów z linterami, które możesz dodać do swojego pipeline:
I przykład etapu weryfikacji:
stages:
- lint
lint:
stage: lint
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;
Pozostaje dodać do swojego pipeline kilka parametrów, aby wszystko działało:
etapy:
- test
variables:
NEED_VOTES: 1
include:
- remote: "https://gitlab.com/gitlab-ce-mr-approvals/ci/-/raw/master/check-approve.gitlab-ci.yml"
Zmienna NEED_VOTES określa, ile „polubień” musi mieć MR, aby Merge był możliwy. Wartość równa jeden oznacza, że sam możesz zatwierdzić swój MR, „lajkując” go.
include dodaje etap testu, który sprawdza liczbę „polubień”.
Najprostszy pipeline na przykładzie myapp.gitlab-ci.yml
etapy:
- budować
- 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
skrypt:
- echo CI_MERGE_REQUEST_TARGET_BRANCH_NAME $CI_MERGE_REQUEST_TARGET_BRANCH_NAME
- java HelloWorld.java
Zawartość check-approve.gitlab-ci.yml
ci-mr:
etap: test
skrypt:
- 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}\/projekty\/${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}\/projekty\/${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}\/projekty\/${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}\/projekty\/${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}\/projekty\/${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} Głos za = 1, głos przeciw = -1, MR OK jeśli głosy >=${NEED_VOTES_REAL}"
- if [ "${MR_VOTES}" -ge "$(expr ${NEED_VOTES_REAL})" ];
then
echo "MR OK";
inaczej
echo "MR BŁĄD Potrzebne więcej głosów";
exit 1;
fi
obraz: laptevss\/gitlab-api-util
zasady:
- if: '$CI_MERGE_REQUEST_TARGET_BRANCH_NAME == "master" || $CI_MERGE_REQUEST_TARGET_BRANCH_NAME =~ \/^release\/.*$\/'
Więcej informacji na temat tego, co dzieje się podczas weryfikacji:
- ustawiono ograniczenie mówiące, że weryfikacja będzie miała miejsce tylko przy tworzeniu MR w gałęziach master lub release/*
- korzystając z API GitLab, uzyskujemy liczbę 'polubień' i 'niepolubień'
- obliczamy różnicę między pozytywnymi a negatywnymi reakcjami
- jeśli różnica jest mniejsza niż ustalona przez nas wartość w NEED_VOTES, blokujemy możliwość wykonania merge
5. Zakazujemy commitów do chronionych gałęzi
Określamy gałęzie, dla których trzeba przeprowadzić przegląd kodu i wskazujemy, że praca z nimi jest możliwa tylko przez MR.
W tym celu przechodzimy do Ustawienia → Repozytorium → Chronione gałęzie:

6. Sprawdzamy
Ustalamy NEED_VOTES: 0
Tworzymy MR i dajemy 'niepolubienie'.

W logach budowy:

Teraz dajemy 'polubienie' i uruchamiamy ponowną weryfikację:

Źródło: habr.com
