vbox-ng 0.1.3 → 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- data/Gemfile +2 -2
- data/Gemfile.lock +7 -1
- data/README.md +7 -8
- data/Rakefile +15 -5
- data/VERSION +1 -1
- data/lib/vbox-ng.rb +14 -2
- data/lib/vbox.rb +14 -2
- data/lib/vbox/cli.rb +208 -75
- data/lib/vbox/cmdlineapi.rb +136 -90
- data/lib/vbox/vm.rb +111 -11
- data/spec/cmdlineapi_spec.rb +52 -0
- data/spec/spec_helper.rb +46 -1
- data/spec/support/vboxmanage_simulator.rb +136 -0
- data/spec/support/vboxmanage_simulator.yml +1171 -0
- data/spec/vm_spec.rb +101 -5
- data/vbox-ng.gemspec +11 -5
- metadata +24 -5
data/Gemfile
CHANGED
data/Gemfile.lock
CHANGED
@@ -10,6 +10,7 @@ GEM
|
|
10
10
|
rake
|
11
11
|
rdoc
|
12
12
|
json (1.7.5)
|
13
|
+
multi_json (1.0.4)
|
13
14
|
rake (10.0.2)
|
14
15
|
rdoc (3.12)
|
15
16
|
json (~> 1.4)
|
@@ -21,12 +22,17 @@ GEM
|
|
21
22
|
rspec-expectations (2.12.0)
|
22
23
|
diff-lcs (~> 1.1.3)
|
23
24
|
rspec-mocks (2.12.0)
|
25
|
+
simplecov (0.7.1)
|
26
|
+
multi_json (~> 1.0)
|
27
|
+
simplecov-html (~> 0.7.1)
|
28
|
+
simplecov-html (0.7.1)
|
24
29
|
|
25
30
|
PLATFORMS
|
26
31
|
ruby
|
27
32
|
|
28
33
|
DEPENDENCIES
|
29
34
|
awesome_print (~> 1.1.0)
|
30
|
-
bundler (
|
35
|
+
bundler (>= 1.0.0)
|
31
36
|
jeweler (~> 1.8.4)
|
32
37
|
rspec (~> 2.12.0)
|
38
|
+
simplecov
|
data/README.md
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
vbox-ng
|
1
|
+
vbox-ng [![Build Status](https://secure.travis-ci.org/zed-0xff/vbox-ng.png)](http://secure.travis-ci.org/zed-0xff/vbox-ng) [![Dependency Status](https://gemnasium.com/zed-0xff/vbox-ng.png)](https://gemnasium.com/zed-0xff/vbox-ng)
|
2
2
|
======
|
3
3
|
|
4
4
|
Description
|
@@ -25,15 +25,14 @@ Commandline usage
|
|
25
25
|
acpipowerbutton, acpisleepbutton, clone, delete, show, snapshots
|
26
26
|
|
27
27
|
OPTIONS:
|
28
|
-
-g, --[no-]glob
|
29
|
-
|
30
|
-
|
31
|
-
plus additional pattern {1-20} which matches
|
32
|
-
a sequence of numbers: 1,2,3,...,19,20
|
28
|
+
-g, --[no-]glob assume <vm_name> is a wildcard & run on multiple VMs.
|
29
|
+
All glob(7) patterns are supported plus additional
|
30
|
+
pattern "{1-20}" - expands to a sequence: 1,2,3,...,19,20
|
33
31
|
-n, --dry-run do not change anything, just print commands to be invoked
|
34
32
|
-v, --verbose increase verbosity
|
35
|
-
-
|
33
|
+
-N, --clones N clone: make N clones
|
36
34
|
-S, --snapshot MODE clone: use LAST shapshot or make NEW
|
35
|
+
--name NAME clone: name for the clone VM
|
37
36
|
-H, --headless start: start VM in headless mode
|
38
37
|
-h, --help show this message
|
39
38
|
|
@@ -41,7 +40,7 @@ Commandline usage
|
|
41
40
|
vbox -v - list VMs with memory and dir sizes
|
42
41
|
vbox "d{1-10}" list - list only VMs named 'd1','d2','d3',...,'d10'
|
43
42
|
vbox "test*" start - start VMs which name starts with 'test'
|
44
|
-
vbox "v[ace]" cpus=2
|
43
|
+
vbox "v[ace]" cpus=2 acpi=on - set number of cpus & ACPI on VMs named 'va','vc','ve'
|
45
44
|
vbox d0 - list all parameters of VM named 'd0'
|
46
45
|
vbox d0 clone -c 10 -S last - make 10 new linked clones of vm 'd0' using the
|
47
46
|
latest hdd snapshot, if any
|
data/Rakefile
CHANGED
@@ -32,17 +32,27 @@ Jeweler::RubygemsDotOrgTasks.new
|
|
32
32
|
|
33
33
|
require 'rspec/core'
|
34
34
|
require 'rspec/core/rake_task'
|
35
|
+
|
35
36
|
RSpec::Core::RakeTask.new(:spec) do |spec|
|
36
37
|
spec.pattern = FileList['spec/**/*_spec.rb']
|
37
38
|
end
|
38
39
|
|
39
|
-
RSpec::Core::RakeTask.new(:rcov) do |spec|
|
40
|
-
spec.pattern = 'spec/**/*_spec.rb'
|
41
|
-
spec.rcov = true
|
42
|
-
end
|
43
|
-
|
44
40
|
task :default => :spec
|
45
41
|
|
42
|
+
namespace :spec do
|
43
|
+
desc "record VBoxManage simulation"
|
44
|
+
task :record do
|
45
|
+
ENV['RECORD_VBOXMANAGE'] = '1'
|
46
|
+
Rake::Task[:spec].invoke
|
47
|
+
end
|
48
|
+
|
49
|
+
desc "replay VBoxManage simulation"
|
50
|
+
task :replay do
|
51
|
+
ENV['SIMULATE_VBOXMANAGE'] = '1'
|
52
|
+
Rake::Task[:spec].execute
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
46
56
|
#require 'rdoc/task'
|
47
57
|
#Rake::RDocTask.new do |rdoc|
|
48
58
|
# version = File.exist?('VERSION') ? File.read('VERSION') : ""
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.
|
1
|
+
1.0.0
|
data/lib/vbox-ng.rb
CHANGED
@@ -5,7 +5,19 @@ module VBOX
|
|
5
5
|
# UUID_RE = /\{\h{8}-\h{4}-\h{4}-\h{4}-\h{12}\}/ # only in ruby 1.9 :(
|
6
6
|
UUID_RE = /\{[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}\}/i
|
7
7
|
|
8
|
-
|
9
|
-
|
8
|
+
@@verbosity = ENV['VBOX_DEBUG'].to_i
|
9
|
+
|
10
|
+
class << self
|
11
|
+
def api
|
12
|
+
@@api ||= CmdLineAPI.new
|
13
|
+
end
|
14
|
+
|
15
|
+
def verbosity
|
16
|
+
@@verbosity
|
17
|
+
end
|
18
|
+
|
19
|
+
def verbosity= v
|
20
|
+
@@verbosity = v
|
21
|
+
end
|
10
22
|
end
|
11
23
|
end
|
data/lib/vbox.rb
CHANGED
@@ -5,7 +5,19 @@ module VBOX
|
|
5
5
|
# UUID_RE = /\{\h{8}-\h{4}-\h{4}-\h{4}-\h{12}\}/ # only in ruby 1.9 :(
|
6
6
|
UUID_RE = /\{[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}\}/i
|
7
7
|
|
8
|
-
|
9
|
-
|
8
|
+
@@verbosity = ENV['VBOX_DEBUG'].to_i
|
9
|
+
|
10
|
+
class << self
|
11
|
+
def api
|
12
|
+
@@api ||= CmdLineAPI.new
|
13
|
+
end
|
14
|
+
|
15
|
+
def verbosity
|
16
|
+
@@verbosity
|
17
|
+
end
|
18
|
+
|
19
|
+
def verbosity= v
|
20
|
+
@@verbosity = v
|
21
|
+
end
|
10
22
|
end
|
11
23
|
end
|
data/lib/vbox/cli.rb
CHANGED
@@ -11,6 +11,7 @@ module VBOX
|
|
11
11
|
@argv = argv
|
12
12
|
end
|
13
13
|
|
14
|
+
private
|
14
15
|
def _join_by_width words, params = {}
|
15
16
|
params[:max_length] ||= 30
|
16
17
|
params[:separator] ||= ", "
|
@@ -28,6 +29,7 @@ module VBOX
|
|
28
29
|
lines.join params[:newline]
|
29
30
|
end
|
30
31
|
|
32
|
+
public
|
31
33
|
def banner
|
32
34
|
bname = File.basename($0)
|
33
35
|
r = []
|
@@ -56,7 +58,7 @@ module VBOX
|
|
56
58
|
r << %Q{\t#{bname} -v - list VMs with memory and dir sizes}
|
57
59
|
r << %Q{\t#{bname} "d{1-10}" list - list only VMs named 'd1','d2','d3',...,'d10'}
|
58
60
|
r << %Q{\t#{bname} "test*" start - start VMs which name starts with 'test'}
|
59
|
-
r << %Q{\t#{bname} "v[ace]" cpus=2
|
61
|
+
r << %Q{\t#{bname} "v[ace]" cpus=2 acpi=on - set number of cpus & ACPI on VMs named 'va','vc','ve'}
|
60
62
|
r << %Q{\t#{bname} d0 - list all parameters of VM named 'd0'}
|
61
63
|
r << %Q{\t#{bname} d0 clone -c 10 -S last - make 10 new linked clones of vm 'd0' using the}
|
62
64
|
r << %Q{\t#{space} latest hdd snapshot, if any}
|
@@ -70,17 +72,16 @@ module VBOX
|
|
70
72
|
r.join("\n")
|
71
73
|
end
|
72
74
|
|
73
|
-
def
|
75
|
+
def parse_argv
|
74
76
|
@options = { :verbose => 0 }
|
75
77
|
optparser = OptionParser.new do |opts|
|
76
78
|
opts.banner = banner
|
77
79
|
opts.summary_indent = "\t"
|
78
80
|
|
79
81
|
opts.on "-g", "--[no-]glob",
|
80
|
-
"
|
81
|
-
"
|
82
|
-
"
|
83
|
-
"plus additional pattern {1-20} which matches","a sequence of numbers: 1,2,3,...,19,20" do |x|
|
82
|
+
"assume <vm_name> is a wildcard & run on multiple VMs.",
|
83
|
+
"All glob(7) patterns are supported plus additional",
|
84
|
+
"pattern \"{1-20}\" - expands to a sequence: 1,2,3,...,19,20" do |x|
|
84
85
|
@options[:multiple] = x
|
85
86
|
end
|
86
87
|
opts.on "-n", "--dry-run", "do not change anything, just print commands to be invoked" do
|
@@ -90,13 +91,16 @@ module VBOX
|
|
90
91
|
@options[:verbose] ||= 0
|
91
92
|
@options[:verbose] += 1
|
92
93
|
end
|
93
|
-
opts.on "-
|
94
|
+
opts.on "-N", "--clones N", Integer, "clone: make N clones" do |x|
|
94
95
|
@options[:clones] = x
|
95
96
|
end
|
96
97
|
a = 'new last take make'.split.map{ |x| [x, x.upcase] }.flatten
|
97
98
|
opts.on "-S", "--snapshot MODE", a, "clone: use LAST shapshot or make NEW" do |x|
|
98
99
|
@options[:snapshot] = x.downcase
|
99
100
|
end
|
101
|
+
opts.on "--name NAME", "clone: name for the clone VM" do |x|
|
102
|
+
@options[:name] = x
|
103
|
+
end
|
100
104
|
opts.on "-H", "--headless", "start: start VM in headless mode" do
|
101
105
|
@options[:headless] = true
|
102
106
|
end
|
@@ -113,97 +117,226 @@ module VBOX
|
|
113
117
|
@options[:multiple] = "{#{@argv.first}}" !~ UUID_RE
|
114
118
|
end
|
115
119
|
|
116
|
-
|
120
|
+
VBOX.verbosity = @options[:verbose]
|
121
|
+
end
|
117
122
|
|
118
|
-
|
119
|
-
|
120
|
-
@vbox.list_vms(:running => true).each do |vm|
|
121
|
-
vms.find{ |vm1| vm1.uuid == vm.uuid }.state = :running
|
122
|
-
end
|
123
|
+
def list_vms name_or_glob
|
124
|
+
vms = _find_vms name_or_glob
|
123
125
|
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
126
|
+
longest = (vms.map(&:name).map(&:size)+[4]).max
|
127
|
+
|
128
|
+
puts "%-*s %5s %6s %-12s %s".gray % [longest, *%w'NAME MEM DIRSZ STATE UUID']
|
129
|
+
vms.each do |vm|
|
130
|
+
if @options[:verbose] > 0
|
131
|
+
vm.fetch_metadata
|
132
|
+
state = (vm.state == :poweroff) ? '' : vm.state.to_s.upcase
|
133
|
+
s = sprintf "%-*s %5d %6s %-12s %s", longest, vm.name, vm.memory_size, vm.dir_size,
|
134
|
+
state, vm.uuid
|
135
|
+
else
|
136
|
+
state = (vm.state == :poweroff) ? '' : vm.state.to_s.upcase
|
137
|
+
s = sprintf "%-*s %5s %6s %-12s %s", longest, vm.name, '', '',
|
138
|
+
state, vm.uuid
|
131
139
|
end
|
140
|
+
s = s.green if vm.state == :running
|
141
|
+
puts s
|
142
|
+
end
|
143
|
+
end
|
132
144
|
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
state, vm.uuid
|
142
|
-
else
|
143
|
-
state = (vm.state == :poweroff) ? '' : vm.state.to_s.upcase
|
144
|
-
s = sprintf "%-*s %5s %6s %-12s %s", longest, vm.name, '', '',
|
145
|
-
state, vm.uuid
|
146
|
-
end
|
147
|
-
s = s.green if vm.state == :running
|
148
|
-
puts s
|
145
|
+
def vm_cmd name_or_glob, cmd='show', *args
|
146
|
+
vms = _find_vms(name_or_glob)
|
147
|
+
if vms.empty?
|
148
|
+
if cmd == 'create'
|
149
|
+
return vm_cmd_create(name_or_glob)
|
150
|
+
else
|
151
|
+
STDERR.puts "[?] no VMs matching #{name_or_glob.inspect}".red
|
152
|
+
exit 1
|
149
153
|
end
|
150
|
-
|
151
|
-
name = @argv.shift
|
152
|
-
cmd = @argv.shift || 'show' # default command is 'show'
|
154
|
+
end
|
153
155
|
|
154
|
-
|
155
|
-
if
|
156
|
-
|
156
|
+
method =
|
157
|
+
if cmd['=']
|
158
|
+
# set some VM variables:
|
159
|
+
# vbox vm_name foo=bar bar=baz xxx=yyy
|
160
|
+
args.unshift(cmd)
|
161
|
+
"vm_cmd_set"
|
157
162
|
else
|
158
|
-
|
163
|
+
"vm_cmd_#{cmd}"
|
159
164
|
end
|
165
|
+
|
166
|
+
unless self.respond_to?(method)
|
167
|
+
STDERR.puts "[?] unknown command #{cmd.inspect}".red
|
168
|
+
exit 1
|
169
|
+
end
|
170
|
+
vms.each do |vm|
|
171
|
+
send method, vm, *args
|
160
172
|
end
|
161
173
|
end
|
162
174
|
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
175
|
+
SHOW_CATEGORIES = {
|
176
|
+
'GENERAL' => %w'name cpus memory vram cpuexecutioncap UUID VMState',
|
177
|
+
'VIRTUALIZATION OPTIONS' =>
|
178
|
+
[
|
179
|
+
/^(groups|ostype|hwvirt|nestedpag|largepag|vtxvpid|ioapic|pagefusion|hpet|synthcpu|pae)/,
|
180
|
+
/accelerate/, /balloon/i
|
181
|
+
],
|
182
|
+
'NET' => [ /^(nic|nat|mac|bridge|cable|hostonly)/, /^sock/, /^tcp/ ],
|
183
|
+
'STORAGE' => [ /storage/, /SATA/, /IDE/ ],
|
184
|
+
'SNAPSHOTS' => [ /snapshot/i ],
|
185
|
+
'VIRTUAL HARDWARE' => [ /^(lpt|uart|audio|ehci|usb|hardware|chipset|monitor|hid|acpi|firmware|USB)/ ],
|
186
|
+
'TELEPORTING' => [ /teleport/i, /cpuid/i ],
|
187
|
+
'SHARED FOLDERS' => [ /SharedFolder/ ]
|
188
|
+
}
|
189
|
+
|
190
|
+
public
|
191
|
+
def vm_cmd_show vm
|
192
|
+
vars = vm.metadata.dup
|
193
|
+
unless @options[:verbose] > 0
|
194
|
+
vars.delete_if{ |k,v| ["none","off","disabled","emptydrive","",0,"0",nil].include?(v) }
|
195
|
+
end
|
196
|
+
maxlen = vars.keys.map(&:size).max
|
197
|
+
|
198
|
+
SHOW_CATEGORIES.each do |name, filters|
|
199
|
+
keys = []; title = nil
|
200
|
+
filters.each do |filter|
|
201
|
+
keys += filter.is_a?(Regexp) ? vars.keys.find_all{ |key| key =~ filter } : [filter]
|
169
202
|
end
|
170
|
-
|
171
|
-
|
172
|
-
|
203
|
+
keys.each do |k|
|
204
|
+
if v = vars.delete(k)
|
205
|
+
puts (title = "--- #{name} ".ljust(80,'-')) unless title
|
206
|
+
printf(" %-*s: %s\n", maxlen, k, v)
|
207
|
+
end
|
208
|
+
end
|
209
|
+
end
|
210
|
+
puts "--- MISC ".ljust(80,'-')
|
211
|
+
vars.each do |k,v|
|
212
|
+
printf(" %-*s: %s\n", maxlen, k, v)
|
173
213
|
end
|
174
214
|
end
|
175
215
|
|
176
|
-
|
177
|
-
|
178
|
-
|
216
|
+
# create VM
|
217
|
+
def vm_cmd_create name
|
218
|
+
VM.new(:name => name).create!
|
219
|
+
end
|
220
|
+
|
221
|
+
# destroy VM
|
222
|
+
def vm_cmd_destroy vm
|
223
|
+
vm.destroy!
|
224
|
+
end
|
225
|
+
alias :vm_cmd_rm :vm_cmd_destroy
|
226
|
+
alias :vm_cmd_delete :vm_cmd_destroy
|
227
|
+
|
228
|
+
# set VM variables
|
229
|
+
def vm_cmd_set vm, *args
|
230
|
+
raise "all arguments must contain '='" unless args.all?{ |arg| arg['='] }
|
231
|
+
args.each do |arg|
|
232
|
+
k,v = arg.split("=",2)
|
233
|
+
vm.set_var k, v
|
179
234
|
end
|
180
|
-
|
235
|
+
vm.save
|
181
236
|
end
|
182
237
|
|
183
|
-
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
|
189
|
-
|
238
|
+
# start VM
|
239
|
+
def vm_cmd_start vm
|
240
|
+
vm.start! :headless => @options[:headless]
|
241
|
+
end
|
242
|
+
|
243
|
+
# pause VM
|
244
|
+
def vm_cmd_pause vm
|
245
|
+
vm.pause!
|
246
|
+
end
|
247
|
+
|
248
|
+
# resume VM
|
249
|
+
def vm_cmd_resume vm
|
250
|
+
vm.resume!
|
251
|
+
end
|
252
|
+
alias :vm_cmd_unpause :vm_cmd_resume
|
253
|
+
|
254
|
+
# reset VM
|
255
|
+
def vm_cmd_reset vm
|
256
|
+
vm.reset!
|
257
|
+
end
|
258
|
+
|
259
|
+
# save VM state
|
260
|
+
def vm_cmd_savestate vm
|
261
|
+
vm.savestate!
|
262
|
+
end
|
263
|
+
alias :vm_cmd_save_state :vm_cmd_savestate
|
264
|
+
|
265
|
+
# stop VM
|
266
|
+
def vm_cmd_poweroff vm
|
267
|
+
vm.poweroff!
|
268
|
+
end
|
269
|
+
alias :vm_cmd_stop :vm_cmd_poweroff
|
270
|
+
|
271
|
+
# ACPI 'Power' Button
|
272
|
+
def vm_cmd_acpipowerbutton vm
|
273
|
+
vm.acpipowerbutton!
|
274
|
+
end
|
275
|
+
|
276
|
+
# ACPI 'Sleep' Button
|
277
|
+
def vm_cmd_acpisleepbutton vm
|
278
|
+
vm.acpisleepbutton!
|
279
|
+
end
|
280
|
+
|
281
|
+
# clone VM
|
282
|
+
def vm_cmd_clone vm
|
283
|
+
# TODO: page fusion
|
284
|
+
unless @options[:snapshot]
|
285
|
+
puts "[!] please gimme --snapshot=LAST OR --snapshot=NEW option".red
|
286
|
+
exit 1
|
287
|
+
end
|
288
|
+
vm.clone! @options
|
289
|
+
end
|
290
|
+
|
291
|
+
# manage VM snapshots
|
292
|
+
def vm_cmd_snapshots vm, *args
|
293
|
+
vm.snapshots.each do |s|
|
294
|
+
printf "%s %s\n", s.uuid, s.name
|
190
295
|
end
|
191
296
|
end
|
297
|
+
alias :vm_cmd_snapshot :vm_cmd_snapshots
|
298
|
+
|
299
|
+
def run
|
300
|
+
parse_argv
|
301
|
+
# now @argv contains only VM name and commands, if any
|
192
302
|
|
193
|
-
|
194
|
-
|
195
|
-
|
196
|
-
|
197
|
-
#
|
198
|
-
@
|
199
|
-
|
200
|
-
|
201
|
-
|
303
|
+
if @argv.empty? || (@argv.size <= 2 && @argv.include?('list'))
|
304
|
+
# vbox
|
305
|
+
# vbox list
|
306
|
+
# vbox list "a*"
|
307
|
+
# vbox "a*" list
|
308
|
+
@argv.delete_at(@argv.index('list') || 999) # delete only 1st 'list' entry
|
309
|
+
list_vms @argv.first
|
310
|
+
elsif @argv.empty? || (@argv.size <= 2 && @argv.include?('ls'))
|
311
|
+
# vbox
|
312
|
+
# vbox ls
|
313
|
+
# vbox ls "a*"
|
314
|
+
# vbox "a*" ls
|
315
|
+
@argv.delete_at(@argv.index('ls') || 999) # delete only 1st 'ls' entry
|
316
|
+
list_vms @argv.first
|
317
|
+
else
|
318
|
+
# vbox VM
|
319
|
+
# vbox VM show
|
320
|
+
# vbox VM ...
|
321
|
+
# - where 'VM' can be vm name or glob or UUID
|
322
|
+
vm_cmd *@argv
|
323
|
+
end
|
324
|
+
end
|
325
|
+
|
326
|
+
private
|
327
|
+
|
328
|
+
def _find_vms name_or_glob
|
329
|
+
if name_or_glob
|
330
|
+
if @options[:multiple]
|
331
|
+
# glob
|
332
|
+
VM.find_all(name_or_glob)
|
333
|
+
else
|
334
|
+
# exact name
|
335
|
+
[ VM.find(name_or_glob) ]
|
202
336
|
end
|
203
337
|
else
|
204
|
-
|
205
|
-
|
206
|
-
exit 1
|
338
|
+
# all VMs
|
339
|
+
VM.all
|
207
340
|
end
|
208
341
|
end
|
209
342
|
end
|