» Plugin Development Basics
Plugins are a great way to augment or change the behavior and functionalityof Vagrant. Since plugins introduce additional external dependencies forusers, they should be used as a last resort when attempting todo something with Vagrant.
But if you need to introduce custom behaviorsinto Vagrant, plugins are the best way, since they are safe against futureupgrades and use a stable API.
Warning: Advanced Topic! Developing plugins is anadvanced topic that only experienced Vagrant users who are reasonablycomfortable with Ruby should approach.
Plugins are written using Ruby and are packagedusing RubyGems. Familiarity with Ruby is required,but the packaging and distribution section should helpguide you to packaging your plugin into a RubyGem.
» Setup and Workflow
Because plugins are packaged as RubyGems, Vagrant plugins should bedeveloped as if you were developing a regular RubyGem. The easiestway to do this is to use the bundle gem
command.
Once the directory structure for a RubyGem is setup, you will wantto modify your Gemfile. Here is the basic structure of a Gemfile forVagrant plugin development:
source "https://rubygems.org"
group :development do
gem "vagrant", git: "https://github.com/hashicorp/vagrant.git"
end
group :plugins do
gem "my-vagrant-plugin", path: "."
end
This Gemfile gets "vagrant" for development. This allows you tobundle exec vagrant
to run Vagrant with your plugin already loaded,so that you can test it manually that way.
The only thing about this Gemfile that may stand out as odd is the"plugins" group and putting your plugin in that group. Becausevagrant plugin
commands do not work in development, this is howyou "install" your plugin into Vagrant. Vagrant will automaticallyload any gems listed in the "plugins" group. Note that this alsoallows you to add multiple plugins to Vagrant for development, ifyour plugin works with another plugin.
When you want to manually test your plugin, usebundle exec vagrant
in order to run Vagrant with your pluginloaded (as we specified in the Gemfile).
» Plugin Definition
All plugins are required to have a definition. A definition contains detailsabout the plugin such as the name of it and what components it contains.
A definition at the bare minimum looks like the following:
class MyPlugin < Vagrant.plugin("2")
name "My Plugin"
end
A definition is a class that inherits from Vagrant.plugin("2")
. The "2"there is the version that the plugin is valid for. API stability is onlypromised for each major version of Vagrant, so this is important. (The1.x series is working towards 2.0, so the API version is "2")
The most critical feature of a plugin definition is that it must _always_load, no matter what version of Vagrant is running. Theoretically, Vagrantversion 87 (does not actually exist) would be able to load a version 2 plugindefinition. This is achieved through clever lazy loading of individual componentsof the plugin, and is covered shortly.
» Plugin Components
Within the definition, a plugin advertises what components it adds toVagrant. An example is shown below where a command and provisioner areadded:
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
Let us go over the major pieces of what is going on here. Note from a generalRuby language perspective the above should be familiar. The syntax shouldnot scare you. If it does, then please familiarize with Ruby further beforeattempting to write a plugin.
The first thing to note is that individual components are defined bymaking a method call with the component name, such as command
orprovisioner
. These in turn take some parameters. In the case of ourexample it is just the name of the command and the name of the provisioner.All component definitions then take a block argument (a callback) thatmust return the actual component implementation class.
The block argument is where the "clever lazy loading" (mentioned above)comes into play. The component blocks should lazy load the actual file thatcontains the implementation of the component, and then return that component.
This is done because the actual dependencies and APIs used when definingcomponents are not stable across major Vagrant versions. A command implementationwritten for Vagrant 2.0 will not be compatible with Vagrant 3.0 and so on. Butthe definition is just plain Ruby that must always be forward compatibleto future Vagrant versions.
To repeat, the lazy loading aspect of plugin components is criticalto the way Vagrant plugins work. All components must be lazily loadedand returned within their definition blocks.
Now, each component has a different API. Please visit the relevant sectionusing the navigation to the left under "Plugins" to learn more about developingeach type of component.
» Error Handling
One of Vagrant's biggest strength is gracefully handling errors and reportingthem in human-readable ways. Vagrant has always strongly believed that ifa user sees a stack trace, it is a bug. It is expected that plugins will behavethe same way, and Vagrant provides strong error handling mechanisms toassist with this.
Error handling in Vagrant is done entirely by raising Ruby exceptions.But Vagrant treats certain errors differently than others. If an erroris raised that inherits from Vagrant::Errors::VagrantError
, then thevagrant
command will output the message of the error in nice red textto the console and exit with an exit status of 1.
Otherwise, Vagrant reports an "unexpected error" that should be reportedas a bug, and shows a full stack trace and other ugliness. Any stack tracesshould be considered bugs.
Therefore, to fit into Vagrant's error handling mechanisms, subclassVagrantError
and set a proper message on your exception. To seeexamples of this, look at Vagrant's built-in errors.
» Console Input and Output
Most plugins are likely going to want to do some sort of input/output.Plugins should never use Ruby's built-in puts
or gets
style methods.Instead, all input/output should go through some sort of Vagrant UI object.The Vagrant UI object properly handles cases where there is no TTY, outputpipes are closed, there is no input pipe, etc.
A UI object is available on every Vagrant::Environment
via the ui
propertyand is exposed within every middleware environment via the :ui
key. UIobjects have decent documentationwithin the comments of their source.