virtualbox 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (36) hide show
  1. data/.gitignore +4 -0
  2. data/Gemfile +11 -0
  3. data/Rakefile +32 -0
  4. data/Readme.md +75 -0
  5. data/TODO +13 -0
  6. data/VERSION +1 -0
  7. data/lib/virtualbox.rb +11 -0
  8. data/lib/virtualbox/abstract_model.rb +95 -0
  9. data/lib/virtualbox/abstract_model/attributable.rb +193 -0
  10. data/lib/virtualbox/abstract_model/dirty.rb +164 -0
  11. data/lib/virtualbox/abstract_model/relatable.rb +163 -0
  12. data/lib/virtualbox/attached_device.rb +104 -0
  13. data/lib/virtualbox/command.rb +54 -0
  14. data/lib/virtualbox/dvd.rb +31 -0
  15. data/lib/virtualbox/errors.rb +7 -0
  16. data/lib/virtualbox/ext/subclass_listing.rb +24 -0
  17. data/lib/virtualbox/hard_drive.rb +169 -0
  18. data/lib/virtualbox/image.rb +94 -0
  19. data/lib/virtualbox/nic.rb +150 -0
  20. data/lib/virtualbox/storage_controller.rb +122 -0
  21. data/lib/virtualbox/vm.rb +287 -0
  22. data/test/test_helper.rb +25 -0
  23. data/test/virtualbox/abstract_model/attributable_test.rb +150 -0
  24. data/test/virtualbox/abstract_model/dirty_test.rb +66 -0
  25. data/test/virtualbox/abstract_model/relatable_test.rb +141 -0
  26. data/test/virtualbox/abstract_model_test.rb +146 -0
  27. data/test/virtualbox/attached_device_test.rb +92 -0
  28. data/test/virtualbox/command_test.rb +30 -0
  29. data/test/virtualbox/dvd_test.rb +58 -0
  30. data/test/virtualbox/ext/subclass_listing_test.rb +25 -0
  31. data/test/virtualbox/hard_drive_test.rb +161 -0
  32. data/test/virtualbox/image_test.rb +113 -0
  33. data/test/virtualbox/nic_test.rb +119 -0
  34. data/test/virtualbox/storage_controller_test.rb +79 -0
  35. data/test/virtualbox/vm_test.rb +313 -0
  36. 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