From e5bc30f76824d8639e18a28c63d42e0c4da13ff5 Mon Sep 17 00:00:00 2001 From: Mikhail Zholobov Date: Wed, 30 Mar 2016 10:48:47 +0300 Subject: [PATCH 01/23] Fix "parallels_not_detected" error message --- locales/en.yml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/locales/en.yml b/locales/en.yml index e39fdb45..8905b6cc 100644 --- a/locales/en.yml +++ b/locales/en.yml @@ -69,12 +69,12 @@ en: appears that every slot is in use. Please lower the number of used network adapters. parallels_not_detected: |- - Vagrant could not detect Parallels Desktop! Make sure it is properly installed. - Vagrant uses the `prlctl` binary that ships with Parallels Desktop, and requires - this to be available on the PATH. If Parallels Desktop is installed, please find - the `prlctl` binary and add it to the PATH environmental variable. + Vagrant could not detect Parallels Desktop Pro! Make sure it is properly installed. + Vagrant uses the `prlctl` binary that only ships with Pro and Business + editions of Parallels Desktop. If the one is installed, please make sure + that the `prlctl` binary is available on the PATH environment variable. parallels_tools_iso_not_found: |- - Parallels Tools ISO file does not exists. The Parallels provider uses it + Parallels Tools ISO file does not exist. The Parallels provider uses it to install or update Parallels Tools in the guest machine. Try to reinstall Parallels Desktop. From ca7d786d9f3cefc6caa8651748a071ceabf1758d Mon Sep 17 00:00:00 2001 From: Mikhail Zholobov Date: Wed, 30 Mar 2016 15:48:25 +0300 Subject: [PATCH 02/23] Disable home folder sharing by default Implements GH-171 --- lib/vagrant-parallels/action/sane_defaults.rb | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/vagrant-parallels/action/sane_defaults.rb b/lib/vagrant-parallels/action/sane_defaults.rb index 16daa606..6a483170 100644 --- a/lib/vagrant-parallels/action/sane_defaults.rb +++ b/lib/vagrant-parallels/action/sane_defaults.rb @@ -61,7 +61,8 @@ def default_settings settings.merge!( startup_view: 'headless', time_sync: 'on', - disable_timezone_sync: 'on' + disable_timezone_sync: 'on', + shf_host_defined: 'off' ) settings From f5bbbdc8e2377d5081b840310450340697c74b67 Mon Sep 17 00:00:00 2001 From: Mikhail Zholobov Date: Fri, 8 Apr 2016 10:48:45 +0300 Subject: [PATCH 03/23] website: Add Gemfile.lock --- .gitignore | 2 +- website/docs/Gemfile.lock | 115 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 116 insertions(+), 1 deletion(-) create mode 100644 website/docs/Gemfile.lock diff --git a/.gitignore b/.gitignore index 94b80470..f29862d8 100644 --- a/.gitignore +++ b/.gitignore @@ -13,7 +13,7 @@ vagrant-spec.config.rb .bundle pkg/* tags -Gemfile.lock +/Gemfile.lock test/tmp/ /vendor diff --git a/website/docs/Gemfile.lock b/website/docs/Gemfile.lock new file mode 100644 index 00000000..714736eb --- /dev/null +++ b/website/docs/Gemfile.lock @@ -0,0 +1,115 @@ +GEM + remote: https://rubygems.org/ + specs: + activesupport (3.2.22.2) + i18n (~> 0.6, >= 0.6.4) + multi_json (~> 1.0) + chunky_png (1.3.5) + coffee-script (2.2.0) + coffee-script-source + execjs + coffee-script-source (1.10.0) + commonjs (0.2.7) + compass (1.0.3) + chunky_png (~> 1.2) + compass-core (~> 1.0.2) + compass-import-once (~> 1.0.5) + rb-fsevent (>= 0.9.3) + rb-inotify (>= 0.9) + sass (>= 3.3.13, < 3.5) + compass-core (1.0.3) + multi_json (~> 1.0) + sass (>= 3.3.0, < 3.5) + compass-import-once (1.0.5) + sass (>= 3.2, < 3.5) + execjs (1.4.1) + multi_json (~> 1.0) + ffi (1.9.10) + haml (4.0.7) + tilt + hike (1.2.3) + i18n (0.6.11) + json (1.8.3) + kramdown (1.10.0) + less (2.2.2) + commonjs (~> 0.2.6) + libv8 (3.16.14.13) + listen (1.3.1) + rb-fsevent (>= 0.9.3) + rb-inotify (>= 0.9) + rb-kqueue (>= 0.2) + middleman (3.2.2) + coffee-script (~> 2.2.0) + compass (>= 0.12.2) + execjs (~> 1.4.0) + haml (>= 3.1.6) + kramdown (~> 1.2) + middleman-core (= 3.2.2) + middleman-sprockets (>= 3.1.2) + sass (>= 3.1.20) + uglifier (~> 2.4.0) + middleman-core (3.2.2) + activesupport (~> 3.2.6) + bundler (~> 1.1) + i18n (~> 0.6.9) + listen (~> 1.1) + rack (>= 1.4.5) + rack-test (~> 0.6.1) + thor (>= 0.15.2, < 2.0) + tilt (~> 1.4.1) + middleman-deploy (1.0.0) + middleman-core (>= 3.2) + net-sftp + ptools + middleman-sprockets (3.3.3) + middleman-core (>= 3.2) + sprockets (~> 2.2) + sprockets-helpers (~> 1.1.0) + sprockets-sass (~> 1.1.0) + multi_json (1.11.2) + net-sftp (2.1.2) + net-ssh (>= 2.6.5) + net-ssh (3.1.1) + ptools (1.3.3) + rack (1.6.4) + rack-contrib (1.1.0) + rack (>= 0.9.1) + rack-test (0.6.3) + rack (>= 1.0) + rb-fsevent (0.9.7) + rb-inotify (0.9.7) + ffi (>= 0.5.0) + rb-kqueue (0.2.4) + ffi (>= 0.5.0) + redcarpet (2.2.2) + ref (2.0.0) + sass (3.4.22) + sprockets (2.12.4) + hike (~> 1.2) + multi_json (~> 1.0) + rack (~> 1.0) + tilt (~> 1.1, != 1.3.0) + sprockets-helpers (1.1.0) + sprockets (~> 2.0) + sprockets-sass (1.1.0) + sprockets (~> 2.0) + tilt (~> 1.1) + therubyracer (0.12.2) + libv8 (~> 3.16.14.0) + ref + thor (0.19.1) + tilt (1.4.1) + uglifier (2.4.0) + execjs (>= 0.3.0) + json (>= 1.8.0) + +PLATFORMS + ruby + +DEPENDENCIES + less (~> 2.2.2) + middleman (~> 3.2.0) + middleman-deploy (~> 1.0) + rack-contrib (~> 1.1.0) + redcarpet (~> 2.2.2) + therubyracer (~> 0.12.0) From 009828434c2c27b7ccf78a40ab8ed1e45036ccc3 Mon Sep 17 00:00:00 2001 From: Mikhail Zholobov Date: Fri, 8 Apr 2016 11:01:47 +0300 Subject: [PATCH 04/23] Add middleman build directory to the root .gitignore --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index f29862d8..d61e23ec 100644 --- a/.gitignore +++ b/.gitignore @@ -6,6 +6,7 @@ acceptance_config.yml boxes/* /Vagrantfile .vagrant +/website/docs/build vagrant-spec.config.rb # Bundler/Rubygems From a597aa05eab69f97164ffd19ad2300ff460d733e Mon Sep 17 00:00:00 2001 From: Mikhail Zholobov Date: Fri, 8 Apr 2016 11:02:58 +0300 Subject: [PATCH 05/23] website: update gems --- website/docs/Gemfile | 10 ++-- website/docs/Gemfile.lock | 109 +++++++++++++++++++++++--------------- 2 files changed, 72 insertions(+), 47 deletions(-) diff --git a/website/docs/Gemfile b/website/docs/Gemfile index f044cfdd..3ed0e933 100644 --- a/website/docs/Gemfile +++ b/website/docs/Gemfile @@ -1,8 +1,8 @@ source 'https://rubygems.org' -gem 'less', '~> 2.2.2' -gem 'middleman', '~> 3.2.0' +gem 'less', '~> 2.6' +gem 'middleman', '~> 3.3.0' gem 'middleman-deploy', '~> 1.0' -gem 'rack-contrib', '~> 1.1.0' -gem 'redcarpet', '~> 2.2.2' -gem 'therubyracer', '~> 0.12.0' +gem 'rack-contrib', '~> 1.2' +gem 'redcarpet', '~> 3.2' +gem 'therubyracer', '~> 0.12' diff --git a/website/docs/Gemfile.lock b/website/docs/Gemfile.lock index 714736eb..377f343b 100644 --- a/website/docs/Gemfile.lock +++ b/website/docs/Gemfile.lock @@ -1,11 +1,16 @@ GEM remote: https://rubygems.org/ specs: - activesupport (3.2.22.2) - i18n (~> 0.6, >= 0.6.4) - multi_json (~> 1.0) + activesupport (4.1.15) + i18n (~> 0.6, >= 0.6.9) + json (~> 1.7, >= 1.7.7) + minitest (~> 5.1) + thread_safe (~> 0.1) + tzinfo (~> 1.1) + celluloid (0.16.0) + timers (~> 4.0.0) chunky_png (1.3.5) - coffee-script (2.2.0) + coffee-script (2.4.1) coffee-script-source execjs coffee-script-source (1.10.0) @@ -22,66 +27,80 @@ GEM sass (>= 3.3.0, < 3.5) compass-import-once (1.0.5) sass (>= 3.2, < 3.5) - execjs (1.4.1) - multi_json (~> 1.0) + erubis (2.7.0) + execjs (2.6.0) ffi (1.9.10) + git-version-bump (0.15.1) haml (4.0.7) tilt hike (1.2.3) - i18n (0.6.11) + hitimes (1.2.3) + hooks (0.4.1) + uber (~> 0.0.14) + i18n (0.7.0) json (1.8.3) kramdown (1.10.0) - less (2.2.2) - commonjs (~> 0.2.6) + less (2.6.0) + commonjs (~> 0.2.7) libv8 (3.16.14.13) - listen (1.3.1) + listen (2.10.1) + celluloid (~> 0.16.0) rb-fsevent (>= 0.9.3) rb-inotify (>= 0.9) - rb-kqueue (>= 0.2) - middleman (3.2.2) - coffee-script (~> 2.2.0) - compass (>= 0.12.2) - execjs (~> 1.4.0) - haml (>= 3.1.6) + middleman (3.3.12) + coffee-script (~> 2.2) + compass (>= 1.0.0, < 2.0.0) + compass-import-once (= 1.0.5) + execjs (~> 2.0) + haml (>= 4.0.5) kramdown (~> 1.2) - middleman-core (= 3.2.2) + middleman-core (= 3.3.12) middleman-sprockets (>= 3.1.2) - sass (>= 3.1.20) - uglifier (~> 2.4.0) - middleman-core (3.2.2) - activesupport (~> 3.2.6) + sass (>= 3.4.0, < 4.0) + uglifier (~> 2.5) + middleman-core (3.3.12) + activesupport (~> 4.1.0) bundler (~> 1.1) - i18n (~> 0.6.9) - listen (~> 1.1) - rack (>= 1.4.5) - rack-test (~> 0.6.1) + erubis + hooks (~> 0.3) + i18n (~> 0.7.0) + listen (>= 2.7.9, < 3.0) + padrino-helpers (~> 0.12.3) + rack (>= 1.4.5, < 2.0) + rack-test (~> 0.6.2) thor (>= 0.15.2, < 2.0) - tilt (~> 1.4.1) + tilt (~> 1.4.1, < 2.0) middleman-deploy (1.0.0) middleman-core (>= 3.2) net-sftp ptools - middleman-sprockets (3.3.3) - middleman-core (>= 3.2) - sprockets (~> 2.2) + middleman-sprockets (3.4.2) + middleman-core (>= 3.3) + sprockets (~> 2.12.1) sprockets-helpers (~> 1.1.0) - sprockets-sass (~> 1.1.0) + sprockets-sass (~> 1.3.0) + minitest (5.8.4) multi_json (1.11.2) net-sftp (2.1.2) net-ssh (>= 2.6.5) net-ssh (3.1.1) + padrino-helpers (0.12.5) + i18n (~> 0.6, >= 0.6.7) + padrino-support (= 0.12.5) + tilt (~> 1.4.1) + padrino-support (0.12.5) + activesupport (>= 3.1) ptools (1.3.3) rack (1.6.4) - rack-contrib (1.1.0) - rack (>= 0.9.1) + rack-contrib (1.4.0) + git-version-bump (~> 0.15) + rack (~> 1.4) rack-test (0.6.3) rack (>= 1.0) rb-fsevent (0.9.7) rb-inotify (0.9.7) ffi (>= 0.5.0) - rb-kqueue (0.2.4) - ffi (>= 0.5.0) - redcarpet (2.2.2) + redcarpet (3.3.4) ref (2.0.0) sass (3.4.22) sprockets (2.12.4) @@ -91,15 +110,21 @@ GEM tilt (~> 1.1, != 1.3.0) sprockets-helpers (1.1.0) sprockets (~> 2.0) - sprockets-sass (1.1.0) + sprockets-sass (1.3.1) sprockets (~> 2.0) tilt (~> 1.1) therubyracer (0.12.2) libv8 (~> 3.16.14.0) ref thor (0.19.1) + thread_safe (0.3.5) tilt (1.4.1) - uglifier (2.4.0) + timers (4.0.4) + hitimes + tzinfo (1.2.2) + thread_safe (~> 0.1) + uber (0.0.15) + uglifier (2.7.2) execjs (>= 0.3.0) json (>= 1.8.0) @@ -107,9 +132,9 @@ PLATFORMS ruby DEPENDENCIES - less (~> 2.2.2) - middleman (~> 3.2.0) + less (~> 2.6) + middleman (~> 3.3.0) middleman-deploy (~> 1.0) - rack-contrib (~> 1.1.0) - redcarpet (~> 2.2.2) - therubyracer (~> 0.12.0) + rack-contrib (~> 1.2) + redcarpet (~> 3.2) + therubyracer (~> 0.12) From 4f966131a1e112b790f94ba1a636d41aff526355 Mon Sep 17 00:00:00 2001 From: Mikhail Zholobov Date: Fri, 8 Apr 2016 12:05:26 +0300 Subject: [PATCH 06/23] website: Delete Rakefile --- website/docs/Rakefile | 1 - 1 file changed, 1 deletion(-) delete mode 100644 website/docs/Rakefile diff --git a/website/docs/Rakefile b/website/docs/Rakefile deleted file mode 100644 index 52a2b4c2..00000000 --- a/website/docs/Rakefile +++ /dev/null @@ -1 +0,0 @@ -require 'middleman-gh-pages' From 0b4ae5ff1267f2cf6da7504373a47295131adf7b Mon Sep 17 00:00:00 2001 From: Mikhail Zholobov Date: Wed, 13 Apr 2016 22:28:28 +0300 Subject: [PATCH 07/23] driver: Add retries for snapshot commands Sometimes prlctl could fail on "snapshot-delete'" and "snapshot-switch" commands with an error: "The operation cannot be performed at the moment. Data synchronization is currently in progress. Try to perform the operation later." --- lib/vagrant-parallels/driver/base.rb | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/lib/vagrant-parallels/driver/base.rb b/lib/vagrant-parallels/driver/base.rb index 2437a869..55c727b9 100644 --- a/lib/vagrant-parallels/driver/base.rb +++ b/lib/vagrant-parallels/driver/base.rb @@ -3,6 +3,7 @@ require 'vagrant/util/busy' require 'vagrant/util/network_ip' require 'vagrant/util/platform' +require 'vagrant/util/retryable' require 'vagrant/util/subprocess' require 'vagrant/util/which' @@ -161,7 +162,11 @@ def delete_disabled_adapters # @param [String] uuid Name or UUID of the target VM # @param [String] snapshot_id Snapshot ID def delete_snapshot(uuid, snapshot_id) - execute_prlctl('snapshot-delete', uuid, '--id', snapshot_id) + # Sometimes this command fails with 'Data synchronization is currently + # in progress'. Just wait and retry. + retryable(on: VagrantPlugins::Parallels::Errors::ExecutionError, tries: 2, sleep: 2) do + execute_prlctl('snapshot-delete', uuid, '--id', snapshot_id) + end end # Deletes any host only networks that aren't being used for anything. @@ -537,7 +542,11 @@ def register(pvm_file, opts=[]) # @param [String] uuid Name or UUID of the target VM # @param [String] snapshot_id Snapshot ID def restore_snapshot(uuid, snapshot_id) - execute_prlctl('snapshot-switch', uuid, '-i', snapshot_id) + # Sometimes this command fails with 'Data synchronization is currently + # in progress'. Just wait and retry. + retryable(on: VagrantPlugins::Parallels::Errors::ExecutionError, tries: 2, sleep: 2) do + execute_prlctl('snapshot-switch', uuid, '-i', snapshot_id) + end end # Resumes the virtual machine. From d383999580acf0137e99b625b4cf92dfb04ce418 Mon Sep 17 00:00:00 2001 From: Mikhail Zholobov Date: Wed, 23 Dec 2015 13:45:05 +0200 Subject: [PATCH 08/23] config: Add warning message about deprecated options --- lib/vagrant-parallels/config.rb | 26 ++++++++++++++++++-------- test/unit/config_test.rb | 30 ++++++++++++++++++++++++++++++ 2 files changed, 48 insertions(+), 8 deletions(-) diff --git a/lib/vagrant-parallels/config.rb b/lib/vagrant-parallels/config.rb index 26596f23..2bd6dc96 100644 --- a/lib/vagrant-parallels/config.rb +++ b/lib/vagrant-parallels/config.rb @@ -13,13 +13,13 @@ class Config < Vagrant.plugin('2', :config) attr_accessor :regen_src_uuid attr_accessor :update_guest_tools + # Deprecated options + attr_accessor :regen_box_uuid + attr_accessor :use_linked_clone + # Compatibility with virtualbox provider's syntax alias :check_guest_additions= :check_guest_tools= - # Compatibility with old names - alias :regen_box_uuid= :regen_src_uuid= - alias :use_linked_clone= :linked_clone= - def initialize @check_guest_tools = UNSET_VALUE @customizations = [] @@ -34,6 +34,10 @@ def initialize @update_guest_tools = UNSET_VALUE network_adapter(0, :shared) + + # Deprecated options + @regen_box_uuid = UNSET_VALUE + @use_linked_clone = UNSET_VALUE end def customize(*command) @@ -55,10 +59,6 @@ def cpus=(count) customize('pre-boot', ['set', :id, '--cpus', count.to_i]) end - def regen_box_uuid=(value) - @regen_src_uuid = value - end - def merge(other) super.tap do |result| c = customizations.dup @@ -68,6 +68,16 @@ def merge(other) end def finalize! + if @regen_box_uuid != UNSET_VALUE + puts "Parallels provider: Vagrantfile option 'regen_box_uuid' is deprecated and will be removed. Please, use 'regen_src_uuid' instead" + @regen_src_uuid = @regen_box_uuid if @regen_src_uuid == UNSET_VALUE + end + + if @use_linked_clone != UNSET_VALUE + puts "Parallels provider: Vagrantfile option 'use_linked_clone' is deprecated and will be removed. Please, use 'linked_clone' instead" + @linked_clone = @use_linked_clone if @linked_clone == UNSET_VALUE + end + if @check_guest_tools == UNSET_VALUE @check_guest_tools = true end diff --git a/test/unit/config_test.rb b/test/unit/config_test.rb index b935eb3d..e36676f9 100644 --- a/test/unit/config_test.rb +++ b/test/unit/config_test.rb @@ -88,4 +88,34 @@ def valid_defaults [:bridged, auto_config: true]) end end + + describe '#linked_clone' do + it 'is compatible with deprecated use_linked_lone' do + subject.use_linked_clone = true + subject.finalize! + expect(subject.linked_clone).to eql(true) + end + + it 'is not overridden by use_linked_lone' do + subject.linked_clone = false + subject.use_linked_clone = true + subject.finalize! + expect(subject.linked_clone).to eql(false) + end + end + + describe '#regen_src_uuid' do + it 'is compatible with deprecated regen_box_uuid' do + subject.regen_box_uuid = false + subject.finalize! + expect(subject.regen_src_uuid).to eql(false) + end + + it 'is not overridden by regen_box_uuid' do + subject.regen_src_uuid = true + subject.regen_box_uuid = false + subject.finalize! + expect(subject.regen_src_uuid).to eql(true) + end + end end From 32d7440b55189f39ed8812f35d48e7caa903dacc Mon Sep 17 00:00:00 2001 From: Mikhail Zholobov Date: Thu, 14 Apr 2016 13:03:02 +0300 Subject: [PATCH 09/23] Fix unit tests for provider configuration --- test/unit/config_test.rb | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/test/unit/config_test.rb b/test/unit/config_test.rb index e36676f9..d3239144 100644 --- a/test/unit/config_test.rb +++ b/test/unit/config_test.rb @@ -19,10 +19,6 @@ def assert_valid end end - def valid_defaults - subject.image = 'foo' - end - before do vm_config = double('vm_config') vm_config.stub(networks: []) @@ -39,10 +35,11 @@ def valid_defaults context 'defaults' do before { subject.finalize! } - it { expect(subject.check_guest_additions).to be_true } + it { expect(subject.check_guest_tools).to eq(true) } it { expect(subject.name).to be_nil } - it { expect(subject.functional_psf).to be_true } - it { expect(subject.optimize_power_consumption).to be_true } + it { expect(subject.functional_psf).to eq(true) } + it { expect(subject.linked_clone).to eq(false) } + it { expect(subject.regen_src_uuid).to eq(true) } it 'should have one Shared adapter' do expect(subject.network_adapters).to eql({ From 41bed9b58f237aaa4e5e2200c37d40c85b677555 Mon Sep 17 00:00:00 2001 From: Mikhail Zholobov Date: Tue, 19 Apr 2016 13:04:15 +0300 Subject: [PATCH 10/23] action/box_unregister: Fix #recover for layered envs There is no box specified in layered env. So, it's nothing to do in this action. --- lib/vagrant-parallels/action/box_unregister.rb | 3 +++ 1 file changed, 3 insertions(+) diff --git a/lib/vagrant-parallels/action/box_unregister.rb b/lib/vagrant-parallels/action/box_unregister.rb index fb00e4ee..8be49110 100644 --- a/lib/vagrant-parallels/action/box_unregister.rb +++ b/lib/vagrant-parallels/action/box_unregister.rb @@ -33,6 +33,9 @@ def call(env) end def recover(env) + # If we don't have a box, nothing to do + return if !env[:machine].box + unregister_box(env) end From 91db9cd93ec87eb0e92e9ce1e22f5d68d053b386 Mon Sep 17 00:00:00 2001 From: Mikhail Zholobov Date: Tue, 26 Apr 2016 13:15:19 +0300 Subject: [PATCH 11/23] Support for packaging linked clones It makes possible to use `vagrant package` for environments created as linked clones. External disk images will be automatically copied, so the resulted box become a full-sized standalone VM. --- lib/vagrant-parallels/action/export.rb | 75 +++++++++++++++++-- lib/vagrant-parallels/driver/base.rb | 24 +++--- lib/vagrant-parallels/driver/meta.rb | 2 +- lib/vagrant-parallels/errors.rb | 4 + locales/en.yml | 8 ++ test/acceptance/provider/package_spec.rb | 68 +++++++++++++++++ .../unit/support/shared/pd_driver_examples.rb | 14 ++-- 7 files changed, 165 insertions(+), 30 deletions(-) create mode 100644 test/acceptance/provider/package_spec.rb diff --git a/lib/vagrant-parallels/action/export.rb b/lib/vagrant-parallels/action/export.rb index caa491e9..878ac41e 100644 --- a/lib/vagrant-parallels/action/export.rb +++ b/lib/vagrant-parallels/action/export.rb @@ -12,8 +12,18 @@ def call(env) raise Vagrant::Errors::VMPowerOffToPackage end + # Clone source VM to the temporary copy clone(env) + @home_path = env[:machine].provider.driver.read_settings(env[:package_box_id]).fetch('Home') + @hdd_list = Dir.glob(File.join(@home_path, '*.hdd')) + + # Convert to full-sized VM, copy all external and linked disks (if any) + convert_to_full(env) + + # Compact all virtual disks compact(env) + + # Preparations completed. Unregister before packaging unregister_vm(env) @app.call(env) @@ -71,16 +81,69 @@ def clone(env) env[:ui].clear_line end + def convert_to_full(env) + is_linked = false + + @hdd_list.each do |hdd_dir| + disk_desc = File.join(hdd_dir, 'DiskDescriptor.xml') + xml = Nokogiri::XML(File.open disk_desc) + + linked_images = xml.xpath('//Parallels_disk_image/StorageData/Storage/Image/File').select do |hds| + Pathname.new(hds).absolute? + end + + # If this is a regular, not linked HDD, then skip it. Otherwise, + # remember this VM as a linked clone. + next if linked_images.empty? + is_linked = true + + + env[:ui].info I18n.t('vagrant_parallels.actions.vm.export.copying_linked_disks') + linked_images.each do |hds| + hds_path = hds.text + + if !File.exist?(hds_path) + raise VagrantPlugins::Parallels::Errors::ExternalDiskNotFound, + path: hds_path + end + + FileUtils.cp(hds_path, hdd_dir, preserve: true) + + # Save relative hds path to the XML file + hds.content = File.basename(hds_path) + end + + File.open(disk_desc, 'w') do |f| + f.write xml.to_xml + end + end + + # Flush elements LinkedVmUuid, LinkedSnapshotUuid from "config.pvs" + if is_linked + @logger.debug 'Converting linked clone to the regular VM' + config_pvs = File.join(@home_path, 'config.pvs') + + xml = Nokogiri::XML(File.open(config_pvs)) + xml.xpath('//ParallelsVirtualMachine/Identification/LinkedVmUuid').first.content = '' + xml.xpath('//ParallelsVirtualMachine/Identification/LinkedSnapshotUuid').first.content = '' + + File.open(config_pvs, 'w') do |f| + f.write xml.to_xml + end + end + end + def compact(env) env[:ui].info I18n.t('vagrant_parallels.actions.vm.export.compacting') - env[:machine].provider.driver.compact(env[:package_box_id]) do |progress| + @hdd_list.each do |hdd| + env[:machine].provider.driver.compact_hdd(hdd) do |progress| + env[:ui].clear_line + env[:ui].report_progress(progress, 100, false) + end + # Clear the line a final time so the next data can appear + # alone on the line. env[:ui].clear_line - env[:ui].report_progress(progress, 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 unregister_vm(env) diff --git a/lib/vagrant-parallels/driver/base.rb b/lib/vagrant-parallels/driver/base.rb index 55c727b9..d10efa45 100644 --- a/lib/vagrant-parallels/driver/base.rb +++ b/lib/vagrant-parallels/driver/base.rb @@ -82,20 +82,16 @@ def clone_vm(src_name, options={}) read_vms[dst_name] end - # Compacts all disk drives of virtual machine - def compact(uuid) - hw_info = read_settings(uuid).fetch('Hardware', {}) - used_drives = hw_info.select do |name, _| - name.start_with? 'hdd' - end - used_drives.each_value do |drive_params| - execute(@prldisktool_path, 'compact', '--hdd', drive_params['image']) do |_, data| - lines = data.split('\r') - # The progress of the compact will be in the last line. Do a greedy - # regular expression to find what we're looking for. - if lines.last =~ /.+?(\d{,3}) ?%/ - yield $1.to_i if block_given? - end + # Compacts the specified virtual disk image + # + # @param [] hdd_path Path to the target '*.hdd' + def compact_hdd(hdd_path) + execute(@prldisktool_path, 'compact', '--hdd', hdd_path) do |_, data| + lines = data.split('\r') + # The progress of the compact will be in the last line. Do a greedy + # regular expression to find what we're looking for. + if lines.last =~ /.+?(\d{,3}) ?%/ + yield $1.to_i if block_given? end end end diff --git a/lib/vagrant-parallels/driver/meta.rb b/lib/vagrant-parallels/driver/meta.rb index 4ede91d2..b9ca5e2e 100644 --- a/lib/vagrant-parallels/driver/meta.rb +++ b/lib/vagrant-parallels/driver/meta.rb @@ -74,7 +74,7 @@ def initialize(uuid=nil) def_delegators :@driver, :clear_forwarded_ports, :clear_shared_folders, - :compact, + :compact_hdd, :create_host_only_network, :create_snapshot, :delete, diff --git a/lib/vagrant-parallels/errors.rb b/lib/vagrant-parallels/errors.rb index 4005c412..b6d00d7c 100644 --- a/lib/vagrant-parallels/errors.rb +++ b/lib/vagrant-parallels/errors.rb @@ -19,6 +19,10 @@ class DhcpLeasesNotAccessible < VagrantParallelsError error_key(:dhcp_leases_file_not_accessible) end + class ExternalDiskNotFound < VagrantParallelsError + error_key(:external_disk_not_found) + end + class JSONParseError < VagrantParallelsError error_key(:json_parse_error) end diff --git a/locales/en.yml b/locales/en.yml index 8905b6cc..668435ef 100644 --- a/locales/en.yml +++ b/locales/en.yml @@ -38,6 +38,13 @@ en: JSON string is shown below: %{data} + + external_disk_not_found: |- + External disk image could not be found. In case of linked clone it is + usually because the parent VM image was removed. It means that the virtual + disk is inconsistent, please remove it from the VM configuration. + + Disk image path: %{path} linux_mount_failed: |- Failed to mount folders in Linux guest. This is usually because the "prl_fs" file system is not available. Please verify that @@ -211,6 +218,7 @@ en: reload your VM. export: compacting: Compacting exported HDDs... + copying_linked_disks: Copying linked disks... forward_ports: forwarding_entry: |- %{guest_port} => %{host_port} diff --git a/test/acceptance/provider/package_spec.rb b/test/acceptance/provider/package_spec.rb new file mode 100644 index 00000000..adde38af --- /dev/null +++ b/test/acceptance/provider/package_spec.rb @@ -0,0 +1,68 @@ +# This tests that packaging works with a given provider. +shared_examples 'provider/package' do |provider, options| + if !options[:box] + raise ArgumentError, + "box option must be specified for provider: #{provider}" + end + + include_context 'acceptance' + + it "can't package before an up" do + expect(execute('vagrant', 'package')).to exit_with(1) + end + + context 'with a running machine' do + before do + # Create VM as a linked clone to test that it could be packaged correctly + environment.skeleton('linked_clone') + + assert_execute('vagrant', 'box', 'add', 'basic', options[:box]) + assert_execute('vagrant', 'up', "--provider=#{provider}") + end + + after do + # Just always do this just in case + execute('vagrant', 'destroy', '--force', log: false) + end + + it 'can package and bring the box back up' do + status('Test: linked clone could be packaged to the standalone box') + expect(execute('vagrant', 'package')).to exit_with(0) + assert_execute('vagrant', 'destroy', '--force') + + path = environment.workdir.join('package.box') + expect(path).to be_file + + begin + # Create a new environment and bring up the packaged box + status('Test: packaged box could be reused') + new_env = new_environment + assert_execute('vagrant', 'box', 'add', 'temp', path.to_s, env: new_env) + assert_execute('vagrant', 'init', 'temp', env: new_env) + assert_execute('vagrant', 'up', "--provider=#{provider}", env: new_env) + + # Test again for Vagrant issue GH-5780 + status('Test: box could be re-package (issue mitchellh/vagrant#5780)') + assert_execute('vagrant', 'package', env: new_env) + assert_execute('vagrant', 'destroy', '--force', env: new_env) + + path = new_env.workdir.join('package.box') + new_env2 = new_environment + assert_execute('vagrant', 'box', 'add', 'temp', path.to_s, env: new_env2) + assert_execute('vagrant', 'init', 'temp', env: new_env2) + assert_execute('vagrant', 'up', "--provider=#{provider}", env: new_env) + assert_execute('vagrant', 'destroy', '--force', env: new_env2) + ensure + if new_env + new_env.execute('vagrant', 'destroy', '--force') + new_env.close + end + + if new_env2 + new_env2.execute('vagrant', 'destroy', '--force') + new_env2.close + end + end + end + end +end diff --git a/test/unit/support/shared/pd_driver_examples.rb b/test/unit/support/shared/pd_driver_examples.rb index f5222318..a518a3e5 100644 --- a/test/unit/support/shared/pd_driver_examples.rb +++ b/test/unit/support/shared/pd_driver_examples.rb @@ -3,17 +3,13 @@ raise ArgumentError, 'Need parallels context to use these shared examples.' unless defined? parallels_context end - describe 'compact' do - settings = {'Hardware' => {'hdd0' => {'image' => '/path/to/disk0.hdd'}, - 'hdd1' => {'image' => '/path/to/disk1.hdd'}}} - it 'compacts the VM disk drives' do - driver.should_receive(:read_settings).and_return(settings) - - subprocess.should_receive(:execute).exactly(2).times. - with('prl_disk_tool', 'compact', '--hdd', /^\/path\/to\/disk(0|1).hdd$/, + describe 'compact_hdd' do + it 'compacts the virtual disk' do + subprocess.should_receive(:execute). + with('prl_disk_tool', 'compact', '--hdd', '/foo.hdd', an_instance_of(Hash)). and_return(subprocess_result(exit_code: 0)) - subject.compact(uuid) + subject.compact_hdd('/foo.hdd') end end From 707e44137545cf052017f60c6fe601ba768e77df Mon Sep 17 00:00:00 2001 From: Mikhail Zholobov Date: Wed, 6 Jul 2016 18:42:52 +0300 Subject: [PATCH 12/23] travis: Use Bundler 1.12.5 and Ruby 2.2.3 --- .travis.yml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 19f40eef..9262ec7e 100644 --- a/.travis.yml +++ b/.travis.yml @@ -4,7 +4,10 @@ sudo: false cache: bundler +before_install: + - gem install bundler -v '1.12.5' + rvm: - - 2.0.0 + - 2.2.3 script: bundle exec rake test:unit From dace8f30a63859d001c24973bfeac169bca7c52e Mon Sep 17 00:00:00 2001 From: Mikhail Zholobov Date: Wed, 6 Jul 2016 19:25:40 +0300 Subject: [PATCH 13/23] test/unit: Fix RSpec expectation config --- test/unit/base.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/unit/base.rb b/test/unit/base.rb index d48523c5..67b62e6d 100644 --- a/test/unit/base.rb +++ b/test/unit/base.rb @@ -20,5 +20,5 @@ # Configure RSpec RSpec.configure do |c| - c.expect_with :rspec, :stdlib + c.expect_with :rspec end From 18cf2131c867bf052fa4ce7b9147ffa7d01797d4 Mon Sep 17 00:00:00 2001 From: Mikhail Zholobov Date: Tue, 5 Jul 2016 11:40:33 +0300 Subject: [PATCH 14/23] driver: Hardcode "Shared" network name It allows to reduce `prlctl` calls. Actually, Shared network name is hard-coded in Parallels Desktop and it will not be changed in the near future. --- lib/vagrant-parallels/driver/base.rb | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/lib/vagrant-parallels/driver/base.rb b/lib/vagrant-parallels/driver/base.rb index d10efa45..1014e513 100644 --- a/lib/vagrant-parallels/driver/base.rb +++ b/lib/vagrant-parallels/driver/base.rb @@ -422,11 +422,7 @@ def read_settings(uuid=@uuid) # # @return [String] Shared network ID def read_shared_network_id - # There should be only one Shared interface - shared_net = read_virtual_networks.detect do |net| - net['Type'] == 'shared' - end - shared_net.fetch('Network ID') + 'Shared' end # Returns info about shared network interface. From b8218991d801cdcfa1559e43de257685ad8c270a Mon Sep 17 00:00:00 2001 From: Mikhail Zholobov Date: Tue, 5 Jul 2016 11:43:19 +0300 Subject: [PATCH 15/23] Reduce CLI calls on "clean_forwarded_ports" action --- .../action/clear_forwarded_ports.rb | 5 ++-- lib/vagrant-parallels/action/forward_ports.rb | 2 +- lib/vagrant-parallels/driver/base.rb | 30 +++++++++++++++++-- lib/vagrant-parallels/driver/pd_10.rb | 8 ++--- 4 files changed, 36 insertions(+), 9 deletions(-) diff --git a/lib/vagrant-parallels/action/clear_forwarded_ports.rb b/lib/vagrant-parallels/action/clear_forwarded_ports.rb index 1ebae32a..c1ee5140 100644 --- a/lib/vagrant-parallels/action/clear_forwarded_ports.rb +++ b/lib/vagrant-parallels/action/clear_forwarded_ports.rb @@ -12,9 +12,10 @@ def call(env) return @app.call(env) end - if !env[:machine].provider.driver.read_forwarded_ports.empty? + ports = env[:machine].provider.driver.read_forwarded_ports + if !ports.empty? env[:ui].info I18n.t('vagrant.actions.vm.clear_forward_ports.deleting') - env[:machine].provider.driver.clear_forwarded_ports + env[:machine].provider.driver.clear_forwarded_ports(ports) end @app.call(env) diff --git a/lib/vagrant-parallels/action/forward_ports.rb b/lib/vagrant-parallels/action/forward_ports.rb index f583fb01..67b83157 100644 --- a/lib/vagrant-parallels/action/forward_ports.rb +++ b/lib/vagrant-parallels/action/forward_ports.rb @@ -45,7 +45,7 @@ def call(env) def forward_ports all_rules = @env[:machine].provider.driver.read_forwarded_ports(true) - names_in_use = all_rules.collect { |r| r[:rule_name] } + names_in_use = all_rules.collect { |r| r[:name] } ports = [] @env[:forwarded_ports].each do |fp| diff --git a/lib/vagrant-parallels/driver/base.rb b/lib/vagrant-parallels/driver/base.rb index 1014e513..8f550a52 100644 --- a/lib/vagrant-parallels/driver/base.rb +++ b/lib/vagrant-parallels/driver/base.rb @@ -40,8 +40,20 @@ def initialize(uuid) @logger.info("prlsrvctl path: #{@prlsrvctl_path}") end - # Removes all port forwarding rules for the virtual machine. - def clear_forwarded_ports + # Removes the specified port forwarding rules for the virtual machine. + # + # @param [Array String>] ports - List of ports. + # Each port should be described as a hash with the following keys: + # + # { + # name: 'example', + # protocol: 'tcp', + # guest: 'target-vm-uuid', + # hostport: '8080', + # guestport: '80' + # } + # + def clear_forwarded_ports(ports) raise NotImplementedError end @@ -293,6 +305,20 @@ def read_bridged_interfaces bridged_ifaces end + # Returns the list of port forwarding rules. + # Each rule will be represented as a hash with the following keys: + # + # { + # name: 'example', + # protocol: 'tcp', + # guest: 'target-vm-uuid', + # hostport: '8080', + # guestport: '80' + # } + # + # @param [Boolean] global If true, returns all the rules on the host. + # Otherwise only rules related to the context VM will be returned. + # @return [Array String>] def read_forwarded_ports(global=false) raise NotImplementedError end diff --git a/lib/vagrant-parallels/driver/pd_10.rb b/lib/vagrant-parallels/driver/pd_10.rb index 859cf725..a3889e82 100644 --- a/lib/vagrant-parallels/driver/pd_10.rb +++ b/lib/vagrant-parallels/driver/pd_10.rb @@ -15,10 +15,10 @@ def initialize(uuid) @logger = Log4r::Logger.new('vagrant_parallels::driver::pd_10') end - def clear_forwarded_ports + def clear_forwarded_ports(ports) args = [] - read_forwarded_ports.each do |r| - args.concat(["--nat-#{r[:protocol]}-del", r[:rule_name]]) + ports.each do |r| + args.concat(["--nat-#{r[:protocol]}-del", r[:name]]) end if !args.empty? @@ -211,7 +211,7 @@ def read_shared_interface net_info['NAT server'].each do |group, rules| rules.each do |name, params| info[:nat] << { - rule_name: name, + name: name, protocol: group == 'TCP rules' ? 'tcp' : 'udp', guest: params['destination IP/VM id'], hostport: params['source port'], From c63c501fdc81f413e17f0896d70f1467e0973957 Mon Sep 17 00:00:00 2001 From: Mikhail Zholobov Date: Tue, 5 Jul 2016 13:22:59 +0300 Subject: [PATCH 16/23] driver: Prepare for the host could be disconnected from Shared and Host-Only networks --- lib/vagrant-parallels/driver/pd_10.rb | 60 +++++++++++++++------------ lib/vagrant-parallels/driver/pd_8.rb | 53 ++++++++++++++--------- 2 files changed, 68 insertions(+), 45 deletions(-) diff --git a/lib/vagrant-parallels/driver/pd_10.rb b/lib/vagrant-parallels/driver/pd_10.rb index a3889e82..ba56fdbd 100644 --- a/lib/vagrant-parallels/driver/pd_10.rb +++ b/lib/vagrant-parallels/driver/pd_10.rb @@ -32,7 +32,7 @@ def delete_unused_host_only_networks # 'Shared'(vnic0) and 'Host-Only'(vnic1) are default in Parallels Desktop # They should not be deleted anyway. networks.keep_if do |net| - net['Type'] == 'host-only' && + net['Type'] == 'host-only' && net['Bound To'] && net['Bound To'].match(/^(?>vnic|Parallels Host-Only #)(\d+)$/)[1].to_i >= 2 end @@ -131,31 +131,36 @@ def read_host_only_interfaces hostonly_ifaces = [] net_list.each do |iface| - info = {} - net_info = json { execute_prlsrvctl('net', 'info', iface['Network ID'], '--json') } - adapter = net_info['Parallels adapter'] + net_info = json do + execute_prlsrvctl('net', 'info', iface['Network ID'], '--json') + end - info[:name] = net_info['Network ID'] - info[:bound_to] = net_info['Bound To'] - # In PD >= 10.1.2 there are new field names for an IP/Subnet - info[:ip] = adapter['IP address'] || adapter['IPv4 address'] - info[:netmask] = adapter['Subnet mask'] || adapter['IPv4 subnet mask'] + iface = { + name: net_info['Network ID'], + status: 'Down' + } - # Such interfaces are always in 'Up' - info[:status] = 'Up' + adapter = net_info['Parallels adapter'] + if adapter && net_info['Bound To'] + # In PD >= 10.1.2 there are new field names for an IP/Subnet + iface[:ip] = adapter['IP address'] || adapter['IPv4 address'] + iface[:netmask] = adapter['Subnet mask'] || adapter['IPv4 subnet mask'] + iface[:bound_to] = net_info['Bound To'] + iface[:status] = 'Up' + end # There may be a fake DHCPv4 parameters # We can trust them only if adapter IP and DHCP IP are in the same subnet dhcp_info = net_info['DHCPv4 server'] - if dhcp_info && (network_address(info[:ip], info[:netmask]) == - network_address(dhcp_info['Server address'], info[:netmask])) - info[:dhcp] = { + if dhcp_info && (network_address(iface[:ip], iface[:netmask]) == + network_address(dhcp_info['Server address'], iface[:netmask])) + iface[:dhcp] = { ip: dhcp_info['Server address'], lower: dhcp_info['IP scope start address'], upper: dhcp_info['IP scope end address'] } end - hostonly_ifaces << info + hostonly_ifaces << iface end hostonly_ifaces end @@ -189,19 +194,22 @@ def read_shared_interface execute_prlsrvctl('net', 'info', read_shared_network_id, '--json') end + iface = { + nat: [], + status: 'Down' + } adapter = net_info['Parallels adapter'] - # In PD >= 10.1.2 there are new field names for an IP/Subnet - info = { - name: net_info['Bound To'], - ip: adapter['IP address'] || adapter['IPv4 address'], - netmask: adapter['Subnet mask'] || adapter['IPv4 subnet mask'], - status: 'Up', - nat: [] - } + if adapter && net_info['Bound To'] + # In PD >= 10.1.2 there are new field names for an IP/Subnet + iface[:ip] = adapter['IP address'] || adapter['IPv4 address'] + iface[:netmask] = adapter['Subnet mask'] || adapter['IPv4 subnet mask'] + iface[:bound_to] = net_info['Bound To'] + iface[:status] = 'Up' + end if net_info.key?('DHCPv4 server') - info[:dhcp] = { + iface[:dhcp] = { ip: net_info['DHCPv4 server']['Server address'], lower: net_info['DHCPv4 server']['IP scope start address'], upper: net_info['DHCPv4 server']['IP scope end address'] @@ -210,7 +218,7 @@ def read_shared_interface net_info['NAT server'].each do |group, rules| rules.each do |name, params| - info[:nat] << { + iface[:nat] << { name: name, protocol: group == 'TCP rules' ? 'tcp' : 'udp', guest: params['destination IP/VM id'], @@ -220,7 +228,7 @@ def read_shared_interface end end - info + iface end def read_used_ports diff --git a/lib/vagrant-parallels/driver/pd_8.rb b/lib/vagrant-parallels/driver/pd_8.rb index 62f5ef3c..986b4b5c 100644 --- a/lib/vagrant-parallels/driver/pd_8.rb +++ b/lib/vagrant-parallels/driver/pd_8.rb @@ -98,27 +98,35 @@ def read_host_only_interfaces hostonly_ifaces = [] net_list.each do |iface| - info = {} - net_info = json { execute_prlsrvctl('net', 'info', iface['Network ID'], '--json') } - info[:name] = net_info['Network ID'] - info[:bound_to] = net_info['Bound To'] - info[:ip] = net_info['Parallels adapter']['IP address'] - info[:netmask] = net_info['Parallels adapter']['Subnet mask'] - # Such interfaces are always in 'Up' - info[:status] = 'Up' + net_info = json do + execute_prlsrvctl('net', 'info', iface['Network ID'], '--json') + end + + iface = { + name: net_info['Network ID'], + status: 'Down' + } + + adapter = net_info['Parallels adapter'] + if adapter && net_info['Bound To'] + iface[:ip] = adapter['IP address'] + iface[:netmask] = adapter['Subnet mask'] + iface[:bound_to] = net_info['Bound To'] + iface[:status] = 'Up' + end # There may be a fake DHCPv4 parameters # We can trust them only if adapter IP and DHCP IP are in the same subnet dhcp_info = net_info['DHCPv4 server'] - if dhcp_info && (network_address(info[:ip], info[:netmask]) == - network_address(dhcp_info['Server address'], info[:netmask])) - info[:dhcp] = { + if dhcp_info && (network_address(iface[:ip], iface[:netmask]) == + network_address(dhcp_info['Server address'], iface[:netmask])) + iface[:dhcp] = { ip: dhcp_info['Server address'], lower: dhcp_info['IP scope start address'], upper: dhcp_info['IP scope end address'] } end - hostonly_ifaces << info + hostonly_ifaces << iface end hostonly_ifaces end @@ -156,22 +164,29 @@ def read_shared_interface net_info = json do execute_prlsrvctl('net', 'info', read_shared_network_id, '--json') end - info = { - name: net_info['Bound To'], - ip: net_info['Parallels adapter']['IP address'], - netmask: net_info['Parallels adapter']['Subnet mask'], - status: 'Up' + + iface = { + nat: [], + status: 'Down' } + adapter = net_info['Parallels adapter'] + if adapter && net_info['Bound To'] + iface[:ip] = adapter['IP address'] + iface[:netmask] = adapter['Subnet mask'] + iface[:bound_to] = net_info['Bound To'] + iface[:status] = 'Up' + end + if net_info.key?('DHCPv4 server') - info[:dhcp] = { + iface[:dhcp] = { ip: net_info['DHCPv4 server']['Server address'], lower: net_info['DHCPv4 server']['IP scope start address'], upper: net_info['DHCPv4 server']['IP scope end address'] } end - info + iface end end end From c8e8e6006b42b2e6dd33a8f085f53be295c0d710 Mon Sep 17 00:00:00 2001 From: Mikhail Zholobov Date: Tue, 5 Jul 2016 17:37:29 +0300 Subject: [PATCH 17/23] action: Check the host is connected to Shared network With PD 11.2.1 and higher it will be automatically fixed by connecting host to Shared network. For older versions the exception will be raised. --- lib/vagrant-parallels/action.rb | 7 +++++ .../action/check_shared_interface.rb | 29 +++++++++++++++++++ lib/vagrant-parallels/cap.rb | 1 - lib/vagrant-parallels/driver/base.rb | 10 ++++++- lib/vagrant-parallels/driver/meta.rb | 2 ++ lib/vagrant-parallels/driver/pd_11.rb | 4 +++ lib/vagrant-parallels/errors.rb | 8 +++-- locales/en.yml | 16 +++++++--- 8 files changed, 69 insertions(+), 8 deletions(-) create mode 100644 lib/vagrant-parallels/action/check_shared_interface.rb diff --git a/lib/vagrant-parallels/action.rb b/lib/vagrant-parallels/action.rb index a11b03de..ed7e4870 100644 --- a/lib/vagrant-parallels/action.rb +++ b/lib/vagrant-parallels/action.rb @@ -11,6 +11,7 @@ module Action # a bootup (i.e. not saved). def self.action_boot Vagrant::Action::Builder.new.tap do |b| + b.use CheckSharedInterface b.use SetName b.use ClearForwardedPorts b.use Provision @@ -74,6 +75,7 @@ def self.action_destroy def self.action_halt Vagrant::Action::Builder.new.tap do |b| b.use ConfigValidate + b.use CheckSharedInterface b.use Call, IsState, :not_created do |env1, b1| if env1[:result] b1.use Message, I18n.t('vagrant.commands.common.vm_not_created') @@ -122,6 +124,7 @@ def self.action_package def self.action_provision Vagrant::Action::Builder.new.tap do |b| b.use ConfigValidate + b.use CheckSharedInterface b.use Call, IsState, :not_created do |env1, b1| if env1[:result] b1.use Message, I18n.t('vagrant.commands.common.vm_not_created') @@ -163,6 +166,7 @@ def self.action_reload def self.action_resume Vagrant::Action::Builder.new.tap do |b| b.use ConfigValidate + b.use CheckSharedInterface b.use Call, IsState, :not_created do |env1, b1| if env1[:result] b1.use Message, I18n.t('vagrant.commands.common.vm_not_created') @@ -227,6 +231,7 @@ def self.action_snapshot_save def self.action_ssh Vagrant::Action::Builder.new.tap do |b| b.use ConfigValidate + b.use CheckSharedInterface b.use Call, IsState, :not_created do |env1, b1| if env1[:result] b1.use Message, I18n.t('vagrant.commands.common.vm_not_created') @@ -248,6 +253,7 @@ def self.action_ssh def self.action_ssh_run Vagrant::Action::Builder.new.tap do |b| b.use ConfigValidate + b.use CheckSharedInterface b.use Call, IsState, :not_created do |env1, b1| if env1[:result] b1.use Message, I18n.t('vagrant.commands.common.vm_not_created') @@ -389,6 +395,7 @@ def self.action_sync_folders autoload :BoxUnregister, File.expand_path('../action/box_unregister', __FILE__) autoload :HandleGuestTools, File.expand_path('../action/handle_guest_tools', __FILE__) autoload :HandleForwardedPortCollisions, File.expand_path('../action/handle_forwarded_port_collisions.rb', __FILE__) + autoload :CheckSharedInterface, File.expand_path('../action/check_shared_interface', __FILE__) autoload :ClearNetworkInterfaces, File.expand_path('../action/clear_network_interfaces', __FILE__) autoload :ClearForwardedPorts, File.expand_path('../action/clear_forwarded_ports', __FILE__) autoload :Customize, File.expand_path('../action/customize', __FILE__) diff --git a/lib/vagrant-parallels/action/check_shared_interface.rb b/lib/vagrant-parallels/action/check_shared_interface.rb new file mode 100644 index 00000000..97e3e98e --- /dev/null +++ b/lib/vagrant-parallels/action/check_shared_interface.rb @@ -0,0 +1,29 @@ +module VagrantPlugins + module Parallels + module Action + class CheckSharedInterface + def initialize(app, env) + @app = app + end + + def call(env) + shared_iface = env[:machine].provider.driver.read_shared_interface + + # Shared interface is connected. Just exit + return @app.call(env) if shared_iface[:status] == 'Up' + + # Since PD 11.2.1 Vagrant can fix this automatically + if !env[:machine].provider.pd_version_satisfies?('>= 11.2.1') + raise Errors::SharedInterfaceDisconnected + end + + env[:ui].info I18n.t('vagrant_parallels.actions.vm.check_shared_interface.connecting') + iface_name = env[:machine].provider.driver.read_shared_network_id + env[:machine].provider.driver.connect_network_interface(iface_name) + + @app.call(env) + end + end + end + end +end diff --git a/lib/vagrant-parallels/cap.rb b/lib/vagrant-parallels/cap.rb index f5fb275d..ca432b7e 100644 --- a/lib/vagrant-parallels/cap.rb +++ b/lib/vagrant-parallels/cap.rb @@ -23,7 +23,6 @@ def self.forwarded_ports(machine) # # @return [String] Host's IP address def self.host_address(machine) - shared_iface = machine.provider.driver.read_shared_interface return shared_iface[:ip] if shared_iface diff --git a/lib/vagrant-parallels/driver/base.rb b/lib/vagrant-parallels/driver/base.rb index 8f550a52..814f0a9a 100644 --- a/lib/vagrant-parallels/driver/base.rb +++ b/lib/vagrant-parallels/driver/base.rb @@ -108,6 +108,14 @@ def compact_hdd(hdd_path) end end + # Connects the host machine to the specified virtual network interface + # Could be used for Parallels' Shared and Host-Only interfaces only. + # + # @param [] name Network interface name. Example: 'Shared' + def connect_network_interface(name) + raise NotImplementedError + end + # Creates a host only network with the given options. # # @param [ String>] options Hostonly network options. @@ -414,7 +422,7 @@ def read_mac_address end if shared_ifaces.empty? - raise Errors::SharedAdapterNotFound + raise Errors::SharedInterfaceNotFound end shared_ifaces.values.first.fetch('mac', nil) diff --git a/lib/vagrant-parallels/driver/meta.rb b/lib/vagrant-parallels/driver/meta.rb index b9ca5e2e..e8686d2f 100644 --- a/lib/vagrant-parallels/driver/meta.rb +++ b/lib/vagrant-parallels/driver/meta.rb @@ -75,6 +75,7 @@ def initialize(uuid=nil) :clear_forwarded_ports, :clear_shared_folders, :compact_hdd, + :connect_network_interface, :create_host_only_network, :create_snapshot, :delete, @@ -99,6 +100,7 @@ def initialize(uuid=nil) :read_network_interfaces, :read_shared_interface, :read_shared_folders, + :read_shared_network_id, :read_settings, :read_state, :read_used_ports, diff --git a/lib/vagrant-parallels/driver/pd_11.rb b/lib/vagrant-parallels/driver/pd_11.rb index ddf6354e..1f4b23ae 100644 --- a/lib/vagrant-parallels/driver/pd_11.rb +++ b/lib/vagrant-parallels/driver/pd_11.rb @@ -14,6 +14,10 @@ def initialize(uuid) @logger = Log4r::Logger.new('vagrant_parallels::driver::pd_11') end + + def connect_network_interface(name) + execute_prlsrvctl('net', 'set', name, '--connect-host-to-net', 'on') + end end end end diff --git a/lib/vagrant-parallels/errors.rb b/lib/vagrant-parallels/errors.rb index b6d00d7c..f6c91ac9 100644 --- a/lib/vagrant-parallels/errors.rb +++ b/lib/vagrant-parallels/errors.rb @@ -75,8 +75,12 @@ class ParallelsUnsupportedVersion < VagrantParallelsError error_key(:parallels_unsupported_version) end - class SharedAdapterNotFound < VagrantParallelsError - error_key(:shared_adapter_not_found) + class SharedInterfaceDisconnected < VagrantParallelsError + error_key(:shared_interface_disconnected) + end + + class SharedInterfaceNotFound < VagrantParallelsError + error_key(:shared_interface_not_found) end class SnapshotIdNotDetected < VagrantParallelsError diff --git a/locales/en.yml b/locales/en.yml index 668435ef..0190dd04 100644 --- a/locales/en.yml +++ b/locales/en.yml @@ -117,10 +117,16 @@ en: check and try again. Snapshot ID: %{snapshot} - shared_adapter_not_found: |- - Shared network adapter was not found in your virtual machine configuration. - It is required to communicate with VM and forward ports. Please check - network configuration in your Vagrantfile. + shared_interface_not_found: |- + Shared network interface was not found in your virtual machine configuration. + It is required for communications with VM and port forwarding. Please + check network configuration in your Vagrantfile. + shared_interface_disconnected: |- + Your Mac host is not connected to Shared network. It is required for + communications with VM and port forwarding. Please enable this option in GUI: + + Parallels Desktop -> Preferences -> Network -> Shared -> Connect Mac to this network + vm_clone_failure: |- The VM cloning failed! Please ensure that the box you're using is not corrupted and try again. @@ -191,6 +197,8 @@ en: box: register: Registering VM image from the base box '%{name}'... unregister: Unregistering the box VM image... + check_shared_interface: + connecting: Connecting host to Shared network... clone: full: Cloning new virtual machine... linked: Creating new virtual machine as a linked clone... From 7000ed6d790313a927c8845bc17d684661973507 Mon Sep 17 00:00:00 2001 From: Mikhail Zholobov Date: Mon, 11 Jul 2016 15:25:13 +0300 Subject: [PATCH 18/23] driver: Skip fetching DHCP details in #read_host_only_interfaces This data is not needed in 'action/network', so we can skip it. --- lib/vagrant-parallels/driver/base.rb | 9 +++++++++ lib/vagrant-parallels/driver/pd_10.rb | 11 ----------- lib/vagrant-parallels/driver/pd_8.rb | 11 ----------- 3 files changed, 9 insertions(+), 22 deletions(-) diff --git a/lib/vagrant-parallels/driver/base.rb b/lib/vagrant-parallels/driver/base.rb index 814f0a9a..5837cd8b 100644 --- a/lib/vagrant-parallels/driver/base.rb +++ b/lib/vagrant-parallels/driver/base.rb @@ -406,6 +406,15 @@ def read_host_info end # Returns a list of available host only interfaces. + # Each interface is represented as a Hash with the following details: + # + # { + # name: 'Host-Only', # Parallels Network ID + # bound_to: 'vnic1', # interface name + # ip: '10.37.129.2', # IP address of the interface + # netmask: '255.255.255.0', # netmask associated with the interface + # status: 'Up' # status of the interface + # } # # @return [Array String>] def read_host_only_interfaces diff --git a/lib/vagrant-parallels/driver/pd_10.rb b/lib/vagrant-parallels/driver/pd_10.rb index ba56fdbd..f3daed38 100644 --- a/lib/vagrant-parallels/driver/pd_10.rb +++ b/lib/vagrant-parallels/driver/pd_10.rb @@ -149,17 +149,6 @@ def read_host_only_interfaces iface[:status] = 'Up' end - # There may be a fake DHCPv4 parameters - # We can trust them only if adapter IP and DHCP IP are in the same subnet - dhcp_info = net_info['DHCPv4 server'] - if dhcp_info && (network_address(iface[:ip], iface[:netmask]) == - network_address(dhcp_info['Server address'], iface[:netmask])) - iface[:dhcp] = { - ip: dhcp_info['Server address'], - lower: dhcp_info['IP scope start address'], - upper: dhcp_info['IP scope end address'] - } - end hostonly_ifaces << iface end hostonly_ifaces diff --git a/lib/vagrant-parallels/driver/pd_8.rb b/lib/vagrant-parallels/driver/pd_8.rb index 986b4b5c..0b9e3456 100644 --- a/lib/vagrant-parallels/driver/pd_8.rb +++ b/lib/vagrant-parallels/driver/pd_8.rb @@ -115,17 +115,6 @@ def read_host_only_interfaces iface[:status] = 'Up' end - # There may be a fake DHCPv4 parameters - # We can trust them only if adapter IP and DHCP IP are in the same subnet - dhcp_info = net_info['DHCPv4 server'] - if dhcp_info && (network_address(iface[:ip], iface[:netmask]) == - network_address(dhcp_info['Server address'], iface[:netmask])) - iface[:dhcp] = { - ip: dhcp_info['Server address'], - lower: dhcp_info['IP scope start address'], - upper: dhcp_info['IP scope end address'] - } - end hostonly_ifaces << iface end hostonly_ifaces From 2cda6561a57ad81930fd317a23056be5769025ec Mon Sep 17 00:00:00 2001 From: Mikhail Zholobov Date: Mon, 11 Jul 2016 15:29:16 +0300 Subject: [PATCH 19/23] action/network: Fix an exception when the host is not connected to host-only interface --- lib/vagrant-parallels/action/network.rb | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/lib/vagrant-parallels/action/network.rb b/lib/vagrant-parallels/action/network.rb index 0539d25d..ac6b9737 100644 --- a/lib/vagrant-parallels/action/network.rb +++ b/lib/vagrant-parallels/action/network.rb @@ -444,20 +444,18 @@ def hostonly_create_network(config) # This finds a matching host only network for the given configuration. def hostonly_find_matching_network(config) - existing = @env[:machine].provider.driver.read_host_only_interfaces + this_netaddr = network_address(config[:ip], config[:netmask]) - if config[:name] - # Search networks strongly by specified name - matched_iface = existing.detect { |i| config[:name] == i[:name] } - else - # Name is not specified - search by network address - this_netaddr = network_address(config[:ip], config[:netmask]) - matched_iface = existing.detect do |i| - this_netaddr == network_address(i[:ip], i[:netmask]) + @env[:machine].provider.driver.read_host_only_interfaces.each do |interface| + return interface if config[:name] && config[:name] == interface[:name] + + if interface[:ip] + return interface if this_netaddr == \ + network_address(interface[:ip], interface[:netmask]) end end - matched_iface || nil + nil end end end From d512e98ad0709e86553bb1b4ef9e11f095f115de Mon Sep 17 00:00:00 2001 From: Mikhail Zholobov Date: Mon, 11 Jul 2016 13:00:08 +0300 Subject: [PATCH 20/23] Update changelog --- CHANGELOG.md | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index de803cd2..3af64bc7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,32 @@ +## 1.6.3 (July 11, 2016) +DEPRECATIONS: + - The following provider options was renamed: + - `regen_box_uuid` was renamed to `regen_src_uuid` + - `use_linked_clone` was renamed to `linked clone` + + Old names are still supported, but will be removed in `vagrant-parallels` v1.7.0. + [[GH-260](https://github.com/Parallels/vagrant-parallels/pull/260)] + +IMPROVEMENTS: + - Allow to package linked clones with `vagrant package`. External disk images + will be automatically copied, so the resulted box become a full-sized + standalone VM. [[GH-262](https://github.com/Parallels/vagrant-parallels/pull/262)] + - Handle the situation when host machine is not connected to Shared network. + With Parallels Desktop 11.2.1+ Vagrant will connect it automatically. With earlier + versions, the human-readable error message will be displayed. + [[GH-266](https://github.com/Parallels/vagrant-parallels/pull/266)] + - Disable home folder sharing by default (Parallels Desktop 11+). + [[GH-257](https://github.com/Parallels/vagrant-parallels/pull/257)] + +BUG FIXES: + - action/box_unregister: Fix `#recover` method for layered environments. + [[GH-261](https://github.com/Parallels/vagrant-parallels/pull/261)] + - action/network: Fix an exception when option "Connect Mac to + this network" is disabled. [[GH-268](https://github.com/Parallels/vagrant-parallels/pull/268)] + - commands/snapshot: Add retries for snapshot commands to avoid `prlctl` + failures. [[GH-259](https://github.com/Parallels/vagrant-parallels/pull/259)] + + ## 1.6.2 (March 23, 2016) BUG FIXES: - Fix unsupported action error for `vagrant snapshot` commands [[GH-254](https://github.com/Parallels/vagrant-parallels/pull/254)] From 7f15cf4998dbe9f9c88679126f1a1dd8423702ce Mon Sep 17 00:00:00 2001 From: Mikhail Zholobov Date: Mon, 11 Jul 2016 13:13:08 +0300 Subject: [PATCH 21/23] Bump version to 1.6.3 --- lib/vagrant-parallels/version.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/vagrant-parallels/version.rb b/lib/vagrant-parallels/version.rb index 2cad11a2..d2e80441 100644 --- a/lib/vagrant-parallels/version.rb +++ b/lib/vagrant-parallels/version.rb @@ -1,5 +1,5 @@ module VagrantPlugins module Parallels - VERSION = '1.6.2' + VERSION = '1.6.3' end end From 9e1d59a2561bbcef09ab18734c00768a48bf9ed8 Mon Sep 17 00:00:00 2001 From: Mikhail Zholobov Date: Mon, 11 Jul 2016 19:13:41 +0300 Subject: [PATCH 22/23] license: Update copyright year --- LICENSE.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/LICENSE.txt b/LICENSE.txt index 9ebf035e..a2a8e992 100644 --- a/LICENSE.txt +++ b/LICENSE.txt @@ -1,5 +1,5 @@ Copyright (c) 2013 Youssef Shahin -Copyright (c) 2013-2015 Parallels IP Holdings GmbH. +Copyright (c) 2013-2016 Parallels IP Holdings GmbH. MIT License From 1aa882d077df2eb5fb63bc30c425fef8ceceb106 Mon Sep 17 00:00:00 2001 From: Mikhail Zholobov Date: Mon, 11 Jul 2016 19:15:01 +0300 Subject: [PATCH 23/23] gemspec: Specify file list explicitly --- vagrant-parallels.gemspec | 35 +---------------------------------- 1 file changed, 1 insertion(+), 34 deletions(-) diff --git a/vagrant-parallels.gemspec b/vagrant-parallels.gemspec index b2be1419..654ae8f8 100644 --- a/vagrant-parallels.gemspec +++ b/vagrant-parallels.gemspec @@ -21,39 +21,6 @@ Gem::Specification.new do |spec| spec.add_development_dependency 'nokogiri' spec.add_development_dependency 'rspec', '~> 2.14.0' - # The following block of code determines the files that should be included - # in the gem. It does this by reading all the files in the directory where - # this gemspec is, and parsing out the ignored files from the gitignore. - # Note that the entire gitignore(5) syntax is not supported, specifically - # the "!" syntax, but it should mostly work correctly. - root_path = File.dirname(__FILE__) - all_files = Dir.chdir(root_path) { Dir.glob('**/{*,.*}') } - all_files.reject! { |file| ['.', '..'].include?(File.basename(file)) } - all_files.reject! { |file| file.start_with?('website/') } - gitignore_path = File.join(root_path, '.gitignore') - gitignore = File.readlines(gitignore_path) - gitignore.map! { |line| line.chomp.strip } - gitignore.reject! { |line| line.empty? || line =~ /^(#|!)/ } - - unignored_files = all_files.reject do |file| - # Ignore any directories, the gemspec only cares about files - next true if File.directory?(file) - - # Ignore any paths that match anything in the gitignore. We do - # two tests here: - # - # - First, test to see if the entire path matches the gitignore. - # - Second, match if the basename does, this makes it so that things - # like '.DS_Store' will match sub-directories too (same behavior - # as git). - # - gitignore.any? do |ignore| - File.fnmatch(ignore, file, File::FNM_PATHNAME) || - File.fnmatch(ignore, File.basename(file), File::FNM_PATHNAME) - end - end - - spec.files = unignored_files - spec.executables = unignored_files.map { |f| f[/^bin\/(.*)/, 1] }.compact + spec.files = Dir['lib/**/*', 'locales/**/*', 'README.md', 'CHANGELOG.md', 'LICENSE.txt'] spec.require_path = 'lib' end