アプリのコンテキストでコマンド実行するappdoコマンドを作ったよ

ホストにログインしてから Ruby on Rails のアプリケーションで `rake` を実行するまでの前準備が長すぎるので、 `appdo` という一つのコマンドにまとめてしまおう、という話をします。

# 解決したい問題

Ruby を rbenv や rvm で入れていると、 `sudo` や `cron` 等でのコマンド実行時に `ruby` が見つからなくて困ります。まあ `~/.bashrc` などを都度読めばいいんですが、そうするとコマンドが複雑になってしまいます。そういう処理をしてる箇所が増えると、修正漏れも起きやすくなります。こまる。

Ruby on Rails のアプリケーションを運用しているときにはさらに `cd $RAILS_ROOT` しないと `bundle exec rake` も `bin/rails runner` も実行できません。結果、運用コマンドはどんどん長くなり、 `crontab` や `config/deploy.rb` はメンテナンスしづらくなってしまいます。こまる。

**どうせ同じホストではいっつもおんなじ準備をするんだから、そんな設定はホストに持つべき**でしょーってことで、そういうコマンドを作りました。

# [`appdo`](https://github.com/sugitak/appdo) で解決だ

アプリのコンテキストでコマンド実行するための `appdo` コマンドというものを作りました。[`pip install`](https://pypi.python.org/pypi/appdo) できます。

```
pip install appdo
```

`appdo` を使うと、これまで以下のように実行していた `rails c` が…、

```bash
$ su - web                       # まずログインして…
$ cd my-awesome-app              # あった、このディレクトリだ
$ cd current                     # あ、capistrano 管理下だった
$ export RAILS_ENV=production    # 環境変数設定してーの
$ export RACK_ENV=production     # （なんかいろいろ）設定してーの
$ bundle exec rails c            # ほい
```

これだけで済むようになります。

```bash
$ sudo -iu web appdo rails c     # webユーザのアプリ内で rails c
```

設定ファイルは以下のように、 `$HOME/.appdo.conf` に書いておきます。書式は [TOML](http://qiita.com/futoase/items/fd697a708fcbcee104de)。

```toml
# /home/web/.appdo.conf
#
##    sudo した場合、 sudo 先のユーザの .appdo.conf が適用されます

[default]
cd = "~/my-awesome-app/current"
source = ["/etc/profile", "~/.bashrc"]
prefix = "bundle exec"

[default.env]
RAILS_ENV = production
RACK_ENV = production
```

見たまんま、 `cd` して `source` して環境変数設定したうえでコマンドを `bundle exec` してくれるわけです。

必ず毎回やることなら、人間がやるべき仕事じゃないですよね。設定ファイルに入れ、そのファイルをプロビジョニング管理する方が人間が幸せになります。

# その他の変化

## `crontab` の変化

#### appdo がないとき

```
RAILS_ROOT=/home/web/my-awesome-app/current
BASHRC=/home/web/.bashrc
RAILS_ENV=production

30 3 * * * bash -c "cd $RAILS_ROOT; source $BASHRC; bundle exec rails runner MyAwesomeApp::MyCron.run()"
30 4 * * * bash -c "cd $RAILS_ROOT; source $BASHRC; bundle exec rails runner MyAwesomeApp::MyCron2.run()"
30 5 * * * bash -c "cd $RAILS_ROOT; source $BASHRC; bundle exec rails runner MyAwesomeApp::MyCron3.run()"
```

#### appdo があるとき

```
30 3 * * * appdo rails runner MyAwesomeApp::MyCron.run()
30 4 * * * appdo rails runner MyAwesomeApp::MyCron2.run()
30 5 * * * appdo rails runner MyAwesomeApp::MyCron3.run()
```

環境変数を設定する必要がない（appdoが吸収してくれる）ので、crontab　が超シンプルになります。
何のコマンドを打ってるのか、appdo なら見えるよ。わたしにもみえるよ

## ssh でのコマンド変化

#### appdo がないとき

```
ssh my.server.example.com sudo -u web bash -c "cd /home/web/my-awesome-app/current; source ~/.bashrc; RAILS_ENV=production bundle exec rake assets:precompile"
```

#### appdo があるとき

```
ssh my.server.example.com sudo -iu web appdo rake assets:precompile
```

こちらも `crontab` と同様、単純になります。
ssh を単純にするのは、 `config/deploy.rb` が簡易化できるっていう利点が大きいです。アプリのコードにホスト固有の情報（パスとか環境変数とか）を入れる必要がなくなるので、分離がよくなる。

## 手順書の簡易化

想像してごらん、すべての `cd/source` がドキュメント外に移動する
うっうー

## ホスト移行が簡単になる

例えばアプリのパスが異なっていたり、ruby が `rvm/rbenv` 混在環境であっても、それぞれのホストにある設定ファイルでこの違いを吸収できます。結果、デプロイスクリプトに変更の必要がないので、順次移行がかなり楽になるかもしれません。

# その他の活用例

## サーバの設定ファイル管理

```toml
# /root/.appdo.conf

[apache]
cd = "/etc/apache2"

[bind]
cd = "/var/bind/etc/bind"
```

`cd` 先だけ指定しておいて、設定ファイルの管理コマンドだけ共有しておくのも便利かも。

```
# appdo --app=apache -- ls sites-available
# appdo --app=apache -- ln -s sites-available/99-default sites-enabled
# appdo --app=bind -- co -l master/my.example.com
# appdo --app=bind -- vim master/my.example.com
# appdo --app=bind -- ci -u master/my.example.com
```

# まとめ

  - appdo を使って「アプリの文脈」でコマンド実行しよう
  - appdo を使って「アプリの文脈」を設定ファイルに落とし込んでしまおう
  - appdo を使ってアプリとインフラの切れ目を整理しよう

# おわりに呟き

まだ個人で作ったばかりで bullet proof でないスクリプトですが、まずはリリースして仕様叩きをしてみることにしました。バグ報告や要望など、お待ちしています。

もともとは、「`rvm/rbenv` を使った際に `sudo` から `ruby` を使えない」ことがストレスで、この `appdo` コマンドを考えました。
昨今インフラに求められているものはまた変化しつつありますが、 docker にせよ serf/consul にせよ autoscaling にせよ、ホストが自律的に動作するように設計したほうがうまく回るように思います。そんなところでも `appdo` を使うことにより「ホスト層」のようなものを作っていろいろ吸収できるのであれば、なんか頭使うところが減って幸せになれるんじゃないかなあとかぼんやりと思います。

実装が Python なのはいわゆるファーストプロトタイピング的なやつです。Python でパッケージ作ったことなかったので、実装に際しては @mkouhei さんの [bootstrap-py](https://github.com/mkouhei/bootstrap-py) を使用させていただきました。オプション等の要件が固まってきたら Go に移行するのがいいのかなーと考えています。

え？Rustのほうがいいって？…