pax_global_header00006660000000000000000000000064127544655260014532gustar00rootroot0000000000000052 comment=5fcc7c70413ece24c05379f84ed31af7a1bfc135 vagrant-mutate-1.2.0/000077500000000000000000000000001275446552600144715ustar00rootroot00000000000000vagrant-mutate-1.2.0/.gitignore000066400000000000000000000002551275446552600164630ustar00rootroot00000000000000*.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 vagrant-mutate-1.2.0/CHANGELOG.md000066400000000000000000000047751275446552600163170ustar00rootroot00000000000000# 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 vagrant-mutate-1.2.0/Gemfile000066400000000000000000000005671275446552600157740ustar00rootroot00000000000000source '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 vagrant-mutate-1.2.0/LICENSE000066400000000000000000000020541275446552600154770ustar00rootroot00000000000000Copyright (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. vagrant-mutate-1.2.0/README.md000066400000000000000000000071441275446552600157560ustar00rootroot00000000000000# 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). vagrant-mutate-1.2.0/Rakefile000066400000000000000000000000341275446552600161330ustar00rootroot00000000000000require "bundler/gem_tasks" vagrant-mutate-1.2.0/lib/000077500000000000000000000000001275446552600152375ustar00rootroot00000000000000vagrant-mutate-1.2.0/lib/vagrant-mutate.rb000066400000000000000000000017221275446552600205250ustar00rootroot00000000000000require '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 vagrant-mutate-1.2.0/lib/vagrant-mutate/000077500000000000000000000000001275446552600201765ustar00rootroot00000000000000vagrant-mutate-1.2.0/lib/vagrant-mutate/box/000077500000000000000000000000001275446552600207665ustar00rootroot00000000000000vagrant-mutate-1.2.0/lib/vagrant-mutate/box/bhyve.rb000066400000000000000000000005461275446552600224350ustar00rootroot00000000000000require_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 vagrant-mutate-1.2.0/lib/vagrant-mutate/box/box.rb000066400000000000000000000022031275446552600221000ustar00rootroot00000000000000require '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 vagrant-mutate-1.2.0/lib/vagrant-mutate/box/kvm.rb000066400000000000000000000016331275446552600221130ustar00rootroot00000000000000require_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 vagrant-mutate-1.2.0/lib/vagrant-mutate/box/libvirt.rb000066400000000000000000000015721275446552600227730ustar00rootroot00000000000000require_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 vagrant-mutate-1.2.0/lib/vagrant-mutate/box/virtualbox.rb000066400000000000000000000113031275446552600235100ustar00rootroot00000000000000require '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 vagrant-mutate-1.2.0/lib/vagrant-mutate/box_loader.rb000066400000000000000000000235721275446552600226520ustar00rootroot00000000000000require '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 vagrant-mutate-1.2.0/lib/vagrant-mutate/converter/000077500000000000000000000000001275446552600222055ustar00rootroot00000000000000vagrant-mutate-1.2.0/lib/vagrant-mutate/converter/bhyve.rb000066400000000000000000000014761275446552600236570ustar00rootroot00000000000000require '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 vagrant-mutate-1.2.0/lib/vagrant-mutate/converter/converter.rb000066400000000000000000000067461275446552600245560ustar00rootroot00000000000000require '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 vagrant-mutate-1.2.0/lib/vagrant-mutate/converter/kvm.rb000066400000000000000000000033371275446552600233350ustar00rootroot00000000000000require '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 vagrant-mutate-1.2.0/lib/vagrant-mutate/converter/libvirt.rb000066400000000000000000000013151275446552600242050ustar00rootroot00000000000000module 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 vagrant-mutate-1.2.0/lib/vagrant-mutate/errors.rb000066400000000000000000000037161275446552600220460ustar00rootroot00000000000000require '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 vagrant-mutate-1.2.0/lib/vagrant-mutate/mutate.rb000066400000000000000000000032701275446552600220240ustar00rootroot00000000000000require '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 ' 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 vagrant-mutate-1.2.0/lib/vagrant-mutate/qemu.rb000066400000000000000000000022241275446552600214720ustar00rootroot00000000000000module 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 vagrant-mutate-1.2.0/lib/vagrant-mutate/version.rb000066400000000000000000000000551275446552600222100ustar00rootroot00000000000000module VagrantMutate VERSION = '1.2.0' end vagrant-mutate-1.2.0/locales/000077500000000000000000000000001275446552600161135ustar00rootroot00000000000000vagrant-mutate-1.2.0/locales/en.yml000066400000000000000000000036651275446552600172520ustar00rootroot00000000000000en: 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} vagrant-mutate-1.2.0/templates/000077500000000000000000000000001275446552600164675ustar00rootroot00000000000000vagrant-mutate-1.2.0/templates/kvm/000077500000000000000000000000001275446552600172645ustar00rootroot00000000000000vagrant-mutate-1.2.0/templates/kvm/box.xml.erb000066400000000000000000000045001275446552600213440ustar00rootroot00000000000000 <%= name %> <% if uuid %> <%= uuid %> <% end %> <%= memory %> <%= memory%> <%= cpus %> hvm destroy restart restart <%= qemu_bin %> <% if disk_bus == 'virtio' %>
<% else %>
<% end %>
<% if gui %> <%= "" %> <% end %>