pauper 0.0.4

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/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 Pauperfile
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/pauper ADDED
@@ -0,0 +1,55 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'rubygems'
4
+ require 'thor'
5
+ require 'pauper'
6
+
7
+ class CLI < Thor
8
+ desc "destroy [NODENAME]", "Completely destroy a VM"
9
+ def destroy(node_name=nil)
10
+ pauper = Pauper.new
11
+ if node_name
12
+ pauper.destroy(node_name)
13
+ else
14
+ pauper.destroy_all
15
+ end
16
+ end
17
+
18
+ desc "start [NODENAME]", "Start a VM"
19
+ def start(node_name=nil)
20
+ pauper = Pauper.new
21
+ if node_name
22
+ pauper.start(node_name)
23
+ else
24
+ pauper.start_all
25
+ end
26
+ end
27
+
28
+ desc "stop [NODENAME]", "Stop a VM"
29
+ def stop(node_name=nil)
30
+ pauper = Pauper.new
31
+ if node_name
32
+ pauper.stop(node_name)
33
+ else
34
+ pauper.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
+ pauper = Pauper.new
41
+ if node_name
42
+ pauper.setup(node_name)
43
+ else
44
+ pauper.setup_all
45
+ end
46
+ end
47
+
48
+ desc 'write_hosts', 'Write out a new /etc/hosts file'
49
+ def write_hosts
50
+ pauper = Pauper.new
51
+ pauper.write_hosts
52
+ end
53
+ end
54
+
55
+ CLI.start
data/lib/client.rb.erb ADDED
@@ -0,0 +1,7 @@
1
+ # Managed by Pauper
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 PAUPER ####"
32
+ END_LINE = "#### END PAUPER ####"
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 = :pauper
42
+ else
43
+ @preamble << line
44
+ end
45
+ when :pauper
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/pauper.rb ADDED
@@ -0,0 +1,385 @@
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 Pauper
15
+ DEFAULT_PAUPERFILE = './Pauperfile'
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(pauperfile=DEFAULT_PAUPERFILE)
22
+ @pauper_config = Pauper::Config.new(DEFAULT_PAUPERFILE)
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] || @pauper_config.config[:default_ram] || 512
47
+ vmx.data['numvcpus'] = node_config.config[:cpus] || @pauper_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(File.join(File.dirname(__FILE__), 'client.rb.erb')))
75
+ client_rb_data = client_erb.result(OpenStruct.new(:config => @pauper_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 => @pauper_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(@pauper_config.config[:chef_options]).merge(node_config.config[:chef_options])
88
+
89
+ puts "Uploading Chef files..."
90
+ Net::SCP.start node_config.config[:ip], @pauper_config.config[:ssh_user], :password => @pauper_config.config[:ssh_password] do |scp|
91
+ scp.upload! tmp_client_rb_path, "client.rb"
92
+ scp.upload! @pauper_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
+ @pauper_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], @pauper_config.config[:ssh_user], :password => @pauper_config.config[:ssh_password] do |ssh|
106
+ ssh_exec ssh, "sudo hostname #{node_name}"
107
+ ssh_exec ssh, "sudo mkdir /etc/chef"
108
+
109
+ @pauper_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
+ @pauper_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
+ @pauper_config.config[:nodes].each { |n| start(n.name) }
178
+ end
179
+
180
+ def stop_all
181
+ puts "Stopping all nodes..."
182
+ @pauper_config.config[:nodes].each { |n| stop(n.name) }
183
+ end
184
+
185
+ def destroy_all
186
+ puts "Destroying all nodes..."
187
+ @pauper_config.config[:nodes].each { |n| destroy(n.name) }
188
+ end
189
+
190
+ def setup_all
191
+ puts "Setting up all nodes..."
192
+ @pauper_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 = @pauper_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(@pauper_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(@pauper_config.config[:vmx])
272
+ end
273
+
274
+ class Config
275
+ attr_reader :config
276
+
277
+ def initialize(pauperfile)
278
+ @config = {
279
+ :nodes => [],
280
+ :ssh_user => 'dev',
281
+ :ssh_password => 'password',
282
+ :run_list => [],
283
+ :shares => [],
284
+ :suffix => '',
285
+ :chef_options => {}
286
+ }
287
+ instance_eval File.read(pauperfile)
288
+ end
289
+
290
+ def get_node(node_name)
291
+ @config[:nodes].detect { |n| n.name == node_name }
292
+ end
293
+
294
+
295
+ def ssh_user(user)
296
+ @config[:ssh_user] = user
297
+ end
298
+
299
+ def ssh_password(pass)
300
+ @config[:ssh_password] = pass
301
+ end
302
+
303
+ def vmx(vmx_file)
304
+ @config[:vmx] = File.expand_path(vmx_file)
305
+ end
306
+
307
+ def node(name, &block)
308
+ @config[:nodes] << Node.new(name + @config[:suffix], name, &block)
309
+ end
310
+
311
+ def ram(megabytes)
312
+ @config[:default_ram] = megabytes
313
+ end
314
+
315
+ def cpus(count)
316
+ @config[:default_cpus] = count
317
+ end
318
+
319
+ def chef_server_url(url)
320
+ @config[:chef_server_url] = url
321
+ end
322
+
323
+ def validation_key_path(path)
324
+ @config[:validation_key_path] = File.expand_path(path)
325
+ end
326
+
327
+ def validation_client_name(name)
328
+ @config[:validation_client_name] = name
329
+ end
330
+
331
+ def chef_environment(env)
332
+ @config[:chef_environment] = env
333
+ end
334
+
335
+ def chef_options(opts)
336
+ @config[:chef_options] = opts
337
+ end
338
+
339
+ def default_run_list(list)
340
+ @config[:default_run_list] = list
341
+ end
342
+
343
+ def share(share_name, guest_path, host_path, options={})
344
+ @config[:shares] << [share_name, guest_path, File.expand_path(host_path), options]
345
+ end
346
+
347
+ def node_suffix(suffix)
348
+ @config[:suffix] = suffix
349
+ end
350
+
351
+ class Node
352
+ attr_reader :name, :pretty_name, :config
353
+
354
+ def initialize(name, pretty_name, &block)
355
+ @name = name
356
+ @pretty_name = pretty_name
357
+ @config = {
358
+ :run_list => [],
359
+ :chef_options => {}
360
+ }
361
+ instance_eval &block
362
+ end
363
+
364
+ def run_list(*recipes)
365
+ @config[:run_list] = recipes
366
+ end
367
+
368
+ def ip(addr)
369
+ @config[:ip] = addr
370
+ end
371
+
372
+ def chef_options(options)
373
+ @config[:chef_options] = options
374
+ end
375
+
376
+ def ram(megabytes)
377
+ @config[:ram] = megabytes
378
+ end
379
+
380
+ def cpus(count)
381
+ @config[:cpus] = count
382
+ end
383
+ end
384
+ end
385
+ 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,116 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: pauper
3
+ version: !ruby/object:Gem::Version
4
+ prerelease: false
5
+ segments:
6
+ - 0
7
+ - 0
8
+ - 4
9
+ version: 0.0.4
10
+ platform: ruby
11
+ authors:
12
+ - Tyler McMullen
13
+ autorequire:
14
+ bindir: bin
15
+ cert_chain: []
16
+
17
+ date: 2011-08-17 00:00:00 -07:00
18
+ default_executable:
19
+ dependencies:
20
+ - !ruby/object:Gem::Dependency
21
+ name: thor
22
+ prerelease: false
23
+ requirement: &id001 !ruby/object:Gem::Requirement
24
+ requirements:
25
+ - - ">="
26
+ - !ruby/object:Gem::Version
27
+ segments:
28
+ - 0
29
+ version: "0"
30
+ type: :runtime
31
+ version_requirements: *id001
32
+ - !ruby/object:Gem::Dependency
33
+ name: net-ssh
34
+ prerelease: false
35
+ requirement: &id002 !ruby/object:Gem::Requirement
36
+ requirements:
37
+ - - ">="
38
+ - !ruby/object:Gem::Version
39
+ segments:
40
+ - 0
41
+ version: "0"
42
+ type: :runtime
43
+ version_requirements: *id002
44
+ - !ruby/object:Gem::Dependency
45
+ name: net-scp
46
+ prerelease: false
47
+ requirement: &id003 !ruby/object:Gem::Requirement
48
+ requirements:
49
+ - - ">="
50
+ - !ruby/object:Gem::Version
51
+ segments:
52
+ - 0
53
+ version: "0"
54
+ type: :runtime
55
+ version_requirements: *id003
56
+ - !ruby/object:Gem::Dependency
57
+ name: json
58
+ prerelease: false
59
+ requirement: &id004 !ruby/object:Gem::Requirement
60
+ requirements:
61
+ - - ">="
62
+ - !ruby/object:Gem::Version
63
+ segments:
64
+ - 0
65
+ version: "0"
66
+ type: :runtime
67
+ version_requirements: *id004
68
+ description: Inspired by Vagrant but much much simpler. Also uses VMware, instead of Virtualbox.
69
+ email:
70
+ - tyler@fastly.com
71
+ executables:
72
+ - pauper
73
+ extensions: []
74
+
75
+ extra_rdoc_files: []
76
+
77
+ files:
78
+ - bin/pauper
79
+ - lib/client.rb.erb
80
+ - lib/dhcpd.rb
81
+ - lib/hosts.rb
82
+ - lib/pauper.rb
83
+ - lib/vmx.rb
84
+ - README
85
+ has_rdoc: true
86
+ homepage: http://github.com/fastly/Pauper
87
+ licenses: []
88
+
89
+ post_install_message:
90
+ rdoc_options: []
91
+
92
+ require_paths:
93
+ - lib
94
+ required_ruby_version: !ruby/object:Gem::Requirement
95
+ requirements:
96
+ - - ">="
97
+ - !ruby/object:Gem::Version
98
+ segments:
99
+ - 0
100
+ version: "0"
101
+ required_rubygems_version: !ruby/object:Gem::Requirement
102
+ requirements:
103
+ - - ">="
104
+ - !ruby/object:Gem::Version
105
+ segments:
106
+ - 0
107
+ version: "0"
108
+ requirements: []
109
+
110
+ rubyforge_project:
111
+ rubygems_version: 1.3.6
112
+ signing_key:
113
+ specification_version: 3
114
+ summary: A semi-sane way to manage a multi-vm dev environment
115
+ test_files: []
116
+