vagrant/website/source/docs/plugins/development-basics.html.md

182 lines
7.2 KiB
Markdown
Raw Normal View History

---
layout: "docs"
page_title: "Plugin Development Basics - Plugins"
sidebar_current: "plugins-development-basics"
description: |-
2016-01-19 19:54:13 +00:00
Plugins are a great way to augment or change the behavior and functionality
of Vagrant. Since plugins introduce additional external dependencies for
users, they should be used as a last resort when attempting to
do something with Vagrant.
---
# Plugin Development Basics
Plugins are a great way to augment or change the behavior and functionality
of Vagrant. Since plugins introduce additional external dependencies for
users, they should be used as a last resort when attempting to
do something with Vagrant.
But if you need to introduce custom behaviors
into Vagrant, plugins are the best way, since they are safe against future
upgrades and use a stable API.
<div class="alert alert-warning">
2016-01-19 19:54:13 +00:00
<strong>Warning: Advanced Topic!</strong> Developing plugins is an
advanced topic that only experienced Vagrant users who are reasonably
comfortable with Ruby should approach.
</div>
Plugins are written using [Ruby](https://www.ruby-lang.org/en/) and are packaged
using [RubyGems](https://rubygems.org/). Familiarity with Ruby is required,
but the [packaging and distribution](/docs/plugins/packaging.html) section should help
guide you to packaging your plugin into a RubyGem.
## Setup and Workflow
Because plugins are packaged as RubyGems, Vagrant plugins should be
developed as if you were developing a regular RubyGem. The easiest
way to do this is to use the `bundle gem` command.
Once the directory structure for a RubyGem is setup, you will want
to modify your Gemfile. Here is the basic structure of a Gemfile for
Vagrant plugin development:
```ruby
source "https://rubygems.org"
group :development do
2018-03-07 22:10:30 +00:00
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 to
`bundle 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. Because
`vagrant plugin` commands do not work in development, this is how
you "install" your plugin into Vagrant. Vagrant will automatically
load any gems listed in the "plugins" group. Note that this also
allows you to add multiple plugins to Vagrant for development, if
your plugin works with another plugin.
When you want to manually test your plugin, use
`bundle exec vagrant` in order to run Vagrant with your plugin
loaded (as we specified in the Gemfile).
## Plugin Definition
All plugins are required to have a definition. A definition contains details
about the plugin such as the name of it and what components it contains.
A definition at the bare minimum looks like the following:
```ruby
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 only
promised for each major version of Vagrant, so this is important. (The
1.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, Vagrant
version 87 (does not actually exist) would be able to load a version 2 plugin
definition. This is achieved through clever lazy loading of individual components
of the plugin, and is covered shortly.
## Plugin Components
Within the definition, a plugin advertises what components it adds to
Vagrant. An example is shown below where a command and provisioner are
added:
2018-07-28 10:03:02 +00:00
```ruby
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 general
Ruby language perspective the above _should_ be familiar. The syntax should
not scare you. If it does, then please familiarize with Ruby further before
attempting to write a plugin.
The first thing to note is that individual components are defined by
making a method call with the component name, such as `command` or
`provisioner`. These in turn take some parameters. In the case of our
example it is just the name of the command and the name of the provisioner.
All component definitions then take a block argument (a callback) that
must 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 that
contains the implementation of the component, and then return that component.
This is done because the actual dependencies and APIs used when defining
components are not stable across major Vagrant versions. A command implementation
written for Vagrant 2.0 will not be compatible with Vagrant 3.0 and so on. But
the _definition_ is just plain Ruby that must always be forward compatible
to future Vagrant versions.
To repeat, **the lazy loading aspect of plugin components is critical**
to the way Vagrant plugins work. All components must be lazily loaded
and returned within their definition blocks.
Now, each component has a different API. Please visit the relevant section
using the navigation to the left under "Plugins" to learn more about developing
each type of component.
## Error Handling
One of Vagrant's biggest strength is gracefully handling errors and reporting
them in human-readable ways. Vagrant has always strongly believed that if
a user sees a stack trace, it is a bug. It is expected that plugins will behave
the same way, and Vagrant provides strong error handling mechanisms to
assist with this.
Error handling in Vagrant is done entirely by raising Ruby exceptions.
But Vagrant treats certain errors differently than others. If an error
is raised that inherits from `Vagrant::Errors::VagrantError`, then the
`vagrant` command will output the message of the error in nice red text
to the console and exit with an exit status of 1.
Otherwise, Vagrant reports an "unexpected error" that should be reported
as a bug, and shows a full stack trace and other ugliness. Any stack traces
should be considered bugs.
Therefore, to fit into Vagrant's error handling mechanisms, subclass
`VagrantError` and set a proper message on your exception. To see
2018-03-07 22:10:30 +00:00
examples of this, look at Vagrant's [built-in errors](https://github.com/hashicorp/vagrant/blob/master/lib/vagrant/errors.rb).
## 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, output
pipes are closed, there is no input pipe, etc.
A UI object is available on every `Vagrant::Environment` via the `ui` property
and is exposed within every middleware environment via the `:ui` key. UI
2018-03-07 22:10:30 +00:00
objects have [decent documentation](https://github.com/hashicorp/vagrant/blob/master/lib/vagrant/ui.rb)
within the comments of their source.