virtualbox 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
@@ -0,0 +1,164 @@
|
|
1
|
+
module VirtualBox
|
2
|
+
class AbstractModel
|
3
|
+
# Tracks "dirtiness" of values for a class. Its not tied to AbstractModel
|
4
|
+
# in any way other than the namespace.
|
5
|
+
#
|
6
|
+
# # Checking if a Value was Changed
|
7
|
+
#
|
8
|
+
# Dynamic methods allow functionality for checking if values changed:
|
9
|
+
#
|
10
|
+
# obj.foo_changed?
|
11
|
+
#
|
12
|
+
# # Previous Value
|
13
|
+
#
|
14
|
+
# Can also view the previous value of an attribute:
|
15
|
+
#
|
16
|
+
# obj.foo # => "foo" initially
|
17
|
+
# obj.foo = "bar"
|
18
|
+
# obj.foo_was # => "foo"
|
19
|
+
#
|
20
|
+
# # Previous and Current Value
|
21
|
+
#
|
22
|
+
# Using the `_change` dynamic method, can view the changes of a field.
|
23
|
+
#
|
24
|
+
# obj.foo # => "foo" initially
|
25
|
+
# obj.foo = "bar"
|
26
|
+
# obj.foo_change # => ["foo", "bar"]
|
27
|
+
#
|
28
|
+
# # All Changes
|
29
|
+
#
|
30
|
+
# Can also view all changes for a class with the `changes` method.
|
31
|
+
#
|
32
|
+
# obj.foo # => "foo" initially
|
33
|
+
# obj.bar # => "bar" initially
|
34
|
+
# obj.foo = "far"
|
35
|
+
# obj.bar = "baz"
|
36
|
+
# obj.changes # => { :foo => ["foo", "far"], :bar => ["bar", "baz"]}
|
37
|
+
#
|
38
|
+
# # Setting Dirty
|
39
|
+
#
|
40
|
+
# Dirtiness tracking only occurs for values which the implementor
|
41
|
+
# explicitly sets as dirty. This is done with the {#set_dirty!}
|
42
|
+
# method. Example implementation below:
|
43
|
+
#
|
44
|
+
# class Person
|
45
|
+
# include VirtualBox::AbstractModel::Dirty
|
46
|
+
#
|
47
|
+
# attr_reader :name
|
48
|
+
#
|
49
|
+
# def name=(value)
|
50
|
+
# set_dirty!(:name, @name, value)
|
51
|
+
# @name = value
|
52
|
+
# end
|
53
|
+
# end
|
54
|
+
#
|
55
|
+
# The above example has all the changes necessary to track changes
|
56
|
+
# on an attribute.
|
57
|
+
#
|
58
|
+
# # Ignoring Dirtiness Tracking
|
59
|
+
#
|
60
|
+
# Sometimes, for features such as mass assignment, dirtiness tracking
|
61
|
+
# should be disabled. This can be done with the `ignore_dirty` method.
|
62
|
+
#
|
63
|
+
# ignore_dirty do |obj|
|
64
|
+
# obj.name = "Foo"
|
65
|
+
# end
|
66
|
+
#
|
67
|
+
# obj.changed? # => false
|
68
|
+
#
|
69
|
+
# # Clearing Dirty State
|
70
|
+
#
|
71
|
+
# Sometimes, such as after saving a model, dirty states should be cleared.
|
72
|
+
# This can be done with the `clear_dirty!` method.
|
73
|
+
#
|
74
|
+
# obj.clear_dirty!(:name)
|
75
|
+
# obj.name_changed? # => false
|
76
|
+
#
|
77
|
+
module Dirty
|
78
|
+
# Manages dirty state for an attribute. This method will handle
|
79
|
+
# setting the dirty state of an attribute (or even clearing it
|
80
|
+
# if the old value is reset). Any implementors of this mixin should
|
81
|
+
# call this for any fields they want tracked.
|
82
|
+
#
|
83
|
+
# @param [Symbol] name Name of field
|
84
|
+
# @param [Object] current Current value (not necessarilly the
|
85
|
+
# original value, but the **current** value)
|
86
|
+
# @param [Object] value The new value being set
|
87
|
+
def set_dirty!(name, current, value)
|
88
|
+
if current != value
|
89
|
+
# If its the first time this attribute has changed, store the
|
90
|
+
# original value in the first field
|
91
|
+
changes[name] ||= [current, nil]
|
92
|
+
|
93
|
+
# Then store the changed value
|
94
|
+
changes[name][1] = value
|
95
|
+
|
96
|
+
# If the value changed back to the original value, remove from the
|
97
|
+
# dirty hash
|
98
|
+
if changes[name][0] == changes[name][1]
|
99
|
+
changes.delete(name)
|
100
|
+
end
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
# Clears dirty state for a field.
|
105
|
+
#
|
106
|
+
# @param [Symbol] key The field to clear dirty state.
|
107
|
+
def clear_dirty!(key)
|
108
|
+
changes.delete(key)
|
109
|
+
end
|
110
|
+
|
111
|
+
# Ignores any dirty changes during the duration of the block.
|
112
|
+
# Guarantees the dirty state will be the same before and after
|
113
|
+
# the method call, but not within the block itself.
|
114
|
+
def ignore_dirty(&block)
|
115
|
+
current_changes = @changed_attributes.dup rescue nil
|
116
|
+
yield self
|
117
|
+
@changed_attributes = current_changes
|
118
|
+
end
|
119
|
+
|
120
|
+
# Returns boolean denoting if field changed or not. If no attribute
|
121
|
+
# is specified, returns true of false showing whether the model
|
122
|
+
# changed at all.
|
123
|
+
#
|
124
|
+
# @param [Symbol] attribute The attribute to check, or if nil,
|
125
|
+
# all fields checked.
|
126
|
+
def changed?(attribute = nil)
|
127
|
+
if attribute.nil?
|
128
|
+
!changes.empty?
|
129
|
+
else
|
130
|
+
changes.has_key?(attribute)
|
131
|
+
end
|
132
|
+
end
|
133
|
+
|
134
|
+
# Returns hash of changes. Keys are fields, values are an
|
135
|
+
# array of the original value and the current value.
|
136
|
+
#
|
137
|
+
# @return [Hash]
|
138
|
+
def changes
|
139
|
+
@changed_attributes ||= {}
|
140
|
+
end
|
141
|
+
|
142
|
+
# Method missing is used to implement the "magic" methods of
|
143
|
+
# `field_changed`, `field_change`, and `field_was`.
|
144
|
+
def method_missing(meth, *args)
|
145
|
+
meth_string = meth.to_s
|
146
|
+
|
147
|
+
if meth_string =~ /^(.+?)_changed\?$/
|
148
|
+
changed?($1.to_sym)
|
149
|
+
elsif meth_string =~ /^(.+?)_change$/
|
150
|
+
changes[$1.to_sym]
|
151
|
+
elsif meth_string =~ /^(.+?)_was$/
|
152
|
+
change = changes[$1.to_sym]
|
153
|
+
if change.nil?
|
154
|
+
nil
|
155
|
+
else
|
156
|
+
change[0]
|
157
|
+
end
|
158
|
+
else
|
159
|
+
super
|
160
|
+
end
|
161
|
+
end
|
162
|
+
end
|
163
|
+
end
|
164
|
+
end
|
@@ -0,0 +1,163 @@
|
|
1
|
+
module VirtualBox
|
2
|
+
class AbstractModel
|
3
|
+
# Provides simple relationship features to any class. These relationships
|
4
|
+
# can be anything, since this module makes no assumptions and doesn't
|
5
|
+
# differentiate between "has many" or "belongs to" or any of that.
|
6
|
+
#
|
7
|
+
# The way it works is simple:
|
8
|
+
#
|
9
|
+
# 1. Relationships are defined with a relationship name and a
|
10
|
+
# class of the relationship objects.
|
11
|
+
# 2. When {#populate_relationships} is called, `populate_relationship` is
|
12
|
+
# called on each relationship class (example: {StorageController.populate_relationship}).
|
13
|
+
# This is expected to return the relationship, which can be any object.
|
14
|
+
# 3. When {#save_relationships} is called, `save_relationship` is
|
15
|
+
# called on each relationship class, which manages saving its own
|
16
|
+
# relationship.
|
17
|
+
# 4. When {#destroy_relationships} is called, `destroy_relationship` is
|
18
|
+
# called on each relationship class, which manages destroying
|
19
|
+
# its own relationship.
|
20
|
+
#
|
21
|
+
# Be sure to read {ClassMethods} for complete documentation of methods.
|
22
|
+
#
|
23
|
+
# # Defining Relationships
|
24
|
+
#
|
25
|
+
# Every relationship has two mandatory parameters: the name and the class.
|
26
|
+
#
|
27
|
+
# relationship :bacons, Bacon
|
28
|
+
#
|
29
|
+
# In this case, there is a relationship `bacons` which refers to the `Bacon`
|
30
|
+
# class.
|
31
|
+
#
|
32
|
+
# # Accessing Relationships
|
33
|
+
#
|
34
|
+
# Relatable offers up dynamically generated accessors for every relationship
|
35
|
+
# which simply returns the relationship data.
|
36
|
+
#
|
37
|
+
# relationship :bacons, Bacon
|
38
|
+
#
|
39
|
+
# # Accessing through an instance "instance"
|
40
|
+
# instance.bacons # => whatever Bacon.populate_relationship created
|
41
|
+
#
|
42
|
+
# # Dependent Relationships
|
43
|
+
#
|
44
|
+
# By setting `:dependent => :destroy` on relationships, {AbstractModel}
|
45
|
+
# will automatically call {#destroy_relationships} when {AbstractModel#destroy}
|
46
|
+
# is called.
|
47
|
+
#
|
48
|
+
# This is not a feature built-in to Relatable but figured it should be
|
49
|
+
# mentioned here.
|
50
|
+
module Relatable
|
51
|
+
def self.included(base)
|
52
|
+
base.extend ClassMethods
|
53
|
+
end
|
54
|
+
|
55
|
+
module ClassMethods
|
56
|
+
# Define a relationship. The name and class must be specified. This
|
57
|
+
# class will be used to call the `populate_relationship,
|
58
|
+
# `save_relationship`, etc. methods.
|
59
|
+
#
|
60
|
+
# @param [Symbol] name Relationship name. This will also be used for
|
61
|
+
# the dynamically generated accessor.
|
62
|
+
# @param [Class] klass Class of the relationship.
|
63
|
+
# @option options [Symbol] :dependent (nil) - If set to `:destroy`
|
64
|
+
# {AbstractModel#destroy} will propagate through to relationships.
|
65
|
+
def relationship(name, klass, options = {})
|
66
|
+
@relationships ||= {}
|
67
|
+
@relationships[name] = { :klass => klass }.merge(options)
|
68
|
+
end
|
69
|
+
|
70
|
+
# Returns a hash of all the relationships.
|
71
|
+
#
|
72
|
+
# @return [Hash]
|
73
|
+
def relationships
|
74
|
+
@relationships ||= {}
|
75
|
+
end
|
76
|
+
|
77
|
+
# Used to propagate relationships to subclasses. This method makes sure that
|
78
|
+
# subclasses of a class with {Relatable} included will inherit the
|
79
|
+
# relationships as well, which would be the expected behaviour.
|
80
|
+
def inherited(subclass)
|
81
|
+
super rescue NoMethodError
|
82
|
+
|
83
|
+
relationships.each do |name, options|
|
84
|
+
subclass.relationship(name, nil, options)
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
# Saves the model, calls save_relationship on all relations. It is up to
|
90
|
+
# the relation to determine whether anything changed, etc. Simply
|
91
|
+
# calls `save_relationship` on each relationshp class passing in the
|
92
|
+
# following parameters:
|
93
|
+
#
|
94
|
+
# * **caller** - The class which is calling save
|
95
|
+
# * **data** - The data associated with the relationship
|
96
|
+
#
|
97
|
+
# In addition to those two args, any arbitrary args may be tacked on to the
|
98
|
+
# end and they'll be pushed through to the `save_relationship` method.
|
99
|
+
def save_relationships(*args)
|
100
|
+
self.class.relationships.each do |name, options|
|
101
|
+
next unless options[:klass].respond_to?(:save_relationship)
|
102
|
+
options[:klass].save_relationship(self, relationship_data[name], *args)
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
# The equivalent to {Attributable#populate_attributes}, but with
|
107
|
+
# relationships.
|
108
|
+
def populate_relationships(data)
|
109
|
+
self.class.relationships.each do |name, options|
|
110
|
+
next unless options[:klass].respond_to?(:populate_relationship)
|
111
|
+
relationship_data[name] = options[:klass].populate_relationship(self, data)
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
# Calls `destroy_relationship` on each of the relationships. Any
|
116
|
+
# arbitrary args may be added and they will be forarded to the
|
117
|
+
# relationship's `destroy_relationship` method.
|
118
|
+
def destroy_relationships(*args)
|
119
|
+
self.class.relationships.each do |name, options|
|
120
|
+
destroy_relationship(name, *args)
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
124
|
+
# Destroys only a single relationship. Any arbitrary args
|
125
|
+
# may be added to the end and they will be pushed through to
|
126
|
+
# the class's `destroy_relationship` method.
|
127
|
+
#
|
128
|
+
# @param [Symbol] name The name of the relationship
|
129
|
+
def destroy_relationship(name, *args)
|
130
|
+
options = self.class.relationships[name]
|
131
|
+
return unless options && options[:klass].respond_to?(:destroy_relationship)
|
132
|
+
options[:klass].destroy_relationship(self, relationship_data[name], *args)
|
133
|
+
end
|
134
|
+
|
135
|
+
# Hash to data associated with relationships. You should instead
|
136
|
+
# use the accessors created by Relatable.
|
137
|
+
#
|
138
|
+
# @return [Hash]
|
139
|
+
def relationship_data
|
140
|
+
@relationship_data ||= {}
|
141
|
+
end
|
142
|
+
|
143
|
+
# Returns boolean denoting if a relationship exists.
|
144
|
+
#
|
145
|
+
# @return [Boolean]
|
146
|
+
def has_relationship?(key)
|
147
|
+
self.class.relationships.has_key?(key.to_sym)
|
148
|
+
end
|
149
|
+
|
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
|
160
|
+
end
|
161
|
+
end
|
162
|
+
end
|
163
|
+
end
|
@@ -0,0 +1,104 @@
|
|
1
|
+
module VirtualBox
|
2
|
+
# Represents an device which is attached to a storage controller. An example
|
3
|
+
# of such a device would be a CD or hard drive attached to an IDE controller.
|
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}.**
|
8
|
+
#
|
9
|
+
# # Attributes and Relationships
|
10
|
+
#
|
11
|
+
# Properties of the model are exposed using standard ruby instance
|
12
|
+
# methods which are generated on the fly. Because of this, they are not listed
|
13
|
+
# below as available instance methods.
|
14
|
+
#
|
15
|
+
# These attributes can be accessed and modified via standard ruby-style
|
16
|
+
# `instance.attribute` and `instance.attribute=` methods. The attributes are
|
17
|
+
# listed below.
|
18
|
+
#
|
19
|
+
# Relationships are also accessed like attributes but can't be set. Instead,
|
20
|
+
# they are typically references to other objects such as an {AttachedDevice} which
|
21
|
+
# in turn have their own attributes which can be modified.
|
22
|
+
#
|
23
|
+
# ## Attributes
|
24
|
+
#
|
25
|
+
# This is copied directly from the class header, but lists all available
|
26
|
+
# attributes. If you don't understand what this means, read {Attributable}.
|
27
|
+
#
|
28
|
+
# attribute :parent, :readonly => true
|
29
|
+
# attribute :uuid
|
30
|
+
# attribute :medium
|
31
|
+
# attribute :port
|
32
|
+
#
|
33
|
+
# ## Relationships
|
34
|
+
#
|
35
|
+
# In addition to the basic attributes, a virtual machine is related
|
36
|
+
# to other things. The relationships are listed below. If you don't
|
37
|
+
# understand this, read {Relatable}.
|
38
|
+
#
|
39
|
+
# relationship :image, Image
|
40
|
+
#
|
41
|
+
class AttachedDevice < AbstractModel
|
42
|
+
attribute :parent, :readonly => true
|
43
|
+
attribute :uuid
|
44
|
+
attribute :medium
|
45
|
+
attribute :port
|
46
|
+
relationship :image, Image
|
47
|
+
|
48
|
+
class <<self
|
49
|
+
# Populate relationship with another model.
|
50
|
+
#
|
51
|
+
# **This method typically won't be used except internally.**
|
52
|
+
#
|
53
|
+
# @return [Array<AttachedDevice>]
|
54
|
+
def populate_relationship(caller, data)
|
55
|
+
relation = []
|
56
|
+
|
57
|
+
counter = 0
|
58
|
+
loop do
|
59
|
+
break unless data["#{caller.name}-#{counter}-0".downcase.to_sym]
|
60
|
+
nic = new(counter, caller, data)
|
61
|
+
relation.push(nic)
|
62
|
+
counter += 1
|
63
|
+
end
|
64
|
+
|
65
|
+
relation
|
66
|
+
end
|
67
|
+
|
68
|
+
# Destroy attached devices associated with another model.
|
69
|
+
#
|
70
|
+
# **This method typically won't be used except internally.**
|
71
|
+
def destroy_relationship(caller, data, *args)
|
72
|
+
data.each { |v| v.destroy(*args) }
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
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)
|
80
|
+
super()
|
81
|
+
|
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
|
+
})
|
88
|
+
end
|
89
|
+
|
90
|
+
# Destroys the attached device. By default, this only removes any
|
91
|
+
# media inserted within the device, but does not destroy it. This
|
92
|
+
# option can be specified, however, through the `destroy_image`
|
93
|
+
# option.
|
94
|
+
#
|
95
|
+
# @option options [Boolean] :destroy_image (false) If true, will also
|
96
|
+
# destroy the image associated with device.
|
97
|
+
def destroy(options={})
|
98
|
+
# parent = storagecontroller
|
99
|
+
# 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
|
102
|
+
end
|
103
|
+
end
|
104
|
+
end
|
@@ -0,0 +1,54 @@
|
|
1
|
+
module VirtualBox
|
2
|
+
# Used by the rest of the virtualbox library to call shell commands.
|
3
|
+
# It also can be used to change the path for your VBoxManage program.
|
4
|
+
#
|
5
|
+
# # Changing VBoxManage Path
|
6
|
+
#
|
7
|
+
# The rest of the library won't work without a proper path to VBoxManage,
|
8
|
+
# so it is crucial to set this properly right away. By default its set
|
9
|
+
# to `VBoxManage` which assumes that it is in your `PATH`.
|
10
|
+
#
|
11
|
+
# VirtualBox::Command.vboxmanage = "/opt/local/bin/VBoxManage"
|
12
|
+
#
|
13
|
+
class Command
|
14
|
+
@@vboxmanage = "VBoxManage"
|
15
|
+
|
16
|
+
class <<self
|
17
|
+
# Sets the path to VBoxManage, which is required for this gem to
|
18
|
+
# work.
|
19
|
+
def vboxmanage=(path)
|
20
|
+
@@vboxmanage = path
|
21
|
+
end
|
22
|
+
|
23
|
+
# Runs a VBoxManage command and returns the output.
|
24
|
+
def vboxmanage(command)
|
25
|
+
execute("#{@@vboxmanage} #{command}")
|
26
|
+
end
|
27
|
+
|
28
|
+
# Runs a command and returns a boolean result showing
|
29
|
+
# if the command ran successfully or not based on the
|
30
|
+
# exit code.
|
31
|
+
def test(command)
|
32
|
+
execute(command)
|
33
|
+
$?.to_i == 0
|
34
|
+
end
|
35
|
+
|
36
|
+
# Runs a command and returns the STDOUT result. The reason this is
|
37
|
+
# a method at the moment is because in the future we may want to
|
38
|
+
# change the way commands are run (replace the backticks), plus it
|
39
|
+
# makes testing easier.
|
40
|
+
def execute(command)
|
41
|
+
`#{command}`
|
42
|
+
end
|
43
|
+
|
44
|
+
# Shell escapes a string. This is almost a direct copy/paste from
|
45
|
+
# the ruby mailing list. I'm not sure how well it works but so far
|
46
|
+
# it hasn't failed!
|
47
|
+
def shell_escape(str)
|
48
|
+
str.to_s.gsub(/(?=[^a-zA-Z0-9_.\/\-\x7F-\xFF\n])/n, '\\').
|
49
|
+
gsub(/\n/, "'\n'").
|
50
|
+
sub(/^$/, "''")
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|