GitLab API 利用

# GitLab API 利用
Personal Access Token を利用して GitLab API をたたく方法メモ と golang で実際に GitLab API を利用するサンプル。
[GitLab API](https://docs.gitlab.com/ee/api/)
## Personal Access Token
GitLab API を利用するためにまずは Personal Access Token を取得する。取得方法は、[こちら](https://capibara1969.com/55/)がとても分かりやすい。Personal Access Token は HTTPリクエストのヘッダか URL Query に埋め込んで利用する。

```
  $ curl https://gitlab.example.com/api/v4/projects?private_token=<your_access_token>
```
or

```
  $ curl --header "Private-Token: <your_access_token>" https://gitlab.example.com/api/v4/projects
```
## URL など
### URL(API v4)
- **["https://" + [GitLabのHost] + "/api/v4/"]** から始まるURLを利用。
その後ろに **"projects/:id/"** とか **"groups/:id/"** とかで続けると id 指定したprojectやgroupの情報をGETしたり、POSTでそのprojectやgroupの操作ができる。
[Group and project members API](https://docs.gitlab.com/ee/api/members.html)

### 注意：取得できるレコード数 (19/5/30 追記)
[pagination](https://docs.gitlab.com/ee/api/#pagination) により、デフォルトで取得できるレコード数が 20 の場合があるので注意。

```
  $ curl --header "Private-Token: <your_access_token>" https://gitlab.example.com/api/v4/projects/
```

上記だと、Token を作成したユーザがアクセスできる Project 数が 20 より多い場合、全ての情報が取得できず 20 個までしか情報を取得できない。全ての情報を取得するには、URLクエリーに page=n (n は 1 から始まる) を指定して JSON 配列が空になるまで n をインクリメントするなどが必要となる。

```
  $ curl --header "Private-Token: <your_access_token>" https://gitlab.example.com/api/v4/projects/page=1
  $ curl --header "Private-Token: <your_access_token>" https://gitlab.example.com/api/v4/projects/page=2
  $ curl --header "Private-Token: <your_access_token>" https://gitlab.example.com/api/v4/projects/page=3
  ...
```

### projects, gorups の [:id]
id には int かパス形式の string を指定することが可能。

- **int型の id:** Webブラウザ でGitLabにアクセスすると確認できる。(Projectのページを開くと表示される Project ID が該当。)
- **string型の id:** 例えば、**"hoge/foo/bar"** という階層構造の **"bar"** を指定したい場合 **"/"** を **"%2F"** に置換した値 (**"hoge%2Ffoo%2Fbar"**) を利用。

```
  "https://" + [GitLabのHost] + "/api/v4/projects/hoge%2Ffoo%2Fbar"
```
FireFoxなどのWebブラウザで、上記の URL にアクセスすると GET で返される JSON形式が確認できる。
![sample.png](https://qiita-image-store.s3.amazonaws.com/0/19939/475a4e6b-d37e-e9a8-ad24-8a9e056fbbfd.png)

#### curlで確認
```
  $ curl https://gitlab.com/api/v4/projects/tsuyuzaki%2Ftsuyu_p1?private_token=abcdefghijk
  {"id":11704151,"description":"","name":"tsuyu_p1","name_with_namespace":"tsuyuzaki / tsuyu_p1","path":"tsuyu_p1","path_with_namespace":"tsuyuzaki/tsuyu_p1","created_at":"2019-04-05T23:47:52.231Z","default_branch":null,"tag_list":[],"ssh_url_to_repo":"git@gitlab.com:tsuyuzaki/tsuyu_p1.git","http_url_to_repo":"https://gitlab.com/tsuyuzaki/tsuyu_p1.git","web_url":"https://gitlab.com/tsuyuzaki/tsuyu_p1","readme_url":null,"avatar_url":null,"star_count":0,"forks_count":0,"last_activity_at":"2019-04-05T23:47:52.231Z","namespace":{"id":4955386,"name":"tsuyuzaki","path":"tsuyuzaki","kind":"user","full_path":"tsuyuzaki","parent_id":null},"_links":{"self":"https://gitlab.com/api/v4/projects/11704151","issues":"https://gitlab.com/api/v4/projects/11704151/issues","merge_requests":"https://gitlab.com/api/v4/projects/11704151/merge_requests","repo_branches":"https://gitlab.com/api/v4/projects/11704151/repository/branches","labels":"https://gitlab.com/api/v4/projects/11704151/labels","events":"https://gitlab.com/api/v4/projects/11704151/events","members":"https://gitlab.com/api/v4/projects/11704151/members"},"archived":false,"visibility":"private","owner":{"id":3803336,"name":"tsuyuzaki","username":"tsuyuzaki","state":"active","avatar_url":"https://secure.gravatar.com/avatar/c03b2bddcaa4fb6d943e72ab23964a0f?s=80\u0026d=identicon","web_url":"https://gitlab.com/tsuyuzaki"},"resolve_outdated_diff_discussions":false,"container_registry_enabled":true,"issues_enabled":true,"merge_requests_enabled":true,"wiki_enabled":true,"jobs_enabled":true,"snippets_enabled":true,"shared_runners_enabled":true,"lfs_enabled":true,"creator_id":3803336,"import_status":"none","import_error":null,"open_issues_count":0,"runners_token":"WibD8zxQ-WsTjk1FmAZU","public_jobs":true,"ci_config_path":null,"shared_with_groups":[{"group_id":4958170,"group_name":"tsuyu_group","group_full_path":"tsuyu_group","group_access_level":30,"expires_at":null}],"only_allow_merge_if_pipeline_succeeds":false,"request_access_enabled":false,"only_allow_merge_if_all_discussions_are_resolved":false,"printing_merge_request_link_enabled":true,"merge_method":"merge","permissions":{"project_access":{"access_level":40,"notification_level":3},"group_access":null},"mirror":false,"external_authorization_classification_label":""}
```

### Project に Member として Group 追加するPOST の例
[こちら](https://docs.gitlab.com/ee/api/projects.html#share-project-with-group)を参照。

- **URL:** "https://"+host+"/api/v4/projects/"+[:id]+"/share"
- **Body(JSON):**  {id: string or int, group_id: int, group_access: int, expires_at: string}
    - group_access は 10,20,30,40,50 のいずれか。expires_at は指定しなくてもよい。

```sample.json
{"id":"tsuyuzaki%2Ftsuyu_p1","group_id":4958170,"group_access":30,"expires_at":""}
```

## サンプル
以下のサンプルを作成。

- Group が Member に追加されている Project 一覧を取得するサンプル
- 複数 Group、複数 User を Member として一括登録するサンプル

### Group が Member に追加されている Project 一覧を取得するサンプル
引数に Personal Access Token と Group の URL を渡すと指定した Group が Member に追加されている ProjectURL の一覧を取得するコード。

```getprjs.go
package main

import (
	"encoding/json"
	"fmt"
	"net/http"
	"net/url"
	"os"
	"strings"
)

type Node struct {
	ID     int
	WebURL string `json:"web_url"`
}
type GroupInfo struct {
	SharedProjects []*Node `json:"shared_projects"`
}

func main() {
	if len(os.Args) != 3 {
		fmt.Println("Please input your token and GroupURL.")
		return
	}
	url, err := url.Parse(os.Args[2])
	if err != nil {
		fmt.Fprintf(os.Stderr, "url.Parse error [%v]", err)
		return
	}

	prjID, ok := toStrID(url.Path)
	if !ok {
		return
	}
	req, err := http.NewRequest(
		"GET",
		"https://"+url.Host+"/api/v4/groups/"+prjID,
		nil)
	if err != nil {
		fmt.Fprintf(os.Stderr, "http.NewRequest error[%v]\n", err)
		return
	}
	req.Header.Set("Private-Token", os.Args[1])

	client := &http.Client{}
	resp, err := client.Do(req)
	if err != nil {
		fmt.Fprintf(os.Stderr, "client.Do error[%v]\n", err)
		return
	}
	defer resp.Body.Close()

	var result GroupInfo
	if err := json.NewDecoder(resp.Body).Decode(&result); err != nil {
		fmt.Fprintf(os.Stderr, "Decode error[%v]\n", err)
		return
	}
	for _, p := range result.SharedProjects {
		fmt.Println(p.WebURL)
	}
}

func toStrID(path string) (string, bool) {
	if path == "" {
		fmt.Println("Invalid Path")
		return "", false
	}
	if path[0] == '/' {
		return strings.Replace(path[1:], `/`, `%2F`, -1), true
	} else {
		return strings.Replace(path, `/`, `%2F`, -1), true
	}
}
```
#### 実行
```
  $ go build getprjs.go
  $ getprjs.exe xyzabcdefg https://gitlab.com/tsuyu_group
  https://gitlab.com/tsuyuzaki/tsuyu_p1
```

### 複数 Group、複数 User を Member として一括登録するサンプル
[サンプル](https://github.com/tsuyuzaki/go/tree/master/Tools/GitLab)
以下の2つの機能を有します。
1. 複数 User を、複数の Group に Member として一括登録する。
2. 複数 Group を、複数の Project に Member として一括登録する。

#### 実行
ExcelがインストールされたWindows上で実行してください。

```
 $ go build postto.go
 $ postto.exe [g or p] <Personal Access Token>
```
- 第一引数に g 指定すると 1. の機能が、p 指定すると 2. の機能が利用できます。
- posttoを実行すると、groups.csv または、users.csv というファイルが Excel で開かれるので、以下の形式の CSV を記述してください。(\# から始まる行は読み飛ばします。)
    - プログラム実行ディレクトリに groups/users.csv というファイルがなければ作成し、ファイルが存在すれば、その CSV ファイルが開かれます。


```users.csv
[Group1のURL],[AccessLevel(10/20/30 or 40)],[UserName1],[UserName2],...
[Group2のURL],[AccessLevel(10/20/30 or 40)],[UserName1],[UserName2],[UserName3],...
...
```
CSV を上書きして、Excelを閉じると、各行の Group に、同一の行で指定された AccessLevel の Role で User が一括で追加されます。

```groups.csv
[Project1のURL],[GroupAccess(10/20/30/40 or 50)],[Group1のURL],[Group2のURL],...
[Project2のURL],[GroupAccess(10/20/30/40 or 50)],[Group1のURL],[Group2のURL],[Group3のURL],...
...
```
CSV を上書きして、Excelを閉じると、各行の Project の Member に、同一の行で指定された GroupAccess の Role で Group が一括で追加されます。

Role の詳細は[こちら](https://docs.gitlab.com/ee/api/members.html)参照。

```
10 => Guest access
20 => Reporter access
30 => Developer access
40 => Maintainer access
50 => Owner access # Only valid for groups
```