Terraformでgitlabのgroup / project作成するどー！

## tl;dr :rocket: 

ここみてくださいまし。。。
https://www.terraform.io/docs/providers/gitlab/index.html

## モチベーション :frowning2:

いくつかのチームが、それぞれ複数のproductを開発する際に、ある一定の規則に従った gitlab group / project (= repository) を作りたくなった。

例えば、どのチームも必ず1つの group を持ち、その中に project を作っていくとする。チームにはそれぞれAWSやGCPなどのリソースを提供するが、そのCredential管理は、一定の規則に則って行いたい。そんなときに gitlab group variable を group 作成時に予め設定しておければポチポチ設定しなくて良いので楽ですよね。変数を protect してないだとか、mask してないみたいなヒューマンエラーも防止できる。

project で言えば、チーム / 組織で使う課題管理ツールやドキュメンテーションフレームワーク（JIRA や Confluence みたいな）が決まっていて、gitlab の wiki / Issues は使えないように設定しておきたい。
だとか、
Group runner 立ててるから、間違って Shared runner が使われないようにしておきたい。
だとか、
こうした設定項目をprojectごとに設定していくのは、なかなか骨が折れるし、設定し忘れも発生する。

## どうしたらいい？ :thinking: 

公式で [Custom Project Templates](https://docs.gitlab.com/ee/gitlab-basics/create-project.html#custom-project-templates-premium) と言うものがあります。が、 Premium / Silver プラン向けの機能として提供されおり、手が出ない。。。
そこで、 terraform でさくっとやってしまおうと言うのが今回のはなし。

## やっていく :muscle: 

### 前提

- 今回は、複数の project を内包した、1つの group を作ることを目標とする
- group / project 共にプライベートにする
- group と project はそれぞれ異なる variable を持っていて、 protect や mask のパラメータが任意に設定できる
- 開発メンバーは group に紐付け、 project 固有のメンバーは設定しない
- project は wiki / snippet / shared runner を無効にする
- project の任意のブランチとタグを命名規則で protect する

### 準備

gitlab の api access token が必要です。アカウント設定の `access tokens` メニューから、 scopes を `api` で作ります。 
作った token を環境変数に登録します。

```bash
 export TF_VAR_gitlab_token=<your token>
```

### 作る

group

```terraform:gitlab.tf
# ここが group そのものを作るリソース
resource "gitlab_group" "group" {
  name        = var.group.name
  path        = var.group.name
  description = var.group.description

  visibility_level = "private"
}

# ここは group に追加したいメンバーのIDをユーザー名から探してくれる
data "gitlab_user" "developer" {
  count    = length(var.group.members)
  username = var.group.members[count.index]
}

# ここが group にメンバーを追加するリソース
resource "gitlab_group_membership" "group_member" {
  count        = length(data.gitlab_user.developer)
  group_id     = gitlab_group.group.id
  user_id      = data.gitlab_user.developer[count.index].id
  access_level = "developer"
}

# ここが group variable を設定するリソース
resource "gitlab_group_variable" "group_variable" {
  count         = length(var.variables.group_variables)
  group         = gitlab_group.group.id
  key           = var.variables.group_variables[count.index].key
  value         = var.variables.group_variables[count.index].value
  variable_type = "env_var"
  protected     = var.variables.group_variables[count.index].protected
}
```

project

```terraform:gitlab.tf
# ここが project そのものを作るリソース
resource "gitlab_project" "proj" {
  count       = length(var.group.projects)
  name        = var.group.projects[count.index].name
  description = var.group.projects[count.index].description

  visibility_level = "private"
  namespace_id     = gitlab_group.group.id

  approvals_before_merge = 1     # マージリクエスト承認者数
  wiki_enabled           = false # wiki 無効化
  snippets_enabled       = false # snippet 無効化
  shared_runners_enabled = false # shared runner 無効化
  initialize_with_readme = true  # readme でリポジトリ初期化
}

# ここはブランチを protect するリソース
# 複数の project に対して複数のルールを設定しているのでやや複雑
resource "gitlab_branch_protection" "branch_protect" {
  count              = length(var.group.projects) * length(var.group.protection_rules.branches)
  project            = gitlab_project.proj[floor(count.index / length(var.group.protection_rules.branches))].id
  branch             = var.group.protection_rules.branches[count.index % length(var.group.protection_rules.branches)]
  push_access_level  = "no one"
  merge_access_level = "maintainer"
}

# ここはタグを protect するリソース
resource "gitlab_tag_protection" "tag_protect" {
  count               = length(var.group.projects) * length(var.group.protection_rules.tags)
  project             = gitlab_project.proj[floor(count.index / length(var.group.protection_rules.tags))].id
  tag                 = var.group.protection_rules.tags[count.index % length(var.group.protection_rules.tags)]
  create_access_level = "maintainer"
}

# ここが project variable を設定するリソース
resource "gitlab_project_variable" "proj_variable" {
  count         = length(var.variables.project_variables)
  project       = gitlab_project.proj[var.variables.project_variables[count.index].project_index].id
  key           = var.variables.project_variables[count.index].key
  value         = var.variables.project_variables[count.index].value
  variable_type = "env_var"
  protected     = var.variables.project_variables[count.index].protected
}
```

変数

```terraform:variable.tf
# gitlab provider 使いますよ宣言
provider "gitlab" {
  token = var.gitlab_token
}

# 環境変数に設定しているtoken
variable "gitlab_token" {
}

# この辺りはよしなに
variable "group" {
  default = {
    name        = "my-terraformed-group"
    description = "my-terraformed-group description"
    members = [
      "登録したいユーザ１のユーザ名",
      "登録したいユーザ２のユーザ名"
    ]
    projects = [
      {
        name        = "terraformed-prj1"
        description = "terraformed-prj1 project description"
      },
      {
        name        = "terraformed-prj2"
        description = "terraformed-prj2 project description"
      }
    ]
    protection_rules = {
      branches = ["dev-*", "master"]
      tags     = ["v*"]
    }
  }
}

# ここもよしなに
variable "variables" {
  default = {
    group_variables = [
      {
        key       = "hoge_key"
        value     = "hoge-val"
        protected = true
      },
      {
        key       = "fuga_key"
        value     = "fuga-val"
        protected = false
      }
    ]
    project_variables = [
      {
        project_index = 0 # 設定したいプロジェクトのindex もっとうまいやり方きっとある
        key           = "a"
        value         = "a.val"
        protected     = true
      },
      {
        project_index = 0
        key           = "b"
        value         = "b.val"
        protected     = false
      }
    ]
  }
}

```

## 感想 

とても簡単に作れた。
terraform は各パラメータごとに、現状からどう変化するか示してくれていいですよね。

以上です :relaxed: