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,31 @@
|
|
1
|
+
module VirtualBox
|
2
|
+
# Represents a DVD image stored by VirtualBox. These DVD images can be
|
3
|
+
# mounted onto virtual machines.
|
4
|
+
#
|
5
|
+
# # Finding all DVDs
|
6
|
+
#
|
7
|
+
# The only method at the moment of finding DVDs is to use {DVD.all}, which
|
8
|
+
# returns an array of {DVD}s.
|
9
|
+
#
|
10
|
+
# DVD.all
|
11
|
+
#
|
12
|
+
class DVD < Image
|
13
|
+
class <<self
|
14
|
+
# Returns an array of all available DVDs as DVD objects
|
15
|
+
def all
|
16
|
+
raw = Command.vboxmanage("list dvds")
|
17
|
+
parse_raw(raw)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
# Deletes the DVD from VBox managed list and also from disk.
|
22
|
+
# This method will fail if the disk is currently mounted to any
|
23
|
+
# virtual machine.
|
24
|
+
#
|
25
|
+
# @return [Boolean]
|
26
|
+
def destroy
|
27
|
+
Command.vboxmanage("closemedium dvd #{uuid} --delete")
|
28
|
+
return $?.to_i == 0
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
# From: http://snippets.dzone.com/posts/show/2992
|
2
|
+
module VirtualBox::SubclassListing
|
3
|
+
def self.included(base)
|
4
|
+
base.extend ClassMethods
|
5
|
+
end
|
6
|
+
|
7
|
+
module ClassMethods
|
8
|
+
def subclasses(direct = false)
|
9
|
+
classes = []
|
10
|
+
if direct
|
11
|
+
ObjectSpace.each_object(Class) do |c|
|
12
|
+
next unless c.superclass == self
|
13
|
+
classes << c
|
14
|
+
end
|
15
|
+
else
|
16
|
+
ObjectSpace.each_object(Class) do |c|
|
17
|
+
next unless c.ancestors.include?(self) and (c != self)
|
18
|
+
classes << c
|
19
|
+
end
|
20
|
+
end
|
21
|
+
classes
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,169 @@
|
|
1
|
+
module VirtualBox
|
2
|
+
# Represents a hard disk which is registered with VirtualBox.
|
3
|
+
#
|
4
|
+
# # Finding a Hard Drive
|
5
|
+
#
|
6
|
+
# Hard drives can be found use {HardDrive.all} and {HardDrive.find}, which
|
7
|
+
# find all or a specific hard drive, respectively. Example below:
|
8
|
+
#
|
9
|
+
# VirtualBox::HardDrive.all
|
10
|
+
#
|
11
|
+
# Or:
|
12
|
+
#
|
13
|
+
# VirtualBox::HardDrive.find("Foo.vdi")
|
14
|
+
#
|
15
|
+
# # Creating a Hard Drive
|
16
|
+
#
|
17
|
+
# The hard drive is one of the few data items at the moment which supports
|
18
|
+
# new creation. Below is a simple example of how this works:
|
19
|
+
#
|
20
|
+
# hd = VirtualBox::HardDrive.new
|
21
|
+
# hd.location = "foo.vdi"
|
22
|
+
# hd.size = 2400 # in megabytes
|
23
|
+
# hd.save
|
24
|
+
#
|
25
|
+
# # You can now access other attributes, since its saved:
|
26
|
+
# hd.uuid
|
27
|
+
# hd.location # will return a full path now
|
28
|
+
#
|
29
|
+
# Although `VDI` is the default format for newly created hard drives, other
|
30
|
+
# formats are supported:
|
31
|
+
#
|
32
|
+
# hd = VirtualBox::HardDrive.new
|
33
|
+
# hd.format = "VMDK"
|
34
|
+
# hd.location = "bar.vmdk"
|
35
|
+
# hd.size = 9001 # Its over 9000! (If you don't understand the reference, just ignore this comment)
|
36
|
+
# hd.save
|
37
|
+
#
|
38
|
+
# Any formats listed by your VirtualBox installation's `VBoxManage list hddbackends` command
|
39
|
+
# can be used with the virtualbox gem.
|
40
|
+
#
|
41
|
+
# # Destroying Hard Drives
|
42
|
+
#
|
43
|
+
# Hard drives can also be deleted, which will completely remove them from
|
44
|
+
# disk. **This operation is not reversable**.
|
45
|
+
#
|
46
|
+
# hd = VirtualBox::HardDrive.find("foo")
|
47
|
+
# hd.destroy
|
48
|
+
#
|
49
|
+
# # Cloning Hard Drives
|
50
|
+
#
|
51
|
+
# Hard drives can just as easily be cloned as they can be created or destroyed.
|
52
|
+
#
|
53
|
+
# hd = VirtualBox::HardDrive.find("foo")
|
54
|
+
# cloned_hd = hd.clone("bar")
|
55
|
+
#
|
56
|
+
# In addition to simply cloning hard drives, this command can be used to
|
57
|
+
# clone to a different format:
|
58
|
+
#
|
59
|
+
# hd = VirtualBox::HardDrive.find("foo")
|
60
|
+
# hd.clone("bar", "VMDK") # Will clone and convert to VMDK format
|
61
|
+
#
|
62
|
+
# # Attributes
|
63
|
+
#
|
64
|
+
# Properties of the model are exposed using standard ruby instance
|
65
|
+
# methods which are generated on the fly. Because of this, they are not listed
|
66
|
+
# below as available instance methods.
|
67
|
+
#
|
68
|
+
# These attributes can be accessed and modified via standard ruby-style
|
69
|
+
# `instance.attribute` and `instance.attribute=` methods. The attributes are
|
70
|
+
# listed below. If you aren't sure what this means or you can't understand
|
71
|
+
# why the below is listed, please read {Attributable}.
|
72
|
+
#
|
73
|
+
# attribute :uuid, :readonly => true
|
74
|
+
# attribute :location
|
75
|
+
# attribute :accessible, :readonly => true
|
76
|
+
# attribute :format, :default => "VDI"
|
77
|
+
# attribute :size
|
78
|
+
#
|
79
|
+
class HardDrive < Image
|
80
|
+
attribute :format, :default => "VDI"
|
81
|
+
attribute :size
|
82
|
+
|
83
|
+
class <<self
|
84
|
+
# Returns an array of all available hard drives as HardDrive
|
85
|
+
# objects.
|
86
|
+
#
|
87
|
+
# @return [Array<HardDrive>]
|
88
|
+
def all
|
89
|
+
raw = Command.vboxmanage("list hdds")
|
90
|
+
parse_blocks(raw).collect { |v| find(v[:uuid]) }
|
91
|
+
end
|
92
|
+
|
93
|
+
# Finds one specific hard drive by UUID or file name. If the
|
94
|
+
# hard drive can not be found, will return `nil`.
|
95
|
+
#
|
96
|
+
# @return [HardDrive]
|
97
|
+
def find(id)
|
98
|
+
raw = Command.vboxmanage("showhdinfo #{id}")
|
99
|
+
|
100
|
+
# Return nil if the hard drive doesn't exist
|
101
|
+
return nil if raw =~ /VERR_FILE_NOT_FOUND/
|
102
|
+
|
103
|
+
data = raw.split(/\n\n/).collect { |v| parse_block(v) }.find { |v| !v.nil? }
|
104
|
+
|
105
|
+
# Set equivalent fields
|
106
|
+
data[:format] = data[:"storage format"]
|
107
|
+
data[:size] = data[:"logical size"].split(/\s+/)[0] if data.has_key?(:"logical size")
|
108
|
+
|
109
|
+
# Return new object
|
110
|
+
new(data)
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
114
|
+
# Clone hard drive, possibly also converting formats. All formats
|
115
|
+
# supported by your local VirtualBox installation are supported
|
116
|
+
# here. If no format is specified, the format of the source drive
|
117
|
+
# will be used.
|
118
|
+
#
|
119
|
+
# @param [String] outputfile The output file. This should be just a
|
120
|
+
# single filename, since VirtualBox will place it in the hard
|
121
|
+
# drives folder.
|
122
|
+
# @param [String] format The format to convert to.
|
123
|
+
# @return [HardDrive] The new, cloned hard drive.
|
124
|
+
def clone(outputfile, format="VDI")
|
125
|
+
raw = Command.vboxmanage("clonehd #{uuid} #{Command.shell_escape(outputfile)} --format #{format} --remember")
|
126
|
+
return nil unless raw =~ /UUID: (.+?)$/
|
127
|
+
|
128
|
+
self.class.find($1.to_s)
|
129
|
+
end
|
130
|
+
|
131
|
+
# Creates a new hard drive.
|
132
|
+
#
|
133
|
+
# **This method should NEVER be called. Call {#save} instead.**
|
134
|
+
def create
|
135
|
+
raw = Command.vboxmanage("createhd --filename #{location} --size #{size} --format #{read_attribute(:format)} --remember")
|
136
|
+
return nil unless raw =~ /UUID: (.+?)$/
|
137
|
+
|
138
|
+
# Just replace our attributes with the newly created ones. This also
|
139
|
+
# will set new_record to false.
|
140
|
+
populate_attributes(self.class.find($1.to_s).attributes)
|
141
|
+
end
|
142
|
+
|
143
|
+
# Saves the hard drive object. If the hard drive is new,
|
144
|
+
# this will create a new hard drive. Otherwise, it will
|
145
|
+
# save any other details about the existing hard drive.
|
146
|
+
#
|
147
|
+
# Currently, **saving existing hard drives does nothing**.
|
148
|
+
# This is a limitation of VirtualBox, rather than the library itself.
|
149
|
+
def save
|
150
|
+
if new_record?
|
151
|
+
# Create a new hard drive
|
152
|
+
create
|
153
|
+
else
|
154
|
+
super
|
155
|
+
end
|
156
|
+
end
|
157
|
+
|
158
|
+
# Destroys the hard drive. This deletes the hard drive off
|
159
|
+
# of disk.
|
160
|
+
#
|
161
|
+
# **This operation is not reversable.**
|
162
|
+
#
|
163
|
+
# @return [Boolean]
|
164
|
+
def destroy
|
165
|
+
Command.vboxmanage("closemedium disk #{uuid} --delete")
|
166
|
+
return $?.to_i == 0
|
167
|
+
end
|
168
|
+
end
|
169
|
+
end
|
@@ -0,0 +1,94 @@
|
|
1
|
+
require 'virtualbox/ext/subclass_listing'
|
2
|
+
|
3
|
+
module VirtualBox
|
4
|
+
# An abstract class which encapsulates the shared behaviour of
|
5
|
+
# images such as {HardDrive} and {DVD}.
|
6
|
+
#
|
7
|
+
# ## Attributes
|
8
|
+
#
|
9
|
+
# All images expose the following attributes. If you don't know how to read
|
10
|
+
# this than read {Attributable}.
|
11
|
+
#
|
12
|
+
# attribute :uuid, :readonly => true
|
13
|
+
# attribute :location
|
14
|
+
# attribute :accessible, :readonly => true
|
15
|
+
#
|
16
|
+
# @abstract
|
17
|
+
class Image < AbstractModel
|
18
|
+
include SubclassListing
|
19
|
+
|
20
|
+
attribute :uuid, :readonly => true
|
21
|
+
attribute :location
|
22
|
+
attribute :accessible, :readonly => true
|
23
|
+
|
24
|
+
class <<self
|
25
|
+
# Parses the raw output of virtualbox into image objects. Used by
|
26
|
+
# subclasses to parse the output of their respective listing functions.
|
27
|
+
#
|
28
|
+
# **This method typically won't be used except internally.**
|
29
|
+
#
|
30
|
+
# @return [Array<Image>]
|
31
|
+
def parse_raw(raw)
|
32
|
+
parse_blocks(raw).collect { |v| new(v) }
|
33
|
+
end
|
34
|
+
|
35
|
+
# Parses the blocks of the output from virtualbox. VirtualBox outputs
|
36
|
+
# image listing in "blocks" which are then parsed down to their attributes.
|
37
|
+
#
|
38
|
+
# **This method typically won't be used except internally.**
|
39
|
+
#
|
40
|
+
# @return [Array<Hash>]
|
41
|
+
def parse_blocks(raw)
|
42
|
+
raw.split(/\n\n/).collect { |v| parse_block(v.chomp) }.compact
|
43
|
+
end
|
44
|
+
|
45
|
+
# Parses a single block from VirtualBox output.
|
46
|
+
#
|
47
|
+
# **This method typically won't be used except internally.**
|
48
|
+
#
|
49
|
+
# @return [Hash]
|
50
|
+
def parse_block(block)
|
51
|
+
return nil unless block =~ /^UUID:/i
|
52
|
+
hd = {}
|
53
|
+
|
54
|
+
# Parses each line which should be in the format:
|
55
|
+
# KEY: VALUE
|
56
|
+
block.lines.each do |line|
|
57
|
+
next unless line =~ /^(.+?):\s+(.+?)$/
|
58
|
+
hd[$1.downcase.to_sym] = $2.to_s
|
59
|
+
end
|
60
|
+
|
61
|
+
# If we don't have a location but have a path, use that, as they
|
62
|
+
# are equivalent but not consistent.
|
63
|
+
hd[:location] = hd[:path] if hd.has_key?(:path)
|
64
|
+
|
65
|
+
hd
|
66
|
+
end
|
67
|
+
|
68
|
+
# Searches the subclasses which implement all method, searching for
|
69
|
+
# a matching UUID and returning that as the relationship.
|
70
|
+
#
|
71
|
+
# **This method typically won't be used except internally.**
|
72
|
+
#
|
73
|
+
# @return [Array<Image>]
|
74
|
+
def populate_relationship(caller, data)
|
75
|
+
return nil if data[:uuid].nil?
|
76
|
+
|
77
|
+
subclasses.each do |subclass|
|
78
|
+
next unless subclass.respond_to?(:all)
|
79
|
+
|
80
|
+
matching = subclass.all.find { |obj| obj.uuid == data[:uuid] }
|
81
|
+
return matching unless matching.nil?
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
# **This should never be called directly on {Image}.** Instead, initialize
|
87
|
+
# one of the subclasses.
|
88
|
+
def initialize(info=nil)
|
89
|
+
super()
|
90
|
+
|
91
|
+
populate_attributes(info) if info
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
@@ -0,0 +1,150 @@
|
|
1
|
+
module VirtualBox
|
2
|
+
# Represents a single NIC (Network Interface Card) of a virtual machine.
|
3
|
+
#
|
4
|
+
# **Currently, new NICs can't be created, so the only way to get this
|
5
|
+
# object is through a {VM}'s `nics` relationship.**
|
6
|
+
#
|
7
|
+
# # Editing a NIC
|
8
|
+
#
|
9
|
+
# Nics can be modified directly in their relationship to other
|
10
|
+
# virtual machines. When {VM#save} is called, it will also save any
|
11
|
+
# changes to its relationships.
|
12
|
+
#
|
13
|
+
# vm = VirtualBox::VM.find("foo")
|
14
|
+
# vm.nics[0].macaddress = @new_mac_address
|
15
|
+
# vm.save
|
16
|
+
#
|
17
|
+
# # Attributes
|
18
|
+
#
|
19
|
+
# Properties of the model are exposed using standard ruby instance
|
20
|
+
# methods which are generated on the fly. Because of this, they are not listed
|
21
|
+
# below as available instance methods.
|
22
|
+
#
|
23
|
+
# These attributes can be accessed and modified via standard ruby-style
|
24
|
+
# `instance.attribute` and `instance.attribute=` methods. The attributes are
|
25
|
+
# listed below. If you aren't sure what this means or you can't understand
|
26
|
+
# why the below is listed, please read {Attributable}.
|
27
|
+
#
|
28
|
+
# attribute :parent, :readonly => :readonly
|
29
|
+
# attribute :nic
|
30
|
+
# attribute :nictype
|
31
|
+
# attribute :macaddress
|
32
|
+
# attribute :cableconnected
|
33
|
+
# attribute :bridgeadapter
|
34
|
+
#
|
35
|
+
class Nic < AbstractModel
|
36
|
+
attribute :parent, :readonly => :readonly
|
37
|
+
attribute :nic
|
38
|
+
attribute :nictype
|
39
|
+
attribute :macaddress
|
40
|
+
attribute :cableconnected
|
41
|
+
attribute :bridgeadapter
|
42
|
+
|
43
|
+
class <<self
|
44
|
+
# Retrives the Nic data from human-readable vminfo. Since some data about
|
45
|
+
# nics is not exposed in the machine-readable virtual machine info, some
|
46
|
+
# extra parsing must be done to get these attributes. This method parses
|
47
|
+
# the nic-specific data from this human readable information.
|
48
|
+
#
|
49
|
+
# **This method typically won't be used except internally.**
|
50
|
+
#
|
51
|
+
# @return [Hash]
|
52
|
+
def nic_data(vmname)
|
53
|
+
raw = VM.human_info(vmname)
|
54
|
+
|
55
|
+
# Complicated chain of methods just maps parse_nic over each line,
|
56
|
+
# removing invalid ones, and then converting it into a single hash.
|
57
|
+
raw.lines.collect { |v| parse_nic(v) }.compact.inject({}) do |acc, obj|
|
58
|
+
acc.merge({ obj[0] => obj[1] })
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
# Parses nic data out of a single line of the human readable output
|
63
|
+
# of vm info.
|
64
|
+
#
|
65
|
+
# **This method typically won't be used except internally.**
|
66
|
+
#
|
67
|
+
# @return [Array] First element is nic name, second is data.
|
68
|
+
def parse_nic(raw)
|
69
|
+
return unless raw =~ /^NIC\s(\d):\s+(.+?)$/
|
70
|
+
return if $2.to_s.strip == "disabled"
|
71
|
+
|
72
|
+
data = {}
|
73
|
+
nicname = "nic#{$1}"
|
74
|
+
$2.to_s.split(/,\s+/).each do |raw_property|
|
75
|
+
next unless raw_property =~ /^(.+?):\s+(.+?)$/
|
76
|
+
|
77
|
+
data[$1.downcase.to_sym] = $2.to_s
|
78
|
+
end
|
79
|
+
|
80
|
+
return nicname.to_sym, data
|
81
|
+
end
|
82
|
+
|
83
|
+
# Populates the nic relationship for anything which is related to it.
|
84
|
+
#
|
85
|
+
# **This method typically won't be used except internally.**
|
86
|
+
#
|
87
|
+
# @return [Array<Nic>]
|
88
|
+
def populate_relationship(caller, data)
|
89
|
+
nic_data = nic_data(caller.name)
|
90
|
+
|
91
|
+
relation = []
|
92
|
+
|
93
|
+
counter = 1
|
94
|
+
loop do
|
95
|
+
break unless data["nic#{counter}".to_sym]
|
96
|
+
|
97
|
+
nictype = nic_data["nic#{counter}".to_sym][:type] rescue nil
|
98
|
+
|
99
|
+
nic = new(counter, caller, data.merge({
|
100
|
+
"nictype#{counter}".to_sym => nictype
|
101
|
+
}))
|
102
|
+
relation.push(nic)
|
103
|
+
counter += 1
|
104
|
+
end
|
105
|
+
|
106
|
+
relation
|
107
|
+
end
|
108
|
+
|
109
|
+
# Saves the relationship. This simply calls {#save} on every
|
110
|
+
# member of the relationship.
|
111
|
+
#
|
112
|
+
# **This method typically won't be used except internally.**
|
113
|
+
def save_relationship(caller, data)
|
114
|
+
# Just call save on each nic with the VM
|
115
|
+
data.each do |nic|
|
116
|
+
nic.save(caller.name)
|
117
|
+
end
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
121
|
+
# Since there is currently no way to create a _new_ nic, this is
|
122
|
+
# only used internally. Developers should NOT try to initialize their
|
123
|
+
# own nic objects.
|
124
|
+
def initialize(index, caller, data)
|
125
|
+
super()
|
126
|
+
|
127
|
+
@index = index
|
128
|
+
|
129
|
+
# Setup the index specific attributes
|
130
|
+
populate_data = {}
|
131
|
+
self.class.attributes.each do |name, options|
|
132
|
+
value = data["#{name}#{index}".to_sym]
|
133
|
+
populate_data[name] = value
|
134
|
+
end
|
135
|
+
|
136
|
+
populate_attributes(populate_data.merge({
|
137
|
+
:parent => caller
|
138
|
+
}))
|
139
|
+
end
|
140
|
+
|
141
|
+
# Saves a single attribute of the nic. This method is automatically
|
142
|
+
# called on {#save}.
|
143
|
+
#
|
144
|
+
# **This method typically won't be used except internally.**
|
145
|
+
def save_attribute(key, value, vmname)
|
146
|
+
Command.vboxmanage("modifyvm #{vmname} --#{key}#{@index} #{Command.shell_escape(value)}")
|
147
|
+
super
|
148
|
+
end
|
149
|
+
end
|
150
|
+
end
|