{"id":15055438,"url":"https://github.com/cschindlbeck/gitlab_repo_manager","last_synced_at":"2025-04-10T03:40:49.455Z","repository":{"id":247131621,"uuid":"824022061","full_name":"cschindlbeck/gitlab_repo_manager","owner":"cschindlbeck","description":"A guide on how to automate settings of complex GitLab repos with Terraform ","archived":false,"fork":false,"pushed_at":"2024-07-05T08:18:21.000Z","size":28,"stargazers_count":2,"open_issues_count":0,"forks_count":2,"subscribers_count":1,"default_branch":"master","last_synced_at":"2025-03-24T04:51:39.731Z","etag":null,"topics":["automation","devops","gitlab","terraform"],"latest_commit_sha":null,"homepage":"","language":"HCL","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/cschindlbeck.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2024-07-04T07:58:57.000Z","updated_at":"2024-07-22T01:48:17.000Z","dependencies_parsed_at":"2024-07-06T20:07:13.620Z","dependency_job_id":null,"html_url":"https://github.com/cschindlbeck/gitlab_repo_manager","commit_stats":null,"previous_names":["cschindlbeck/gitlab_repo_manager"],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cschindlbeck%2Fgitlab_repo_manager","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cschindlbeck%2Fgitlab_repo_manager/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cschindlbeck%2Fgitlab_repo_manager/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cschindlbeck%2Fgitlab_repo_manager/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/cschindlbeck","download_url":"https://codeload.github.com/cschindlbeck/gitlab_repo_manager/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248154890,"owners_count":21056541,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["automation","devops","gitlab","terraform"],"created_at":"2024-09-24T21:42:08.761Z","updated_at":"2025-04-10T03:40:49.434Z","avatar_url":"https://github.com/cschindlbeck.png","language":"HCL","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Gitlab Repository Manager\n\nThis repo shows an example on how to automate GitLab settings with Terraform.\n\nFork this repo and use it as a template to start adapting it for your organization!\n\nYou can extend this for various other automation tasks.\n\n## Reasoning and Advantages of this Approach\n\nAre you responsible in your organization for a complex project with many repositories? Are you tired of going through all repositories manually and do such tasks as adding/removing users and setting merge permissions?\n\nBy following this guide, you can:\n\n- Save Time: Automate repetitive tasks and focus on what truly matters.\n- Reduce Errors: Minimize human errors with consistent automation scripts.\n- Improve Productivity: Streamline your workflow and enhance team collaboration.\n\nThis guide exemplifies how to leverage Terraform to automate branch protection permissions in GitLab.\n\n## Fictitious example\n\nIn this example, branch protection rules for two fictitious teams (backend and frontend) with different repositories respectively, are automated.\n\nThe repositories hosted on GitLab and each teams requires specific branch protection rules. The frontend team handles solely frontend repos, and the backend team handles the backend repos.\n\nTo get a rough idea, this table summarizes what we want to achieve automatically\n\n![table](table.png)\n\n## Step-by-Step Explanation\n\n### Initial Setup\n\nSecurity first, so export your Gitlab PAT (private access token) via the command line, by setting an environment variable with\n\n```bash\nexport $TF_VAR_gitlab_token=YOURTOKEN\n```\n\nin order to avoid exposing sensitive information in your git repo (put it in your .bashrc/.zshrc if you do not want to do this each time you open a terminal). This environment variable is then accessible in HCL (Terraform's own programming language) via `var.gitlab_token` (make sure you give at least repo_read, repo_write permissions for the PAT).\n\nNow we can add the [Gitlab provider](https://registry.terraform.io/providers/gitlabhq/gitlab/latest/docs) to enable Terraform to communicate with Gitlab:\n\n```hcl\nterraform {\n  required_providers {\n    gitlab = {\n      source  = \"gitlabhq/gitlab\"\n      version = \"17.1.0\"\n    }\n  }\n}\n\nvariable \"gitlab_token\" {}\nprovider \"gitlab\" {\n  base_url = \"https://gitlab.iav.com/api/v4\"\n  token    = var.gitlab_token\n}\n```\n\nInitialize the terraform directory\n\n\n```bash\nterraform init\n```\n\nwhich will download the necessary code for communication with the Gitlab API in a .terraform directory.\n\n### Get a list of frontend and backend repositories\n\nThen, we must obtain a list of all frontend and backend repositories. For simplicity, we assume that these repositories (in Gitlab called \"projects\") are found in the same directory (in Gitlab called \"group\").\n\nEnter the path to the group in the next block and \n\n```hcl\n# Specify the path to your group\ndata \"gitlab_group\" \"group\" {\n  full_path = \"\u003cYOUR_PATH_TO_YOUR_GROUP\u003e\"\n}\n\n# Filter repositories for the frontend\ndata \"gitlab_projects\" \"frontend_projects\" {\n  group_id          = data.gitlab_group.group.id\n  order_by          = \"name\"\n  include_subgroups = true\n  search            = \"frontend\" # filter out repos containing the word 'frontend'\n}\n\n# Filter repositories for the backend\ndata \"gitlab_projects\" \"backend_projects\" {\n  group_id          = data.gitlab_group.group.id\n  order_by          = \"name\"\n  include_subgroups = true\n  search            = \"backend\" # filter out repos containing the word 'backend'\n}\n```\n\nThese so-called data resources will obtain the current information of the repositories and store them in the terraform.tfstate file. By specifing a search field, we can obtain only the repos that contain a certain word, here front- and backend. This makes them accessible via `data.gitlab_projects.frontend_projects.projects` and `data.gitlab_projects.backend_projects.projects` so that we can change the settings there accordingly. Note that this will only work if the repos adhere to a certain naming scheme, here they must include the words frontend or backend.\n\n### Specify who is admin or dev\n\nNow, let's define who is a backend and a frontend admin or developer, starting with the frontend admins.\n\nYou can get the IDs by searching for the person in Gitlab and right-clicking the three dots on the right to copy their ID.\n\nHere, we set a list of frontend admins\n\n```hcl\nvariable \"frontend_admins\" {\n  description = \"List of frontend admin ids\"\n  type        = list(number)\n  default     = [71, 63]\n}\n```\n\nAnd let's repeat the same for the remaining ones with\n\n```hcl\nvariable \"frontend_devs\" {\n  description = \"List of frontend dev ids\"\n  type        = list(number)\n  default     = [82, 102, 182]\n}\n\nvariable \"backend_admins\" {\n  description = \"List of backend admin ids\"\n  type        = list(number)\n  default     = [713]\n}\n\nvariable \"backend_devs\" {\n  description = \"List of backend dev ids\"\n  type        = list(number)\n  default     = [767, 90, 152]\n}\n```\n\n### Protect branches\n\nFinally we can set our branch protection rules for all the repositories in an automated way.\n\nLet's start with protecting the master branch of the frontend repositories, by including this block.\n\n```hcl\nresource \"gitlab_branch_protection\" \"frontend_master_protection\" {\n  for_each                     = { for project in data.gitlab_projects.frontend_projects.projects : project.id =\u003e project }\n  project                      = each.value.id\n  allow_force_push             = true\n  code_owner_approval_required = true\n  branch                       = \"master\"\n  push_access_level            = \"no one\" # do not push to master directly\n  merge_access_level           = \"no one\" # we will specify who can push below\n  dynamic \"allowed_to_merge\" {\n    for_each = var.frontend_admins\n    content {\n      user_id = allowed_to_merge.value\n    }\n  }\n}\n```\n\nLet's explore this large block in more detail:\n\n- `for_each`: We loop over all our frontend repositories that we gathered in the data block previously\n- `allow_force_push`/`code_owner_approval_required`: Allow force push by the admins/Require code owner approval\n- `push_access_level`/`merge_access_level`: Set level of push/merge access (no one, developer, maintainer, owner), we allow no role here, but individual accounts\n- `allowed_to_merge`: We loop over all frontend admins for merge rights\n\nWe set levels to `no one` in this example because backend and frontend devs might both be classified as developer but we want only to include the ones responsible for their part.\n\nNow, we can do the same for the dev branch\n\n```hcl\nresource \"gitlab_branch_protection\" \"frontend_dev_protection\" {\n  for_each                     = { for project in data.gitlab_projects.frontend_projects.projects : project.id =\u003e project }\n  project                      = each.value.id\n  allow_force_push             = false\n  code_owner_approval_required = true\n  branch                       = \"dev\"\n  push_access_level            = \"developer\" # here, let's allow every developer to push, regardless of front- or backend\n  merge_access_level           = \"no one\"    # we will specify who can push below\n  dynamic \"allowed_to_merge\" {\n    for_each = distinct(concat(var.frontend_admins, var.frontend_devs)) # union over devs and admins\n    content {\n      user_id = allowed_to_merge.value\n    }\n  }\n  dynamic \"allowed_to_push\" {\n    for_each = distinct(concat(var.frontend_admins, var.frontend_devs)) # union over devs and admins\n    content {\n      user_id = allowed_to_push.value\n    }\n  }\n}\n```\n\nWe follow the same approach as for the master branch with a few differences for the dev branch:\n\n- `allow_force_push`/`code_owner_approval_required`: We disallow force push for anyone here\n- `allowed_to_push`/`allowed_to_merge`: We loop over the union of frontend admins and devs to let them have push/merge rights (admins should be able to merge/push too, right?)\n\nNow that we have done this for the frontend repos, we can do this in an analagous fashion for the backend repos by changing only `frontend_projects` to `backend_projects` and `frontend_admins` and `frontend_devs` to `backend_admins` and `backend_devs`, such as\n\n```hcl\n  ...\n  for_each                     = { for project in data.gitlab_projects.backend_projects.projects : project.id =\u003e project }\n  ...\n    for_each = distinct(concat(var.backend_admins, var.backend_devs)) # union over devs and admins\n```\n\nYou can take a look at the entire code [here](https://github.com/cschindlbeck/gitlab_repo_manager), or adapt the settings to your preferences/organizational process.\n\nNow we have every set up to make the changes readily availabe in GitLab.\n\n### Deployment\n\nFinally, we can apply our changes via\n\n```bash\nterraform apply\n```\n\nCarefully look through the upcoming changes and confirm with `yes` if you are sure everything is set correctly.\n\nAttention: if you have already set up your repos (and settings) in Gitlab, this will fail as Terraform will complain that the resources already exist. In this case, you will need to import the current state to your terraform.tfstate first, so that terraform is able to manage this.\n\nYou can do this for each project you want to import via\n\n```bash\nterraform import gitlab_project.gitlab_repo_manager \u003cproject_id\u003e\n```\n\nor, alternatively, for more complex projects, check out [terraformer](https://github.com/GoogleCloudPlatform/terraformer), a CLI tool to import/update your tfstate from existing infrastructure.\n\n\n## Summary\n\nThis guide walked you through an example on how to set up branch protection in multiple Gitlab repos automatically via Terraform. You can take this example as a starting point on how to automate much more settings in Gitlab repos, such as setting the preferred merge method, tag protection, branch rules and much more! \n\nIf you host your repos on GitHub you can take this example as a template and take the structure and adapt it step-by-step for the [GitHub provider](https://registry.terraform.io/providers/integrations/github/latest/docs).\n\nThe days are numbered where you had to do this manually for (countless) repos in a complex project for every subteam. Embrace the mighty tooling capabilities of Terraform!\n\n\nBonus: Check out how to set the preferred merge method (the only correct answer is fast-forward only, right?) in my [github repo](https://github.com/cschindlbeck/gitlab_repo_manager).\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcschindlbeck%2Fgitlab_repo_manager","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fcschindlbeck%2Fgitlab_repo_manager","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcschindlbeck%2Fgitlab_repo_manager/lists"}