lib/vagrant-libvirt/action/package_domain.rb in vagrant-libvirt-0.5.3 vs lib/vagrant-libvirt/action/package_domain.rb in vagrant-libvirt-0.6.0

- old
+ new

@@ -1,8 +1,16 @@ +# frozen_string_literal: true + require 'fileutils' require 'log4r' +class String + def unindent + gsub(/^#{scan(/^\s*/).min_by{|l|l.length}}/, "") + end +end + module VagrantPlugins module ProviderLibvirt module Action # Action for create new box for Libvirt provider class PackageDomain @@ -10,112 +18,169 @@ def initialize(app, env) @logger = Log4r::Logger.new('vagrant_libvirt::action::package_domain') @app = app - env['package.files'] ||= {} - env['package.output'] ||= 'package.box' + + @options = ENV.fetch('VAGRANT_LIBVIRT_VIRT_SYSPREP_OPTIONS', '') + @operations = ENV.fetch('VAGRANT_LIBVIRT_VIRT_SYSPREP_OPERATIONS', 'defaults,-ssh-userdir,-ssh-hostkeys,-customize') end def call(env) env[:ui].info(I18n.t('vagrant_libvirt.package_domain')) libvirt_domain = env[:machine].provider.driver.connection.client.lookup_domain_by_uuid( env[:machine].id ) domain = env[:machine].provider.driver.connection.servers.get(env[:machine].id.to_s) - root_disk = domain.volumes.select do |x| + + volumes = domain.volumes.select { |x| !x.nil? } + root_disk = volumes.select do |x| x.name == libvirt_domain.name + '.img' end.first raise Errors::NoDomainVolume if root_disk.nil? - boxname = env['package.output'] - raise "#{boxname}: Already exists" if File.exist?(boxname) - @tmp_dir = Dir.pwd + '/_tmp_package' - @tmp_img = @tmp_dir + '/box.img' - FileUtils.mkdir_p(@tmp_dir) - env[:ui].info("Downloading #{root_disk.name} to #{@tmp_img}") - ret = download_image(@tmp_img, env[:machine].provider_config.storage_pool_name, - root_disk.name, env) do |progress,image_size| - rewriting(env[:ui]) do |ui| - ui.clear_line - ui.report_progress(progress, image_size, false) + + package_func = method(:package_v1) + + box_format = ENV.fetch('VAGRANT_LIBVIRT_BOX_FORMAT_VERSION', nil) + + case box_format + when nil + if volumes.length() > 1 + msg = "Detected more than one volume for machine, in the future this will switch to using the v2 " + msg += "box format v2 automatically." + msg += "\nIf you want to include the additional disks attached when packaging please set the " + msg += "env variable VAGRANT_LIBVIRT_BOX_FORMAT_VERSION=v2 to use the new format. If you want " + msg += "to ensure that your box uses the old format for single disk only, please set the " + msg += "environment variable explicitly to 'v1'" + env[:ui].warn(msg) end + when 'v2' + package_func = method(:package_v2) + when 'v1' + else + env[:ui].warn("Unrecognized value for 'VAGRANT_LIBVIRT_BOX_FORMAT_VERSION', defaulting to v1") end - # Clear the line one last time since the progress meter doesn't - # disappear immediately. - rewriting(env[:ui]) {|ui| ui.clear_line} - backing = `qemu-img info "#{@tmp_img}" | grep 'backing file:' | cut -d ':' -f2`.chomp - if backing - env[:ui].info('Image has backing image, copying image and rebasing ...') - `qemu-img rebase -p -b "" #{@tmp_img}` - end - # remove hw association with interface - # working for centos with lvs default disks - options = ENV.fetch('VAGRANT_LIBVIRT_VIRT_SYSPREP_OPTIONS', '') - operations = ENV.fetch('VAGRANT_LIBVIRT_VIRT_SYSPREP_OPERATIONS', 'defaults,-ssh-userdir,-customize') - `virt-sysprep --no-logfile --operations #{operations} -a #{@tmp_img} #{options}` - `virt-sparsify --in-place #{@tmp_img}` - # add any user provided file - extra = '' - @tmp_include = @tmp_dir + '/_include' - if env['package.include'] - extra = './_include' - Dir.mkdir(@tmp_include) - env['package.include'].each do |f| - env[:ui].info("Including user file: #{f}") - FileUtils.cp(f, @tmp_include) - end - end - if env['package.vagrantfile'] - extra = './_include' - Dir.mkdir(@tmp_include) unless File.directory?(@tmp_include) - env[:ui].info('Including user Vagrantfile') - FileUtils.cp(env['package.vagrantfile'], @tmp_include + '/Vagrantfile') - end - Dir.chdir(@tmp_dir) - info = JSON.parse(`qemu-img info --output=json #{@tmp_img}`) - img_size = (Float(info['virtual-size'])/(1024**3)).ceil - File.write(@tmp_dir + '/metadata.json', metadata_content(img_size)) - File.write(@tmp_dir + '/Vagrantfile', vagrantfile_content) - assemble_box(boxname, extra) - FileUtils.mv(@tmp_dir + '/' + boxname, '../' + boxname) - FileUtils.rm_rf(@tmp_dir) - env[:ui].info('Box created') - env[:ui].info('You can now add the box:') - env[:ui].info("vagrant box add #{boxname} --name any_comfortable_name") + + metadata = package_func.call(env, volumes) + + # metadata / Vagrantfile + package_directory = env["package.directory"] + File.write(package_directory + '/metadata.json', metadata) + File.write(package_directory + '/Vagrantfile', vagrantfile_content(env)) + @app.call(env) end - def assemble_box(boxname, extra) - `tar cvzf "#{boxname}" --totals ./metadata.json ./Vagrantfile ./box.img #{extra}` + def package_v1(env, volumes) + domain_img = download_volume(env, volumes.first, 'box.img') + + sysprep_domain(domain_img) + sparsify_volume(domain_img) + + info = JSON.parse(`qemu-img info --output=json #{domain_img}`) + img_size = (Float(info['virtual-size'])/(1024**3)).ceil + + return metadata_content_v1(img_size) end - def vagrantfile_content - <<-EOF + def package_v2(env, volumes) + disks = [] + volumes.each_with_index do |vol, idx| + disk = {:path => "box_#{idx+1}.img"} + volume_img = download_volume(env, vol, disk[:path]) + + if idx == 0 + sysprep_domain(volume_img) + end + + sparsify_volume(volume_img) + + disks.push(disk) + end + + return metadata_content_v2(disks) + end + + def vagrantfile_content(env) + include_vagrantfile = "" + + if env["package.vagrantfile"] + include_vagrantfile = <<-EOF + + # Load include vagrant file if it exists after the auto-generated + # so it can override any of the settings + include_vagrantfile = File.expand_path("../include/_Vagrantfile", __FILE__) + load include_vagrantfile if File.exist?(include_vagrantfile) + EOF + end + + <<-EOF.unindent Vagrant.configure("2") do |config| config.vm.provider :libvirt do |libvirt| libvirt.driver = "kvm" - libvirt.host = "" - libvirt.connect_via_ssh = false - libvirt.storage_pool_name = "default" end + #{include_vagrantfile} end - - user_vagrantfile = File.expand_path('../_include/Vagrantfile', __FILE__) - load user_vagrantfile if File.exists?(user_vagrantfile) EOF end - def metadata_content(filesize) - <<-EOF + def metadata_content_v1(filesize) + <<-EOF.unindent { "provider": "libvirt", "format": "qcow2", "virtual_size": #{filesize} } EOF end + def metadata_content_v2(disks) + data = { + "provider": "libvirt", + "format": "qcow2", + "disks": disks.each do |disk| + {'path': disk[:path]} + end + } + JSON.pretty_generate(data) + end + protected + + def sparsify_volume(volume_img) + `virt-sparsify --in-place #{volume_img}` + end + + def sysprep_domain(domain_img) + # remove hw association with interface + # working for centos with lvs default disks + `virt-sysprep --no-logfile --operations #{@operations} -a #{domain_img} #{@options}` + end + + def download_volume(env, volume, disk_path) + package_directory = env["package.directory"] + volume_img = package_directory + '/' + disk_path + env[:ui].info("Downloading #{volume.name} to #{volume_img}") + download_image(volume_img, env[:machine].provider_config.storage_pool_name, + volume.name, env) do |progress,image_size| + rewriting(env[:ui]) do |ui| + ui.clear_line + ui.report_progress(progress, image_size, false) + end + end + # Clear the line one last time since the progress meter doesn't + # disappear immediately. + rewriting(env[:ui]) {|ui| ui.clear_line} + + # Prep domain disk + backing = `qemu-img info "#{volume_img}" | grep 'backing file:' | cut -d ':' -f2`.chomp + if backing + env[:ui].info('Image has backing image, copying image and rebasing ...') + `qemu-img rebase -p -b "" #{volume_img}` + end + + return volume_img + end # Fog libvirt currently doesn't support downloading images from storage # pool volumes. Use ruby-libvirt client instead. def download_image(image_file, pool_name, volume_name, env) begin