One of the most needed features that is not in the free version of GitLab is the ability to vote against zeroing the repository to control the Merge request (MR) using the mandatory code review.
We will do the minimum functionality ourselves - we will disable Merge until several developers give a “thumbs up” to MR.
Why is this at all?
Our organization can afford to buy a GitLab license. But, since the development is carried out in a closed loop without access to the Internet, and there is a strict budget planning, the purchase of self-managed licenses with the necessary functionality can take many months, and you need to work now.
As a result, you have to:
- or completely prohibit Merge into protected branches for some developers, but then developers who have the right to Merge receive conflicts when merging other people's MRs as a bonus;
- or allow you to do uncontrolled merges with your master branch without code review, even if it's a Junior who just settled in yesterday.
The first thing I did was go google, believing that someone had already done something similar (without refining the code), but it turned out that there was no such implementation in the community version yet.
General scheme of work
As an example, let's set up Merge request approvals on a test repository
- Let's create a token for accessing the GitLab API (through it we will receive information about the number of votes for and against)
- Add a token to GitLab variables
- Disable Merge if there are errors in the pipeline (if there are not enough “for” votes)
- Set up vote validation as part of the CI/CD pipeline
- We will prohibit making commits to protected branches, we make all changes only through MR
- Let's check what happened in the end
1. Create a token to access the API
Go to User Settings → Access Tokens and write down the token:
Account to receive the token
API access allows you to do almost anything with your repositories, so I suggest you create a separate Gitlab account, give it minimal rights to your repositories (like Reporter) and get a token for that account.
2. Add the token to Gitlab variables
For example, in the previous step, we received a token QmN2Y0NOUFlfeXhvd21ZS01aQzgK
Open Settings → CI/CD → Variables → Add Variable → GITLAB_TOKEN_FOR_CI
As a result, we get:
This can be done both on one repository and on a group of repositories.
3. We put a ban on Merge if the approval of colleagues is not received after the code review
In our case, the ban on Merge will be that the assembly pipeline will return an error if there are not enough votes.
Go to Settings → General → Merge Requests → Merge Checks and enable the option Assembly lines must run successfully.
4. Set up the pipeline
If you haven't made a CI/CD pipeline for your application yet
Create a file in the root of the repository .gitlab-ci.yml with simple content:
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"
Separate repository for CI/CD configuration
I would recommend making a separate repository where you need to create a myapp.gitlab-ci.yml file to set up the pipeline. This way you can better control the access of contributors who can change the build pipeline and get an access token.
The location of the new pipeline file will need to be specified by going to the myapp repository - Settings - CI / CD - Assembly lines - Custom CI configuration path - specify a new file, for example myapp.gitlab-ci.yml@gitlab-ce-mr-approvals/ci
Tip: Use a linter to make changes to GitLab CI files
Even if you work alone, working through MR will be a good helper, running all your changes to the pipeline files through the linter. If you make a mistake in the syntax of the YAML file, this will not allow you to break the working pipeline, but will simply block Merge.
An example of containers with linters that you can embed in your pipeline:
And an example of the validation stage:
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;
It remains to add a few parameters to your pipeline to make it work:
stages:
- test
variables:
NEED_VOTES: 1
include:
- remote: "https://gitlab.com/gitlab-ce-mr-approvals/ci/-/raw/master/check-approve.gitlab-ci.yml"
The NEED_VOTES variable determines how many "thumbs up" MR must have in order for Merge to be available. A value of one means that you yourself can approve your MR by "liking" it.
include includes the test stage, which checks the number of "likes".
The simplest pipeline using myapp.gitlab-ci.yml as an example
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
Contents of check-approve.gitlab-ci.yml
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/.*$/'
Learn more about what happens when checking:
- the restriction is set that the check will be only when creating an MR in the master or release /* branches
- using the GitLab API, get the number of "likes" and "dislikes"
- calculate the difference between positive and negative responses
- if the difference is less than the value we set in NEED_VOTES, then we block the ability to merge
5. Disable commits to protected branches
We determine the branches for which we should conduct code review and indicate that they can only be worked with through MR.
To do this, go to Settings → Repository → Protected Branches:
6. Checking
Set NEED_VOTES: 0
We do MR and put a "dislike".
In the build logs:
Now put "like" and run a re-check:
Source: habr.com