Blog

Vagrantのコアの挙動を変更する方法

 2013/05/28
このエントリーをはてなブックマークに追加

Vagrantは素晴らしいプロダクトであることは言うまでもないことですが、時にはVagrant自体の挙動を変更したいと思うこともあります。

たとえば、Vagrantでは作成したインスタンスにChefなどのProvisionerを使ってパッケージをインストールするときに、sudoコマンドを利用してユーザー権限からインストールを実行しますが、そのとき、インスタンス側ではrequiretty (コンソールデバイス必須) の無効化の設定がなされていることが前提になっています。 でも、これ最初からboxをそのように作って入れば良いのですが、そうじゃない場合(virtualbox以外のproviderを使ってクラウドサービス上にインスタンスを立ち上げるとか)は、いちいち自分でその変更を加えたbox(もしくはそれに準ずるもの)を作るのは面倒でかないません。

ということで、今回はVagrantのコアの挙動を、Vagrant本体に手を入れることなく変更する方法を紹介します。

基本的な考え方

Vagrant自体は内部のコマンド自体がプラグインモデルで作られていて、Vagrantは起動時に、Vagrantfileの内容の処理に入る前に、コアのプラグインをロードします。 たとえば export VAGRANT_LOG=info などにした上で、Vagrantfileに exit とだけ記述して起動すると以下のように出力されます。

 INFO global: Vagrant version: 1.2.2
 INFO manager: Registered plugin: ssh communicator
 INFO manager: Registered plugin: kernel
 INFO manager: Registered plugin: suspend command
 INFO manager: Registered plugin: destroy command
 INFO manager: Registered plugin: plugin command
 INFO manager: Registered plugin: up command
 INFO manager: Registered plugin: init command
 INFO manager: Registered plugin: status command
 INFO manager: Registered plugin: reload command
 INFO manager: Registered plugin: ssh-config command
 INFO manager: Registered plugin: provision command
 INFO manager: Registered plugin: package command
 INFO manager: Registered plugin: halt command
 INFO manager: Registered plugin: box command
 INFO manager: Registered plugin: resume command
 INFO manager: Registered plugin: ssh command
 INFO manager: Registered plugin: kernel
 INFO manager: Registered plugin: FreeBSD guest
 INFO manager: Registered plugin: RedHat guest
 INFO manager: Registered plugin: Debian guest
 INFO manager: Registered plugin: Fedora guest
 INFO manager: Registered plugin: Solaris guest.
 INFO manager: Registered plugin: Ubuntu guest
 INFO manager: Registered plugin: OpenBSD guest
 INFO manager: Registered plugin: Gentoo guest
 INFO manager: Registered plugin: Linux guest.
 INFO manager: Registered plugin: SUSE guest
 INFO manager: Registered plugin: Arch guest
 INFO manager: Registered plugin: PLD Linux guest
 INFO manager: Registered plugin: VirtualBox provider
 INFO manager: Registered plugin: puppet
 INFO manager: Registered plugin: CFEngine Provisioner
 INFO manager: Registered plugin: chef
 INFO manager: Registered plugin: ansible
 INFO manager: Registered plugin: shell
 INFO manager: Registered plugin: FreeBSD host
 INFO manager: Registered plugin: Fedora host
 INFO manager: Registered plugin: Gentoo host
 INFO manager: Registered plugin: Linux host
 INFO manager: Registered plugin: OpenSUSE host
 INFO manager: Registered plugin: Arch host
 INFO manager: Registered plugin: Windows host
 INFO manager: Registered plugin: BSD host
 INFO vagrant: `vagrant` invoked: ["up"]
(略)

ここでは、いわゆるユーザープラグイン(saharaとかvagrant-awsみたいなやつ)はロードされず、あくまでコアのみがロードされます。 このコアプラグインの挙動を書き換えるためには、初期化処理終了後に、改変した同名のプラグインを再度ロードしなおせばOKです。

実装

今回はsudoの際にrequirettyを無効にしなくても動作するように挙動を変更します。Vagrantでこの処理を担っているのはVagrantPlugins::CommunicatorSSH::Communicatorです。 まず、Vagrantfileがおいてある場所にファイルをコピーします。

mkdir -p patch/plugins/communicators/ssh
cp /opt/vagrant/embedded/gems/gems/vagrant-1.2.2/plugins/communicators/ssh/*.rb patch/plugins/communicators/ssh/

次にmonkey patchしましょう!対象はコピーしたディレクトリにあるcommunicator.rbです。 263行目付近に以下を追加します。requirettyをスキップするコードが追加されています。

        # Open the channel so we can execute or command
        channel = connection.open_channel do |ch|
          # monkey path for avoid requiretty ここから追加
          channel.request_pty do |ch, success|
            @logger.info("Could not obtain pty") if !success
          end
          #-- end of monkey patch

さてこのモンキーパッチを読み込む処理をVagrantfileに追加しましょう。 ここから追加とかかれているところからが追加対象です。

# -*- mode: ruby -*-
# vi: set ft=ruby :

### ここから追加
plugin_load_proc = lambda do |directory|
  # We only care about directories
  next false if !directory.directory?

  # If there is a plugin file in the top-level directory, then load
  # that up.
  plugin_file = directory.join("plugin.rb")
  if plugin_file.file?
    puts "[INFO]loading monkey patch: #{plugin_file}"
    load(plugin_file)
    next true
  end
end

Vagrant.source_root.join(File.dirname(__FILE__) + "/patch/plugins/communicators/").children(true).each do |directory|
  # Ignore non-directories
  next if !directory.directory?

  # Load from this directory, and exit if we successfully loaded a plugin
  puts directory
  next if plugin_load_proc.call(directory)
end
### 追加ここまで

Vagrant.configure("2") do |config|
  config.vm.box = "<your_box_name_here>"
  config.vm.network :private_network, ip: "192.168.33.33"
  config.omnibus.chef_version = "11.4.4"
end

やっていることは、Vagrantのソース探索ディレクトリにモンキーパッチ用のパスを追加した上で、そのパス上に存在するプラグインを上書きロードしているだけです。 これでvagrant upコマンドで起動すれば、requirettyが無効でも大丈夫になります。ただし実行時に画面上にインスタンスの中で発行されるコマンドや標準出力が画面上に表示されることにはなります。

まとめ

以上のように簡単に挙動を書き換えることができます。挙動を書き換えたいと思う機会はそう多くないかもしれませんが、例えば特定箇所のログの出力を増やしたいとか出力を他のサービスに連携したいとかインスタンス上で発行するコマンドを書き換えたい、といった場合には使えるかもしれません。 ただしご利用は自己責任でお願いします。

 2013/05/28
このエントリーをはてなブックマークに追加