組織名の汎用コマンドを作ってスクリプトを共用する

# インターフェース

`my-company` という自作コマンドを例に挙げていきます。

```bash
$ my-company
usage: my-company <command> [<args>]

commands:
  hosts                     サーバのホスト名一覧を表示
  git-log-remote [<hosts>]  デプロイされていない差分コミットがあれば表示
  env-diff [<hosts>]        環境変数の設定漏れを表示
  check-update [<hosts>]    yum パッケージの更新を表示
  self-upgrade              my-company コマンドを更新
  update [<hosts>]          yum パッケージを更新
```

----

# よくある風景

- Chat bot は環境構築が面倒（言語・ツール等の巨大な依存があったり・・・）

- 運用ツールのコマンドをコピペ中心に運用している

運用で使うコマンドを管理する話です

----

各自インストールして使う汎用コマンドの話をします

----

## 汎用コマンドの構成

各自 `git clone` した後に `bin/` へパスを通しておく

```
|- bin/my-company  # パスを通しておく実行ファイル
|- lib/            # Bash,Perl,PHP,Ruby,Python などのスクリプト
`- README.md
```

----

## 主な依存は SSH

チームで ssh config の Host を合わせる。

- サーバに名前がついてると意思疎通しやすい

- ハイフンで名前空間を作るようにすると機械的に扱いやすい

----

## コマンドを作る

第一引数をサブコマンドとして解釈する。

以降の引数はサブコマンドへ移譲する。

```bash:bin/my-company
set +e
subcmd=${1:-help}
shift
set -e

if type "my-company-${subcmd}" >/dev/null 2>&1 ; then
  my-company-${subcmd} "$@"
else
  echo "'${subcmd}' is not a my-company command."
fi
```

----

準備おわり

----

## サブコマンドを作る

あると便利なサブコマンドを `my-company` へ実装していきます。

```bash
$ my-company
usage: my-company <command> [<args>]

commands:
  hosts                     サーバのホスト名一覧を表示
  git-log-remote [<hosts>]  デプロイされていない差分コミットがあれば表示
  env-diff [<hosts>]        環境変数の設定漏れを表示
  check-update [<hosts>]    yum パッケージの更新を表示
  self-upgrade              my-company コマンドを更新
  update [<hosts>]          yum パッケージを更新
```

*記事内では引数処理やループ処理などのボイラープレートは省いてあります。*


----

## hosts - サーバのホスト名一覧を表示する

`$HOME/.ssh/config` から Host を抽出する。 `peco` などと組み合わせると便利。

```
$ my-company hosts
my-company-vpn
my-company-service-prod-bastion
my-company-service-prod-web-1
my-company-service-prod-admin
my-company-service-prod-api
```

### 実装

ホスト名をチーム内で合わせると活きる

```bash
my-company-hosts() {
  cat ${HOME}/.ssh/config | grep 'Host my-company-' | grep -v '*' | awk '{print $2}'
}
```

----

## check-update - yum パッケージの更新を表示

```
$ my-company check-update
# my-company-service-stg-web
        Loaded plugins: priorities, update-motd, upgrade-helper
        Security: kernel-4.4.41-36.55.amzn1.x86_64 is an installed security update
        Security: kernel-4.4.19-29.55.amzn1.x86_64 is the currently running version

```

### 実装

対象のホストへ向けてリモート実行をする。

```
ssh -A $host yum check-update
```

----

## update - yum パッケージを更新する

```bash
$ my-company update
# my-company-service-stg-web
Loaded plugins: priorities, update-motd, upgrade-helper
No packages marked for update
```

### 実装

対象のホストへ向けてリモート実行をする。

```sh:bin/my-company
function my-company-update {
    ssh -A $host sudo yum -y update
}
```

----

## git-log-remote - デプロイされていないコミットがあれば表示する

本番・ステージングのリリースついでに全環境のデプロイ漏れを確認する癖がつく

```
$ my-company git-log-remote                                                                                         
# my-company-service-prod-web

# my-company-service-stg-web
        > 0123abcde キャッシュさせない
```

----

### 実装

対象のホストで `git log` を実行して master ブランチとの差分を出力します。

```bash:my-company
function my-company-git-log-remote() {
  ssh -A "$host" 2>/dev/null <<'EOT'
    cd /var/www/html && \
    git fetch && \
    git log --oneline --left-right HEAD..origin/master | perl -pe 's/^/\t/g'
EOT
}
```

----

## phan-filter - phan のログからノイズを除去します

Lint の出力に含まれるノイズをフィルタリングする。

```
$ phan -ophan-orig.log; cat phan-orig.log \
    | my-company phan-filter > phan-filtered.log
```

Composer scripts での利用例

```json:composer.json
{
    "scripts": {
        "lint": "phan --progress-bar -oapp/tmp/phan-original.log ; cat app/tmp/phan-original.log | my-company phan-filter > app/tmp/phan-filtered.log"
    }
}
```

----

## 実装

参考: [Phan静的解析がもたらす大PHP型検査時代 - pixiv inside](http://inside.pixiv.net/entry/2016/11/11/202656)

```bin/my-company
my-company-phan-filter() {
  cd "$(dirname $0)/../"
  php lib/phan-filter.php < /dev/stdin
}
```

```lib/phan-filter.php
<?php

$const = [
    'undeclared type \ComponentCollection',
    'undeclared class \MissingViewException',
    'undeclared type \Model',
    'undeclared type \View',
    'PhanRedefineClass Class \AppHelper defined',
];

while ($line = fgets(STDIN)) {
    foreach ($const as $c) {
        if (strpos($line, $c) !== false) {
            goto next;
        }
    }

    echo $line;
next:
}
```

----

## self-upgrade - my-company コマンドを更新

```
$ my-company self-upgrade
Already up-to-date.
```

### 実装

```sh:bin/my-company
my-company-self-upgrade() {
  cd "$(dirname $0)/../"
  git pull
}
```

----


## env-diff - 設定漏れの環境変数を出力する

`Git 管理下にあるテンプレート` と `Git 管理下にない設定ファイル` の差分を検出します

```
$ my-company env-diff my-company-service-stg-web
# my-company-service-prod-web

# my-company-service-stg-web
array(3) {
  [0]=>
  string(13) "some_var_name"
}
```

----

### 実装

対象ホストで `lib/env-diff.php` をリモート実行します。

```bash:bin/my-company
function my-company-env-diff() {
  php_cmd=$(cat lib/check_env.php | sed -e 's/<?php//' | sed -e 's/\$/\\$/g')
  echo "# $host"
  echo "test \"\$(whereis php)\" != \"php:\" && php -r \"$php_cmd\"" | ssh $host || true
}
```

----

# 便利

```bash
$ my-company
usage: my-company <command> [<args>]

commands:
  hosts                     サーバのホスト名一覧を表示
  git-log-remote [<hosts>]  デプロイされていない差分コミットがあれば表示
  env-diff [<hosts>]        環境変数の設定漏れを表示
  check-update [<hosts>]    yum パッケージの更新を表示
  self-upgrade              my-company コマンドを更新
  update [<hosts>]          yum パッケージを更新
```

----

## 汎用コマンドの所感

- 実行環境が統一されていると汎用化しやすい
- Chat bot を整備しきれていない環境では汎用コマンドの方が楽なこともある
    - 例: Chat bot 化する前段階として導入する
- サーバの管理台数が少なければ `ssh -A` のリモート実行でだいたい何とかなる
- 踏み台を用意しておくと `ssh -A` により `サーバ用の鍵ファイル` を用意する手間が省ける
- ちょっとしたドキュメント代わりに使えるかも
    - 例: スニペッツを吐き出すサブコマンドを用意する