Getting Started with the VirtualBox Gem
Basic Conventions
The entire virtualbox library follows a few conventions to make sure things work uniformly across the entire codebase, and so that nothing should surprise any developers once they understand these conventions.
When browsing the documentation, you'll probably notice that a lot of the classes inherit from VirtualBox::AbstractModel. This just means that all these classes act the same way! Every AbstractModel shares the following behaviors:
- Finding
- Accessing
- Modifying
- Saving
These behaviors should be similar if not the exact same across all virtualbox models. Each of these behaviors is covered below.
Finding Models
All data models have a find
or all
method (or sometimes both!) These
methods do what you expect them to: all
will return an array of all instances
of that model which is typically unordered. find
will allow you to find a
specific instance of that model, typically by name or UUID. Below are a couple
examples of this.
All
This example uses VirtualBox::HardDrive. As you can see, its just an
unmodified ruby Array
which is returned by all
. This can be used find,
sort, enumerate, etc.
drives = VirtualBox::HardDrive.all
puts "You have #{drives.length} hard drives!"
drives.each do |drive|
puts "Drive: #{drive.uuid}"
end
In the case that all
returns an empty array, this simply means that none
of that model exist.
Find
This example uses VirtualBox::VM, which will probably be the most common model you search for.
vm = VirtualBox::VM.find("MyVM")
puts "This VM has #{vm.memory} MB of RAM allocated to it."
Find can also be used with UUIDs:
vm = VirtualBox::VM.find("3d0f87b4-50f7-4fc5-ad89-93375b1b32a3")
puts "This VM's name is: #{vm.name}"
When a find fails, it will return nil
.
Accessing Models
Every model has an attribute list associated with it. These attributes are
what can be accessed on the model via the typical ruby attribute accessing
syntax with the .
(dot) operator. Because these methods are generated
dynamically, they don't show up as methods in the documentation. Because of this,
attributes are listed for every model in their overviews. For examples, see the
overviews of VirtualBox::VM, VirtualBox::HardDrive, etc.
In addition to an attribute list, many models also have relationships. Relationships are, for our purposes, similar enough to attributes that they can be treated the same. Relationship accessing methods are also dynamically generated, so they are listed within the overviews of the models as well (if they have any). Relationships allow two models to show that they are connected in some way, and can therefore be accessed through each other.
Attributes
Reading attributes is simple. Let's use a VirtualBox::VM as an example:
vm = VirtualBox::VM.find("FooVM")
# Accessing attributes:
vm.memory
vm.name
vm.boot1
vm.ioapic
Relationships
Relationships are read the exact same way as attributes. Again using a VirtualBox::VM as an example:
vm = VirtualBox::VM.find("FooVM")
# storage_controllers is a relationship containing an array of all the
# storage controllers on this VM
vm.storage_controllers.each do |sc|
puts "Storage Controller: #{sc.uuid}"
end
The difference from an attribute is that while attributes are typically ruby
primitives such as String
or Boolean
, relationship objects are always other
virtualbox models such as VirtualBox::StorageController.
Modifying Models
In addition to simply reading attributes and relationships, most can be modified
as well. I say "most" because some attributes are readonly
and some relationships
simply don't support being directly modified (though their objects may, I'll get to
this in a moment). By looking at the attribute list it is easy to spot a readonly
attribute, which will have the :readonly
option set to true
. Below is an example
of what you might see in the overview of some model:
attribute :uuid, :readonly => true
In the above case, you could read the uuid
attribute as normal, but it wouldn't support
modification (and you'll simply get a NoMethodError
if you try to set it).
Relationships are a little bit trickier, since when discussing modifying a relationship,
it could either be taken to mean the items in the relationship, or the relationship
itself. A good rule of thumb, assuming there exists a relationship foos
,is if you ever
want to do object.foos =
something, then you're modifying the relationship and not
the objects. But if you ever do object.foos[0].destroy
, then you're modifying the
relationship objects and not the relationship itself.
Attributes
Attributes which support modification are modified like standard ruby attributes. The following example uses VirtualBox::HardDrive:
hd = VirtualBox::HardDrive.new
hd.size = 2000 # megabytes
hd.format = "VMDK"
As you can see, there is nothing sneaky going on here, and does what you expect.
Relationships
Modifying relationships, on the other hand, is a little different. If the model supports modifying the relationship (which it'll note in its respective documentation), then you can set it just like an attribute. Below, we use VirtualBox::AttachedDevice as an example:
ad = VirtualBox::AttachedDevice.new
# Attached devices have an image relationship
ad.image = VirtualBox::DVD.empty_drive
If a relationship doesn't support setting it, it will raise a VirtualBox::Exceptions::NonSettableRelationshipException.
Note: Below is an example of modifying a relationship object, rather than a relationship itself. The example below uses VirtualBox::VM.
vm = VirtualBox::VM.find("FooVM")
vm.storage_controllers[0].name = "Foo Controller"
Saving Models
Saving models is really easy: you simply call save
. That's all! Well, there are
some subtleties, but that's the basic idea. save
will typically also save relationships
so if you modify a relationship object or relationship itself, calling save
on the
parent object will typically save the relationships as well. save
always returns
true
or false
depending on whether the operation was a success or not. If you'd like
instead to know why a save
failed, you can call the method with a true
parameter
which sets raise_errors
to true
and will raise a VirtualBox::Exceptions::CommandFailedException
if there is a failure. The message on this object contains the reason.
Below is an example of saving a simple VirtualBox::VM object:
vm = VirtualBox::VM.find("FooVM")
# Double the memory
vm.memory = vm.memory.to_i * 2
# This will return true/false depending on success
vm.save
Below is an example where an exception will be raised if an error occurs:
vm = VirtualBox::VM.find("FooVM")
vm.memory = "INVALID"
# This will raise an exception, since the memory is invalid
vm.save(true)