diff --git a/lib/vagrant/action/general/package_setup_files.rb b/lib/vagrant/action/general/package_setup_files.rb new file mode 100644 index 000000000..b8882f3eb --- /dev/null +++ b/lib/vagrant/action/general/package_setup_files.rb @@ -0,0 +1,51 @@ +module Vagrant + module Action + module General + class PackageSetupFiles + 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 diff --git a/lib/vagrant/action/general/package_setup_folders.rb b/lib/vagrant/action/general/package_setup_folders.rb new file mode 100644 index 000000000..387c11999 --- /dev/null +++ b/lib/vagrant/action/general/package_setup_folders.rb @@ -0,0 +1,37 @@ +require "fileutils" +require_relative "package" + +module Vagrant + module Action + module General + class PackageSetupFolders + include Vagrant::Util::Presence + + def initialize(app, env) + @app = app + end + + def call(env) + env["package.output"] ||= "package.box" + env["package.directory"] ||= Dir.mktmpdir("vagrant-package-", env[:tmp_path]) + + # Match up a couple environmental variables so that the other parts of + # Vagrant will do the right thing. + env["export.temp_dir"] = env["package.directory"] + + Vagrant::Action::General::Package.validate!( + env["package.output"], env["package.directory"]) + + @app.call(env) + end + + def recover(env) + dir = env["package.directory"] + if File.exist?(dir) + FileUtils.rm_rf(dir) + end + end + end + end + end +end diff --git a/plugins/providers/hyperv/action.rb b/plugins/providers/hyperv/action.rb index ade9fd4f4..2f310acb5 100644 --- a/plugins/providers/hyperv/action.rb +++ b/plugins/providers/hyperv/action.rb @@ -64,6 +64,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 CheckEnabled + b.use Call, IsState, :not_created do |env1, b2| + if env1[:result] + b2.use Message, I18n.t("vagrant_hyperv.message_not_created") + next + end + + b2.use PackageSetupFolders + b2.use PackageSetupFiles + b2.use action_halt + b2.use SyncedFolderCleanup + b2.use Package + b2.use PackageVagrantfile + b2.use PackageMetadataJson + b2.use Export + end + end + end + def self.action_provision Vagrant::Action::Builder.new.tap do |b| b.use ConfigValidate @@ -261,9 +283,16 @@ module VagrantPlugins # The autoload farm action_root = Pathname.new(File.expand_path("../action", __FILE__)) + autoload :PackageSetupFolders, action_root.join("package_setup_folders") + autoload :PackageSetupFiles, action_root.join("package_setup_files") + autoload :PackageVagrantfile, action_root.join("package_vagrantfile") + autoload :PackageMetadataJson, action_root.join("package_metadata_json") + autoload :Export, action_root.join("export") + autoload :CheckEnabled, action_root.join("check_enabled") autoload :DeleteVM, action_root.join("delete_vm") autoload :Import, action_root.join("import") + autoload :Package, action_root.join("package") autoload :IsWindows, action_root.join("is_windows") autoload :ReadState, action_root.join("read_state") autoload :ResumeVM, action_root.join("resume_vm") diff --git a/plugins/providers/hyperv/action/export.rb b/plugins/providers/hyperv/action/export.rb new file mode 100644 index 000000000..d3d8929ed --- /dev/null +++ b/plugins/providers/hyperv/action/export.rb @@ -0,0 +1,39 @@ +require "fileutils" + +module VagrantPlugins + module HyperV + module Action + class Export + def initialize(app, env) + @app = app + end + + def call(env) + @env = env + + @env[:ui].info @env[:machine].state.id.to_s + + raise Vagrant::Errors::VMPowerOffToPackage if + @env[:machine].state.id != :off + + export + + @app.call(env) + end + + def export + @env[:ui].info I18n.t("vagrant.actions.vm.export.exporting") + @env[:machine].provider.driver.export(@env["export.temp_dir"]) 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 + + end + end + end +end diff --git a/plugins/providers/hyperv/action/package.rb b/plugins/providers/hyperv/action/package.rb new file mode 100644 index 000000000..62b1a3774 --- /dev/null +++ b/plugins/providers/hyperv/action/package.rb @@ -0,0 +1,16 @@ +require_relative "../../../../lib/vagrant/action/general/package" + +module VagrantPlugins + module HyperV + 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) + general_call(env) + end + end + end + end +end diff --git a/plugins/providers/hyperv/action/package_metadata_json.rb b/plugins/providers/hyperv/action/package_metadata_json.rb new file mode 100644 index 000000000..de2364268 --- /dev/null +++ b/plugins/providers/hyperv/action/package_metadata_json.rb @@ -0,0 +1,34 @@ +require "json" + +#require 'vagrant/util/template_renderer' + +module VagrantPlugins + module HyperV + module Action + class PackageMetadataJson + # For TemplateRenderer + include Vagrant::Util + + def initialize(app, env) + @app = app + end + + def call(env) + @env = env + create_metadata + @app.call(env) + end + + # This method creates a metadata.json file to tell vagrant this is a + # Hyper V box + def create_metadata + File.open(File.join(@env["export.temp_dir"], "metadata.json"), "w") do |f| + f.write(JSON.generate({ + provider: "hyperv" + })) + end + end + end + end + end +end diff --git a/plugins/providers/hyperv/action/package_setup_files.rb b/plugins/providers/hyperv/action/package_setup_files.rb new file mode 100644 index 000000000..ea0fd1b6c --- /dev/null +++ b/plugins/providers/hyperv/action/package_setup_files.rb @@ -0,0 +1,16 @@ +require_relative "../../../../lib/vagrant/action/general/package_setup_files" + +module VagrantPlugins + module HyperV + module Action + class PackageSetupFiles < Vagrant::Action::General::PackageSetupFiles + # 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) + general_call(env) + end + end + end + end +end diff --git a/plugins/providers/hyperv/action/package_setup_folders.rb b/plugins/providers/hyperv/action/package_setup_folders.rb new file mode 100644 index 000000000..12a2ff190 --- /dev/null +++ b/plugins/providers/hyperv/action/package_setup_folders.rb @@ -0,0 +1,18 @@ +require "fileutils" + +require_relative "../../../../lib/vagrant/action/general/package_setup_folders" + +module VagrantPlugins + module HyperV + module Action + class PackageSetupFolders < Vagrant::Action::General::PackageSetupFolders + # 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) + general_call(env) + end + end + end + end +end diff --git a/plugins/providers/hyperv/action/package_vagrantfile.rb b/plugins/providers/hyperv/action/package_vagrantfile.rb new file mode 100644 index 000000000..171daa065 --- /dev/null +++ b/plugins/providers/hyperv/action/package_vagrantfile.rb @@ -0,0 +1,34 @@ +require 'vagrant/util/template_renderer' + +module VagrantPlugins + module HyperV + 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| + mac_address = @env[:machine].provider.driver.read_mac_address + f.write(TemplateRenderer.render("package_Vagrantfile", { + base_mac: mac_address["mac"] + })) + end + end + end + end + end +end diff --git a/plugins/providers/hyperv/driver.rb b/plugins/providers/hyperv/driver.rb index 1a2f1dd39..abd353663 100644 --- a/plugins/providers/hyperv/driver.rb +++ b/plugins/providers/hyperv/driver.rb @@ -53,10 +53,18 @@ module VagrantPlugins execute('delete_vm.ps1', { VmId: vm_id }) end + def export(path) + execute('export_vm.ps1', {VmId: vm_id, Path: path}) + end + def read_guest_ip execute('get_network_config.ps1', { VmId: vm_id }) end + def read_mac_address + execute('get_network_mac.ps1', { VmId: vm_id }) + end + def resume execute('resume_vm.ps1', { VmId: vm_id }) end diff --git a/plugins/providers/hyperv/scripts/export_vm.ps1 b/plugins/providers/hyperv/scripts/export_vm.ps1 new file mode 100644 index 000000000..b13429f49 --- /dev/null +++ b/plugins/providers/hyperv/scripts/export_vm.ps1 @@ -0,0 +1,15 @@ +Param( + [Parameter(Mandatory=$true)] + [string]$VmId, + [Parameter(Mandatory=$true)] + [string]$Path +) + +$vm = Get-VM -Id $VmId -ErrorAction "Stop" +$vm | Export-VM -Path $Path + +# Prepare directory structure for box import +$name = $vm.Name +Move-Item $Path/$name/* $Path +Remove-Item -Path $Path/Snapshots -Force -Recurse +Remove-Item -Path $Path/$name -Force \ No newline at end of file diff --git a/plugins/providers/hyperv/scripts/get_network_mac.ps1 b/plugins/providers/hyperv/scripts/get_network_mac.ps1 new file mode 100644 index 000000000..017a16b4a --- /dev/null +++ b/plugins/providers/hyperv/scripts/get_network_mac.ps1 @@ -0,0 +1,28 @@ +Param( + [Parameter(Mandatory=$true)] + [string]$VmId + ) + +# Include the following modules +$Dir = Split-Path $script:MyInvocation.MyCommand.Path +. ([System.IO.Path]::Combine($Dir, "utils\write_messages.ps1")) + +$ip_address = "" +$vm = Get-VM -Id $VmId -ErrorAction "Stop" +$networks = Get-VMNetworkAdapter -VM $vm +foreach ($network in $networks) { + if ($network.MacAddress -gt 0) { + $mac_address = $network.MacAddress + if (-Not ([string]::IsNullOrEmpty($mac_address))) { + # We found our mac address! + break + } + } +} + + +$resultHash = @{ + mac = "$mac_address" +} +$result = ConvertTo-Json $resultHash +Write-Output-Message $result \ No newline at end of file diff --git a/plugins/providers/virtualbox/action/package_setup_files.rb b/plugins/providers/virtualbox/action/package_setup_files.rb index a70c7751a..f256b173d 100644 --- a/plugins/providers/virtualbox/action/package_setup_files.rb +++ b/plugins/providers/virtualbox/action/package_setup_files.rb @@ -1,49 +1,14 @@ +require_relative "../../../../lib/vagrant/action/general/package_setup_files" + module VagrantPlugins module ProviderVirtualBox module Action - class PackageSetupFiles - def initialize(app, env) - @app = app - - env["package.include"] ||= [] - env["package.vagrantfile"] ||= nil - end - + class PackageSetupFiles < Vagrant::Action::General::PackageSetupFiles + # 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) - 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) + general_call(env) end end end diff --git a/plugins/providers/virtualbox/action/package_setup_folders.rb b/plugins/providers/virtualbox/action/package_setup_folders.rb index 26a6503a5..a0baf516f 100644 --- a/plugins/providers/virtualbox/action/package_setup_folders.rb +++ b/plugins/providers/virtualbox/action/package_setup_folders.rb @@ -1,36 +1,16 @@ require "fileutils" -require_relative "../../../../lib/vagrant/action/general/package" +require_relative "../../../../lib/vagrant/action/general/package_setup_folders" module VagrantPlugins module ProviderVirtualBox module Action - class PackageSetupFolders - include Vagrant::Util::Presence - - def initialize(app, env) - @app = app - end - + class PackageSetupFolders < Vagrant::Action::General::PackageSetupFolders + # 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) - env["package.output"] ||= "package.box" - env["package.directory"] ||= Dir.mktmpdir("vagrant-package-", env[:tmp_path]) - - # Match up a couple environmental variables so that the other parts of - # Vagrant will do the right thing. - env["export.temp_dir"] = env["package.directory"] - - Vagrant::Action::General::Package.validate!( - env["package.output"], env["package.directory"]) - - @app.call(env) - end - - def recover(env) - dir = env["package.directory"] - if File.exist?(dir) - FileUtils.rm_rf(dir) - end + general_call(env) end end end