vagrant-parallels 1.5.1 → 1.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (38) hide show
  1. checksums.yaml +4 -4
  2. data/.travis.yml +7 -4
  3. data/CHANGELOG.md +33 -0
  4. data/Gemfile +2 -2
  5. data/README.md +1 -1
  6. data/debug.log +1237 -1
  7. data/lib/vagrant-parallels/action.rb +8 -0
  8. data/lib/vagrant-parallels/action/box_register.rb +113 -0
  9. data/lib/vagrant-parallels/action/box_unregister.rb +43 -0
  10. data/lib/vagrant-parallels/action/export.rb +36 -35
  11. data/lib/vagrant-parallels/action/forward_ports.rb +32 -18
  12. data/lib/vagrant-parallels/action/import.rb +59 -106
  13. data/lib/vagrant-parallels/action/network.rb +17 -13
  14. data/lib/vagrant-parallels/action/prepare_clone_snapshot.rb +67 -0
  15. data/lib/vagrant-parallels/action/set_name.rb +1 -1
  16. data/lib/vagrant-parallels/action/snapshot_delete.rb +25 -0
  17. data/lib/vagrant-parallels/action/snapshot_restore.rb +23 -0
  18. data/lib/vagrant-parallels/action/snapshot_save.rb +23 -0
  19. data/lib/vagrant-parallels/cap.rb +63 -0
  20. data/lib/vagrant-parallels/config.rb +7 -3
  21. data/lib/vagrant-parallels/driver/base.rb +61 -28
  22. data/lib/vagrant-parallels/driver/meta.rb +20 -10
  23. data/lib/vagrant-parallels/driver/pd_11.rb +0 -21
  24. data/lib/vagrant-parallels/errors.rb +14 -6
  25. data/lib/vagrant-parallels/guest_cap/darwin/install_parallels_tools.rb +34 -0
  26. data/lib/vagrant-parallels/plugin.rb +23 -8
  27. data/lib/vagrant-parallels/version.rb +1 -1
  28. data/locales/en.yml +31 -17
  29. data/test/acceptance/provider/linked_clone_spec.rb +6 -2
  30. data/test/acceptance/skeletons/linked_clone/Vagrantfile +1 -1
  31. data/test/unit/base.rb +1 -0
  32. data/test/unit/cap_test.rb +96 -0
  33. data/test/unit/support/shared/pd_driver_examples.rb +7 -10
  34. metadata +12 -6
  35. data/lib/vagrant-parallels/cap/forwarded_ports.rb +0 -22
  36. data/lib/vagrant-parallels/cap/host_address.rb +0 -15
  37. data/lib/vagrant-parallels/cap/nic_mac_addresses.rb +0 -17
  38. data/lib/vagrant-parallels/cap/public_address.rb +0 -15
@@ -161,18 +161,23 @@ module VagrantPlugins
161
161
  @logger.debug("Bridge was directly specified in config, searching for: #{config[:bridge]}")
162
162
 
163
163
  # Search for a matching bridged interface
164
- bridgedifs.each do |interface|
165
- if interface[:name].downcase == config[:bridge].downcase
166
- @logger.debug('Specific bridge found as configured in the Vagrantfile. Using it.')
167
- chosen_bridge = interface[:name]
168
- break
164
+ Array(config[:bridge]).each do |bridge|
165
+ bridge = bridge.downcase if bridge.respond_to?(:downcase)
166
+ bridgedifs.each do |interface|
167
+ if bridge === interface[:name].downcase
168
+ @logger.debug('Specific bridge found as configured in the Vagrantfile. Using it.')
169
+ chosen_bridge = interface[:name]
170
+ break
171
+ end
169
172
  end
173
+ break if chosen_bridge
170
174
  end
171
175
 
172
176
  # If one wasn't found, then we notify the user here.
173
177
  if !chosen_bridge
174
- @env[:ui].info I18n.t('vagrant.actions.vm.bridged_networking.specific_not_found',
175
- :bridge => config[:bridge])
178
+ @env[:ui].info I18n.t(
179
+ 'vagrant.actions.vm.bridged_networking.specific_not_found',
180
+ bridge: config[:bridge])
176
181
  end
177
182
  end
178
183
 
@@ -188,14 +193,14 @@ module VagrantPlugins
188
193
  else
189
194
  # More than one bridgable interface requires a user decision, so
190
195
  # show options to choose from.
191
- @env[:ui].info I18n.t('vagrant.actions.vm.bridged_networking.available',
192
- :prefix => false)
196
+ @env[:ui].info I18n.t(
197
+ 'vagrant.actions.vm.bridged_networking.available', prefix: false)
193
198
  bridgedifs.each_index do |index|
194
199
  interface = bridgedifs[index]
195
- @env[:ui].info("#{index + 1}) #{interface[:name]}", :prefix => false)
200
+ @env[:ui].info("#{index + 1}) #{interface[:name]}", prefix: false)
196
201
  end
197
202
  @env[:ui].info(I18n.t(
198
- 'vagrant.actions.vm.bridged_networking.choice_help')+"\n")
203
+ 'vagrant.actions.vm.bridged_networking.choice_help')+"\n")
199
204
 
200
205
  # The range of valid choices
201
206
  valid = Range.new(1, bridgedifs.length)
@@ -410,8 +415,7 @@ module VagrantPlugins
410
415
  if net_nums.empty?
411
416
  'vagrant-vnet0'
412
417
  else
413
- net_nums.sort! if net_nums
414
- free_names = Array(0..net_nums.last.next) - net_nums
418
+ free_names = Array(0..net_nums.max) - net_nums
415
419
  "vagrant-vnet#{free_names.first}"
416
420
  end
417
421
  end
@@ -0,0 +1,67 @@
1
+ require 'log4r'
2
+
3
+ require 'digest/md5'
4
+
5
+ module VagrantPlugins
6
+ module Parallels
7
+ module Action
8
+ class PrepareCloneSnapshot
9
+ @@lock = Mutex.new
10
+
11
+ def initialize(app, env)
12
+ @app = app
13
+ @logger = Log4r::Logger.new('vagrant_parallels::action::prepare_clone_snapshot')
14
+ end
15
+
16
+ def call(env)
17
+ if !env[:clone_id]
18
+ @logger.info('No source VM for cloning, skip snapshot preparing')
19
+ return @app.call(env)
20
+ end
21
+
22
+ # If we're not doing a linked clone, snapshots don't matter
23
+ if !env[:machine].provider_config.linked_clone \
24
+ || env[:machine].provider.pd_version_satisfies?('< 11')
25
+ return @app.call(env)
26
+ end
27
+
28
+ # We lock so that we don't snapshot in parallel
29
+ @@lock.synchronize do
30
+ lock_key = Digest::MD5.hexdigest("#{env[:clone_id]}-snapshot")
31
+ env[:machine].env.lock(lock_key, retry: true) do
32
+ prepare_snapshot(env)
33
+ end
34
+ end
35
+
36
+ # Continue
37
+ @app.call(env)
38
+ end
39
+
40
+ protected
41
+
42
+ def prepare_snapshot(env)
43
+ set_snapshot = env[:machine].provider_config.linked_clone_snapshot
44
+ env[:clone_snapshot] = set_snapshot || 'vagrant_linked_clone'
45
+
46
+ # Get the snapshots. We're done if it already exists
47
+ snapshots = env[:machine].provider.driver.list_snapshots(env[:clone_id])
48
+
49
+ if snapshots.include?(env[:clone_snapshot])
50
+ env[:clone_snapshot_id] = snapshots[env[:clone_snapshot]]
51
+ @logger.info('Linked clone snapshot already exists, doing nothing')
52
+ return
53
+ end
54
+
55
+ # We've specified the snapshot name but it doesn't exist
56
+ if set_snapshot
57
+ raise Errors::SnapshotNotFound, snapshot: set_snapshot
58
+ end
59
+
60
+ @logger.info('Creating a new snapshot for linked clone')
61
+ env[:clone_snapshot_id] = env[:machine].provider.driver.create_snapshot(
62
+ env[:clone_id], env[:clone_snapshot])
63
+ end
64
+ end
65
+ end
66
+ end
67
+ end
@@ -37,7 +37,7 @@ module VagrantPlugins
37
37
  else
38
38
  env[:ui].info(I18n.t(
39
39
  'vagrant.actions.vm.set_name.setting_name', name: name))
40
- env[:machine].provider.driver.set_name(name)
40
+ env[:machine].provider.driver.set_name(env[:machine].id, name)
41
41
  end
42
42
 
43
43
  # Create the sentinel
@@ -0,0 +1,25 @@
1
+ module VagrantPlugins
2
+ module Parallels
3
+ module Action
4
+ class SnapshotDelete
5
+ def initialize(app, env)
6
+ @app = app
7
+ end
8
+
9
+ def call(env)
10
+ snapshots = env[:machine].provider.driver.list_snapshots(env[:machine].id)
11
+ snapshot_id = snapshots[env[:snapshot_name]]
12
+
13
+ env[:ui].info I18n.t('vagrant.actions.vm.snapshot.deleting',
14
+ name: env[:snapshot_name])
15
+ env[:machine].provider.driver.delete_snapshot(
16
+ env[:machine].id, snapshot_id)
17
+
18
+ env[:ui].success I18n.t('vagrant.actions.vm.snapshot.deleted',
19
+ name: env[:snapshot_name])
20
+ @app.call(env)
21
+ end
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,23 @@
1
+ module VagrantPlugins
2
+ module Parallels
3
+ module Action
4
+ class SnapshotRestore
5
+ def initialize(app, env)
6
+ @app = app
7
+ end
8
+
9
+ def call(env)
10
+ snapshots = env[:machine].provider.driver.list_snapshots(env[:machine].id)
11
+ snapshot_id = snapshots[env[:snapshot_name]]
12
+
13
+ env[:ui].info I18n.t('vagrant.actions.vm.snapshot.restoring',
14
+ name: env[:snapshot_name])
15
+ env[:machine].provider.driver.restore_snapshot(
16
+ env[:machine].id, snapshot_id)
17
+
18
+ @app.call(env)
19
+ end
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,23 @@
1
+ module VagrantPlugins
2
+ module Parallels
3
+ module Action
4
+ class SnapshotSave
5
+ def initialize(app, env)
6
+ @app = app
7
+ end
8
+
9
+ def call(env)
10
+ env[:ui].info I18n.t('vagrant.actions.vm.snapshot.saving',
11
+ name: env[:snapshot_name])
12
+ env[:machine].provider.driver.create_snapshot(
13
+ env[:machine].id, env[:snapshot_name])
14
+
15
+ env[:ui].success I18n.t('vagrant.actions.vm.snapshot.saved',
16
+ name: env[:snapshot_name])
17
+
18
+ @app.call(env)
19
+ end
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,63 @@
1
+ module VagrantPlugins
2
+ module Parallels
3
+ module Cap
4
+ # Reads the forwarded ports that currently exist on the machine
5
+ # itself.
6
+ #
7
+ # This also may not match up with configured forwarded ports, because
8
+ # Vagrant auto port collision fixing may have taken place.
9
+ #
10
+ # @return [Hash<Integer, Integer>] Host => Guest port mappings.
11
+ def self.forwarded_ports(machine)
12
+ return nil if machine.state.id != :running
13
+
14
+ {}.tap do |result|
15
+ machine.provider.driver.read_forwarded_ports.each do |fp|
16
+ result[fp[:hostport]] = fp[:guestport]
17
+ end
18
+ end
19
+ end
20
+
21
+ # Returns host's IP address that can be used to access the host machine
22
+ # from the VM.
23
+ #
24
+ # @return [String] Host's IP address
25
+ def self.host_address(machine)
26
+
27
+ shared_iface = machine.provider.driver.read_shared_interface
28
+ return shared_iface[:ip] if shared_iface
29
+
30
+ nil
31
+ end
32
+
33
+ # Reads the network interface card MAC addresses and returns them.
34
+ #
35
+ # @return [Hash<Integer, String>] Adapter => MAC address
36
+ def self.nic_mac_addresses(machine)
37
+ nic_macs = machine.provider.driver.read_mac_addresses
38
+
39
+ # Make numeration starting from 1, as it is expected in Vagrant.
40
+ Hash[nic_macs.map.with_index{ |mac, index| [index+1, mac] }]
41
+ end
42
+
43
+ # Returns guest's IP address that can be used to access the VM from the
44
+ # host machine.
45
+ #
46
+ # @return [String] Guest's IP address
47
+ def self.public_address(machine)
48
+ return nil if machine.state.id != :running
49
+
50
+ ssh_info = machine.ssh_info
51
+ return nil if !ssh_info
52
+ ssh_info[:host]
53
+ end
54
+
55
+ # Returns a list of the snapshots that are taken on this machine.
56
+ #
57
+ # @return [Array<String>] Snapshot Name
58
+ def self.snapshot_list(machine)
59
+ machine.provider.driver.list_snapshots(machine.id).keys
60
+ end
61
+ end
62
+ end
63
+ end
@@ -6,7 +6,8 @@ module VagrantPlugins
6
6
  attr_accessor :destroy_unused_network_interfaces
7
7
  attr_accessor :functional_psf
8
8
  attr_accessor :optimize_power_consumption
9
- attr_accessor :use_linked_clone
9
+ attr_accessor :linked_clone
10
+ attr_accessor :linked_clone_snapshot
10
11
  attr_accessor :name
11
12
  attr_reader :network_adapters
12
13
  attr_accessor :regen_src_uuid
@@ -17,13 +18,15 @@ module VagrantPlugins
17
18
 
18
19
  # Compatibility with old names
19
20
  alias :regen_box_uuid= :regen_src_uuid=
21
+ alias :use_linked_clone= :linked_clone=
20
22
 
21
23
  def initialize
22
24
  @check_guest_tools = UNSET_VALUE
23
25
  @customizations = []
24
26
  @destroy_unused_network_interfaces = UNSET_VALUE
25
27
  @functional_psf = UNSET_VALUE
26
- @use_linked_clone = UNSET_VALUE
28
+ @linked_clone = UNSET_VALUE
29
+ @linked_clone_snapshot = UNSET_VALUE
27
30
  @network_adapters = {}
28
31
  @name = UNSET_VALUE
29
32
  @optimize_power_consumption = UNSET_VALUE
@@ -77,7 +80,8 @@ module VagrantPlugins
77
80
  @functional_psf = true
78
81
  end
79
82
 
80
- @use_linked_clone = false if @use_linked_clone == UNSET_VALUE
83
+ @linked_clone = false if @linked_clone == UNSET_VALUE
84
+ @linked_clone_snapshot = nil if @linked_clone_snapshot == UNSET_VALUE
81
85
 
82
86
  @name = nil if @name == UNSET_VALUE
83
87
 
@@ -55,12 +55,12 @@ module VagrantPlugins
55
55
  # Makes a clone of the virtual machine.
56
56
  #
57
57
  # @param [String] src_name Name or UUID of the source VM or template.
58
- # @param [String] dst_name Name of the destination VM.
59
58
  # @param [<String => String>] options Options to clone virtual machine.
60
59
  # @return [String] UUID of the new VM.
61
- def clone_vm(src_name, dst_name, options={})
60
+ def clone_vm(src_name, options={})
61
+ dst_name = "vagrant_temp_#{(Time.now.to_f * 1000.0).to_i}_#{rand(100000)}"
62
+
62
63
  args = ['clone', src_name, '--name', dst_name]
63
- args << '--template' if options[:template]
64
64
  args.concat(['--dst', options[:dst]]) if options[:dst]
65
65
 
66
66
  # Linked clone options
@@ -127,11 +127,16 @@ module VagrantPlugins
127
127
 
128
128
  # Creates a snapshot for the specified virtual machine.
129
129
  #
130
- # @param [String] uuid Name or UUID of the target VM.
131
- # @param [<Symbol => String, Boolean>] options Snapshot options.
130
+ # @param [String] uuid Name or UUID of the target VM
131
+ # @param [String] snapshot_name Snapshot name
132
132
  # @return [String] ID of the created snapshot.
133
- def create_snapshot(uuid, options)
134
- raise NotImplementedError
133
+ def create_snapshot(uuid, snapshot_name)
134
+ stdout = execute_prlctl('snapshot', uuid, '--name', snapshot_name)
135
+ if stdout =~ /\{([\w-]+)\}/
136
+ return Regexp.last_match(1)
137
+ end
138
+
139
+ raise Errors::SnapshotIdNotDetected, stdout: stdout
135
140
  end
136
141
 
137
142
  # Deletes the virtual machine references by this driver.
@@ -148,6 +153,14 @@ module VagrantPlugins
148
153
  end
149
154
  end
150
155
 
156
+ # Deletes the specified snapshot
157
+ #
158
+ # @param [String] uuid Name or UUID of the target VM
159
+ # @param [String] snapshot_id Snapshot ID
160
+ def delete_snapshot(uuid, snapshot_id)
161
+ execute_prlctl('snapshot-delete', uuid, '--id', snapshot_id)
162
+ end
163
+
151
164
  # Deletes any host only networks that aren't being used for anything.
152
165
  def delete_unused_host_only_networks
153
166
  raise NotImplementedError
@@ -203,6 +216,35 @@ module VagrantPlugins
203
216
  raise NotImplementedError
204
217
  end
205
218
 
219
+ # Lists all snapshots for the specified VM. Returns an empty hash if
220
+ # there are no snapshots.
221
+ #
222
+ # @param [String] uuid Name or UUID of the target VM.
223
+ # @return [<String => String>] {'Snapshot Name' => 'Snapshot UUID'}
224
+ def list_snapshots(uuid)
225
+ settings = read_settings(uuid)
226
+ snap_config = File.join(settings.fetch('Home'), 'Snapshots.xml')
227
+
228
+ # There are no snapshots, exit
229
+ return {} if !File.exist?(snap_config)
230
+
231
+ xml = Nokogiri::XML(File.read(snap_config))
232
+ snapshots = {}
233
+
234
+ # Loop over all 'SavedStateItem' and fetch 'Name' => 'ID' pairs
235
+ xml.xpath('//SavedStateItem').each do |snap|
236
+ snap_id = snap.attr('guid')
237
+
238
+ # The first entry is always empty (the base sate)
239
+ next if snap_id.empty?
240
+
241
+ snap_name = snap.at('Name').text
242
+ snapshots[snap_name] = snap_id
243
+ end
244
+
245
+ snapshots
246
+ end
247
+
206
248
  # Halts the virtual machine (pulls the plug).
207
249
  def halt(force=false)
208
250
  args = ['stop', @uuid]
@@ -247,15 +289,6 @@ module VagrantPlugins
247
289
  bridged_ifaces
248
290
  end
249
291
 
250
- # Returns current snapshot ID for the specified VM. Returns nil if
251
- # the VM doesn't have any snapshot.
252
- #
253
- # @param [String] uuid Name or UUID of the target VM.
254
- # @return [String]
255
- def read_current_snapshot(uuid)
256
- raise NotImplementedError
257
- end
258
-
259
292
  def read_forwarded_ports(global=false)
260
293
  raise NotImplementedError
261
294
  end
@@ -491,8 +524,9 @@ module VagrantPlugins
491
524
  # Registers the virtual machine
492
525
  #
493
526
  # @param [String] pvm_file Path to the machine image (*.pvm)
494
- def register(pvm_file)
495
- args = [@prlctl_path, 'register', pvm_file]
527
+ # @param [Array<String>] opts List of options for "prlctl register"
528
+ def register(pvm_file, opts=[])
529
+ args = [@prlctl_path, 'register', pvm_file, *opts]
496
530
 
497
531
  3.times do
498
532
  result = raw(*args)
@@ -513,14 +547,12 @@ module VagrantPlugins
513
547
  execute(*args)
514
548
  end
515
549
 
516
- # Checks that specified virtual machine is registered
550
+ # Switches the VM state to the specified snapshot
517
551
  #
518
- # @return [Boolean]
519
- def registered?(uuid)
520
- args = %w(list --all --info --no-header -o uuid)
521
-
522
- execute_prlctl(*args).include?(uuid) ||
523
- execute_prlctl(*args, '--template').include?(uuid)
552
+ # @param [String] uuid Name or UUID of the target VM
553
+ # @param [String] snapshot_id Snapshot ID
554
+ def restore_snapshot(uuid, snapshot_id)
555
+ execute_prlctl('snapshot-switch', uuid, '-i', snapshot_id)
524
556
  end
525
557
 
526
558
  # Resumes the virtual machine.
@@ -531,9 +563,10 @@ module VagrantPlugins
531
563
 
532
564
  # Sets the name of the virtual machine.
533
565
  #
534
- # @param [String] name New VM name.
535
- def set_name(name)
536
- execute_prlctl('set', @uuid, '--name', name)
566
+ # @param [String] uuid VM name or UUID
567
+ # @param [String] new_name New VM name
568
+ def set_name(uuid, new_name)
569
+ execute_prlctl('set', uuid, '--name', new_name)
537
570
  end
538
571
 
539
572
  # Sets Power Consumption method.