diff --git a/bin/vagrant-unpackage b/bin/vagrant-unpackage new file mode 100755 index 000000000..3cbdaf5da --- /dev/null +++ b/bin/vagrant-unpackage @@ -0,0 +1,27 @@ +#!/usr/bin/env ruby +begin + require File.expand_path('../../.bundle/environment', __FILE__) +rescue LoadError + # Fallback on rubygems + require "rubygems" +end + +require 'git-style-binary/command' + +# Get library +libdir = File.join(File.dirname(__FILE__), '..', 'lib') +$:.unshift(libdir) unless $:.include?(libdir) +require 'vagrant' + +GitStyleBinary.command do + short_desc "unpackage a vagrant environment" + banner <<-EOS +Usage: #{command.full_name} #{all_options_string} + +Unpackage a vagrant environment + +EOS + run do |command| + Vagrant::Commands.unpackage(command.argv[0]) + end +end diff --git a/config/default.rb b/config/default.rb index 80a95bf57..304ada189 100644 --- a/config/default.rb +++ b/config/default.rb @@ -1,6 +1,7 @@ Vagrant::Config.run do |config| # default config goes here config.vagrant.log_output = STDOUT + config.vagrant.home = "~/.vagrant" config.ssh.username = "vagrant" config.ssh.password = "vagrant" @@ -17,6 +18,8 @@ Vagrant::Config.run do |config| config.vm.disk_image_format = 'VMDK' config.package.name = 'vagrant' + config.package.delimiter = 'VAGRANT' + config.package.delimiter_regex = /(.*)#{config.package.delimiter}(.+)#{config.package.delimiter}(.*[\n\r])/ config.chef.cookbooks_path = "cookbooks" config.chef.provisioning_path = "/tmp/vagrant-chef" diff --git a/lib/vagrant/commands.rb b/lib/vagrant/commands.rb index c89a0c851..cbea1742b 100644 --- a/lib/vagrant/commands.rb +++ b/lib/vagrant/commands.rb @@ -109,6 +109,14 @@ The vagrant virtual environment you are trying to package must be powered off error Env.persisted_vm.package(name || Vagrant.config[:package][:name], FileUtils.pwd) end + + def unpackage(name) + Env.load! + error_and_exit(<<-error) unless name +Please specify a target package to unpack and import +error + VM.up(VM.unpackage(name)) + end end end end diff --git a/lib/vagrant/config.rb b/lib/vagrant/config.rb index 7993b207e..a05aa71d9 100644 --- a/lib/vagrant/config.rb +++ b/lib/vagrant/config.rb @@ -70,10 +70,16 @@ module Vagrant raise Exception.new "disk_storage must be set to a directory" unless File.directory?(val) @hd_location=val end + + def base + File.expand_path(@base) + end end class PackageConfig < Base attr_accessor :name + attr_accessor :delimiter + attr_accessor :delimiter_regex end class ChefConfig < Base @@ -84,6 +90,11 @@ module Vagrant class VagrantConfig < Base attr_accessor :log_output + attr_accessor :home + + def home + File.expand_path(@home) + end end class Top < Base diff --git a/lib/vagrant/util.rb b/lib/vagrant/util.rb index c34f0203b..d62f8505b 100644 --- a/lib/vagrant/util.rb +++ b/lib/vagrant/util.rb @@ -1,6 +1,6 @@ module Vagrant module Util - def self.included?(base) + def self.included(base) base.extend Vagrant::Util end diff --git a/lib/vagrant/vm.rb b/lib/vagrant/vm.rb index db1f6f482..c36202873 100644 --- a/lib/vagrant/vm.rb +++ b/lib/vagrant/vm.rb @@ -2,12 +2,71 @@ module Vagrant class VM include Vagrant::Util attr_reader :vm + attr_accessor :from class << self # Bring up the virtual machine. Imports the base image and # provisions it. - def up - new.create + def up(from=Vagrant.config[:vm][:base]) + vm = new + vm.from = from + vm.create + end + + # Unpack the specified vm package + def unpackage(package_path) + working_dir = package_path.chomp(File.extname(package_path)) + new_base_dir = File.join(Vagrant.config[:vagrant][:home], File.basename(package_path, '.*')) + + # Exit if folder of same name exists + # TODO provide a way for them to specify the directory name + error_and_exit(<<-error) if File.exists?(new_base_dir) +The directory `#{File.basename(package_path, '.*')}` already exists under #{Vagrant.config[:vagrant][:home]}. Please +remove it, rename your packaged VM file, or (TODO) specifiy an +alternate directory +error + + logger.info "Creating working dir: #{working_dir} ..." + FileUtils.mkpath(working_dir) + + logger.info "Decompressing the packaged VM: #{package_path} ..." + decompress(package_path, working_dir) + + logger.info "Moving the unpackaged VM to #{new_base_dir} ..." + FileUtils.mv(working_dir, Vagrant.config[:vagrant][:home]) + + #Return the ovf file for importation + Dir["#{new_base_dir}/*.ovf"].first + end + + def decompress(path, dir, file_delimeter=Vagrant.config[:package][:delimiter_regex]) + file = nil + Zlib::GzipReader.open(path) do |gz| + begin + gz.each_line do |line| + + # If the line is a file delimiter create new file and write to it + if line =~ file_delimeter + + #Write the the part of the line belonging to the previous file + if file + file.write $1 + file.close + end + + #Open a new file with the name contained in the delimiter + file = File.open(File.join(dir, $2), 'w') + + #Write the rest of the line to the new file + file.write $3 + else + file.write line + end + end + ensure + file.close if file + end + end end # Finds a virtual machine by a given UUID and either returns @@ -76,7 +135,7 @@ error def import logger.info "Importing base VM (#{Vagrant.config[:vm][:base]})..." - @vm = VirtualBox::VM.import(File.expand_path(Vagrant.config[:vm][:base])) + @vm = VirtualBox::VM.import(@from) end def persist @@ -173,6 +232,7 @@ error # TODO the longest method, needs to be split up def package(name, to) + delimiter = Vagrant.config[:package][:delimiter] folder = FileUtils.mkpath(File.join(to, name)) logger.info "Creating working directory: #{folder} ..." @@ -182,17 +242,15 @@ error logger.info "Exporting required VM files to working directory ..." @vm.export(ovf_path) - # TODO use zlib ... - logger.info "Packaging VM into #{name}.box ..." - Tar.open(tar_path, File::CREAT | File::WRONLY, 0644, Tar::GNU) do |tar| - begin - # appending the expanded file path adds the whole folder tree - # to the tar archive there must be a better way - working_dir = FileUtils.pwd - FileUtils.cd(to) - tar.append_tree(name) - ensure - FileUtils.cd(working_dir) + logger.info "Packaging VM into #{tar_path} ..." + Zlib::GzipWriter.open(tar_path) do |gz| + first_file = true + Dir.new(folder).each do |file| + next if File.directory?(file) + # Delimit the files, and guarantee new line for next file if not the first + gz.write "#{delimiter}#{file}#{delimiter}" + File.open(File.join(folder, file)).each { |line| gz.write(line) } + first_file = false end end @@ -202,6 +260,7 @@ error tar_path end + # TODO need a better way to which controller is the hd def hd @vm.storage_controllers.first.devices.first diff --git a/test/test_helper.rb b/test/test_helper.rb index ffe38f2fe..93894fae4 100644 --- a/test/test_helper.rb +++ b/test/test_helper.rb @@ -36,11 +36,16 @@ class Test::Unit::TestCase config.vm.project_directory = "/hobo" config.vm.forward_port("ssh", 22, 2222) + config.package.delimiter = 'V' + config.package.delimiter_regex = /'V(.+)V'/ + config.chef.cookbooks_path = "cookbooks" config.chef.provisioning_path = "/tmp/hobo-chef" config.chef.json = { :recipes => ["hobo_main"] } + + config.vagrant.home = '~/.home' end Vagrant::Config.execute! diff --git a/test/vagrant/vm_test.rb b/test/vagrant/vm_test.rb index 12c7bb27d..31f09453c 100644 --- a/test/vagrant/vm_test.rb +++ b/test/vagrant/vm_test.rb @@ -15,6 +15,7 @@ class VMTest < Test::Unit::TestCase should "create a Vagrant::VM instance and call create" do inst = mock("instance") inst.expects(:create).once + inst.expects(:from=).with(File.expand_path(Vagrant.config[:vm][:base])) Vagrant::VM.expects(:new).returns(inst) Vagrant::VM.up end @@ -242,6 +243,7 @@ class VMTest < Test::Unit::TestCase end end + # TODO more comprehensive testing context "packaging a vm" do should "dump the three necessary files to a tar in the current working dir" do location = FileUtils.pwd @@ -250,10 +252,24 @@ class VMTest < Test::Unit::TestCase @mock_vm.expects(:export).with(File.join(new_dir, "#{name}.ovf")) FileUtils.expects(:mkpath).with(new_dir).returns(new_dir) FileUtils.expects(:rm_r).with(new_dir) - Tar.expects(:open) + Zlib::GzipWriter.expects(:open).with("#{location}/#{name}.box") # TODO test whats passed to the open tar.append_tree assert_equal Vagrant::VM.new(@mock_vm).package(name, location), "#{new_dir}.box" end end + + context "unpackaging a vm" do + + # TODO test actual decompression + should "call decompress with the path to the file and the directory to decompress to" do + working_dir = File.join FileUtils.pwd, 'something' + file = File.join(FileUtils.pwd, 'something.box') + FileUtils.expects(:mkpath).with(working_dir).once + FileUtils.expects(:mv).with(working_dir, Vagrant.config[:vagrant][:home]).once + Vagrant::VM.expects(:decompress).with(file, working_dir).once + Vagrant::VM.unpackage(file) + + end + end end