bum 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (8) hide show
  1. data/README +12 -0
  2. data/bin/bum +55 -0
  3. data/lib/bum.rb +380 -0
  4. data/lib/client.rb.erb +7 -0
  5. data/lib/dhcpd.rb +84 -0
  6. data/lib/hosts.rb +61 -0
  7. data/lib/vmx.rb +24 -0
  8. metadata +129 -0
data/README ADDED
@@ -0,0 +1,12 @@
1
+ To make a new base VM:
2
+
3
+ 1) Install your OS (only tested with Ubuntu 10.04 64-bit)
4
+ 2) Setup a user with a password that you don't mind hardcoding in your Bumfile
5
+ 3) Make sure that user has no-password sudo access
6
+ 4) sudo apt-get install ruby1.8 ruby1.8-dev libopenssl-ruby rubygems ssh
7
+ 5) sudo gem install chef
8
+ 6) Delete /etc/udev/rules.d/70-persistent-net.whatever
9
+ 7) Shut down VM
10
+ 8) Remove all *.lck files from inside the VM's vmwarevm folder
11
+
12
+ DONE!
data/bin/bum ADDED
@@ -0,0 +1,55 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'rubygems'
4
+ require 'thor'
5
+ require 'bum'
6
+
7
+ class CLI < Thor
8
+ desc "destroy [NODENAME]", "Completely destroy a VM"
9
+ def destroy(node_name=nil)
10
+ bum = Bum.new
11
+ if node_name
12
+ bum.destroy(node_name)
13
+ else
14
+ bum.destroy_all
15
+ end
16
+ end
17
+
18
+ desc "start [NODENAME]", "Start a VM"
19
+ def start(node_name=nil)
20
+ bum = Bum.new
21
+ if node_name
22
+ bum.start(node_name)
23
+ else
24
+ bum.start_all
25
+ end
26
+ end
27
+
28
+ desc "stop [NODENAME]", "Stop a VM"
29
+ def stop(node_name=nil)
30
+ bum = Bum.new
31
+ if node_name
32
+ bum.stop(node_name)
33
+ else
34
+ bum.stop_all
35
+ end
36
+ end
37
+
38
+ desc "setup [NODENAME]", "Refresh configs and run Chef on a VM"
39
+ def setup(node_name=nil)
40
+ bum = Bum.new
41
+ if node_name
42
+ bum.setup(node_name)
43
+ else
44
+ bum.setup_all
45
+ end
46
+ end
47
+
48
+ desc 'write_hosts', 'Write out a new /etc/hosts file'
49
+ def write_hosts
50
+ bum = Bum.new
51
+ bum.write_hosts
52
+ end
53
+ end
54
+
55
+ CLI.start
data/lib/bum.rb ADDED
@@ -0,0 +1,380 @@
1
+ require 'rubygems'
2
+ require 'net/ssh'
3
+ require 'net/scp'
4
+ require 'json'
5
+
6
+ require 'fileutils'
7
+ require 'erb'
8
+ require 'ostruct'
9
+
10
+ require 'vmx'
11
+ require 'dhcpd'
12
+ require 'hosts'
13
+
14
+ class Bum
15
+ DEFAULT_BUMFILE = './Bumfile'
16
+ DEFAULT_VM_PATH = File.expand_path('~/Documents/Virtual Machines.localized/')
17
+
18
+ VMWARE_PATH = '/Library/Application Support/VMware Fusion'
19
+ DHCPD_CONF_PATH = "#{VMWARE_PATH}/vmnet8/dhcpd.conf"
20
+
21
+ def initialize(bumfile=DEFAULT_BUMFILE)
22
+ @bum_config = Bum::Config.new(DEFAULT_BUMFILE)
23
+ end
24
+
25
+ def bootstrap
26
+ vmx = template_vmx
27
+ vmx.data['sharedFolder0.hostPath'] = File.expand_path('~')
28
+ vmx.data['sharedFolder0.guestName'] = 'host_home'
29
+ vmx.save
30
+ end
31
+
32
+ def create(node_name)
33
+ node_config = get_node_config(node_name)
34
+
35
+ raise "VM already exists!" if vm_exists?(node_name)
36
+
37
+ puts "Cloning template..."
38
+ clone_template node_name
39
+
40
+ puts "Updating machine configuration..."
41
+ mac = generate_mac
42
+ uuid = generate_uuid
43
+
44
+ vmx = node_vmx(node_name)
45
+ vmx.data['displayName'] = node_name
46
+ vmx.data['memsize'] = node_config.config[:ram] || @bum_config.config[:default_ram] || 512
47
+ vmx.data['numvcpus'] = node_config.config[:cpus] || @bum_config.config[:default_cpus] || 1
48
+ vmx.data['ethernet0.address'] = mac
49
+ vmx.data['uuid.bios'] = uuid
50
+ vmx.data['uuid.location'] = uuid
51
+ vmx.save
52
+
53
+ dhcpd = DHCPD.new(DHCPD_CONF_PATH)
54
+ dhcpd.config[node_name] = {
55
+ 'hardware ethernet' => mac,
56
+ 'fixed-address' => node_config.config[:ip]
57
+ }
58
+ dhcpd.save
59
+
60
+ puts "Restarting dhcpd..."
61
+ dhcpd.restart
62
+
63
+ puts "Starting..."
64
+ start_node(node_name)
65
+
66
+ setup(node_name)
67
+ end
68
+
69
+ def setup(node_name)
70
+ node_config = get_node_config(node_name)
71
+
72
+ puts "Setting up #{node_name}..."
73
+
74
+ client_erb = ERB.new(File.read('client.rb.erb'))
75
+ client_rb_data = client_erb.result(OpenStruct.new(:config => @bum_config.config, :node_config => node_config).send(:binding))
76
+ tmp_client_rb_path = ".tmp.#{node_name}.client.rb"
77
+ File.open(tmp_client_rb_path,'w') do |f|
78
+ f.puts client_rb_data
79
+ end
80
+
81
+ chef_attribs = {
82
+ :run_list => @bum_config.config[:default_run_list] + node_config.config[:run_list],
83
+ :ip => {
84
+ :private => node_config.config[:ip],
85
+ :private_netmask => '255.255.255.0'
86
+ }
87
+ }.merge(@bum_config.config[:chef_options]).merge(node_config.config[:chef_options])
88
+
89
+ puts "Uploading Chef files..."
90
+ Net::SCP.start node_config.config[:ip], @bum_config.config[:ssh_user], :password => @bum_config.config[:ssh_password] do |scp|
91
+ scp.upload! tmp_client_rb_path, "client.rb"
92
+ scp.upload! @bum_config.config[:validation_key_path], "validation.pem"
93
+ scp.upload! StringIO.new(chef_attribs.to_json), "client-config.json"
94
+ end
95
+
96
+ FileUtils.rm(tmp_client_rb_path)
97
+
98
+ enable_shared_folders node_name
99
+ @bum_config.config[:shares].each do |share_name, guest_path, host_path, options|
100
+ cmd "mkdir -p #{host_path}"
101
+ share_folder node_name, share_name, host_path
102
+ end
103
+
104
+ puts "Connecting over SSH..."
105
+ Net::SSH.start node_config.config[:ip], @bum_config.config[:ssh_user], :password => @bum_config.config[:ssh_password] do |ssh|
106
+ ssh_exec ssh, "sudo hostname #{node_name}"
107
+ ssh_exec ssh, "sudo mkdir /etc/chef"
108
+
109
+ @bum_config.config[:shares].each do |share_name, guest_path, host_path, options|
110
+ ssh_exec ssh, "sudo mkdir -p #{File.dirname(guest_path)} && sudo ln -sf /mnt/hgfs/#{share_name} #{guest_path}"
111
+ end
112
+
113
+ ssh_exec ssh, "sudo mv client.rb /etc/chef/"
114
+ ssh_exec ssh, "sudo mv validation.pem /etc/chef/"
115
+ ssh_exec ssh, "sudo mv client-config.json /etc/chef/"
116
+
117
+ ssh.exec! "sudo /var/lib/gems/1.8/bin/chef-client" do |channel, stream, data|
118
+ print data
119
+ end
120
+ end
121
+ end
122
+
123
+ def destroy(node_name)
124
+ node_config = get_node_config(node_name)
125
+
126
+ if vm_exists?(node_name)
127
+ stop_node(node_name)
128
+
129
+ puts "Removing #{node_name} from Chef..."
130
+ `knife node delete #{node_name} -y`
131
+ `knife client delete #{node_name} -y`
132
+
133
+ puts "Destroying #{node_name}..."
134
+ FileUtils.rm_rf(node_name_to_vm_path(node_name))
135
+ else
136
+ puts "#{node_name} hasn't even been created yet, thusly you can't destroy it!"
137
+ end
138
+ end
139
+
140
+ def stop(node_name)
141
+ node_config = get_node_config(node_name)
142
+
143
+ if vm_running?(node_name)
144
+ puts "Stopping #{node_name}..."
145
+ stop_node(node_name)
146
+ else
147
+ puts "wtf, #{node_name} is not running."
148
+ end
149
+ end
150
+
151
+ def start(node_name)
152
+ node_config = get_node_config(node_name)
153
+
154
+ if vm_running?(node_name)
155
+ puts "Dude, #{node_name} is already running!"
156
+ elsif vm_exists?(node_name)
157
+ puts "Starting #{node_name}..."
158
+ start_node node_name
159
+ else
160
+ puts "Generating, then starting #{node_name}..."
161
+ create node_name
162
+ end
163
+ end
164
+
165
+ def write_hosts
166
+ puts "Writing /etc/hosts file..."
167
+ hosts = Hosts.new
168
+ @bum_config.config[:nodes].each do |node|
169
+ hosts.config[node.config[:ip]] = node.name
170
+ end
171
+ hosts.save
172
+ end
173
+
174
+
175
+ def start_all
176
+ puts "Starting all nodes..."
177
+ @bum_config.config[:nodes].each { |n| start(n.name) }
178
+ end
179
+
180
+ def stop_all
181
+ puts "Stopping all nodes..."
182
+ @bum_config.config[:nodes].each { |n| stop(n.name) }
183
+ end
184
+
185
+ def destroy_all
186
+ puts "Destroying all nodes..."
187
+ @bum_config.config[:nodes].each { |n| destroy(n.name) }
188
+ end
189
+
190
+ def setup_all
191
+ puts "Setting up all nodes..."
192
+ @bum_config.config[:nodes].each { |n| setup(n.name) }
193
+ end
194
+
195
+ private
196
+
197
+ def cmd(*args)
198
+ puts ["local>", *args].join(" ")
199
+ system *args
200
+ end
201
+
202
+ def ssh_exec(ssh, cmd)
203
+ puts "ssh> #{cmd}"
204
+ print ssh.exec!(cmd)
205
+ end
206
+
207
+ def enable_shared_folders(node_name)
208
+ cmd "'#{VMWARE_PATH}/vmrun' enableSharedFolders '#{node_name_to_vmx(node_name)}'"
209
+ end
210
+
211
+ def share_folder(node_name, share_name, path)
212
+ cmd "'#{VMWARE_PATH}/vmrun' addSharedFolder '#{node_name_to_vmx(node_name)}' '#{share_name}' '#{path}'"
213
+ end
214
+
215
+ def get_node_config(node_name)
216
+ node_config = @bum_config.get_node(node_name)
217
+ raise "Alas, I don't know about a node named #{node_name.inspect}..." unless node_config
218
+ node_config
219
+ end
220
+
221
+ def vm_exists?(node_name)
222
+ File.exists? node_name_to_vm_path(node_name)
223
+ end
224
+
225
+ def vm_running?(node_name)
226
+ `'#{VMWARE_PATH}/vmrun' list`.include?("#{node_name}.vmwarevm")
227
+ end
228
+
229
+ def clone_template(node_name)
230
+ FileUtils.cp_r(template_vm_path, node_name_to_vm_path(node_name))
231
+ end
232
+
233
+ def template_vmx
234
+ @template_vmx ||= VMX.new(@bum_config.config[:vmx])
235
+ end
236
+
237
+ def node_vmx(node_name)
238
+ VMX.new(node_name_to_vmx(node_name))
239
+ end
240
+
241
+ def start_node(node_name)
242
+ cmd "'#{VMWARE_PATH}/vmrun' start '#{node_name_to_vmx(node_name)}' nogui >>vmware.log 2>&1"
243
+ end
244
+
245
+ def stop_node(node_name)
246
+ cmd "'#{VMWARE_PATH}/vmrun' stop '#{node_name_to_vmx(node_name)}' >>vmware.log 2>&1"
247
+ end
248
+
249
+ def generate_mac
250
+ "00:50:56:" + 3.times.map { ("%2s" % rand(256).to_s(16)).gsub(' ','0') }.join(':').upcase
251
+ end
252
+
253
+ # not actually universal - doesn't fucking matter
254
+ def generate_uuid
255
+ 2.times.map {
256
+ 8.times.map {
257
+ ("%2s" % rand(256).to_s(16)).gsub(' ','0')
258
+ }.join(' ')
259
+ }.join('-')
260
+ end
261
+
262
+ def node_name_to_vmx(node_name)
263
+ Dir[File.join(node_name_to_vm_path(node_name), '*.vmx')][0]
264
+ end
265
+
266
+ def node_name_to_vm_path(node_name)
267
+ File.join(DEFAULT_VM_PATH, "#{node_name}.vmwarevm")
268
+ end
269
+
270
+ def template_vm_path
271
+ File.dirname(@bum_config.config[:vmx])
272
+ end
273
+
274
+ class Config
275
+ attr_reader :config
276
+
277
+ def initialize(bumfile)
278
+ @config = {
279
+ :nodes => [],
280
+ :ssh_user => 'dev',
281
+ :ssh_password => 'password',
282
+ :shares => [],
283
+ :suffix => ''
284
+ }
285
+ instance_eval File.read(bumfile)
286
+ end
287
+
288
+ def get_node(node_name)
289
+ @config[:nodes].detect { |n| n.name == node_name }
290
+ end
291
+
292
+
293
+ def ssh_user(user)
294
+ @config[:ssh_user] = user
295
+ end
296
+
297
+ def ssh_password(pass)
298
+ @config[:ssh_password] = pass
299
+ end
300
+
301
+ def vmx(vmx_file)
302
+ @config[:vmx] = File.expand_path(vmx_file)
303
+ end
304
+
305
+ def node(name, &block)
306
+ @config[:nodes] << Node.new(name + @config[:suffix], name, &block)
307
+ end
308
+
309
+ def ram(megabytes)
310
+ @config[:default_ram] = megabytes
311
+ end
312
+
313
+ def cpus(count)
314
+ @config[:default_cpus] = count
315
+ end
316
+
317
+ def chef_server_url(url)
318
+ @config[:chef_server_url] = url
319
+ end
320
+
321
+ def validation_key_path(path)
322
+ @config[:validation_key_path] = File.expand_path(path)
323
+ end
324
+
325
+ def validation_client_name(name)
326
+ @config[:validation_client_name] = name
327
+ end
328
+
329
+ def chef_environment(env)
330
+ @config[:chef_environment] = env
331
+ end
332
+
333
+ def chef_options(opts)
334
+ @config[:chef_options] = opts
335
+ end
336
+
337
+ def default_run_list(list)
338
+ @config[:default_run_list] = list
339
+ end
340
+
341
+ def share(share_name, guest_path, host_path, options={})
342
+ @config[:shares] << [share_name, guest_path, File.expand_path(host_path), options]
343
+ end
344
+
345
+ def node_suffix(suffix)
346
+ @config[:suffix] = suffix
347
+ end
348
+
349
+ class Node
350
+ attr_reader :name, :pretty_name, :config
351
+
352
+ def initialize(name, pretty_name, &block)
353
+ @name = name
354
+ @pretty_name = pretty_name
355
+ @config = {}
356
+ instance_eval &block
357
+ end
358
+
359
+ def run_list(*recipes)
360
+ @config[:run_list] = recipes
361
+ end
362
+
363
+ def ip(addr)
364
+ @config[:ip] = addr
365
+ end
366
+
367
+ def chef_options(options)
368
+ @config[:chef_options] = options
369
+ end
370
+
371
+ def ram(megabytes)
372
+ @config[:ram] = megabytes
373
+ end
374
+
375
+ def cpus(count)
376
+ @config[:cpus] = count
377
+ end
378
+ end
379
+ end
380
+ end
data/lib/client.rb.erb ADDED
@@ -0,0 +1,7 @@
1
+ # Managed by Bum
2
+
3
+ node_name "<%= node_config.name %>"
4
+ chef_server_url "<%= config[:chef_server_url] %>"
5
+ validation_client_name "<%= config[:validation_client_name] %>"
6
+ environment "<%= config[:chef_environment] %>"
7
+ json_attribs "/etc/chef/client-config.json"
data/lib/dhcpd.rb ADDED
@@ -0,0 +1,84 @@
1
+ class DHCPD
2
+ attr_reader :config
3
+
4
+ def initialize(conf_path)
5
+ @conf_path = conf_path
6
+ @preamble = ''
7
+ @postamble = ''
8
+ @config = {}
9
+
10
+ parse
11
+ end
12
+
13
+ def save
14
+ tmp_path = '.tmp.dhcpd.conf'
15
+ File.open(tmp_path, 'w') do |f|
16
+ f.write @preamble
17
+ f.puts BEGIN_BUM
18
+
19
+ @config.each do |host, host_config|
20
+ f.puts "host #{host} {"
21
+ host_config.each do |key, value|
22
+ f.puts "\t#{key} #{value};"
23
+ end
24
+ f.puts "}"
25
+ end
26
+
27
+ f.puts END_BUM
28
+ f.write @postamble
29
+ end
30
+
31
+ system 'sudo', 'mv', tmp_path, @conf_path
32
+ end
33
+
34
+ def restart
35
+ system 'sudo "/Library/Application Support/VMware Fusion/boot.sh" --restart >>vmware.log 2>&1'
36
+ end
37
+
38
+
39
+ private
40
+
41
+ BEGIN_BUM = "#### BEGIN BUM ####"
42
+ END_BUM = "#### END BUM ####"
43
+
44
+ def parse
45
+ state = :preamble
46
+ host = nil
47
+
48
+ File.open(@conf_path).each_line do |rawline|
49
+ line = rawline.strip
50
+
51
+ case state
52
+ when :preamble
53
+ if line == BEGIN_BUM
54
+ state = :outside_host
55
+ else
56
+ @preamble << rawline
57
+ end
58
+
59
+ when :outside_host
60
+ next unless line =~ /^host .* \{$/
61
+ words = line.split(' ').map { |x| x.strip }
62
+ host = words[1]
63
+ @config[host] = {}
64
+ state = :inside_host
65
+
66
+ when :inside_host
67
+ next if line.size == 0 || line.strip =~ /^#/
68
+ if line == '}'
69
+ state = :outside_host
70
+ host = nil
71
+ elsif line == END_BUM
72
+ state = :postamble
73
+ else
74
+ parts = line.chomp(';').split(' ')
75
+ @config[host][parts[0..-2].join(' ')] = parts[-1]
76
+ end
77
+
78
+ when :postamble
79
+ @postamble << rawline
80
+ end
81
+ end
82
+
83
+ end
84
+ end
data/lib/hosts.rb ADDED
@@ -0,0 +1,61 @@
1
+ # Class for parsing and writing /etc/hosts file
2
+
3
+ class Hosts
4
+ def initialize
5
+ @preamble = ""
6
+ @postamble = ""
7
+ @config = {}
8
+
9
+ parse
10
+ end
11
+
12
+ attr_reader :config
13
+
14
+ def save
15
+ File.open('.tmp.hosts','w') do |f|
16
+ f.write @preamble
17
+ f.puts BEGIN_LINE
18
+
19
+ @config.each do |ip, host|
20
+ f.puts "#{ip}\t#{host}"
21
+ end
22
+
23
+ f.puts END_LINE
24
+ f.write @postamble
25
+ end
26
+ system "sudo mv .tmp.hosts /etc/hosts"
27
+ end
28
+
29
+ private
30
+
31
+ BEGIN_LINE = "#### BEGIN BUM ####"
32
+ END_LINE = "#### END BUM ####"
33
+
34
+ def parse
35
+ state = :preamble
36
+
37
+ File.open('/etc/hosts').each_line do |line|
38
+ case state
39
+ when :preamble
40
+ if line.include? BEGIN_LINE
41
+ state = :bum
42
+ else
43
+ @preamble << line
44
+ end
45
+ when :bum
46
+ if line.include? END_LINE
47
+ state = :postamble
48
+ else
49
+ if line.strip =~ /^#/
50
+ # skip
51
+ else
52
+ ip, host = line.split(/\s+/)
53
+ @config[ip] = host
54
+ end
55
+ end
56
+ when :postamble
57
+ @postamble << line
58
+ end
59
+ end
60
+ end
61
+ end
data/lib/vmx.rb ADDED
@@ -0,0 +1,24 @@
1
+ require 'fileutils'
2
+
3
+ class VMX
4
+ attr_reader :data
5
+
6
+ def initialize(filename)
7
+ @filename = filename
8
+ @data = {}
9
+
10
+ File.open(filename).each_line do |line|
11
+ key, value = line.split('=', 2)
12
+ @data[key.strip] = eval(value)
13
+ end
14
+ end
15
+
16
+ def save(filename=@filename)
17
+ FileUtils.cp(filename, filename + '.bak')
18
+ File.open(filename, 'w') do |f|
19
+ @data.sort_by { |(k,v)| k }.each do |(k,v)|
20
+ f.puts "#{k} = #{v.to_s.inspect}"
21
+ end
22
+ end
23
+ end
24
+ end
metadata ADDED
@@ -0,0 +1,129 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: bum
3
+ version: !ruby/object:Gem::Version
4
+ hash: 29
5
+ prerelease:
6
+ segments:
7
+ - 0
8
+ - 0
9
+ - 1
10
+ version: 0.0.1
11
+ platform: ruby
12
+ authors:
13
+ - Tyler McMullen
14
+ autorequire:
15
+ bindir: bin
16
+ cert_chain: []
17
+
18
+ date: 2011-08-16 00:00:00 -07:00
19
+ default_executable:
20
+ dependencies:
21
+ - !ruby/object:Gem::Dependency
22
+ name: thor
23
+ prerelease: false
24
+ requirement: &id001 !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ">="
28
+ - !ruby/object:Gem::Version
29
+ hash: 3
30
+ segments:
31
+ - 0
32
+ version: "0"
33
+ type: :runtime
34
+ version_requirements: *id001
35
+ - !ruby/object:Gem::Dependency
36
+ name: net-ssh
37
+ prerelease: false
38
+ requirement: &id002 !ruby/object:Gem::Requirement
39
+ none: false
40
+ requirements:
41
+ - - ">="
42
+ - !ruby/object:Gem::Version
43
+ hash: 3
44
+ segments:
45
+ - 0
46
+ version: "0"
47
+ type: :runtime
48
+ version_requirements: *id002
49
+ - !ruby/object:Gem::Dependency
50
+ name: net-scp
51
+ prerelease: false
52
+ requirement: &id003 !ruby/object:Gem::Requirement
53
+ none: false
54
+ requirements:
55
+ - - ">="
56
+ - !ruby/object:Gem::Version
57
+ hash: 3
58
+ segments:
59
+ - 0
60
+ version: "0"
61
+ type: :runtime
62
+ version_requirements: *id003
63
+ - !ruby/object:Gem::Dependency
64
+ name: json
65
+ prerelease: false
66
+ requirement: &id004 !ruby/object:Gem::Requirement
67
+ none: false
68
+ requirements:
69
+ - - ">="
70
+ - !ruby/object:Gem::Version
71
+ hash: 3
72
+ segments:
73
+ - 0
74
+ version: "0"
75
+ type: :runtime
76
+ version_requirements: *id004
77
+ description: Inspired by Vagrant but much much simpler. Also uses VMware, instead of Virtualbox.
78
+ email:
79
+ - tyler@fastly.com
80
+ executables:
81
+ - bum
82
+ extensions: []
83
+
84
+ extra_rdoc_files: []
85
+
86
+ files:
87
+ - bin/bum
88
+ - lib/bum.rb
89
+ - lib/client.rb.erb
90
+ - lib/dhcpd.rb
91
+ - lib/hosts.rb
92
+ - lib/vmx.rb
93
+ - README
94
+ has_rdoc: true
95
+ homepage: http://github.com/fastly/Bum
96
+ licenses: []
97
+
98
+ post_install_message:
99
+ rdoc_options: []
100
+
101
+ require_paths:
102
+ - lib
103
+ required_ruby_version: !ruby/object:Gem::Requirement
104
+ none: false
105
+ requirements:
106
+ - - ">="
107
+ - !ruby/object:Gem::Version
108
+ hash: 3
109
+ segments:
110
+ - 0
111
+ version: "0"
112
+ required_rubygems_version: !ruby/object:Gem::Requirement
113
+ none: false
114
+ requirements:
115
+ - - ">="
116
+ - !ruby/object:Gem::Version
117
+ hash: 3
118
+ segments:
119
+ - 0
120
+ version: "0"
121
+ requirements: []
122
+
123
+ rubyforge_project:
124
+ rubygems_version: 1.6.2
125
+ signing_key:
126
+ specification_version: 3
127
+ summary: A semi-sane way to manage a multi-vm dev environment
128
+ test_files: []
129
+