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.
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
data/.gitignore ADDED
@@ -0,0 +1,4 @@
1
+ bin/*
2
+ vendor/gems/*
3
+ doc/*
4
+ .yardoc/*
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