Go Modulesに対応しつつ、Dockerコンテナの中でプライベートリポジトリを go get する

# TL;DR

`Dockerコンテナ` x `module aware mode` x `プライベートリポジトリ` の組み合わせで `go get` できないことにお困りの場合は、 **`gitconfig` 書き換えや `ssh agent forwarding` などをしなくても**、 `GOPROXY` を使うとモジュール利用者が何も設定することなくコンテナ内で `go get` できるようになるよという話


# 1. はじめに

弊社では長らくGoのモジュール管理に [glide](https://github.com/Masterminds/glide) を使っていましたが、`Go Modules` が `Go1.13` で正式にサポートされることもあり乗り換えようとしました。

社内には多くの内製ライブラリがあり、それらをプライベートリポジトリで管理していました。 `glide` で管理していた際は、 `glide.yaml` に以下のように `x-oauth-basic` をつけて対応していました。

```yaml
- package: github.com/knocknote/private_repo
  repo: https://${basic_auth_token}:x-oauth-basic@github.com/knocknote/private_repo.git
```

また、開発にはDockerを利用しており、Goの実行環境は Dockerコンテナ上にあります。

これらの前提の上で、`Go1.12` x `GO111MODULE=on` な環境に移行することが目的です。  
しかし、 `go get` はプライベートリポジトリに対応していないため、普通にやると `gitconfig` をいじって `x-oauth-basic` をつけてアクセスするようにしたり、 `go get` を利用するときに内部的に `SSH` でアクセスできるように変更する必要があります。  

ですが、モジュールの依存解決をしたいがためにこれらの設定をチームメンバ全員に強いるのも嫌ですし、何も設定することなく `Docker` コンテナ内で `go get` できるようにしたいところです。  

そこで、`GOPROXY` に目をつけました。 `GOPROXY=http://localhost:6000` な具合に `GOPROXY` を設定しておくと、 `go get` する際に `http://localhost:6000` を通してモジュールをダウンロードしにいくような挙動になります。これによって、例えば `http://localhost:6000` で立ち上げているサーバーからモジュールを取得しに行くときには必ず `x-oauth-basic` でトークンをつけてアクセスしにいくみたいなことができるようになる可能性があります。

今回、この `GOPROXY` を利用した方法でやりたいことが実現できたため、そのアプローチの紹介をしたいと思います。
また、前段として今まで似たようなことをしたいときに利用したであろう解決パターンについても触れたいと思います。

# 2. よくある(と思っている)解決方法

## 2.1 gitconfig 書き換えパターン

### 2.1.1 gitconfig 書き換えで `x-oauth-basic` を常につけるようにする

`git config --global url."https://${token}:x-oauth-basic@github.com/".insteadOf "https://github.com/"`

### 2.1.2 gitconfig 書き換えで `https` でアクセスする代わりに SSH を利用する

`.gitconfig` に以下を記述

```
[url "git@github.com:"]
    insteadOf = https://github.com/
```


## 2.2 あらかじめ git clone パターン

はじめからプライベートリポジトリを `git clone` しておいて、それらを `git-submodule` なりで紐づけておくようなやり方です。  
弊社では `glide` でモジュール管理をしていた際、バージョン固定などの観点から `vendor` 配下をバージョン管理するようにし、プライベートリポジトリもそこに含めていました。また、`vendor` を `submodule` としてアプリケーションコードのリポジトリにマウントしておき、ライブラリを利用する側としては `git submodule update` してやれば簡単に更新できる状態で運用していました。

`module aware mode` では `vendor` 廃止の方向に動いているため、この方法をとりずらくなっていますが、
例えば `go.mod` の `replace` directive に

```
replace (
    github.com/knocknote/private_repo => ./vendor/github.com/knocknote/private_repo
)
```
と書けば同様のことができます。

# 3. GOPROXY を使ったやり方

`GOPROXY` を利用する場合に便利なプロジェクトとして [goproxyio/goproxy](https://github.com/goproxyio/goproxy) があります。これはまさにやりたいことそのままで、 `go get` をプロキシできるようになるのですが、プライベートリポジトリには対応していません。  

そこで、 簡単なパッチを当てたバージョンを [knocknote/goproxy](https://github.com/knocknote/goproxy) に作成しました。やっていることは `-basicAuthToken` オプションを足して、指定されている場合は `goproxy` 内部で(というよりかは `modfetch` の内部で) `git ls-remote https://github.com/knocknote/private-repo` を実行するときに、 `git ls-remote https://${basic_auth_token}:x-oauth-basic@github.com/knocknote/private-repo` を代わりに行うようにするというものです。

こちらは ビルド済みのものを DockerHub で公開しているため ( https://hub.docker.com/r/knocknote/goproxy )、以下のように `docker-compose.yml` に書くだけでプライベートリポジトリが取得できるようになるはずです。

```yaml
services:
  module_proxy:
    image: knocknote/goproxy:latest
    ports:
      - "8002:8002"
    entrypoint: /goproxy -cacheDir=/go -basicAuthToken=xxxxx -listen=0.0.0.0:8002

  app:
    environment:
      GO111MODULE: "on"
      GOPROXY: "http://module_proxy:8002"
(略)
```

この設定を一度書いてしまえば、使う側としては何も設定することなく、プライベートリポジトリだとしても `go get` でとってくることができます！ :)

# 4. まとめ

`GOPROXY` を使ったプライベートリポジトリの取得方法を紹介してみました。同じような悩みをかかえていた方の参考になれば嬉しいです。まだ昨日今日でガッと作った仕組みですが、`Go Modules` に移行できる兆しが見えてきた気がします。

また、もっと良い方法でやっているよとか、これだとこのケースに対応するのがしんどいのでこうしたほうが良いといった情報があれば教えていただけると嬉しいです！