I started out writing a naive bot in GitLab CI/CD pipeline that would provide informational feedback about a merge request (MR) in the MR. Since this was my first attempt writing such a thing, I kept the scope limited and simple yet effective. The basic scope of the bot was, - Run the bot in the context of an MR (see _Rules_ below) - Work only with one MR, the one running the pipeline - Use GitLab REST APIs to read from the MR and write to it - Use bash, curl, git, and jq. I could have used Python or Go but this was a _naive_ bot so I couldn't make it simpler than this. In this post I will not provide the code. Instead, I will provide enough information for anyone to get started writing their own bot. [TOC] # Predefined Variables GitLab pipelines provide a lot of [predefined variables](https://docs.gitlab.com/ee/ci/variables/predefined_variables.html). I highly enocurage you to review the list and make use of them. I found through experience that a lot of the features I expected to be available in the REST APIs, or would have to write git commands for, were available as data in the form of predefined variables. The variables I used were, - CI_API_V4_URL - CI_MERGE_REQUEST_DIFF_BASE_SHA - CI_MERGE_REQUEST_IID - CI_PROJECT_ID For example, the base URL I created to work with MRs looked like, "${CI_API_V4_URL}/projects/${CI_PROJECT_ID}/merge_requests/${CI_MERGE_REQUEST_IID}" # API Endpoints Since this post is about working with MRs, I only used the [Merge requests API](https://docs.gitlab.com/ee/api/merge_requests.html) endpoints. They were, - [/merge_requests/${CI_MERGE_REQUEST_IID}](https://docs.gitlab.com/ee/api/merge_requests.html#update-mr) - [/merge_requests/${CI_MERGE_REQUEST_IID}/diffs](https://docs.gitlab.com/ee/api/merge_requests.html#list-merge-request-diffs) - [/merge_requests/${CI_MERGE_REQUEST_IID}/discussions](https://docs.gitlab.com/ee/api/discussions.html#merge-requests) - [/merge_requests/${CI_MERGE_REQUEST_IID}/notes](https://docs.gitlab.com/ee/api/notes.html#merge-requests) ## diffs I wanted to use the /diffs endpoint as it gives structured data about the diff in the MR. One thing I was looking for was the names of files that were changed in the MR and this endpoint gave me exactly what I needed. $ curl -X GET "${CI_API_V4_URL}/projects/${CI_PROJECT_ID}/merge_requests/${CI_MERGE_REQUEST_IID}/diffs" | jq '.[] | select(.new_path=="SOME-FILE-NAME") | .diff' | wc -w One version of the bot I wrote used git to give me the list of files that had changed. The easiest way to do that in the pipeline was to use the predefined variable CI_MERGE_REQUEST_DIFF_BASE_SHA, $ git diff --name-only "${CI_MERGE_REQUEST_DIFF_BASE_SHA}" ## discussions and notes These are related but different. To start a new thread in the MR, use /discussions. To delete it, use /notes. To list all comments (including threads), use /notes. $ curl -X POST --header 'Content-Type: application/json' --data '{"body": "YOUR TEXT FOR THREAD/COMMENT"}' "${CI_API_V4_URL}/projects/${CI_PROJECT_ID}/merge_requests/${CI_MERGE_REQUEST_IID}/discussions" $ curl -X GET "${CI_API_V4_URL}/projects/${CI_PROJECT_ID}/merge_requests/${CI_MERGE_REQUEST_IID}/notes" | jq '.[] | select(.type=="DiscussionNote" and .body=="YOUR TEXT FOR THREAD/COMMENT" and .resolved==false) | .id' $ curl -X DELETE "${CI_API_V4_URL}/projects/${CI_PROJECT_ID}/merge_requests/${CI_MERGE_REQUEST_IID}/notes/:id" ## labels I also added and removed labels. Use [Update MR](https://docs.gitlab.com/ee/api/merge_requests.html#update-mr) for that use case. $ curl -X PUT --header 'Content-Type: application/x-www-form-urlencode' --data 'add_labels=YOUR-LABEL' "${CI_API_V4_URL}/projects/${CI_PROJECT_ID}/merge_requests/${CI_MERGE_REQUEST_IID}" # Authentication This was the most interesting part. When a pipeline is running it gets a predefined variable called [CI_JOB_TOKEN](https://docs.gitlab.com/ee/ci/jobs/ci_job_token.html). This token can be used to authenticate to GitLab REST APIs. However, it has a drawback because GitLab doesn't allow access to all REST API endpoints with this authentication method. Sadly, it doesn't support merge request API endpoints. There are three other options available, - [Personal access token](https://docs.gitlab.com/ee/user/profile/personal_access_tokens.html) - [Project access token](https://docs.gitlab.com/ee/user/project/settings/project_access_tokens.html) - [Group access token](https://docs.gitlab.com/ee/user/group/settings/group_access_tokens.html) As the names suggest, these access tokens have differing scopes but provide the same ability: authenticate to GitLab and use REST APIs. They all support merge request API endpoints. Since I was writing a shared pipeline, I didn't want to use my personal access token. I didn't have enough permissions in the GitLab group to create group access token. That left me with project access token because I was a _maintainer_ on the project and had access to create it. I created the token and injected it into the pipeline using a [CI/CD variable](https://docs.gitlab.com/ee/ci/variables/#define-a-cicd-variable-in-the-ui). The variable was called GITLAB_TOKEN, following the convention used by [glab](https://docs.gitlab.com/ee/editor_extensions/gitlab_cli/) ([Authenticate with GitLab](https://docs.gitlab.com/ee/editor_extensions/gitlab_cli/#authenticate-with-gitlab)). To authenticate I used a header with curl everytime, $ curl --header "PRIVATE-TOKEN: ${GITLAB_TOKEN}" # ... rest of the command # Rules I wanted to run the stage only in MRs. The _rules_ section in .gitlab-ci.yml had the following rule, rules: - if: '$CI_PIPELINE_SOURCE == "merge_request_event"'