vmit 0.0.3 → 0.0.3.99
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/bin/vmit +89 -13
- data/lib/vmit.rb +25 -18
- data/lib/vmit/autoyast.rb +81 -22
- data/lib/vmit/debian_preseed.rb +35 -1
- data/lib/vmit/install_media.rb +224 -0
- data/lib/vmit/kickstart.rb +44 -8
- data/lib/vmit/libvirt_vm.rb +274 -0
- data/lib/vmit/plugins/bootstrap.rb +33 -17
- data/lib/vmit/registry.rb +208 -0
- data/lib/vmit/unattended_install.rb +43 -0
- data/lib/vmit/utils.rb +51 -1
- data/lib/vmit/version.rb +1 -1
- data/lib/vmit/workspace.rb +181 -0
- data/test/data/registry.yml +5 -0
- data/test/data/registry/key1 +1 -0
- data/test/data/registry/key2 +1 -0
- data/test/data/registry/key4/key1 +1 -0
- data/test/install_media_test.rb +67 -0
- data/test/registry_test.rb +118 -0
- data/test/{bootstrap_test.rb → workspace_test.rb} +23 -19
- data/vmit.gemspec +4 -1
- metadata +46 -19
- data/bin/vmit-ifdown +0 -10
- data/bin/vmit-ifup +0 -18
- data/lib/vmit/bootstrap.rb +0 -273
- data/lib/vmit/virtual_machine.rb +0 -299
data/lib/vmit/kickstart.rb
CHANGED
@@ -18,17 +18,53 @@
|
|
18
18
|
# IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
19
19
|
# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
20
20
|
#
|
21
|
-
require 'vmit'
|
22
21
|
require 'erb'
|
22
|
+
require 'vmit/unattended_install'
|
23
|
+
require 'vmit/vfs'
|
23
24
|
|
24
25
|
module Vmit
|
25
26
|
|
26
|
-
class Kickstart
|
27
|
+
class Kickstart < UnattendedInstall
|
27
28
|
|
28
|
-
|
29
|
+
def initialize(location)
|
30
|
+
super(location)
|
29
31
|
|
30
|
-
|
31
|
-
|
32
|
+
media = Vmit::VFS.from(location)
|
33
|
+
case media
|
34
|
+
when Vmit::VFS::URI
|
35
|
+
@install = location
|
36
|
+
when Vmit::VFS::ISO
|
37
|
+
@install = :cdrom
|
38
|
+
vm.config.configure(:cdrom => location.to_s)
|
39
|
+
else raise ArgumentError.new("Unsupported autoinstallation: #{location}")
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
def execute_autoinstall(vm, args)
|
44
|
+
vm.config.push!
|
45
|
+
begin
|
46
|
+
vm.config.configure(args)
|
47
|
+
if @install == :cdrom
|
48
|
+
vm.config.configure(:cdrom => location.to_s)
|
49
|
+
end
|
50
|
+
|
51
|
+
Dir.mktmpdir do |floppy_dir|
|
52
|
+
FileUtils.chmod_R 0755, floppy_dir
|
53
|
+
vm.config.floppy = floppy_dir
|
54
|
+
vm.config.add_kernel_cmdline!('ks=floppy')
|
55
|
+
vm.config.add_kernel_cmdline!("repo=#{@install}")
|
56
|
+
vm.config.reboot = false
|
57
|
+
|
58
|
+
File.write(File.join(floppy_dir, 'ks.cfg'), to_ks_script)
|
59
|
+
Vmit.logger.info "Kickstart: 1st stage."
|
60
|
+
vm.up
|
61
|
+
vm.wait_until_shutdown! do
|
62
|
+
vm.vnc
|
63
|
+
end
|
64
|
+
end
|
65
|
+
ensure
|
66
|
+
vm.config.pop!
|
67
|
+
end
|
32
68
|
end
|
33
69
|
|
34
70
|
def to_ks_script
|
@@ -41,10 +77,10 @@ keyboard us
|
|
41
77
|
timezone --utc America/New_York
|
42
78
|
bootloader --location=mbr --driveorder=sda --append="rhgb quiet"
|
43
79
|
install
|
44
|
-
<% if install.is_a?(String) || install.is_a?(::URI)%>
|
45
|
-
url --url=<%= install.to_s.strip %>
|
80
|
+
<% if @install.is_a?(String) || @install.is_a?(::URI)%>
|
81
|
+
url --url=<%= @install.to_s.strip %>
|
46
82
|
<% else %>
|
47
|
-
<%= install %>
|
83
|
+
<%= @install %>
|
48
84
|
<% end %>
|
49
85
|
network --device eth0 --bootproto dhcp
|
50
86
|
zerombr yes
|
@@ -0,0 +1,274 @@
|
|
1
|
+
require 'confstruct'
|
2
|
+
require 'libvirt'
|
3
|
+
require 'nokogiri'
|
4
|
+
require 'vmit/workspace'
|
5
|
+
|
6
|
+
module Vmit
|
7
|
+
|
8
|
+
class LibvirtVM
|
9
|
+
|
10
|
+
attr_reader :workspace
|
11
|
+
attr_reader :config
|
12
|
+
attr_reader :conn
|
13
|
+
|
14
|
+
def self.from_pwd(opts={})
|
15
|
+
workspace = Vmit::Workspace.from_pwd
|
16
|
+
LibvirtVM.new(workspace, opts)
|
17
|
+
end
|
18
|
+
|
19
|
+
# @param [Vmit::Workspace] workspace to run
|
20
|
+
# @param [Hash] runtime options that override
|
21
|
+
# the virtual machine options
|
22
|
+
def initialize(workspace, opts={})
|
23
|
+
@workspace = workspace
|
24
|
+
@config = Confstruct::Configuration.new(@workspace.config)
|
25
|
+
@config.configure(opts)
|
26
|
+
|
27
|
+
@conn = ::Libvirt::open("qemu:///system")
|
28
|
+
if not @conn
|
29
|
+
raise 'Can\'t initialize hypervisor'
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
# @return [Libvirt::Domain] returns the libvirt domain
|
34
|
+
# or creates one for the workspace if it does not exist
|
35
|
+
def domain
|
36
|
+
conn.lookup_domain_by_name(workspace.name) rescue nil
|
37
|
+
end
|
38
|
+
|
39
|
+
def up
|
40
|
+
unless down?
|
41
|
+
Vmit.logger.error "#{workspace.name} is already up. Run 'vmit ssh' or 'vmit vnc' to access it."
|
42
|
+
return
|
43
|
+
end
|
44
|
+
|
45
|
+
Vmit.logger.debug "\n#{conn.capabilities}"
|
46
|
+
Vmit.logger.info "Starting VM..."
|
47
|
+
|
48
|
+
network = conn.lookup_network_by_name('default')
|
49
|
+
Vmit.logger.debug "\n#{network.xml_desc}"
|
50
|
+
if not network.active?
|
51
|
+
network.create
|
52
|
+
end
|
53
|
+
Vmit.logger.debug "\n#{self.to_libvirt_xml}"
|
54
|
+
|
55
|
+
puts domain.inspect
|
56
|
+
domain.destroy if domain
|
57
|
+
if domain.nil?
|
58
|
+
conn.create_domain_xml(self.to_libvirt_xml)
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
def state
|
63
|
+
assert_up
|
64
|
+
st, reason = domain.state
|
65
|
+
st_sym = case st
|
66
|
+
when Libvirt::Domain::NOSTATE then :unknown
|
67
|
+
when Libvirt::Domain::RUNNING then :running
|
68
|
+
when Libvirt::Domain::BLOCKED then :blocked
|
69
|
+
when Libvirt::Domain::PAUSED then :paused
|
70
|
+
when Libvirt::Domain::SHUTDOWN then :shutdown
|
71
|
+
when Libvirt::Domain::SHUTOFF then :shutoff
|
72
|
+
when Libvirt::Domain::CRASHED then :crashed
|
73
|
+
when Libvirt::Domain::PMSUSPENDED then :pmsuspended
|
74
|
+
end
|
75
|
+
return st_sym, reason
|
76
|
+
end
|
77
|
+
|
78
|
+
def up?
|
79
|
+
!domain.nil? && domain.active?
|
80
|
+
end
|
81
|
+
|
82
|
+
def assert_up
|
83
|
+
unless up?
|
84
|
+
raise "VM is not running. Try 'vmit up'..."
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
def assert_down
|
89
|
+
if up?
|
90
|
+
raise "VM is running. Try 'vmit down'..."
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
def down?
|
95
|
+
!up?
|
96
|
+
end
|
97
|
+
|
98
|
+
def reboot
|
99
|
+
assert_up
|
100
|
+
domain.reboot
|
101
|
+
end
|
102
|
+
|
103
|
+
def shutdown
|
104
|
+
assert_up
|
105
|
+
domain.shutdown
|
106
|
+
end
|
107
|
+
|
108
|
+
def destroy
|
109
|
+
if domain
|
110
|
+
domain.destroy
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
114
|
+
# Waits until the machine is shutdown
|
115
|
+
# executing the passed block.
|
116
|
+
#
|
117
|
+
# If the machine is shutdown, the
|
118
|
+
# block will be killed. If the block
|
119
|
+
# exits, the machine will be stopped
|
120
|
+
# immediately (domain destroyed)
|
121
|
+
#
|
122
|
+
# @example
|
123
|
+
# vm.wait_until_shutdown! do
|
124
|
+
# vm.vnc
|
125
|
+
# end
|
126
|
+
#
|
127
|
+
def wait_until_shutdown!(&block)
|
128
|
+
chars = %w{ | / - \\ }
|
129
|
+
thread = Thread.new(&block)
|
130
|
+
thread.abort_on_exception = true
|
131
|
+
|
132
|
+
Vmit.logger.info "Waiting for machine..."
|
133
|
+
while true
|
134
|
+
print chars[0]
|
135
|
+
|
136
|
+
if down?
|
137
|
+
Thread.kill(thread)
|
138
|
+
return
|
139
|
+
end
|
140
|
+
if not thread.alive?
|
141
|
+
domain.destroy
|
142
|
+
end
|
143
|
+
sleep(1)
|
144
|
+
print "\b"
|
145
|
+
chars.push chars.shift
|
146
|
+
end
|
147
|
+
|
148
|
+
end
|
149
|
+
|
150
|
+
def ip_address
|
151
|
+
File.open('/var/lib/libvirt/dnsmasq/default.leases') do |f|
|
152
|
+
f.each_line do |line|
|
153
|
+
parts = line.split(' ')
|
154
|
+
if parts[1] == config.mac_address
|
155
|
+
return parts[2]
|
156
|
+
end
|
157
|
+
end
|
158
|
+
end
|
159
|
+
nil
|
160
|
+
end
|
161
|
+
|
162
|
+
def spice_address
|
163
|
+
assert_up
|
164
|
+
doc = Nokogiri::XML(domain.xml_desc)
|
165
|
+
port = doc.xpath("//graphics[@type='spice']/@port")
|
166
|
+
listen = doc.xpath("//graphics[@type='spice']/listen[@type='address']/@address")
|
167
|
+
return listen, port
|
168
|
+
end
|
169
|
+
|
170
|
+
def vnc_address
|
171
|
+
assert_up
|
172
|
+
doc = Nokogiri::XML(domain.xml_desc)
|
173
|
+
port = doc.xpath("//graphics[@type='vnc']/@port")
|
174
|
+
listen = doc.xpath("//graphics[@type='vnc']/listen[@type='address']/@address")
|
175
|
+
"#{listen}:#{port}"
|
176
|
+
end
|
177
|
+
|
178
|
+
# synchronus spice viewer
|
179
|
+
def spice
|
180
|
+
assert_up
|
181
|
+
addr, port = spice_address
|
182
|
+
raise "Can't get the SPICE information from the VM" unless addr
|
183
|
+
system("spicec --host #{addr} --port #{port}")
|
184
|
+
end
|
185
|
+
|
186
|
+
# synchronus vnc viewer
|
187
|
+
def vnc
|
188
|
+
assert_up
|
189
|
+
addr = vnc_address
|
190
|
+
raise "Can't get the VNC information from the VM" unless addr
|
191
|
+
system("vncviewer #{addr}")
|
192
|
+
end
|
193
|
+
|
194
|
+
def [](key)
|
195
|
+
if @runtime_opts.has_key?(key)
|
196
|
+
@runtime_opts[key]
|
197
|
+
else
|
198
|
+
workspace[key]
|
199
|
+
end
|
200
|
+
end
|
201
|
+
|
202
|
+
def to_libvirt_xml
|
203
|
+
builder = Nokogiri::XML::Builder.new do |xml|
|
204
|
+
xml.domain(:type => 'kvm') {
|
205
|
+
xml.name workspace.name
|
206
|
+
xml.uuid config.uuid
|
207
|
+
match = /([0-9+])([^0-9+])/.match config.memory
|
208
|
+
xml.memory(match[1], :unit => match[2])
|
209
|
+
xml.vcpu 1
|
210
|
+
xml.os {
|
211
|
+
xml.type('hvm', :arch => 'x86_64')
|
212
|
+
if config.lookup!('kernel')
|
213
|
+
xml.kernel config.kernel
|
214
|
+
if config.lookup!('kernel_cmdline')
|
215
|
+
xml.cmdline config.kernel_cmdline.join(' ')
|
216
|
+
end
|
217
|
+
end
|
218
|
+
xml.initrd config.initrd if config.lookup!('initrd')
|
219
|
+
xml.boot(:dev => 'cdrom') if config.lookup!('cdrom')
|
220
|
+
}
|
221
|
+
# for shutdown to work
|
222
|
+
xml.features {
|
223
|
+
xml.acpi
|
224
|
+
}
|
225
|
+
#xml.on_poweroff 'destroy'
|
226
|
+
unless config.lookup!('reboot').nil? || config.lookup!('reboot')
|
227
|
+
xml.on_reboot 'destroy'
|
228
|
+
end
|
229
|
+
#xml.on_crash 'destroy'
|
230
|
+
#xml.on_lockfailure 'poweroff'
|
231
|
+
|
232
|
+
xml.devices {
|
233
|
+
xml.emulator '/usr/bin/qemu-kvm'
|
234
|
+
xml.channel(:type => 'spicevmc') do
|
235
|
+
xml.target(:type => 'virtio', :name => 'com.redhat.spice.0')
|
236
|
+
end
|
237
|
+
|
238
|
+
xml.disk(:type => 'file', :device => 'disk') {
|
239
|
+
xml.driver(:name => 'qemu', :type => 'qcow2')
|
240
|
+
xml.source(:file => workspace.current_image)
|
241
|
+
if config.virtio
|
242
|
+
xml.target(:dev => 'sda', :bus => 'virtio')
|
243
|
+
else
|
244
|
+
xml.target(:dev => 'sda', :bus => 'ide')
|
245
|
+
end
|
246
|
+
}
|
247
|
+
if config.lookup!('cdrom')
|
248
|
+
xml.disk(:type => 'file', :device => 'cdrom') {
|
249
|
+
xml.source(:file => config.cdrom)
|
250
|
+
xml.target(:dev => 'hdc')
|
251
|
+
xml.readonly
|
252
|
+
}
|
253
|
+
end
|
254
|
+
if config.lookup!('floppy')
|
255
|
+
xml.disk(:type => 'dir', :device => 'floppy') {
|
256
|
+
xml.source(:dir => config.floppy)
|
257
|
+
xml.target(:dev => 'fda')
|
258
|
+
xml.readonly
|
259
|
+
}
|
260
|
+
end
|
261
|
+
xml.graphics(:type => 'vnc', :autoport => 'yes')
|
262
|
+
xml.graphics(:type => 'spice', :autoport => 'yes')
|
263
|
+
xml.interface(:type => 'network') {
|
264
|
+
xml.source(:network => 'default')
|
265
|
+
xml.mac(:address => config.mac_address)
|
266
|
+
}
|
267
|
+
}
|
268
|
+
}
|
269
|
+
end
|
270
|
+
builder.to_xml
|
271
|
+
end
|
272
|
+
|
273
|
+
end
|
274
|
+
end
|
@@ -18,8 +18,6 @@
|
|
18
18
|
# IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
19
19
|
# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
20
20
|
#
|
21
|
-
require 'vmit/autoyast'
|
22
|
-
require 'vmit/kickstart'
|
23
21
|
require 'abstract_method'
|
24
22
|
require 'clamp'
|
25
23
|
require 'net/http'
|
@@ -46,34 +44,52 @@ module Vmit
|
|
46
44
|
disk_size
|
47
45
|
end
|
48
46
|
|
49
|
-
|
47
|
+
option ['-F', '--packages'], "PACKAGES",
|
48
|
+
"Add packages. Either a file with one package name per line or a
|
49
|
+
comma separated list", :default => [] do |pkgs|
|
50
|
+
case
|
51
|
+
when File.exist?(pkgs)
|
52
|
+
begin
|
53
|
+
File.read(pkgs).each_line.to_a.map(&:strip)
|
54
|
+
rescue
|
55
|
+
raise ArgumentError, "Can't read package list from #{pkgs}"
|
56
|
+
end
|
57
|
+
else
|
58
|
+
list = pkgs.split(',')
|
59
|
+
if list.empty?
|
60
|
+
raise ArgumentError, "Not a valid comma separated list of packages"
|
61
|
+
end
|
62
|
+
list
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
parameter "LOCATION ...", "Repository URL, ISO image or distribution name"
|
50
67
|
|
51
68
|
def execute
|
52
69
|
Vmit.logger.info 'Starting bootstrap'
|
53
|
-
|
54
|
-
vm = Vmit::VirtualMachine.new(curr_dir)
|
70
|
+
workspace = Vmit::Workspace.from_pwd
|
55
71
|
|
56
72
|
Vmit.logger.info ' Deleting old images'
|
57
73
|
FileUtils.rm_f(Dir.glob('*.qcow2'))
|
58
74
|
opts = {}
|
59
75
|
opts[:disk_size] = disk_size if disk_size
|
60
|
-
|
61
|
-
|
76
|
+
workspace.disk_image_init!(opts)
|
77
|
+
workspace.save_config!
|
62
78
|
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
end
|
79
|
+
location = location_list.join(' ')
|
80
|
+
install_media = Vmit::InstallMedia.scan(location)
|
81
|
+
|
82
|
+
Vmit.logger.info "Install media: #{install_media}"
|
68
83
|
|
69
|
-
|
70
|
-
|
71
|
-
else
|
72
|
-
raise "Can't bootstrap from #{location}"
|
84
|
+
packages.each do |pkg|
|
85
|
+
install_media.unattended_install.config.add_packages!(pkg)
|
73
86
|
end
|
74
87
|
|
88
|
+
vm = Vmit::LibvirtVM.from_pwd
|
89
|
+
install_media.autoinstall(vm)
|
90
|
+
|
75
91
|
Vmit.logger.info 'Creating snapshot of fresh system.'
|
76
|
-
|
92
|
+
workspace.disk_snapshot!
|
77
93
|
Vmit.logger.info 'Bootstraping done. Call vmit run to start your system.'
|
78
94
|
end
|
79
95
|
|
@@ -0,0 +1,208 @@
|
|
1
|
+
#
|
2
|
+
# Copyright (C) 2013 Duncan Mac-Vicar P. <dmacvicar@suse.de>
|
3
|
+
#
|
4
|
+
# Permission is hereby granted, free of charge, to any person obtaining a copy of
|
5
|
+
# this software and associated documentation files (the "Software"), to deal in
|
6
|
+
# the Software without restriction, including without limitation the rights to
|
7
|
+
# use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
8
|
+
# the Software, and to permit persons to whom the Software is furnished to do so,
|
9
|
+
# subject to the following conditions:
|
10
|
+
#
|
11
|
+
# The above copyright notice and this permission notice shall be included in all
|
12
|
+
# copies or substantial portions of the Software.
|
13
|
+
#
|
14
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
15
|
+
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
16
|
+
# FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
17
|
+
# COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
18
|
+
# IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
19
|
+
# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
20
|
+
#
|
21
|
+
require 'yaml'
|
22
|
+
|
23
|
+
module Vmit
|
24
|
+
|
25
|
+
class Registry
|
26
|
+
include Enumerable
|
27
|
+
end
|
28
|
+
|
29
|
+
# Wraps a registry providing buffered
|
30
|
+
# semantics. All writes are buffered
|
31
|
+
# until +save!+ is called.
|
32
|
+
#
|
33
|
+
# Changes in the wrapped registry for
|
34
|
+
# already read keys are not reflected
|
35
|
+
# until +reload!+ is called.
|
36
|
+
#
|
37
|
+
# reg = ufferedRegistry.new(backing_registry)
|
38
|
+
# reg[:key1] = "value" # backing_registry not changed
|
39
|
+
# reg.save! # backing_registry changed
|
40
|
+
#
|
41
|
+
class BufferedRegistry < Registry
|
42
|
+
|
43
|
+
def initialize(registry)
|
44
|
+
@buffer = Hash.new
|
45
|
+
@registry = registry
|
46
|
+
end
|
47
|
+
|
48
|
+
def [](key)
|
49
|
+
if not @buffer.has_key?(key)
|
50
|
+
@buffer[key] = @registry[key]
|
51
|
+
end
|
52
|
+
@buffer[key]
|
53
|
+
end
|
54
|
+
|
55
|
+
def []=(key, val)
|
56
|
+
@buffer[key] = val
|
57
|
+
end
|
58
|
+
|
59
|
+
def save!
|
60
|
+
@buffer.each do |key, val|
|
61
|
+
@registry[key] = val
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
def reload!
|
66
|
+
@buffer.keys.each do |key|
|
67
|
+
@buffer[key] = @registry[key]
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
end
|
72
|
+
|
73
|
+
# Add types to keys
|
74
|
+
# You need to inherit from this class
|
75
|
+
#
|
76
|
+
# @example
|
77
|
+
# class MyTypes < TypedRegistry
|
78
|
+
# type :key1, Fixnum
|
79
|
+
# type :key2, Float
|
80
|
+
# end
|
81
|
+
#
|
82
|
+
# reg = MyTypes.new(backing_registry)
|
83
|
+
#
|
84
|
+
class TypedRegistry < Registry
|
85
|
+
|
86
|
+
class << self
|
87
|
+
def type(key, t=nil)
|
88
|
+
@type_info ||= Hash.new
|
89
|
+
@type_info[key] = t unless t.nil?
|
90
|
+
@type_info[key]
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
def initialize(registry)
|
95
|
+
@registry = registry
|
96
|
+
end
|
97
|
+
|
98
|
+
def type(key)
|
99
|
+
self.class.type(key)
|
100
|
+
end
|
101
|
+
|
102
|
+
def [](key)
|
103
|
+
rawval = @registry[key]
|
104
|
+
case type(key).to_s
|
105
|
+
when 'String' then rawval.to_s
|
106
|
+
when 'Fixnum' then rawval.to_i
|
107
|
+
when 'Float' then rawval.to_f
|
108
|
+
else rawval
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
def []=(key, val)
|
113
|
+
if type(key)
|
114
|
+
unless val.is_a?(type(key))
|
115
|
+
raise TypeError.new("Expected #{type(key)} for #{key}")
|
116
|
+
end
|
117
|
+
end
|
118
|
+
@registry[key] = val
|
119
|
+
end
|
120
|
+
end
|
121
|
+
|
122
|
+
# Takes configuration options from a yml
|
123
|
+
# file.
|
124
|
+
class YamlRegistry < Registry
|
125
|
+
def initialize(file_path)
|
126
|
+
@file_path = file_path
|
127
|
+
reload!
|
128
|
+
end
|
129
|
+
|
130
|
+
def reload!
|
131
|
+
@data = YAML::load(File.read(@file_path))
|
132
|
+
end
|
133
|
+
|
134
|
+
def save!
|
135
|
+
File.write(@file_path, @data.to_yaml)
|
136
|
+
end
|
137
|
+
|
138
|
+
def [](key)
|
139
|
+
# YAML uses strings for keys
|
140
|
+
# we use symbols.
|
141
|
+
if @data.has_key?(key)
|
142
|
+
@data[key]
|
143
|
+
else
|
144
|
+
@data[key.to_s]
|
145
|
+
end
|
146
|
+
end
|
147
|
+
|
148
|
+
def []=(key, val)
|
149
|
+
@data[key.to_s] = val
|
150
|
+
save!
|
151
|
+
reload!
|
152
|
+
end
|
153
|
+
|
154
|
+
def each(&block)
|
155
|
+
Enumerator.new do |enum|
|
156
|
+
@data.each do |key, val|
|
157
|
+
enum.yield key.to_sym, val
|
158
|
+
end
|
159
|
+
end
|
160
|
+
end
|
161
|
+
|
162
|
+
def keys
|
163
|
+
each.to_a.map(&:first)
|
164
|
+
end
|
165
|
+
end
|
166
|
+
|
167
|
+
# Takes configuration options from a
|
168
|
+
# filesystem tree where the files are
|
169
|
+
# the keys and the content the values
|
170
|
+
class FilesystemRegistry < Registry
|
171
|
+
def initialize(base_path)
|
172
|
+
@base_path = base_path
|
173
|
+
end
|
174
|
+
|
175
|
+
def [](key)
|
176
|
+
begin
|
177
|
+
path = File.join(@base_path, key.to_s)
|
178
|
+
unless File.directory?(path)
|
179
|
+
File.read(path)
|
180
|
+
else
|
181
|
+
return FilesystemRegistry.new(File.join(@base_path, path))
|
182
|
+
end
|
183
|
+
rescue Errno::ENOENT
|
184
|
+
nil
|
185
|
+
end
|
186
|
+
end
|
187
|
+
|
188
|
+
def []=(key, val)
|
189
|
+
File.write(File.join(@base_path, key.to_s), val)
|
190
|
+
end
|
191
|
+
|
192
|
+
def each(&block)
|
193
|
+
Enumerator.new do |enum|
|
194
|
+
Dir.entries(@base_path).reject do |elem|
|
195
|
+
['.', '..'].include?(elem)
|
196
|
+
end.each do |key|
|
197
|
+
enum.yield key.to_sym, self[key]
|
198
|
+
end
|
199
|
+
end
|
200
|
+
end
|
201
|
+
|
202
|
+
def keys
|
203
|
+
each.to_a.map(&:first)
|
204
|
+
end
|
205
|
+
|
206
|
+
end
|
207
|
+
|
208
|
+
end
|