privateなgemサーバの構築とgem作成から利用まで

privateなgemサーバをローカルでDockerで起動し、オリジナルのgem作成からそれを利用するまでの流れを試しましたので、手順を残しておきます。この記事の一番下に載せたリンクを参考にしました。

3段階の手順です。手順ごとにディレクトリを作ります。

- Dockerイメージ作成とgemサーバ起動 (Gem in a Box)
- gemを作成してgemサーバに登録 (`gem inabox`)
- そのgemを利用

Docker、Ruby、gem、bundleなどはすでに入っているものとします。

# Dockerイメージ作成とgemサーバ起動

Gem in a Boxというプライベートなgemサーバを作るツールを使います。

```
$ mkdir docker
$ cd docker
```

このディレクトリに以下の3ファイルを作成します。

Dockerイメージ作成のためのファイル

```dockerfile:Dockerfile
FROM ruby:2.6.4

RUN mkdir /geminabox-data
COPY . /geminabox-app

WORKDIR /geminabox-app
RUN bundle install
```

Dockerイメージの中にGem in a Boxをインストールするためのファイル

```gemfile:Gemfile
source "https://rubygems.org"

gem "geminabox"
```

Gem in a Boxの設定ファイル

```ruby:config.ru
require "rubygems"
require "geminabox"

Geminabox.data = "/geminabox-data"

use Rack::Session::Pool, expire_after: 1000 # sec
use Rack::Protection

run Geminabox::Server
```

この3ファイルのあるディレクトリでDockerイメージを作成します。

```
$ ls
config.ru  Dockerfile  Gemfile
$ docker build -t geminabox .
```

以下のコマンドでDockerイメージを起動すると、Gem in a Boxが動きます。ターミナルにはそのログが吐き出されます。

```
$ docker run -it --rm -p 9292:9292  geminabox /usr/local/bundle/bin/rackup --port 9292 --host 0.0.0.0
```

Gem in a Boxで動く流れをつかむための一時的な環境です。このコマンドでDockerを起動してgemを登録しても、Dockerを止めたらgemは消えてしまいます。

このGem in a Boxを動かしたまま、次以降の手順に入りますので別のターミナルからもとのディレクトリ（dockerディレクトリの上の階層）に入ります。

# gemを作成してgemサーバに登録

## gemを作成

以下のコマンドでgem作成のテンプレートを作ることができます。パラメータはgemの名前です。単純に`helloworld`だとすでに誰かがRubyGemsで公開しているgemと名前が被ってしまって試行錯誤するには混乱しやすいので、被らなそうなものにして試します。

```
$ bundle gem helloworld-suzuki
$ cd helloworld-suzuki
```

`helloworld-suzuki.gemspec`というファイルが生成されているのでそれを編集します。`TODO`という記述が含まれているといけないらしいので、その個所を以下のように書き換えます。

```helloworld-suzuki.gemspec
  spec.summary       = %q{Hello, world!}
  spec.description   = %q{Hello, world!}
  spec.homepage      = "http://www.example.com/"
```

`spec.metadata`の設定にも`TODO`が含まれますが、それらの記述4行をまるごと消してしまいました。

パッケージのソースの本体となるファイルにサンプルのメソッドを書きます。以下のようにしました。

```lib/helloworld/suzuki.rb
require "helloworld/suzuki/version"

module Helloworld
  module Suzuki
    class Error < StandardError; end
    def self.say
      print "Hello, World!\n"
    end
  end
end
```

以下のコマンドを実行するとパッケージが生成されます。

```
$ rake build
```

`pkg/helloworld-suzuki-0.1.0.gem`にファイルが生成されます。

## gemサーバに登録

できあがったパッケージファイルをさきほどのDockerで動いているGem in a Boxに登録したいと思います。ブラウザで`http://localhost:9292/`にアクセスしてもいいらしいのですが、ここではコマンドでやってみます。

`gem inabox`というコマンドでパッケージファイルをGem in a Boxに登録できるのですが、このコマンドは普通のgemをインストールしただけの環境にはなくて、`geminabox`をインストールしないといけないようです。なので、ローカルにインストールします。

```
$ gem install geminabox
```

繰り返しですが、あくまで`gem inabox`コマンドを使えるようにするためのインストールで、Gem in a Box自体はさきほどのDockerの中で`Gemfile`をもとにインストールした環境ですでに動作中です。

次に`gem inabox`コマンドでパッケージファイルをGem in a Boxに登録します。

```
$ gem inabox --host http://localhost:9292/  pkg/helloworld-suzuki-0.1.0.gem
```

これを実行すると、Dockerを実行しているターミナルにアクセスログが出力されます。

次の手順に入るためにいったん上の階層に戻ります。

```
$ cd ..
```

# そのgemを利用

別のディレクトリを作成します。

```
$ mkdir sample
$ cd sample
```

先ほど作成したgemを利用するための`Gemfile`を作成します。

```gemfile:Gemfile
source "https://rubygems.org"

gem "helloworld-suzuki"
```

`helloworld-suzuki`という名前のgemは一般のRubyGemsにはないはずですので、このままですとエラーになるはずです。

```
$ bundle install
Fetching gem metadata from https://rubygems.org/.
Fetching gem metadata from https://rubygems.org/.
Could not find gem 'helloworld-suzuki' in any of the gem sources listed in your Gemfile.
```

はい、エラーになりました。

```
$ gem sources -a http://localhost:9292/
```

これを実行するとgemの取得元サーバが追加されます。以下のコマンドで設定状況を確認できます。

```
$ gem sources
*** CURRENT SOURCES ***

https://rubygems.org/
http://localhost:9292/
```

`~/.gemrc`というファイルにこの設定が書かれています。

このファイルにではなく、今回のsampleプロジェクトのみに適用したい場合は`Gemfile`に以下のように書きます。

```gemfile:Gemfile
source "https://rubygems.org"

source "http://localhost:9292" do
  gem "helloworld-suzuki"
end
```

またはこのように書いてもよいです。

```gemfile:Gemfile
source "https://rubygems.org"

gem "helloworld-suzuki", :source => "http://localhost:9292"
```

`helloworld-suzuki`というgemだけ`http://localhost:9292`からダウンロードすることを指定する設定です。複数のgemを`http://localhost:9292`からダウンロードする場合には1つ目のブロックでの書き方が便利です。

これをもとに再度`bundle install`を実行します。

```
$ bundle install
Fetching gem metadata from http://localhost:9292/..
Fetching gem metadata from https://rubygems.org/.
Resolving dependencies...
Using bundler 2.0.2
Fetching helloworld-suzuki 0.1.0
Installing helloworld-suzuki 0.1.0
Bundle complete! 1 Gemfile dependency, 2 gems now installed.
Use `bundle info [gemname]` to see where a bundled gem is installed.
```

ちなみに以下のように書いてもとりあえずは動きますが、`Gemfile`に書いたすべてのgemをいちいち2つのサーバに見に行ってしまいます。

```gemfile:Gemfile
source "https://rubygems.org"
source "http://localhost:9292"

gem "helloworld-suzuki"
```

この環境でRubyを動かしてみます。

```
$ bundle exec ruby -rhelloworld/suzuki -e 'Helloworld::Suzuki.say'
Hello, World!
```

無事動きました。

# 参考

DockerでGem in a Boxを起動するのは次の記事を参考にしました。
[Gem in a BoxでプライベートなRubyGemsをDockerで簡単に構築する - YOMON8.NET](https://yomon.hatenablog.com/entry/2019/10/rubygemsdocker)

今回の私の記事ではDockerでその場で動かしましたがちゃんとdaemon化するには次の記事が参考になります。
[privateなgemサーバー構築のお話 - Qiita](https://qiita.com/park-jh/items/6246b1153f491b60d6cb)