virtualbox 0.1.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 +4 -0
- data/Gemfile +11 -0
- data/Rakefile +32 -0
- data/Readme.md +75 -0
- data/TODO +13 -0
- data/VERSION +1 -0
- data/lib/virtualbox.rb +11 -0
- data/lib/virtualbox/abstract_model.rb +95 -0
- data/lib/virtualbox/abstract_model/attributable.rb +193 -0
- data/lib/virtualbox/abstract_model/dirty.rb +164 -0
- data/lib/virtualbox/abstract_model/relatable.rb +163 -0
- data/lib/virtualbox/attached_device.rb +104 -0
- data/lib/virtualbox/command.rb +54 -0
- data/lib/virtualbox/dvd.rb +31 -0
- data/lib/virtualbox/errors.rb +7 -0
- data/lib/virtualbox/ext/subclass_listing.rb +24 -0
- data/lib/virtualbox/hard_drive.rb +169 -0
- data/lib/virtualbox/image.rb +94 -0
- data/lib/virtualbox/nic.rb +150 -0
- data/lib/virtualbox/storage_controller.rb +122 -0
- data/lib/virtualbox/vm.rb +287 -0
- data/test/test_helper.rb +25 -0
- data/test/virtualbox/abstract_model/attributable_test.rb +150 -0
- data/test/virtualbox/abstract_model/dirty_test.rb +66 -0
- data/test/virtualbox/abstract_model/relatable_test.rb +141 -0
- data/test/virtualbox/abstract_model_test.rb +146 -0
- data/test/virtualbox/attached_device_test.rb +92 -0
- data/test/virtualbox/command_test.rb +30 -0
- data/test/virtualbox/dvd_test.rb +58 -0
- data/test/virtualbox/ext/subclass_listing_test.rb +25 -0
- data/test/virtualbox/hard_drive_test.rb +161 -0
- data/test/virtualbox/image_test.rb +113 -0
- data/test/virtualbox/nic_test.rb +119 -0
- data/test/virtualbox/storage_controller_test.rb +79 -0
- data/test/virtualbox/vm_test.rb +313 -0
- metadata +103 -0
data/.gitignore
ADDED
data/Gemfile
ADDED
@@ -0,0 +1,11 @@
|
|
1
|
+
# Gems required for testing only. To install run
|
2
|
+
# gem bundle test
|
3
|
+
only :test do
|
4
|
+
gem "contest", ">= 0.1.2"
|
5
|
+
gem "mocha"
|
6
|
+
gem "ruby-debug", ">= 0.10.3" if RUBY_VERSION < '1.9'
|
7
|
+
end
|
8
|
+
|
9
|
+
# Makes sure that our code doesn't request gems outside
|
10
|
+
# of our dependency list.
|
11
|
+
disable_system_gems
|
data/Rakefile
ADDED
@@ -0,0 +1,32 @@
|
|
1
|
+
begin
|
2
|
+
require 'jeweler'
|
3
|
+
Jeweler::Tasks.new do |gemspec|
|
4
|
+
gemspec.name = "virtualbox"
|
5
|
+
gemspec.summary = "Create and modify virtual machines in VirtualBox using pure ruby."
|
6
|
+
gemspec.description = "Create and modify virtual machines in VirtualBox using pure ruby."
|
7
|
+
gemspec.email = "mitchell.hashimoto@gmail.com"
|
8
|
+
gemspec.homepage = "http://github.com/mitchellh/virtualbox"
|
9
|
+
gemspec.authors = ["Mitchell Hashimoto"]
|
10
|
+
end
|
11
|
+
Jeweler::GemcutterTasks.new
|
12
|
+
rescue LoadError
|
13
|
+
puts "Jeweler (or a dependency) not available. Install it with: gem install jeweler"
|
14
|
+
end
|
15
|
+
|
16
|
+
require 'rake/testtask'
|
17
|
+
|
18
|
+
task :default => :test
|
19
|
+
|
20
|
+
Rake::TestTask.new do |t|
|
21
|
+
t.libs << "test"
|
22
|
+
t.pattern = 'test/**/*_test.rb'
|
23
|
+
end
|
24
|
+
|
25
|
+
begin
|
26
|
+
require 'yard'
|
27
|
+
YARD::Rake::YardocTask.new do |t|
|
28
|
+
t.options = ['--main', 'Readme.md', '--markup', 'markdown', '--files', 'TODO']
|
29
|
+
end
|
30
|
+
rescue LoadError
|
31
|
+
puts "Yard not available. Install it with: gem install yard"
|
32
|
+
end
|
data/Readme.md
ADDED
@@ -0,0 +1,75 @@
|
|
1
|
+
# VirtualBox Ruby Gem
|
2
|
+
|
3
|
+
The VirtualBox ruby gem is a library which allows anyone to control VirtualBox
|
4
|
+
from ruby code! Create, destroy, start, stop, suspend, and resume virtual machines.
|
5
|
+
Also list virtual machines, list hard drives, network devices, etc.
|
6
|
+
|
7
|
+
## Installation and Requirements
|
8
|
+
|
9
|
+
First you need to install [VirtualBox](http://www.virtualbox.org/) which is available for
|
10
|
+
Windows, Linux, and OS X. After installation, install the gem:
|
11
|
+
|
12
|
+
sudo gem install virtualbox
|
13
|
+
|
14
|
+
The gem assumes that `VBoxManage` will be available on the `PATH`. If not, before using
|
15
|
+
the gem, you must set the path to your `VBoxManage` binary:
|
16
|
+
|
17
|
+
VirtualBox::Command.vboxmanage = "/path/to/my/VBoxManage"
|
18
|
+
|
19
|
+
## Basic Usage
|
20
|
+
|
21
|
+
The virtualbox gem is modeled after ActiveRecord. If you've used ActiveRecord, you'll
|
22
|
+
feel very comfortable using the virtualbox gem.
|
23
|
+
|
24
|
+
Complete documentation can be found at [http://mitchellh.github.com/virtualbox](http://mitchellh.github.com/virtualbox).
|
25
|
+
|
26
|
+
Below are some examples:
|
27
|
+
|
28
|
+
require 'virtualbox'
|
29
|
+
|
30
|
+
vm = VirtualBox::VM.find("my-vm")
|
31
|
+
|
32
|
+
# Let's first print out some basic info about the VM
|
33
|
+
puts "Memory: #{vm.memory}"
|
34
|
+
|
35
|
+
vm.storage_controllers.each do |sc
|
36
|
+
sc.attached_devices.each do |device|
|
37
|
+
puts "Attached Device: #{device.uuid}"
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
# Let's modify the memory and name...
|
42
|
+
vm.memory = 360
|
43
|
+
vm.name = "my-renamed-vm"
|
44
|
+
|
45
|
+
# Save it!
|
46
|
+
vm.save
|
47
|
+
|
48
|
+
Or here is an example of creating a hard drive:
|
49
|
+
|
50
|
+
require 'virtualbox'
|
51
|
+
|
52
|
+
hd = VirtualBox::HardDrive.new
|
53
|
+
hd.location = "foo.vdi"
|
54
|
+
hd.size = 2000 # megabytes
|
55
|
+
hd.save
|
56
|
+
|
57
|
+
## Known Issues or Uncompleted Features
|
58
|
+
|
59
|
+
VirtualBox has a _ton_ of features! As such, this gem is still not totally complete.
|
60
|
+
You can see the features that are still left to do in the TODO file.
|
61
|
+
|
62
|
+
## Reporting Bugs or Feature Requests
|
63
|
+
|
64
|
+
Please use the [issue tracker](https://github.com/mitchellh/virtualbox/issues).
|
65
|
+
|
66
|
+
## Contributing
|
67
|
+
|
68
|
+
If you'd like to contribute to VirtualBox, the first step to developing is to
|
69
|
+
clone this repo, get [wycat's bundler](http://github.com/wycats/bundler) if you
|
70
|
+
don't have it already, and do the following:
|
71
|
+
|
72
|
+
gem bundle test
|
73
|
+
rake
|
74
|
+
|
75
|
+
This will run the test suite, which should come back all green! Then you're good to go!
|
data/TODO
ADDED
@@ -0,0 +1,13 @@
|
|
1
|
+
# TODO
|
2
|
+
|
3
|
+
Not all these features will make it into initial releases of virtualbox ruby gem.
|
4
|
+
But they will definitely all make it for version 1.0.
|
5
|
+
|
6
|
+
* Creating a VM from scratch (non-import)
|
7
|
+
* Pause/Resume VMs
|
8
|
+
* Exporting a VM
|
9
|
+
* Modifying attached devices
|
10
|
+
* Parsing bridged IFs
|
11
|
+
* Shared folders
|
12
|
+
* Snapshots
|
13
|
+
* Getting/Setting guest properties
|
data/VERSION
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
0.1.0
|
data/lib/virtualbox.rb
ADDED
@@ -0,0 +1,11 @@
|
|
1
|
+
$:.unshift(File.expand_path(File.dirname(__FILE__)))
|
2
|
+
require 'virtualbox/errors'
|
3
|
+
require 'virtualbox/command'
|
4
|
+
require 'virtualbox/abstract_model'
|
5
|
+
require 'virtualbox/image'
|
6
|
+
require 'virtualbox/attached_device'
|
7
|
+
require 'virtualbox/dvd'
|
8
|
+
require 'virtualbox/hard_drive'
|
9
|
+
require 'virtualbox/nic'
|
10
|
+
require 'virtualbox/storage_controller'
|
11
|
+
require 'virtualbox/vm'
|
@@ -0,0 +1,95 @@
|
|
1
|
+
require 'virtualbox/abstract_model/attributable'
|
2
|
+
require 'virtualbox/abstract_model/dirty'
|
3
|
+
require 'virtualbox/abstract_model/relatable'
|
4
|
+
|
5
|
+
module VirtualBox
|
6
|
+
# AbstractModel is the base class used for most of virtualbox's classes.
|
7
|
+
# It provides convenient ActiveRecord-style model behavior to subclasses.
|
8
|
+
#
|
9
|
+
# @abstract
|
10
|
+
class AbstractModel
|
11
|
+
include Attributable
|
12
|
+
include Dirty
|
13
|
+
include Relatable
|
14
|
+
|
15
|
+
# Returns a boolean denoting if the record is new or existing. This
|
16
|
+
# method is provided for subclasses to use to differentiate between
|
17
|
+
# creating a new object or saving an existing one. An example of this
|
18
|
+
# is {HardDrive#save} which will create a new hard drive if it didn't
|
19
|
+
# previously exist, or save an old one if it did exist.
|
20
|
+
def new_record?
|
21
|
+
@new_record = true if @new_record.nil?
|
22
|
+
@new_record
|
23
|
+
end
|
24
|
+
|
25
|
+
# Saves the model attributes and relationships.
|
26
|
+
#
|
27
|
+
# The method can be passed any arbitrary arguments, which are
|
28
|
+
# implementation specific (see {VM#save}, which does this).
|
29
|
+
def save(*args)
|
30
|
+
# Go through changed attributes and call save_attribute for
|
31
|
+
# those only
|
32
|
+
changes.each do |key, values|
|
33
|
+
save_attribute(key, values[1], *args)
|
34
|
+
end
|
35
|
+
|
36
|
+
save_relationships(*args)
|
37
|
+
|
38
|
+
# No longer a new record
|
39
|
+
@new_record = false
|
40
|
+
end
|
41
|
+
|
42
|
+
# Saves a single attribute of the model. This method on the abstract
|
43
|
+
# model does nothing on its own, and is expected to be overridden
|
44
|
+
# by any subclasses.
|
45
|
+
#
|
46
|
+
# This method clears the dirty status of the attribute.
|
47
|
+
def save_attribute(key, value, *args)
|
48
|
+
clear_dirty!(key)
|
49
|
+
end
|
50
|
+
|
51
|
+
# Sets the initial attributes from a hash. This method is meant to be used
|
52
|
+
# once to initially setup the attributes. It is **not a mass-assignment**
|
53
|
+
# method for updating attributes.
|
54
|
+
#
|
55
|
+
# This method does **not** affect dirtiness, but also does not clear it.
|
56
|
+
# This means that if you call populate_attributes, the same attributes
|
57
|
+
# that were dirty before the call will be dirty after the call (but no
|
58
|
+
# more and no less). This distinction is important because most subclasses
|
59
|
+
# of AbstractModel only save changed attributes, and ignore unchanged
|
60
|
+
# attributes. Attempting to change attributes through this method will
|
61
|
+
# cause them to not be saved, which is surely unexpected behaviour for
|
62
|
+
# most users.
|
63
|
+
#
|
64
|
+
# Calling this method will also cause the model to assume that it is not
|
65
|
+
# a new record (see {#new_record?}).
|
66
|
+
def populate_attributes(attribs)
|
67
|
+
# No longer a new record
|
68
|
+
@new_record = false
|
69
|
+
|
70
|
+
ignore_dirty do
|
71
|
+
super
|
72
|
+
|
73
|
+
populate_relationships(attribs)
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
# Overwrites {Attributable#write_attribute} to set the dirty state of
|
78
|
+
# the written attribute. See {Dirty#set_dirty!} as well.
|
79
|
+
def write_attribute(name, value)
|
80
|
+
set_dirty!(name, read_attribute(name), value)
|
81
|
+
super
|
82
|
+
end
|
83
|
+
|
84
|
+
# Destroys the model. The exact behaviour of this method is expected to be
|
85
|
+
# defined on the subclasses. This method on AbstractModel simply
|
86
|
+
# propagates the destroy to the dependent relationships. For more information
|
87
|
+
# on relationships, see {Relatable}.
|
88
|
+
def destroy(*args)
|
89
|
+
# Destroy dependent relationships
|
90
|
+
self.class.relationships.each do |name, options|
|
91
|
+
destroy_relationship(name, *args) if options[:dependent] == :destroy
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
@@ -0,0 +1,193 @@
|
|
1
|
+
module VirtualBox
|
2
|
+
class AbstractModel
|
3
|
+
# Module which can be included into any other class which allows
|
4
|
+
# that class to have attributes using the "attribute" class method.
|
5
|
+
# This creates the reader/writer for the attribute but also provides
|
6
|
+
# other useful options such as readonly attributes, default values,
|
7
|
+
# and more.
|
8
|
+
#
|
9
|
+
# Make sure to also see the {ClassMethods}.
|
10
|
+
#
|
11
|
+
# ## Defining a Basic Attribute
|
12
|
+
#
|
13
|
+
# attribute :name
|
14
|
+
#
|
15
|
+
# The example above would put the "name" attribute on the class. This
|
16
|
+
# would give the class the following abilities
|
17
|
+
#
|
18
|
+
# instance.name = "Harry!"
|
19
|
+
# puts instance.name # => "Harry!"
|
20
|
+
#
|
21
|
+
# Basic attributes alone are not different than ruby's built-in
|
22
|
+
# `attr_*` methods.
|
23
|
+
#
|
24
|
+
# ## Defining a Readonly Attribute
|
25
|
+
#
|
26
|
+
# attribute :age, :readonly => true
|
27
|
+
#
|
28
|
+
# The example above allows age to be read, but not written to via the
|
29
|
+
# `age=` method. The attribute is still able to written using
|
30
|
+
# {#write_attribute} but this is generally only for
|
31
|
+
# inter-class use, and not for users of it.
|
32
|
+
#
|
33
|
+
# ## Defining Default Values
|
34
|
+
#
|
35
|
+
# attribute :format, :default => "VDI"
|
36
|
+
#
|
37
|
+
# The example above applies a default value to format. So if no value
|
38
|
+
# is ever given to it, `format` would return `VDI`.
|
39
|
+
#
|
40
|
+
# ## Populating Multiple Attributes
|
41
|
+
#
|
42
|
+
# Attributes can be mass populated using {#populate_attributes}. Below
|
43
|
+
# is an example of the use.
|
44
|
+
#
|
45
|
+
# class Person
|
46
|
+
# include Attributable
|
47
|
+
#
|
48
|
+
# attribute :name
|
49
|
+
# attribute :age, :readonly => true
|
50
|
+
#
|
51
|
+
# def initialize
|
52
|
+
# populate_attributes({
|
53
|
+
# :name => "Steven",
|
54
|
+
# :age => 27
|
55
|
+
# })
|
56
|
+
# end
|
57
|
+
# end
|
58
|
+
#
|
59
|
+
# **Note:** Populating attributes is not the same as mass-updating attributes.
|
60
|
+
# {#populate_attributes} is meant to do initial population only. There is
|
61
|
+
# currently no method for mass assignment for updating.
|
62
|
+
#
|
63
|
+
# ## Custom Populate Keys
|
64
|
+
#
|
65
|
+
# Sometimes the attribute names don't match the keys of the hash that will be
|
66
|
+
# used to populate it. For this purpose, you can define a custom
|
67
|
+
# `populate_key`. Example:
|
68
|
+
#
|
69
|
+
# attribute :path, :populate_key => :location
|
70
|
+
#
|
71
|
+
# def initialize
|
72
|
+
# populate_attributes(:location => "Home")
|
73
|
+
# puts path # => "Home"
|
74
|
+
# end
|
75
|
+
#
|
76
|
+
module Attributable
|
77
|
+
def self.included(base)
|
78
|
+
base.extend ClassMethods
|
79
|
+
end
|
80
|
+
|
81
|
+
# Defines the class methods for the {Attributable} module. For
|
82
|
+
# detailed overview documentation, see {Attributable}.
|
83
|
+
module ClassMethods
|
84
|
+
# Defines an attribute on the model.
|
85
|
+
#
|
86
|
+
# @param [Symbol] name The name of the attribute, which will also be
|
87
|
+
# used to set the accessor methods.
|
88
|
+
# @option options [Boolean] :readonly (false) If true, attribute will be readonly.
|
89
|
+
# More specifically, the `attribute=` method won't be defined for it.
|
90
|
+
# @option options [Object] :default (nil) Specifies a default value for the
|
91
|
+
# attribute.
|
92
|
+
# @option options [Symbol] :populate_key (attribute name) Specifies
|
93
|
+
# a custom populate key to use for {Attributable#populate_attributes}
|
94
|
+
def attribute(name, options = {})
|
95
|
+
attributes[name.to_sym] = options
|
96
|
+
end
|
97
|
+
|
98
|
+
# Returns the hash of attributes and their associated options.
|
99
|
+
def attributes
|
100
|
+
@attributes ||= {}
|
101
|
+
end
|
102
|
+
|
103
|
+
# Used to propagate attributes to subclasses. This method makes sure that
|
104
|
+
# subclasses of a class with {Attributable} included will inherit the
|
105
|
+
# attributes as well, which would be the expected behaviour.
|
106
|
+
def inherited(subclass)
|
107
|
+
super rescue NoMethodError
|
108
|
+
|
109
|
+
attributes.each do |name, option|
|
110
|
+
subclass.attribute(name, option)
|
111
|
+
end
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
# Does the initial population of the various attributes. It will
|
116
|
+
# ignore attributes which are not defined or have no value in the
|
117
|
+
# hash.
|
118
|
+
#
|
119
|
+
# Population uses the attributes `populate_key` if present to
|
120
|
+
# determine which value to take. Example:
|
121
|
+
#
|
122
|
+
# attribute :name, :populate_key => :namae
|
123
|
+
# attribute :age
|
124
|
+
#
|
125
|
+
# def initialize
|
126
|
+
# populate_attributes(:namae => "Henry", :age => 27)
|
127
|
+
# end
|
128
|
+
#
|
129
|
+
# The above example would set `name` to `Henry` since that is
|
130
|
+
# the `populate_key`. If a `populate_key` is not present, the
|
131
|
+
# attribute name is used.
|
132
|
+
def populate_attributes(attribs)
|
133
|
+
self.class.attributes.each do |key, options|
|
134
|
+
value_key = options[:populate_key] || key
|
135
|
+
write_attribute(key, attribs[value_key])
|
136
|
+
end
|
137
|
+
end
|
138
|
+
|
139
|
+
# Writes an attribute. This method ignores the `readonly` option
|
140
|
+
# on attribute definitions. This method is mostly meant for
|
141
|
+
# internal use on setting attributes (including readonly
|
142
|
+
# attributes), whereas users of a class which includes this
|
143
|
+
# module should use the accessor methods, such as `name=`.
|
144
|
+
def write_attribute(name, value)
|
145
|
+
attributes[name] = value
|
146
|
+
end
|
147
|
+
|
148
|
+
# Reads an attribute. This method will return `nil` if the
|
149
|
+
# attribute doesn't exist. If the attribute does exist but
|
150
|
+
# doesn't have a value set, it'll use the `default` value
|
151
|
+
# if specified.
|
152
|
+
def read_attribute(name)
|
153
|
+
if has_attribute?(name)
|
154
|
+
attributes[name] || self.class.attributes[name][:default]
|
155
|
+
end
|
156
|
+
end
|
157
|
+
|
158
|
+
# Returns a hash of all attributes and their options.
|
159
|
+
def attributes
|
160
|
+
@attribute_values ||= {}
|
161
|
+
end
|
162
|
+
|
163
|
+
# Returns boolean value denoting if an attribute exists.
|
164
|
+
def has_attribute?(name)
|
165
|
+
self.class.attributes.has_key?(name.to_sym)
|
166
|
+
end
|
167
|
+
|
168
|
+
# Returns a boolean value denoting if an attribute is readonly.
|
169
|
+
# This method also returns false for **nonexistent attributes**
|
170
|
+
# so it should be used in conjunction with {#has_attribute?} if
|
171
|
+
# existence is important.
|
172
|
+
def readonly_attribute?(name)
|
173
|
+
name = name.to_sym
|
174
|
+
has_attribute?(name) && self.class.attributes[name][:readonly]
|
175
|
+
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
|
+
end
|
192
|
+
end
|
193
|
+
end
|