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 CHANGED
@@ -1 +1 @@
1
- 0.7.1
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
- self.class.attributes[name][:default]
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
@@ -121,7 +121,7 @@ module VirtualBox
121
121
  # @param [Hash] data The initial attributes to populate.
122
122
  def initialize(data={})
123
123
  super()
124
- populate_attributes(data)
124
+ populate_attributes(data) if !data.empty?
125
125
  end
126
126
 
127
127
  # Validates a forwarded port.
@@ -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