Disclaimer: This is only relevant for Gitlab Enterprise Edition

Objective

I have several GitLab Projects (repositories) in several Groups and Subgroups. I have another set of Gitlab Groups and Subgroups which contain Members. Each of the projects can use a CODEOWNERS file to determine who can approve Merge Requests. The goal is to have everyone being able to contribute code everywhere and at the same time have dedicated groups of merge request approvers.

Detail

I don’t want to repeat what has been already written in the docs. Please read those first in case this doesn’t make sense. Specifically, look at Project, Group, Member, Permissions and CODEOWNERS.

Consider following structure

1
2
3
4
5
6
7
8
Gitlab
├── peoplegroups
│   ├── bazengineers
│   ├── quuxengineers
│   └── ...
└── fooprojects
    ├── bar
    └── ...

where fooprojects is a Gitlab Group containing several projects, one of which is bar. Next, each Subgroup of peoplegroups contains Members whom I want to give some level of Gitlab access. My goal for this example is that all bazengineers can make code contributions in all fooprojects and that quuxengineers are merge request approvers for the bar project.

How do?

Giving bazengineers access to fooprojects is as simple as inviting them to the fooprojects group with at least Developer permissions. Since permission cascades, they can now contribute everywhere. However, with Developer permissions, bazengineers have also gained the permission to approve Merge Requests.

To fix what we’ve done, we can either make a cumbersome custom Approval rule in the repository. I repeat. In each repository. This sucks even when done in Terraform (check this provider).

The other option is to use the cryptic “All eligible users”, which is a black magic incantation permitting the use of CODEOWNERS schema. So, what this rule actually does? It takes Eligible users as Merge Request approvers. As of now, those eligible users are only Developers invited into the project (bar). To include quuxengineers, we will invite them to the bar project. Next, let’s setup CODEOWNERS file in the project root with following contents

1
* @peoplegroups/quuxengineers

This makes quuxengineers Code Owners of the whole repository. List of owners can be conveniently seen at the top of each file. To have complete setup, we must require Code Owners Approval. This setting is not in Merge Request Approvals, but in Protected Branches under Repository settings.

Finally, we have what we wanted. All bazengineers can create merge requests in all repositories under fooprojects. In each repository we can set specific groups of owners for specific files in CODEOWNERS and require their approval for related changes.

Caveats

I have skipped over some pitfalls which can be encountered when trying to set up the above system.

Terraforming this

I mentioned “Eligible Users” setting to be cryptic and these are the reasons why. What this setting actually does is not clear, but it creates any_approver approval rule to appear in the Projects API responce. You can check be hitting Gitlab API with https://<yourgitlabhost>/api/v4/projects/<yourprojectid>/approval_rules and getting something like this:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
[
  {
    "id": <yourprojectid>,
    "name": "All Members",
    "rule_type": "any_approver",
    "eligible_approvers": [],
    "approvals_required": 1,
    "users": [],
    "groups": [],
    "contains_hidden_groups": false,
    "protected_branches": [],
    "applies_to_all_protected_branches": false
  }
]

Obscure thing about this any_approver rule is that unless you increase the number of approvals required above zero, this rule does not appear at all. Since then, it is there forever. And it breaks Terraform as of now. What happens is that if you play around with this setting in a project and then try to create this rule via Terraform, apply fails because there can be only one rule of this kind.

Inheriting Owner Groups

Given all the necessary setup in Gitlab Projects, it is quite natural to assume that inviting quuxengineers just to fooprojects would be enough. However, this is not the case. Firstly, it doesn’t work because it is not supported and after careful reading it can be seen the CODEOWNERS docs:

Inviting Subgroup Y to a parent group of Project A is not supported. To set Subgroup Y as Code Owners, add this group directly to the project itself.

Secondly, if you do actually set it up, it will appear as if it works even though it doesn’t. To illustrate it in our example, let’s invite quuxengineers to fooprojects as Owners. We will now see that in bar project, the quuxengineers group is member with source fooprojects.

Now, let’s create a new project under fooprojects, name it nope, and set up CODEOWNERS file same as in bar. Inspecting membership, we will see that quuxengineers are member with source fooprojects, same as in bar. However, when we try to make a Merge Request, the approvers don’t appear there. Also, if we take a look at any file in the repository, we don’t see the Code Owners list header.

What is happening here is that Gitlab UI sucks, because the two repositories look the same yet they don’t behave the same. Problem is in how the permissions are displayed. What is shown is the Max Role. We have set up Owner role on the fooprojects and this is what it shows and where it comes from. However, this inheritance does not work for CODEOWNERS, so these permissions are essentially useless in this respect. What is actually permitting the CODEOWNERS approvals is the Developer level access directly granted in project bar, but this permission is overshadowed by the one from fooprojects.