virtualbox 0.1.1 → 0.2.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.
data/.gitignore CHANGED
@@ -2,4 +2,5 @@ bin/*
2
2
  vendor/gems/*
3
3
  doc/*
4
4
  .yardoc/*
5
- pkg/*
5
+ pkg/*
6
+ test/coverage/*
data/.yardopts ADDED
@@ -0,0 +1,3 @@
1
+ -
2
+ docs/GettingStarted.md
3
+ TODO
data/Rakefile CHANGED
@@ -26,8 +26,22 @@ end
26
26
  begin
27
27
  require 'yard'
28
28
  YARD::Rake::YardocTask.new do |t|
29
- t.options = ['--main', 'Readme.md', '--markup', 'markdown', '--files', 'TODO']
29
+ t.options = ['--main', 'Readme.md', '--markup', 'markdown']
30
+ t.options += ['--title', 'VirtualBox Ruby Library Documentation']
30
31
  end
31
32
  rescue LoadError
32
33
  puts "Yard not available. Install it with: gem install yard"
34
+ end
35
+
36
+ begin
37
+ require 'rcov/rcovtask'
38
+ Rcov::RcovTask.new do |t|
39
+ t.libs << "test"
40
+ t.test_files = FileList["test/**/*_test.rb"]
41
+ t.output_dir = "test/coverage"
42
+ t.verbose = true
43
+ end
44
+ rescue LoadError
45
+ puts "Rcov not available. Coverage data tasks not available."
46
+ puts "Install it with: gem install rcov"
33
47
  end
data/Readme.md CHANGED
@@ -21,6 +21,9 @@ the gem, you must set the path to your `VBoxManage` binary:
21
21
  The virtualbox gem is modeled after ActiveRecord. If you've used ActiveRecord, you'll
22
22
  feel very comfortable using the virtualbox gem.
23
23
 
24
+ There is a [quick getting started guide](http://mitchellh.github.com/virtualbox/file.GettingStarted.html) to
25
+ get you acquainted with the conventions of the virtualbox gem.
26
+
24
27
  Complete documentation can be found at [http://mitchellh.github.com/virtualbox](http://mitchellh.github.com/virtualbox).
25
28
 
26
29
  Below are some examples:
data/TODO CHANGED
@@ -4,9 +4,6 @@ Not all these features will make it into initial releases of virtualbox ruby gem
4
4
  But they will definitely all make it for version 1.0.
5
5
 
6
6
  * Creating a VM from scratch (non-import)
7
- * Pause/Resume VMs
8
- * Exporting a VM
9
- * Modifying attached devices
10
7
  * Parsing bridged IFs
11
8
  * Shared folders
12
9
  * Snapshots
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.1.1
1
+ 0.2.0
@@ -0,0 +1,196 @@
1
+ # Getting Started with the VirtualBox Gem
2
+
3
+ * [Basic Conventions](#basic-conventions)
4
+ * [Finding Models](#bc-finding-models)
5
+ * [Accessing Models](#bc-accessing-models)
6
+ * [Modifying Models](#bc-modifying-models)
7
+ * [Saving Models](#bc-saving-models)
8
+
9
+ <a name="basic-conventions"></a>
10
+ # Basic Conventions
11
+
12
+ The entire virtualbox library follows a few conventions to make sure
13
+ things work uniformly across the entire codebase, and so that nothing
14
+ should surprise any developers once they understand these conventions.
15
+
16
+ When browsing the documentation, you'll probably notice that a lot of the
17
+ classes inherit from {VirtualBox::AbstractModel}. This just means that all
18
+ these classes act the same way! Every {VirtualBox::AbstractModel AbstractModel}
19
+ shares the following behaviors:
20
+
21
+ * Finding
22
+ * Accessing
23
+ * Modifying
24
+ * Saving
25
+
26
+ These behaviors should be similar if not the exact same across all
27
+ virtualbox models. Each of these behaviors is covered below.
28
+
29
+ <a name="bc-finding-models"></a>
30
+ ## Finding Models
31
+
32
+ All data models have a `find` or `all` method (or sometimes both!) These
33
+ methods do what you expect them to: `all` will return an array of all instances
34
+ of that model which is typically unordered. `find` will allow you to find a
35
+ specific instance of that model, typically by name or UUID. Below are a couple
36
+ examples of this.
37
+
38
+ ### All
39
+
40
+ This example uses {VirtualBox::HardDrive}. As you can see, its just an
41
+ unmodified ruby `Array` which is returned by `all`. This can be used find,
42
+ sort, enumerate, etc.
43
+
44
+ drives = VirtualBox::HardDrive.all
45
+ puts "You have #{drives.length} hard drives!"
46
+
47
+ drives.each do |drive|
48
+ puts "Drive: #{drive.uuid}"
49
+ end
50
+
51
+ In the case that `all` returns an empty array, this simply means that none
52
+ of that model exist.
53
+
54
+ ### Find
55
+
56
+ This example uses {VirtualBox::VM}, which will probably be the most common
57
+ model you search for.
58
+
59
+ vm = VirtualBox::VM.find("MyVM")
60
+ puts "This VM has #{vm.memory} MB of RAM allocated to it."
61
+
62
+ Find can also be used with UUIDs:
63
+
64
+ vm = VirtualBox::VM.find("3d0f87b4-50f7-4fc5-ad89-93375b1b32a3")
65
+ puts "This VM's name is: #{vm.name}"
66
+
67
+ When a find fails, it will return `nil`.
68
+
69
+ <a name="bc-accessing-models"></a>
70
+ ## Accessing Models
71
+
72
+ Every model has an _attribute list_ associated with it. These attributes are
73
+ what can be accessed on the model via the typical ruby attribute accessing
74
+ syntax with the `.` (dot) operator. Because these methods are generated
75
+ dynamically, they don't show up as methods in the documentation. Because of this,
76
+ attributes are listed for every model in their overviews. For examples, see the
77
+ overviews of {VirtualBox::VM}, {VirtualBox::HardDrive}, etc.
78
+
79
+ In addition to an attribute list, many models also have _relationships_.
80
+ Relationships are, for our purposes, similar enough to attributes that they
81
+ can be treated the same. Relationship accessing methods are also dynamically
82
+ generated, so they are listed within the overviews of the models as well (if they
83
+ have any). Relationships allow two models to show that they are connected in some
84
+ way, and can therefore be accessed through each other.
85
+
86
+ ### Attributes
87
+
88
+ Reading attributes is simple. Let's use a {VirtualBox::VM} as an example:
89
+
90
+ vm = VirtualBox::VM.find("FooVM")
91
+
92
+ # Accessing attributes:
93
+ vm.memory
94
+ vm.name
95
+ vm.boot1
96
+ vm.ioapic
97
+
98
+ ### Relationships
99
+
100
+ Relationships are read the exact same way as attributes. Again using a
101
+ {VirtualBox::VM} as an example:
102
+
103
+ vm = VirtualBox::VM.find("FooVM")
104
+
105
+ # storage_controllers is a relationship containing an array of all the
106
+ # storage controllers on this VM
107
+ vm.storage_controllers.each do |sc|
108
+ puts "Storage Controller: #{sc.uuid}"
109
+ end
110
+
111
+ The difference from an attribute is that while attributes are typically ruby
112
+ primitives such as `String` or `Boolean`, relationship objects are always other
113
+ virtualbox models such as {VirtualBox::StorageController}.
114
+
115
+ <a name="bc-modifying-models"></a>
116
+ ## Modifying Models
117
+
118
+ In addition to simply reading attributes and relationships, most can be modified
119
+ as well. I say "most" because some attributes are `readonly` and some relationships
120
+ simply don't support being directly modified (though their objects may, I'll get to
121
+ this in a moment). By looking at the attribute list it is easy to spot a readonly
122
+ attribute, which will have the `:readonly` option set to `true`. Below is an example
123
+ of what you might see in the overview of some model:
124
+
125
+ attribute :uuid, :readonly => true
126
+
127
+ In the above case, you could read the `uuid` attribute as normal, but it wouldn't support
128
+ modification (and you'll simply get a `NoMethodError` if you try to set it).
129
+
130
+ Relationships are a little bit trickier, since when discussing modifying a relationship,
131
+ it could either be taken to mean the items _in_ the relationship, or the relationship
132
+ itself. A good rule of thumb, assuming there exists a relationship `foos`,is if you ever
133
+ want to do `object.foos =` something, then you're _modifying the relationship_ and _not_
134
+ the objects. But if you ever do `object.foos[0].destroy`, then you're _modifying the
135
+ relationship objects_ and _not_ the relationship itself.
136
+
137
+ ### Attributes
138
+
139
+ Attributes which support modification are modified like standard ruby attributes. The
140
+ following example uses {VirtualBox::HardDrive}:
141
+
142
+ hd = VirtualBox::HardDrive.new
143
+ hd.size = 2000 # megabytes
144
+ hd.format = "VMDK"
145
+
146
+ As you can see, there is nothing sneaky going on here, and does what you expect.
147
+
148
+ ### Relationships
149
+
150
+ Modifying relationships, on the other hand, is a little different. If the model supports
151
+ modifying the relationship (which it'll note in its respective documentation), then
152
+ you can set it just like an attribute. Below, we use {VirtualBox::AttachedDevice} as
153
+ an example:
154
+
155
+ ad = VirtualBox::AttachedDevice.new
156
+
157
+ # Attached devices have an image relationship
158
+ ad.image = VirtualBox::DVD.empty_drive
159
+
160
+ If a relationship doesn't support setting it, it will raise a {VirtualBox::Exceptions::NonSettableRelationshipException}.
161
+
162
+ **Note**: Below is an example of modifying a relationship object, rather than a
163
+ relationship itself. The example below uses {VirtualBox::VM}.
164
+
165
+ vm = VirtualBox::VM.find("FooVM")
166
+ vm.storage_controllers[0].name = "Foo Controller"
167
+
168
+ <a name="bc-saving-models"></a>
169
+ ## Saving Models
170
+
171
+ Saving models is _really_ easy: you simply call `save`. That's all! Well, there are
172
+ some subtleties, but that's the basic idea. `save` will typically **also save relationships**
173
+ so if you modify a relationship object or relationship itself, calling `save` on the
174
+ parent object will typically save the relationships as well. `save` always returns
175
+ `true` or `false` depending on whether the operation was a success or not. If you'd like
176
+ instead to know why a `save` failed, you can call the method with a `true` parameter
177
+ which sets `raise_errors` to `true` and will raise a {VirtualBox::Exceptions::CommandFailedException}
178
+ if there is a failure. The message on this object contains the reason.
179
+
180
+ Below is an example of saving a simple {VirtualBox::VM} object:
181
+
182
+ vm = VirtualBox::VM.find("FooVM")
183
+
184
+ # Double the memory
185
+ vm.memory = vm.memory.to_i * 2
186
+
187
+ # This will return true/false depending on success
188
+ vm.save
189
+
190
+ Below is an example where an exception will be raised if an error occurs:
191
+
192
+ vm = VirtualBox::VM.find("FooVM")
193
+ vm.memory = "INVALID"
194
+
195
+ # This will raise an exception, since the memory is invalid
196
+ vm.save(true)
@@ -92,7 +92,21 @@ module VirtualBox
92
92
  # @option options [Symbol] :populate_key (attribute name) Specifies
93
93
  # a custom populate key to use for {Attributable#populate_attributes}
94
94
  def attribute(name, options = {})
95
- attributes[name.to_sym] = options
95
+ name = name.to_sym
96
+ attributes[name] = options
97
+
98
+ # Create the method for reading this attribute
99
+ define_method(name) { read_attribute(name) }
100
+
101
+ # Create the writer method for it unless the attribute is readonly,
102
+ # then remove the method if it exists
103
+ if !options[:readonly]
104
+ define_method("#{name}=") do |value|
105
+ write_attribute(name, value)
106
+ end
107
+ elsif method_defined?("#{name}=")
108
+ undef_method("#{name}=")
109
+ end
96
110
  end
97
111
 
98
112
  # Returns the hash of attributes and their associated options.
@@ -173,21 +187,6 @@ module VirtualBox
173
187
  name = name.to_sym
174
188
  has_attribute?(name) && self.class.attributes[name][:readonly]
175
189
  end
176
-
177
- # Method missing is used to add dynamic handlers for accessors and
178
- # setters. Due to the general ugliness of `method_missing`, this may
179
- # change in the near future, but for now it is what it is.
180
- def method_missing(meth, *args)
181
- meth_string = meth.to_s
182
-
183
- if has_attribute?(meth)
184
- read_attribute(meth)
185
- elsif meth_string =~ /^(.+?)=$/ && has_attribute?($1) && !readonly_attribute?($1)
186
- write_attribute($1.to_sym, *args)
187
- else
188
- super
189
- end
190
- end
191
190
  end
192
191
  end
193
192
  end
@@ -74,6 +74,13 @@ module VirtualBox
74
74
  # obj.clear_dirty!(:name)
75
75
  # obj.name_changed? # => false
76
76
  #
77
+ # If no specific field is speciied, `clear_dirty!` will clear the dirty
78
+ # status on the entire model.
79
+ #
80
+ # obj.changed? # => assume true
81
+ # obj.clear_dirty!
82
+ # obj.changed? # => false
83
+ #
77
84
  module Dirty
78
85
  # Manages dirty state for an attribute. This method will handle
79
86
  # setting the dirty state of an attribute (or even clearing it
@@ -104,8 +111,12 @@ module VirtualBox
104
111
  # Clears dirty state for a field.
105
112
  #
106
113
  # @param [Symbol] key The field to clear dirty state.
107
- def clear_dirty!(key)
108
- changes.delete(key)
114
+ def clear_dirty!(key=nil)
115
+ if key.nil?
116
+ @changed_attributes = {}
117
+ else
118
+ changes.delete(key)
119
+ end
109
120
  end
110
121
 
111
122
  # Ignores any dirty changes during the duration of the block.
@@ -39,6 +39,37 @@ module VirtualBox
39
39
  # # Accessing through an instance "instance"
40
40
  # instance.bacons # => whatever Bacon.populate_relationship created
41
41
  #
42
+ # # Settable Relationships
43
+ #
44
+ # It is often convenient that relationships become "settable." That is,
45
+ # for a relationship `foos`, there would exist a `foos=` method. This is
46
+ # possible by implementing the `set_relationship` method on the relationship
47
+ # class. Consider the following relationship:
48
+ #
49
+ # relationship :foos, Foo
50
+ #
51
+ # If `Foo` has the `set_relationship` method, then it will be called by
52
+ # `foos=`. It is expected to return the new value for the relationship. To
53
+ # facilitate this need, the `set_relationship` method is given three
54
+ # parameters: caller, old value, and new value. An example implementation,
55
+ # albeit a silly one, is below:
56
+ #
57
+ # class Foo
58
+ # def self.set_relationship(caller, old_value, new_value)
59
+ # return "Changed to: #{new_value}"
60
+ # end
61
+ # end
62
+ #
63
+ # In this case, the following behavior would occur:
64
+ #
65
+ # instance.foos # => assume "foo"
66
+ # instance.foos = "bar"
67
+ # instance.foos # => "Changed to: bar"
68
+ #
69
+ # If the relationship class _does not implement_ the `set_relationship`
70
+ # method, then a {Exceptions::NonSettableRelationshipException} will be raised if
71
+ # a user attempts to set that relationship.
72
+ #
42
73
  # # Dependent Relationships
43
74
  #
44
75
  # By setting `:dependent => :destroy` on relationships, {AbstractModel}
@@ -63,8 +94,16 @@ module VirtualBox
63
94
  # @option options [Symbol] :dependent (nil) - If set to `:destroy`
64
95
  # {AbstractModel#destroy} will propagate through to relationships.
65
96
  def relationship(name, klass, options = {})
97
+ name = name.to_sym
98
+
66
99
  @relationships ||= {}
67
100
  @relationships[name] = { :klass => klass }.merge(options)
101
+
102
+ # Define the method to read the relationship
103
+ define_method(name) { relationship_data[name] }
104
+
105
+ # Define the method to set the relationship
106
+ define_method("#{name}=") { |*args| set_relationship(name, *args) }
68
107
  end
69
108
 
70
109
  # Returns a hash of all the relationships.
@@ -147,16 +186,26 @@ module VirtualBox
147
186
  self.class.relationships.has_key?(key.to_sym)
148
187
  end
149
188
 
150
- # Method missing is used to add dynamic handlers for relationship
151
- # accessors.
152
- def method_missing(meth, *args)
153
- meth_string = meth.to_s
154
-
155
- if has_relationship?(meth)
156
- relationship_data[meth.to_sym]
157
- else
158
- super
159
- end
189
+ # Sets a relationship to the given value. This is not guaranteed to
190
+ # do anything, since "set_relationship" will be called on the class
191
+ # that the relationship is associated with and its expected to return
192
+ # the resulting relationship to set.
193
+ #
194
+ # If the relationship class doesn't respond to the set_relationship
195
+ # method, then an exception {Exceptions::NonSettableRelationshipException} will
196
+ # be raised.
197
+ #
198
+ # This method is called by the "magic" method of `relationship=`.
199
+ #
200
+ # @param [Symbol] key Relationship key.
201
+ # @param [Object] value The new value of the relationship.
202
+ def set_relationship(key, value)
203
+ key = key.to_sym
204
+ relationship = self.class.relationships[key]
205
+ return unless relationship
206
+
207
+ raise Exceptions::NonSettableRelationshipException.new unless relationship[:klass].respond_to?(:set_relationship)
208
+ relationship_data[key] = relationship[:klass].set_relationship(self, relationship_data[key], value)
160
209
  end
161
210
  end
162
211
  end
@@ -18,10 +18,24 @@ module VirtualBox
18
18
  # is {HardDrive#save} which will create a new hard drive if it didn't
19
19
  # previously exist, or save an old one if it did exist.
20
20
  def new_record?
21
- @new_record = true if @new_record.nil?
21
+ new_record! if @new_record.nil?
22
22
  @new_record
23
23
  end
24
24
 
25
+ # Explicitly resets the model to a new record. If you're using this
26
+ # method outside of virtualbox library core, you should really be
27
+ # asking yourself "why?"
28
+ def new_record!
29
+ @new_record = true
30
+ end
31
+
32
+ # Explicitly sets the model to not be a new record. If you're using
33
+ # this method outside of virtualbox library core, you should really
34
+ # be asking yourself "why?"
35
+ def existing_record!
36
+ @new_record = false
37
+ end
38
+
25
39
  # Saves the model attributes and relationships.
26
40
  #
27
41
  # The method can be passed any arbitrary arguments, which are
@@ -81,6 +95,14 @@ module VirtualBox
81
95
  super
82
96
  end
83
97
 
98
+ # Overwrites {Relatable#set_relationship} to set the dirty state of the
99
+ # relationship. See {Dirty#set_dirty!} as well.
100
+ def set_relationship(key, value)
101
+ existing = relationship_data[key]
102
+ new_value = super
103
+ set_dirty!(key, existing, new_value)
104
+ end
105
+
84
106
  # Destroys the model. The exact behaviour of this method is expected to be
85
107
  # defined on the subclasses. This method on AbstractModel simply
86
108
  # propagates the destroy to the dependent relationships. For more information
@@ -2,9 +2,39 @@ module VirtualBox
2
2
  # Represents an device which is attached to a storage controller. An example
3
3
  # of such a device would be a CD or hard drive attached to an IDE controller.
4
4
  #
5
- # **Currently, attached devices can not be created from scratch. The only way
6
- # to access them is through relationships with other models such as
7
- # {StorageController}.**
5
+ # # Creating a New Attached Device
6
+ #
7
+ # Creating a new attached device is simple. The following is a simple example
8
+ # of creating a DVD with an empty drive:
9
+ #
10
+ # ad = VirtualBox::AttachedDevice.new
11
+ # ad.port = 0
12
+ # ad.image = VirtualBox::DVD.empty_drive
13
+ # storage_controller.devices << ad
14
+ # ad.save
15
+ #
16
+ # The only quirk is that the attached device **must** be attached to a
17
+ # storage controller. The above assumes that `storage_controller` exists,
18
+ # which adds the device.
19
+ #
20
+ # Any {Image} subclass can be set to the `image` relationship.
21
+ #
22
+ # The following is an example using {VM.find}:
23
+ #
24
+ # # First creating the new device...
25
+ # ad = VirtualBox::AttachedDevice.new
26
+ # ad.port = 0
27
+ # ad.image = VirtualBox::DVD.empty_drive
28
+ #
29
+ # # Now attaching to existing VM
30
+ # vm = VirtualBox::VM.find("FooVM")
31
+ # vm.storage_controllers[0].devices << ad
32
+ # vm.save
33
+ #
34
+ # The interesting thing in this example is that the `save` method is called on
35
+ # the virtual machine rather than the AttachedDevice. This will actually work
36
+ # as expected! Saving a virtual machine automatically saves all it's relationships
37
+ # as well.
8
38
  #
9
39
  # # Attributes and Relationships
10
40
  #
@@ -40,8 +70,7 @@ module VirtualBox
40
70
  #
41
71
  class AttachedDevice < AbstractModel
42
72
  attribute :parent, :readonly => true
43
- attribute :uuid
44
- attribute :medium
73
+ attribute :uuid, :readonly => true
45
74
  attribute :port
46
75
  relationship :image, Image
47
76
 
@@ -52,7 +81,7 @@ module VirtualBox
52
81
  #
53
82
  # @return [Array<AttachedDevice>]
54
83
  def populate_relationship(caller, data)
55
- relation = []
84
+ relation = Proxies::Collection.new(caller)
56
85
 
57
86
  counter = 0
58
87
  loop do
@@ -71,20 +100,87 @@ module VirtualBox
71
100
  def destroy_relationship(caller, data, *args)
72
101
  data.each { |v| v.destroy(*args) }
73
102
  end
103
+
104
+ # Saves the relationship. This simply calls {#save} on every
105
+ # member of the relationship.
106
+ #
107
+ # **This method typically won't be used except internally.**
108
+ def save_relationship(caller, data)
109
+ # Just call save on each nic with the VM
110
+ data.each do |ad|
111
+ ad.save
112
+ end
113
+ end
74
114
  end
75
115
 
76
- # Since attached devices can not be created from scratch yet, this
77
- # method should never be called. Instead access attached devices
78
- # through relationships from other models such as {StorageController}.
79
- def initialize(index, caller, data)
116
+ # @overload initialize(data={})
117
+ # Creates a new AttachedDevice which is a new record. This
118
+ # should be attached to a storage controller and saved.
119
+ # @param [Hash] data (optional) A hash which contains initial attribute
120
+ # values for the AttachedDevice.
121
+ # @overload initialize(index, caller, data)
122
+ # Creates an AttachedDevice for a relationship. **This should
123
+ # never be called except internally.**
124
+ # @param [Integer] index Index of the port
125
+ # @param [Object] caller The parent
126
+ # @param [Hash] data A hash of data which must be used
127
+ # to extract the relationship data.
128
+ def initialize(*args)
80
129
  super()
81
130
 
82
- populate_attributes({
83
- :parent => caller,
84
- :port => index,
85
- :medium => data["#{caller.name}-#{index}-0".downcase.to_sym],
86
- :uuid => data["#{caller.name}-ImageUUID-#{index}-0".downcase.to_sym]
87
- })
131
+ if args.length == 3
132
+ populate_from_data(*args)
133
+ elsif args.length == 1
134
+ populate_attributes(*args)
135
+ new_record!
136
+ elsif args.empty?
137
+ return
138
+ else
139
+ raise NoMethodError.new
140
+ end
141
+ end
142
+
143
+ # Saves or creates an attached device.
144
+ #
145
+ # @param [Boolean] raise_errors If true, {Exceptions::CommandFailedException}
146
+ # will be raised if the command failed.
147
+ # @return [Boolean] True if command was successful, false otherwise.
148
+ def save(raise_errors=false)
149
+ raise Exceptions::NoParentException.new if parent.nil?
150
+ raise Exceptions::InvalidObjectException.new("Image must be set") if image.nil?
151
+ return true unless changed?
152
+
153
+ # If the port changed, we have to destroy the old one, then create
154
+ # a new one
155
+ destroy({:port => port_was}, raise_errors) if port_changed? && !port_was.nil?
156
+
157
+ Command.vboxmanage("storageattach #{Command.shell_escape(parent.parent.name)} --storagectl #{Command.shell_escape(parent.name)} --port #{port} --device 0 --type #{image.image_type} --medium #{medium}")
158
+ existing_record!
159
+ clear_dirty!
160
+
161
+ true
162
+ rescue Exceptions::CommandFailedException
163
+ raise if raise_errors
164
+ false
165
+ end
166
+
167
+ # Medium of the attached image. This attribute will be dependent
168
+ # on the attached image and will return one of the following values:
169
+ #
170
+ # * **none** - There is no attached image
171
+ # * **emptydrive** - An image with an empty drive is attached (see
172
+ # {DVD.empty_drive})
173
+ # * **image uuid** - The image's UUID
174
+ #
175
+ # @return [String]
176
+ def medium
177
+ if image.nil?
178
+ "none"
179
+ elsif image.empty_drive?
180
+ "emptydrive"
181
+ else
182
+ image.uuid
183
+ end
88
184
  end
89
185
 
90
186
  # Destroys the attached device. By default, this only removes any
@@ -94,11 +190,40 @@ module VirtualBox
94
190
  #
95
191
  # @option options [Boolean] :destroy_image (false) If true, will also
96
192
  # destroy the image associated with device.
97
- def destroy(options={})
193
+ # @param [Boolean] raise_errors If true, {Exceptions::CommandFailedException}
194
+ # will be raised if the command failed.
195
+ # @return [Boolean] True if command was successful, false otherwise.
196
+ def destroy(options={}, raise_errors=false)
98
197
  # parent = storagecontroller
99
198
  # parent.parent = vm
100
- Command.vboxmanage("storageattach #{Command.shell_escape(parent.parent.name)} --storagectl #{Command.shell_escape(parent.name)} --port #{port} --device 0 --medium none")
101
- image.destroy if options[:destroy_image] && image
199
+ destroy_port = options[:port] || port
200
+ Command.vboxmanage("storageattach #{Command.shell_escape(parent.parent.name)} --storagectl #{Command.shell_escape(parent.name)} --port #{destroy_port} --device 0 --medium none")
201
+ image.destroy(raise_errors) if options[:destroy_image] && image
202
+ rescue Exceptions::CommandFailedException
203
+ raise if raise_errors
204
+ false
205
+ end
206
+
207
+ # Relationship callback when added to a collection. This is automatically
208
+ # called by any relationship collection when this object is added.
209
+ def added_to_relationship(parent)
210
+ write_attribute(:parent, parent)
211
+ end
212
+
213
+ protected
214
+
215
+ # Populates the model based on data from a parsed vminfo. This
216
+ # method is used to create a model which already exists and is
217
+ # part of a relationship.
218
+ #
219
+ # **This method should never be called except internally.**
220
+ def populate_from_data(index, caller, data)
221
+ populate_attributes({
222
+ :parent => caller,
223
+ :port => index,
224
+ :medium => data["#{caller.name}-#{index}-0".downcase.to_sym],
225
+ :uuid => data["#{caller.name}-ImageUUID-#{index}-0".downcase.to_sym]
226
+ })
102
227
  end
103
228
  end
104
229
  end