vbox-ng 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,5 @@
1
+ lib/**/*.rb
2
+ bin/*
3
+ -
4
+ features/**/*.feature
5
+ LICENSE.txt
data/.rspec ADDED
@@ -0,0 +1 @@
1
+ --color
data/Gemfile ADDED
@@ -0,0 +1,16 @@
1
+ source "http://rubygems.org"
2
+ # Add dependencies required to use your gem here.
3
+ # Example:
4
+ # gem "activesupport", ">= 2.3.5"
5
+
6
+ gem "awesome_print", "~> 1.1.0"
7
+
8
+ # Add dependencies to develop your gem here.
9
+ # Include everything needed to run rake, tests, features, etc.
10
+ group :development do
11
+ gem "rspec", "~> 2.12.0"
12
+ # gem "rdoc", "~> 3.12"
13
+ gem "bundler", "~> 1.0.0"
14
+ gem "jeweler", "~> 1.8.4"
15
+ # gem "rcov", ">= 0"
16
+ end
@@ -0,0 +1,32 @@
1
+ GEM
2
+ remote: http://rubygems.org/
3
+ specs:
4
+ awesome_print (1.1.0)
5
+ diff-lcs (1.1.3)
6
+ git (1.2.5)
7
+ jeweler (1.8.4)
8
+ bundler (~> 1.0)
9
+ git (>= 1.2.5)
10
+ rake
11
+ rdoc
12
+ json (1.7.5)
13
+ rake (10.0.2)
14
+ rdoc (3.12)
15
+ json (~> 1.4)
16
+ rspec (2.12.0)
17
+ rspec-core (~> 2.12.0)
18
+ rspec-expectations (~> 2.12.0)
19
+ rspec-mocks (~> 2.12.0)
20
+ rspec-core (2.12.1)
21
+ rspec-expectations (2.12.0)
22
+ diff-lcs (~> 1.1.3)
23
+ rspec-mocks (2.12.0)
24
+
25
+ PLATFORMS
26
+ ruby
27
+
28
+ DEPENDENCIES
29
+ awesome_print (~> 1.1.0)
30
+ bundler (~> 1.0.0)
31
+ jeweler (~> 1.8.4)
32
+ rspec (~> 2.12.0)
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2012 Andrey "Zed" Zaikin
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,48 @@
1
+ vbox-ng
2
+ ======
3
+
4
+ Description
5
+ -----------
6
+ A comfortable way of doing common VirtualBox tasks.
7
+
8
+ Installation
9
+ ------------
10
+ gem install vbox-ng
11
+
12
+ Usage
13
+ -----
14
+
15
+ # vbox -h
16
+
17
+ USAGE:
18
+ vbox [options] - list VMs
19
+ vbox [options] <vm_name> - show VM params
20
+ vbox [options] <vm_name> <param>=<value> - change VM params (name, cpus, usb, etc)
21
+ vbox [options] <vm_name> <command> - make some action (start, reset, etc) on VM
22
+
23
+ COMMANDS:
24
+ acpipowerbutton
25
+ acpisleepbutton
26
+ clone
27
+ delete
28
+ pause
29
+ poweroff
30
+ reset
31
+ resume
32
+ savestate
33
+ show
34
+ snapshots
35
+ start
36
+
37
+ OPTIONS:
38
+ -m, --[no-]multiple (default: auto) assume <vm_name> is a wildcard,
39
+ and run on multiple VMs.
40
+ All glob(7) patterns like *,?,[a-z] are supported
41
+ plus additional pattern {1-20} which matches
42
+ a sequence of numbers: 1,2,3,...,19,20
43
+ -n, --dry-run do not change anything, just print commands to be invoked
44
+ -v, --verbose increase verbosity
45
+ -c, --clones N clone: make N clones
46
+ -s, --snapshot MODE clone: use LAST shapshot or make NEW
47
+ -H, --headless start: start VM in headless mode
48
+ -h, --help show this message
@@ -0,0 +1,75 @@
1
+ # encoding: utf-8
2
+
3
+ require 'rubygems'
4
+ require 'bundler'
5
+ begin
6
+ Bundler.setup(:default, :development)
7
+ rescue Bundler::BundlerError => e
8
+ $stderr.puts e.message
9
+ $stderr.puts "Run `bundle install` to install missing gems"
10
+ exit e.status_code
11
+ end
12
+ require 'rake'
13
+
14
+ require 'jeweler'
15
+ Jeweler::Tasks.new do |gem|
16
+ # gem is a Gem::Specification... see http://docs.rubygems.org/read/chapter/20 for more options
17
+ gem.name = "vbox-ng"
18
+ gem.homepage = "http://github.com/zed-0xff/vbox-ng"
19
+ gem.license = "MIT"
20
+ gem.summary = %Q{A comfortable way of doing common VirtualBox tasks.}
21
+ gem.description = %Q{Create/start/stop/reboot/modify dozens of VirtualBox VMS with one short command.}
22
+ gem.email = "zed.0xff@gmail.com"
23
+ gem.authors = ["Andrey \"Zed\" Zaikin"]
24
+ # dependencies defined in Gemfile
25
+
26
+ gem.executables = %w'vbox'
27
+ gem.files.include "lib/**/*.rb"
28
+ gem.files.exclude "samples/*", "README.md.tpl"
29
+ gem.extra_rdoc_files.exclude "README.md.tpl"
30
+ end
31
+ Jeweler::RubygemsDotOrgTasks.new
32
+
33
+ require 'rspec/core'
34
+ require 'rspec/core/rake_task'
35
+ RSpec::Core::RakeTask.new(:spec) do |spec|
36
+ spec.pattern = FileList['spec/**/*_spec.rb']
37
+ end
38
+
39
+ RSpec::Core::RakeTask.new(:rcov) do |spec|
40
+ spec.pattern = 'spec/**/*_spec.rb'
41
+ spec.rcov = true
42
+ end
43
+
44
+ task :default => :spec
45
+
46
+ #require 'rdoc/task'
47
+ #Rake::RDocTask.new do |rdoc|
48
+ # version = File.exist?('VERSION') ? File.read('VERSION') : ""
49
+ #
50
+ # rdoc.rdoc_dir = 'rdoc'
51
+ # rdoc.title = "vbox-ng #{version}"
52
+ # rdoc.rdoc_files.include('README*')
53
+ # rdoc.rdoc_files.include('lib/**/*.rb')
54
+ #end
55
+
56
+ desc "build readme"
57
+ task :readme do
58
+ require 'erb'
59
+ tpl = File.read('README.md.tpl').gsub(/^%\s+(.+)/) do |x|
60
+ x.sub! /^%/,''
61
+ "<%= run(\"#{x}\") %>"
62
+ end
63
+ def run cmd
64
+ cmd.strip!
65
+ puts "[.] #{cmd} ..."
66
+ r = " # #{cmd}\n\n"
67
+ cmd.sub! /^vbox/,"./bin/vbox"
68
+ lines = `#{cmd}`.sub(/\A\n+/m,'').sub(/\s+\Z/,'').split("\n")
69
+ lines = lines[0,25] + ['...'] if lines.size > 50
70
+ r << lines.map{|x| " #{x}"}.join("\n")
71
+ r << "\n"
72
+ end
73
+ result = ERB.new(tpl,nil,'%>').result
74
+ File.open('README.md','w'){ |f| f << result }
75
+ end
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.0.1
@@ -0,0 +1,7 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ $LOAD_PATH.unshift(File.expand_path(File.dirname(__FILE__) + '/../lib'))
4
+ require 'vbox'
5
+ require 'vbox/cli'
6
+
7
+ VBOX::CLI.new.run
File without changes
@@ -0,0 +1,88 @@
1
+ #module VBOX
2
+ # class API41
3
+ # def initialize
4
+ # require 'virtualbox'
5
+ # end
6
+ #
7
+ # def list_vms
8
+ # vms = VirtualBox::VM.all.sort_by(&:name)
9
+ #
10
+ # longest = vms.map(&:name).map(&:size).max
11
+ #
12
+ # puts "%-*s %5s %6s".gray % [longest, *%w'NAME MEM DIRSZ']
13
+ # vms.each do |vm|
14
+ # s = `du -s -BM "#{File.dirname(vm.settings_file_path)}"`
15
+ # size = s.split("\t").first.tr("M","")
16
+ #
17
+ # s = sprintf "%-*s %5d %6s", longest, vm.name, vm.memory_size, size
18
+ # s = "#{s} RUNNING".green if vm.state == :running
19
+ # puts s
20
+ # end
21
+ # end
22
+ #
23
+ # def show_vm_info name
24
+ # vm = VirtualBox::VM.find name
25
+ # unless vm
26
+ # STDERR.puts "[!] cannot find vm #{name.inspect}".red
27
+ # exit 1
28
+ # end
29
+ #
30
+ # h = {}
31
+ # vm.methods.each do |m|
32
+ # next if m.size < 4 || m !~ /=$/ || !vm.respond_to?(m1=m[0..-2])
33
+ # r = nil
34
+ # begin
35
+ # r = vm.send(m1)
36
+ # rescue VirtualBox::Exceptions::UnsupportedVersionException
37
+ # end
38
+ # h[m1] = r unless r.nil? || r == ""
39
+ # end
40
+ #
41
+ # @longest = h.map{ |k,v| v == false ? 0 : k.size }.max + 1
42
+ # h.each do |k,v|
43
+ # next if v == false
44
+ # printf "%*s: ", @longest, k
45
+ # case v
46
+ # when String, Numeric, TrueClass, FalseClass, Symbol
47
+ # puts v
48
+ # when VirtualBox::AbstractModel
49
+ # p _attrs(v)
50
+ # else
51
+ # _print_value k,v
52
+ # end
53
+ # end
54
+ # end
55
+ #
56
+ # def _print_value k,v
57
+ # newline = "\n "+" "*@longest
58
+ # case k.to_sym
59
+ # when :boot_order, :extra_data
60
+ # p v
61
+ # when :shared_folders
62
+ # puts v.map{ |f| _attrs(f).inspect }.join(newline)
63
+ # when :network_adapters
64
+ # puts v.find_all{ |x| x.enabled }.map{ |x| _attrs(x).inspect }.join(newline)
65
+ # when :medium_attachments
66
+ # v.each_with_index do |ma, idx|
67
+ # print newline if idx > 0
68
+ # print _attrs(ma)
69
+ # if ma.medium
70
+ # print newline + " "
71
+ # p _attrs(ma.medium)
72
+ # end
73
+ # end
74
+ # when :storage_controllers
75
+ # puts v.map{ |f| _attrs(f).inspect }.join(newline)
76
+ # else
77
+ # puts v.class.to_s.red
78
+ # end
79
+ # end
80
+ #
81
+ # def _attrs x
82
+ # return nil unless x.respond_to?(:attributes)
83
+ # x.attributes.reject{ |k,v|
84
+ # [:parent, :interface].include?(k) || v == "" || v.to_s['VirtualBox::']
85
+ # }
86
+ # end
87
+ # end
88
+ #end
@@ -0,0 +1,411 @@
1
+ #!/usr/bin/env ruby
2
+ require 'awesome_print'
3
+ require 'optparse'
4
+
5
+ # for natural sort order
6
+ # http://stackoverflow.com/questions/4078906/is-there-a-natural-sort-by-method-for-ruby
7
+ class String
8
+ def naturalized
9
+ scan(/[^\d]+|\d+/).collect { |f| f.match(/\d+/) ? f.to_i : f }
10
+ end
11
+ end
12
+
13
+ module VBOX
14
+
15
+ COMMANDS = %w'start pause resume reset poweroff savestate acpipowerbutton acpisleepbutton clone delete show'
16
+ ALIASES = {'clonevm'=>'clone', 'destroy'=>'delete', 'rm'=>'delete'}
17
+
18
+ UUID_RE = /\{\h{8}-\h{4}-\h{4}-\h{4}-\h{12}\}/
19
+
20
+ class CLI
21
+ def initialize argv = ARGV
22
+ @argv = argv
23
+ end
24
+
25
+ def banner
26
+ bname = File.basename(__FILE__)
27
+ r = []
28
+ r << "USAGE:"
29
+ r << "\t#{bname} [options] - list VMs"
30
+ r << "\t#{bname} [options] <vm_name> - show VM params"
31
+ r << "\t#{bname} [options] <vm_name> <param>=<value> - change VM params (name, cpus, usb, etc)"
32
+ r << "\t#{bname} [options] <vm_name> <command> - make some action (start, reset, etc) on VM"
33
+
34
+ r << ""
35
+ r << "COMMANDS:"
36
+ (COMMANDS+['snapshots']).sort.each do |c|
37
+ r << "\t#{c}"
38
+ end
39
+ r << ""
40
+ r << "OPTIONS:"
41
+ r.join("\n")
42
+ end
43
+
44
+ def run
45
+ @options = { :verbose => 0 }
46
+ optparser = OptionParser.new do |opts|
47
+ opts.banner = banner
48
+
49
+ opts.on "-m", "--[no-]multiple",
50
+ "(default: auto) assume <vm_name> is a wildcard,",
51
+ "and run on multiple VMs.",
52
+ "All glob(7) patterns like *,?,[a-z] are supported",
53
+ "plus additional pattern {1-20} which matches","a sequence of numbers: 1,2,3,...,19,20" do |x|
54
+ @options[:multiple] = x
55
+ end
56
+ opts.on "-n", "--dry-run", "do not change anything, just print commands to be invoked" do
57
+ @options[:dry_run] = true
58
+ end
59
+ opts.on "-v", "--verbose", "increase verbosity" do
60
+ @options[:verbose] ||= 0
61
+ @options[:verbose] += 1
62
+ end
63
+ opts.on "-c", "--clones N", Integer, "clone: make N clones" do |x|
64
+ @options[:clones] = x
65
+ end
66
+ a = 'new last take make'.split.map{ |x| [x, x.upcase] }.flatten
67
+ opts.on "-snapshot", "--snapshot MODE", a, "clone: use LAST shapshot or make NEW" do |x|
68
+ @options[:snapshot] = x.downcase
69
+ end
70
+ opts.on "-H", "--headless", "start: start VM in headless mode" do
71
+ @options[:headless] = true
72
+ end
73
+ opts.on "-h", "--help", "show this message" do
74
+ puts @help
75
+ exit
76
+ end
77
+ end
78
+ @help = optparser.help
79
+ @argv = optparser.parse(@argv)
80
+
81
+ # disable glob matching if first arg is a UUID
82
+ unless @options.key?(:multiple)
83
+ @options[:multiple] = "{#{@argv.first}}" !~ UUID_RE
84
+ end
85
+
86
+ @vbox = VBOX::CmdLineAPI.new(@options)
87
+
88
+ if @argv.size == 0 || @argv.last == 'list'
89
+ vms = @vbox.list_vms
90
+ @vbox.list_vms(:running => true).each do |vm|
91
+ vms.find{ |vm1| vm1.uuid == vm.uuid }.state = :running
92
+ end
93
+
94
+ if @argv.size == 2 && @argv.last == 'list'
95
+ if @options[:multiple]
96
+ @globs = _expand_glob(@argv.first).flatten
97
+ vms = vms.keep_if{ |vm| _fnmatch(vm.name) }
98
+ else
99
+ vms = vms.keep_if{ |vm| vm.name == @argv.first }
100
+ end
101
+ end
102
+
103
+ longest = (vms.map(&:name).map(&:size)+[4]).max
104
+
105
+ puts "%-*s %5s %6s %-12s %s".gray % [longest, *%w'NAME MEM DIRSZ STATE UUID']
106
+ vms.each do |vm|
107
+ if @options[:verbose] > 0
108
+ @vbox.get_vm_details vm
109
+ state = (vm.state == :poweroff) ? '' : vm.state.to_s.upcase
110
+ s = sprintf "%-*s %5d %6s %-12s %s", longest, vm.name, vm.memory_size||0, vm.dir_size||0,
111
+ state, vm.uuid
112
+ else
113
+ state = (vm.state == :poweroff) ? '' : vm.state.to_s.upcase
114
+ s = sprintf "%-*s %5s %6s %-12s %s", longest, vm.name, '', '',
115
+ state, vm.uuid
116
+ end
117
+ s = s.green if vm.state == :running
118
+ puts s
119
+ end
120
+ else
121
+ name = @argv.shift
122
+ cmd = @argv.shift || 'show' # default command is 'show'
123
+
124
+ cmd = ALIASES[cmd] if ALIASES[cmd]
125
+ if @options[:multiple]
126
+ _run_multiple_cmd cmd, name
127
+ else
128
+ _run_cmd cmd, name
129
+ end
130
+ end
131
+ end
132
+
133
+ # expand globs like "d{1-30}" to d1,d2,d3,d4,...,d29,d30
134
+ def _expand_glob glob
135
+ if glob[/\{(\d+)-(\d+)\}/]
136
+ r = []
137
+ $1.to_i.upto($2.to_i) do |i|
138
+ r << _expand_glob(glob.sub($&,i.to_s))
139
+ end
140
+ r
141
+ else
142
+ [glob]
143
+ end
144
+ end
145
+
146
+ def _fnmatch fname
147
+ @globs.each do |glob|
148
+ return true if File.fnmatch(glob, fname)
149
+ end
150
+ false
151
+ end
152
+
153
+ def _run_multiple_cmd cmd, name
154
+ vms = @vbox.list_vms
155
+ @globs = _expand_glob(name).flatten
156
+ vms.each do |vm|
157
+ if _fnmatch(vm.name)
158
+ _run_cmd cmd, vm.name
159
+ end
160
+ end
161
+ end
162
+
163
+ def _run_cmd cmd, name
164
+ if COMMANDS.include?(cmd)
165
+ @vbox.send cmd, name
166
+ elsif cmd['=']
167
+ # set some variable, f.ex. "macaddress1=BADC0FFEE000"
168
+ @vbox.modify name, *cmd.split('=',2)
169
+ elsif cmd == 'snapshots'
170
+ @vbox.get_snapshots(name).each do |x|
171
+ printf "%s %s\n", x.uuid, x.name
172
+ end
173
+ else
174
+ STDERR.puts "[!] unknown command #{cmd.inspect}".red
175
+ puts @help
176
+ exit 1
177
+ end
178
+ end
179
+ end
180
+
181
+ VMInfo = Struct.new :name, :uuid, :memory_size, :dir_size, :state
182
+ Snapshot = Struct.new :name, :uuid
183
+
184
+ class CmdLineAPI
185
+ def initialize options={}
186
+ @options = options
187
+ @options[:verbose] ||= 2 if @options[:dry_run]
188
+ @options[:verbose] ||= 0
189
+ end
190
+
191
+ # run as in backtick operator, and return result
192
+ def ` cmd
193
+ puts "[.] #{cmd}".gray if @options[:verbose] >= 2
194
+ exit if @options[:dry_run]
195
+ r = super
196
+ #exit 1 unless $?.success?
197
+ r
198
+ end
199
+
200
+ # run as in system() call
201
+ def system *args
202
+ puts "[.] #{args.inspect}".gray if @options[:verbose] >= 2
203
+ exit if @options[:dry_run]
204
+ r = super
205
+ exit 1 unless $?.success?
206
+ r
207
+ end
208
+
209
+ def get_vm_details vm
210
+ data = `VBoxManage showvminfo #{vm.uuid} --machinereadable`
211
+ data.each_line do |line|
212
+ a = line.strip.split('=',2)
213
+ next unless a.size == 2
214
+ k,v = a
215
+ case k
216
+ when 'memory'
217
+ vm.memory_size = v.to_i
218
+ when 'VMState'
219
+ vm.state = v.tr('"','').to_sym
220
+ when 'CfgFile'
221
+ dir = File.dirname(v.tr('"',''))
222
+ s = `du -s -BM "#{dir}"`
223
+ vm.dir_size = s.split("\t").first.tr("M","")
224
+ end
225
+ end
226
+ vm
227
+ end
228
+
229
+ def list_vms params = {}
230
+ if params[:running]
231
+ data = `VBoxManage list runningvms`
232
+ else
233
+ data = `VBoxManage list vms`
234
+ end
235
+ r = []
236
+ data.strip.each_line do |line|
237
+ if line[UUID_RE]
238
+ vm = VMInfo.new
239
+ vm.uuid = $&
240
+ vm.name = line.gsub($&, '').strip.sub(/^"/,'').sub(/"$/,'')
241
+ r << vm
242
+ end
243
+ end
244
+ r.sort_by{ |vm| vm.name.naturalized }
245
+ end
246
+
247
+ def get_vm_info name
248
+ data = `VBoxManage showvminfo "#{name}" --machinereadable`
249
+ h = {}
250
+ data.each_line do |line|
251
+ line.strip!
252
+ k,v = line.split('=',2)
253
+ h[k] = v
254
+ end
255
+ h
256
+ end
257
+
258
+ def show name
259
+ get_vm_info(name).each do |k,v|
260
+ next if ['"none"', '"off"', '""'].include?(v)
261
+ puts "#{k}=#{v}"
262
+ end
263
+ end
264
+
265
+ COMMANDS.each do |cmd|
266
+ class_eval <<-EOF unless instance_methods.include?(cmd.to_sym)
267
+ def #{cmd} name
268
+ system "VBoxManage", "controlvm", name, "#{cmd}"
269
+ end
270
+ EOF
271
+ end
272
+
273
+ def start name
274
+ if ENV['DISPLAY'] && !@options[:headless]
275
+ system "VBoxManage", "startvm", name
276
+ else
277
+ puts "[.] $DISPLAY is not set, assuming --headless".gray unless @options[:headless]
278
+ @options[:headless] = true
279
+ system "VBoxManage", "startvm", name, "--type", "headless"
280
+ end
281
+ end
282
+
283
+
284
+ def get_snapshots _name
285
+ r = []
286
+ name = uuid = nil
287
+ `VBoxManage snapshot "#{_name}" list --machinereadable`.strip.each_line do |line|
288
+ k,v = line.strip.split('=',2)
289
+ next unless v
290
+ v = v.strip.sub(/^"/,'').sub(/"$/,'')
291
+ case k
292
+ when /SnapshotName/
293
+ name = v
294
+ when /SnapshotUUID/
295
+ uuid = v
296
+ end
297
+ if name && uuid
298
+ r << Snapshot.new(name, uuid)
299
+ name = uuid = nil
300
+ end
301
+ end
302
+ r
303
+ end
304
+
305
+ # d0 -> d1, d2, d3
306
+ # d1 -> d1.1, d1.2, d1.3
307
+ # d1.1 -> d1.1.1, d1.1.2, d1.1.3
308
+ def _gen_vm_name parent_name
309
+ # try to guess new name
310
+ numbers = parent_name.scan /\d+/
311
+ if numbers.any?
312
+ lastnum = numbers.last
313
+ names = list_vms.map(&:name)
314
+ if lastnum.to_i == 0
315
+ # d0 -> d1, d2, d3
316
+ newnum = lastnum.to_i + 1
317
+ while true
318
+ newname = parent_name.dup
319
+ newname[parent_name.rindex(lastnum),lastnum.size] = newnum.to_s
320
+ return newname unless names.include?(newname)
321
+ newnum += 1
322
+ end
323
+ else
324
+ # d1 -> d1.1, d1.2, d1.3
325
+ # d1.1 -> d1.1.1, d1.1.2, d1.1.3
326
+ newnum = 1
327
+ while true
328
+ newname = "#{parent_name}.#{newnum}"
329
+ return newname unless names.include?(newname)
330
+ newnum += 1
331
+ end
332
+ end
333
+ end
334
+ nil
335
+ end
336
+
337
+ def take_snapshot vm_name, params = {}
338
+ system "VBoxManage", "snapshot", vm_name, "take", params[:name] || "noname"
339
+ exit 1 unless $?.success?
340
+ get_snapshots(vm_name).last
341
+ end
342
+
343
+ def _name2macpart name
344
+ r = name.scan(/\d+/).map{ |x| "%02x" % x }.join
345
+ r == '' ? nil : r
346
+ end
347
+
348
+ def clone old_vm_name
349
+ @clone_use_snapshot = nil
350
+ (@options[:clones] || 1).times{ _clone(old_vm_name) }
351
+ end
352
+
353
+ def _clone old_vm_name
354
+ args = []
355
+ if new_vm_name = @options['name'] || _gen_vm_name(old_vm_name)
356
+ args += ["--name", new_vm_name]
357
+ end
358
+
359
+ snapshot = @clone_use_snapshot ||= case @options[:snapshot]
360
+ when 'new', 'take', 'make'
361
+ take_snapshot(old_vm_name, new_vm_name ? {:name => "for #{new_vm_name}"} : {})
362
+ when 'last'
363
+ get_snapshots(old_vm_name).last
364
+ else
365
+ puts "[!] please gimme --snapshot=LAST OR --snapshot=NEW option"
366
+ exit 1
367
+ end
368
+ unless snapshot
369
+ puts "[!] failed to get snapshot, cannot continue".red
370
+ exit 1
371
+ end
372
+
373
+ args += ["--options","link"]
374
+ args += ["--register"]
375
+ args += ["--snapshot", snapshot.uuid]
376
+
377
+ system "VBoxManage", "clonevm", old_vm_name, *args
378
+
379
+ get_vm_info(old_vm_name).each do |k,v|
380
+ if k =~ /^macaddress/
381
+ old_mac = v.tr('"','').downcase
382
+ puts "[.] old #{k}=#{old_mac}"
383
+ old_automac = _name2macpart(old_vm_name)
384
+ if old_automac && old_mac[-old_automac.size..-1] == old_automac
385
+ new_automac = _name2macpart(new_vm_name)
386
+ new_mac = old_mac[0,old_mac.size-new_automac.size] + new_automac
387
+ puts "[.] new #{k}=#{new_mac}"
388
+ modify new_vm_name, k, new_mac, :quiet => true
389
+ end
390
+ end
391
+ end
392
+ end
393
+
394
+ def modify name, k, v, params = {}
395
+ system "VBoxManage", "modifyvm", name, "--#{k}", v
396
+ if $?.success? && !params[:quiet]
397
+ h = get_vm_info(k == 'name' ? v : name)
398
+ puts "#{k}=#{h[k]}"
399
+ end
400
+ end
401
+
402
+ def delete name
403
+ system "VBoxManage", "unregistervm", name, "--delete"
404
+ end
405
+ end
406
+ end
407
+
408
+
409
+ if $0 == __FILE__
410
+ VBOX::CLI.new.run
411
+ end
@@ -0,0 +1,12 @@
1
+ $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
2
+ $LOAD_PATH.unshift(File.dirname(__FILE__))
3
+ require 'rspec'
4
+ require 'vbox-ng'
5
+
6
+ # Requires supporting files with custom matchers and macros, etc,
7
+ # in ./support/ and its subdirectories.
8
+ Dir["#{File.dirname(__FILE__)}/support/**/*.rb"].each {|f| require f}
9
+
10
+ RSpec.configure do |config|
11
+
12
+ end
@@ -0,0 +1,7 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
2
+
3
+ describe "VboxNg" do
4
+ it "fails" do
5
+ fail "hey buddy, you should probably rename this file and start specing for real"
6
+ end
7
+ end
@@ -0,0 +1,64 @@
1
+ # Generated by jeweler
2
+ # DO NOT EDIT THIS FILE DIRECTLY
3
+ # Instead, edit Jeweler::Tasks in Rakefile, and run 'rake gemspec'
4
+ # -*- encoding: utf-8 -*-
5
+
6
+ Gem::Specification.new do |s|
7
+ s.name = "vbox-ng"
8
+ s.version = "0.0.1"
9
+
10
+ s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
+ s.authors = ["Andrey \"Zed\" Zaikin"]
12
+ s.date = "2012-12-05"
13
+ s.description = "Create/start/stop/reboot/modify dozens of VirtualBox VMS with one short command."
14
+ s.email = "zed.0xff@gmail.com"
15
+ s.executables = ["vbox"]
16
+ s.extra_rdoc_files = [
17
+ "LICENSE.txt",
18
+ "README.md"
19
+ ]
20
+ s.files = [
21
+ ".document",
22
+ ".rspec",
23
+ "Gemfile",
24
+ "Gemfile.lock",
25
+ "LICENSE.txt",
26
+ "README.md",
27
+ "Rakefile",
28
+ "VERSION",
29
+ "bin/vbox",
30
+ "lib/vbox.rb",
31
+ "lib/vbox/api41.rb",
32
+ "lib/vbox/cli.rb",
33
+ "spec/spec_helper.rb",
34
+ "spec/vbox-ng_spec.rb",
35
+ "vbox-ng.gemspec"
36
+ ]
37
+ s.homepage = "http://github.com/zed-0xff/vbox-ng"
38
+ s.licenses = ["MIT"]
39
+ s.require_paths = ["lib"]
40
+ s.rubygems_version = "1.8.24"
41
+ s.summary = "A comfortable way of doing common VirtualBox tasks."
42
+
43
+ if s.respond_to? :specification_version then
44
+ s.specification_version = 3
45
+
46
+ if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
47
+ s.add_runtime_dependency(%q<awesome_print>, ["~> 1.1.0"])
48
+ s.add_development_dependency(%q<rspec>, ["~> 2.12.0"])
49
+ s.add_development_dependency(%q<bundler>, ["~> 1.0.0"])
50
+ s.add_development_dependency(%q<jeweler>, ["~> 1.8.4"])
51
+ else
52
+ s.add_dependency(%q<awesome_print>, ["~> 1.1.0"])
53
+ s.add_dependency(%q<rspec>, ["~> 2.12.0"])
54
+ s.add_dependency(%q<bundler>, ["~> 1.0.0"])
55
+ s.add_dependency(%q<jeweler>, ["~> 1.8.4"])
56
+ end
57
+ else
58
+ s.add_dependency(%q<awesome_print>, ["~> 1.1.0"])
59
+ s.add_dependency(%q<rspec>, ["~> 2.12.0"])
60
+ s.add_dependency(%q<bundler>, ["~> 1.0.0"])
61
+ s.add_dependency(%q<jeweler>, ["~> 1.8.4"])
62
+ end
63
+ end
64
+
metadata ADDED
@@ -0,0 +1,131 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: vbox-ng
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Andrey "Zed" Zaikin
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2012-12-05 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: awesome_print
16
+ requirement: !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ~>
20
+ - !ruby/object:Gem::Version
21
+ version: 1.1.0
22
+ type: :runtime
23
+ prerelease: false
24
+ version_requirements: !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ~>
28
+ - !ruby/object:Gem::Version
29
+ version: 1.1.0
30
+ - !ruby/object:Gem::Dependency
31
+ name: rspec
32
+ requirement: !ruby/object:Gem::Requirement
33
+ none: false
34
+ requirements:
35
+ - - ~>
36
+ - !ruby/object:Gem::Version
37
+ version: 2.12.0
38
+ type: :development
39
+ prerelease: false
40
+ version_requirements: !ruby/object:Gem::Requirement
41
+ none: false
42
+ requirements:
43
+ - - ~>
44
+ - !ruby/object:Gem::Version
45
+ version: 2.12.0
46
+ - !ruby/object:Gem::Dependency
47
+ name: bundler
48
+ requirement: !ruby/object:Gem::Requirement
49
+ none: false
50
+ requirements:
51
+ - - ~>
52
+ - !ruby/object:Gem::Version
53
+ version: 1.0.0
54
+ type: :development
55
+ prerelease: false
56
+ version_requirements: !ruby/object:Gem::Requirement
57
+ none: false
58
+ requirements:
59
+ - - ~>
60
+ - !ruby/object:Gem::Version
61
+ version: 1.0.0
62
+ - !ruby/object:Gem::Dependency
63
+ name: jeweler
64
+ requirement: !ruby/object:Gem::Requirement
65
+ none: false
66
+ requirements:
67
+ - - ~>
68
+ - !ruby/object:Gem::Version
69
+ version: 1.8.4
70
+ type: :development
71
+ prerelease: false
72
+ version_requirements: !ruby/object:Gem::Requirement
73
+ none: false
74
+ requirements:
75
+ - - ~>
76
+ - !ruby/object:Gem::Version
77
+ version: 1.8.4
78
+ description: Create/start/stop/reboot/modify dozens of VirtualBox VMS with one short
79
+ command.
80
+ email: zed.0xff@gmail.com
81
+ executables:
82
+ - vbox
83
+ extensions: []
84
+ extra_rdoc_files:
85
+ - LICENSE.txt
86
+ - README.md
87
+ files:
88
+ - .document
89
+ - .rspec
90
+ - Gemfile
91
+ - Gemfile.lock
92
+ - LICENSE.txt
93
+ - README.md
94
+ - Rakefile
95
+ - VERSION
96
+ - bin/vbox
97
+ - lib/vbox.rb
98
+ - lib/vbox/api41.rb
99
+ - lib/vbox/cli.rb
100
+ - spec/spec_helper.rb
101
+ - spec/vbox-ng_spec.rb
102
+ - vbox-ng.gemspec
103
+ homepage: http://github.com/zed-0xff/vbox-ng
104
+ licenses:
105
+ - MIT
106
+ post_install_message:
107
+ rdoc_options: []
108
+ require_paths:
109
+ - lib
110
+ required_ruby_version: !ruby/object:Gem::Requirement
111
+ none: false
112
+ requirements:
113
+ - - ! '>='
114
+ - !ruby/object:Gem::Version
115
+ version: '0'
116
+ segments:
117
+ - 0
118
+ hash: -1207365192943186499
119
+ required_rubygems_version: !ruby/object:Gem::Requirement
120
+ none: false
121
+ requirements:
122
+ - - ! '>='
123
+ - !ruby/object:Gem::Version
124
+ version: '0'
125
+ requirements: []
126
+ rubyforge_project:
127
+ rubygems_version: 1.8.24
128
+ signing_key:
129
+ specification_version: 3
130
+ summary: A comfortable way of doing common VirtualBox tasks.
131
+ test_files: []