vmit 0.0.3 → 0.0.3.99
Sign up to get free protection for your applications and to get access to all the features.
- 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
|