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
@@ -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
|