はじめに
本文章は、Vagrant公式サイト内のドキュメント内のPLUGINSに記載されている内容を意訳に近い形で日本語化したものである。
誤訳が含まれている可能性が十分にあることを踏まえて、参考いただきたい。
プラグイン
Vagrantには、その環境を起動して運用させるにあたり、多くの素晴らしいBox外の機能がある。しかしながら時折、Vagrantが何かを行うための方法を変更したり、Vagrantを拡張する機能を追加したいと思うだろう。これはVagrantプラグインによって実現される。
プラグインは強力で、文書で十分裏付けられ、メジャー・バージョン・アップグレードに耐えることのできる安定したAPIを使用するので、Vagrantを拡張する第一級市民である。
事実、Vagrantのコアな部分の多くは プラグインを使用して実装されている。Vagrantは自分自身のプラグインAPIをドッグフーディングしているので、インタフェースが安定しており十分にサポートされていることを確認できる。
プラグインの使い方やビルドの仕方について学びたいのであれば、左側にあるナビゲーションを使って"プラグイン”セクションを参照のこと。
プラグインの使い方
Vagrantのプラグインのインストール方法は簡単で数秒もかからない。
使い方に関する詳細な情報は、使用する予定のプラグインのドキュメントを参照すること。しかし、プラグインのインストールや有効化に関する共通的な一つの方法がある。
**警告!**サード・パーティのプラグインは、非中心的なユーザーによって書かれており、その性質のためVagrantに不安定さを導入してしまうことがある。
インストール
プラグインはvagrant plugin install
を使用してインストールされる。
# Installing a plugin from a known gem source
$ vagrant plugin install my-plugin
# Installing a plugin from a local file source
$ vagrant plugin install /path/to/my-plugin.gem
一度プラグインがインストールされると、それは自動的にVagrantによってロードされる。ロードすることができなかったプラグインがVagrantをクラッシュさせることはない。その代わり、Vagrantはプラグインのロードに失敗したエラーメッセージを表示する。
使い方
一度プラグインをインストールしたら、その使い方を正確に理解するためにプラグインのドキュメントを参照するべきである。コマンドを追加するプラグインはvagrant
によってすぐに使用可能となる。プロビジョナはconfig.vm.provision
によって使用することができる。
**注意:**将来的に、vagrant plugin
コマンドはそれぞれのプラグインのインストールのコンポーネントのドキュメントのためのサブ・コマンドを含むようになるだろう。
アップデート
プラグインはvagrant plugin update
を実行することでアップデートできる。これはインストールされているすべてのプラグインを最新のバージョンにアップデートする。vagrant plugin update NAME
を呼び出すことで特定のプラグインをアップデートすることができる。Vagrantはどのプラグインがどのバージョンにアップデートされたかを出力する。
プラグインの特定のバージョンへの変更を決める際には、(たいていGitHubページもしくはそれに似た)プラグインのホームページを参照すること。彼または彼女が選んだプラグインに対してチェンジログを提供するのは、プラグイン作者の責任である。
アンインストール
アンインストールもインストールと同様に簡単である。vagrant plugin uninstall
コマンドを使用するだけでプラグインは削除される。例を示す。
$ vagrant plugin uninstall my-plugin
プラグイン一覧
Vagrant環境に何のプラグインがインストールされているかを見るにはvagrant plugin list
コマンドを使用する。これは、バージョンとともにインストールされているプラグインを一覧表示する。
プラグイン開発の基礎
プラグインは、Vagrantの振る舞いや機能性を拡張または変更するための、素晴らしい方法である。プラグインはユーザーにとって、更なる外部への依存性を導入することになるので、Vagrantで何かをしようとする際の最後の手段として使うべきであろう。
しかし、機能のアップグレードや安定したAPIの使用に対しては安全であるため、Vagrantにカスタマイズした動作を導入する必要があれば、プラグインは最も適切な方法である。
**警告:上級者向け!**プラグインの開発は、Rubyに対して適度に不安がない、経験豊かなVagrantユーザーだけが近づくべき、上級者向けの記載である。
プラグインはRubyを使って書かれ、RubyGemsを使用してパッケージ化される。Rubyに精通していることが必須であるが、パッケージ化とディストリビューションのセクションは、プラグインをRubyGemにパッケージ化するためのガイドの助けとなるだろう。
セットアップとワークフロー
プラグインはRubyGemsとしてパッケージ化されているので、Vagrantプラグインは、通常のRubyGemsと同じように開発されるべきである。これを行うための最も簡単な方法はbundle gem
コマンドを使用することである。
一度、RubyGem用のディレクトリ構造をセットアップすれば、次はGemfileを編集したいと思うだろう。ここにVagrantのプラグイン開発用のGemfileの基本的な構造を示す。
source "https://rubygems.org"
group :development do
gem "vagrant", git: "https://github.com/mitchellh/vagrant.git"
end
group :plugins do
gem "my-vagrant-plugin", path: "."
end
このGemfileは開発用の"vagrant"を取得する。これはbundle exec vagrant
によってすでにロードされたプラグインとともにVagrantと実行することができる。そして、その方法で手動でテストすることができる。
奇妙に見えるこのGemfileの唯一の点は、"plugins"グループとそのグループにpluginを入れているところだろう。vagrant plugin
コマンドはdevelopment内では動作しないので、これはVagrantにプラグインを"インストール"する方法である。Vagrantは"plugins"グループにあるすべてのgemsを自動的にロードする。もし、プラグインがほかのプラグインとともに動作するのであれば、これは開発用のVagrantに複数のプラグインを追加することもできる。
適切なこの構造において、ワークフローはそのほかのRubyのようになるだろう。プラグインの手動テストを行いたい場合、(Gemfileの中で指定された)ロードしたプラグインとともにVagrantを動かす為に、bundle exec vagrant
を使用する。
プラグインの定義
すべてのプラグインは定義される必要がある。定義はその名前や、どのようなコンポーネントが含まれているかなどのプラグインの詳細を含む。
最低限の定義は以下のようになる。
class MyPlugin < Vagrant.plugin("2")
name "My Plugin"
end
定義はVagrant.plugin("2")
から継承したクラスである。"2"はプラグインが有効となるバージョンである。APIの安定性はVagrantのそれぞれのメジャー・バージョンに対してのみ保証され、これは重要である。(1.x系は、APIバージョンが"2"であるので2.0に対して動作する)
プラグイン定義で最も重要な機能は、たとえどのようなバージョンのVagrantが稼働していても、常にロードされることである。理論的には、Vagrantのバージョン87(これは実際には存在しない)は、バージョン2のプラグイン定義をロードすることができる。これは、プラグインの個別のコンポーネントの賢い遅延読み込みによって達成され、そして瞬時に行われる。
プラグインのコンポーネント
定義の中で、プラグインは何のコンポーネントがVagrantに追加されるかを示す。以下に示す例では、コマンドとプロビジョナが追加されることを示す。
class MyPlugin < Vagrant.plugin("2")
name "My Plugin"
command "run-my-plugin" do
require_relative "command"
Command
end
provisioner "my-provisioner" do
require_relative "provisioner"
Provisioner
end
end
ここで何が行われているかという大きな点を見てみよう。一般的なRuby言語の考え方から、上記には親近感があるだろう。文法に恐れることはないだろう。もしそうであれば、プラグインを書こうとする前に、Rubyに対してもっと詳しくなっておくこと。
初めに注意すべきことは、それぞれのコンポーネントは、command
やprovisioner
のようなコンポーネントの名前とともにメソッド・コールされることで、定義される点である。これらは、いくつかのパラメータを持っている。上記の例の場合、コマンドの名前とプロビジョナの名前だけである。すべてのコンポーネントの定義は、実際のコンポーネント実装クラスを返すための、ブロック引数(コールバック)をとる。
ブロック引数は、(上記の例では)"clever lazy loading"が効果を示し始める部分である。コンポーネント・ブロックは、コンポーネントの実装を含む実際のファイルを遅延読み込みし、その後コンポーネントに戻ってくる。
これは、定義されたコンポーネントがメジャーなVagrantバージョンに対して安定的でない時に使用された場合に、実際に依存しているものやAPIが使用されるために行われる。Vagrant 2.0用のコマンドの実装はVagrant 3.0以降と互換性がない。しかし、定義は、将来のVagrantバージョンにとって、常に前方互換性のある、ただの素のRubyである。
繰り返しとなるが、Vagrantのプラグインの作業の方法としてプラグイン・コンポーネントの遅延読み込みは重要である。すべてのコンポーネントは遅延読み込みと定義ブロック内から戻ることが必要である。
さて、それぞれのコンポーネントは異なるAPIを持っている。コンポーネントのそれぞれの種類を開発する為に、詳細を学びたいのであれば、左側にあるナビゲーションを使用して"プラグイン"にある関連するセクションを参照すること。
エラー制御
Vagrantの最大の強みの一つは、潔いエラー制御と人間が読み取ることのできる方法によるその報告である。Vagrantは、ユーザーがスタック・トレースを見たとき、バグであると常に強く信じるさせることができる。プラグインは同じ方法をとるように期待され、そのためにVagrantは強力なエラー制御機構を提供する。
Vagrantにおけるエラー制御は、Rubyの例外の発生によって完全に行われる。しかし、Vagrantはそのほかのエラーとこれらのエラーを異なるものとして扱う。Vagrant::Errors::VagrantError
から継承されたエラーが発生した場合、vagrant
コマンドはコンソールへ素敵な赤色のテキストでエラーのメッセージを出力し、終了ステータス1で終了する。
そうでなければ、Vagrantはバグとして報告されるべき"unexpected error"を報告し、すべてのスタック・トレースやそのほかの見苦しいものを表示する。いずれのスタック・トレースもバグとして考えられるべきである。
従って、Vagrantのエラー制御機構に合わせるために、VagrantError
のサブクラスと例外に対する適切なメッセージを設定する。この例を見るには、Vagrantの組み込みエラー
を参照すること。
コンソール入力と出力
多くのプラグインはおそらく何らかの入力/出力を行うであろう。プラグインはRubyの組み込みのputs
やgets
形式のメソッドを使用することはない。その代わりに、すべての入力/出力はある種のVagrant UIオブジェクトを通過する必要がある。そのVagnrat UIオブジェクトは、TTYがない、出力用パイプが閉じている、入力用パイプがないなどの状況を適切に制御する。
UIオブジェクトはui
プロパティを介したすべてのVagrant::Environment
上で使用できる。そして、:ui
キーによってすべてのミドルウェア環境上に公開される。UIオブジェクトは、そのソースの中のコメントに適切なドキュメント
を持っている。
アクション・フック
アクション・フックは、Vagrantのライフサイクルのさまざまなフェーズにおいて、ミドルウェアに注入することによって、非常に低いレベルで対話するための方法を提供する。これはプラグイン開発にとっても上級者向けである。
**警告:上級者向け!**プラグインの開発は、Rubyに対して適度に不安がない、経験豊かなVagrantユーザーだけが近づくべき、上級者向けの記載である。
パブリック・アクション・フック
次に示すアクション・フックはVagrantの中心で使用することができる。このリストはすべてを網羅していないこと、追加的なフックがプラグインによって追加できることについて注意すること。
-
environment_plugins_loaded
- プラグインが読み込まれた後に呼び出されるが、設定、プロビジョナ、プロビジョナなどが読み込まれる前である。 -
environment_load
- 環境やすべての設定が完全に読み込まれた後に呼び出される。 -
environment_unload
- 環境が使用され始めた後に呼び出される。環境はこのフックで使用されてはならない。 -
machine_action_boot
- ハイパー・バイザがマシンが起動したと報告した後に呼び出される。 -
machine_action_config_validate
- すべてのVagrantfiles
が読み込まれ、マージされ、有効化された後に呼び出される。 -
machine_action_destroy
- ハイパー・バイザが、仮想マシンが停止したと報告した後に呼び出される。 -
machine_action_halt
- ハイパー・バイザがマシンをホルト状態(たいてい"stopped"であり”terminated"でない)に移動した後に呼び出される。 -
machine_action_package
- Vagrantが新しいBoxのパッケージ化に成功した後に呼び出される。 -
machine_action_provision
- すべてのプロビジョナが実行された後に呼び出される。 -
machine_action_read_state
- Vagrantがディスクとハイパー・バイザから状態を読み込んだ後に呼び出される。 -
machine_action_reload
- 仮想マシンが再読み込み(ハイパー・バイザによる変更)を行った後に呼び出される。 -
machine_action_resume
- 仮想マシンが停止状態から起動状態に変化した後に呼び出される。 -
machine_action_run_command
- コマンドがマシン上で実行された後に呼び出される。 -
machine_action_ssh
- SSH接続が確立された後に呼び出される。 -
machine_action_ssh_run
- SSHコマンドが実行された後に呼び出される。 -
machine_action_start
- マシンが開始された後に呼び出される。 -
machine_action_suspend
- マシンがサスペンドした後に呼び出される。 -
machine_action_sync_folders
- 同期フォルダが設定完了した後に呼び出される。 -
machine_action_up
- マシンが起動状態になった後に呼び出される。
プライベートAPI
Vagrantのソースコードを見れば、更なるアクション・フックを見つけることができるが、
ここにあるアクション・フックのリストにあるものは、Vagrantのリリースの間で維持されることを保証されている。予告なく変更されることがあるような内部APIに依存しないようにすること。
プラグインの開発:コマンド
このページでは、VagrantへあなたのVagrantコマンド
として呼び出し可能な、新しいコマンドを追加する方法を示す。これを読む前にプラグイン開発の基礎をよく読んでおくこと。
**警告:上級者向け!**プラグインの開発は、Rubyに対して適度に不安がない、経験豊かなVagrantユーザーだけが近づくべき、上級者向けの記載である。
コンポーネントの定義
プラグイン定義において、新しいコマンドは以下のように定義される。
command "foo" do
require_relative "command"
Command
end
コマンドは、ここでは"foo"のようにコマンドの名前を引数としてとるcommand
メソッドで定義される。これは、vagrant foo
によって呼び出し可能となるコマンドであることを意味する。続くブロック引数はVagrant.plugin(2, "command")
インタフェースを実装したクラスを返す。
基本的なコマンド以外も定義することもできる。これらのコマンドはvagrant -h
の出力上では表示されない。これらはユーザーが明示的に、使用可能なコマンドの全リストを表示するvagrant list-commands
を実行するときだけ表示される。これは、Vagrantの初心者が容易に使用できないように、高度に特定されたコマンドやプラグインに対して有用である。Vagrant自身は基本的なコマンド以外を、何らかの内部機能を公開する為に使用することができる。
基本的なコマンド以外を定義する例を以下に示す。
command("foo", primary: false) do
require_relative "command"
Command
end
実装
コマンドの実装は、バージョン2コマンドに対して適切なスーパークラスを返すVagrantのメソッドであるVagrant.plugin(2, :command)
のサブクラスである。実装そのものは、execute
メソッドのみが必須の実装であるクラスなため、とても単純である。例を示す。
class Command < Vagrant.plugin(2, :command)
def execute
puts "Hello!"
0
end
end
execute
メソッドは、コマンドが呼び出されたときに呼ばれる。そして、終了ステータス(0が成功、それ以外はエラー)を返す。
これは最も単純な形のコマンドである。もちろん、コマンドのスーパークラスはVagrant環境へのアクセスする方法や、コマンドラインの解析などの共通のタスクを行うためのヘルパのようなものも提供する。
コマンドライン解析オプション
parse_options
メソッドは、コマンドラインの解析を使用することができる。これは引数としてオプション・パーサをとり、もし要求された場合に自動的にヘルプを表示するための--help
フラグなどのような共通の要素のようなものを追加する。
これはコマンドライン・フラグの解析/操作に対して推奨される。以下は組み込みのVatrantのdestroy
コマンドから直接引っ張ってきたコマンドライン・フラグの解析の例である。
options = {}
options[:force] = false
opts = OptionParser.new do |o|
o.banner = "Usage: vagrant destroy [vm-name]"
o.separator ""
o.on("-f", "--force", "Destroy without confirmation.") do |f|
options[:force] = f
end
end
# Parse the options
argv = parse_options(opts)
Vagrantマシンを使う
with_target_vms
メソッドは、Vagrantが標準的なVagrantの方法で管理しているマシンと双方向に対話することを助けるようなヘルパである。このメソッドは、コマンドライン(vagrant foo my-vm
)で対象のマシンを制御して、マルチ・マシン環境である場合でも自動的にきちんと動作する。もし、SSHアクセスを含むVagrantマシンの何らかの操作が必要であれば、このヘルパを使用するべきである。
ヘルパの使用方法の例について、再び組み込みのdestroy
コマンドを引っ張ってきた。
with_target_vms(argv, reverse: true) do |machine|
machine.action(:destroy)
end
この場合、逆順でマシンを確認し、それぞれに対してdestroyの実行を呼び出す。もし、ユーザーがvagrant destroy foo
を呼び出した場合、ヘルパは自動的にfoo
マシンに対してのみ効果を及ぼす。もし、パラメータが与えられず、マルチ・マシン環境であれば、環境にあるすべてのマシンに効果を次々に及ぼす。それは単純に適切なことを行うのだ。
素のVagrant環境を使う
素の読み込んだVagrant::Evvironment
オブジェクトは、'@env'インスタンス変数によって使用することができる。
プラグインの開発:設定
このページでは、VagrantにVagrantfile内でconfig.キー
で設定することが可能な、新しい設定オプションを追加する方法を示す。これを読む前にプラグイン開発の基礎をよく読んでおくこと。
**警告:上級者向け!**プラグインの開発は、Rubyに対して適度に不安がない、経験豊かなVagrantユーザーだけが近づくべき、上級者向けの記載である。
コンポーネントの定義
プラグイン定義において、新しい設定キーは以下のように定義される。
config "foo" do
require_relative "config"
Config
end
設定キーは、引数としての設定変数の名前を引数に盗るconfig
メソッドで定義される。これは、Vagrantfile内でconfig.foo
を介してアクセス可能な設定オブジェクトであることを意味する。その後に、Vagrant.plugin(2, :config)
インタフェースを実装したクラスを返す、ブロック引数が続く。
実装
設定キーの実装は、バージョン2設定セクションに対して適切なスーパークラスを返すVagrantのメソッドであるVagrant.plugin(2, :config)
のサブクラスである。実装はとても単純で、そのままのRubyオブジェクトとしておおむね動作する。ここに例を示す。
class Config < Vagrant.plugin(2, :config)
attr_accessor :widgets
def initialize
@widgets = UNSET_VALUE
end
def finalize!
@widgets = 0 if @widgets == UNSET_VALUE
end
end
この設定クラスを使用する場合、以下のようになるだろう。
Vagrant.configure("2") do |config|
# ...
config.foo.widgets = 12
end
簡単であろう。ただ唯一の奇妙な点は、UNSET_VALUE
である。これは実は、Vagrantが適切にかつ自動的に複数の設定をマージすることができるということである。マージは次のセクションの範囲に含まれており、UNSET_VALUE
はそこで説明する。
マージ
Vagrantは、複数のVagrantfileとそれらのマージされたものを読み込むことで動作する。このマージ・ロジックは設定クラスに対して組み込まれている。2つの設定オブジェクトをマージする際、それらを"旧"と"新"と呼ぶとして、"新"で定義されたUNSET_VALUE
でないすべてのインスタンス変数をデフォルトでとる。そして、マージの結果となる。
UNSET_VALUE
はRubyのnil
の代わりに使用される。それは、デフォルトを何らかの値にしたい場合やユーザーが実際にnil
に値を設定したい場合にそれを可能とするため、また、ユーザーにインスタンス変数を設定するかどうかを自動的に決定させることやnilとしてデフォルトとすることをVagrantに対して不可能とするためである。
これは、ほとんど毎回望まれるマージのロジックである。ゆえに、上記の例において@widgets
はUNSET_VALUE
が設定される。もし、2つのVagrant設定オブジェクトが同じファイル内にあるならば、Vagrantは次のように適切なマージを行うだろう。以下の例はそれを示す。
Vagrant.configure("2") do |config|
config.widgets = 1
end
Vagrant.configure("2") do |config|
# ... other stuff
end
Vagrant.configure("2") do |config|
config.widgets = 2
end
これがVagrantfile内にあれば、マージしたのち、widgetsの値は"2"となる。
finalize!
メソッドは、デフォルトに設定する為に、最後の設定オブジェクト上で一度だけ呼ばれる。もしfinalize!
が呼び出されれば、設定は2度とマージされない、まさに最後である。これは、上記で示したように、どんなUNSET_VALUE
をも検出させ、適切なデフォルト値を設定させる。
もちろん、時折、独自のマージ・ロジックがほしいと思うだろう。追加的なwidgetsがほしいとしてみよう。その場合、このようにしてmerge
メソッドを上書きすることができる。:
class Config < Vagrant.config("2", :config)
attr_accessor :widgets
def initialize
@widgets = 0
end
def merge(other)
super.tap do |result|
result.widgets = @widgets + other.widgets
end
end
end
この場合、その振る舞いを必要としないため、widgetsに対してUNSET_VALUE
は使用されない。0をデフォルトとし、2つのwidgetsの統合によるマージが常に行われる。さて、3つの設定ブロックを持つ上記の例を実行した場合、widgetsの最終的な値は"3"となる。
検証
設定クラスは、その値の検証の責任も持っている。Vagrantはこのためにvalidate
メソッドを呼び出す。検証メソッドの例を以下に示す。
class Config < Vagrant.plugin("2", :config)
# ...
def validate(machine)
errors = _detected_errors
if @widgets <= 5
errors << "widgets must be greater than 5"
end
{ "foo" => errors }
end
end
検証メソッドは、Vagrantが管理しているそれぞれのマシンに対して検証を行うため、machine
オブジェクトを与えられる。これによりマシンやそれに準ずるものに基づいた何らかのキーを、条件に従いって検証することができる。
_detected_errors
メソッドはVagrantによってすでに検出された、未知の設定キーのようなエラーを返す。 これはエラーメッセージの配列を返し、必ず後で返すための適切なハッシュ・オブジェクトに変換すること。
戻り値は、キーがセクション名で、値がエラー・メッセージのリストとなるRubyのハッシュオブジェクトである。これらはVagrantによって表示される。エラーがない場合、ハッシュには何の値も含まない。
アクセス
すべての設定オプションがマージされ、ファイナライズされた後、おそらくプラグインのファイナライズされた値へアクセスしたいと思うだろう。イニシャライズ関数はプラグインの種類ごとに異なるが、多くのプラグインはイニシャライズを以下のように公開している。
def initialize(machine, config)
@machine = machine
@config = config
end
プラグインを開発するとき、これらのインスタンス変数を設定する為にイニシャライズ関数で単純にsuper
を呼ぶ。
def initialize(*)
super
@config.is_now_available
# ...existing code
end
def my_helper
@config.is_here_too
end
例えば、GitHub上にあるVagrantのソースにあるplugins
フォルダのVagrant自身の内部プラグインを見ている。
プラグインの開発:ゲスト
このページでは、Vagratへ新しいゲストOSの検出を追加する方法を示す。これを読む前にプラグイン開発の基礎をよく読んでおくこと。
**警告:上級者向け!**プラグインの開発は、Rubyに対して適度に不安がない、経験豊かなVagrantユーザーだけが近づくべき、上級者向けの記載である。
Vagrantは、フォルダのマウントやネットワークの設定など、ゲストOS特有の動作を必須とする多くの機能を持っている。これらのタスクはオペレーティング・システムごとにさまざまである。もし、あなたのオペレーティング・システムでこれらのうちの一つが動作しなかった場合、ゲストの実装が不十分か不適切なのかもしれない。
コンポーネントの定義
プラグイン定義において、新しいゲストは以下のように定義される。
guest "ubuntu" do
require_relative "guest"
Guest
end
ゲストはguest
メソッドで定義される。一つ目の引数はゲストの名前である。この名前はどこかで実際に使用されていないが、将来的に何らかの役に立つようなものを選ぶ。続くブロック引数はVagrant.plugin(2, :guest)
インタフェースを実装したクラスを返す。
実装
ゲストの実装はVagrant.plugin("2", "guest")
のサブクラスである。この実装において、唯一detect?
メソッドのみ実装が必須である。
detect?
メソッドは、何のオペレーティング・システムがゲストで稼働しているかを決定する為に、マシンが起動した後のある時点でVagrantによって呼び出される。そのオペレーティング・システムを検出した場合、detect?
からtrue
が返される。そうでなければ、false
が返される。
マシンとの通信チャネルはこの時点で稼働することが保証され、オペレーティング・システムを検出するための最も共通的な方法は、いくつかの基本的なテストを行うことである。
class MyGuest < Vagrant.plugin("2", "guest")
def detect?(machine)
machine.communicate.test("cat /etc/myos-release")
end
end
OSを検出した後、そのOSは、要求される様々なゲストの能力に対して使用される。
ゲストの継承
ときどき、共通の祖先から枝分かれしたオペレーティング・システムもあるので、Vagrantはゲストに対する継承の形をサポートしている。この良い例は、LinuxはDebianの祖先であり、さらに多くにおいてUbuntuの祖先である。継承は、ディストリビューション特有の上書きを許すことで、ゲストが多くの共通の振る舞いを共有できるようにする。
Vagrantはカスタム化した能力ベースシステムを使用するため、継承は標準的なRubyのクラス継承によって行われない。Vagrantは継承の振り分けを制御する。
異なるゲストのサブクラスに対して、ゲスト定義における2つ目のパラメータとしてゲストの名前を指定する。
guest "ubuntu", "debian" do
require_relative "guest"
Guest
end
上記のコンポーネントで、"ubuntu"ゲストは"debian"から継承される。"ubuntu"に対する能力をみてみると、"debian"からのすべての能力も使用することができ、いくつかの"ubuntu"の能力は親の能力を上書きする。
detect?
によってオペレーティング・システムを検出したとき、Vagranは常に、それらの親を確認する前に、子のオペレーティングシステムを検索することで深さ優先サーチを行う。そのため、上記の例では"ubuntu"上のdetect?
メソッドは"debian"の前に呼び出される。
プラグインの開発:ゲストの能力
このページでは、ゲストに対して、新しい動作が指定したゲスト・オペレーティング・システム上で行えるうように、新しい機能を追加する方法を示す。これを読む前にプラグイン開発の基礎をよく読んでおくこと。
**警告:上級者向け!**プラグインの開発は、Rubyに対して適度に不安がない、経験豊かなVagrantユーザーだけが近づくべき、上級者向けの記載である。
ゲストの能力は、ゲストのオペレーティング・システムで動作するような、特定の"能力”をゲストに付与することによって、ゲスト を拡張する。
能力は、プラグインがVagrantのコアな部分を変更することなしに、既存のゲスト・オペレーティング・システムに新しい機能を追加することができる。Vagrantの早期のバージョンでは、ゲストのロジックのすべては、Vagrantのコアな部分に含まれており、簡単に増大することができなかった。
コンポーネントの定義
プラグインの定義において、ゲストの能力は次のように定義される。
guest_capability "ubuntu", "my_custom_capability" do
require_relative "cap/my_custom_capability"
Cap::MyCustomCapability
end
ゲストの能力は、能力を追加したいゲストと能力そのものの名前の2つのパラメータをとるguest_capability
メソッドを呼ぶことで定義される。そして、ブロック引数は、能力と同じ名前のメソッドを実装するクラスを返す。これは次のセクションで詳しく述べる。
実装
実装は、能力と同じ名前であるメソッドを持つ、クラスまたはモジュールとなる。guest_capability
コンポーネントから返されたクラスにおいて、メソッドは即座にアクセス可能となるべきである。それは、インスタンスメソッドであれば、インスタンスが返されることを意味する。
通常、クラス・メソッドは能力に対して使用される。例えば、ここに上記の能力に対する実装を示す。
module Cap
class MyCustomCapability
def self.my_custom_capability(machine)
# implementation
end
end
end
すべての能力は、一つ目の引数としてVagrantのマシン・オブジェクトをとる。追加の引数は、指定した能力によって決定される。より詳細な情報のために、実装しようとする能力の使い方や文書を見ること。
いくつかの能力は、呼び元へ値を返す必要もあるので、能力を実装する際には注意すること。
能力は常にマシン上でSSHなどの通信チャネルへのアクセス方法を持ち、マシンは一般的にブートされているとみなすことができる。
能力の呼び出し
すべての能力でマシンにアクセスすることができるので、能力はほかの能力を呼び出すこともできる。これはより詳しい情報をヘルパーに尋ねる可能性のために、能力の継承機構を使用することに対して有用である。例えば、"redhat"ゲストは、ネットワーク・スクリプトの場所を返す単純な能力を持っている。
CentOSやFedoraのようなRedHatの子ゲストはネットワーク・スクリプトの場所を決定する為に子の能力を使用する。また、時々それらを上書きしている。
能力はこのように呼び出される。
machine.guest.capability(:capability_name)
メソッドに与えられるいかなる追加の引数も能力に受け渡され、そして能力は実際に能力が返す値を返す。
プラグインの開発:ホスト
このページでは、Vagrantが新しいオペレーティング・システム上でホスト特有の操作を適切に実行させるために、Vagratへ新しいホストOSの検出を追加する方法を示す。これを読む前にプラグイン開発の基礎をよく読んでおくこと。
**警告:上級者向け!**プラグインの開発は、Rubyに対して適度に不安がない、経験豊かなVagrantユーザーだけが近づくべき、上級者向けの記載である。
Vagrantは、NFSフォルダのエクスポートのような、ホスト特有の機能を要求するいくつかの機能を持っている。これらのタスクはオペレーティング・システムごとにさまざまである。Vagrantは、これらのホストOS特有の操作を行うためにホストの能力だけでなくホストの検出も使用する。
コンポーネントの定義
プラグインの定義において、新しいホストは以下のように定義することができる。
host "ubuntu" do
require_relative "host"
Host
end
ホストはhost
メソッドで定義される。一つ目の引数はホストの名前である。この名前はどこかで実際に使用されていないが、将来的に何らかの役に立つようなものを選ぶ。そして、ブロック引数は`Vagrant.plugin(2, ;host)'インタフェースを実装したクラスを返す。
実装
ホストの実装はVagrant.plugin("2", "host")
である。この実装において、detect?
メソッドが唯一実装が必須なものである。
detect?
メソッドは、Vagrantが稼働しているOSがこのホストであるかを決定する為に、その初期化の過程の早い段階でVagrantによって呼び出される。そのオペレーティング・システムであると検出した場合、detect?
からtrue
を返す。そうでなければfalse
を返す。
class MyHost < Vagrant.plugin("2", "host")
def detect?(environment)
File.file?("/etc/arch-release")
end
end
OSを検出したのち、そのOSは、要求される様々な[ホストの能力]host capabilitiesを使用する。
ホストの継承
ときどき、共通の祖先から枝分かれしたオペレーティング・システムもあるので、Vagrantはホストに対する継承の形をサポートしている。この良い例は、LinuxはDebianの祖先であり、さらに多くにおいてUbuntuの祖先である。継承は、ディストリビューション特有の上書きを許すことで、ホストが多くの共通の振る舞いを共有できるようにする。
Vagrantはカスタム化した能力ベースシステムを使用するため、継承は標準的なRubyのクラス継承によって行われない。Vagrantは継承の振り分けを制御する。
異なるホストのサブクラスに対して、ホスト定義における2つ目のパラメータとしてホストの名前を指定する。
host "ubuntu", "debian" do
require_relative "host"
Host
end
上記のコンポーネントで、"ubuntu"ホストは"debian"から継承される。"ubuntu"に対する能力を見てみると、"debian"からのすべての能力も使用することができ、いくつかの"ubuntu"の能力は親の能力を上書きする。
detect?
によってオペレーティング・システムを検出したとき、Vagranは常に、それらの親を確認する前に、子のオペレーティングシステムを検索することで深さ優先サーチを行う。そのため、上記の例では"ubuntu"上のdetect?
メソッドは"debian"の前に呼び出される。
プラグインの開発:ホストの能力
このページでは、ホストに対して、新しい動作が指定したホスト・オペレーティング・システム上で行えるうように、新しい機能を追加する方法を示す。これを読む前にプラグイン開発の基礎をよく読んでおくこと。
**警告:上級者向け!**プラグインの開発は、Rubyに対して適度に不安がない、経験豊かなVagrantユーザーだけが近づくべき、上級者向けの記載である。
ホストの能力は、ホストのオペレーティング・システムで動作するような、特定の"能力”をホストに付与することによって、ホスト を拡張する。
能力は、プラグインがVagrantのコアな部分を変更することなしに、既存のホスト・オペレーティング・システムに新しい機能を追加することができる。Vagrantの早期のバージョンでは、ホストのロジックのすべては、Vagrantのコアな部分に含まれており、簡単に拡張することができなかった。
定義と実装
ホストの能力の芸地と実装はゲストの能力と同じである。
しかしながら、ゲストの能力との主な違いは、一つ目の引数としてマシンをとる代わりに、すべてのホストの能力は一つ目の引数としてVagrant::Environment
をとる。
環境へのアクセスは、ホストの能力がグローバルな状態へのアクセスすることを可能とする。そして、ほかのホストの能力を呼び出すこともできる。
能力の呼び出し
すべての能力において環境へアクセスできるので、能力は他の能力を呼び出すこともできる。これはより詳しい情報をヘルパーに尋ねる可能性のために、能力の継承機構を使用することに対して有用である。例えば、"linux"ゲストは、NFSが稼働しているかを確認する為に使用するコマンドを返す"nfs_check_command"能力を持っている。
ここに示す細かな点を除いて、RedHatやArchなどのようなLinuxの子ゲストの能力は、この能力を、たいていLinuxの振る舞いを継承したものとして使用する。
能力はこのように呼び出すことができる。
environment.host.capability(:capability_name)
メソッドに与えられるいかなる追加の引数も能力に受け渡され、そして能力は実際に能力が返す値を返す。
プラグインの開発:プロバイダ
このページでは、VagrantがVirtualBox以外のシステムによって動かされているマシンの実行や管理ができるように、Vagrantに新しいプロバイダに対するサポートを追加する方法を示す。これを読む前にプラグイン開発の基礎をよく読んでおくこと。
プロバイダを開発する前に、ユーザー視点からみてどのようにプロバイダが動くかについても熟知しておくべきである。
**警告:上級者向け!**プラグインの開発は、Rubyに対して適度に不安がない、経験豊かなVagrantユーザーだけが近づくべき、上級者向けの記載である。
プロバイダの例:AWS
プロバイダをどのように書くかを学ぶための最も良い方法は、練習としてあるものがどのように書かれているかをみることであろう。この文書を拡張する為に、AWSプロバイダの実装であるvagrant-awsプラグインについて十分学習しておこう。このプラグインは、あなたのプラグインの構造やテストや実装をどのように行うかの良い例である。
コンポーネントの定義
プラグインの定義において、新しいプロバイダはこのように定義される。
provider "my_cloud" do
require_relative "provider"
Provider
end
プロバイダは、プロバイダの名前を指定する一つの引数をとるprovider
メソッドで定義される。これは、プロバイダを指定する為にvagrant up
と共に使用される名前である。上記の例では、プロバイダはvagrant up --provider=my_cloud
で呼ぶことで使用することができる。
ブロック引数は、次で説明するVagrant.plugin(2, :provider)
を実装したクラス、を遅延読み込みして返す。
プロバイダ・クラス
プロバイダ・クラスは、Vagrantが適切な親クラスを返させるための安全なアップグレードの方法であるVagrant.plugin(2, :provider)
のサブクラスであり実装であるべきである。
実装されるべきこのクラスとメソッドについてはとても良い文書がある。コメント内にあるクラスの文書は何をすべきかを理解するのに十分である。
プラグインの全体的な構造と同様にAWSプロバイダ・クラスを見ることは、初めの一歩として強く勧められることである。
実装される必要のあるメソッドのそれぞれについて深く見ていく代わりに、その文書は、高度であるが、プロバイダを作る手助けとなる重要なポイントを網羅している。
Boxのフォーマット
それぞれのプロバイダは、自分自身のBoxのフォーマットをもつ責任がある。これは実際には、一般的なBoxはどうするかという、きわめて単純な手順である。それを説明する前に、一般的なBoxのフォーマットに詳しくなるべきである。
Boxのフォーマットに対する唯一の要求は、metadata.json
ファイルが、上記の例で選んだプロバイダの名前と一致するprovider
キーを持つことである。
これに加えて、metadataに何らかのデータを置くのと同様に、archiveに何らかのファイルも置くだろう。Vagrantのコア自身には配慮されていないので、Boxのデータを制御するのはそのプロバイダ次第である。Vagrantのコアはただ、展開とそのBoxが適切なプロバイダようであるか検証を制御するのみである。
実際に使用されている、一組のBoxフォーマット例としては、
-
virtualbox
のBoxフォーマットはちょうど、VBoxManage export
コマンドの内容であるフラットなディレクトリである。 -
vmware_fusion
のBoxフォーマットはちょうど、vmwarevm
フォルダの内容であるフラットなディレクトリである。ただしVMWareが機能する為の素の必須ファイルをふくむだけのものである。 -
aws
のBoxフォーマットはちょうど、Vagrantfileがデフォルトとしているいくつかの設定内容である。awsのBoxを展開したものをここで見ることができる。
プロバイダに関する何かをまさに書こうとする前に、vagrant box add
とともに実行することによってBoxのフォーマットが動作するかを検証することができる。vagrant box list
を実行した場合、どのプロバイダがインストールされるかを見ることができる。
そのプロバイダに対するBoxを追加する為に、プロバイダのプラグインはインストールされる必要はない。
アクション
プロバイダを構築する際、理解する為に最も重要な概念は、おそらく、プロバイダの"action"インタフェースである。それは、プロバイダが魔法のような動作をするための秘密のソースである。
アクションは、ミドルウェアの概念の最上位に構築され、それはプロバイダが複数の異なる段階を実行したり、エラー回復機構をもったり、前後のふるまいや、そのほか多くのものを持つことを許す。
Vagrantコアは、プロバイダクラス上のaction
メソッドを通してプロバイダから特定のアクションを要求する。要求されるアクションの完全なリストは、そのスーパークラス上のメソッドのコメント上にリスト化されている。もし、プロバイダが適切なアクションを実装していない場合、Vagrantコアは親切なエラーを表示するだろう。どんなミスをしても心配しなくてもよい。それらは壮大なる爆発や破壊をすることはない。
VirtualBoxのプロバイダがどのように複雑な何段階ものステップのプロセスを作り上げるためにアクションを使用するかを見ておこう。AWSプロバイダは似たようなプロセスを使用している。
組み込みミドルウェア
共通のタスクを補助する為に、Vagrantは組み込みミドルウェアのセットを搭載している。いずれのミドルウェアも、その振る舞いやオプションのそれぞれに対する十分なコメントがある。そして、これらの組み込みミドルウェアの使い方は、適切なふるまいをするプロバイダを構築する為の肝である。
これらの組み込みミドルウェアはプロバイダのアクションのための標準ライブラリのように考えることができる。VirtualBoxプロバイダのコアはこれらの組み込みミドルウェアを頻繁に使用する。
状態維持
マシンの生成と管理のプロセスにおいて、プロバイダは通常、状態がどこにあるかなどを格納する必要がある。Vagrantはそれぞれのマシンに、この状態を格納するためのディレクトリを提供する。
これに関するユースケースの例としては、VirtualBoxプロバイダは、VirtualBox仮想マシンが生成したUUIDを格納する。これはプロバイダに、マシンが生成状態か、稼働状態か、サスペンド状態かどうかを追跡できるようにする。
実際にVMWareプロバイダはこの状態ディレクトリに、全仮想ディスク・ドライブとあらゆるもの、仮想マシンのすべてを格納する。
そのディレクトリは、プロバイダの初期化をするために与えられるMachine
インスタンスのdata_dir
属性から取得することができる。ミドルウェアのアクションとともに、マシンは常にその環境の:machine
キーを介して取得することができる。data_dir
属性はRubyのPathnameオブジェクトである。
プロバイダにとって、このディレクトリのすべての内容を慎重に管理することが非常に重要である。Vagrantコア自身は、このディレクトリを少し掃除する。しかしながら、マシンが削除された、このディレクトリからすべての状態を消去することを確認すること。
設定
Vagrantは、ユーザーがVagrantfileから特定のプロバイダを適切に調整したり制御したりするためにプロバイダ特有の設定をサポートする。カスタムプロバイダがカスタム設定を公開できるようにするのも同じように簡単である。
プロバイダ特有の設定は、標準的なプラグインの設定の特殊なケースである。設定コンポーネントの定義をするとき、設定の名前のつけ方はプロバイダと同様であり、2つ目のパラメータと同じように、:provier
を指定する。
config("my_cloud", :provider) do
require_relative "config"
Config
end
プロバイダに名前が一致し、2つ目に:provider
パラメータが与えられた場合、Vagrantは自動的に、これをそのプロバイダ用のプロバイダ特有の設定として公開する。ユーザーはVagrantfile内で以下のようにできる。
config.vm.provider :my_cloud do |config|
# Your specific configuration!
end
プラグインの中のconfig
コンポーネントから返される設定クラスは、そのほかのプラグインの設定と同様であり、詳細な情報はそのページに読むこと。Vagrantは自動的にその設定をそのほかの設定項目のように制御する。
プロバイダ特有の設定は、provider_config
属性を介して、マシン上で使用することができる。アクションやプロバイダクラスのように、machine.provier_config
を介してアクセスすることができる。
ベスト・プラクティスそのプロバイダが機能に対してプロバイダ特有の設定を必要としないならば、それは可能である。Vagrantは強く、設定より規約 の哲学の実践している。ユーザーがそのプロバイダをインストールする際に、
vagrant up --provider=your_provider
をするだけで、それが動くことを理想としている。
並列化
Vagrantは、プロバイダがそれを明確にサポートしている場合、vagrant up
などのようないくつかのアクションの並列化をサポートしている。デフォルトでは、Vagrantはプロバイダの並列化をしない。
並列化が有効になった場合、複数のアクションが並列に実行されるだろう。そのため、プロバイダはそれらのアクションのスタックがスレッド・セーフであることを保証すべきである。Vagrantのコア自身は(Boxコレクション、SSHなど)はスレッド・セーフである。
プロバイダコンポーネント上にparalled
オプションを設定することで、プロバイダは明確に並列化をすることができる。
provider("my_cloud", parallel: true) do
require_relative "provider"
Provider
end
並列化を有効化するのに必要な変更はこれだけである。
プラグインの開発:プロビジョナ
このページでは、Vagrantがカスタム・プロビジョナを使用して、自動的にソフトウェアのインストールやソフトウェアの設定をできるための新しいプロビジョナをVagrantへ追加する方法を示す。これを読む前にプラグイン開発の基礎をよく読んでおくこと。
**警告:上級者向け!**プラグインの開発は、Rubyに対して適度に不安がない、経験豊かなVagrantユーザーだけが近づくべき、上級者向けの記載である。
コンポーネントの定義
プラグイン定義において、新しいプロビジョナは以下のように定義される。
provisioner "custom" do
require_relative "provisioner"
Provisioner
end
プロビジョナは、プロビジョナの名前を指定する一つの引数をとるprovisioner
メソッドとして定義される。これはプロビジョナを設定したり、有効化したりするときにconfig.vm.provision
と共に使用される名前である。上記の例では、プロビジョナはconfig.vm.provision :custiom
を使用して有効化されている。
ブロック引数は遅延読み込みをし、次に示すVagrant.plugin(2, :provisioner)
インタフェースを実装したクラスを返す。
プロビジョナ・クラス
プロビジョナ・クラスは、Vagrantが適切な親クラスを返させるための安全なアップグレードの方法であるVagrant.plugin(2, :provisioner)
のサブクラスであり実装であるべきである。
実装されるべきこのクラスとメソッドについてはとても良い文書がある。コメント内にあるクラスの文書は何をすべきかを理解するのに十分である。
実装すべき2つの主要なメソッドがある。configure
メソッドとprovision
メソッドである。
configure
メソッドは、マシンのブート・プロセスの早い段階において、プロビジョナがマシン上の共有フォルダやネットワークの定義など、新しい設定を定義できるように、呼び出される。一つの例として、Chef solo プロビジョナはこれを共有フォルダの定義に使用している。
provision
メソッドは、マシンがブートし、SSH接続の準備ができた時点で呼び出される。このメソッドにおいて、プロビジョナは、実行する必要のある何らかのコマンドを実行すべきである。
プラグインの開発:パッケージ化と配布
このページでは
This page documents how to organize the file structure of your plugin and distribute it so that it is installable using 標準的なインストール方法を使用してインストールできるように、プラグインのファイル構造の構築の仕方や、配布の仕方を示す。これを読む前にプラグイン開発の基礎をよく読んでおくこと。
**警告:上級者向け!**プラグインの開発は、Rubyに対して適度に不安がない、経験豊かなVagrantユーザーだけが近づくべき、上級者向けの記載である。
プラグインの例
パッケージ化や配布の記述するための最も良い近道は、ほかのプラグインのやり方を見ることである。これに関する最も適切な例のプラグインはvagrant-awsである。
BundlerやRakeを使用して、新しいvagrant-awsパッケージを生成することは簡単である。単純にrake package
を呼び出すことで、一つのgem
ファイルがディレクトリに配置される。rake release
を呼び出すことでgemが生成され、
それが、vagrant plugin install
を使用することでインストール可能となるRubyGems 中央リポジトリにアップロードされる。
Bundlerを使用することで基本的に無用で手に入れられるので、プラグインは簡単にできるし、そうあるべきでもある。
プロジェクトの設定
プロジェクトを設定するには、bunndle gem vagrant-my-plugin
を実行する。これは、RubyGem用の初期レイアウトを持つvagrant-my-plugin
ディレクトリを生成する。
依存性の追加やメタデータの変更をするためにvagrant-my-plugin.gemspec
ファイルを修正するべきである。良い例としてvagrant-aws.gemspecを参照すること。
そのgemのためにVagrantに依存しないこと。Vagrantはもはやgemとして配布されていないし、そのプラグインがインストールされていればいつでも入手可能であると考えることが出来るだろう。
一度、RubyGem用のディレクトリ構造が構築されれば、そのGemfileを修正したくなるだろう。ここにVagrantプラグインの開発のための基本的なGemfileの構造を示す。
source "https://rubygems.org"
group :development do
gem "vagrant", git: "https://github.com/mitchellh/vagrant.git"
end
group :plugins do
gem "my-vagrant-plugin", path: "."
end
このGemfileは開発用の"vagrant"を取得する。これによって、すでにロードされているプラグインとともにVagranを稼働するためにbundle exec vagrant
を行うことができるようになる。そして、手動でテストすることが出来る。
奇妙に見えるこのGemfileの唯一の点は、"plugins"グループとそのグループにpluginを入れているところだろう。vagrant plugin
コマンドはdevelopment内では動作しないので、これはVagrantにプラグインを"インストール"する方法である。Vagrantは"plugins"グループにあるすべてのgemsを自動的にロードする。もし、プラグインがほかのプラグインとともに動作するのであれば、これは開発用のVagrantに複数のプラグインを追加することもできる。
次に、いかに中身をしめすような最小限のRakefile
を生成する。
require "rubygems"
require "bundler/setup"
Bundler::GemHelper.install_tasks
もしrake -T
を今実行した場合、使用できるrakeのタスクが一覧表示され、package
やrelease
のタスクを持っていることを見られるだろう。ついに、あなたはプラグインを開発することができ、それを作り出すことができたのだ。
テストを含むわかりやすい例については、vagrant-aws Rakefileを見ればよいだろう。
プラグインのテスト
開発期間においてプラグインのテストを手動で行うためには、そのプラグインをロードしてVagrantを起動する為にbundle exec vagrant
を使用する(先に行ったGemfileの設定を思い出そう)。
自動テストに対しては、vagrant-specプロジェクトが、 プラグインのユニットテストや受け入れテストの双方に対する補助を提供してくれる。そのプロジェクトに対してvagrant-specをどのように統合させていくかに関する詳細な説明については、巨大なREADMEを参照すること。Vagrant自身(さらにそのコアなプラグイン)は自動テストに対してvagrant-specを使用している。and all of its core plugins)。