Go言語の共通モジュールをGitHubのPrivate Repoからimportしたい – ローカル編

こんにちは。てぃろです。

今回はGo言語についての話です。

プロダクト開発をしていて、可能な限りリポジトリを細分化しないように構成してきたのですが、どうしてもリポジトリを跨いだ共通的なモジュールというのが出てきてしまいました。

こういうケースは珍しくなく、経験のある運用方法としてはGitサブモジュールで別リポジトリの中身を参照してしまうというのがありました。

ブランチ名を明確にしておくなど運用ルールをきちんとすれば運用できるのですが、コミットログにはコミットハッシュが出るのみなのでサブモジュールの何を使っているかわからないため、レビューが面倒でわかりにくくなってしまいます。そういったことも影響して、開発時にサブモジュールのブランチ切り替えを忘れやすかったり、コンテナビルド時にはサブモジュールのブランチ切り替えを気をつけないといけないなど、何かと負担が大きかった記憶があります。

今回は運用負荷の高いことはしたくないので、別の方法としてGoモジュールの機能を使ってGitHubから直接importしてみようと思います。

ただこれですべてが今解決したかというとそんなこともなさそうだということを先に述べておきます。結論というか、現状のわかったことと不安点を箇条書すると以下のようになります。

  • GOPRIVATEの環境変数とghコマンドでの認証で手軽にPrivate Repoのインポートができる
  • 共通モジュールのリポジトリでバージョンのタグを付ければ任意のバージョンをインポートできる
  • 開発中のバージョンの切り替えでgo.modを触ることになるが、Productionに上げるまでの間に書き直しを忘れないか心配 <-ココ重要

ここからは一つ一つについて説明していきます。

GOPRIVATEとghコマンドの認証でPrivate Repoをインポートする

go 1.13から採用されている環境変数にGORIVATEがあります。

GOPRIVATEを使用することによって、プライベートリポジトリでもインポートできるようになるということで設定します。後述するバージョン指定の観点でも重要ですが、指定するのはリポジトリのルートであることが望ましいと思います。例えば以下のような形です。

export GOPRIVATE=github.com/YOUR_ORGANIZATION/YOUR_REPOSITORY

具体的には、以下のとおりです。

export GOPRIVATE=github.com/google/s2a-go

調べてみると、複数指定をカンマ区切りしたり、アスタリスクを使って一度に複数指定するなどあるようですが今回どれもうまく動かなかったのでこの設定に今回は落ち着きました。

ただ、このリポジトリの中で複数のパッケージで分けてコードを書いてしまえば、インポートするリポジトリは一つでも複数種類のソースをインポートできるので今回は問題ないです。

次に、インポートするにはGitHubのプライベートリポジトリにアクセスするための認証が必要です。いくつか方法があるようですが、今回オススメするのはghコマンドで認証しておくという方法です。

ghコマンドはGitHub CLIのコマンドのことです。

他にはPersonal Access Tokenを使用する方法がありますが、ghコマンドで認証する方法はGitHubの権限管理を活用して許可した人しか触れないという制約を自然に実現できるので組織的な観点から見て便利です。もちろん個人の観点から見たとしてもいつものghコマンドを打って認証をするだけなのでそこまで手間ではないと思います。

バージョンのタグを付けて任意のバージョンをインポートする

共通モジュールは開発において最新版だけを使えばいいわけではなく、少し前のバージョンのままにしておきたかったり、共通モジュールも一緒に開発したいというケースも出ます。そこでバージョンを柔軟に指定できるようにしたいですが、これがバージョンのタグを付ければ実現できます。

Goのモジュールのバージョンを決めるにはGitでタグ付けをすることでバージョンを指定することができます。GitHubのWeb画面からもReleaseを作成するという形でタグ付けすることができます。

今回ここでハマったのが、go.modファイルの位置によってバージョン指定がうまくできなかったことです。

リポジトリ構造を工夫している中で最初は以下のような構成にしていました。

├── Makefile
├── README.md
├── cmd
│   └── main-sample
│       ├── go.mod
│       └── main.go
├── configs
└── pkg
    ├── packageA
    │   └── packageA.go
    ├── packageB
    │   └── packageB.go
    └── go.mod

これは私の勝手な趣味ですがリポジトリルートにgo.modを置かないようにしてpkg/をモジュールとして切り出して置きたかったと思っていました。これは他にconfigs/などのモジュールとして参照させるわけではないファイルも入ってるディレクトリだったので明確に分けたかったのです。

ですが、これではバージョン指定がうまくいきませんでした。

バージョン指定しないとインポートできたのですが、タグを切った上でバージョン指定をしてインポートしようとすると404が返ってきてそのバージョンのモジュールが見つからないと言われてしまうのです。

そこで調べたり試したりした結果、以下のようにgo.modをリポジトリルートに置くことが必要ということがわかりました。(加えて言えば、go.modの管理が面倒になるので一つだけルートに置くほうがやっぱりよかったです・・・。)

├── Makefile
├── README.md
├── cmd
│   └── main-sample
│       ├── go.mod
│       └── main.go
├── configs
├── go.mod
└── pkg
    ├── packageA
    │   └── packageA.go
    └── packageB
        └── packageB.go

GOPRIVATEの指定でもモジュールを指定する = go.modがある階層を指定する必要があるということなので、これが今回の最適解だったということです。

ここまでくれば、あとはソースコードをプッシュして必要に応じてリリースバージョンのタグを切っていけばOKです。

バージョン指定の仕方ついては、 公式でも説明されているようにSemantic Versioningの仕様に従う必要があります。ここでは詳しく説明しませんが、v0.0.1のようにタグ付けすればOKです。

具体的にバージョンを切りかえるには、go getでモジュールをインポートするときに以下のようにバージョンを指定すればOKです。

go get github.com/google/s2a-go@v1.0.1

また、共通モジュール側が開発中でその変更を取り入れたいときには、go.modの中でバージョン番号の部分をブランチ名にしてgo mod tidyすると開発中のバージョンを取り込むことができます。

開発中のバージョン切り替えを安全にやりたい(願望)

ここまで説明したことを実践すれば、プライベートリポジトリにあるモジュールをインポートすることも、必要に応じてバージョンを切り替えることもできるようになりました。

しかし、安全にバージョンを切り替える方法についてまだうまい方法が見当たりません…。

上述したようにバージョンを切り替えて開発をしたあと、本当は最新バージョンにしないといけないところを古いバージョンのままにしてしまっていた、なんてことは容易に想像できます。

これについては、結局go.modに書いてあるバージョン番号をレビューできちんと確認するというくらいしかないように思います。

これだと人間の運用に頼ることになるので不安があります…。なにかいい方法があれば教えてほしいくらいです…。

最後に

今回はプライベートリポジトリのGo言語のモジュールをインポートする方法を解説しました。

ただ実際の開発では、コンテナ化して docker compose で動かしたり、デプロイするときにCI/CDでビルドしたりと今回のようにローカルだけで完結しないことがほとんどです。

次回の記事では、今回の記事の内容を前提としてそのあたりを解説していきたいと思います。