» Plugin Development: Configuration

This page documents how to add new configuration options to Vagrant,settable with config.YOURKEY in Vagrantfiles. Prior to reading this,you should be familiar with theplugin development basics.

Warning: Advanced Topic! Developing plugins is anadvanced topic that only experienced Vagrant users who are reasonablycomfortable with Ruby should approach.

» Definition Component

Within the context of a plugin definition, new configuration keys can be definedlike so:

  1. config "foo" do
  2. require_relative "config"
  3. Config
  4. end

Configuration keys are defined with the config method, which takes as anargument the name of the configuration variable as the argument. Thismeans that the configuration object will be accessible via config.fooin Vagrantfiles. Then, the block argument returns a class that implementsthe Vagrant.plugin(2, :config) interface.

» Implementation

Implementations of configuration keys should subclass Vagrant.plugin(2, :config),which is a Vagrant method that will return the proper subclass for a version2 configuration section. The implementation is very simple, and acts mostlyas a plain Ruby object. Here is an example:

  1. class Config < Vagrant.plugin(2, :config)
  2. attr_accessor :widgets
  3. def initialize
  4. @widgets = UNSET_VALUE
  5. end
  6. def finalize!
  7. @widgets = 0 if @widgets == UNSET_VALUE
  8. end
  9. end

When using this configuration class, it looks like the following:

  1. Vagrant.configure("2") do |config|
  2. # ...
  3. config.foo.widgets = 12
  4. end

Easy. The only odd thing is the UNSET_VALUE bits above. This is actuallyso that Vagrant can properly automatically merge multiple configurations.Merging is covered in the next section, and UNSET_VALUE will be explainedthere.

» Merging

Vagrant works by loading multiple Vagrantfiles and merging them.This merge logic is built-in to configuration classes. When merging twoconfiguration objects, we will call them "old" and "new", it'll by defaulttake all the instance variables defined on "new" that are not UNSET_VALUEand set them onto the merged result.

The reason UNSET_VALUE is used instead of Ruby's nil is becauseit is possible that you want the default to be some value, and the useractually wants to set the value to nil, and it is impossible for Vagrantto automatically determine whether the user set the instance variable, orif it was defaulted as nil.

This merge logic is what you want almost every time. Hence, in the exampleabove, @widgets is set to UNSET_VALUE. If we had two Vagrant configurationobjects in the same file, then Vagrant would properly merge the follows.The example below shows this:

  1. Vagrant.configure("2") do |config|
  2. config.widgets = 1
  3. end
  4. Vagrant.configure("2") do |config|
  5. # ... other stuff
  6. end
  7. Vagrant.configure("2") do |config|
  8. config.widgets = 2
  9. end

If this were placed in a Vagrantfile, after merging, the value of widgetswould be "2".

The finalize! method is called only once ever on the final configurationobject in order to set defaults. If finalize! is called, that configurationwill never be merged again, it is final. This lets you detect any UNSET_VALUEand set the proper default, as we do in the above example.

Of course, sometimes you want custom merge logic. Let us say wewanted our widgets to be additive. We can override the merge method todo this:

  1. class Config < Vagrant.config("2", :config)
  2. attr_accessor :widgets
  3. def initialize
  4. @widgets = 0
  5. end
  6. def merge(other)
  7. super.tap do |result|
  8. result.widgets = @widgets + other.widgets
  9. end
  10. end
  11. end

In this case, we did not use UNSET_VALUE for widgets because we did notneed that behavior. We default to 0 and always merge by summing thetwo widgets. Now, if we ran the example above that had the 3 configurationblocks, the final value of widgets would be "3".

» Validation

Configuration classes are also responsible for validating their ownvalues. Vagrant will call the validate method to do this. An examplevalidation method is shown below:

  1. class Config < Vagrant.plugin("2", :config)
  2. # ...
  3. def validate(machine)
  4. errors = _detected_errors
  5. if @widgets <= 5
  6. errors << "widgets must be greater than 5"
  7. end
  8. { "foo" => errors }
  9. end
  10. end

The validation method is given a machine object, since validation isdone for each machine that Vagrant is managing. This allows you toconditionally validate some keys based on the state of the machine and so on.

The _detected_errors method returns any errors already detected by Vagrant,such as unknown configuration keys. This returns an array of error messages,so be sure to turn it into the proper Hash object to return later.

The return value is a Ruby Hash object, where the key is a section name,and the value is a list of error messages. These will be displayed byVagrant. The hash must not contain any values if there are no errors.

» Accessing

After all the configuration options are merged and finalized, you will likelywant to access the finalized value in your plugin. The initializer functionvaries with each type of plugin, but most plugins expose an initializer likethis:

  1. def initialize(machine, config)
  2. @machine = machine
  3. @config = config
  4. end

When authoring a plugin, simply call super in your initialize function tosetup these instance variables:

  1. def initialize(*)
  2. super
  3. @config.is_now_available
  4. # ...existing code
  5. end
  6. def my_helper
  7. @config.is_here_too
  8. end

For examples, take a look at Vagrant's own internal plugins in the pluginsfolder in Vagrant's source on GitHub.