vagrant-parallels 1.3.13 → 1.4.0.rc1

Sign up to get free protection for your applications and to get access to all the features.
@@ -18,7 +18,8 @@ module VagrantPlugins
18
18
  include Vagrant::Util::Retryable
19
19
  include Vagrant::Util::NetworkIP
20
20
 
21
- def initialize
21
+ def initialize(uuid)
22
+ @uuid = uuid
22
23
  @logger = Log4r::Logger.new('vagrant_parallels::driver::base')
23
24
 
24
25
  # This flag is used to keep track of interrupted state (SIGINT)
@@ -38,38 +39,125 @@ module VagrantPlugins
38
39
  @logger.info("prlsrvctl path: #{@prlsrvctl_path}")
39
40
  end
40
41
 
42
+ # Removes all port forwarding rules for the virtual machine.
43
+ def clear_forwarded_ports
44
+ raise NotImplementedError
45
+ end
46
+
41
47
  # Clears the shared folders that have been set on the virtual machine.
42
48
  def clear_shared_folders
49
+ share_ids = read_shared_folders.keys
50
+ share_ids.each do |id|
51
+ execute_prlctl('set', @uuid, '--shf-host-del', id)
52
+ end
53
+ end
54
+
55
+ # Makes a clone of the virtual machine.
56
+ #
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
+ # @param [<String => String>] options Options to clone virtual machine.
60
+ # @return [String] UUID of the new VM.
61
+ def clone_vm(src_name, dst_name, options={})
62
+ args = ['clone', src_name, '--name', dst_name]
63
+ args << '--template' if options[:template]
64
+ args.concat(['--dst', options[:dst]]) if options[:dst]
65
+
66
+ # Linked clone options
67
+ args << '--linked' if options[:linked]
68
+ args.concat(['--id', options[:snapshot_id]]) if options[:snapshot_id]
69
+
70
+ execute_prlctl(*args) do |_, data|
71
+ lines = data.split('\r')
72
+ # The progress of the clone will be in the last line. Do a greedy
73
+ # regular expression to find what we're looking for.
74
+ if lines.last =~ /Copying hard disk.+?(\d{,3}) ?%/
75
+ yield $1.to_i if block_given?
76
+ end
77
+ end
78
+ read_vms[dst_name]
43
79
  end
44
80
 
45
81
  # Compacts all disk drives of virtual machine
46
- def compact
82
+ def compact(uuid)
83
+ hw_info = read_settings(uuid).fetch('Hardware', {})
84
+ used_drives = hw_info.select do |name, _|
85
+ name.start_with? 'hdd'
86
+ end
87
+ used_drives.each_value do |drive_params|
88
+ execute(@prldisktool_path, 'compact', '--hdd', drive_params['image']) do |_, data|
89
+ lines = data.split('\r')
90
+ # The progress of the compact will be in the last line. Do a greedy
91
+ # regular expression to find what we're looking for.
92
+ if lines.last =~ /.+?(\d{,3}) ?%/
93
+ yield $1.to_i if block_given?
94
+ end
95
+ end
96
+ end
47
97
  end
48
98
 
49
99
  # Creates a host only network with the given options.
50
100
  #
51
- # @param [Hash] options Options to create the host only network.
52
- # @return [Hash] The details of the host only network, including
53
- # keys `:name`, `:ip`, `:netmask` and `:dhcp`
101
+ # @param [<Symbol => String>] options Hostonly network options.
102
+ # @return [<Symbol => String>] The details of the host only network,
103
+ # including keys `:name`, `:ip`, `:netmask` and `:dhcp`
104
+ # @param [<Symbol => String>] options
54
105
  def create_host_only_network(options)
106
+ # Create the interface
107
+ execute_prlsrvctl('net', 'add', options[:network_id], '--type', 'host-only')
108
+
109
+ # Configure it
110
+ args = ['--ip', "#{options[:adapter_ip]}/#{options[:netmask]}"]
111
+ if options[:dhcp]
112
+ args.concat(['--dhcp-ip', options[:dhcp][:ip],
113
+ '--ip-scope-start', options[:dhcp][:lower],
114
+ '--ip-scope-end', options[:dhcp][:upper]])
115
+ end
116
+
117
+ execute_prlsrvctl('net', 'set', options[:network_id], *args)
118
+
119
+ # Return the details
120
+ {
121
+ name: options[:network_id],
122
+ ip: options[:adapter_ip],
123
+ netmask: options[:netmask],
124
+ dhcp: options[:dhcp]
125
+ }
126
+ end
127
+
128
+ # Creates a snapshot for the specified virtual machine.
129
+ #
130
+ # @param [String] uuid Name or UUID of the target VM.
131
+ # @param [<Symbol => String, Boolean>] options Snapshot options.
132
+ # @return [String] ID of the created snapshot.
133
+ def create_snapshot(uuid, options)
134
+ raise NotImplementedError
55
135
  end
56
136
 
57
137
  # Deletes the virtual machine references by this driver.
58
138
  def delete
139
+ execute_prlctl('delete', @uuid)
59
140
  end
60
141
 
61
142
  # Deletes all disabled network adapters from the VM configuration
62
143
  def delete_disabled_adapters
144
+ read_settings.fetch('Hardware', {}).each do |adapter, params|
145
+ if adapter.start_with?('net') and !params.fetch('enabled', true)
146
+ execute_prlctl('set', @uuid, '--device-del', adapter)
147
+ end
148
+ end
63
149
  end
64
150
 
65
151
  # Deletes any host only networks that aren't being used for anything.
66
152
  def delete_unused_host_only_networks
153
+ raise NotImplementedError
67
154
  end
68
155
 
69
156
  # Disables requiring password on such operations as creating, adding,
70
157
  # removing or cloning the virtual machine.
71
158
  #
72
159
  def disable_password_restrictions
160
+ raise NotImplementedError
73
161
  end
74
162
 
75
163
  # Enables network adapters on the VM.
@@ -85,104 +173,259 @@ module VagrantPlugins
85
173
  #
86
174
  # This must support setting up both host only and bridged networks.
87
175
  #
88
- # @param [Array<Hash>] adapters Array of adapters to enable.
176
+ # @param [Array<Symbol => Symbol, String>] adapters
177
+ # Array of adapters to be enabled.
89
178
  def enable_adapters(adapters)
179
+ raise NotImplementedError
90
180
  end
91
181
 
92
- # Exports the virtual machine to the given path.
182
+ # Create a set of port forwarding rules for a virtual machine.
183
+ #
184
+ # This will not affect any previously set forwarded ports,
185
+ # so be sure to delete those if you need to.
93
186
  #
94
- # @param [String] path Path to the OVF file.
95
- # @yield [progress] Yields the block with the progress of the export.
96
- def export(path)
187
+ # The format of each port hash should be the following:
188
+ #
189
+ # {
190
+ # guestport: 80,
191
+ # hostport: 8500,
192
+ # name: "foo",
193
+ # protocol: "tcp"
194
+ # }
195
+ #
196
+ # Note that "protocol" is optional and will default to "tcp".
197
+ #
198
+ # @param [Array<Hash>] ports An array of ports to set. See documentation
199
+ # for more information on the format.
200
+ def forward_ports(ports)
201
+ raise NotImplementedError
97
202
  end
98
203
 
99
204
  # Halts the virtual machine (pulls the plug).
100
- def halt(force)
205
+ def halt(force=false)
206
+ args = ['stop', @uuid]
207
+ args << '--kill' if force
208
+ execute_prlctl(*args)
101
209
  end
102
210
 
103
- # Imports the VM by cloning from registered template.
211
+ # Returns a list of bridged interfaces.
104
212
  #
105
- # @param [String] template_uuid Registered template UUID.
106
- # @return [String] UUID of the imported VM.
107
- def import(template_uuid)
213
+ # @return [Array<Symbol => String>]
214
+ def read_bridged_interfaces
215
+ host_hw_info = read_host_info.fetch('Hardware info', {})
216
+ net_list = host_hw_info.select do |name, attrs|
217
+ # Get all network interfaces except 'vnicXXX'
218
+ attrs.fetch('type') == 'net' and name !~ /^(vnic(.+?))$/
219
+ end
220
+
221
+ bridged_ifaces = []
222
+ net_list.keys.each do |iface|
223
+ info = {}
224
+ ifconfig = execute('ifconfig', iface)
225
+ # Assign default values
226
+ info[:name] = iface
227
+ info[:ip] = '0.0.0.0'
228
+ info[:netmask] = '0.0.0.0'
229
+ info[:status] = 'Down'
230
+
231
+ if ifconfig =~ /(?<=inet\s)(\S*)/
232
+ info[:ip] = $1.to_s
233
+ end
234
+ if ifconfig =~ /(?<=netmask\s)(\S*)/
235
+ # Netmask will be converted from hex to dec:
236
+ # '0xffffff00' -> '255.255.255.0'
237
+ info[:netmask] = $1.hex.to_s(16).scan(/../).each.map{|octet| octet.hex}.join('.')
238
+ end
239
+ if ifconfig =~ /\W(UP)\W/ and ifconfig !~ /(?<=status:\s)inactive$/
240
+ info[:status] = 'Up'
241
+ end
242
+
243
+ bridged_ifaces << info
244
+ end
245
+ bridged_ifaces
108
246
  end
109
247
 
110
- # Parses given block (JSON string) to object
111
- def json(default=nil)
112
- data = yield
113
- JSON.parse(data) rescue default
248
+ # Returns current snapshot ID for the specified VM. Returns nil if
249
+ # the VM doesn't have any snapshot.
250
+ #
251
+ # @param [String] uuid Name or UUID of the target VM.
252
+ # @return [String]
253
+ def read_current_snapshot(uuid)
254
+ raise NotImplementedError
114
255
  end
115
256
 
116
- # Returns the maximum number of network adapters.
117
- def max_network_adapters
118
- 16
257
+ def read_forwarded_ports(global=false)
258
+ raise NotImplementedError
119
259
  end
120
260
 
121
- # Returns a list of bridged interfaces.
261
+ # Returns an IP of the virtual machine. It requires that Shared network
262
+ # adapter is configured for this VM and it obtains an IP via DHCP.
122
263
  #
123
- # @return [Hash]
124
- def read_bridged_interfaces
264
+ #
265
+ # @return [String] IP address leased by DHCP server in "Shared" network
266
+ def read_guest_ip
267
+ mac_addr = read_mac_address.downcase
268
+ leases_file = '/Library/Preferences/Parallels/parallels_dhcp_leases'
269
+ begin
270
+ File.open(leases_file).grep(/#{mac_addr}/) do |line|
271
+ return line[/^(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})/]
272
+ end
273
+ rescue Errno::EACCES
274
+ raise Errors::DhcpLeasesNotAccessible, :leases_file => leases_file.to_s
275
+ rescue Errno::ENOENT
276
+ # File does not exist
277
+ # Perhaps, it is the fist start of Parallels Desktop
278
+ return nil
279
+ end
280
+
281
+ nil
282
+ end
283
+
284
+ # Returns path to the Parallels Tools ISO file.
285
+ #
286
+ # @param [String] guest_os Guest os type: "linux", "darwin" or "windows"
287
+ # @return [String] Path to the ISO.
288
+ def read_guest_tools_iso_path(guest_os)
289
+ guest_os = guest_os.to_sym
290
+ iso_name ={
291
+ linux: 'prl-tools-lin.iso',
292
+ darwin: 'prl-tools-mac.iso',
293
+ windows: 'prl-tools-win.iso'
294
+ }
295
+ return nil if !iso_name[guest_os]
296
+
297
+ bundle_id = 'com.parallels.desktop.console'
298
+ bundle_path = execute('mdfind', "kMDItemCFBundleIdentifier == #{bundle_id}")
299
+ iso_path = File.expand_path("./Contents/Resources/Tools/#{iso_name[guest_os]}",
300
+ bundle_path.split("\n")[0])
301
+
302
+ if !File.exist?(iso_path)
303
+ raise Errors::ParallelsToolsIsoNotFound, :iso_path => iso_path
304
+ end
305
+
306
+ iso_path
125
307
  end
126
308
 
127
309
  # Returns the state of guest tools that is installed on this VM.
128
310
  # Can be any of:
129
- # * "installed"
130
- # * "not_installed"
131
- # * "possibly_installed"
132
- # * "outdated"
311
+ # * :installed
312
+ # * :not_installed
313
+ # * :possibly_installed
314
+ # * :outdated
133
315
  #
134
- # @return [String]
316
+ # @return [Symbol]
135
317
  def read_guest_tools_state
318
+ state = read_settings.fetch('GuestTools', {}).fetch('state', nil)
319
+ state = 'not_installed' if !state
320
+ state.to_sym
136
321
  end
137
322
 
138
- # Returns path to the Parallels Tools ISO file.
323
+ # Returns Parallels Desktop properties and common information about
324
+ # the host machine.
139
325
  #
140
- # @param [String] guest_os Guest os type: "linux", "darwin" or "windows"
141
- # @return [String] Path to the ISO.
142
- def read_guest_tools_iso_path(guest_os)
326
+ # @return [<Symbol => String>]
327
+ def read_host_info
328
+ json { execute_prlctl('server', 'info', '--json') }
143
329
  end
144
330
 
145
331
  # Returns a list of available host only interfaces.
146
332
  #
147
- # @return [Hash]
333
+ # @return [Array<Symbol => String>]
148
334
  def read_host_only_interfaces
335
+ raise NotImplementedError
149
336
  end
150
337
 
151
338
  # Returns the MAC address of the first Shared network interface.
152
339
  #
153
340
  # @return [String]
154
341
  def read_mac_address
342
+ hw_info = read_settings.fetch('Hardware', {})
343
+ shared_ifaces = hw_info.select do |name, params|
344
+ name.start_with?('net') && params['type'] == 'shared'
345
+ end
346
+
347
+ if shared_ifaces.empty?
348
+ raise Errors::SharedAdapterNotFound
349
+ end
350
+
351
+ shared_ifaces.values.first.fetch('mac', nil)
155
352
  end
156
353
 
157
354
  # Returns the array of network interface card MAC addresses
158
355
  #
159
356
  # @return [Array<String>]
160
357
  def read_mac_addresses
358
+ read_vm_option('mac').strip.gsub(':', '').split(' ')
161
359
  end
162
360
 
163
361
  # Returns a list of network interfaces of the VM.
164
362
  #
165
- # @return [Hash]
363
+ # @return [<Integer => Hash>]
166
364
  def read_network_interfaces
365
+ raise NotImplementedError
366
+ end
367
+
368
+ # Returns virtual machine settings
369
+ #
370
+ # @return [<String => String, Hash>]
371
+ def read_settings(uuid=@uuid)
372
+ vm = json { execute_prlctl('list', uuid, '--info', '--no-header', '--json') }
373
+ vm.last
374
+ end
375
+
376
+ # Returns the unique name (e.q. "ID") on the first Shared network in
377
+ # Parallels Desktop configuration.
378
+ # By default there is only one called "Shared".
379
+ #
380
+ # @return [String] Shared network ID
381
+ def read_shared_network_id
382
+ # There should be only one Shared interface
383
+ shared_net = read_virtual_networks.detect do |net|
384
+ net['Type'] == 'shared'
385
+ end
386
+ shared_net.fetch('Network ID')
167
387
  end
168
388
 
169
389
  # Returns info about shared network interface.
170
390
  #
171
- # @return [Hash]
391
+ # @return [<Symbol => String, Hash>]
172
392
  def read_shared_interface
393
+ raise NotImplementedError
173
394
  end
174
395
 
175
396
  # Returns a list of shared folders in format:
176
397
  # { id => hostpath, ... }
177
398
  #
178
- # @return [Hash]
399
+ # @return [<String => String>]
179
400
  def read_shared_folders
401
+ shf_info = read_settings.fetch('Host Shared Folders', {})
402
+ list = {}
403
+ shf_info.delete_if { |k,v| k == 'enabled' }.each do |id, data|
404
+ list[id] = data.fetch('path')
405
+ end
406
+
407
+ list
180
408
  end
181
409
 
182
410
  # Returns the current state of this VM.
183
411
  #
184
- # @return [Symbol]
412
+ # @return [Symbol] Virtual machine state
185
413
  def read_state
414
+ read_vm_option('status').strip.to_sym
415
+ end
416
+
417
+ # Returns a list of all forwarded ports in use by active
418
+ # virtual machines.
419
+ #
420
+ # @return [Array]
421
+ def read_used_ports
422
+ end
423
+
424
+ # Returns the configuration of all virtual networks in Parallels Desktop.
425
+ #
426
+ # @return [Array<String => String>]
427
+ def read_virtual_networks
428
+ json { execute_prlsrvctl('net', 'list', '--json') }
186
429
  end
187
430
 
188
431
  # Returns a value of specified VM option. Raises an exception if value
@@ -192,41 +435,99 @@ module VagrantPlugins
192
435
  # @param [String] uuid Virtual machine UUID
193
436
  # @return [String]
194
437
  def read_vm_option(option, uuid=@uuid)
438
+ out = execute_prlctl('list', uuid,'--no-header', '-o', option).strip
439
+ if out.empty?
440
+ raise Errors::ParallelsVMOptionNotFound, vm_option: option
441
+ end
442
+
443
+ out
195
444
  end
196
445
 
197
- # Returns a list of all registered
198
- # virtual machines and templates.
446
+ # Returns names and ids of all virtual machines and templates.
199
447
  #
200
- # @return [Hash]
448
+ # @return [<String => String>]
201
449
  def read_vms
450
+ args = %w(list --all --no-header --json -o name,uuid)
451
+ vms_arr = json([]) { execute_prlctl(*args) }
452
+ templates_arr = json([]) { execute_prlctl(*args, '--template') }
453
+
454
+ vms = vms_arr | templates_arr
455
+ Hash[vms.map { |i| [i.fetch('name'), i.fetch('uuid')] }]
456
+ end
457
+
458
+ # Returns the configuration of all VMs and templates.
459
+ #
460
+ # @return [Array <String => String>]
461
+ def read_vms_info
462
+ args = %w(list --all --info --no-header --json)
463
+ vms_arr = json([]) { execute_prlctl(*args) }
464
+ templates_arr = json([]) { execute_prlctl(*args, '--template') }
465
+
466
+ vms_arr | templates_arr
202
467
  end
203
468
 
204
469
  # Regenerates 'SourceVmUuid' to avoid SMBIOS UUID collision [GH-113]
205
470
  #
206
471
  def regenerate_src_uuid
472
+ settings = read_settings
473
+ vm_config = File.join(settings.fetch('Home'), 'config.pvs')
474
+
475
+ # Generate and put new SourceVmUuid
476
+ xml = Nokogiri::XML(File.open(vm_config))
477
+ p = '//ParallelsVirtualMachine/Identification/SourceVmUuid'
478
+ xml.xpath(p).first.content = "{#{SecureRandom.uuid}}"
479
+
480
+ File.open(vm_config, 'w') do |f|
481
+ f.write xml.to_xml
482
+ end
207
483
  end
208
484
 
209
485
  # Registers the virtual machine
210
486
  #
211
487
  # @param [String] pvm_file Path to the machine image (*.pvm)
212
488
  def register(pvm_file)
489
+ args = [@prlctl_path, 'register', pvm_file]
490
+
491
+ 3.times do
492
+ result = raw(*args)
493
+ # Exit if everything is OK
494
+ return if result.exit_code == 0
495
+
496
+ # It may occur in the race condition with other Vagrant processes.
497
+ # It is OK, just exit.
498
+ return if result.stderr.include?('is already registered.')
499
+
500
+ # Sleep a bit though to give Parallels Desktop time to fix itself
501
+ sleep 2
502
+ end
503
+
504
+ # If we reach this point, it means that we consistently got the
505
+ # failure, do a standard execute now. This will raise an
506
+ # exception if it fails again.
507
+ execute(*args)
213
508
  end
214
509
 
215
- # Resumes the virtual machine.
510
+ # Checks that specified virtual machine is registered
216
511
  #
217
- def resume
512
+ # @return [Boolean]
513
+ def registered?(uuid)
514
+ args = %w(list --all --info --no-header -o uuid)
515
+
516
+ execute_prlctl(*args).include?(uuid) ||
517
+ execute_prlctl(*args, '--template').include?(uuid)
218
518
  end
219
519
 
220
- # Sets the MAC address of the first network adapter.
520
+ # Resumes the virtual machine.
221
521
  #
222
- # @param [String] mac MAC address without any spaces/hyphens.
223
- def set_mac_address(mac)
522
+ def resume
523
+ execute_prlctl('resume', @uuid)
224
524
  end
225
525
 
226
526
  # Sets the name of the virtual machine.
227
527
  #
228
528
  # @param [String] name New VM name.
229
529
  def set_name(name)
530
+ execute_prlctl('set', @uuid, '--name', name)
230
531
  end
231
532
 
232
533
  # Sets Power Consumption method.
@@ -234,39 +535,104 @@ module VagrantPlugins
234
535
  # @param [Boolean] optimized Use "Longer Battery Life"
235
536
  # instead "Better Performance"
236
537
  def set_power_consumption_mode(optimized)
538
+ raise NotImplementedError
237
539
  end
238
540
 
239
541
  # Share a set of folders on this VM.
240
542
  #
241
- # @param [Array<Hash>] folders
543
+ # @param [Array<Symbol => String>] folders
242
544
  def share_folders(folders)
545
+ folders.each do |folder|
546
+ # Add the shared folder
547
+ execute_prlctl('set', @uuid,
548
+ '--shf-host-add', folder[:name],
549
+ '--path', folder[:hostpath])
550
+ end
551
+ end
552
+
553
+ # Reads the SSH IP of this VM.
554
+ #
555
+ # @return [String] IP address to use for SSH connection to the VM.
556
+ def ssh_ip
557
+ read_guest_ip
243
558
  end
244
559
 
245
560
  # Reads the SSH port of this VM.
246
561
  #
247
562
  # @param [Integer] expected Expected guest port of SSH.
563
+ # @return [Integer] Port number to use for SSH connection to the VM.
248
564
  def ssh_port(expected)
565
+ expected
249
566
  end
250
567
 
251
568
  # Starts the virtual machine.
252
569
  #
253
570
  def start
571
+ execute_prlctl('start', @uuid)
254
572
  end
255
573
 
256
- # Suspend the virtual machine.
574
+ # Suspends the virtual machine.
257
575
  def suspend
576
+ execute_prlctl('suspend', @uuid)
577
+ end
578
+
579
+ # Performs un-registeration of the specified VM in Parallels Desktop.
580
+ # Virtual machine will be removed from the VM list, but its image will
581
+ # not be deleted from the disk. So, it can be registered again.
582
+ def unregister(uuid)
583
+ args = [@prlctl_path, 'unregister', uuid]
584
+ 3.times do
585
+ result = raw(*args)
586
+ # Exit if everything is OK
587
+ return if result.exit_code == 0
588
+
589
+ # It may occur in the race condition with other Vagrant processes.
590
+ # Both are OK, just exit.
591
+ return if result.stderr.include?('is not registered')
592
+ return if result.stderr.include?('is being cloned')
593
+
594
+ # Sleep a bit though to give Parallels Desktop time to fix itself
595
+ sleep 2
596
+ end
597
+
598
+ # If we reach this point, it means that we consistently got the
599
+ # failure, do a standard execute now. This will raise an
600
+ # exception if it fails again.
601
+ execute(*args)
258
602
  end
259
603
 
260
604
  # Unshare folders.
261
605
  def unshare_folders(names)
606
+ names.each do |name|
607
+ execute_prlctl('set', @uuid, '--shf-host-del', name)
608
+ end
262
609
  end
263
610
 
264
611
  # Checks if a VM with the given UUID exists.
265
612
  #
266
613
  # @return [Boolean]
267
614
  def vm_exists?(uuid)
615
+ 5.times do
616
+ result = raw(@prlctl_path, 'list', uuid)
617
+ return true if result.exit_code == 0
618
+
619
+ # Sometimes this happens. In this case, retry.
620
+ # If we don't see this text, the VM really doesn't exist.
621
+ return false if !result.stderr.include?('Login failed:')
622
+
623
+ # Sleep a bit though to give Parallels Desktop time to fix itself
624
+ sleep 2
625
+ end
626
+
627
+ # If we reach this point, it means that we consistently got the
628
+ # failure, do a standard prlctl now. This will raise an
629
+ # exception if it fails again.
630
+ execute_prlctl('list', uuid)
631
+ true
268
632
  end
269
633
 
634
+ private
635
+
270
636
  # Wraps 'execute' and returns the output of given 'prlctl' subcommand.
271
637
  def execute_prlctl(*command, &block)
272
638
  execute(@prlctl_path, *command, &block)
@@ -285,7 +651,7 @@ module VagrantPlugins
285
651
  # nicely handled by Vagrant.
286
652
  if r.exit_code != 0
287
653
  if @interrupted
288
- @logger.info("Exit code != 0, but interrupted. Ignoring.")
654
+ @logger.info('Exit code != 0, but interrupted. Ignoring.')
289
655
  else
290
656
  # If there was an error running command, show the error and the
291
657
  # output.
@@ -297,6 +663,12 @@ module VagrantPlugins
297
663
  r.stdout
298
664
  end
299
665
 
666
+ # Parses given block (JSON string) to object
667
+ def json(default=nil)
668
+ data = yield
669
+ JSON.parse(data) rescue default
670
+ end
671
+
300
672
  # Executes a command and returns the raw result object.
301
673
  def raw(*command, &block)
302
674
  int_callback = lambda do
@@ -304,7 +676,7 @@ module VagrantPlugins
304
676
 
305
677
  # We have to execute this in a thread due to trap contexts
306
678
  # and locks.
307
- Thread.new { @logger.info("Interrupted.") }
679
+ Thread.new { @logger.info('Interrupted.') }
308
680
  end
309
681
 
310
682
  # Append in the options for subprocess
@@ -315,8 +687,6 @@ module VagrantPlugins
315
687
  end
316
688
  end
317
689
 
318
- private
319
-
320
690
  def util_path(bin)
321
691
  path = Vagrant::Util::Which.which(bin)
322
692
  return path if path
@@ -325,6 +695,7 @@ module VagrantPlugins
325
695
  path = File.join(folder, bin)
326
696
  return path if File.file?(path)
327
697
  end
698
+ nil
328
699
  end
329
700
 
330
701
  end