[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