diff --git a/plugins/commands/package/command.rb b/plugins/commands/package/command.rb index d8b5e35fb..e1f14721a 100644 --- a/plugins/commands/package/command.rb +++ b/plugins/commands/package/command.rb @@ -6,25 +6,25 @@ module VagrantPlugins def execute options = {} - opts = OptionParser.new do |opts| - opts.banner = "Usage: vagrant package [vm-name] [--base name] [--output name.box]" - opts.separator " [--include one,two,three] [--vagrantfile file]" + opts = OptionParser.new do |o| + o.banner = "Usage: vagrant package [vm-name] [--base name] [--output name.box]" + o.separator " [--include one,two,three] [--vagrantfile file]" - opts.separator "" + o.separator "" - opts.on("--base NAME", "Name of a VM in virtualbox to package as a base box") do |b| + o.on("--base NAME", "Name of a VM in virtualbox to package as a base box") do |b| options[:base] = b end - opts.on("--output NAME", "Name of the file to output") do |o| - options[:output] = o + o.on("--output NAME", "Name of the file to output") do |output| + options[:output] = output end - opts.on("--include x,y,z", Array, "Additional files to package with the box.") do |i| + o.on("--include x,y,z", Array, "Additional files to package with the box.") do |i| options[:include] = i end - opts.on("--vagrantfile file", "Vagrantfile to package with the box.") do |v| + o.on("--vagrantfile file", "Vagrantfile to package with the box.") do |v| options[:vagrantfile] = v end end @@ -42,7 +42,7 @@ module VagrantPlugins # Success, exit status 0 0 - end + end protected @@ -55,7 +55,6 @@ module VagrantPlugins def package_target(name, options) with_target_vms(name, :single_target => true) do |vm| - raise Vagrant::Errors::VMNotCreatedError if !vm.created? @logger.debug("Packaging VM: #{vm.name}") package_vm(vm, options) end @@ -68,7 +67,7 @@ module VagrantPlugins acc end - vm.package(opts) + vm.action(:package, opts) end end end diff --git a/plugins/providers/virtualbox/action.rb b/plugins/providers/virtualbox/action.rb index 47f5021c0..a775c72e3 100644 --- a/plugins/providers/virtualbox/action.rb +++ b/plugins/providers/virtualbox/action.rb @@ -22,6 +22,7 @@ module VagrantPlugins autoload :DestroyConfirm, File.expand_path("../action/destroy_confirm", __FILE__) autoload :DestroyUnusedNetworkInterfaces, File.expand_path("../action/destroy_unused_network_interfaces", __FILE__) autoload :DiscardState, File.expand_path("../action/discard_state", __FILE__) + autoload :Export, File.expand_path("../action/export", __FILE__) autoload :ForwardPorts, File.expand_path("../action/forward_ports", __FILE__) autoload :Halt, File.expand_path("../action/halt", __FILE__) autoload :HostName, File.expand_path("../action/host_name", __FILE__) @@ -34,11 +35,14 @@ module VagrantPlugins autoload :MessageWillNotDestroy, File.expand_path("../action/message_will_not_destroy", __FILE__) autoload :Network, File.expand_path("../action/network", __FILE__) autoload :NFS, File.expand_path("../action/nfs", __FILE__) + autoload :Package, File.expand_path("../action/package", __FILE__) + autoload :PackageVagrantfile, File.expand_path("../action/package_vagrantfile", __FILE__) autoload :Provision, File.expand_path("../action/provision", __FILE__) autoload :ProvisionerCleanup, File.expand_path("../action/provisioner_cleanup", __FILE__) autoload :PruneNFSExports, File.expand_path("../action/prune_nfs_exports", __FILE__) autoload :Resume, File.expand_path("../action/resume", __FILE__) autoload :SaneDefaults, File.expand_path("../action/sane_defaults", __FILE__) + autoload :SetupPackageFiles, File.expand_path("../action/setup_package_files", __FILE__) autoload :ShareFolders, File.expand_path("../action/share_folders", __FILE__) autoload :Suspend, File.expand_path("../action/suspend", __FILE__) @@ -116,6 +120,28 @@ module VagrantPlugins end end + # This action packages the virtual machine into a single box file. + def self.action_package + Vagrant::Action::Builder.new.tap do |b| + b.use CheckVirtualbox + b.use Call, Created do |env1, b2| + if !env1[:result] + b2.use MessageNotCreated + next + end + + b2.use SetupPackageFiles + b2.use CheckAccessible + b2.use action_halt + b2.use ClearForwardedPorts + b2.use ClearSharedFolders + b2.use Export + b2.use PackageVagrantfile + b2.use Package + end + end + end + # This action just runs the provisioners on the machine. def self.action_provision Vagrant::Action::Builder.new.tap do |b| diff --git a/plugins/providers/virtualbox/action/export.rb b/plugins/providers/virtualbox/action/export.rb new file mode 100644 index 000000000..2bf9f3353 --- /dev/null +++ b/plugins/providers/virtualbox/action/export.rb @@ -0,0 +1,56 @@ +require "fileutils" + +module VagrantPlugins + module ProviderVirtualBox + module Action + class Export + attr_reader :temp_dir + + def initialize(app, env) + @app = app + end + + def call(env) + @env = env + + raise Vagrant::Errors::VMPowerOffToPackage if @env[:machine].provider.state != :poweroff + + setup_temp_dir + export + + @app.call(env) + + recover(env) # called to cleanup temp directory + end + + def recover(env) + if temp_dir && File.exist?(temp_dir) + FileUtils.rm_rf(temp_dir) + end + end + + def setup_temp_dir + @env[:ui].info I18n.t("vagrant.actions.vm.export.create_dir") + @temp_dir = @env["export.temp_dir"] = @env[:tmp_path].join(Time.now.to_i.to_s) + FileUtils.mkpath(@env["export.temp_dir"]) + end + + def export + @env[:ui].info I18n.t("vagrant.actions.vm.export.exporting") + @env[:machine].provider.driver.export(ovf_path) do |progress| + @env[:ui].clear_line + @env[:ui].report_progress(progress.percent, 100, false) + end + + # Clear the line a final time so the next data can appear + # alone on the line. + @env[:ui].clear_line + end + + def ovf_path + File.join(@env["export.temp_dir"], "box.ovf") + end + end + end + end +end diff --git a/plugins/providers/virtualbox/action/package.rb b/plugins/providers/virtualbox/action/package.rb new file mode 100644 index 000000000..5a6c581ce --- /dev/null +++ b/plugins/providers/virtualbox/action/package.rb @@ -0,0 +1,20 @@ +require 'vagrant/action/general/package' + +module VagrantPlugins + module ProviderVirtualBox + module Action + class Package < Vagrant::Action::General::Package + # Doing this so that we can test that the parent is properly + # called in the unit tests. + alias_method :general_call, :call + def call(env) + # Just match up a couple environmental variables so that + # the superclass will do the right thing. Then, call the + # superclass + env["package.directory"] = env["export.temp_dir"] + general_call(env) + end + end + end + end +end diff --git a/plugins/providers/virtualbox/action/package_vagrantfile.rb b/plugins/providers/virtualbox/action/package_vagrantfile.rb new file mode 100644 index 000000000..0517718ee --- /dev/null +++ b/plugins/providers/virtualbox/action/package_vagrantfile.rb @@ -0,0 +1,33 @@ +require 'vagrant/util/template_renderer' + +module VagrantPlugins + module ProviderVirtualBox + module Action + class PackageVagrantfile + # For TemplateRenderer + include Vagrant::Util + + def initialize(app, env) + @app = app + end + + def call(env) + @env = env + create_vagrantfile + @app.call(env) + end + + # This method creates the auto-generated Vagrantfile at the root of the + # box. This Vagrantfile contains the MAC address so that the user doesn't + # have to worry about it. + def create_vagrantfile + File.open(File.join(@env["export.temp_dir"], "Vagrantfile"), "w") do |f| + f.write(TemplateRenderer.render("package_Vagrantfile", { + :base_mac => @env[:machine].provider.driver.read_mac_address + })) + end + end + end + end + end +end diff --git a/plugins/providers/virtualbox/action/setup_package_files.rb b/plugins/providers/virtualbox/action/setup_package_files.rb new file mode 100644 index 000000000..93e1fdd89 --- /dev/null +++ b/plugins/providers/virtualbox/action/setup_package_files.rb @@ -0,0 +1,51 @@ +module VagrantPlugins + module ProviderVirtualBox + module Action + class SetupPackageFiles + def initialize(app, env) + @app = app + + env["package.include"] ||= [] + env["package.vagrantfile"] ||= nil + end + + def call(env) + files = {} + env["package.include"].each do |file| + source = Pathname.new(file) + dest = nil + + # If the source is relative then we add the file as-is to the include + # directory. Otherwise, we copy only the file into the root of the + # include directory. Kind of strange, but seems to match what people + # expect based on history. + if source.relative? + dest = source + else + dest = source.basename + end + + # Assign the mapping + files[file] = dest + end + + if env["package.vagrantfile"] + # Vagrantfiles are treated special and mapped to a specific file + files[env["package.vagrantfile"]] = "_Vagrantfile" + end + + # Verify the mapping + files.each do |from, _| + raise Vagrant::Errors::PackageIncludeMissing, + :file => from if !File.exist?(from) + end + + # Save the mapping + env["package.files"] = files + + @app.call(env) + end + end + end + end +end