fwissr 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ data.tar.gz: 933e4bc006d16954c87cc4f7b1290ec7fde652a4
4
+ metadata.gz: 2213070acd5d61baf5ebb2e5ae1188547aafb402
5
+ SHA512:
6
+ data.tar.gz: 42223b4c419f6c65e4ff88f35abfd14d787448bc79fb3aebdd1ab40746764e7542a42d10a4730f70aa86212192af708220dd4c6344cbc860afb79e088bd12fae
7
+ metadata.gz: 7f223d8a1d5f91a943b0fb0ac51fcc862a448f4279fa5479db8319a7441d20c36bb558f6eb8b2bd841bb1c6a37d4d3d4c29bdfc603a0cc5cdeea73e94ce052dc
data/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2013 Fotonauts
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy of
6
+ this software and associated documentation files (the "Software"), to deal in
7
+ the Software without restriction, including without limitation the rights to
8
+ use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
9
+ the Software, and to permit persons to whom the Software is furnished to do so,
10
+ subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
17
+ FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
18
+ COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
19
+ IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
20
+ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,384 @@
1
+ Fwissr
2
+ ======
3
+
4
+ A simple configuration registry tool by Fotonauts.
5
+
6
+
7
+ Install
8
+ =======
9
+
10
+ ```bash
11
+ $ [sudo] gem install fwissr
12
+ ```
13
+
14
+ Or add it to your `Gemfile`.
15
+
16
+
17
+ Usage
18
+ =====
19
+
20
+ Create the main `fwissr.json` configuration file in either `/etc/fwissr/` or `~/.fwissr/` directory:
21
+
22
+ ```json
23
+ {
24
+ "foo" : "bar",
25
+ "horn" : { "loud" : true, "sounds": [ "TUuuUuuuu", "tiiiiiiIIiii" ] }
26
+ }
27
+ ```
28
+
29
+ In your application, you can access `fwissr`'s global registry that way:
30
+
31
+ ```ruby
32
+ require 'fwissr'
33
+
34
+ Fwissr['/foo']
35
+ # => "bar"
36
+
37
+ Fwissr['/horn']
38
+ # => { "loud" => true, "sounds" => [ "TUuuUuuuu", "tiiiiiiIIiii" ] }
39
+
40
+ Fwissr['/horn/loud']
41
+ # => true
42
+
43
+ Fwissr['/horn/sounds']
44
+ # => [ "TUuuUuuuu", "tiiiiiiIIiii" ]
45
+ ```
46
+
47
+ In bash you can call the `fwissr` tool:
48
+
49
+ ```bash
50
+ $ fwissr /foo
51
+ bar
52
+
53
+ # json output
54
+ $ fwissr -j /horn
55
+ { "loud" : true, "sounds": [ "TUuuUuuuu", "tiiiiiiIIiii" ] }
56
+
57
+ # pretty print json output
58
+ $ fwissr -j -p /horn
59
+ {
60
+ "loud": true,
61
+ "sound": [
62
+ "TUuuUuuuu",
63
+ "tiiiiiiIIiii"
64
+ ]
65
+ }
66
+
67
+ # dump registry with pretty print json output
68
+ # NOTE: yes, that's the same as 'fwissr -jp /'
69
+ $ fwissr --dump -jp
70
+ {
71
+ "horn": {
72
+ "loud": true,
73
+ "sound": [
74
+ "TUuuUuuuu",
75
+ "tiiiiiiIIiii"
76
+ ]
77
+ }
78
+ }
79
+ ```
80
+
81
+
82
+ Additional configuration file
83
+ =============================
84
+
85
+ In addition to the main `fwissr.json` configuration file, all files in `/etc/fwissr/` and `~/.fwissr/` directories are automatically loaded. The settings for these additional configurations are prefixed with the file name.
86
+
87
+ You can provide more configuration file locations with the `fwissr_sources` setting in `fwissr.json`:
88
+
89
+ ```json
90
+ {
91
+ "fwissr_sources": [
92
+ { "filepath": "/etc/my_app.json" }
93
+ ]
94
+ }
95
+ ```
96
+
97
+ For example, with that `/etc/my_app.json`:
98
+
99
+ ```json
100
+ { "foo": "bar", "bar": "baz" }
101
+ ```
102
+
103
+ settings are accessed that way:
104
+
105
+ ```ruby
106
+ require 'fwissr'
107
+
108
+ Fwissr['/my_app']
109
+ # => { "foo" => "bar", "bar" => "baz" }
110
+
111
+ Fwissr['/my_app/foo']
112
+ # => "bar"
113
+
114
+ Fwissr['/my_app/bar']
115
+ # => "baz"
116
+ ```
117
+
118
+ You can bypass that behaviour with the `top_level` setting:
119
+
120
+ ```json
121
+ {
122
+ "fwissr_sources": [
123
+ { "filepath": "/etc/my_app.json", "top_level": true }
124
+ ]
125
+ }
126
+ ```
127
+
128
+ With the `top_level` setting activated the configuration settings are added to registry root:
129
+
130
+ ```ruby
131
+ require 'fwissr'
132
+
133
+ Fwissr['/']
134
+ # => { "foo" => "bar", "bar" => "baz" }
135
+
136
+ Fwissr['/foo']
137
+ # => "bar"
138
+
139
+ Fwissr['/bar']
140
+ # => "baz"
141
+ ```
142
+
143
+ Fwissr supports `.json` and `.yaml` configuration files.
144
+
145
+
146
+ Directory of configuration files
147
+ ================================
148
+
149
+ If the `filepath` setting is a directory then all the configuration files in that directory (but NOT in subdirectories) are imported:
150
+
151
+ ```json
152
+ {
153
+ "fwissr_sources": [
154
+ { "filepath": "/mnt/my_app/conf/" },
155
+ ],
156
+ }
157
+ ```
158
+
159
+ With `/mnt/my_app/conf/database.yaml`:
160
+
161
+ ```yaml
162
+ production:
163
+ adapter: mysql2
164
+ encoding: utf8
165
+ database: my_app_db
166
+ username: my_app_user
167
+ password: my_app_pass
168
+ host: db.my_app.com
169
+ ```
170
+
171
+ and `/mnt/my_app/conf/credentials.json`:
172
+
173
+ ```json
174
+ { "key": "i5qw64816c", "code": "448e4wef161" }
175
+ ```
176
+
177
+ settings are accessed that way:
178
+
179
+ ```ruby
180
+ require 'fwissr'
181
+
182
+ Fwissr['/database']
183
+ # => { "production" => { "adapter" => "mysql2", "encoding" => "utf8", "database" => "my_app_db", "username" => "my_app_user", "password" => "my_app_pass", "host" => "db.my_app.com" } }
184
+
185
+ Fwissr['/database/production/host']
186
+ # => "db.my_app.com"
187
+
188
+ Fwissr['/credentials']
189
+ # => { "key" => "i5qw64816c", "code" => "448e4wef161" }
190
+
191
+ Fwissr['/credentials/key']
192
+ # => "i5qw64816c"
193
+ ```
194
+
195
+
196
+ File name mapping to setting path
197
+ =================================
198
+
199
+ Use dots in file name to define a path for configuration settings.
200
+
201
+ For example:
202
+
203
+ ```json
204
+ {
205
+ "fwissr_sources": [
206
+ { "filepath": "/etc/my_app.database.slave.json" }
207
+ ]
208
+ }
209
+ ```
210
+
211
+ with that `/etc/my_app.database.slave.json`:
212
+
213
+ ```json
214
+ { "host": "db.my_app.com", "port": "1337" }
215
+ ```
216
+
217
+ settings are accessed that way:
218
+
219
+ ```ruby
220
+ require 'fwissr'
221
+
222
+ Fwissr['/my_app/database/slave/host']
223
+ # => "db.my_app.com"
224
+
225
+ Fwissr['/my_app/database/slave/port']
226
+ # => "1337"
227
+ ```
228
+
229
+
230
+ Mongodb source
231
+ ==============
232
+
233
+ You can define a mongob collection as a configuration source:
234
+
235
+ ```json
236
+ {
237
+ "fwissr_sources": [
238
+ { "mongodb": "mongodb://db1.example.net/my_app", "collection": "config" }
239
+ ]
240
+ }
241
+ ```
242
+
243
+ Each document in the collection is a setting for that configuration.
244
+
245
+ The `_id` document field is the setting key, and the `value` document field is the setting value.
246
+
247
+ For example:
248
+
249
+ ```
250
+ > db["my_app.stuff"].find()
251
+ { "_id" : "foo", "value" : "bar" }
252
+ { "_id" : "database", "value" : { "host": "db.my_app.com", "port": "1337" } }
253
+ ```
254
+
255
+ ```ruby
256
+ require 'mongo'
257
+ require 'fwissr'
258
+
259
+ Fwissr['/my_app/stuff/foo']
260
+ # => "bar"
261
+
262
+ Fwissr['/my_app/stuff/database']
263
+ # => { "host": "db.my_app.com", "port": "1337" }
264
+
265
+ Fwissr['/my_app/stuff/database/port']
266
+ # => "1337"
267
+ ```
268
+
269
+ As with configuration files you can use dots in collection name to define a path for configuration settings. The `top_level` setting is also supported to bypass that behaviour. Note that the `fwissr` collection is by default a `top_level` configuration.
270
+
271
+ Fwissr supports both the official `mongo` ruby driver and the `mongoid`'s `moped` driver. Don't forget to require one of these gems.
272
+
273
+
274
+ Refreshing registry
275
+ ===================
276
+
277
+ Enable registry auto-update with the `refresh` source setting.
278
+
279
+ For example:
280
+
281
+ ```json
282
+ {
283
+ "fwissr_sources": [
284
+ { "filepath": "/etc/my_app/my_app.json" },
285
+ { "filepath": "/etc/my_app/stuff.json", "refresh": true },
286
+ { "mongodb": "mongodb://db1.example.net/my_app", "collection": "production" },
287
+ { "mongodb": "mongodb://db1.example.net/my_app", "collection": "config", "refresh": true }
288
+ ]
289
+ }
290
+ ```
291
+
292
+ The `/etc/my_app/my_app.json` configuration file and the `production` mongodb collection are read once, whereas the settings holded by the `/etc/my_app/stuff.json` configuration file and the `config` mongodb collection expire periodically and re-fetched.
293
+
294
+ The default freshness is 30 seconds, but you can change it with the `fwissr_refresh_period` setting:
295
+
296
+ ```json
297
+ {
298
+ "fwissr_sources": [
299
+ { "filepath": "/etc/my_app/my_app.json" },
300
+ { "filepath": "/etc/my_app/stuff.json", "refresh": true },
301
+ { "mongodb": "mongodb://db1.example.net/my_app", "collection": "production" },
302
+ { "mongodb": "mongodb://db1.example.net/my_app", "collection": "config", "refresh": true }
303
+ ],
304
+ "fwissr_refresh_period": 60
305
+ }
306
+ ```
307
+
308
+ The refresh is done periodically in a thread:
309
+
310
+ ```ruby
311
+ require 'fwissr'
312
+
313
+ Fwissr['/stuff/foo']
314
+ # => "bar"
315
+
316
+ # > Change '/etc/my_app/stuff.json' file by setting: {"foo":"baz"}
317
+
318
+ # Wait 2 minutes
319
+ sleep(120)
320
+
321
+ # The new value is now in the registry
322
+ Fwissr['/stuff/foo']
323
+ # => "baz"
324
+ ```
325
+
326
+
327
+ Create a custom registry
328
+ ========================
329
+
330
+ `fwissr` is intended to be easy to setup: just create a configuration file and that configuration is accessible via the global registry. But if you need to, you can create your own custom registry.
331
+
332
+ ```ruby
333
+ require 'fwissr'
334
+
335
+ # create a custom registry
336
+ registry = Fwissr::Registry.new('refresh_period' => 20)
337
+
338
+ # add configuration sources to registry
339
+ registry.add_source(Fwissr::Source.from_settings({ 'filepath': '/etc/my_app/my_app.json' }))
340
+ registry.add_source(Fwissr::Source.from_settings({ 'filepath': '/etc/my_app/stuff.json', 'refresh': true }))
341
+ registry.add_source(Fwissr::Source.from_settings({ 'mongodb': 'mongodb://db1.example.net/my_app', 'collection': 'production' }))
342
+ registry.add_source(Fwissr::Source.from_settings({ 'mongodb': 'mongodb://db1.example.net/my_app', 'collection': 'config', 'refresh': true }))
343
+
344
+ registry['/stuff/foo']
345
+ # => 'bar'
346
+ ```
347
+
348
+
349
+ Create a custom source
350
+ ======================
351
+
352
+ Currently `Fwissr::Source::File` and `Fwissr::Source::Mongodb` are the two kinds of possible registry sources, but you can define your own source:
353
+
354
+
355
+ ```ruby
356
+ class MyFwissrSource < Fwissr::Source
357
+
358
+ def initialize(db_handler, options = { })
359
+ super(options)
360
+
361
+ @db_handler = db_handler
362
+ end
363
+
364
+ def fetch_conf
365
+ @db_handler.find('my_conf').to_hash
366
+ # => { 'foo' => [ 'bar', 'baz' ] }
367
+ end
368
+
369
+ end # class MyFwissrSource
370
+
371
+ registry = Fwissr::Registry.new('refresh_period' => 20)
372
+ registry.add_source(MyFwissrSource.new(my_db_handler, 'refresh' => true))
373
+
374
+ registry['/foo']
375
+ # => [ 'bar', 'baz' ]
376
+ ```
377
+
378
+
379
+ Credits
380
+ =======
381
+
382
+ The Fotonauts team: http://www.fotopedia.com
383
+
384
+ Copyright (c) 2013 Fotonauts released under the MIT license.
data/Rakefile ADDED
@@ -0,0 +1,50 @@
1
+ require 'rake'
2
+
3
+ $:.unshift File.expand_path(File.dirname(__FILE__) + "/./lib")
4
+ require 'fwissr'
5
+
6
+ task :default => :list
7
+ task :list do
8
+ system 'rake -T'
9
+ end
10
+
11
+ ##############################################################################
12
+ # SYNTAX CHECKING
13
+ ##############################################################################
14
+ desc 'Check code syntax'
15
+ task :check_syntax do
16
+ `find . -name "*.rb" |xargs -n1 ruby -c |grep -v "Syntax OK"`
17
+ puts "* Done"
18
+ end
19
+
20
+ ##############################################################################
21
+ # Stats
22
+ ##############################################################################
23
+ desc 'Show some stats about the code'
24
+ task :stats do
25
+ line_count = proc do |path|
26
+ Dir[path].collect { |f| File.open(f).readlines.reject { |l| l =~ /(^\s*(\#|\/\*))|^\s*$/ }.size }.inject(0){ |sum,n| sum += n }
27
+ end
28
+ comment_count = proc do |path|
29
+ Dir[path].collect { |f| File.open(f).readlines.select { |l| l =~ /^\s*\#/ }.size }.inject(0) { |sum,n| sum += n }
30
+ end
31
+ lib = line_count['lib/**/*.rb']
32
+ comment = comment_count['lib/**/*.rb']
33
+ ext = line_count['ext/**/*.{c,h}']
34
+ spec = line_count['spec/**/*.rb']
35
+
36
+ comment_ratio = '%1.2f' % (comment.to_f / lib.to_f)
37
+ spec_ratio = '%1.2f' % (spec.to_f / lib.to_f)
38
+
39
+ puts '/======================\\'
40
+ puts '| Part LOC |'
41
+ puts '|======================|'
42
+ puts "| lib #{lib.to_s.ljust(5)}|"
43
+ puts "| lib comments #{comment.to_s.ljust(5)}|"
44
+ puts "| ext #{ext.to_s.ljust(5)}|"
45
+ puts "| spec #{spec.to_s.ljust(5)}|"
46
+ puts '| ratios: |'
47
+ puts "| lib/comment #{comment_ratio.to_s.ljust(5)}|"
48
+ puts "| lib/spec #{spec_ratio.to_s.ljust(5)}|"
49
+ puts '\======================/'
50
+ end
data/bin/fwissr ADDED
@@ -0,0 +1,40 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'rubygems'
4
+ require 'pp'
5
+
6
+ $:.unshift File.expand_path(File.dirname(__FILE__) + "/../lib")
7
+ require 'fwissr'
8
+
9
+ begin
10
+ # parse arguments
11
+ args = Fwissr.parse_args!(ARGV)
12
+
13
+ # get value
14
+ result = if args[:dump]
15
+ Fwissr.dump
16
+ else
17
+ Fwissr.get(args[:key])
18
+ end
19
+
20
+ # display result
21
+ if args[:json]
22
+ if FWISSR_USE_YAJL
23
+ yajl_options = args[:pretty] ? { :pretty => true, :indent => " " } : { }
24
+ puts Yajl::Encoder.encode(result, yajl_options)
25
+ else
26
+ puts args[:pretty] ? JSON.pretty_generate(result) : result.to_json
27
+ end
28
+ elsif args[:inspect]
29
+ if args[:pretty]
30
+ pp result
31
+ else
32
+ puts result.inspect
33
+ end
34
+ else
35
+ puts result
36
+ end
37
+ rescue => ex
38
+ puts "#{ex.message}"
39
+ raise ex
40
+ end
data/lib/fwissr.rb ADDED
@@ -0,0 +1,267 @@
1
+ require 'rubygems'
2
+ require 'optparse'
3
+
4
+ require 'yaml'
5
+
6
+ FWISSR_USE_YAJL = true
7
+
8
+ if FWISSR_USE_YAJL
9
+ require 'yajl'
10
+ else
11
+ require 'json'
12
+ end
13
+
14
+
15
+ require 'fwissr/version'
16
+ require 'fwissr/source'
17
+ require 'fwissr/registry'
18
+
19
+ module Fwissr
20
+
21
+ # default path where main conf file is located
22
+ DEFAULT_MAIN_CONF_PATH = "/etc/fwissr"
23
+
24
+ # default directory (relative to current user's home) where user's main conf file is located
25
+ DEFAULT_MAIN_USER_CONF_DIR = ".fwissr"
26
+
27
+ # main conf file
28
+ MAIN_CONF_FILE = "fwissr.json"
29
+
30
+ class << self
31
+ attr_writer :main_conf_path, :main_user_conf_path
32
+
33
+ # Get config files directory
34
+ def main_conf_path
35
+ @main_conf_path ||= DEFAULT_MAIN_CONF_PATH
36
+ end
37
+
38
+ # Get user's specific config files directory
39
+ def main_user_conf_path
40
+ @main_user_conf_path ||= File.join(Fwissr.find_home, DEFAULT_MAIN_USER_CONF_DIR)
41
+ end
42
+
43
+ # finds the user's home directory
44
+ #
45
+ # Borrowed from rubygems
46
+ def find_home
47
+ ['HOME', 'USERPROFILE'].each do |homekey|
48
+ return ENV[homekey] if ENV[homekey]
49
+ end
50
+
51
+ if ENV['HOMEDRIVE'] && ENV['HOMEPATH'] then
52
+ return "#{ENV['HOMEDRIVE']}:#{ENV['HOMEPATH']}"
53
+ end
54
+
55
+ begin
56
+ File.expand_path("~")
57
+ rescue
58
+ if File::ALT_SEPARATOR then
59
+ "C:/"
60
+ else
61
+ "/"
62
+ end
63
+ end
64
+ end
65
+
66
+ # Parse command line arguments
67
+ def parse_args!(argv)
68
+ args = {
69
+ :inspect => false,
70
+ :json => false,
71
+ :dump => false,
72
+ :pretty => false,
73
+ }
74
+
75
+ # define parser
76
+ opt_parser = OptionParser.new do |opts|
77
+ opts.banner = "Usage: fwissr [-ijph] <key>\nWith key:\n\t#{Fwissr.keys.sort.join("\n\t")}\n\n"
78
+
79
+ opts.define_head "The configuration registry."
80
+
81
+ opts.on("-i", "--inspect", "Returns 'inspected' result") do
82
+ args[:inspect] = true
83
+ end
84
+
85
+ opts.on("-j", "--json", "Returns result in json") do
86
+ args[:json] = true
87
+ end
88
+
89
+ opts.on("--dump", "Dump all keys and values") do
90
+ args[:dump] = true
91
+ end
92
+
93
+ opts.on("-p", "--pretty", "Pretty output") do
94
+ args[:pretty] = true
95
+ end
96
+
97
+ opts.on("-?", "-h", "--help", "Show this help message") do
98
+ puts opts
99
+ exit
100
+ end
101
+
102
+ opts.on_tail("--version", "Show version") do
103
+ puts Fwissr::VERSION
104
+ exit
105
+ end
106
+
107
+ end
108
+
109
+ # parse what we have on the command line
110
+ opt_parser.parse!(argv)
111
+
112
+ # get key
113
+ if argv.empty? && !args[:dump]
114
+ puts "Please specify the key, e.g. 'fwissr /fqdn'"
115
+ puts opt_parser
116
+ exit
117
+ end
118
+
119
+ args[:key] = argv.first unless argv.empty?
120
+
121
+ args
122
+ end
123
+
124
+
125
+ #
126
+ # Global Registry
127
+ #
128
+ #
129
+ # NOTE: Parses main conf files (/etc/fwissr/fwissr.json and ~/.fwissr/fwissr.json) then uses 'fwissr_sources' setting to setup additional sources
130
+ #
131
+ # Example of /etc/fwissr/fwissr.json file:
132
+ #
133
+ # {
134
+ # 'fwissr_sources': [
135
+ # { 'filepath': '/mnt/my_app/conf/' },
136
+ # { 'filepath': '/etc/my_app.json' },
137
+ # { 'mongodb': 'mongodb://db1.example.net/my_app', 'collection': 'config', 'refresh': true },
138
+ # ],
139
+ # 'fwissr_refresh_period': 30,
140
+ # }
141
+ #
142
+
143
+ # access global registry with Fwissr['/foo/bar']
144
+ def global_registry
145
+ @global_registry ||= begin
146
+ result = Fwissr::Registry.new('refresh_period' => self.main_conf['fwissr_refresh_period'])
147
+
148
+ # check main conf files
149
+ if File.exists?(self.main_conf_path) || File.exists?(self.main_user_conf_path)
150
+ # setup main conf files sources
151
+ if File.exists?(self.main_conf_path)
152
+ result.add_source(Fwissr::Source.from_settings({ 'filepath' => self.main_conf_path }))
153
+ end
154
+
155
+ if File.exists?(self.main_user_conf_path)
156
+ result.add_source(Fwissr::Source.from_settings({ 'filepath' => self.main_user_conf_path }))
157
+ end
158
+
159
+ # setup additional sources
160
+ if !self.main_conf['fwissr_sources'].nil?
161
+ self.main_conf['fwissr_sources'].each do |source_setting|
162
+ result.add_source(Fwissr::Source.from_settings(source_setting))
163
+ end
164
+ end
165
+ end
166
+
167
+ result
168
+ end
169
+ end
170
+
171
+ # fetch main fwissr conf
172
+ def main_conf
173
+ @main_conf ||= begin
174
+ result = { }
175
+
176
+ if File.exists?(self.main_conf_file)
177
+ result = self.merge_conf!(result, self.parse_conf_file(self.main_conf_file))
178
+ end
179
+
180
+ if File.exists?(self.main_user_conf_file)
181
+ result = self.merge_conf!(result, self.parse_conf_file(self.main_user_conf_file))
182
+ end
183
+
184
+ result
185
+ end
186
+ end
187
+
188
+ def main_conf_file
189
+ @main_conf_file ||= File.join(self.main_conf_path, MAIN_CONF_FILE)
190
+ end
191
+
192
+ def main_user_conf_file
193
+ @main_user_conf_file ||= File.join(self.main_user_conf_path, MAIN_CONF_FILE)
194
+ end
195
+
196
+ # delegate to global registry
197
+ [ :[], :get ].each do |meth_name|
198
+ class_eval <<-EOS, __FILE__, __LINE__
199
+ def #{meth_name}(key)
200
+ self.global_registry[key]
201
+ end
202
+ EOS
203
+ end
204
+
205
+ [ :keys, :dump ].each do |meth_name|
206
+ class_eval <<-EOS, __FILE__, __LINE__
207
+ def #{meth_name}
208
+ self.global_registry.__send__('#{meth_name}')
209
+ end
210
+ EOS
211
+ end
212
+
213
+
214
+ #
215
+ # Utils
216
+ #
217
+
218
+ def parse_conf_file(conf_file_path)
219
+ conf_file_ext = File.extname(conf_file_path)
220
+
221
+ case conf_file_ext
222
+ when ".json"
223
+ # json file
224
+ if FWISSR_USE_YAJL
225
+ Yajl::Parser.parse(File.read(conf_file_path), :check_utf8 => false)
226
+ else
227
+ JSON.parse(File.read(conf_file_path))
228
+ end
229
+ when ".yaml", ".yml"
230
+ # yaml file
231
+ YAML.load_file(conf_file_path)
232
+ else
233
+ raise "Unsupported conf file kind: #{conf_file_path}"
234
+ end
235
+ end
236
+
237
+ # borrowed from rails
238
+ def merge_conf(to_hash, other_hash)
239
+ self.merge_conf!(to_hash.dup, other_hash)
240
+ end
241
+
242
+ # borrowed from rails
243
+ def merge_conf!(to_hash, other_hash)
244
+ other_hash.each_pair do |k,v|
245
+ tv = to_hash[k]
246
+ to_hash[k] = (tv.is_a?(Hash) && v.is_a?(Hash)) ? self.merge_conf(tv, v) : v
247
+ end
248
+ to_hash
249
+ end
250
+
251
+ # simple deep freezer
252
+ def deep_freeze(obj)
253
+ if obj.is_a?(Hash)
254
+ obj.each do |k, v|
255
+ self.deep_freeze(v)
256
+ end
257
+ elsif obj.is_a?(Array)
258
+ obj.each do |v|
259
+ self.deep_freeze(v)
260
+ end
261
+ end
262
+
263
+ obj.freeze
264
+ end
265
+ end # class << self
266
+
267
+ end # module Fwissr
@@ -0,0 +1,154 @@
1
+ require 'thread'
2
+
3
+ module Fwissr
4
+
5
+ class Registry
6
+
7
+ # refresh period in seconds
8
+ DEFAULT_REFRESH_PERIOD = 30
9
+
10
+ #
11
+ # API
12
+ #
13
+
14
+ attr_reader :refresh_period
15
+
16
+ def initialize(options = { })
17
+ @refresh_period = options['refresh_period'] || DEFAULT_REFRESH_PERIOD
18
+
19
+ @registry = { }
20
+ @sources = [ ]
21
+
22
+ # mutex for @registry and @sources
23
+ @semaphore = Mutex.new
24
+
25
+ @refresh_thread = nil
26
+ end
27
+
28
+ def add_source(source)
29
+ @semaphore.synchronize do
30
+ @sources << source
31
+ end
32
+
33
+ if @registry.frozen?
34
+ # already frozen, must reload everything
35
+ self.reload!
36
+ else
37
+ @semaphore.synchronize do
38
+ Fwissr.merge_conf!(@registry, source.get_conf)
39
+ end
40
+ end
41
+
42
+ self.ensure_refresh_thread
43
+ end
44
+
45
+ def reload!
46
+ self.reset!
47
+ self.load!
48
+ end
49
+
50
+ def get(key)
51
+ # split key
52
+ key_ary = key.split('/')
53
+
54
+ # remove first empty part
55
+ key_ary.shift if (key_ary.first == '')
56
+
57
+ cur_hash = self.registry
58
+ key_ary.each do |key_part|
59
+ cur_hash = cur_hash[key_part]
60
+ return nil if cur_hash.nil?
61
+ end
62
+
63
+ cur_hash
64
+ end
65
+
66
+ alias :[] :get
67
+
68
+ def keys
69
+ result = [ ]
70
+ _keys(result, [ ], self.registry)
71
+ result.sort
72
+ end
73
+
74
+ def dump
75
+ self.registry
76
+ end
77
+
78
+
79
+ #
80
+ # PRIVATE
81
+ #
82
+
83
+ def refresh_thread
84
+ @refresh_thread
85
+ end
86
+
87
+ def have_refreshable_source?
88
+ @semaphore.synchronize do
89
+ !@sources.find { |source| source.can_refresh? }.nil?
90
+ end
91
+ end
92
+
93
+ def ensure_refresh_thread
94
+ # check refresh thread state
95
+ if ((@refresh_period > 0) && self.have_refreshable_source?) && (!@refresh_thread || !@refresh_thread.alive?)
96
+ # (re)start refresh thread
97
+ @refresh_thread = Thread.new do
98
+ while(true) do
99
+ sleep(@refresh_period)
100
+ self.load!
101
+ end
102
+ end
103
+ end
104
+ end
105
+
106
+ def ensure_frozen
107
+ if !@registry.frozen?
108
+ @semaphore.synchronize do
109
+ Fwissr.deep_freeze(@registry)
110
+ end
111
+ end
112
+ end
113
+
114
+ def reset!
115
+ @semaphore.synchronize do
116
+ @registry = { }
117
+
118
+ @sources.each do |source|
119
+ source.reset!
120
+ end
121
+ end
122
+ end
123
+
124
+ def load!
125
+ @semaphore.synchronize do
126
+ @registry = { }
127
+
128
+ @sources.each do |source|
129
+ source_conf = source.get_conf
130
+ Fwissr.merge_conf!(@registry, source_conf)
131
+ end
132
+ end
133
+ end
134
+
135
+ def registry
136
+ self.ensure_refresh_thread
137
+ self.ensure_frozen
138
+
139
+ @registry
140
+ end
141
+
142
+ # helper for #keys
143
+ def _keys(result, key_ary, hash)
144
+ hash.each do |key, value|
145
+ key_ary << key
146
+ result << "/#{key_ary.join('/')}"
147
+ _keys(result, key_ary, value) if value.is_a?(Hash)
148
+ key_ary.pop
149
+ end
150
+ end
151
+
152
+ end # module Registry
153
+
154
+ end # module Fwissr
@@ -0,0 +1,60 @@
1
+ class Fwissr::Source
2
+
3
+ autoload :File, 'fwissr/source/file'
4
+ autoload :Mongodb, 'fwissr/source/mongodb'
5
+
6
+ class << self
7
+ def from_settings(settings)
8
+ raise "Unexpected source settings class: #{settings.inspect}" unless settings.is_a?(Hash)
9
+
10
+ if settings['filepath']
11
+ Fwissr::Source::File.from_settings(settings)
12
+ elsif settings['mongodb']
13
+ Fwissr::Source::Mongodb.from_settings(settings)
14
+ else
15
+ raise "Unexpected source settings kind: #{settings.inspect}"
16
+ end
17
+ end
18
+ end # class << self
19
+
20
+
21
+ #
22
+ # API
23
+ #
24
+
25
+ attr_reader :options
26
+
27
+ def initialize(options = { })
28
+ @options = options
29
+
30
+ @conf = nil
31
+ end
32
+
33
+ # reset source
34
+ def reset!
35
+ @conf = nil
36
+ end
37
+
38
+ # source can be refreshed ?
39
+ def can_refresh?
40
+ @options && (@options['refresh'] == true)
41
+ end
42
+
43
+ # get conf
44
+ def get_conf
45
+ if (@conf && !self.can_refresh?)
46
+ # return already fetched conf if refresh is not allowed
47
+ @conf
48
+ else
49
+ # fetch conf
50
+ @conf = self.fetch_conf
51
+ end
52
+ end
53
+
54
+ # fetch conf from source
55
+ def fetch_conf
56
+ # MUST be implemented by child class
57
+ raise "not implemented"
58
+ end
59
+
60
+ end # class Fwissr::Source
@@ -0,0 +1,88 @@
1
+ class Fwissr::Source::File < Fwissr::Source
2
+
3
+ class << self
4
+
5
+ def from_path(path, options = { })
6
+ if path.nil? || (path == '')
7
+ raise "Unexpected file source path: #{path.inspect}"
8
+ end
9
+
10
+ self.new(path, options)
11
+ end
12
+
13
+ def from_settings(settings)
14
+ options = settings.dup
15
+ options.delete('filepath')
16
+
17
+ self.from_path(settings['filepath'], options)
18
+ end
19
+
20
+ end # class << self
21
+
22
+
23
+ TOP_LEVEL_CONF_FILES = [ 'fwissr' ].freeze
24
+
25
+ attr_reader :path
26
+
27
+ #
28
+ # API
29
+ #
30
+
31
+ def initialize(path, options = { })
32
+ super(options)
33
+
34
+ raise "File not found: #{path}" if !::File.exists?(path)
35
+
36
+ @path = path
37
+ end
38
+
39
+ def fetch_conf
40
+ result = { }
41
+
42
+ conf_files = if ::File.directory?(@path)
43
+ Dir[@path + "/*.{json,yml}"].sort
44
+ else
45
+ [ @path ]
46
+ end
47
+
48
+ conf_files.each do |conf_file_path|
49
+ next unless ::File.file?(conf_file_path)
50
+
51
+ self.merge_conf_file!(result, conf_file_path)
52
+ end
53
+
54
+ result
55
+ end
56
+
57
+
58
+ #
59
+ # PRIVATE
60
+ #
61
+
62
+ def merge_conf_file!(result, conf_file_path)
63
+ # parse conf file
64
+ conf = Fwissr.parse_conf_file(conf_file_path)
65
+ if conf
66
+ conf_file_name = ::File.basename(conf_file_path, ::File.extname(conf_file_path))
67
+
68
+ result_part = result
69
+
70
+ unless TOP_LEVEL_CONF_FILES.include?(conf_file_name) || @options['top_level']
71
+ # merge conf at the correct place in registry
72
+ #
73
+ # eg: my_app.json => /my_app
74
+ # my_app.database.yml => /my_app/database
75
+ # my_app.database.slave.yml => /my_app/database/slave
76
+ key_ary = conf_file_name.split('.')
77
+ key_ary.each do |key_part|
78
+ result_part = (result_part[key_part] ||= { })
79
+ end
80
+ end
81
+
82
+ Fwissr.merge_conf!(result_part, conf)
83
+ else
84
+ raise "Failed to parse conf file: #{conf_file_path}"
85
+ end
86
+ end
87
+
88
+ end # class Fwissr::Source::File
@@ -0,0 +1,180 @@
1
+ require 'uri'
2
+
3
+ begin
4
+ require 'moped'
5
+ rescue LoadError
6
+ begin
7
+ require 'mongo'
8
+ rescue LoadError
9
+ raise "[fwissr] Can't find any suitable mongodb driver: please install 'mongo' or 'moped' gem"
10
+ end
11
+ end
12
+
13
+ class Fwissr::Source::Mongodb < Fwissr::Source
14
+
15
+ class Connection
16
+
17
+ attr_reader :db_name
18
+
19
+ # init
20
+ def initialize(uri)
21
+ raise "URI is missing: #{uri}" if (uri.nil? || uri == '')
22
+
23
+ @uri = uri
24
+ @collections = { }
25
+
26
+ @kind = if defined?(::Moped)
27
+ # moped driver
28
+ :moped
29
+ elsif defined?(::Mongo::MongoClient)
30
+ # mongo ruby driver < 2.0.0
31
+ :mongo
32
+ elsif defined?(::Mongo::Client)
33
+ raise "Sorry, mongo gem >= 2.0 is not supported yet"
34
+ else
35
+ raise "Can't find any suitable mongodb driver: please install 'mongo' or 'moped' gem"
36
+ end
37
+
38
+ # parse URI
39
+ parsed_uri = URI.parse(@uri)
40
+ @db_name = parsed_uri.path[1..-1]
41
+
42
+ if @db_name.nil? || (@db_name == '')
43
+ raise "Missing database in mongodb settings: #{settings['mongodb'].inspect}"
44
+ end
45
+ end
46
+
47
+ def conn
48
+ @conn ||= begin
49
+ case @kind
50
+ when :moped
51
+ ::Moped::Session.connect(@uri)
52
+ when :mongo
53
+ ::Mongo::MongoClient.from_uri(@uri)
54
+ end
55
+ end
56
+ end
57
+
58
+ def collection(col_name)
59
+ @collections[col_name] ||= begin
60
+ case @kind
61
+ when :moped
62
+ self.conn[col_name]
63
+ when :mongo
64
+ self.conn.db(@db_name).collection(col_name)
65
+ end
66
+ end
67
+ end
68
+
69
+ # returns an Enumerator for all documents from given collection
70
+ def fetch(col_name)
71
+ case @kind
72
+ when :moped, :mongo
73
+ self.collection(col_name).find()
74
+ end
75
+ end
76
+
77
+ # insert document in collection
78
+ def insert(col_name, doc)
79
+ case @kind
80
+ when :moped, :mongo
81
+ self.collection(col_name).insert(doc)
82
+ end
83
+ end
84
+
85
+ # create a collection
86
+ def create_collection(col_name)
87
+ case @kind
88
+ when :moped
89
+ # NOOP
90
+ when :mongo
91
+ self.conn.db(@db_name).create_collection(col_name)
92
+ end
93
+ end
94
+
95
+ # drop database
96
+ def drop_database(db_name)
97
+ case @kind
98
+ when :moped
99
+ self.conn.drop
100
+ when :mongo
101
+ self.conn.drop_database(db_name)
102
+ end
103
+ end
104
+ end # class Connection
105
+
106
+ class << self
107
+
108
+ def from_settings(settings)
109
+ if settings['mongodb'].nil? || (settings['mongodb'] == '') || settings['collection'].nil? || (settings['collection'] == '')
110
+ raise "Erroneous mongodb settings: #{settings.inspect}"
111
+ end
112
+
113
+ conn = self.connection_for_uri(settings['mongodb'])
114
+
115
+ options = settings.dup
116
+ options.delete('mongodb')
117
+ options.delete('collection')
118
+
119
+ self.new(conn, settings['collection'], options)
120
+ end
121
+
122
+ def connection_for_uri(uri)
123
+ @connections ||= { }
124
+ @connections[uri] ||= Fwissr::Source::Mongodb::Connection.new(uri)
125
+ end
126
+
127
+ end # class << self
128
+
129
+
130
+ TOP_LEVEL_COLLECTIONS = [ 'fwissr' ].freeze
131
+
132
+ attr_reader :conn, :collection_name
133
+
134
+ #
135
+ # API
136
+ #
137
+
138
+ def initialize(conn, collection_name, options = { })
139
+ super(options)
140
+
141
+ @conn = conn
142
+ @collection_name = collection_name
143
+ end
144
+
145
+ def fetch_conf
146
+ result = { }
147
+ result_part = result
148
+
149
+ unless TOP_LEVEL_COLLECTIONS.include?(@collection_name) || @options['top_level']
150
+ # merge conf at the correct place in registry
151
+ #
152
+ # eg: m_app => /my_app
153
+ # my_app.database => /my_app/database
154
+ # my_app.database.slave => /my_app/database/slave
155
+ key_ary = @collection_name.split('.')
156
+ key_ary.each do |key_part|
157
+ result_part = (result_part[key_part] ||= { })
158
+ end
159
+ end
160
+
161
+ # build conf hash from collection's documents
162
+ conf = { }
163
+ self.conn.fetch(@collection_name).each do |doc|
164
+ key = doc['_id']
165
+ value = if doc['value'].nil?
166
+ doc.delete('_id')
167
+ doc
168
+ else
169
+ doc['value']
170
+ end
171
+
172
+ conf[key] = value
173
+ end
174
+
175
+ Fwissr.merge_conf!(result_part, conf)
176
+
177
+ result
178
+ end
179
+
180
+ end # class Fwissr::Source::Mongodb
@@ -0,0 +1,3 @@
1
+ module Fwissr
2
+ VERSION = '1.0.0'
3
+ end
metadata ADDED
@@ -0,0 +1,78 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: fwissr
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.0
5
+ platform: ruby
6
+ authors:
7
+ - Fotonauts Team
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+
12
+ date: 2013-12-03 00:00:00 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: yajl-ruby
16
+ prerelease: false
17
+ requirement: &id001 !ruby/object:Gem::Requirement
18
+ requirements:
19
+ - &id002
20
+ - ">="
21
+ - !ruby/object:Gem::Version
22
+ version: "0"
23
+ type: :runtime
24
+ version_requirements: *id001
25
+ - !ruby/object:Gem::Dependency
26
+ name: rspec
27
+ prerelease: false
28
+ requirement: &id003 !ruby/object:Gem::Requirement
29
+ requirements:
30
+ - *id002
31
+ type: :development
32
+ version_requirements: *id003
33
+ description: " A simple configuration registry tool by Fotonauts.\n"
34
+ email:
35
+ - aymerick@fotonauts.com
36
+ - oct@fotonauts.com
37
+ executables:
38
+ - fwissr
39
+ extensions: []
40
+
41
+ extra_rdoc_files: []
42
+
43
+ files:
44
+ - LICENSE
45
+ - Rakefile
46
+ - README.md
47
+ - bin/fwissr
48
+ - lib/fwissr/registry.rb
49
+ - lib/fwissr/source/file.rb
50
+ - lib/fwissr/source/mongodb.rb
51
+ - lib/fwissr/source.rb
52
+ - lib/fwissr/version.rb
53
+ - lib/fwissr.rb
54
+ homepage: https://github.com/fotonauts/fwissr
55
+ licenses: []
56
+
57
+ metadata: {}
58
+
59
+ post_install_message:
60
+ rdoc_options: []
61
+
62
+ require_paths:
63
+ - lib
64
+ required_ruby_version: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - *id002
67
+ required_rubygems_version: !ruby/object:Gem::Requirement
68
+ requirements:
69
+ - *id002
70
+ requirements: []
71
+
72
+ rubyforge_project:
73
+ rubygems_version: 2.1.11
74
+ signing_key:
75
+ specification_version: 4
76
+ summary: Fwissr
77
+ test_files: []
78
+