virtual_box 0.0.1 → 0.1.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 (47) hide show
  1. data/.autotest +9 -0
  2. data/.document +5 -0
  3. data/.project +12 -0
  4. data/.rspec +1 -0
  5. data/Gemfile +14 -0
  6. data/Gemfile.lock +44 -0
  7. data/LICENSE.txt +20 -0
  8. data/README.markdown +67 -0
  9. data/Rakefile +35 -22
  10. data/VERSION +1 -0
  11. data/lib/virtual_box/board.rb +287 -0
  12. data/lib/virtual_box/cli.rb +20 -0
  13. data/lib/virtual_box/dhcp.rb +149 -0
  14. data/lib/virtual_box/disk.rb +138 -0
  15. data/lib/virtual_box/io_bus.rb +261 -0
  16. data/lib/virtual_box/net.rb +144 -0
  17. data/lib/virtual_box/nic.rb +213 -0
  18. data/lib/virtual_box/version.rb +37 -24
  19. data/lib/virtual_box/vm.rb +289 -24
  20. data/lib/virtual_box.rb +14 -9
  21. data/test/helper.rb +24 -0
  22. data/test/tasks/tinycore.rake +378 -0
  23. data/test/virtual_box/board_test.rb +39 -0
  24. data/test/virtual_box/cli_test.rb +33 -0
  25. data/test/virtual_box/dhcp_test.rb +52 -0
  26. data/test/virtual_box/disk_test.rb +116 -0
  27. data/test/virtual_box/integration_test.rb +53 -0
  28. data/test/virtual_box/io_bus_test.rb +54 -0
  29. data/test/virtual_box/net_test.rb +80 -0
  30. data/test/virtual_box/nic_test.rb +50 -0
  31. data/test/virtual_box/version_test.rb +71 -0
  32. data/test/virtual_box/vm_test.rb +55 -0
  33. data/virtual_box.gemspec +81 -22
  34. metadata +208 -89
  35. data/CHANGELOG +0 -1
  36. data/LICENSE +0 -21
  37. data/Manifest +0 -17
  38. data/README.textile +0 -19
  39. data/lib/virtual_box/command_line.rb +0 -27
  40. data/lib/virtual_box/vm/general_settings.rb +0 -136
  41. data/lib/virtual_box/vm/identity.rb +0 -43
  42. data/lib/virtual_box/vm/lifecycle.rb +0 -42
  43. data/test/command_line_test.rb +0 -19
  44. data/test/general_settings_test.rb +0 -20
  45. data/test/lifecycle_test.rb +0 -64
  46. data/test/version_test.rb +0 -56
  47. data/testdata/golden_general_params.txt +0 -1
@@ -0,0 +1,149 @@
1
+ module VirtualBox
2
+
3
+ # Specification for a virtual DHCP server.
4
+ class Dhcp
5
+ # The name of the VirtualBox network served by this DHCP server.
6
+ #
7
+ # This name must match the name of a host-only or internal network that is
8
+ # registered with VirtualBox.
9
+ # @return [String]
10
+ attr_accessor :net_name
11
+
12
+ # This DHCP server's IP address on the virtual network that it serves.
13
+ # @return [String]
14
+ attr_accessor :ip
15
+
16
+ # The network mask reported by this DHCP server.
17
+ # @return [String]
18
+ attr_accessor :netmask
19
+
20
+ # The first IP address in this DHCP server's address pool.
21
+ # @return [String]
22
+ attr_accessor :start_ip
23
+
24
+ # The last IP address in this DHCP server's address pool.
25
+ # @return [String]
26
+ attr_accessor :end_ip
27
+
28
+ undef :ip
29
+ def ip
30
+ @ip ||= self.class.ip_btos((self.class.ip_stob(start_ip) &
31
+ self.class.ip_stob(netmask)) + 1)
32
+ end
33
+
34
+ undef :netmask
35
+ def netmask
36
+ @netmask ||= '255.255.255.0'
37
+ end
38
+
39
+ undef :start_ip
40
+ def start_ip
41
+ @start_ip ||= self.class.ip_btos(self.class.ip_stob(ip) + 1)
42
+ end
43
+
44
+ undef :end_ip
45
+ def end_ip
46
+ nm = self.class.ip_stob(netmask)
47
+ @end_ip ||= self.class.ip_btos((self.class.ip_stob(start_ip) & nm) +
48
+ ~nm - 1)
49
+ end
50
+
51
+ # Creates a DHCP server specification based on the given attributes.
52
+ #
53
+ # The DHCP server is not automatically added to VirtualBox.
54
+ # @param [Hash<Symbol, Object>] options ActiveRecord-style initial values for
55
+ # attributes; can be used together with Net#to_hash to save and restore
56
+ def initialize(options = {})
57
+ options.each { |k, v| self.send :"#{k}=", v }
58
+ end
59
+
60
+ # Hash capturing this specification. Can be passed to Dhcp#new.
61
+ #
62
+ # @return [Hash<Symbol, Object>] Ruby-friendly Hash that can be used to
63
+ # re-create this DHCP server specification
64
+ def to_hash
65
+ { :net_name => 'net_name', :ip => ip, :netmask => netmask,
66
+ :start_ip => start_ip, :end_ip => end_ip }
67
+ end
68
+
69
+ # True if this DHCP rule has been registered with VirtualBox.
70
+ def live?
71
+ servers = self.class.all
72
+ dhcp = servers.find { |server| server.net_name == net_name }
73
+ dhcp ? true : false
74
+ end
75
+
76
+ # Adds this DHCP server to VirtualBox.
77
+ #
78
+ # @return [VirtualBox::Dhcp] self, for easy call chaining
79
+ def add
80
+ remove if live?
81
+
82
+ result = VirtualBox.run_command ['VBoxManage', 'dhcpserver', 'add',
83
+ '--netname', net_name, '--ip', ip, '--netmask', netmask,
84
+ '--lowerip', start_ip, '--upperip', end_ip, '--enable']
85
+ if result.status != 0
86
+ raise 'Unexpected error code returned by VirtualBox'
87
+ end
88
+ self
89
+ end
90
+
91
+ # Removes this DHCP server from VirtualBox.
92
+ #
93
+ # @return [VirtualBox::Dhcp] self, for easy call chaining
94
+ def remove
95
+ VirtualBox.run_command ['VBoxManage', 'dhcpserver', 'remove', '--netname',
96
+ net_name]
97
+ self
98
+ end
99
+
100
+ # The DHCP servers added to with VirtualBox.
101
+ #
102
+ # @return [Array<VirtualBox::Dhcp>] all the DHCP servers added to VirtualBox
103
+ def self.all
104
+ result = VirtualBox.run_command ['VBoxManage', '--nologo', 'list', '--long',
105
+ 'dhcpservers']
106
+ if result.status != 0
107
+ raise 'Unexpected error code returned by VirtualBox'
108
+ end
109
+ result.output.split("\n\n").
110
+ map { |dhcp_info| new.from_dhcp_info(dhcp_info) }
111
+ end
112
+
113
+ # Parses information about a DHCP server returned by VirtualBox.
114
+ #
115
+ # The parsed information is used to replace this network's specification.
116
+ # @param [String] dhcp_info output from "VBoxManage list --long dhcpservers"
117
+ # for one server
118
+ # @return [VirtualBox::Dhcp] self, for easy call chaining
119
+ def from_dhcp_info(dhcp_info)
120
+ info = Hash[dhcp_info.split("\n").map { |line|
121
+ line.split(':', 2).map(&:strip)
122
+ }]
123
+
124
+ self.net_name = info['NetworkName']
125
+ self.ip = info['IP']
126
+ self.netmask = info['NetworkMask']
127
+ self.start_ip = info['lowerIPAddress']
128
+ self.end_ip = info['upperIPAddress']
129
+ self
130
+ end
131
+
132
+ # Converts an IP number into a string.
133
+ #
134
+ # @param [Integer] ip_number a 32-bit big-endian number holding an IP address
135
+ # @return [String] the IP address, encoded using the dot (.) notation
136
+ def self.ip_btos(ip_number)
137
+ [ip_number].pack('N').unpack('C*').join('.')
138
+ end
139
+
140
+ # Converts an IP string into a number.
141
+ #
142
+ # @param [String] ip_string an IP address using the dot (.) notation
143
+ # @return [Integer] the IP adddres, encoded as a 32-bit big-endian number
144
+ def self.ip_stob(ip_string)
145
+ ip_string.split('.').map(&:to_i).pack('C*').unpack('N').first
146
+ end
147
+ end # class VirtualBox::Dhcp
148
+
149
+ end # namespace VirtualBox
@@ -0,0 +1,138 @@
1
+ module VirtualBox
2
+
3
+ # Descriptor for a VirtualBox hard-disk or DVD image.
4
+ class Disk
5
+ # Path to the file storing this disk image.
6
+ #
7
+ # @return [String]
8
+ attr_accessor :file
9
+
10
+ # The format of this disk image.
11
+ #
12
+ # The recognized formats are :raw, :vdi, :vmdk, and :vhd.
13
+ # @return [Symbol]
14
+ attr_accessor :format
15
+
16
+ # The type of media that the image represents.
17
+ #
18
+ # The recognized types are :disk and :dvd.
19
+ # @return [Symbol]
20
+ attr_accessor :media
21
+
22
+ undef :format
23
+ def format
24
+ @format ||= self.class.guess_image_format file
25
+ end
26
+
27
+ undef :media
28
+ def media
29
+ @media ||= self.class.guess_media_type file
30
+ end
31
+
32
+ # Creates an image descriptor with the given attributes.
33
+ #
34
+ # @param [Hash<Symbol, Object>] options ActiveRecord-style initial values for
35
+ # attributes; can be used together with Disk#to_hash to save and restore
36
+ def initialize(options)
37
+ options.each { |k, v| self.send :"#{k}=", v }
38
+ self.file = File.expand_path file
39
+ end
40
+
41
+ # Attaches this disk to a virtual machine.
42
+ #
43
+ # @param [VirtualBox::Vm] vm the VM that this image will be attached to
44
+ # @param [VirtualBox::IoBus] io_bus the IO controller that this disk will be
45
+ # attached to
46
+ # @param [Integer] port the IO bus port this disk will be connected to
47
+ # @param [Integer] device number indicating the device's ordering on its port
48
+ # @return [VirtualBox::Disk] self, for easy call chaining
49
+ def add_to(vm, io_bus, port, device)
50
+ media_arg = case media
51
+ when :disk
52
+ 'hdd'
53
+ when :dvd
54
+ 'dvddrive'
55
+ end
56
+ result = VirtualBox.run_command ['VBoxManage', '--nologo', 'storageattach',
57
+ vm.uid, '--storagectl', io_bus.name, '--port', port.to_s,
58
+ '--device', device.to_s, '--type', media_arg, '--medium', file]
59
+ if result.status != 0
60
+ raise 'Unexpected error code returned by VirtualBox'
61
+ end
62
+ self
63
+ end
64
+
65
+ # Creates a new image descriptor based on the given attributes.
66
+ #
67
+ # @param Hash<Symbol, Object> options ActiveRecord-style initial values for
68
+ # attributes; can be used together with Disk#to_hash to save and restore
69
+ def to_hash
70
+ { :file => file, :format => format, :media => media }
71
+ end
72
+
73
+ # Creates a VirtualBox disk image.
74
+ #
75
+ # @param [Hash] options one or many of the options documented below
76
+ # @option options [String] file path to the file that will hold the disk image
77
+ # @option options [Integer] size the image size, in bytes
78
+ # @option options [Symbol] format the image format; if not provided, an
79
+ # intelligent guess is made, based on the file extension
80
+ # @option options [Boolean] prealloc unless explicitly set to true, the image
81
+ # file will grow in size as the disk's blocks are used
82
+ #
83
+ # @return [VirtualBox::Disk] a Disk describing the image that was created
84
+ def self.create(options)
85
+ path = options[:file]
86
+ format = options[:format] || guess_image_format(path)
87
+ size_mb = (options[:size] / (1024 * 1024)).to_i
88
+ memo = options[:memo] || 'Created with the virtual_box RubyGem'
89
+ variant = options[:prealloc] ? 'Fixed' : 'Standard'
90
+
91
+ result = VirtualBox.run_command ['VBoxManage', '--nologo', 'createhd',
92
+ '--filename', path, '--size', size_mb.to_s, '--format', format.to_s,
93
+ '--variant', variant]
94
+ if result.status != 0
95
+ raise 'Unexpected error code returned by VirtualBox'
96
+ end
97
+
98
+ Disk.new :file => path, :format => format, :media => :disk
99
+ end
100
+
101
+ # Disk image format based on the extension in the file name.
102
+ def self.guess_image_format(image_file)
103
+ parts = File.basename(image_file).split('.')
104
+ if parts.length >= 2
105
+ case parts.last
106
+ when 'vdi'
107
+ :vdi
108
+ when 'vmdk'
109
+ :vmdk
110
+ when 'vhd'
111
+ :vhd
112
+ when 'iso'
113
+ :raw
114
+ else
115
+ :vdi
116
+ end
117
+ else
118
+ :vdi
119
+ end
120
+ end
121
+
122
+ # Disk media type on the extension in the file name.
123
+ def self.guess_media_type(image_file)
124
+ parts = File.basename(image_file).split('.')
125
+ if parts.length >= 2
126
+ case parts.last
127
+ when 'iso'
128
+ :dvd
129
+ else
130
+ :disk
131
+ end
132
+ else
133
+ :disk
134
+ end
135
+ end
136
+ end # class VirtualBox::Disk
137
+
138
+ end # namespace VirtualBox
@@ -0,0 +1,261 @@
1
+ module VirtualBox
2
+
3
+ # Specification for a IO controller attached to a virtual machine.
4
+ class IoBus
5
+ # A user-friendly name for the I/O controller.
6
+ #
7
+ # I/O controller names must be unique within the scope of a virtual machine.
8
+ # VirtualBox uses "IDE Controller" and "SATA Controller" as default names.
9
+ # @return [String]
10
+ attr_accessor :name
11
+
12
+ # The kind of bus used by this controller.
13
+ #
14
+ # The following bus types are recognized: :ide, :sata, :sas, :scsi, :floppy.
15
+ # @return [Symbol]
16
+ attr_accessor :bus
17
+
18
+ # The chipset simulated by the IO controller
19
+ #
20
+ # The following chipsets are recogniezd:
21
+ # IDE :: :piix3, :piix4, and :ich6
22
+ # SATA :: :ahci (Intel AHCI)
23
+ # SCSI :: :lsi_logic and :bus_logic
24
+ # SAS :: lsi_logic_sas
25
+ # Floppy :: :i82078
26
+ # @return [Symbol]
27
+ attr_accessor :chip
28
+
29
+ # True if the VM BIOS considers this controller bootable.
30
+ # @return [Boolean]
31
+ attr_accessor :bootable
32
+
33
+ # True if the controller's I/O bypasses the host OS cache.
34
+ # @return [Boolean]
35
+ attr_accessor :no_cache
36
+
37
+ # The maximum number of I/O devices supported by this controller.
38
+ # @return [Integer]
39
+ attr_accessor :max_ports
40
+
41
+ # The disks connected to this controller's bus.
42
+ # @return [Hash<Array<Integer>, VirtualBox::Disk>]
43
+ attr_accessor :disks
44
+
45
+ undef :name
46
+ def name
47
+ @name ||= case bus
48
+ when :ide, :sata, :scsi, :sas
49
+ "#{bus.upcase} Controller"
50
+ when :floppy
51
+ 'Floppy Controller'
52
+ end
53
+ end
54
+
55
+ undef :no_cache
56
+ def no_cache
57
+ @no_cache.nil? ? false : @bootable
58
+ end
59
+
60
+ undef :bootable
61
+ def bootable
62
+ @bootable.nil? ? true : @bootable
63
+ end
64
+
65
+ undef :chip
66
+ def chip
67
+ @chip ||= case @bus
68
+ when :ide
69
+ :piix4
70
+ when :sata
71
+ :ahci
72
+ when :scsi
73
+ :lsi_logic
74
+ when :sas
75
+ :lsi_logic_sas
76
+ when :floppy
77
+ :i82078
78
+ end
79
+ end
80
+
81
+ undef :bus
82
+ def bus
83
+ @bus ||= case @chip
84
+ when :piix3, :piix4, :ich6
85
+ :ide
86
+ when :ahci
87
+ :sata
88
+ when :lsi_logic, :bus_logic
89
+ :scsi
90
+ when :lsi_logic_sas
91
+ :sas
92
+ when :i82078
93
+ :floppy
94
+ end
95
+ end
96
+
97
+ undef :max_ports
98
+ def max_ports
99
+ @max_ports ||= case bus
100
+ when :sata
101
+ 30
102
+ when :sas, :scsi
103
+ 16
104
+ when :ide
105
+ 2
106
+ when :floppy
107
+ 1
108
+ end
109
+ end
110
+
111
+ undef :disks=
112
+ def disks=(new_disks)
113
+ @disks = {}
114
+ new_disks.each do |disk|
115
+ if disk.kind_of? VirtualBox::Disk
116
+ @disks[[first_free_port, 0]] = disk
117
+ else
118
+ options = disk.dup
119
+ port = options.delete(:port) || first_free_port
120
+ device = options.delete(:device) || 0
121
+ @disks[[port, device]] = VirtualBox::Disk.new options
122
+ end
123
+ end
124
+ new_disks
125
+ end
126
+
127
+ # Parses "VBoxManage showvminfo --machinereadable" output into this instance.
128
+ #
129
+ # @return [VirtualBox::IoBus] self, for easy call chaining
130
+ def from_params(params, bus_id)
131
+ self.name = params["storagecontrollername#{bus_id}"]
132
+ self.bootable = params["storagecontrollerbootable#{bus_id}"] == 'on'
133
+ self.max_ports = params["storagecontrollermaxportcount#{bus_id}"].to_i
134
+ case params["storagecontrollertype#{bus_id}"]
135
+ when 'PIIX3'
136
+ self.chip = :piix3
137
+ when 'PIIX4'
138
+ self.chip = :piix4
139
+ when 'ICH6'
140
+ self.chip = :ich6
141
+ when 'IntelAhci'
142
+ self.chip = :ahci
143
+ when 'LsiLogic'
144
+ self.chip = :lsi_logic
145
+ when 'BusLogic'
146
+ self.chip = :bus_logic
147
+ when 'LSILogicSAS'
148
+ self.chip = :lsi_logic_sas
149
+ when 'I82078'
150
+ self.chip = :i82078
151
+ end
152
+ self.no_cache = nil
153
+
154
+ image_re = /\A#{name}-(\d+)-(\d+)\Z/
155
+ @disks = {}
156
+ params.each do |key, value|
157
+ next unless match = image_re.match(key)
158
+ next if value == 'none'
159
+ port, device = match[1].to_i, match[2].to_i
160
+ @disks[[port, device]] = VirtualBox::Disk.new :file => value
161
+ end
162
+ self
163
+ end
164
+
165
+ # Parameters for "VBoxManage storagectl" to add this IO bus to a VM.
166
+ #
167
+ # @return [Array<String>] the parameters for a "VBoxManage storagectl" command
168
+ # that get this IO bus added to a VM.
169
+ def to_params
170
+ params = []
171
+ params.push '--name', name
172
+ params.push '--add', bus.to_s
173
+ params.push '--controller', case chip
174
+ when :piix3, :piix4, :ich6, :i82078
175
+ chip.to_s.upcase
176
+ when :ahci
177
+ 'IntelAhci'
178
+ when :lsi_logic
179
+ 'LsiLogic'
180
+ when :bus_logic
181
+ 'BusLogic'
182
+ when :lsi_logic_sas
183
+ 'LSILogicSAS'
184
+ end
185
+ params.push '--sataportcount', max_ports.to_s
186
+ params.push '--hostiocache', (no_cache ? 'off' : 'on')
187
+ params.push '--bootable', (bootable ? 'on' : 'off')
188
+ end
189
+
190
+
191
+ # Adds this IO bus and all its disks to a virtual machine.
192
+ #
193
+ # @param [VirtualBox::Vm] vm the virtual machine that this IO controller will
194
+ # be added to
195
+ # @return [VirtualBox::IoBus] self, for easy call chaining
196
+ def add_to(vm)
197
+ add_bus_to vm
198
+ disks.each do |port_device, disk|
199
+ disk.add_to vm, self, port_device.first, port_device.last
200
+ end
201
+ self
202
+ end
203
+
204
+ # Removes this IO bus from a virtual machine.
205
+ #
206
+ # @param [VirtualBox::Vm] vm the virtual machine that this IO controller will
207
+ # be removed from
208
+ # @return [VirtualBox::IoBus] self, for easy call chaining
209
+ def remove_from(vm)
210
+ result = VirtualBox.run_command ['VBoxManage', '--nologo', 'storagectl',
211
+ vm.uuid, '--name', name, '--remove']
212
+ if result.status != 0
213
+ raise 'Unexpected error code returned by VirtualBox'
214
+ end
215
+ self
216
+ end
217
+
218
+ # Adds this IO bus to a virtual machine.
219
+ #
220
+ # @param [VirtualBox::Vm] vm the virtual machine that this IO bus will be
221
+ # added to
222
+ # @return [VirtualBox::IoBus] self, for easy call chaining
223
+ def add_bus_to(vm)
224
+ command = ['VBoxManage', '--nologo', 'storagectl', vm.uid].concat to_params
225
+ result = VirtualBox.run_command command
226
+ if result.status != 0
227
+ raise 'Unexpected error code returned by VirtualBox'
228
+ end
229
+ self
230
+ end
231
+
232
+ # Creates a new IO controller specification based on the given attributes.
233
+ #
234
+ # @param [Hash<Symbol, Object>] options ActiveRecord-style initial values for
235
+ # attributes; can be used together with IoBus#to_hash to save and restore
236
+ def initialize(options = {})
237
+ @disks = {}
238
+ options.each { |k, v| self.send :"#{k}=", v }
239
+ end
240
+
241
+ # Hash capturing this specification. Can be passed to IoBus#new.
242
+ #
243
+ # @return [Hash<Symbol, Object>] Ruby-friendly Hash that can be used to
244
+ # re-create this IO controller specification
245
+ def to_hash
246
+ disk_hashes = disks.map do |port_device, disk|
247
+ disk.to_hash.merge! :port => port_device.first,
248
+ :device => port_device.last
249
+ end
250
+ { :name => name, :bus => bus, :chip => chip, :bootable => bootable,
251
+ :no_cache => no_cache, :max_ports => max_ports, :disks => disk_hashes }
252
+ end
253
+
254
+ # Finds an unused port on this IO controller's bus.
255
+ # @return [Integer] a port number that a new disk can be attached to
256
+ def first_free_port
257
+ disks.empty? ? 0 : disks.keys.min.first + 1
258
+ end
259
+ end # class VirtualBox::IoBus
260
+
261
+ end # namespace VirtualBox
@@ -0,0 +1,144 @@
1
+ module VirtualBox
2
+
3
+ # Descriptor for a virtual network managed by Virtual Box.
4
+ class Net
5
+ # The IP address received by the host computer on this virtual network.
6
+ # @return [String]
7
+ attr_accessor :ip
8
+
9
+ # The network mask received by the host computer on this virtual network.
10
+ # @return [String]
11
+ attr_accessor :netmask
12
+
13
+ # The name of the host's virtual NIC that's connected to this network.
14
+ #
15
+ # VirtualBox's CLI does not provide a way to set the NIC name. Therefore, this
16
+ # attribute is read-only, and it is automatically set when the network is
17
+ # registered with VirtualBox.
18
+ # @return [String]
19
+ attr_reader :if_name
20
+
21
+ # The name of the VirtualBox internal network.
22
+ #
23
+ # VirtualBox's CLI does not provide a way to set the internal network name.
24
+ # Therefore, this attribute is read-only, and it is automatically set when the
25
+ # network is registered with VirtualBox.
26
+ # @return [String]
27
+ attr_reader :name
28
+
29
+ # The MAC address of the host's virtual NIC that's connected to this network.
30
+ #
31
+ # VirtualBox's CLI does not provide a way to set the MAC. Therefore, this
32
+ # attribute is read-only, and it is automatically set when the network is
33
+ # registered with VirtualBox.
34
+ # @return [String]
35
+ attr_reader :mac
36
+
37
+ # Creates a virtual network specification rule based on the given attributes.
38
+ #
39
+ # The network is not automatically added to VirtualBox.
40
+ # @param [Hash<Symbol, Object>] options ActiveRecord-style initial values for
41
+ # attributes; can be used together with Net#to_hash to save and restore
42
+ def initialize(options = {})
43
+ options.each { |k, v| self.send :"#{k}=", v }
44
+ end
45
+
46
+ # Hash capturing this specification. Can be passed to Net#new.
47
+ #
48
+ # @return [Hash<Symbol, Object>] Ruby-friendly Hash that can be used to
49
+ # re-create this virtual network specification
50
+ def to_hash
51
+ { :ip => ip, :netmask => netmask }
52
+ end
53
+
54
+ # True if this virtual network has been added to VirtualBox.
55
+ # @return [Boolean] true if this network exists, false otherwise
56
+ def live?
57
+ networks = self.class.all
58
+ network = networks.find { |net| net.if_name == if_name }
59
+ network ? true : false
60
+ end
61
+
62
+ # Adds this virtual network specification to VirtualBox.
63
+ #
64
+ # @return [VirtualBox::Net] self, for easy call chaining
65
+ def add
66
+ unless if_name.nil?
67
+ raise "Virtual network already added to VirtualBox"
68
+ end
69
+
70
+ # Create the network and pull its name.
71
+ result = VirtualBox.run_command ['VBoxManage', '--nologo', 'hostonlyif',
72
+ 'create']
73
+ if result.status != 0
74
+ raise 'Unexpected error code returned by VirtualBox'
75
+ end
76
+ unless match = /^interface\s+'(.*)'\s+.*created/i.match(result.output)
77
+ raise "VirtualBox output does not include interface name"
78
+ end
79
+ @if_name = match[1]
80
+
81
+ # Now list all the networks to pull the rest of the information.
82
+ networks = self.class.all
83
+ network = networks.find { |net| net.if_name == if_name }
84
+ @name = network.name
85
+ @mac = network.mac
86
+
87
+ if (ip && ip != network.ip) || (netmask && netmask != network.netmask)
88
+ result = VirtualBox.run_command ['VBoxManage', '--nologo', 'hostonlyif',
89
+ 'ipconfig', if_name, '--ip', ip, '--netmask', netmask]
90
+ if result.status != 0
91
+ raise 'Unexpected error code returned by VirtualBox'
92
+ end
93
+ else
94
+ self.ip = network.ip
95
+ self.netmask = network.netmask
96
+ end
97
+
98
+ self
99
+ end
100
+
101
+ # Removes this virtual network from VirtualBox's database.
102
+ #
103
+ # @return [VirtualBox::Net] self, for easy call chaining
104
+ def remove
105
+ unless if_name.nil?
106
+ VirtualBox.run_command ['VBoxManage', 'hostonlyif', 'remove', if_name]
107
+ end
108
+ self
109
+ end
110
+
111
+ # The DHCP servers added to with VirtualBox.
112
+ #
113
+ # @return [Array<VirtualBox::Dhcp>] all the DHCP servers added to VirtualBox
114
+ def self.all
115
+ result = VirtualBox.run_command ['VBoxManage', '--nologo', 'list', '--long',
116
+ 'hostonlyifs']
117
+ if result.status != 0
118
+ raise 'Unexpected error code returned by VirtualBox'
119
+ end
120
+ result.output.split("\n\n").
121
+ map { |net_info| new.from_net_info(net_info) }
122
+ end
123
+
124
+ # Parses information about a DHCP server returned by VirtualBox.
125
+ #
126
+ # The parsed information is used to replace this network's specification.
127
+ # @param [String] net_info output from "VBoxManage list --long hostonlyifs"
128
+ # for one network
129
+ # @return [VirtualBox::Net] self, for easy call chaining
130
+ def from_net_info(net_info)
131
+ info = Hash[net_info.split("\n").map { |line|
132
+ line.split(':', 2).map(&:strip)
133
+ }]
134
+
135
+ @if_name = info['Name']
136
+ @name = info['VBoxNetworkName']
137
+ @mac = info['HardwareAddress'].upcase.gsub(/[^0-9A-F]/, '')
138
+ self.ip = info['IPAddress']
139
+ self.netmask = info['NetworkMask']
140
+ self
141
+ end
142
+ end # class VirtualBox::Dhcp
143
+
144
+ end # namespace VirtualBox