packaging and unpackaging

This commit is contained in:
John Bender 2010-02-15 13:55:42 -08:00
parent 8f63272403
commit c9d0d0d9d9
8 changed files with 145 additions and 16 deletions

27
bin/vagrant-unpackage Executable file
View File

@ -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

View File

@ -1,6 +1,7 @@
Vagrant::Config.run do |config| Vagrant::Config.run do |config|
# default config goes here # default config goes here
config.vagrant.log_output = STDOUT config.vagrant.log_output = STDOUT
config.vagrant.home = "~/.vagrant"
config.ssh.username = "vagrant" config.ssh.username = "vagrant"
config.ssh.password = "vagrant" config.ssh.password = "vagrant"
@ -17,6 +18,8 @@ Vagrant::Config.run do |config|
config.vm.disk_image_format = 'VMDK' config.vm.disk_image_format = 'VMDK'
config.package.name = 'vagrant' 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.cookbooks_path = "cookbooks"
config.chef.provisioning_path = "/tmp/vagrant-chef" config.chef.provisioning_path = "/tmp/vagrant-chef"

View File

@ -109,6 +109,14 @@ The vagrant virtual environment you are trying to package must be powered off
error error
Env.persisted_vm.package(name || Vagrant.config[:package][:name], FileUtils.pwd) Env.persisted_vm.package(name || Vagrant.config[:package][:name], FileUtils.pwd)
end 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 end
end end

View File

@ -70,10 +70,16 @@ module Vagrant
raise Exception.new "disk_storage must be set to a directory" unless File.directory?(val) raise Exception.new "disk_storage must be set to a directory" unless File.directory?(val)
@hd_location=val @hd_location=val
end end
def base
File.expand_path(@base)
end
end end
class PackageConfig < Base class PackageConfig < Base
attr_accessor :name attr_accessor :name
attr_accessor :delimiter
attr_accessor :delimiter_regex
end end
class ChefConfig < Base class ChefConfig < Base
@ -84,6 +90,11 @@ module Vagrant
class VagrantConfig < Base class VagrantConfig < Base
attr_accessor :log_output attr_accessor :log_output
attr_accessor :home
def home
File.expand_path(@home)
end
end end
class Top < Base class Top < Base

View File

@ -1,6 +1,6 @@
module Vagrant module Vagrant
module Util module Util
def self.included?(base) def self.included(base)
base.extend Vagrant::Util base.extend Vagrant::Util
end end

View File

@ -2,12 +2,71 @@ module Vagrant
class VM class VM
include Vagrant::Util include Vagrant::Util
attr_reader :vm attr_reader :vm
attr_accessor :from
class << self class << self
# Bring up the virtual machine. Imports the base image and # Bring up the virtual machine. Imports the base image and
# provisions it. # provisions it.
def up def up(from=Vagrant.config[:vm][:base])
new.create 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 end
# Finds a virtual machine by a given UUID and either returns # Finds a virtual machine by a given UUID and either returns
@ -76,7 +135,7 @@ error
def import def import
logger.info "Importing base VM (#{Vagrant.config[:vm][:base]})..." 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 end
def persist def persist
@ -173,6 +232,7 @@ error
# TODO the longest method, needs to be split up # TODO the longest method, needs to be split up
def package(name, to) def package(name, to)
delimiter = Vagrant.config[:package][:delimiter]
folder = FileUtils.mkpath(File.join(to, name)) folder = FileUtils.mkpath(File.join(to, name))
logger.info "Creating working directory: #{folder} ..." logger.info "Creating working directory: #{folder} ..."
@ -182,17 +242,15 @@ error
logger.info "Exporting required VM files to working directory ..." logger.info "Exporting required VM files to working directory ..."
@vm.export(ovf_path) @vm.export(ovf_path)
# TODO use zlib ... logger.info "Packaging VM into #{tar_path} ..."
logger.info "Packaging VM into #{name}.box ..." Zlib::GzipWriter.open(tar_path) do |gz|
Tar.open(tar_path, File::CREAT | File::WRONLY, 0644, Tar::GNU) do |tar| first_file = true
begin Dir.new(folder).each do |file|
# appending the expanded file path adds the whole folder tree next if File.directory?(file)
# to the tar archive there must be a better way # Delimit the files, and guarantee new line for next file if not the first
working_dir = FileUtils.pwd gz.write "#{delimiter}#{file}#{delimiter}"
FileUtils.cd(to) File.open(File.join(folder, file)).each { |line| gz.write(line) }
tar.append_tree(name) first_file = false
ensure
FileUtils.cd(working_dir)
end end
end end
@ -202,6 +260,7 @@ error
tar_path tar_path
end end
# TODO need a better way to which controller is the hd # TODO need a better way to which controller is the hd
def hd def hd
@vm.storage_controllers.first.devices.first @vm.storage_controllers.first.devices.first

View File

@ -36,11 +36,16 @@ class Test::Unit::TestCase
config.vm.project_directory = "/hobo" config.vm.project_directory = "/hobo"
config.vm.forward_port("ssh", 22, 2222) config.vm.forward_port("ssh", 22, 2222)
config.package.delimiter = 'V'
config.package.delimiter_regex = /'V(.+)V'/
config.chef.cookbooks_path = "cookbooks" config.chef.cookbooks_path = "cookbooks"
config.chef.provisioning_path = "/tmp/hobo-chef" config.chef.provisioning_path = "/tmp/hobo-chef"
config.chef.json = { config.chef.json = {
:recipes => ["hobo_main"] :recipes => ["hobo_main"]
} }
config.vagrant.home = '~/.home'
end end
Vagrant::Config.execute! Vagrant::Config.execute!

View File

@ -15,6 +15,7 @@ class VMTest < Test::Unit::TestCase
should "create a Vagrant::VM instance and call create" do should "create a Vagrant::VM instance and call create" do
inst = mock("instance") inst = mock("instance")
inst.expects(:create).once inst.expects(:create).once
inst.expects(:from=).with(File.expand_path(Vagrant.config[:vm][:base]))
Vagrant::VM.expects(:new).returns(inst) Vagrant::VM.expects(:new).returns(inst)
Vagrant::VM.up Vagrant::VM.up
end end
@ -242,6 +243,7 @@ class VMTest < Test::Unit::TestCase
end end
end end
# TODO more comprehensive testing
context "packaging a vm" do context "packaging a vm" do
should "dump the three necessary files to a tar in the current working dir" do should "dump the three necessary files to a tar in the current working dir" do
location = FileUtils.pwd location = FileUtils.pwd
@ -250,10 +252,24 @@ class VMTest < Test::Unit::TestCase
@mock_vm.expects(:export).with(File.join(new_dir, "#{name}.ovf")) @mock_vm.expects(:export).with(File.join(new_dir, "#{name}.ovf"))
FileUtils.expects(:mkpath).with(new_dir).returns(new_dir) FileUtils.expects(:mkpath).with(new_dir).returns(new_dir)
FileUtils.expects(:rm_r).with(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 # TODO test whats passed to the open tar.append_tree
assert_equal Vagrant::VM.new(@mock_vm).package(name, location), "#{new_dir}.box" assert_equal Vagrant::VM.new(@mock_vm).package(name, location), "#{new_dir}.box"
end end
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 end