bum 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.
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
+