[DRE-commits] [vagrant-mutate] 01/04: Imported Upstream version 1.2.0

Hans-Christoph Steiner eighthave at moszumanska.debian.org
Wed Sep 21 20:09:54 UTC 2016


This is an automated email from the git hooks/post-receive script.

eighthave pushed a commit to branch master
in repository vagrant-mutate.

commit 214b45eced5f822f34ba66fef6d9131b0983df47
Author: Hans-Christoph Steiner <hans at eds.org>
Date:   Mon Sep 5 17:38:21 2016 +0200

    Imported Upstream version 1.2.0
---
 .gitignore                                         |  18 ++
 CHANGELOG.md                                       |  93 +++++++
 Gemfile                                            |  15 +
 LICENSE                                            |  22 ++
 README.md                                          | 104 +++++++
 Rakefile                                           |   1 +
 lib/vagrant-mutate.rb                              |  37 +++
 lib/vagrant-mutate/box/bhyve.rb                    |  19 ++
 lib/vagrant-mutate/box/box.rb                      |  40 +++
 lib/vagrant-mutate/box/kvm.rb                      |  31 +++
 lib/vagrant-mutate/box/libvirt.rb                  |  45 +++
 lib/vagrant-mutate/box/virtualbox.rb               | 158 +++++++++++
 lib/vagrant-mutate/box_loader.rb                   | 301 +++++++++++++++++++++
 lib/vagrant-mutate/converter/bhyve.rb              |  40 +++
 lib/vagrant-mutate/converter/converter.rb          | 116 ++++++++
 lib/vagrant-mutate/converter/kvm.rb                |  71 +++++
 lib/vagrant-mutate/converter/libvirt.rb            |  33 +++
 lib/vagrant-mutate/errors.rb                       |  85 ++++++
 lib/vagrant-mutate/mutate.rb                       |  57 ++++
 lib/vagrant-mutate/qemu.rb                         |  36 +++
 lib/vagrant-mutate/version.rb                      |   3 +
 locales/en.yml                                     |  50 ++++
 templates/kvm/box.xml.erb                          |  72 +++++
 test/expected_output/kvm/libvirt/Vagrantfile       |   5 +
 test/expected_output/kvm/libvirt/box.img           | Bin 0 -> 197120 bytes
 test/expected_output/kvm/libvirt/metadata.json     |   1 +
 test/expected_output/libvirt/kvm/Vagrantfile       |   3 +
 test/expected_output/libvirt/kvm/box-disk1.img     | Bin 0 -> 197120 bytes
 test/expected_output/libvirt/kvm/box.xml           |  68 +++++
 test/expected_output/libvirt/kvm/metadata.json     |   1 +
 test/expected_output/virtualbox/bhyve/Vagrantfile  |   6 +
 test/expected_output/virtualbox/bhyve/disk.img     | Bin 0 -> 4194304 bytes
 .../expected_output/virtualbox/bhyve/metadata.json |   1 +
 test/expected_output/virtualbox/kvm/Vagrantfile    |   3 +
 test/expected_output/virtualbox/kvm/box-disk1.img  | Bin 0 -> 197120 bytes
 test/expected_output/virtualbox/kvm/box.xml        |  68 +++++
 test/expected_output/virtualbox/kvm/metadata.json  |   1 +
 .../expected_output/virtualbox/libvirt/Vagrantfile |   5 +
 test/expected_output/virtualbox/libvirt/box.img    | Bin 0 -> 197120 bytes
 .../virtualbox/libvirt/metadata.json               |   1 +
 test/input/kvm/mutate-test.box                     | Bin 0 -> 1575 bytes
 test/input/libvirt/mutate-test.box                 | Bin 0 -> 828 bytes
 test/input/virtualbox/mutate-test.box              | Bin 0 -> 3667 bytes
 test/test.rb                                       | 101 +++++++
 vagrant-mutate.gemspec                             |  23 ++
 45 files changed, 1734 insertions(+)

diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..b557e05
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,18 @@
+*.gem
+*.rbc
+.bundle
+.config
+.yardoc
+Gemfile.lock
+InstalledFiles
+_yardoc
+coverage
+doc/
+lib/bundler/man
+pkg
+rdoc
+spec/reports
+test/actual_output
+test/tmp
+test/version_tmp
+tmp
diff --git a/CHANGELOG.md b/CHANGELOG.md
new file mode 100644
index 0000000..6f2e702
--- /dev/null
+++ b/CHANGELOG.md
@@ -0,0 +1,93 @@
+# 1.2.0 (2016-08-15)
+* Add support for converting to bhyve (#86)
+
+# 1.1.1 (2016-02-24)
+* Add option to force use of virtio for disks (#82)
+
+# 1.0.4 (2016-01-01)
+* Support spaces in box names (#79)
+
+# 1.0.3 (2015-10-15)
+* Fix compatibility with qemu version in EL6 (#76)
+
+# 1.0.2 (2015-09-06)
+* Allow period in box name when loading from URL (#75)
+
+# 1.0.1 (2015-08-16)
+* Throw error during version search when missing box (#72)
+
+# 1.0.0 (2015-05-20)
+* Support versioned boxes (#51)
+* Support boxes with slash in name (#52)
+* Rename input provider option (#68)
+* Allow hyphen in box name when loading from url (#70)
+
+# 0.3.2 (2015-03-03)
+* Specify qcow compat instead of using default (#59)
+* Provide clearer error when qemu-img missing (#62)
+
+# 0.3.1 (2014-08-17)
+* Improve compatibility with newer qemu-img releases
+
+# 0.3.0 (2014-05-04)
+* Support Vagrant 1.5 with unversioned boxes (#47)
+* Drop support for Vagrant < 1.5
+* Move input provider specifier into own option
+* Warn if user provided identifier for Vagrant Cloud
+
+# 0.2.6 (2014-05-04)
+* Include CentOS 6.5/RHEL 6.5-friendly Qemu paths (#50)
+
+# 0.2.5 (2014-02-01)
+* Fix pci id for drives in kvm (#39)
+
+# 0.2.4 (2014-01-23)
+* Generate new vagrantfiles instead of copying them
+* Set disk bus when converting to vagrant-libvirt (#41)
+
+# 0.2.3 (2014-01-20)
+* Warn when qemu version cannot read vmdk3 files (#29)
+* Fix errors in how box name and provider were parsed (#35)
+* Load box from file based on existence not name (#36)
+* Warn when image is not the expected type for the provider (#38)
+
+# 0.2.2 (2014-01-05)
+* Determine virtualbox disk filename from ovf (#30)
+* Move Qemu checks to own class
+
+# 0.2.1 (2014-01-02)
+* Support kvm as input (#17)
+
+# 0.2.0 (2014-01-02)
+* Fix how box is loaded by name (#19)
+* Quit if input and output provider are the same (#27)
+* Support libvirt as input (#18)
+
+# 0.1.5 (2013-12-17)
+* Preserve dsik interface type when coverting to KVM (#21)
+* Remove dependency in minitar (#24)
+* Support downloading input box (#9)
+* Handle errors when reading ovf file
+
+# 0.1.4 (2013-12-08)
+* Rework box and converter implementation (#7)
+* Write disk images as sparse files (#13)
+* Switch vagrant-kvm disk format from raw to qcow2 (#16)
+* Prefer the binary named qemu-system-* over qemu-kvm or kvm (#20)
+
+# 0.1.3 (2013-12-03)
+
+* Add support for vagrant-kvm (#12)
+* Add acceptance tests
+
+# 0.1.2 (2013-11-20)
+
+* Rework provider and converter implementation (#7)
+
+# 0.1.1 (2013-11-12)
+
+* Fix handling of fractional virtual disk sizes (#11)
+
+# 0.1.0 (2013-11-02)
+
+* Initial release
diff --git a/Gemfile b/Gemfile
new file mode 100644
index 0000000..4b3c0a8
--- /dev/null
+++ b/Gemfile
@@ -0,0 +1,15 @@
+source 'https://rubygems.org'
+
+gemspec
+
+group :development do
+  # We depend on Vagrant for development, but we don't add it as a
+  # gem dependency because we expect to be installed within the
+  # Vagrant environment itself using `vagrant plugin`.
+  gem "vagrant", git: "https://github.com/mitchellh/vagrant.git"
+end
+
+group :plugins do
+  gem "vagrant-mutate", path: "."
+end
+
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..153691f
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,22 @@
+Copyright (c) 2013 Brian Pitts
+
+MIT License
+
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of this software and associated documentation files (the
+"Software"), to deal in the Software without restriction, including
+without limitation the rights to use, copy, modify, merge, publish,
+distribute, sublicense, and/or sell copies of the Software, and to
+permit persons to whom the Software is furnished to do so, subject to
+the following conditions:
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..21c6862
--- /dev/null
+++ b/README.md
@@ -0,0 +1,104 @@
+# Vagrant-Mutate
+
+Vagrant-mutate is a vagrant plugin to convert vagrant boxes to work with different providers.
+
+## Supported Conversions
+
+* Virtualbox to kvm
+* Virtualbox to libvirt
+* Virtualbox to bhyve
+* Libvirt to kvm
+* Kvm to libvirt
+
+## Compatibility
+
+Vagrant-mutate 0.3 and later requires Vagrant 1.5. If you are using an older vagrant, install vagrant-mutate version 0.2.6.
+
+## Installation
+
+### qemu-img and libvirt development
+
+First, you must install [qemu-img](http://wiki.qemu.org/Main_Page) and the libvirt development libraries. Information on supported versions is listed at [QEMU Version Compatibility](https://github.com/sciurus/vagrant-mutate/wiki/QEMU-Version-Compatibility).
+
+#### Debian and derivatives
+
+    apt-get install qemu-utils libvirt-dev ruby-dev
+
+#### Red Hat and derivatives
+
+    yum install qemu-img libvirt-devel ruby-libvirt ruby-devel
+
+#### OS X
+
+QEMU and libvirt are available from [homebrew](http://brew.sh/)
+
+#### Windows
+
+Download and install it from [Stefan Weil](http://qemu.weilnetz.de/) or compile it yourself.
+
+### vagrant-mutate
+
+Now you're ready to install vagrant-mutate. To install the latest released version simply run
+
+    vagrant plugin install vagrant-mutate
+
+To install from source, clone the repository and run `rake build`. That will produce a gem file in the _pkg_ directory which you can then install with `vagrant plugin install`.
+
+## Usage
+
+The basic usage is
+
+    vagrant mutate box-name-or-file-or-url output-provider
+
+For example, if you wanted to download a box created for virtualbox and add it to vagrant for libvirt
+
+    vagrant mutate http://files.vagrantup.com/precise32.box libvirt
+
+Or if you had already downloaded it
+
+    vagrant mutate precise32.box libvirt
+
+Or if you had already added the box to vagrant and now want to use it with libvirt
+
+    vagrant mutate precise32 libvirt
+
+The latter syntax works for boxes you added from Vagrant Cloud or Atlas too. If you have installed multiple versions of these boxes, vagrant-mutate will always use the latest one.
+
+    $ vagrant box list
+    hashicorp/precise64  (virtualbox, 1.1.0)
+    $ vagrant mutate hashicorp/precise32 libvirt
+
+If you have a box for multiple providers, you must specify the provider to use for input using the *--input-provider* option, e.g.
+
+    $ vagrant box list
+    precise32  (kvm)
+    precise32  (virtualbox)
+    $ vagrant mutate --input_provider=virtualbox precise32 libvirt
+
+To export a box you created with vagrant mutate, just repackage it, e.g.
+
+    vagrant box repackage precise32 libvirt
+
+If you want to force the output box to use virtio for the disk interface, no matter what interface the input box used, use the *--force-virtio* option.
+
+
+## Debugging
+
+vagrant and vagrant-mutate will output lots of information as they run if you set the VAGRANT_LOG environment variable to INFO. See [here](http://docs-v1.vagrantup.com/v1/docs/debugging.html) for information on how to do that on your operating system.
+
+If you experience any problems, please open an issue on [github](https://github.com/sciurus/vagrant-mutate/issues).
+
+## Contributing
+
+Contributions are welcome! I'd especially like to see support for converting between more providers added.
+
+To contribute, follow the standard flow of
+
+1. Fork it
+1. Create your feature branch (`git checkout -b my-new-feature`)
+1. Commit your changes (`git commit -am 'Add some feature'`)
+1. Make sure the acceptance tests pass (`ruby test/test.rb`)
+1. Push to the branch (`git push origin my-new-feature`)
+1. Create new Pull Request
+
+Even if you can't contribute code, if you have an idea for an improvement please open an [issue](https://github.com/sciurus/vagrant-mutate/issues).
diff --git a/Rakefile b/Rakefile
new file mode 100644
index 0000000..2995527
--- /dev/null
+++ b/Rakefile
@@ -0,0 +1 @@
+require "bundler/gem_tasks"
diff --git a/lib/vagrant-mutate.rb b/lib/vagrant-mutate.rb
new file mode 100644
index 0000000..d348f5d
--- /dev/null
+++ b/lib/vagrant-mutate.rb
@@ -0,0 +1,37 @@
+require 'vagrant-mutate/version'
+require 'vagrant-mutate/errors'
+
+module VagrantMutate
+  def self.source_root
+    @source_root ||= Pathname.new(File.expand_path('../../', __FILE__))
+  end
+
+  # http://stackoverflow.com/questions/2108727/which-in-ruby-checking-if-program-exists-in-path-from-ruby
+  def self.find_bin(bin)
+    exts = ENV['PATHEXT'] ? ENV['PATHEXT'].split(';') : ['']
+    ENV['PATH'].split(File::PATH_SEPARATOR).each do |path|
+      exts.each do |ext|
+        exe = File.join(path, "#{bin}#{ext}")
+        if File.executable? exe
+          return exe if File.executable?(exe) && !File.directory?(exe)
+        end
+      end
+    end
+    return nil
+  end
+
+  class Plugin < Vagrant.plugin('2')
+    name 'vagrant-mutate'
+
+    command 'mutate' do
+      setup_i18n
+      require 'vagrant-mutate/mutate'
+      Mutate
+    end
+
+    def self.setup_i18n
+      I18n.load_path << File.expand_path('locales/en.yml', VagrantMutate.source_root)
+      I18n.reload!
+    end
+  end
+end
diff --git a/lib/vagrant-mutate/box/bhyve.rb b/lib/vagrant-mutate/box/bhyve.rb
new file mode 100644
index 0000000..736fd27
--- /dev/null
+++ b/lib/vagrant-mutate/box/bhyve.rb
@@ -0,0 +1,19 @@
+require_relative 'box'
+
+module VagrantMutate
+  module Box
+    class Bhyve < Box
+      def initialize(env, name, version, dir)
+        super
+        @provider_name    = 'bhyve'
+        @supported_input  = true
+        @supported_output = true
+        @image_format     = 'raw'
+        @image_name       = 'disk.img'
+      end
+
+      # TODO
+
+    end
+  end
+end
diff --git a/lib/vagrant-mutate/box/box.rb b/lib/vagrant-mutate/box/box.rb
new file mode 100644
index 0000000..62f8fce
--- /dev/null
+++ b/lib/vagrant-mutate/box/box.rb
@@ -0,0 +1,40 @@
+require 'shellwords'
+
+module VagrantMutate
+  module Box
+    class Box
+      attr_reader :name, :dir, :version, :provider_name, :supported_input, :supported_output, :image_format, :image_name
+
+      def initialize(env, name, version, dir)
+        @env     = env
+        @name    = name
+        @dir     = dir
+        @version = version
+        @logger  = Log4r::Logger.new('vagrant::mutate')
+      end
+
+      def virtual_size
+        extract_from_qemu_info(/(\d+) bytes/)
+      end
+
+      def verify_format
+        format_found = extract_from_qemu_info(/file format: (\w+)/)
+        unless format_found == @image_format
+          @env.ui.warn "Expected input image format to be #{@image_format} but "\
+            "it is #{format_found}. Attempting conversion anyway."
+        end
+      end
+
+      def extract_from_qemu_info(expression)
+        input_file = File.join(@dir, image_name).shellescape
+        info = `qemu-img info #{input_file}`
+        @logger.debug "qemu-img info output\n#{info}"
+        if info =~ expression
+          return Regexp.last_match[1]
+        else
+          fail Errors::QemuInfoFailed
+        end
+      end
+    end
+  end
+end
diff --git a/lib/vagrant-mutate/box/kvm.rb b/lib/vagrant-mutate/box/kvm.rb
new file mode 100644
index 0000000..d0abd30
--- /dev/null
+++ b/lib/vagrant-mutate/box/kvm.rb
@@ -0,0 +1,31 @@
+require_relative 'box'
+require 'nokogiri'
+
+module VagrantMutate
+  module Box
+    class Kvm < Box
+      def initialize(env, name, version, dir)
+        super
+        @provider_name    = 'kvm'
+        @supported_input  = true
+        @supported_output = true
+        @image_format     = 'qcow2'
+        @image_name       = 'box-disk1.img'
+      end
+
+      # TODO: implement these methods
+      #  architecture, mac_address, cpus, memory
+      #  to support converting to providers besides libvirt
+
+      def disk_interface
+        domain_file = File.join(@dir, 'box.xml')
+        begin
+          domain = File.open(domain_file) { |f| Nokogiri::XML(f) }
+          domain.xpath("//*[local-name()='domain']/*[local-name()='devices']/*[local-name()='disk']/*[local-name()='target']").attribute('bus')
+        rescue => e
+          raise Errors::BoxAttributeError, error_message: e.message
+        end
+      end
+    end
+  end
+end
diff --git a/lib/vagrant-mutate/box/libvirt.rb b/lib/vagrant-mutate/box/libvirt.rb
new file mode 100644
index 0000000..9932322
--- /dev/null
+++ b/lib/vagrant-mutate/box/libvirt.rb
@@ -0,0 +1,45 @@
+require_relative 'box'
+
+module VagrantMutate
+  module Box
+    class Libvirt < Box
+      def initialize(env, name, version, dir)
+        super
+        @provider_name    = 'libvirt'
+        @supported_input  = true
+        @supported_output = true
+        @image_format     = 'qcow2'
+        @image_name       = 'box.img'
+        @mac              = nil
+      end
+
+      # since none of below can be determined from the box
+      # we just generate sane values
+
+      def architecture
+        'x86_64'
+      end
+
+      # kvm prefix is 52:54:00
+      def mac_address
+        unless @mac
+          octets = 3.times.map { rand(255).to_s(16) }
+          @mac = "525400#{octets[0]}#{octets[1]}#{octets[2]}"
+        end
+        @mac
+      end
+
+      def cpus
+        1
+      end
+
+      def memory
+        536_870_912
+      end
+
+      def disk_interface
+        'virtio'
+      end
+    end
+  end
+end
diff --git a/lib/vagrant-mutate/box/virtualbox.rb b/lib/vagrant-mutate/box/virtualbox.rb
new file mode 100644
index 0000000..5a34ec9
--- /dev/null
+++ b/lib/vagrant-mutate/box/virtualbox.rb
@@ -0,0 +1,158 @@
+require 'rexml/document'
+require_relative 'box'
+
+module VagrantMutate
+  module Box
+    class Virtualbox < Box
+      def initialize(env, name, version, dir)
+        super
+        @provider_name    = 'virtualbox'
+        @supported_input  = true
+        @supported_output = false
+        @image_format     = 'vmdk'
+      end
+
+      # this is usually box-disk1.vmdk but some tools like packer customize it
+      def image_name
+        ovf.elements['//References/File'].attributes['ovf:href']
+      end
+
+      # the architecture is not defined in the ovf file,
+      # we could try to guess from OSType
+      # (https://www.virtualbox.org/browser/vbox/trunk/src/VBox/Main/include/ovfreader.h)
+      # but if that is not set correctly we risk a 64-bit box not booting
+      # because we try to run in 32-bit vm.
+      # in contrast, running 32-bit box in a 64-bit vm should work.
+      def architecture
+        'x86_64'
+      end
+
+      # use mac from the first enabled nic
+      def mac_address
+        mac = nil
+
+        ovf.elements.each('//vbox:Machine/Hardware//Adapter') do |ele|
+          if ele.attributes['enabled'] == 'true'
+            mac = ele.attributes['MACAddress']
+            break
+          end
+        end
+
+        if mac
+          return mac
+        else
+          fail Errors::BoxAttributeError, error_message: 'Could not determine mac address'
+        end
+      end
+
+      def cpus
+        cpu_count = nil
+
+        ovf.elements.each('//VirtualHardwareSection/Item') do |device|
+          if device.elements['rasd:ResourceType'].text == '3'
+            cpu_count = device.elements['rasd:VirtualQuantity'].text
+          end
+        end
+
+        if cpu_count
+          return cpu_count
+        else
+          fail Errors::BoxAttributeError, error_message: 'Could not determine number of CPUs'
+        end
+      end
+
+      def memory
+        memory_in_bytes = nil
+
+        ovf.elements.each('//VirtualHardwareSection/Item') do |device|
+          if device.elements['rasd:ResourceType'].text == '4'
+            memory_in_bytes = size_in_bytes(device.elements['rasd:VirtualQuantity'].text,
+                                            device.elements['rasd:AllocationUnits'].text)
+          end
+        end
+
+        if memory_in_bytes
+          return memory_in_bytes
+        else
+          fail Errors::BoxAttributeError, error_message: 'Could not determine amount of memory'
+        end
+      end
+
+      def disk_interface
+        controller_type = {}
+        controller_used_by_disk = nil
+        ovf.elements.each('//VirtualHardwareSection/Item') do |device|
+          # when we find a controller, store its ID and type
+          # when we find a disk, store the ID of the controller it is connected to
+          case device.elements['rasd:ResourceType'].text
+          when '5'
+            controller_type[device.elements['rasd:InstanceID'].text] = 'ide'
+           when '6'
+             controller_type[device.elements['rasd:InstanceID'].text] = 'scsi'
+           when '20'
+             controller_type[device.elements['rasd:InstanceID'].text] = 'sata'
+           when '17'
+             controller_used_by_disk = device.elements['rasd:Parent'].text
+          end
+        end
+        if controller_used_by_disk and controller_type[controller_used_by_disk]
+          return controller_type[controller_used_by_disk]
+        else
+          fail Errors::BoxAttributeError, error_message: 'Could not determine disk interface'
+        end
+      end
+
+      private
+
+      def ovf
+        if @ovf
+          return @ovf
+        end
+
+        ovf_file = File.join(@dir, 'box.ovf')
+        begin
+          @ovf = REXML::Document.new(File.read(ovf_file))
+        rescue => e
+          raise Errors::BoxAttributeError, error_message: e.message
+        end
+      end
+
+      # Takes a quantity and a unit
+      # returns quantity in bytes
+      # mib = true to use mebibytes, etc
+      # defaults to false because ovf MB != megabytes
+      def size_in_bytes(qty, unit, mib = false)
+        qty = qty.to_i
+        unit = unit.downcase
+        unless mib
+          case unit
+          when 'kb', 'kilobytes'
+            unit = 'kib'
+          when 'mb', 'megabytes'
+            unit = 'mib'
+          when 'gb', 'gigabytes'
+            unit = 'gib'
+          end
+        end
+        case unit
+        when 'b', 'bytes'
+          qty
+        when 'kb', 'kilobytes'
+          (qty * 1000)
+        when 'kib', 'kibibytes'
+          (qty * 1024)
+        when 'mb', 'megabytes'
+          (qty * 1_000_000)
+        when 'm', 'mib', 'mebibytes'
+          (qty * 1_048_576)
+        when 'gb', 'gigabytes'
+          (qty * 1_000_000_000)
+        when 'g', 'gib', 'gibibytes'
+          (qty * 1_073_741_824)
+        else
+          fail ArgumentError, "Unknown unit #{unit}"
+        end
+      end
+    end
+  end
+end
diff --git a/lib/vagrant-mutate/box_loader.rb b/lib/vagrant-mutate/box_loader.rb
new file mode 100644
index 0000000..62fe532
--- /dev/null
+++ b/lib/vagrant-mutate/box_loader.rb
@@ -0,0 +1,301 @@
+require 'fileutils'
+require 'json'
+require 'uri'
+require 'vagrant/util/subprocess'
+require 'vagrant/util/downloader'
+require 'vagrant/box'
+require 'vagrant/box_metadata'
+
+module VagrantMutate
+  class BoxLoader
+    def initialize(env)
+      @env = env
+      @logger = Log4r::Logger.new('vagrant::mutate')
+      @tmp_files = []
+    end
+
+    def prepare_for_output(name, provider_name, version)
+      @logger.info "Preparing #{name} for output as #{provider_name} with version #{version}."
+      safe_name = sanitize_name(name)
+      dir = create_output_dir(safe_name, provider_name, version)
+      box = create_box(provider_name, name, version, dir)
+
+      if box.supported_output
+        return box
+      else
+        fail Errors::ProviderNotSupported, provider: provider_name, direction: 'output'
+      end
+    end
+
+    def load(box_arg, provider_name, input_version)
+      if box_arg =~ /:\/\//
+        box = load_from_url(box_arg)
+      elsif File.file?(box_arg)
+        box = load_from_file(box_arg)
+      else
+        box = load_from_boxes_path(box_arg, provider_name, input_version)
+      end
+
+      if box.supported_input
+        return box
+      else
+        fail Errors::ProviderNotSupported, provider: box.provider_name, direction: 'input'
+      end
+    end
+
+    def load_from_url(url)
+      @logger.info "Loading box from url #{url}"
+
+      # test that we have a valid url
+      url = URI(url)
+      unless url.scheme and url.host and url.path
+        fail Errors::URLError, url: url
+      end
+
+      # extract the name of the box from the url
+      # if it ends in .box remove that extension
+      # if not just remove leading slash
+      name = nil
+      if url.path =~ /([-.\w]+).box$/
+        name = Regexp.last_match[1]
+      else
+        name = url.path.sub(/^\//, '')
+      end
+      if name.empty?
+        fail Errors::URLError, url: url
+      end
+
+      # Extract the version of the box from the URL
+      if url.path =~ /\/([\d.]+)\//
+        version = Regexp.last_match[1]
+        @logger.info "Pulled version from URL (#{version})"
+      else
+        version = '0'
+        @logger.info "No version found in URL, assuming '0'"
+      end
+
+      # using same path as in vagrants box add action
+      download_path = File.join(@env.tmp_path, 'box' + Digest::SHA1.hexdigest(url.to_s))
+      @tmp_files << download_path
+
+      # if this fails it will raise an error and we'll quit
+      @env.ui.info "Downloading box #{name} from #{url}"
+      downloader = Vagrant::Util::Downloader.new(url, download_path,  ui: @env.ui)
+      downloader.download!
+
+      dir = unpack(download_path)
+      provider_name = determine_provider(dir)
+
+      create_box(provider_name, name, version, dir)
+    end
+
+    def load_from_file(file)
+      @logger.info "Loading box from file #{file}"
+      name = File.basename(file, File.extname(file))
+
+      dir = unpack(file)
+      provider_name = determine_provider(dir)
+      version = determine_version(dir)
+
+      create_box(provider_name, name, version, dir)
+    end
+
+    def load_from_boxes_path(name, provider_name, input_version)
+      @logger.info "Loading box #{name} from vagrants box path using provider #{provider_name} and version #{input_version}."
+      safe_name = sanitize_name(name)
+      if provider_name
+        @logger.info "Checking directory for provider #{provider_name}."
+        if input_version
+          @logger.info 'Input version provided, using it.'
+          version = input_version
+        else
+          @logger.info 'No version provided, getting it.'
+          version = get_version(safe_name)
+          @logger.info "Version = #{version}"
+        end
+        dir = verify_input_dir(provider_name, safe_name, version)
+      else
+        @logger.info 'Working out provider, version and directory...'
+        provider_name, version, dir = find_input_dir(safe_name)
+      end
+      @logger.info "Creating #{name} box using provider #{provider_name} with version #{version} in #{dir}."
+      create_box(provider_name, name, version, dir)
+    end
+
+    def cleanup
+      unless @tmp_files.empty?
+        @env.ui.info 'Cleaning up temporary files.'
+        @tmp_files.each do |f|
+          @logger.info "Deleting #{f}"
+          FileUtils.remove_entry_secure(f)
+        end
+      end
+    end
+
+    private
+
+    def create_box(provider_name, name, version, dir)
+      @logger.info "Creating box #{name} with provider #{provider_name} and version #{version} in #{dir}"
+      case provider_name
+      when 'bhyve'
+        require_relative 'box/bhyve'
+        Box::Bhyve.new(@env, name, version, dir)
+      when 'kvm'
+        require_relative 'box/kvm'
+        Box::Kvm.new(@env, name, version, dir)
+      when 'libvirt'
+        require_relative 'box/libvirt'
+        Box::Libvirt.new(@env, name, version, dir)
+      when 'virtualbox'
+        require_relative 'box/virtualbox'
+        Box::Virtualbox.new(@env, name, version, dir)
+      else
+        fail Errors::ProviderNotSupported, provider: provider_name, direction: 'input or output'
+      end
+    end
+
+    def create_output_dir(name, provider_name, version)
+      # e.g. $HOME/.vagrant.d/boxes/fedora-19/0/libvirt
+      @logger.info "Attempting to create output dir for #{name} with version #{version} and provider #{provider_name}."
+      out_dir = File.join(@env.boxes_path, name, version, provider_name)
+      @logger.info "Creating out_dir #{out_dir}."
+      begin
+        FileUtils.mkdir_p(out_dir)
+      rescue => e
+        raise Errors::CreateBoxDirFailed, error_message: e.message
+      end
+      @logger.info "Created output directory #{out_dir}"
+      out_dir
+    end
+
+    def unpack(file)
+      @env.ui.info 'Extracting box file to a temporary directory.'
+      unless File.exist? file
+        fail Errors::BoxNotFound, box: file
+      end
+      tmp_dir = Dir.mktmpdir(nil, @env.tmp_path)
+      @tmp_files << tmp_dir
+      result = Vagrant::Util::Subprocess.execute(
+       'bsdtar', '-v', '-x', '-m', '-C', tmp_dir.to_s, '-f', file)
+      if result.exit_code != 0
+        fail Errors::ExtractBoxFailed, error_message: result.stderr.to_s
+      end
+      @logger.info "Unpacked box to #{tmp_dir}"
+      tmp_dir
+    end
+
+    def determine_provider(dir)
+      metadata_file = File.join(dir, 'metadata.json')
+      if File.exist? metadata_file
+        begin
+          metadata = JSON.load(File.new(metadata_file, 'r'))
+        rescue => e
+          raise Errors::LoadMetadataFailed, error_message: e.message
+        end
+        @logger.info "Determined input provider is #{metadata['provider']}"
+        return metadata['provider']
+      else
+        @logger.info 'No metadata found, so assuming input provider is virtualbox'
+        return 'virtualbox'
+      end
+    end
+
+    def determine_version(dir)
+      metadata_file = File.join(dir, 'metadata.json')
+      if File.exist? metadata_file
+        begin
+          metadata = JSON.load(File.new(metadata_file, 'r'))
+        rescue => e
+          raise Errors::LoadMetadataFailed, error_message: e.message
+        end
+        # Handle single or multiple versions
+        if metadata['versions'].nil?
+          @logger.info 'No versions provided by metadata, asuming version 0'
+          version = '0'
+        elsif metadata['versions'].length > 1
+          metadata['versions'].each do |metadata_version|
+            @logger.info 'Itterating available metadata versions for active version.'
+            next unless metadata_version['status'] == 'active'
+            version = metadata_version['version']
+          end
+        else
+          @logger.info 'Only one metadata version, grabbing version.'
+          version = metadata['versions'][0]['version']
+        end
+        @logger.info "Determined input version is #{version}"
+        return version
+      else
+        @logger.info 'No metadata found, so assuming version is 0'
+        return '0'
+      end
+    end
+
+    def verify_input_dir(provider_name, name, version)
+      input_dir = File.join(@env.boxes_path, name, version, provider_name)
+      if File.directory?(input_dir)
+        @logger.info "Found input directory #{input_dir}"
+        return input_dir
+      else
+        fail Errors::BoxNotFound, box: input_dir
+      end
+    end
+
+    def find_input_dir(name)
+      @logger.info "Looking for input dir for box #{name}."
+      version = get_version(name)
+      box_parent_dir = File.join(@env.boxes_path, name, version)
+
+      if Dir.exist?(box_parent_dir)
+        providers = Dir.entries(box_parent_dir).reject { |entry| entry =~ /^\./ }
+        @logger.info "Found potential providers #{providers}"
+      else
+        providers = []
+      end
+
+      case
+      when providers.length < 1
+        fail Errors::BoxNotFound, box: name
+      when providers.length > 1
+        fail Errors::TooManyBoxesFound, box: name
+      else
+        provider_name = providers.first
+        input_dir = File.join(box_parent_dir, provider_name)
+        @logger.info "Found source for box #{name} from provider #{provider_name} with version #{version} at #{input_dir}"
+        return provider_name, version, input_dir
+      end
+    end
+
+    def get_version(name)
+      # Get a list of directories for this box
+      @logger.info "Getting versions for #{name}."
+
+      box_dir = File.join(@env.boxes_path, name, '*')
+      possible_versions = Dir.glob(box_dir).select { |f| File.directory? f }.map { |x| x.split('/').last }
+
+      @logger.info "Possible_versions = #{possible_versions.inspect}"
+
+      if possible_versions.length > 1
+        @logger.info 'Got multiple possible versions, selecting max value'
+        version = possible_versions.max
+      elsif possible_versions.length == 1
+        @logger.info 'Got a single version, so returning it'
+        version = possible_versions.first
+      else
+        fail Errors::BoxNotFound, box: name
+      end
+
+      @logger.info "Found version #{version}"
+      version
+    end
+
+    def sanitize_name(name)
+      if name =~ /\//
+        @logger.info 'Replacing / with -VAGRANTSLASH-.'
+        name = name.dup
+        name.gsub!('/', '-VAGRANTSLASH-')
+        @logger.info "New name = #{name}."
+      end
+      name
+    end
+  end
+end
diff --git a/lib/vagrant-mutate/converter/bhyve.rb b/lib/vagrant-mutate/converter/bhyve.rb
new file mode 100644
index 0000000..18f0fc5
--- /dev/null
+++ b/lib/vagrant-mutate/converter/bhyve.rb
@@ -0,0 +1,40 @@
+require 'erb'
+
+module VagrantMutate
+  module Converter
+    class Bhyve < Converter
+      def generate_metadata
+
+        output_name = File.join(@output_box.dir, @output_box.image_name).shellescape
+
+        file_output = `file #{output_name}`
+
+        if file_output.include? "GRUB"
+         loader = "grub-bhyve"
+        else
+         loader = "bhyveload"
+        end
+
+        {
+          "provider" => @output_box.provider_name,
+        }
+      end
+
+      def generate_vagrantfile
+        memory = @input_box.memory / 1024 / 1024 # convert bytes to mebibytes
+        cpus = @input_box.cpus
+        <<-EOF
+        config.vm.provider :bhyve do |vm|
+          vm.memory = "#{memory}M"
+          vm.cpus = "#{cpus}"
+        end
+        EOF
+      end
+
+      def write_specific_files
+        # nothing yet
+      end
+
+    end
+  end
+end
diff --git a/lib/vagrant-mutate/converter/converter.rb b/lib/vagrant-mutate/converter/converter.rb
new file mode 100644
index 0000000..f137491
--- /dev/null
+++ b/lib/vagrant-mutate/converter/converter.rb
@@ -0,0 +1,116 @@
+require 'fileutils'
+require 'shellwords'
+
+module VagrantMutate
+  module Converter
+    class Converter
+      def self.create(env, input_box, output_box, force_virtio='false')
+      case output_box.provider_name
+        when 'bhyve'
+          require_relative 'bhyve'
+          Bhyve.new(env, input_box, output_box)
+        when 'kvm'
+          require_relative 'kvm'
+          Kvm.new(env, input_box, output_box)
+        when 'libvirt'
+          require_relative 'libvirt'
+          Libvirt.new(env, input_box, output_box, force_virtio)
+        else
+          fail Errors::ProviderNotSupported, provider: output_box.provider_name, direction: 'output'
+        end
+      end
+
+      def initialize(env, input_box, output_box, force_virtio='false')
+        @env = env
+        @input_box  = input_box
+        @output_box = output_box
+        @force_virtio = force_virtio
+        @logger = Log4r::Logger.new('vagrant::mutate')
+      end
+
+      def convert
+        if @input_box.provider_name == @output_box.provider_name
+          fail Errors::ProvidersMatch
+        end
+
+        @env.ui.info "Converting #{@input_box.name} from #{@input_box.provider_name} "\
+          "to #{@output_box.provider_name}."
+
+        @input_box.verify_format
+        write_disk
+        write_metadata
+        write_vagrantfile
+        write_specific_files
+      end
+
+      private
+
+      def write_metadata
+        metadata = generate_metadata
+        begin
+          File.open(File.join(@output_box.dir, 'metadata.json'), 'w') do |f|
+            f.write(JSON.generate(metadata))
+          end
+        rescue => e
+          raise Errors::WriteMetadataFailed, error_message: e.message
+        end
+        @logger.info 'Wrote metadata'
+      end
+
+      def write_vagrantfile
+        body = generate_vagrantfile
+        begin
+          File.open(File.join(@output_box.dir, 'Vagrantfile'), 'w') do |f|
+            f.puts('Vagrant.configure("2") do |config|')
+            f.puts(body)
+            f.puts('end')
+          end
+        rescue => e
+          raise Errors::WriteVagrantfileFailed, error_message: e.message
+        end
+        @logger.info 'Wrote vagrantfile'
+      end
+
+      def write_disk
+        if @input_box.image_format == @output_box.image_format
+          copy_disk
+        else
+          convert_disk
+        end
+      end
+
+      def copy_disk
+        input = File.join(@input_box.dir, @input_box.image_name)
+        output = File.join(@output_box.dir, @output_box.image_name)
+        @logger.info "Copying #{input} to #{output}"
+        begin
+          FileUtils.copy_file(input, output)
+        rescue => e
+          raise Errors::WriteDiskFailed, error_message: e.message
+        end
+      end
+
+      def convert_disk
+        input_file    = File.join(@input_box.dir, @input_box.image_name).shellescape
+        output_file   = File.join(@output_box.dir, @output_box.image_name).shellescape
+        output_format = @output_box.image_format
+
+        # p for progress bar
+        # S for sparse file
+        qemu_options = '-p -S 16k'
+        qemu_version = Qemu.qemu_version()
+        if qemu_version >= Gem::Version.new('1.1.0')
+          if output_format == 'qcow2'
+            qemu_options += ' -o compat=1.1'
+          end
+        end
+
+        command = "qemu-img convert #{qemu_options} -O #{output_format} #{input_file} #{output_file}"
+        @logger.info "Running #{command}"
+        unless system(command)
+          fail Errors::WriteDiskFailed, error_message: "qemu-img exited with status #{$CHILD_STATUS.exitstatus}"
+        end
+      end
+    end
+  end
+end
diff --git a/lib/vagrant-mutate/converter/kvm.rb b/lib/vagrant-mutate/converter/kvm.rb
new file mode 100644
index 0000000..a1a7f50
--- /dev/null
+++ b/lib/vagrant-mutate/converter/kvm.rb
@@ -0,0 +1,71 @@
+require 'erb'
+
+module VagrantMutate
+  module Converter
+    class Kvm < Converter
+      def generate_metadata
+        { 'provider' => @output_box.provider_name }
+      end
+
+      def generate_vagrantfile
+        "  config.vm.base_mac = '#{@input_box.mac_address}'"
+      end
+
+      def write_specific_files
+        template_path = VagrantMutate.source_root.join('templates', 'kvm', 'box.xml.erb')
+        template = File.read(template_path)
+
+        uuid = nil
+        gui = true
+
+        if @force_virtio == true
+          disk_bus = 'virtio'
+        else
+          disk_bus = @input_box.disk_interface
+        end
+
+        image_type = @output_box.image_format
+        disk = @output_box.image_name
+
+        name = @input_box.name
+        memory = @input_box.memory / 1024 # convert bytes to kib
+        cpus = @input_box.cpus
+        mac = format_mac(@input_box.mac_address)
+        arch = @input_box.architecture
+
+        qemu_bin = find_kvm
+
+        File.open(File.join(@output_box.dir, 'box.xml'), 'w') do |f|
+          f.write(ERB.new(template).result(binding))
+        end
+      end
+
+      private
+
+      def find_kvm
+        qemu_bin = nil
+
+        qemu_bin_list = ['qemu-system-x86_64',
+                         'qemu-system-i386',
+                         'qemu-kvm',
+                         'kvm']
+        logger = Log4r::Logger.new('vagrant::mutate')
+        qemu_bin_list.each do |qemu|
+          qemu_bin = VagrantMutate.find_bin(qemu)
+          break
+        end
+
+        unless qemu_bin
+          fail Errors::QemuNotFound
+        end
+        logger.info 'Found qemu_bin: ' + qemu_bin
+        qemu_bin
+      end
+
+      # convert to format with colons
+      def format_mac(mac)
+        mac.scan(/(.{2})/).join(':')
+      end
+    end
+  end
+end
diff --git a/lib/vagrant-mutate/converter/libvirt.rb b/lib/vagrant-mutate/converter/libvirt.rb
new file mode 100644
index 0000000..66f7cbd
--- /dev/null
+++ b/lib/vagrant-mutate/converter/libvirt.rb
@@ -0,0 +1,33 @@
+module VagrantMutate
+  module Converter
+    class Libvirt < Converter
+
+      def generate_metadata
+        {
+          'provider' => @output_box.provider_name,
+          'format'   => @output_box.image_format,
+          'virtual_size' => ( @input_box.virtual_size.to_f / (1024 * 1024 * 1024)).ceil
+        }
+      end
+
+      def generate_vagrantfile
+
+        if @force_virtio == true
+          disk_bus = 'virtio'
+        else
+          disk_bus = @input_box.disk_interface
+        end
+
+        <<-EOF
+        config.vm.provider :libvirt do |libvirt|
+          libvirt.disk_bus = '#{disk_bus}'
+        end
+        EOF
+      end
+
+      def write_specific_files
+        # nothing to do here
+      end
+    end
+  end
+end
diff --git a/lib/vagrant-mutate/errors.rb b/lib/vagrant-mutate/errors.rb
new file mode 100644
index 0000000..f061ca9
--- /dev/null
+++ b/lib/vagrant-mutate/errors.rb
@@ -0,0 +1,85 @@
+require 'vagrant'
+
+module VagrantMutate
+  module Errors
+    class VagrantMutateError < Vagrant::Errors::VagrantError
+      error_namespace('vagrant_mutate.errors')
+    end
+
+    class ProvidersMatch < VagrantMutateError
+      error_key(:providers_match)
+    end
+
+    class ProviderNotSupported < VagrantMutateError
+      error_key(:provider_not_supported)
+    end
+
+    class QemuNotFound < VagrantMutateError
+      error_key(:qemu_not_found)
+    end
+
+    class QemuImgNotFound < VagrantMutateError
+      error_key(:qemu_img_not_found)
+    end
+
+    class BoxNotFound < VagrantMutateError
+      error_key(:box_not_found)
+    end
+
+    class TooManyBoxesFound < VagrantMutateError
+      error_key(:too_many_boxes_found)
+    end
+
+    class ExtractBoxFailed < VagrantMutateError
+      error_key(:extract_box_failed)
+    end
+
+    class ParseIdentifierFailed < VagrantMutateError
+      error_key(:parse_identifier_failed)
+    end
+
+    class DetermineProviderFailed < VagrantMutateError
+      error_key(:determine_provider_failed)
+    end
+
+    class LoadMetadataFailed < VagrantMutateError
+      error_key(:load_metadata_failed)
+    end
+
+    class CreateBoxDirFailed < VagrantMutateError
+      error_key(:create_box_dir_failed)
+    end
+
+    class WriteMetadataFailed < VagrantMutateError
+      error_key(:write_metadata_failed)
+    end
+
+    class WriteVagrantfileFailed < VagrantMutateError
+      error_key(:write_vagrantfile_failed)
+    end
+
+    class WriteDiskFailed < VagrantMutateError
+      error_key(:write_disk_failed)
+    end
+
+    class ParseQemuVersionFailed < VagrantMutateError
+      error_key(:parse_qemu_version_failed)
+    end
+
+    class QemuInfoFailed < VagrantMutateError
+      error_key(:qemu_info_failed)
+    end
+
+    class BoxAttributeError < VagrantMutateError
+      error_key(:box_attribute_error)
+    end
+
+    class URLError < VagrantMutateError
+      error_key(:url_error)
+    end
+
+    class MetadataNotFound < VagrantMutateError
+      error_key(:metadata_not_found)
+    end
+  end
+end
diff --git a/lib/vagrant-mutate/mutate.rb b/lib/vagrant-mutate/mutate.rb
new file mode 100644
index 0000000..7fa8667
--- /dev/null
+++ b/lib/vagrant-mutate/mutate.rb
@@ -0,0 +1,57 @@
+require 'vagrant-mutate/box_loader'
+require 'vagrant-mutate/qemu'
+require 'vagrant-mutate/converter/converter'
+
+module VagrantMutate
+  class Mutate < Vagrant.plugin(2, :command)
+    def execute
+      options = {}
+      options[:input_provider] = nil
+      options[:version] = nil
+      options[:force_virtio] = false
+
+      opts = OptionParser.new do |o|
+        o.banner = 'Usage: vagrant mutate <box-name-or-file-or-url> <provider>'
+        o.on('--input-provider PROVIDER',
+             'Specify provider for input box') do |p|
+          options[:input_provider] = p
+        end
+        o.on('--version VERSION',
+             'Specify version for input box') do |p|
+          options[:version] = p
+        end
+        o.on('--force-virtio',
+             'Force virtio disk driver') do |p|
+          options[:force_virtio] = true
+        end
+      end
+
+      argv = parse_options(opts)
+      return unless argv
+
+      unless argv.length == 2
+        @env.ui.info(opts.help)
+        return
+      end
+
+      options[:box_arg] = argv[0]
+      options[:output_provider] = argv[1]
+
+      Qemu.verify_qemu_installed
+      Qemu.verify_qemu_version(@env)
+
+      input_loader  = BoxLoader.new(@env)
+      input_box = input_loader.load(options[:box_arg], options[:input_provider], options[:version])
+
+      output_loader = BoxLoader.new(@env)
+      output_box = output_loader.prepare_for_output(input_box.name, options[:output_provider], input_box.version)
+
+      converter  = Converter::Converter.create(@env, input_box, output_box, options[:force_virtio])
+      converter.convert
+
+      input_loader.cleanup
+
+      @env.ui.info "The box #{output_box.name} (#{output_box.provider_name}) is now ready to use."
+    end
+  end
+end
diff --git a/lib/vagrant-mutate/qemu.rb b/lib/vagrant-mutate/qemu.rb
new file mode 100644
index 0000000..bf27332
--- /dev/null
+++ b/lib/vagrant-mutate/qemu.rb
@@ -0,0 +1,36 @@
+module VagrantMutate
+  class Qemu
+    def self.verify_qemu_installed
+      qemu_img_bin = nil
+      logger = Log4r::Logger.new('vagrant::mutate')
+      qemu_img_bin = VagrantMutate.find_bin("qemu-img")
+      unless qemu_img_bin
+        fail Errors::QemuImgNotFound
+      end
+      logger.info 'Found qemu-img: ' + qemu_img_bin
+      qemu_img_bin
+    end
+
+    def self.qemu_version()
+      usage = `qemu-img --version`
+      if usage =~ /(\d+\.\d+\.\d+)/
+        return Gem::Version.new(Regexp.last_match[1])
+      else
+        fail Errors::ParseQemuVersionFailed
+      end
+    end
+
+    def self.verify_qemu_version(env)
+      installed_version = qemu_version()
+      # less than 1.2 or equal to 1.6.x
+      if installed_version < Gem::Version.new('1.2.0') or (installed_version >= Gem::Version.new('1.6.0') and installed_version < Gem::Version.new('1.7.0'))
+
+        env.ui.warn "You have qemu #{installed_version} installed. "\
+          'This version cannot read some virtualbox boxes. '\
+          'If conversion fails, see below for recommendations. '\
+          'https://github.com/sciurus/vagrant-mutate/wiki/QEMU-Version-Compatibility'
+
+      end
+    end
+  end
+end
diff --git a/lib/vagrant-mutate/version.rb b/lib/vagrant-mutate/version.rb
new file mode 100644
index 0000000..9f631d4
--- /dev/null
+++ b/lib/vagrant-mutate/version.rb
@@ -0,0 +1,3 @@
+module VagrantMutate
+  VERSION = '1.2.0'
+end
diff --git a/locales/en.yml b/locales/en.yml
new file mode 100644
index 0000000..41bbe29
--- /dev/null
+++ b/locales/en.yml
@@ -0,0 +1,50 @@
+en:
+  vagrant_mutate:
+    errors:
+      providers_match: |-
+        Input and output provider are the same. Aborting.
+      provider_not_supported: |-
+        Vagrant-mutate does not support %{provider} for %{direction}
+      qemu_not_found: |-
+        QEMU was not found in your path
+      qemu_img_not_found: |-
+        qemu-img was not found in your path
+      box_not_found: |-
+        The box %{box} was not found
+      too_many_boxes_found: |-
+        More than one box named %{box} was found. Please specify the input provider as well.
+      extract_box_failed: |-
+        Extracting box failed with error:
+        %{error_message}
+      parse_identifier_failed: |-
+        Expected name or provider/name but you gave %{identifier}.
+        If you meant to specify a box by name fix this. If you meant to specify a box by path make sure the file exists.
+      determine_provider_failed: |-
+        Determining provider for box failed with error:
+        %{error_message}
+      load_metadata_failed: |-
+        Loading metadata for box failed with error:
+        %{error_message}
+      create_box_dir_failed: |-
+        Creating directory for box failed with error:
+        %{error_message}
+      write_metadata_failed: |-
+        Writing metadata for box failed with error:
+        %{error_message}
+      write_vagrantfile_failed: |-
+        Writing vagrantfile for box failed with error:
+        %{error_message}
+      write_disk_failed: |-
+        Writing disk image for box failed with error:
+        %{error_message}
+      parse_qemu_version_failed: |-
+        Determining the version of qemu-img installed failed
+      qemu_info_failed: |-
+          Getting information about the disk image via qemu-info failed
+      box_attribute_error: |-
+        Error determining information about the input box:
+        %{error_message}
+      url_error: |-
+          The url %{url} is not valid
+      metadata_not_found: |-
+        Unable to find metadata file for %{box}
diff --git a/templates/kvm/box.xml.erb b/templates/kvm/box.xml.erb
new file mode 100644
index 0000000..c2ac62b
--- /dev/null
+++ b/templates/kvm/box.xml.erb
@@ -0,0 +1,72 @@
+<domain type='kvm'>
+<name><%= name %></name>
+<% if uuid %>
+<uuid><%= uuid %></uuid>
+<% end %>
+<memory unit='KiB'><%= memory %></memory>
+<currentMemory unit='KiB'><%= memory%></currentMemory>
+<vcpu placement='static'><%= cpus %></vcpu>
+  <os>
+  <type arch='<%= arch %>' machine='pc-1.2'>hvm</type>
+    <boot dev='hd'/>
+  </os>
+  <features>
+    <acpi/>
+    <apic/>
+    <pae/>
+  </features>
+  <clock offset='utc'/>
+  <on_poweroff>destroy</on_poweroff>
+  <on_reboot>restart</on_reboot>
+  <on_crash>restart</on_crash>
+  <devices>
+    <emulator><%= qemu_bin %></emulator>
+    <disk type='file' device='disk'>
+      <driver name='qemu' type='<%= image_type %>'/>
+      <source file='<%= disk %>'/>
+      <target dev='vda' bus='<%= disk_bus %>'/>
+<% if disk_bus == 'virtio' %>
+      <address type='pci' domain='0x0000' bus='0x00' slot='0x08' function='0x0'/>
+<% else %>
+      <address type='drive' controller='0' bus='0' target='0' unit='0'/>
+<% end %>
+    </disk>
+    <interface type='network'>
+      <mac address='<%= mac %>'/>
+      <source network='vagrant'/>
+      <model type='virtio'/>
+    </interface>
+    <serial type='pty'>
+      <target port='0'/>
+    </serial>
+    <console type='pty'>
+      <target type='serial' port='0'/>
+    </console>
+    <input type='mouse' bus='ps2'/>
+    <sound model='ich6'>
+      <address type='pci' domain='0x0000' bus='0x00' slot='0x04' function='0x0'/>
+    </sound>
+    <% if gui %>
+    <%= "<graphics type='vnc' port='-1' autoport='yes'/>" %>
+    <% end %>
+    <video>
+      <model type='cirrus' vram='9216' heads='1'/>
+      <address type='pci' domain='0x0000' bus='0x00' slot='0x02' function='0x0'/>
+    </video>
+    <controller type='ide' index='0'>
+      <address type='pci' domain='0x0000' bus='0x00' slot='0x01' function='0x1'/>
+    </controller>
+    <controller type='usb' index='0'>
+      <address type='pci' domain='0x0000' bus='0x00' slot='0x01' function='0x2'/>
+    </controller>
+    <controller type='virtio-serial' index='0'>
+      <address type='pci' domain='0x0000' bus='0x00' slot='0x05' function='0x0'/>
+    </controller>
+    <controller type='sata' index='0'>
+      <address type='pci' domain='0x0000' bus='0x00' slot='0x06' function='0x0'/>
+    </controller>
+    <memballoon model='virtio'>
+      <address type='pci' domain='0x0000' bus='0x00' slot='0x07' function='0x0'/>
+    </memballoon>
+  </devices>
+</domain>
diff --git a/test/expected_output/kvm/libvirt/Vagrantfile b/test/expected_output/kvm/libvirt/Vagrantfile
new file mode 100644
index 0000000..aa4d404
--- /dev/null
+++ b/test/expected_output/kvm/libvirt/Vagrantfile
@@ -0,0 +1,5 @@
+Vagrant.configure("2") do |config|
+        config.vm.provider :libvirt do |libvirt|
+          libvirt.disk_bus = 'virtio'
+        end
+end
diff --git a/test/expected_output/kvm/libvirt/box.img b/test/expected_output/kvm/libvirt/box.img
new file mode 100644
index 0000000..9019499
Binary files /dev/null and b/test/expected_output/kvm/libvirt/box.img differ
diff --git a/test/expected_output/kvm/libvirt/metadata.json b/test/expected_output/kvm/libvirt/metadata.json
new file mode 100644
index 0000000..1e732e0
--- /dev/null
+++ b/test/expected_output/kvm/libvirt/metadata.json
@@ -0,0 +1 @@
+{"provider":"libvirt","format":"qcow2","virtual_size":1}
\ No newline at end of file
diff --git a/test/expected_output/libvirt/kvm/Vagrantfile b/test/expected_output/libvirt/kvm/Vagrantfile
new file mode 100644
index 0000000..5a0e55e
--- /dev/null
+++ b/test/expected_output/libvirt/kvm/Vagrantfile
@@ -0,0 +1,3 @@
+Vagrant.configure("2") do |config|
+  config.vm.base_mac = '525400cbb280'
+end
diff --git a/test/expected_output/libvirt/kvm/box-disk1.img b/test/expected_output/libvirt/kvm/box-disk1.img
new file mode 100644
index 0000000..9019499
Binary files /dev/null and b/test/expected_output/libvirt/kvm/box-disk1.img differ
diff --git a/test/expected_output/libvirt/kvm/box.xml b/test/expected_output/libvirt/kvm/box.xml
new file mode 100644
index 0000000..5a7dd1f
--- /dev/null
+++ b/test/expected_output/libvirt/kvm/box.xml
@@ -0,0 +1,68 @@
+<domain type='kvm'>
+<name>mutate-test</name>
+
+<memory unit='KiB'>524288</memory>
+<currentMemory unit='KiB'>524288</currentMemory>
+<vcpu placement='static'>1</vcpu>
+  <os>
+  <type arch='x86_64' machine='pc-1.2'>hvm</type>
+    <boot dev='hd'/>
+  </os>
+  <features>
+    <acpi/>
+    <apic/>
+    <pae/>
+  </features>
+  <clock offset='utc'/>
+  <on_poweroff>destroy</on_poweroff>
+  <on_reboot>restart</on_reboot>
+  <on_crash>restart</on_crash>
+  <devices>
+    <emulator>/usr/bin/qemu-system-x86_64</emulator>
+    <disk type='file' device='disk'>
+      <driver name='qemu' type='qcow2'/>
+      <source file='box-disk1.img'/>
+      <target dev='vda' bus='virtio'/>
+
+      <address type='pci' domain='0x0000' bus='0x00' slot='0x08' function='0x0'/>
+
+    </disk>
+    <interface type='network'>
+      <mac address='52:54:00:cb:b2:80'/>
+      <source network='vagrant'/>
+      <model type='virtio'/>
+    </interface>
+    <serial type='pty'>
+      <target port='0'/>
+    </serial>
+    <console type='pty'>
+      <target type='serial' port='0'/>
+    </console>
+    <input type='mouse' bus='ps2'/>
+    <sound model='ich6'>
+      <address type='pci' domain='0x0000' bus='0x00' slot='0x04' function='0x0'/>
+    </sound>
+    
+    <graphics type='vnc' port='-1' autoport='yes'/>
+    
+    <video>
+      <model type='cirrus' vram='9216' heads='1'/>
+      <address type='pci' domain='0x0000' bus='0x00' slot='0x02' function='0x0'/>
+    </video>
+    <controller type='ide' index='0'>
+      <address type='pci' domain='0x0000' bus='0x00' slot='0x01' function='0x1'/>
+    </controller>
+    <controller type='usb' index='0'>
+      <address type='pci' domain='0x0000' bus='0x00' slot='0x01' function='0x2'/>
+    </controller>
+    <controller type='virtio-serial' index='0'>
+      <address type='pci' domain='0x0000' bus='0x00' slot='0x05' function='0x0'/>
+    </controller>
+    <controller type='sata' index='0'>
+      <address type='pci' domain='0x0000' bus='0x00' slot='0x06' function='0x0'/>
+    </controller>
+    <memballoon model='virtio'>
+      <address type='pci' domain='0x0000' bus='0x00' slot='0x07' function='0x0'/>
+    </memballoon>
+  </devices>
+</domain>
diff --git a/test/expected_output/libvirt/kvm/metadata.json b/test/expected_output/libvirt/kvm/metadata.json
new file mode 100644
index 0000000..c1eb222
--- /dev/null
+++ b/test/expected_output/libvirt/kvm/metadata.json
@@ -0,0 +1 @@
+{"provider":"kvm"}
\ No newline at end of file
diff --git a/test/expected_output/virtualbox/bhyve/Vagrantfile b/test/expected_output/virtualbox/bhyve/Vagrantfile
new file mode 100644
index 0000000..d1720e5
--- /dev/null
+++ b/test/expected_output/virtualbox/bhyve/Vagrantfile
@@ -0,0 +1,6 @@
+Vagrant.configure("2") do |config|
+        config.vm.provider :bhyve do |vm|
+          vm.memory = "256M"
+          vm.cpus = "1"
+        end
+end
diff --git a/test/expected_output/virtualbox/bhyve/disk.img b/test/expected_output/virtualbox/bhyve/disk.img
new file mode 100644
index 0000000..98fc2c0
Binary files /dev/null and b/test/expected_output/virtualbox/bhyve/disk.img differ
diff --git a/test/expected_output/virtualbox/bhyve/metadata.json b/test/expected_output/virtualbox/bhyve/metadata.json
new file mode 100644
index 0000000..0365c59
--- /dev/null
+++ b/test/expected_output/virtualbox/bhyve/metadata.json
@@ -0,0 +1 @@
+{"provider":"bhyve"}
\ No newline at end of file
diff --git a/test/expected_output/virtualbox/kvm/Vagrantfile b/test/expected_output/virtualbox/kvm/Vagrantfile
new file mode 100644
index 0000000..6ed92e2
--- /dev/null
+++ b/test/expected_output/virtualbox/kvm/Vagrantfile
@@ -0,0 +1,3 @@
+Vagrant.configure("2") do |config|
+  config.vm.base_mac = '08002755B88D'
+end
diff --git a/test/expected_output/virtualbox/kvm/box-disk1.img b/test/expected_output/virtualbox/kvm/box-disk1.img
new file mode 100644
index 0000000..25d7c10
Binary files /dev/null and b/test/expected_output/virtualbox/kvm/box-disk1.img differ
diff --git a/test/expected_output/virtualbox/kvm/box.xml b/test/expected_output/virtualbox/kvm/box.xml
new file mode 100644
index 0000000..e39aaf9
--- /dev/null
+++ b/test/expected_output/virtualbox/kvm/box.xml
@@ -0,0 +1,68 @@
+<domain type='kvm'>
+<name>mutate-test</name>
+
+<memory unit='KiB'>262144</memory>
+<currentMemory unit='KiB'>262144</currentMemory>
+<vcpu placement='static'>1</vcpu>
+  <os>
+  <type arch='x86_64' machine='pc-1.2'>hvm</type>
+    <boot dev='hd'/>
+  </os>
+  <features>
+    <acpi/>
+    <apic/>
+    <pae/>
+  </features>
+  <clock offset='utc'/>
+  <on_poweroff>destroy</on_poweroff>
+  <on_reboot>restart</on_reboot>
+  <on_crash>restart</on_crash>
+  <devices>
+    <emulator>/usr/bin/qemu-system-x86_64</emulator>
+    <disk type='file' device='disk'>
+      <driver name='qemu' type='qcow2'/>
+      <source file='box-disk1.img'/>
+      <target dev='vda' bus='sata'/>
+
+      <address type='drive' controller='0' bus='0' target='0' unit='0'/>
+
+    </disk>
+    <interface type='network'>
+      <mac address='08:00:27:55:B8:8D'/>
+      <source network='vagrant'/>
+      <model type='virtio'/>
+    </interface>
+    <serial type='pty'>
+      <target port='0'/>
+    </serial>
+    <console type='pty'>
+      <target type='serial' port='0'/>
+    </console>
+    <input type='mouse' bus='ps2'/>
+    <sound model='ich6'>
+      <address type='pci' domain='0x0000' bus='0x00' slot='0x04' function='0x0'/>
+    </sound>
+    
+    <graphics type='vnc' port='-1' autoport='yes'/>
+    
+    <video>
+      <model type='cirrus' vram='9216' heads='1'/>
+      <address type='pci' domain='0x0000' bus='0x00' slot='0x02' function='0x0'/>
+    </video>
+    <controller type='ide' index='0'>
+      <address type='pci' domain='0x0000' bus='0x00' slot='0x01' function='0x1'/>
+    </controller>
+    <controller type='usb' index='0'>
+      <address type='pci' domain='0x0000' bus='0x00' slot='0x01' function='0x2'/>
+    </controller>
+    <controller type='virtio-serial' index='0'>
+      <address type='pci' domain='0x0000' bus='0x00' slot='0x05' function='0x0'/>
+    </controller>
+    <controller type='sata' index='0'>
+      <address type='pci' domain='0x0000' bus='0x00' slot='0x06' function='0x0'/>
+    </controller>
+    <memballoon model='virtio'>
+      <address type='pci' domain='0x0000' bus='0x00' slot='0x07' function='0x0'/>
+    </memballoon>
+  </devices>
+</domain>
diff --git a/test/expected_output/virtualbox/kvm/metadata.json b/test/expected_output/virtualbox/kvm/metadata.json
new file mode 100644
index 0000000..c1eb222
--- /dev/null
+++ b/test/expected_output/virtualbox/kvm/metadata.json
@@ -0,0 +1 @@
+{"provider":"kvm"}
\ No newline at end of file
diff --git a/test/expected_output/virtualbox/libvirt/Vagrantfile b/test/expected_output/virtualbox/libvirt/Vagrantfile
new file mode 100644
index 0000000..1e3ba59
--- /dev/null
+++ b/test/expected_output/virtualbox/libvirt/Vagrantfile
@@ -0,0 +1,5 @@
+Vagrant.configure("2") do |config|
+        config.vm.provider :libvirt do |libvirt|
+          libvirt.disk_bus = 'sata'
+        end
+end
diff --git a/test/expected_output/virtualbox/libvirt/box.img b/test/expected_output/virtualbox/libvirt/box.img
new file mode 100644
index 0000000..25d7c10
Binary files /dev/null and b/test/expected_output/virtualbox/libvirt/box.img differ
diff --git a/test/expected_output/virtualbox/libvirt/metadata.json b/test/expected_output/virtualbox/libvirt/metadata.json
new file mode 100644
index 0000000..1e732e0
--- /dev/null
+++ b/test/expected_output/virtualbox/libvirt/metadata.json
@@ -0,0 +1 @@
+{"provider":"libvirt","format":"qcow2","virtual_size":1}
\ No newline at end of file
diff --git a/test/input/kvm/mutate-test.box b/test/input/kvm/mutate-test.box
new file mode 100644
index 0000000..52e743f
Binary files /dev/null and b/test/input/kvm/mutate-test.box differ
diff --git a/test/input/libvirt/mutate-test.box b/test/input/libvirt/mutate-test.box
new file mode 100644
index 0000000..44578d8
Binary files /dev/null and b/test/input/libvirt/mutate-test.box differ
diff --git a/test/input/virtualbox/mutate-test.box b/test/input/virtualbox/mutate-test.box
new file mode 100644
index 0000000..1fa9c41
Binary files /dev/null and b/test/input/virtualbox/mutate-test.box differ
diff --git a/test/test.rb b/test/test.rb
new file mode 100755
index 0000000..ef043ce
--- /dev/null
+++ b/test/test.rb
@@ -0,0 +1,101 @@
+#!/usr/bin/env ruby
+
+require 'fileutils'
+
+def cleanup
+  puts "\nCLEANING UP"
+  base_output_dir = File.expand_path('../actual_output', __FILE__)
+  if File.directory? base_output_dir
+    FileUtils.rm_rf base_output_dir
+  end
+end
+
+def ensure_pkg_dir
+  pkg_dir = File.expand_path('../../pkg', __FILE__)
+  unless Dir.exist? pkg_dir
+    Dir.mkdir pkg_dir
+  end
+end
+
+def build_plugin
+  puts "\nBUILDING PLUGIN"
+  pkg_dir = File.expand_path('../../pkg', __FILE__)
+  working_dir = Dir.pwd
+  Dir.chdir pkg_dir
+  FileUtils.rm(Dir.glob('*.gem'))
+  system('rake build')
+  Dir.chdir working_dir
+end
+
+def install_plugin
+  puts "\nINSTALLING PLUGIN"
+  pkg_dir = File.expand_path('../../pkg', __FILE__)
+  working_dir = Dir.pwd
+  Dir.chdir pkg_dir
+  system('vagrant plugin install *.gem')
+  Dir.chdir working_dir
+end
+
+# convering libvirt to kvm has some random output which we must replace
+# with the static "random" value in the sample output we are comparing against
+def derandomize_output(input, output_dir)
+  if input == 'libvirt'
+    if File.split(output_dir).last == 'kvm'
+      ['box.xml', 'Vagrantfile'].each do |f|
+        path = File.join(output_dir, f)
+        contents = File.read(path)
+        contents.gsub!(/52:54:00:[0-9a-f:]+/, '52:54:00:cb:b2:80')
+        contents.gsub!(/525400[0-9a-f]+/, '525400cbb280')
+        File.open(path, 'w') do |o|
+          o.write(contents)
+        end
+      end
+    end
+  end
+end
+
+def test(input, outputs)
+  failures = []
+  test_dir = File.expand_path(File.dirname(__FILE__))
+
+  input_box = File.join(test_dir, 'input', input, 'mutate-test.box')
+
+  vagrant_dir = File.join(test_dir, 'actual_output', input)
+  FileUtils.mkdir_p vagrant_dir
+  ENV['VAGRANT_HOME'] = vagrant_dir
+  install_plugin
+
+  outputs.each do |output|
+    puts "\nTESTING #{input} to #{output}"
+    system("vagrant mutate #{input_box} #{output}")
+    # 0 because input boxes are unversioned
+    output_dir = File.join(vagrant_dir, 'boxes', 'mutate-test', '0', output)
+    expected_output_dir = File.join(test_dir, 'expected_output', input, output)
+    derandomize_output(input, output_dir)
+    Dir.foreach(expected_output_dir) do |f|
+      next if f == '.' or f == '..'
+      output = File.join(output_dir, f)
+      expected_output = File.join(expected_output_dir, f)
+      test_passed = FileUtils.compare_file(output, expected_output)
+      unless test_passed
+        failures.push "These two files do not match #{output} #{expected_output}"
+      end
+    end
+  end
+
+  failures
+end
+
+cleanup
+ensure_pkg_dir
+build_plugin
+failures = test('virtualbox', %w(kvm libvirt bhyve))
+failures += test('libvirt', ['kvm'])
+failures += test('kvm', ['libvirt'])
+
+if failures.empty?
+  puts "\nALL TESTS PASSED"
+else
+  puts "\nTESTS FAILED"
+  failures.each { |f| puts f }
+end
diff --git a/vagrant-mutate.gemspec b/vagrant-mutate.gemspec
new file mode 100644
index 0000000..562b50d
--- /dev/null
+++ b/vagrant-mutate.gemspec
@@ -0,0 +1,23 @@
+# coding: utf-8
+lib = File.expand_path('../lib', __FILE__)
+$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
+require 'vagrant-mutate/version'
+
+Gem::Specification.new do |spec|
+  spec.name          = "vagrant-mutate"
+  spec.version       = VagrantMutate::VERSION
+  spec.authors       = ["Brian Pitts"]
+  spec.email         = ["brian at polibyte.com"]
+  spec.description   = %q{Convert vagrant boxes to work with different providers}
+  spec.summary       = spec.description
+  spec.homepage      = ""
+  spec.license       = "MIT"
+
+  spec.files         = `git ls-files`.split($/)
+  spec.executables   = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
+  spec.test_files    = spec.files.grep(%r{^(test|spec|features)/})
+  spec.require_paths = ["lib"]
+
+  spec.add_development_dependency "bundler", "~> 1.3"
+  spec.add_development_dependency "rake"
+end

-- 
Alioth's /usr/local/bin/git-commit-notice on /srv/git.debian.org/git/pkg-ruby-extras/vagrant-mutate.git



More information about the Pkg-ruby-extras-commits mailing list