Follow me on Twitter Twitter_16
Subscribe to my RSS feed Rss_16
Posted over 8 years ago

Creating a vagrant plugin: Introducing vagrant-hiera

vagrant-hiera is a plugin for vagrant that enables simple configuration of puppet-hiera on a vagrant box.

Creating a vagrant plugin is simple once you get your head around how the config and middleware stack work and tie together. Mitchelleh has put together some great documentation to help get you started.

The purpose of this post is to extend upon his documentation a bit more and hopefully save you some time. I spent about 5-6 hours playing around, looking through Vagrant’s source, and reviewing other Vagrant plugins before I felt comfortable diving in. Hopefully this post will save you that time. I ended up using “vagrant-vbguest” as a starting template. Special thanks to dotless-de as that plugin has saved me and my co-workers a ton of frustration.

To see vagrant-hiera’s source code, check out the project on github.

Directory Structure

|-- lib
    |-- vagrant-hiera
    |   |-- config.rb
    |   |-- middleware
    |       |-- prepare.rb
    |       `-- setup.rb
    |-- vagrant-hiera.rb
    `-- vagrant_init.rb

After reviewing other vagrant plugins, I decided to follow the above directory structure, as it seemed pretty consistent across the other plugins I looked at. The lib directory, as expected, contains the ‘meat’ of the code. Inside that directory create a vagrant_init.rb file. Vagrant automatically calls this file (an any other gem containing a file of the same name) and it’s job is to load the plugin. It seemed pretty standard to have the init file simply require a .rb file and have that file actually do the loading. vagrant-hiera.rb is responsible for loading all the necessary classes and tying them into the vagrant framework.

Most of the plugins I reviewed only added one piece of middleware. Thus they had a middleware.rb file and no middleware directory. In my case I needed two additions to Vagrant’s middleware stack. The prepare.rb file is responsible for setting up the shared folders. This must happen before the VM starts. The setup.rb file is responsible for mounting the shared folders after the Vagrant box boots.

Tying it together

Mitchelleh’s documentation does a great job of explaining how to build the elements of a plugin, so I won’t go into detail about that. What I had to spend time figuring out was the structural conventions (which I reviewed in the previous section) and how to get my plugin into the Vagrant middleware stack. To get an idea of what your options are, take a look at the source of built-in.rb and “builder.rb”: https://github.com/mitchellh/vagrant/blob/master/lib/vagrant/action/builder.rb" in the vagrant source. builder.rb has all the available options for tying your middleware into the stack. The options include use (add my middleware to the end of the stack), insert (add my middleware before a given middleware in the stack), insert_after (I think this is obvious now), delete, and replace. builtin.rb contains the order of built in middleware in the default stack.

From vagrant-hiera.rb:
#Inserts my_middleware after the built in 'share_folders' middleware
Vagrant.actions[:start].insert_after Vagrant::Action::VM::ShareFolders, VagrantHiera::Middleware::Prepare 

Interacting with the VM

Take a look at driver.rb. This class has a ton of useful functions for interacting with the VM. These functions primarily interact with the VM through VBoxManage. If there’s something you would like to do that does not have a function already defined, there is also an ‘execute_command’ function that allows you to issue VBoxManage commands directly.

In most cases you’ll need to tie middleware that utilize these functions into the middleware stack before the VM boots.

From prepare.rb:
#Configure shared folders
folders = [{:name => 'vagrant-hiera-config', :hostpath => @env[:vm].config.hiera.config_path},
                {:name => 'vagrant-hiera-data', :hostpath => @env[:vm].config.hiera.data_path}]
@env[:vm].driver.share_folders(folders) 

To interact with VM after boot, review guest.rb. These methods interact with the VM via the shell/command line after boot. It also provides ‘execute’ and ‘sudo’ methods that allow commands to be sent to the VM directly.

From setup.rb:
#Create the puppet config directory
@env[:vm].channel.sudo("mkdir -p /etc/puppet") 

Other things I learned:

bundler
If you deal with ruby gems at all, your likely already familiar with bundler. It’s a fantastic tool for managing and maintaining project dependencies during development and deploys. What I didn’t know until undertaking this project is that it’s also a fantastic tool for developing, building, installing, and releasing gems. Here are some of the basic commands:

bundle gem gem-name # Creates a directory framework and necessary files for gem development
rake build # Build the gem
rake install # Build and install the gem
rake release #Push the gem to ruby gems.org

I’ve used gemcutter in the past and really liked it, but bundler has dumbed gem development down even more. Gem development really doesn’t get any simpler than this. For more information check out Ryan Bates’ screencast.

comments powered by Disqus