既存環境のMac でもansible で環境構築を自動化するには

先日、Macの新しい環境を構築する際に以下のエントリを参考にしてansible で環境構築することにした。

t-wada.hatenablog.jp

参考、というか正確にはそのまま真似しただけだ。

# Run below command in ~/.macbook-provisioning
# HOMEBREW_CASK_OPTS="--appdir=/Applications" ansible-playbook -i hosts -vv localhost.yml
#
# Refferring to http://t-wada.hatenablog.jp/entry/mac-provisioning-by-ansible
- hosts: localhost
  connection: local
  gather_facts: no
  sudo: no
  vars:
    homebrew_taps:
      - homebrew/binary
      - homebrew/dupes
      - caskroom/cask
      - railwaycat/emacsmacport
      - sanemat/font
      - rcmdnk/file
    homebrew_packages:
      - { name: readline }
      - { name: openssl }
      - { name: openssl, state: linked, install_options: force }
      - { name: python }
      - { name: ansible }
      - { name: coreutils }
      - { name: git }
      - { name: zsh, install_options: disable-etcdir }
      - { name: wget }
      - { name: curl }
      - { name: cmake }
      - { name: autoconf }
      - { name: automake }
      - { name: pkg-config }
      - { name: ctags }
      - { name: tree }
      - { name: lv }
      - { name: nkf }
      - { name: jq }
      - { name: go }
      - { name: direnv }
      - { name: peco }
      - { name: hub }
      - { name: tig }
      - { name: fish }
      - { name: rbenv }
      - { name: ruby-build }
      - { name: lha }
      - { name: flow }
      - { name: mysql }
      - { name: sqlite }
      - { name: redis }
      - { name: imagemagick }
      - { name: mercurial }
      - { name: packer }
      - { name: xz }
      - { name: socat }
      - { name: rlwrap }
      - { name: w3m }
      - { name: tmux }
      - { name: reattach-to-user-namespace }
      - { name: phantomjs }
      - { name: graphviz }
      - { name: autojump }
      - { name: gibo }
      - { name: source-highlight }
      - { name: nodebrew }
      - { name: elixir }
      - { name: erlang }
      - { name: gauche }
      - { name: vimpager }
      - { name: docker }
      - { name: boot2docker }
      - { name: brew-file }
    homebrew_cask_packages:
      - { name: emacs-mac }
      - { name: iterm2 }
      - { name: firefox }
      - { name: google-chrome }
      - { name: google-japanese-ime }
      - { name: adobe-reader }
      - { name: java }
      - { name: skype }
      - { name: slack }
      - { name: sourcetree }
      - { name: gitx }
      - { name: karabiner }
      - { name: seil }
      - { name: flux }
      - { name: dash }
      - { name: skitch }
      - { name: seashore }
      - { name: atom }
      - { name: kobito }
      - { name: webstorm }
      - { name: phpstorm }
      - { name: intellij-idea }
      - { name: vagrant }
      - { name: virtualbox }
      - { name: dropbox }
      - { name: sublime-text }
      - { name: coda }
      - { name: mysqlworkbench }
      - { name: kindle }
      - { name: mplayerx }
      - { name: sequel-pro }
      - { name: processing }
  tasks:
    - name: homebrew の tap リポジトリを追加
      homebrew_tap: tap={{ item }} state=present
      with_items: homebrew_taps

    - name: homebrew をアップデート
      homebrew: update_homebrew=yes
    # brew
    - name: brew パッケージをインストール
      homebrew: >
        name={{ item.name }}
        state={{ item.state | default('latest') }}
        install_options={{
          item.install_options | default() | join(',')
          if item.install_options is not string
          else item.install_options
        }}
      with_items: homebrew_packages
      register: brew_result
    - name: brew パッケージの情報保存先ディレクトリを作成
      file: path=brew_info state=directory
    - name: brew パッケージの情報を保存
      shell: brew info {{ item }} > brew_info/{{ item }}
      with_items: brew_result.results | selectattr('changed') | map(attribute='item') | map(attribute='name') | list

    # cask
    - name: homebrew-cask のインストール
      homebrew: name=brew-cask state=latest
    - name: cask パッケージをインストール
      homebrew_cask: name={{ item.name }} state={{ item.state|default('installed') }}
      with_items: homebrew_cask_packages
      register: cask_result
    - name: cask パッケージの情報保存先ディレクトリを作成
      file: path=cask_info state=directory
    - name: cask パッケージの情報を保存
      shell: brew cask info {{ item }} > cask_info/{{ item }}
      with_items: cask_result.results | selectattr('changed') | map(attribute='item') | map(attribute='name') | list

    # oh-my-zsh
    - name: oh-my-zsh のインストール
      shell: curl -L https://raw.github.com/robbyrussell/oh-my-zsh/master/tools/install.sh | sh
      args:
        creates: ~/.oh-my-zsh/

    # Ricty
    - name: xquartz のインストール (for Ricty)
      homebrew_cask: name=xquartz
    - name: fontforge のインストール (for Ricty)
      homebrew: name=fontforge
    - name: Ricty のインストール
      homebrew: name=ricty
    - name: 生成されたフォントファイルをコピー
      shell: cp -f $(brew --cellar ricty)/*/share/fonts/Ricty*.ttf ~/Library/Fonts/
      args:
        creates: ~/Library/Fonts/Ricty-Bold.ttf
      notify: run fc-cache

  handlers:
    - name: run fc-cache
      shell: fc-cache -vf

そして、これが思ってた以上に快適だったので布教したいと思う。

やる作業内容としては、リンク先のコマンドを実行して、playbook と呼ばれる .yml ファイルにbrew もしくはbrew cask でインストールしたいパッケージの内容をつらつらと書いていくだけなのだ(リンク先ではlocalhost.ymlと呼ばれている)。

で、新規環境だったら簡単なのだが既存の環境にどうやったら楽にansible で環境構築できるか調べてみた。

大雑把な理解なのだがパッケージ(アプリ)がCUIGUIかでlocalhost.ymlに書く場所が違う。

  • homebrew_packages:CUI パッケージ
  • homebrew_cask_packages:GUIパッケージ

ので、それぞれすでにインストールしているパッケージを.yml 形式でリスト化すれば既存環境でも簡単に移行できるのかなと思った。

既存のパッケージを.yml 形式で出力する

terminal からbrew install したパッケージもansible での管理対象としたいので、今までbrew installしたパッケージを.yml 形式でリスト化します。

brew でインストールしたもの

brew list | xargs -n 1 | sed -E 's/(.*)/  - { name: \1 }/g'

を実行すると以下のフォーマットで標準出力に書きだされる。

- { name: ansible }
- { name: autoconf }
- { name: autojump }
- { name: automake }
- { name: bdw-gc }
- { name: boost }
- { name: boot2docker }
- { name: brew-cask }
- { name: brew-file }

この内容をhomebrew_packages:の次の行に貼り付ける。 タブを入れるのを忘れないこと。

brew cask でインストールしたもの

cat Caskfile | sed -E 's/(.*)/ - { name: \1 }/g'

casklist の場合、つまりGUIなツール群はということだがこれはパパっと整形することができなかった。 まず、brew cask installしたものを.yml形式にリスト化するコマンドを叩く。

brew cask list | xargs -n 1 | sed -E 's/(.*)/  - { name: \1 }/g'

以下のフォーマットで標準出力に書きだされる。

- { name: emacs-mac }
- { name: iterm2 }
- { name: firefox }
- { name: google-chrome }
- { name: google-japanese-ime }
- { name: adobe-reader }

これは、homebrew_cask_packages:の次の行に貼り付ける。 タブを入れるのを忘れないこと。

ドラッグ&ドロップしてインストールしたもの

次にbrew cask install以外、つまりアイコンをApplication内にドラッグ&ドロップしてインストールしたものだ。 これは、どのように.yml 形式で出力するか、現在調査中。。

以下のコマンドを実行する。

brew file casklist

すると、カレントディレクトリにCaskfileというファイルが作成される。 このファイルにbrew cask installしたもの、そうでないものという情報があるのでそれをゴニョゴニョしてどうにかできないか検討中。

ここはスパっと諦めてもいいかなと思う。

終わりに

別のアプリケーションでパッケージ管理を行っていると移行するというのはなかなか骨の折れる作業だと思う。

ただ、brew install で都度必要な物をインストールしているって人には導入も管理も簡単なのでおすすめしたい。

入門Ansible