vagrant-kvm 0.1.4 → 0.1.5

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 (58) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +2 -0
  3. data/.rspec +1 -0
  4. data/.travis.yml +14 -0
  5. data/CHANGELOG.md +29 -0
  6. data/DEVELOPMENT.md +87 -0
  7. data/Gemfile +1 -0
  8. data/INSTALL.md +229 -0
  9. data/LICENSE +2 -1
  10. data/README.md +154 -63
  11. data/Rakefile +24 -1
  12. data/example_box/README.md +8 -8
  13. data/lib/vagrant-kvm/action.rb +47 -5
  14. data/lib/vagrant-kvm/action/boot.rb +0 -4
  15. data/lib/vagrant-kvm/action/clear_forwarded_ports.rb +53 -0
  16. data/lib/vagrant-kvm/action/forward_ports.rb +104 -0
  17. data/lib/vagrant-kvm/action/import.rb +97 -18
  18. data/lib/vagrant-kvm/action/init_storage_pool.rb +3 -1
  19. data/lib/vagrant-kvm/action/message_not_running.rb +16 -0
  20. data/lib/vagrant-kvm/action/network.rb +12 -13
  21. data/lib/vagrant-kvm/action/package_vagrantfile.rb +3 -1
  22. data/lib/vagrant-kvm/action/prepare_gui.rb +20 -0
  23. data/lib/vagrant-kvm/action/prepare_nfs_settings.rb +8 -16
  24. data/lib/vagrant-kvm/action/prepare_nfs_valid_ids.rb +17 -0
  25. data/lib/vagrant-kvm/action/reset_image_permission.rb +23 -0
  26. data/lib/vagrant-kvm/action/resume_network.rb +28 -0
  27. data/lib/vagrant-kvm/action/share_folders.rb +6 -5
  28. data/lib/vagrant-kvm/action/suspend.rb +8 -1
  29. data/lib/vagrant-kvm/config.rb +103 -2
  30. data/lib/vagrant-kvm/driver/driver.rb +321 -99
  31. data/lib/vagrant-kvm/errors.rb +18 -0
  32. data/lib/vagrant-kvm/provider.rb +4 -1
  33. data/lib/vagrant-kvm/util.rb +3 -0
  34. data/lib/vagrant-kvm/util/commands.rb +23 -0
  35. data/lib/vagrant-kvm/util/definition_attributes.rb +33 -0
  36. data/lib/vagrant-kvm/util/disk_info.rb +48 -0
  37. data/lib/vagrant-kvm/util/network_definition.rb +44 -84
  38. data/lib/vagrant-kvm/util/vm_definition.rb +91 -103
  39. data/lib/vagrant-kvm/version.rb +1 -1
  40. data/locales/en.yml +8 -0
  41. data/locales/ja.yml +14 -0
  42. data/spec/acceptance/vagrant-kvm_spec.rb +80 -0
  43. data/spec/fedora/10.virt.rules +10 -0
  44. data/spec/fedora/50-vagrant-libvirt-access.pkla +6 -0
  45. data/spec/spec_helper.rb +30 -0
  46. data/spec/support/libvirt_helper.rb +37 -0
  47. data/spec/support/vagrant_kvm_helper.rb +39 -0
  48. data/spec/test_files/box.xml +74 -0
  49. data/spec/vagrant-kvm/config_spec.rb +56 -0
  50. data/spec/vagrant-kvm/driver/driver_spec.rb +36 -0
  51. data/spec/vagrant-kvm/errors_spec.rb +25 -0
  52. data/spec/vagrant-kvm/util/network_definition_spec.rb +60 -0
  53. data/spec/vagrant-kvm/util/vm_definition_spec.rb +76 -0
  54. data/templates/libvirt_domain.erb +34 -12
  55. data/templates/libvirt_network.erb +13 -0
  56. data/templates/package_Vagrantfile.erb +11 -0
  57. data/vagrant-kvm.gemspec +1 -2
  58. metadata +41 -42
@@ -6,12 +6,30 @@ module VagrantPlugins
6
6
  class VagrantKVMError < Vagrant::Errors::VagrantError
7
7
  error_namespace("vagrant_kvm.errors")
8
8
  end
9
+
9
10
  class KvmNoConnection < VagrantKVMError
10
11
  error_key(:kvm_no_connection)
11
12
  end
13
+
12
14
  class KvmInvalidVersion < VagrantKVMError
13
15
  error_key(:kvm_invalid_version)
14
16
  end
17
+
18
+ class KvmNoQEMUBinary < VagrantKVMError
19
+ error_key(:kvm_no_qemu_binary)
20
+ end
21
+
22
+ class KvmFailImageConversion < VagrantKVMError
23
+ error_key(:kvm_fail_image_conversion)
24
+ end
25
+
26
+ class KvmBadBoxFormat < VagrantKVMError
27
+ error_key(:kvm_bad_box_format)
28
+ end
29
+
30
+ class KvmFailedCommand < VagrantKVMError
31
+ error_key(:kvm_failed_command)
32
+ end
15
33
  end
16
34
  end
17
35
  end
@@ -66,7 +66,9 @@ module VagrantPlugins
66
66
  end
67
67
  end
68
68
 
69
- nil
69
+ # XXX duplicated with network.rb default
70
+ # If no private network configuration, return default ip
71
+ "192.168.123.10"
70
72
  end
71
73
 
72
74
  # Return the state of the VM
@@ -81,6 +83,7 @@ module VagrantPlugins
81
83
  state_id = :not_created if !@driver.uuid
82
84
  state_id = @driver.read_state if !state_id
83
85
  state_id = :unknown if !state_id
86
+ @logger.info("state is now:", state_id)
84
87
 
85
88
  # TODO Translate into short/long descriptions
86
89
  short = state_id
@@ -4,9 +4,12 @@ module VagrantPlugins
4
4
  module ProviderKvm
5
5
  module Util
6
6
  util_root = Pathname.new(File.expand_path("../util", __FILE__))
7
+ autoload :DefinitionAttributes, util_root.join("definition_attributes")
7
8
  autoload :VmDefinition, util_root.join("vm_definition")
8
9
  autoload :NetworkDefinition, util_root.join("network_definition")
9
10
  autoload :KvmTemplateRenderer, util_root.join("kvm_template_renderer")
11
+ autoload :Commands, util_root.join("commands")
12
+ autoload :DiskInfo, util_root.join("disk_info")
10
13
  end
11
14
  end
12
15
  end
@@ -0,0 +1,23 @@
1
+ module VagrantPlugins
2
+ module ProviderKvm
3
+ module Util
4
+ module Commands
5
+ def run_root_command(cmd)
6
+ # FIXME detect whether 'sudo' or 'su -c'
7
+ # for safety, we run cmd as single argument of sudo
8
+ unless res = system('sudo ' + cmd)
9
+ raise Errors::KvmFailedCommand, cmd: cmd, res: res
10
+ end
11
+ end
12
+
13
+ def run_command(cmd)
14
+ unless res = system(cmd)
15
+ raise Errors::KvmFailedCommand, cmd: cmd, res: res
16
+ end
17
+ res
18
+ end
19
+ end
20
+ end
21
+ end
22
+ end
23
+
@@ -0,0 +1,33 @@
1
+ module VagrantPlugins
2
+ module ProviderKvm
3
+ module Util
4
+ module DefinitionAttributes
5
+ module InstanceMethods
6
+ def attributes
7
+ @attributes ||= {}
8
+ end
9
+
10
+ def attributes=(attrs)
11
+ @attributes = attrs
12
+ end
13
+
14
+ def get(key)
15
+ attributes[key]
16
+ end
17
+
18
+ def set(key, val)
19
+ attributes[key] = val
20
+ end
21
+
22
+ def update(args={})
23
+ attributes.merge!(args)
24
+ end
25
+ end
26
+
27
+ def self.included(receiver)
28
+ receiver.send :include, InstanceMethods
29
+ end
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,48 @@
1
+ module VagrantPlugins
2
+ module ProviderKvm
3
+ module Util
4
+ class DiskInfo
5
+ include Errors
6
+
7
+ attr_reader :backing, :capacity, :cluster, :size, :type
8
+
9
+ def initialize(vol_path)
10
+ logger = Log4r::Logger.new("vagrant::kvm::util::disk_info")
11
+ # default values
12
+ @capacity = {:size => 10, :unit => 'G'}
13
+ @backing = nil
14
+ @cluster = nil
15
+ begin
16
+ diskinfo = %x[qemu-img info #{vol_path}]
17
+ diskinfo.each_line do |line|
18
+ case line
19
+ when /^file format:/
20
+ result = line.match(%r{file format:\s+(?<format>(\w+))})
21
+ @type = result[:format]
22
+ when /virtual size:/
23
+ result = line.match(%r{virtual size:\s+(?<size>\d+(\.\d+)?)(?<unit>.)\s+\((?<bytesize>\d+)\sbytes\)})
24
+ # always take the size in bytes to avoid conversion
25
+ @capacity = {:size => result[:bytesize], :unit => "B"}
26
+ when /^disk size:/
27
+ result = line.match(%r{disk size:\s+(?<size>\d+(\.\d+)?)(?<unit>.)})
28
+ @size = {:size => result[:size], :unit => result[:unit]}
29
+ when /^backing file:/
30
+ result = line.match(%r{backing file:\s+(?<file>(\S+))})
31
+ @backing = result[:file]
32
+ when /^cluster[_ ]size:/
33
+ result = line.match(%r{cluster[_ ]size:\s+(?<size>(\d+))})
34
+ @cluster = result[:size]
35
+ end
36
+ end
37
+ rescue Errors::KvmFailedCommand => e
38
+ logger.error 'Failed to find volume size. Using defaults.'
39
+ logger.error e
40
+ end
41
+ end
42
+
43
+ # TBD backing chain 'qemu-img info --backing-chain'
44
+
45
+ end
46
+ end
47
+ end
48
+ end
@@ -1,105 +1,65 @@
1
1
  # Utility class to manage libvirt network definition
2
- require "nokogiri"
2
+ require "rexml/document"
3
3
 
4
4
  module VagrantPlugins
5
5
  module ProviderKvm
6
6
  module Util
7
7
  class NetworkDefinition
8
- # Attributes of the Network
9
- attr_reader :name
10
- attr_reader :domain_name
11
- attr_reader :base_ip
8
+ include DefinitionAttributes
12
9
 
13
10
  def initialize(name, definition=nil)
14
- @name = name
11
+ # create with defaults
12
+ # XXX defaults should move to config
13
+ self.attributes = {
14
+ :forward => "nat",
15
+ :domain_name => "vagrant.local",
16
+ :base_ip => "192.168.192.1",
17
+ :netmask => "255.255.255.0",
18
+ :range => {
19
+ :start => "192.168.192.100",
20
+ :end => "192.168.192.200",
21
+ },
22
+ :hosts => [],
23
+ name: name,
24
+ }
25
+
15
26
  if definition
16
- doc = Nokogiri::XML(definition)
17
- @forward = doc.at_css("network forward")["mode"] if doc.at_css("network forward")
18
- @domain_name = doc.at_css("network domain")["name"] if doc.at_css("network domain")
19
- @base_ip = doc.at_css("network ip")["address"]
20
- @netmask = doc.at_css("network ip")["netmask"]
21
- @range = {
22
- :start => doc.at_css("network ip dhcp range")["start"],
23
- :end => doc.at_css("network ip dhcp range")["end"]
24
- }
25
- @hosts = []
26
- doc.css("network ip dhcp host").each do |host|
27
- @hosts << {
28
- :mac => host["mac"],
29
- :name => host["name"],
30
- :ip => host["ip"]
27
+ doc = REXML::Document.new definition
28
+ if doc.elements["/network/forward"]
29
+ set(:forward, doc.elements["/network/forward"].attributes["mode"])
30
+ end
31
+
32
+ if doc.elements["/network/domain"]
33
+ set(:domain_name, doc.elements["/network/domain"].attributes["name"])
34
+ end
35
+ set(:base_ip, doc.elements["/network/ip"].attributes["address"])
36
+ set(:netmask, doc.elements["/network/ip"].attributes["netmask"])
37
+ set(:range, {
38
+ :start => doc.elements["/network/ip/dhcp/range"].attributes["start"],
39
+ :end => doc.elements["/network/ip/dhcp/range"].attributes["end"]
40
+ })
41
+ hosts = []
42
+ doc.elements.each("/network/ip/dhcp/host") do |host|
43
+ hosts << {
44
+ :mac => host.attributes["mac"],
45
+ :name => host.attributes["name"],
46
+ :ip => host.attributes["ip"]
31
47
  }
32
48
  end
33
- else
34
- # create with defaults
35
- # XXX defaults should move to config
36
- @forward = "nat"
37
- @domain_name = "vagrant.local"
38
- @base_ip = "192.168.192.1"
39
- @netmask = "255.255.255.0"
40
- @range = {
41
- :start => "192.168.192.100",
42
- :end => "192.168.192.200"}
43
- @hosts = []
49
+ set(:hosts, hosts)
44
50
  end
45
51
  end
46
52
 
47
- def configure(config)
48
- config = {
49
- :forward => @forward,
50
- :domain_name => @domain_name,
51
- :base_ip => @base_ip,
52
- :netmask => @netmask,
53
- :range => @range,
54
- :hosts => @hosts}.merge(config)
55
-
56
- @forward = config[:forward]
57
- @domain_name = config[:domain_name]
58
- @base_ip = config[:base_ip]
59
- @netmask = config[:netmask]
60
- @range = config[:range]
61
- @hosts = config[:hosts]
62
- end
63
-
64
- def as_xml
65
- xml = <<-EOXML
66
- <network>
67
- <name>#{@name}</name>
68
- <forward mode='#{@forward}'/>
69
- <domain name='#{@domain_name}'/>
70
- <ip address='#{@base_ip}' netmask='#{@netmask}'>
71
- <dhcp>
72
- <range start='#{@range[:start]}' end='#{@range[:end]}' />
73
- </dhcp>
74
- </ip>
75
- </network>
76
- EOXML
77
- xml = inject_hosts(xml) if @hosts.length > 0
78
- xml
79
- end
80
-
81
- def add_host(host)
82
- cur_host = @hosts.detect {|h| h[:mac] == host[:mac]}
83
- if cur_host
84
- cur_host[:ip] = host[:ip]
85
- cur_host[:name] = host[:name]
86
- else
87
- @hosts << {
88
- :mac => host[:mac],
89
- :name => host[:name],
90
- :ip => host[:ip]}
53
+ def ==(other)
54
+ # Don't compare the hosts
55
+ [:forward, :domain_name, :base_ip, :netmask, :range,].all? do |key|
56
+ get(key) == other.get(key)
91
57
  end
92
58
  end
93
59
 
94
- def inject_hosts(xml)
95
- doc = Nokogiri::XML(xml)
96
- entry_point = doc.at_css("network ip dhcp range")
97
- @hosts.each do |host|
98
- entry_point.add_next_sibling "<host mac='#{host[:mac]}' name='#{host[:name]}' ip='#{host[:ip]}' />"
99
- end
100
- doc.to_xml
60
+ def as_xml
61
+ KvmTemplateRenderer.render("libvirt_network", attributes.dup)
101
62
  end
102
-
103
63
  end
104
64
  end
105
65
  end
@@ -1,135 +1,119 @@
1
1
  # Utility class to translate ovf definition to libvirt XML
2
2
  # and manage XML formatting for libvirt interaction
3
3
  # Not a full OVF converter, only the minimal needed definition
4
- require "nokogiri"
4
+ require "rexml/document"
5
5
 
6
6
  module VagrantPlugins
7
7
  module ProviderKvm
8
8
  module Util
9
9
  class VmDefinition
10
- # Attributes of the VM
11
- attr_accessor :name
12
- attr_reader :cpus
13
- attr_accessor :disk
14
- attr_reader :mac
15
- attr_reader :arch
16
- attr_reader :network
17
- attr_accessor :image_type
10
+ include Errors
11
+ include DefinitionAttributes
18
12
 
19
13
  def self.list_interfaces(definition)
20
- nics = {}
21
- ifcount = 0
22
- doc = Nokogiri::XML(definition)
14
+ nics = []
15
+ doc = REXML::Document.new definition
23
16
  # look for user mode interfaces
24
- doc.css("devices interface[type='user']").each do |item|
25
- ifcount += 1
26
- adapter = ifcount
27
- nics[adapter] ||= {}
28
- nics[adapter][:type] = :user
17
+ doc.elements.each("//devices/interface[@type='user']") do |item|
18
+ nics << {:type => :user, :network => nil }
29
19
  end
30
20
  # look for interfaces on virtual network
31
- doc.css("devices interface[type='network']").each do |item|
32
- ifcount += 1
33
- adapter = ifcount
34
- nics[adapter] ||= {}
35
- nics[adapter][:network] = item.at_css("source")["network"]
36
- nics[adapter][:type] = :network
21
+ doc.elements.each("//devices/interface[@type='network']") do |item|
22
+ nics << {
23
+ :type => :network,
24
+ :network => item.elements["source"].attributes["network"].to_s
25
+ }
37
26
  end
38
27
  nics
39
28
  end
40
29
 
41
- def initialize(definition, source_type='libvirt')
42
- @uuid = nil
43
- @gui = nil
44
- @network = 'default'
45
- if source_type == 'ovf'
46
- create_from_ovf(definition)
47
- else
48
- create_from_libvirt(definition)
30
+ def initialize(definition)
31
+ self.attributes = {
32
+ :uuid => nil,
33
+ :gui => nil,
34
+ :vnc_autoport => false,
35
+ :vnc_password => nil,
36
+ :network => 'default',
37
+ :network_model => 'virtio',
38
+ :video_model => 'cirrus'
39
+ }
40
+ doc = REXML::Document.new definition
41
+ memory_unit = doc.elements["/domain/memory"].attributes["unit"]
42
+ update({
43
+ :name => doc.elements["/domain/name"].text,
44
+ :cpus => doc.elements["/domain/vcpu"].text,
45
+ :memory => size_in_bytes(doc.elements["/domain/memory"].text,
46
+ memory_unit), # always :memory is in bytes
47
+ :arch => doc.elements["/domain/os/type"].attributes["arch"],
48
+ :machine_type => doc.elements["/domain/os/type"].attributes["machine"],
49
+ :disk => doc.elements["//devices/disk/source"].attributes["file"],
50
+ :network => doc.elements["//devices/interface/source"].attributes["network"],
51
+ :network_model => :default,
52
+ :mac => format_mac(doc.elements["//devices/interface/mac"].attributes["address"]),
53
+ :image_type => doc.elements["//devices/disk/driver"].attributes["type"],
54
+ :qemu_bin => doc.elements["/domain/devices/emulator"].text,
55
+ :video_model => doc.elements["/domain/devices/video/model"].attributes["type"],
56
+ :disk_bus => doc.elements["//devices/disk/target"].attributes["bus"]
57
+ })
58
+ model_node = doc.elements["//devices/interface/model"]
59
+ if model_node
60
+ update({
61
+ :model_node => model_node,
62
+ :network_model => model_node.attributes["type"]
63
+ })
49
64
  end
50
- end
51
-
52
- def create_from_ovf(definition)
53
- doc = Nokogiri::XML(definition)
54
- # we don't need no namespace
55
- doc.remove_namespaces!
56
- @name = doc.at_css("VirtualSystemIdentifier").content
57
- devices = doc.css("VirtualHardwareSection Item")
58
- for device in devices
59
- case device.at_css("ResourceType").content
60
- # CPU
61
- when "3"
62
- @cpus = device.at_css("VirtualQuantity").content
63
- # Memory
64
- when "4"
65
- @memory = size_in_bytes(device.at_css("VirtualQuantity").content,
66
- device.at_css("AllocationUnits").content)
67
- end
65
+ if doc.elements["/domain/uuid"]
66
+ update({:uuid => doc.elements["/domain/uuid"].text})
67
+ end
68
+ if doc.elements["//devices/graphics"]
69
+ attrs = doc.elements["//devices/graphics"].attributes
70
+ update({
71
+ :gui => attrs["type"] == 'vnc',
72
+ :vnc_port => attrs['port'].to_i,
73
+ :vnc_autoport => attrs['autoport'] == 'yes',
74
+ :vnc_password => attrs['passwd']
75
+ })
68
76
  end
69
-
70
- # disk volume
71
- diskref = doc.at_css("DiskSection Disk")["fileRef"]
72
- @disk = doc.at_css("References File[id='#{diskref}']")["href"]
73
- @image_type = 'raw'
74
- # mac address
75
- # XXX we use only the first nic
76
- @mac = format_mac(doc.at_css("Machine Hardware Adapter[enabled='true']")['MACAddress'])
77
-
78
- # the architecture is not defined in the ovf file
79
- # we try to guess from OSType
80
- # see https://www.virtualbox.org/browser/vbox/trunk/src/VBox/Main/include/ovfreader.h
81
- @arch = doc.at_css("VirtualSystemIdentifier").
82
- content[-2..-1] == '64' ? "x86_64" : "i686"
83
77
  end
84
78
 
85
- def create_from_libvirt(definition)
86
- doc = Nokogiri::XML(definition)
87
- @name = doc.at_css("domain name").content
88
- @uuid = doc.at_css("domain uuid").content if doc.at_css("domain uuid")
89
- memory_unit = doc.at_css("domain memory")["unit"]
90
- @memory = size_in_bytes(doc.at_css("domain memory").content,
91
- memory_unit)
92
- @cpus = doc.at_css("domain vcpu").content
93
- @arch = doc.at_css("domain os type")["arch"]
94
- @disk = doc.at_css("devices disk source")["file"]
95
- @mac = doc.at_css("devices interface mac")["address"]
96
- @network = doc.at_css("devices interface source")["network"]
97
- @image_type = doc.at_css("devices disk driver")["type"]
98
- end
79
+ def as_xml
80
+ if attributes[:qemu_bin]
81
+ # user specified path of qemu binary
82
+ qemu_bin_list = [attributes[:qemu_bin]]
83
+ else
84
+ # RedHat and Debian-based systems have different executable names
85
+ # depending on version/architectures
86
+ qemu_bin_list = ['/usr/bin/qemu-system-x86_64'] if get(:arch).match(/64$/)
87
+ qemu_bin_list = ['/usr/bin/qemu-system-i386'] if get(:arch).match(/^i.86$/)
88
+ qemu_bin_list += [ '/usr/bin/qemu-kvm', '/usr/bin/kvm' ]
89
+ end
99
90
 
100
- def as_libvirt
101
- # RedHat and Debian-based systems have different executable names
102
- # depending on version/architectures
103
- qemu_bin = [ '/usr/bin/qemu-kvm', '/usr/bin/kvm' ]
104
- qemu_bin << '/usr/bin/qemu-system-x86_64' if @arch.match(/64$/)
105
- qemu_bin << '/usr/bin/qemu-system-i386' if @arch.match(/^i.86$/)
91
+ qemu_bin = qemu_bin_list.detect { |binary| File.exists? binary }
92
+ if not qemu_bin
93
+ raise Errors::KvmNoQEMUBinary,
94
+ :cause => attributes[:qemu_bin] ?
95
+ "Vagrantfile (specified binary: #{attributes[:qemu_bin]})" : "QEMU installation"
96
+ end
106
97
 
107
- xml = KvmTemplateRenderer.render("libvirt_domain", {
108
- :name => @name,
109
- :uuid => @uuid,
110
- :memory => size_from_bytes(@memory, "KiB"),
111
- :cpus => @cpus,
112
- :arch => @arch,
113
- :disk => @disk,
114
- :mac => format_mac(@mac),
115
- :network => @network,
116
- :gui => @gui,
117
- :image_type => @image_type,
118
- :qemu_bin => qemu_bin.detect { |binary| File.exists? binary }
119
- })
98
+ xml = KvmTemplateRenderer.render("libvirt_domain",
99
+ attributes.merge!(:memory_size => get_memory("KiB"),
100
+ :memory_unit => "KiB")
101
+ )
120
102
  xml
121
103
  end
122
104
 
123
105
  def get_memory(unit="bytes")
124
- size_from_bytes(@memory, unit)
106
+ size_from_bytes(get(:memory), unit)
125
107
  end
126
108
 
127
- def set_mac(mac)
128
- @mac = format_mac(mac)
129
- end
130
-
131
- def set_gui
132
- @gui = true
109
+ def update(args={})
110
+ args.each {|k,v|
111
+ case k
112
+ when :mac
113
+ args[:mac] = format_mac(args[:mac])
114
+ end
115
+ }
116
+ super(args)
133
117
  end
134
118
 
135
119
  # Takes a quantity and a unit
@@ -193,6 +177,10 @@ module VagrantPlugins
193
177
  end
194
178
  end
195
179
 
180
+ def format_bool(v)
181
+ v ? "yes" : "no"
182
+ end
183
+
196
184
  def format_mac(mac)
197
185
  if mac.length == 12
198
186
  mac = mac[0..1] + ":" + mac[2..3] + ":" +