vbox-ng 0.0.1

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.
@@ -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: []