Gitlab-python approval rules

I am looking to create a python script using the gitlab module to keep an inventory of all projects under my group and make sure they all have the same two approval rules applied to them.

  • Team Lead approval
  • Team group approval

I know my group ID and can get the list of projects and also whether or not approval rules exist or not at every project level. I’m unable to figure out how to apply/create the rules to projects that don’t have rules currently in place.

Any help would be greatly appreciated:

General python script is as follows:

import os
import gitlab

gitlab_url = ‘’
gitlab_token = os.environ[‘GITLAB_TOKEN’]
gl = gitlab.Gitlab(url=gitlab_url, private_token=gitlab_token)

group = gl.groups.get(126)
project = gl.projects.get(858)
print(f’PROJECT: {project}’)
pmras = project.approvalrules.list()

if not pmras:
print(“The project does not contain any rules. Let’s add some.”)
# create rule here and apply it to the project
else:
print(f’There are rules currently applied. They are: {pmras}’)

1 Like

Hi @cshafe02

The documentation for the API is here and the relevant Python documentation is here.

Sometimes you need to dig around a bit to see how the API works and how the Python library matches it! This example on the page linked above is the closest I could find to what you want to do:

mr.approvals.set_approvers(approvals_required = 1, approver_ids=[105],
                           approver_group_ids=[653, 654],
                           approval_rule_name="my MR custom approval rule")

Personally when I’m writing code like this, I fire up an interactive Python terminal, and create a project object, and dig around with dir() and similar until I can see how everything fits together.

3 Likes

Hi @snim2

Thank you for this information! I’ve been reading the same documentation for the python module and have been messing around with it. The thing I’m trying to figure out is being able to reach the “mr” class declaration with what information I currently have. The documentation gives plenty of examples but nothing relating to getting from the project class to the mr class. I’ll do some more research and when I arise on a solution, I’ll share here for others.

Are you running this in a CI pipeline? If so, you can use the CI_OPEN_MERGE_REQUESTS environment variable.

Currently I am not. I am developing it locally but would eventually move it to a scheduled pipeline job within a project.

Did you made any progress …I am also looking for some help on this . API methods in the documentation are confusing did not help much . However my research I found following … but it is not working for me ,but I was told it workd with diffrent gitlab version 2019.(non EE ) .

projects = gl.projects.list(all=True)
| for project in projects:
|
|     # [ .. ]
|
|     print("[MRGE] Setting merge request approval settings: num_approvers = %d" % int(num_approvers))
|     project.approvals_before_merge = int(num_approvers)
|
|     p_mras = project.approvals.get()
|     p_mras.approvals_before_merge = int(num_approvers)
|
|     project.reset_approvals_on_push = 1
|     project.disable_overriding_approvers_per_merge_request = 0
|     project.merge_requests_author_approval = 0
|     project.only_allow_merge_if_all_discussions_are_resolved = 1
|     project.remove_source_branch_after_merge = 1
|
|     p_mras.reset_approvals_on_push = 1
|     p_mras.disable_overriding_approvers_per_merge_request = 0
|     p_mras.merge_requests_author_approval = 0
|     p_mras.only_allow_merge_if_all_discussions_are_resolved = 1
|     ret = project.approvals.set_approvers(approver_ids=[1], approver_group_ids=[11])
|
|     p_mras.save()
|     project.save()

I wasn’t able to work on what I was requesting last year but was able to do so this year. I initially worked it out with my team on writing it out with API requests against GitLab initially before taking the time to rewrite it using the gitlab-python module. Below is what I came up with:

`
gl = gitlab.Gitlab(url=GITLAB_URL, private_token=GL_TOKEN)
projects = gl.projects.list(search=“idm”, search_namespaces=True, get_all=True)
project_list = [(project.name, project.id) for project in projects]

for repo_name, repo_id in project_list:
    single_project = gl.projects.get(repo_id)
    repo_rules = single_project.approvalrules.list()

    if repo_rules:
        for rule in repo_rules:
            user_list = []
            if any(x in rule.name.lower() for x in ["all team", "lead", "team"]):
                if "all team" in rule.name.lower():
                    approval_group = [idmteam_users.get(k) for k in all_approval_group]
                elif "lead" in rule.name.lower():
                    approval_group = [idmteam_users.get(k) for k in lead_approval_group]
                else:
                    approval_group = [idmteam_users.get(k) for k in main_approval_group]
                for user in rule.eligible_approvers:
                    user_list.append(user["id"])

                approval_group.sort()
                user_list.sort()

                if approval_group != user_list:
                    rule.user_ids = approval_group
                    rule.save()
                    changed_list.append(repo_name)
            else:
                nonstandard_list.append(repo_name)
    else:
        skipped_list.append(repo_name)

if not nonstandard_list:
    print("No non-standard repositories found!\n")
else:
    print("Repositories with non-standard approval rules:")
    print(f"{nonstandard_list}\n")

if not skipped_list:
    print("No repositories were skipped\n")
else:
    print("Repositories being skipped due to no approval rules:")
    print(f"{skipped_list}\n")

if not changed_list:
    print("All repositories with approval rules are good!\n")
else:
    print("The following repositories were updated:")
    print(f"{changed_list}\n")

If all one cares about it checking and conforming MR approval rules, then the output lines can be removed along with some of the conditionals. Here, I had some lists of standard users to match specific rule name strings. If they didn’t match, then correct them if needed. Otherwise, carry on and give some output at the end.