AnsibleでHomebrew, Cask, Atomエディターのパッケージを管理する

# はじめに

HomebrewとHomebrew Caskのパッケージは[Brewfile](http://robandlauren.com/2013/11/27/homebrew-brewfile/)を使う方法もありますが、Brewfileにパッケージを追加して2回目以降の実行ではalready installedとかの警告が多数出てあまり気分がよくありません。また、Atomエディターのパッケージはapmコマンドで管理できますが、これらをまとめてAnsibleで管理すればスッキリできるということで、やってみました。

Ansible用のroleを3個とapmのモジュールを作成し、[ansible-galaxy を使ってみた - @znz blog](http://blog.n-z.jp/blog/2014-06-02-ansible-galaxy.html)を参考にAnsible Galaxyに登録しましたので、簡単に利用できます。

# 利用手順

## HomebrewとPythonとAnsibleのインストール

```shell-session
xcode-select --install
```

ここで一度Xcodeを起動してライセンスに同意してください。

```shell-session
ruby -e "$(curl -fsSL https://raw.github.com/Homebrew/homebrew/go/install)"
brew install python
brew install ansible
```

## Homebrew, Cask, Atomパッケージ管理用のAnsible roleをダウンロード

ここでは~/ansible-workを作業ディレクトリとします。

```shell-session
mkdir ~/ansible-works
cd ~/ansible-works
ansible-galaxy install --roles-path=. hnakamur.homebrew-packages
ansible-galaxy install --roles-path=. hnakamur.homebrew-cask-packages
ansible-galaxy install --roles-path=. hnakamur.atom-packages 
```

## インベントリファイルを作成

Homebrewでインストールした場合は/usr/local/etc/ansible/hostsがインベントリファイルになります。

以下のようにファイルを作ります。

```shell-session
mkdir -p /usr/local/etc/ansible
echo localhost > /usr/local/etc/ansible/hosts
```

## localhost.yamlを作成

下記のような感じでlocalhost.yamlを作成します。使いたいパッケージに合わせて適宜調整してください。

```yaml:localhost.yaml
- hosts: localhost
  connection: local
  gather_facts: no           
  sudo: no
  vars:
    homebrew_packages_taps:
      - homebrew/binary
      - homebrew/dupes
    homebrew_packages_packages:
      - { name: readline }
      - { name: openssl, state: linked }
      - { name: ansible }
      - { name: cmake }
      - { name: ctags }
      - { name: direnv }
      - { name: docker }
      - { name: git }
      - { name: jq }
      - { name: hub }
      - { name: imagemagick }
      - { name: mercurial }
      - { name: mysql }
      - { name: packer }
      - { name: pkg-config }
      - { name: pwgen }
      - { name: python }
      - { name: reattach-to-user-namespace }
      - { name: redis }
      - { name: subversion }
      - { name: the_silver_searcher }
      - { name: tig }
      - { name: tmux }
      - { name: whois }
      - { name: xz }
      - { name: vim }
      - { name: wget }
      - { name: zsh, install_options: disable-etcdir }
    homebrew_cask_packages_packages:
      - atom
      - calibre
      - firefox
      - google-chrome
      - google-japanese-ime
      - grandperspective
      - hipchat
      - iterm2
      - java
      - keepassx
      - kindle
      - mysqlworkbench
      - skype
      - spark
      - vagrant
      - virtualbox
      - zeroxed
    atom_packages_packages:
      - project-manager
      - recent-files
  roles:
    - hnakamur.homebrew-packages
    - hnakamur.homebrew-cask-packages
    - hnakamur.atom-packages
```

## ansibleを実行

```shell-session
ansible-playbook localhost.yml
```

# 2回目以降の実行結果

```shell-session
% ansible-playbook localhost.yml

PLAY [localhost] **************************************************************

TASK: [hnakamur.homebrew-packages | homebrew_tap tap= state=present] **********
ok: [localhost] => (item=homebrew/binary)
ok: [localhost] => (item=homebrew/dupes)

TASK: [hnakamur.homebrew-packages | homebrew update_homebrew=yes] *************
ok: [localhost]

TASK: [hnakamur.homebrew-packages | homebrew name={{item.name}} state={{item.state|default("latest")}} install_options={{item.install_options|default()}}] ***
ok: [localhost] => (item={'name': 'readline'})
changed: [localhost] => (item={'state': 'linked', 'name': 'openssl'})
ok: [localhost] => (item={'name': 'ansible'})
ok: [localhost] => (item={'name': 'cmake'})
ok: [localhost] => (item={'name': 'ctags'})
ok: [localhost] => (item={'name': 'direnv'})
ok: [localhost] => (item={'name': 'docker'})
ok: [localhost] => (item={'name': 'git'})
ok: [localhost] => (item={'name': 'jq'})
ok: [localhost] => (item={'name': 'hub'})
ok: [localhost] => (item={'name': 'imagemagick'})
ok: [localhost] => (item={'name': 'mercurial'})
ok: [localhost] => (item={'name': 'mysql'})
ok: [localhost] => (item={'name': 'packer'})
ok: [localhost] => (item={'name': 'pkg-config'})
ok: [localhost] => (item={'name': 'pwgen'})
ok: [localhost] => (item={'name': 'python'})
ok: [localhost] => (item={'name': 'reattach-to-user-namespace'})
ok: [localhost] => (item={'name': 'redis'})
ok: [localhost] => (item={'name': 'subversion'})
ok: [localhost] => (item={'name': 'the_silver_searcher'})
ok: [localhost] => (item={'name': 'tig'})
ok: [localhost] => (item={'name': 'tmux'})
ok: [localhost] => (item={'name': 'whois'})
ok: [localhost] => (item={'name': 'xz'})
ok: [localhost] => (item={'name': 'vim'})
ok: [localhost] => (item={'name': 'wget'})
ok: [localhost] => (item={'install_options': 'disable-etcdir', 'name': 'zsh'})

TASK: [hnakamur.homebrew-cask-packages | homebrew_tap tap=caskroom/cask state=present] ***
ok: [localhost]

TASK: [hnakamur.homebrew-cask-packages | homebrew name=brew-cask state=latest] ***
ok: [localhost]

TASK: [hnakamur.homebrew-cask-packages | homebrew_cask name= state=installed] ***
ok: [localhost] => (item=atom)
ok: [localhost] => (item=calibre)
ok: [localhost] => (item=firefox)
ok: [localhost] => (item=google-chrome)
ok: [localhost] => (item=google-japanese-ime)
ok: [localhost] => (item=grandperspective)
ok: [localhost] => (item=hipchat)
ok: [localhost] => (item=iterm2)
ok: [localhost] => (item=java)
ok: [localhost] => (item=keepassx)
ok: [localhost] => (item=kindle)
ok: [localhost] => (item=mysqlworkbench)
ok: [localhost] => (item=skype)
ok: [localhost] => (item=spark)
ok: [localhost] => (item=vagrant)
ok: [localhost] => (item=virtualbox)
ok: [localhost] => (item=zeroxed)

TASK: [hnakamur.atom-packages | apm name= state=latest] ***********************
ok: [localhost] => (item=project-manager)
ok: [localhost] => (item=recent-files)

PLAY RECAP ********************************************************************
localhost                  : ok=7    changed=1    unreachable=0    failed=0
```

homebrewモジュールでstate=linkと指定した場合は常にchanged=1となってしまうのがイマイチですが、それ以外はchangedにならずokが並んでいい感じです。