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:

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)