A step by step walkthrough to understanding how to create a plugin for Vagrant.
- This walkthrough may corrupt your Vagrant install (I accidentally nuked mine)
- You'll be installing the [development gem on GitHub](I accidentally nuked mine), as mentioned in the Vagrant plugin docs
- The development gem installs a vagrant executable, which blows away the executable created by the standard vagrant installer
- You may also encounter conflicts with the vagrant data cached in
$HOME/.vagrant.d/
- For your development pleasure, I recommend developing your plugin in a Vagrant-less VM
$ bundle gem my_vagrant_plugin --test=rspec
$ cd my_vagrant_plugin
$ git add .
$ git commit -m "Create initial commit"
# this fails
$ bundle
You have one or more invalid gemspecs that need to be fixed.
The gemspec at </path/of/my_vagrant_plugin.gemspec> is not valid. Please fix this gemspec.
The validation error was '"FIXME" or "TODO" is not an author'
# after updating gemspec, this works
$ bundle
Fetching gem metadata from https://rubygems.org/..........
Fetching version metadata from https://rubygems.org/.
Resolving dependencies...
Using rake 10.5.0
Using bundler 1.13.6
Using diff-lcs 1.2.5
Using my_vagrant_plugin 0.1.0 from source at `.`
Using rspec-support 3.5.0
Using rspec-core 3.5.4
Using rspec-expectations 3.5.0
Using rspec-mocks 3.5.0
Using rspec 3.5.0
Bundle complete! 4 Gemfile dependencies, 9 gems now installed.
Use `bundle show [gemname]` to see where a bundled gem is installed.
# 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
# remove from Gemspec
spec.add_development_dependency "bundler", "~> 1.13"
spec.add_development_dependency "rake", "~> 10.0"
spec.add_development_dependency "rspec", "~> 3.0"
# add to ':plugins' group in Gemfile
group :plugins do
# existing plugins
gem "my_vagrant_plugin", path: "."
# new plugins
gem "bundler", "~> 1.13"
gem "rake", "~> 10.0"
gem "rspec", "~> 3.0"
end
# run tests
$ bundle exec rspec
MyVagrantPlugin
has a version number
does something useful (FAILED - 1)
Failures:
1) MyVagrantPlugin does something useful
Failure/Error: expect(false).to eq(true)
expected: true
got: false
(compared using ==)
# ./spec/my_vagrant_plugin_spec.rb:9:in \`block (2 levels) in <top (required)>'
Finished in 0.01383 seconds (files took 0.08105 seconds to load)
2 examples, 1 failure
Failed examples:
rspec ./spec/my_vagrant_plugin_spec.rb:8 # MyVagrantPlugin does something useful
# spec/my_vagrant_plugin_spec.rb (after deleting failing test)
require "spec_helper"
describe MyVagrantPlugin do
it "has a version number" do
expect(MyVagrantPlugin::VERSION).not_to be nil
end
end
The default method for setting the gem version is not compatible with Vagrant conventions, and will create problems later when we call Vagrant.plugin("2")
.
-
Delete
lib/my_vagrant_plugin/version.rb
-
From
lib/my_vagrant_plugin.rb
, removerequire "my_vagrant_plugin/version"
-
From
my_vagrant_plugin.gemspec
, removerequire "my_vagrant_plugin/version"
-
From
spec/spec_helper.rb
, remote"has a version number"
test -
Change version in
my_vagrant_plugin.gemspec
spec.version = MyVagrantPlugin::VERSION
spec.version = "0.1.0"
# lib/my_vagrant_plugin.rb
require "vagrant"
class MyVagrantPlugin < Vagrant.plugin("2")
# Your code goes here...
end
$ bundle exec rspec
No examples found.
Finished in 0.00025 seconds (files took 0.28553 seconds to load)
0 examples, 0 failures
# A plug in has no name (and therefor can't "plug in")
class MyVagrantPlugin < Vagrant.plugin("2")
# Your code goes here...
end
# A name is required for Vagrant to load the plugin
class MyVagrantPlugin < Vagrant.plugin("2")
name "My Vagrant Plugin"
end
First, declare the command lib/my_vagrant_plugin.rb
class MyVagrantPlugin < Vagrant.plugin("2")
name "My Vagrant Plugin"
# in case you've wanted to announce tuberousness
command "declare-self-potato" do
require "my_vagrant_plugin/declare_self_potato"
MyVagrantPlugin::DeclareSelfPotato
end
end
Now try look for declare_self_potato
in command list (this wil fail). Also, you'll see the You appear to be running Vagrant outside of the official installers...
error whenever you run bundle exec vagrant
, and from now on this walkthrough will display <outside installer warning>
.
$ bundle exec vagrant
You appear to be running Vagrant outside of the official installers.
Note that the installers are what ensure that Vagrant has all required
dependencies, and Vagrant assumes that these dependencies exist. By
running outside of the installer environment, Vagrant may not function
properly. To remove this warning, install Vagrant using one of the
official packages from vagrantup.com.
bundler: failed to load command: vagrant (/Users/mpope/.rbenv/versions/2.3.3/lib/ruby/gems/2.3.0/bin/vagrant)
LoadError: cannot load such file -- my_vagrant_plugin/declare_self_potato
Create this following in lib/my_vagrant_plugin/declare_self_potato
class MyVagrantPlugin
class DeclareSelfPotato < Vagrant.plugin("2", :command)
class << self
def synopsis
"Declares self a potato"
end
end
end
end
You should now see declare-self-potato
in the command list
$ bundle exec vagrant
<outside installer warning>
Usage: vagrant [options] <command> [<args>]
-v, --version Print the version and exit.
-h, --help Print this help.
Common commands:
box manages boxes: installation, removal, etc.
declare-self-potato Declares self a potato
destroy stops and deletes all traces of the vagrant machine
# rest of output trimmed for brevity
However, when you run the command, it doesn't yet do anything
$ bundle exec vagrant declare-self-potato
<outside installer warning>
$
Implement declare-self-potato
by adding the execute
method
class MyVagrantPlugin
class DeclareSelfPotato < Vagrant.plugin("2", :command)
def execute
# you don't wanna use puts, which we'll explore in next section
puts "Because I used 'puts', I am a naughty potato"
# exit code 1 (cuz puts bad in vagrant command)
1
end
class << self
def synopsis
"Declares self a potato"
end
end
end
end
Now become a naughty potato
$ bundle exec vagrant declare-self-potato
<outside installer warning>
Because I used 'puts', I am a naughty potato
$ echo $?
1
In the previous section, you were a naughty potato because you used puts
to write to the screen. For reasons I haven't yet taken the time to fully understand, there are times when writing directly to standard out causes problems for vagrant. In the Vagrant documentation, the development basics page, under the CONSOLE INPUT AND OUTPUT heading, says "Plugins should never use Ruby's built-in puts or gets style methods", which is contradicted on the commands page under the IMPLEMENTATION heading by the example which uses puts
.
Digging into the Vagrant source code a bit, it appears the path for a proper potato to follow is to the safe_puts
command in found in Util::SafePuts, which is implemented in the example below.
class MyVagrantPlugin
class DeclareSelfPotato < Vagrant.plugin("2", :command)
include Vagrant::Util::SafePuts
def execute
safe_puts("I am a proper potato")
0
end
class << self
def synopsis
"Declares self a potato"
end
end
end
end
Now become a the proper potato you've always wanted to be
$ bundle exec vagrant declare-self-potato
<outside installer warning>
I am a proper potato
mpope-mbpr15:my_vagrant_plugin mpope$ echo $?
0
I wanted to give a basic example of how to get started with a Vagrant plugin. Now that you've seen how it to get started, dig through the quite excellent Vagrant docs, and figure out the rest to creating your own!
If you'd like to expand the examples in this walkthrough:
- Add to the README
- Add some example code
- Make sure the code in your final example works
- Submit a pull request!