virtualbox 0.7.1 → 0.7.2
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.
- data/VERSION +1 -1
- data/lib/virtualbox/abstract_model.rb +10 -1
- data/lib/virtualbox/abstract_model/attributable.rb +21 -2
- data/lib/virtualbox/abstract_model/interface_attributes.rb +3 -1
- data/lib/virtualbox/abstract_model/relatable.rb +11 -1
- data/lib/virtualbox/abstract_model/version_matcher.rb +33 -0
- data/lib/virtualbox/com/ffi/interfaces.rb +9 -1
- data/lib/virtualbox/exceptions.rb +2 -1
- data/lib/virtualbox/forwarded_port.rb +1 -1
- data/lib/virtualbox/nat_engine.rb +71 -0
- data/lib/virtualbox/nat_forwarded_port.rb +171 -0
- data/lib/virtualbox/network_adapter.rb +10 -1
- data/lib/virtualbox/shared_folder.rb +1 -1
- data/lib/virtualbox/vm.rb +7 -2
- data/test/test_helper.rb +1 -1
- data/test/virtualbox/abstract_model/attributable_test.rb +24 -3
- data/test/virtualbox/abstract_model/interface_attributes_test.rb +26 -1
- data/test/virtualbox/abstract_model/relatable_test.rb +64 -5
- data/test/virtualbox/abstract_model/version_matcher_test.rb +37 -0
- data/test/virtualbox/abstract_model_test.rb +29 -11
- data/test/virtualbox/forwarded_port_test.rb +20 -3
- data/test/virtualbox/nat_engine_test.rb +106 -0
- data/test/virtualbox/nat_forwarded_port_test.rb +216 -0
- data/test/virtualbox/network_adapter_test.rb +2 -0
- data/test/virtualbox/shared_folder_test.rb +1 -0
- data/test/virtualbox/vm_test.rb +16 -2
- data/virtualbox.gemspec +12 -3
- metadata +12 -3
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.7.
|
1
|
+
0.7.2
|
@@ -111,7 +111,6 @@ module VirtualBox
|
|
111
111
|
# Go through and only save the loaded relationships, since
|
112
112
|
# only those would be modified.
|
113
113
|
self.class.relationships.each do |name, options|
|
114
|
-
next if lazy_relationship?(name) && !loaded_relationship?(name)
|
115
114
|
save_relationship(name, *args)
|
116
115
|
end
|
117
116
|
|
@@ -226,6 +225,16 @@ module VirtualBox
|
|
226
225
|
end
|
227
226
|
end
|
228
227
|
|
228
|
+
# Gets the root machine of an AbstractModel by traversing a
|
229
|
+
# `parent` attribute until it reaches a type of {VM}.
|
230
|
+
#
|
231
|
+
# @return [VM]
|
232
|
+
def parent_machine
|
233
|
+
current = parent
|
234
|
+
current = current.parent while current && !current.is_a?(VM)
|
235
|
+
current
|
236
|
+
end
|
237
|
+
|
229
238
|
# Creates a human-readable format for this model. This method overrides the
|
230
239
|
# default `#<class>` syntax since this doesn't work well for AbstractModels.
|
231
240
|
# Instead, it abbreviates it, instead showing all the attributes and their
|
@@ -1,3 +1,5 @@
|
|
1
|
+
require 'virtualbox/abstract_model/version_matcher'
|
2
|
+
|
1
3
|
module VirtualBox
|
2
4
|
class AbstractModel
|
3
5
|
# Module which can be included into any other class which allows
|
@@ -37,6 +39,18 @@ module VirtualBox
|
|
37
39
|
# The example above applies a default value to format. So if no value
|
38
40
|
# is ever given to it, `format` would return `VDI`.
|
39
41
|
#
|
42
|
+
# ## Attributes for a Specific VirtualBox Version
|
43
|
+
#
|
44
|
+
# Attributes change with different VirtualBox versions. One way to
|
45
|
+
# provide version-specific behavior is to specify the version
|
46
|
+
# which the attribute applies to.
|
47
|
+
#
|
48
|
+
# attribute :name, :version => "3.2"
|
49
|
+
# attribute :age, :version => "3.1"
|
50
|
+
#
|
51
|
+
# These versions only apply to major and minor releases of
|
52
|
+
# VirtualBox. Patch releases can't be specified (such as "3.2.4")
|
53
|
+
#
|
40
54
|
# ## Populating Multiple Attributes
|
41
55
|
#
|
42
56
|
# Attributes can be mass populated using {#populate_attributes}. Below
|
@@ -118,6 +132,8 @@ module VirtualBox
|
|
118
132
|
# puts model.expensive_attribute # => 42
|
119
133
|
#
|
120
134
|
module Attributable
|
135
|
+
include VersionMatcher
|
136
|
+
|
121
137
|
def self.included(base)
|
122
138
|
base.extend ClassMethods
|
123
139
|
end
|
@@ -224,13 +240,16 @@ module VirtualBox
|
|
224
240
|
# if specified.
|
225
241
|
def read_attribute(name)
|
226
242
|
if has_attribute?(name)
|
243
|
+
options = self.class.attributes[name]
|
244
|
+
|
245
|
+
assert_version_match(options[:version], VirtualBox.version) if options[:version]
|
227
246
|
if lazy_attribute?(name) && !loaded_attribute?(name)
|
228
247
|
# Load the lazy attribute
|
229
248
|
load_attribute(name.to_sym)
|
230
249
|
end
|
231
250
|
|
232
251
|
if attributes[name].nil?
|
233
|
-
|
252
|
+
options[:default]
|
234
253
|
else
|
235
254
|
attributes[name]
|
236
255
|
end
|
@@ -268,4 +287,4 @@ module VirtualBox
|
|
268
287
|
end
|
269
288
|
end
|
270
289
|
end
|
271
|
-
end
|
290
|
+
end
|
@@ -23,6 +23,7 @@ module VirtualBox
|
|
23
23
|
return unless has_attribute?(key)
|
24
24
|
options = self.class.attributes[key.to_sym]
|
25
25
|
return if options.has_key?(:property) && !options[:property]
|
26
|
+
return if options.has_key?(:version) && !version_match?(options[:version], VirtualBox.version)
|
26
27
|
getter = options[:property] || options[:property_getter] || key.to_sym
|
27
28
|
return unless getter
|
28
29
|
|
@@ -49,6 +50,7 @@ module VirtualBox
|
|
49
50
|
options = self.class.attributes[key.to_sym]
|
50
51
|
return if options[:readonly]
|
51
52
|
return if options.has_key?(:property) && !options[:property]
|
53
|
+
return if options.has_key?(:version) && !version_match?(options[:version], VirtualBox.version)
|
52
54
|
|
53
55
|
setter = options[:property] || options[:property_setter] || "#{key}=".to_sym
|
54
56
|
return unless setter
|
@@ -93,4 +95,4 @@ module VirtualBox
|
|
93
95
|
end
|
94
96
|
end
|
95
97
|
end
|
96
|
-
end
|
98
|
+
end
|
@@ -1,3 +1,5 @@
|
|
1
|
+
require 'virtualbox/abstract_model/version_matcher'
|
2
|
+
|
1
3
|
module VirtualBox
|
2
4
|
class AbstractModel
|
3
5
|
# Provides simple relationship features to any class. These relationships
|
@@ -119,6 +121,8 @@ module VirtualBox
|
|
119
121
|
# relationship, it will first load the relationship, since destroy typically
|
120
122
|
# depends on some data of the relationship.
|
121
123
|
module Relatable
|
124
|
+
include VersionMatcher
|
125
|
+
|
122
126
|
def self.included(base)
|
123
127
|
base.extend ClassMethods
|
124
128
|
end
|
@@ -181,6 +185,9 @@ module VirtualBox
|
|
181
185
|
# Reads a relationship. This is equivalent to {Attributable#read_attribute},
|
182
186
|
# but for relationships.
|
183
187
|
def read_relationship(name)
|
188
|
+
options = self.class.relationships_hash[name.to_sym]
|
189
|
+
assert_version_match(options[:version], VirtualBox.version) if options[:version]
|
190
|
+
|
184
191
|
if lazy_relationship?(name) && !loaded_relationship?(name)
|
185
192
|
load_relationship(name)
|
186
193
|
end
|
@@ -209,6 +216,8 @@ module VirtualBox
|
|
209
216
|
# calls `save_relationship` on the relationship class.
|
210
217
|
def save_relationship(name, *args)
|
211
218
|
options = self.class.relationships_hash[name]
|
219
|
+
return if lazy_relationship?(name) && !loaded_relationship?(name)
|
220
|
+
return if options[:version] && !version_match?(options[:version], VirtualBox.version)
|
212
221
|
return unless relationship_class(name).respond_to?(:save_relationship)
|
213
222
|
relationship_class(name).save_relationship(self, relationship_data[name], *args)
|
214
223
|
end
|
@@ -225,6 +234,7 @@ module VirtualBox
|
|
225
234
|
def populate_relationship(name, data)
|
226
235
|
options = self.class.relationships_hash[name]
|
227
236
|
return unless relationship_class(name).respond_to?(:populate_relationship)
|
237
|
+
return if options[:version] && !version_match?(options[:version], VirtualBox.version)
|
228
238
|
relationship_data[name] = relationship_class(name).populate_relationship(self, data)
|
229
239
|
end
|
230
240
|
|
@@ -315,4 +325,4 @@ module VirtualBox
|
|
315
325
|
end
|
316
326
|
end
|
317
327
|
end
|
318
|
-
end
|
328
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
module VirtualBox
|
2
|
+
class AbstractModel
|
3
|
+
module VersionMatcher
|
4
|
+
# Asserts that two versions match. Otherwise raises an
|
5
|
+
# exception.
|
6
|
+
def assert_version_match(req, cur)
|
7
|
+
if !version_match?(req, cur)
|
8
|
+
message = "Required version: #{req}; Current: #{cur}"
|
9
|
+
raise Exceptions::UnsupportedVersionException.new(message)
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
# Checks if a given version requirement matches the current
|
14
|
+
# version.
|
15
|
+
#
|
16
|
+
# @return [Boolean]
|
17
|
+
def version_match?(requirement, current)
|
18
|
+
split_version(requirement) == split_version(current)
|
19
|
+
end
|
20
|
+
|
21
|
+
# Splits a version string into a two-item array with the parts
|
22
|
+
# of the version, respectively. If the version has more than two
|
23
|
+
# parts, the rest are ignored.
|
24
|
+
#
|
25
|
+
# @param [String] version
|
26
|
+
# @return [Array]
|
27
|
+
def split_version(version)
|
28
|
+
version.split(/\./)[0,2]
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
@@ -5,6 +5,9 @@ module VirtualBox
|
|
5
5
|
# file should be conditionally loaded based on OS, so that Windows users
|
6
6
|
# don't have to wait for all this translation to occur.
|
7
7
|
def self.setup(version)
|
8
|
+
# TODO: This is so hacky and hard to maintain. Can we
|
9
|
+
# programatically get the modules in a namespace and
|
10
|
+
# instantiate them somehow?
|
8
11
|
for_version version do
|
9
12
|
create_interface(:NSISupports)
|
10
13
|
create_interface(:NSIException, :NSISupports)
|
@@ -39,8 +42,13 @@ module VirtualBox
|
|
39
42
|
|
40
43
|
create_interface(:HostUSBDevice, :USBDevice)
|
41
44
|
create_interface(:HostUSBDeviceFilter, :USBDeviceFilter)
|
45
|
+
|
46
|
+
# 3.2.x interfaces
|
47
|
+
if version == "3.2.x"
|
48
|
+
create_interface(:NATEngine, :NSISupports)
|
49
|
+
end
|
42
50
|
end
|
43
51
|
end
|
44
52
|
end
|
45
53
|
end
|
46
|
-
end
|
54
|
+
end
|
@@ -10,6 +10,7 @@ module VirtualBox
|
|
10
10
|
class MediumCreationFailedException < Exception; end
|
11
11
|
class MediumNotUpdatableException < Exception; end
|
12
12
|
class ReadonlyVMStateException < Exception; end
|
13
|
+
class UnsupportedVersionException < Exception; end
|
13
14
|
|
14
15
|
class FFIException < Exception
|
15
16
|
attr_accessor :data
|
@@ -35,4 +36,4 @@ module VirtualBox
|
|
35
36
|
class InvalidSessionStateException < FFIException; end
|
36
37
|
class ObjectInUseException < FFIException; end
|
37
38
|
end
|
38
|
-
end
|
39
|
+
end
|
@@ -0,0 +1,71 @@
|
|
1
|
+
module VirtualBox
|
2
|
+
# Represents a NAT engine for a given {NetworkAdapter}. This data is
|
3
|
+
# available through the `nat_driver` relationship on
|
4
|
+
# {NetworkAdapter} only if the adapter is a NAT adapter.
|
5
|
+
class NATEngine < AbstractModel
|
6
|
+
attribute :parent, :readonly => true, :property => false
|
7
|
+
attribute :network
|
8
|
+
attribute :tftp_prefix
|
9
|
+
attribute :tftp_boot_file
|
10
|
+
attribute :tftp_next_server
|
11
|
+
attribute :alias_mode
|
12
|
+
attribute :dns_pass_domain
|
13
|
+
attribute :dns_proxy
|
14
|
+
attribute :dns_use_host_resolver
|
15
|
+
attribute :interface, :readonly => true, :property => false
|
16
|
+
relationship :forwarded_ports, :NATForwardedPort
|
17
|
+
|
18
|
+
class << self
|
19
|
+
# Populates the NAT engine for anything which is related to it.
|
20
|
+
#
|
21
|
+
# **This method typically won't be used except internally.**
|
22
|
+
#
|
23
|
+
# @return [NATEngine]
|
24
|
+
def populate_relationship(caller, inat)
|
25
|
+
return nil if inat.nil?
|
26
|
+
new(caller, inat)
|
27
|
+
end
|
28
|
+
|
29
|
+
# Saves the relationship. This simply calls {#save} on every
|
30
|
+
# member of the relationship.
|
31
|
+
#
|
32
|
+
# **This method typically won't be used except internally.**
|
33
|
+
def save_relationship(caller, item)
|
34
|
+
item.save
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
def initialize(caller, inat)
|
39
|
+
super()
|
40
|
+
initialize_attributes(caller, inat)
|
41
|
+
end
|
42
|
+
|
43
|
+
# Initializes the attributes of an existing NAT engine.
|
44
|
+
def initialize_attributes(parent, inat)
|
45
|
+
write_attribute(:parent, parent)
|
46
|
+
write_attribute(:interface, inat)
|
47
|
+
|
48
|
+
# Load the interface attributes associated with this model
|
49
|
+
load_interface_attributes(inat)
|
50
|
+
populate_relationships(inat)
|
51
|
+
|
52
|
+
# Clear dirty and set as existing
|
53
|
+
clear_dirty!
|
54
|
+
existing_record!
|
55
|
+
end
|
56
|
+
|
57
|
+
def save
|
58
|
+
modify_engine do |nat|
|
59
|
+
save_changed_interface_attributes(nat)
|
60
|
+
save_relationships
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
# Helper method to get the mutable `INATEngine` interface.
|
65
|
+
def modify_engine
|
66
|
+
parent.modify_adapter do |adapter|
|
67
|
+
yield adapter.nat_driver
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
@@ -0,0 +1,171 @@
|
|
1
|
+
module VirtualBox
|
2
|
+
# When a VM uses NAT as its NIC type, VirtualBox acts like its
|
3
|
+
# own private router for all virtual machines. Because of this,
|
4
|
+
# the host machine can't access services within the guest machine.
|
5
|
+
# To get around this, NAT supports port forwarding, which allows the
|
6
|
+
# guest machine services to be forwarded to some port on the host
|
7
|
+
# machine. Port forwarding is done completely through {ExtraData}, but
|
8
|
+
# is a complicated enough procedure that this class was made to
|
9
|
+
# faciliate it.
|
10
|
+
#
|
11
|
+
# **Note:** After changing any forwarded ports, the entire VirtualBox
|
12
|
+
# process must be restarted completely for them to take effect. When
|
13
|
+
# working with the ruby library, this isn't so much of an issue, but
|
14
|
+
# if you have any VMs running, they must all be shut down and restarted.
|
15
|
+
#
|
16
|
+
# # Adding a new Forwarded Port
|
17
|
+
#
|
18
|
+
# Since forwarded ports rely on being part of a {VM}, we're going to
|
19
|
+
# assume that `vm` points to a {VM} which has already been found.
|
20
|
+
#
|
21
|
+
# port = VirtualBox::NATForwardedPort.new
|
22
|
+
# port.name = "apache" # This can be anything
|
23
|
+
# port.guestport = 80
|
24
|
+
# port.hostport = 8080
|
25
|
+
# vm.network_adapters[0].nat_driver.forwarded_ports << port
|
26
|
+
# port.save # Or vm.save
|
27
|
+
#
|
28
|
+
# # Modifying an Existing Forwarded Port
|
29
|
+
#
|
30
|
+
# This is assuming that `vm` is a local variable storing a {VM} object
|
31
|
+
# which has already been found.
|
32
|
+
#
|
33
|
+
# ports = vm.network_adapters[0].nat_driver.forwarded_ports
|
34
|
+
# ports.first.hostport = 1919
|
35
|
+
# vm.save
|
36
|
+
#
|
37
|
+
# # Deleting a Forwarded Port
|
38
|
+
#
|
39
|
+
# To delete a forwarded port, you simply destroy it like any other model:
|
40
|
+
#
|
41
|
+
# ports = vm.network_adapters[0].nat_driver.forwarded_ports
|
42
|
+
# ports.first.destroy
|
43
|
+
#
|
44
|
+
# # Attributes and Relationships
|
45
|
+
#
|
46
|
+
# Properties of the model are exposed using standard ruby instance
|
47
|
+
# methods which are generated on the fly. Because of this, they are not listed
|
48
|
+
# below as available instance methods.
|
49
|
+
#
|
50
|
+
# These attributes can be accessed and modified via standard ruby-style
|
51
|
+
# `instance.attribute` and `instance.attribute=` methods. The attributes are
|
52
|
+
# listed below.
|
53
|
+
#
|
54
|
+
# Relationships are also accessed like attributes but can't be set. Instead,
|
55
|
+
# they are typically references to other objects such as an {AttachedDevice} which
|
56
|
+
# in turn have their own attributes which can be modified.
|
57
|
+
#
|
58
|
+
# ## Attributes
|
59
|
+
#
|
60
|
+
# This is copied directly from the class header, but lists all available
|
61
|
+
# attributes. If you don't understand what this means, read {Attributable}.
|
62
|
+
#
|
63
|
+
# attribute :parent, :readonly => true
|
64
|
+
# attribute :name
|
65
|
+
# attribute :protocol, :default => "TCP"
|
66
|
+
# attribute :guestport
|
67
|
+
# attribute :hostport
|
68
|
+
#
|
69
|
+
class NATForwardedPort < AbstractModel
|
70
|
+
attribute :parent, :readonly => true, :property => false
|
71
|
+
attribute :parent_collection, :readonly => true, :property => false
|
72
|
+
attribute :name
|
73
|
+
attribute :protocol, :default => :tcp
|
74
|
+
attribute :guestport
|
75
|
+
attribute :hostport
|
76
|
+
|
77
|
+
class << self
|
78
|
+
# Populates a relationship with another model.
|
79
|
+
#
|
80
|
+
# **This method typically won't be used except internally.**
|
81
|
+
#
|
82
|
+
# @return [Array<NATForwardedPort>]
|
83
|
+
def populate_relationship(caller, interface)
|
84
|
+
relation = Proxies::Collection.new(caller)
|
85
|
+
|
86
|
+
interface.redirects.each do |key, value|
|
87
|
+
parts = key.split(",")
|
88
|
+
|
89
|
+
port = new({
|
90
|
+
:parent => caller,
|
91
|
+
:parent_collection => relation,
|
92
|
+
:name => parts[0],
|
93
|
+
:protocol => COM::Util.versioned_interface(:NATProtocol).index(parts[1]),
|
94
|
+
:guestport => parts[5],
|
95
|
+
:hostport => parts[3]
|
96
|
+
})
|
97
|
+
|
98
|
+
port.existing_record!
|
99
|
+
|
100
|
+
relation.push(port)
|
101
|
+
end
|
102
|
+
|
103
|
+
relation
|
104
|
+
end
|
105
|
+
|
106
|
+
# Saves the relationship. This simply calls {#save} on every
|
107
|
+
# member of the relationship.
|
108
|
+
#
|
109
|
+
# **This method typically won't be used except internally.**
|
110
|
+
def save_relationship(caller, data)
|
111
|
+
data.dup.each do |fp|
|
112
|
+
fp.save
|
113
|
+
end
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
117
|
+
# @param [Hash] data The initial attributes to populate.
|
118
|
+
def initialize(data={})
|
119
|
+
super()
|
120
|
+
populate_attributes(data) if !data.empty?
|
121
|
+
end
|
122
|
+
|
123
|
+
# Validates a forwarded port.
|
124
|
+
def validate
|
125
|
+
super
|
126
|
+
|
127
|
+
validates_presence_of :parent
|
128
|
+
validates_presence_of :name
|
129
|
+
validates_presence_of :guestport
|
130
|
+
validates_presence_of :hostport
|
131
|
+
end
|
132
|
+
|
133
|
+
# Saves the forwarded port.
|
134
|
+
#
|
135
|
+
# @return [Boolean] True if command was successful, false otherwise.
|
136
|
+
def save
|
137
|
+
return true if !new_record? && !changed?
|
138
|
+
raise Exceptions::ValidationFailedException.new(errors) if !valid?
|
139
|
+
destroy if !new_record?
|
140
|
+
|
141
|
+
parent.modify_engine do |nat|
|
142
|
+
nat.add_redirect(name, protocol, "", hostport, "", guestport)
|
143
|
+
end
|
144
|
+
|
145
|
+
clear_dirty!
|
146
|
+
existing_record!
|
147
|
+
true
|
148
|
+
end
|
149
|
+
|
150
|
+
# Destroys the port forwarding mapping.
|
151
|
+
#
|
152
|
+
# @return [Boolean] True if command was successful, false otherwise.
|
153
|
+
def destroy
|
154
|
+
return if new_record?
|
155
|
+
previous_name = name_changed? ? name_was : name
|
156
|
+
parent.modify_engine do |nat|
|
157
|
+
nat.remove_redirect(previous_name)
|
158
|
+
end
|
159
|
+
parent_collection.delete(self, true) if parent_collection
|
160
|
+
new_record!
|
161
|
+
true
|
162
|
+
end
|
163
|
+
|
164
|
+
# Relationship callback when added to a collection. This is automatically
|
165
|
+
# called by any relationship collection when this object is added.
|
166
|
+
def added_to_relationship(proxy)
|
167
|
+
write_attribute(:parent, proxy.parent)
|
168
|
+
write_attribute(:parent_collection, proxy)
|
169
|
+
end
|
170
|
+
end
|
171
|
+
end
|