vbox-ng 0.1.3 → 1.0.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/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 [](http://secure.travis-ci.org/zed-0xff/vbox-ng) [](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
|