senv 0.4.2

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,11 @@
1
+ #!/bin/bash
2
+ set -e
3
+
4
+ # Figure out where this script is located.
5
+ SELFDIR="`dirname \"$0\"`"
6
+ SELFDIR="`cd \"$SELFDIR\" && pwd`"
7
+
8
+ # Run the actual app using the bundled Ruby interpreter.
9
+ exec "$SELFDIR/lib/ruby/bin/ruby" "$SELFDIR/lib/app/senv.rb" "$@"
10
+
11
+ # Thanks @FooBarWidget! ref: https://github.com/phusion/traveling-ruby
@@ -0,0 +1,663 @@
1
+ #
2
+ require 'erb'
3
+ require 'yaml'
4
+ require 'json'
5
+ require 'rbconfig'
6
+ require 'pp'
7
+ require 'time'
8
+ require 'fileutils'
9
+ require 'pathname'
10
+ require 'thread'
11
+ require 'openssl'
12
+ require 'tmpdir'
13
+ require 'securerandom'
14
+
15
+ #
16
+ module Senv
17
+ #
18
+ VERSION = '0.4.2'.freeze
19
+
20
+ def Senv.version
21
+ VERSION
22
+ end
23
+
24
+ #
25
+ LICENSE = 'MIT'.freeze
26
+
27
+ def Senv.license
28
+ LICENSE
29
+ end
30
+
31
+ #
32
+ SUMMARY = ''
33
+
34
+ #
35
+ DEFAULT = 'development'.freeze
36
+
37
+ def Senv.default
38
+ DEFAULT
39
+ end
40
+
41
+ #
42
+ class Error < StandardError
43
+ end
44
+
45
+ def Senv.error!(*args, &block)
46
+ raise Error.new(*args, &block)
47
+ end
48
+
49
+ #
50
+ def Senv.env
51
+ ENV['SENV']
52
+ end
53
+
54
+ def Senv.env=(env)
55
+ if env
56
+ ENV['SENV'] = env.to_s.strip
57
+ else
58
+ ENV.delete('SENV')
59
+ end
60
+ end
61
+
62
+ #
63
+ def Senv.load(*args)
64
+ Senv.thread_safe do
65
+ #
66
+ env, options = Senv.parse_load_args(*args)
67
+
68
+ #
69
+ force = !!(options['force'] || options[:force])
70
+
71
+ #
72
+ a_parent_process_has_already_loaded_the_senv = (
73
+ ENV['SENV'] == env &&
74
+ ENV['SENV_LOADED'] &&
75
+ ENV['SENV_ENVIRONMENT']
76
+ )
77
+
78
+ if(a_parent_process_has_already_loaded_the_senv && !force)
79
+ Senv.env = env
80
+ Senv.loaded = JSON.parse(ENV['SENV_LOADED'])
81
+ Senv.environment = JSON.parse(ENV['SENV_ENVIRONMENT'])
82
+ return env
83
+ end
84
+
85
+ #
86
+ unless Senv.loading
87
+ loading = Senv.loading
88
+
89
+ begin
90
+ Senv.loading = env
91
+
92
+ Senv.loaded.clear
93
+ Senv.environment.clear
94
+
95
+ Senv.load_config_paths_for(env)
96
+
97
+ Senv.env = env
98
+
99
+ ENV['SENV'] = Senv.env
100
+ ENV['SENV_LOADED'] = JSON.generate(Senv.loaded)
101
+ ENV['SENV_ENVIRONMENT'] = JSON.generate(Senv.environment)
102
+
103
+ return env
104
+ ensure
105
+ Senv.loading = loading
106
+ end
107
+ else
108
+ a_config_file_imports_another_senv = (
109
+ Senv.loading != env
110
+ )
111
+
112
+ if a_config_file_imports_another_senv
113
+ Senv.load_config_paths_for(env)
114
+ return env
115
+ end
116
+
117
+ a_config_file_imports_itself_recursively = (
118
+ Senv.loading == env
119
+ )
120
+
121
+ if a_config_file_imports_itself_recursively
122
+ :cowardly_refuse_to_infinitely_recurse
123
+ return nil
124
+ end
125
+ end
126
+ end
127
+ end
128
+
129
+ def Senv.load!(*args)
130
+ env, options = Senv.parse_load_args(*args)
131
+ options['force'] = options[:force] = true
132
+ Senv.load(env, options)
133
+ end
134
+
135
+ def Senv.parse_load_args(*args)
136
+ env = Senv.env || Senv.default
137
+ options = Hash.new
138
+
139
+ case args.first
140
+ when String, Symbol
141
+ env = args.shift.to_s
142
+ end
143
+
144
+ case args.first
145
+ when Hash
146
+ options = args.shift
147
+ end
148
+
149
+ [env, options]
150
+ end
151
+
152
+ def Senv.thread_safe(&block)
153
+ THREAD_SAFE.synchronize(&block)
154
+ end
155
+ THREAD_SAFE = ::Monitor.new
156
+
157
+ def Senv.load_config_paths_for(env)
158
+ paths = Senv.config_paths_for(env)
159
+ Senv.load_config_paths(*paths)
160
+ end
161
+
162
+ def Senv.config_paths_for(env)
163
+ glob = "**/#{ env }.{rb,enc.rb}"
164
+
165
+ Senv.directory.glob(glob).sort_by do |path|
166
+ ext = path.basename.extname.split('.')
167
+ [path.basename.to_s.size, ext.size]
168
+ end
169
+ end
170
+
171
+ def Senv.load_config_paths(*paths)
172
+ libs = []
173
+ configs = []
174
+
175
+ paths.each do |path|
176
+ exts = path.extname.split('.')[1..-1]
177
+
178
+ case
179
+ when exts.include?('rb')
180
+ libs << path
181
+ else
182
+ configs << path
183
+ end
184
+ end
185
+
186
+ {
187
+ libs => :load_lib,
188
+ configs => :load_config,
189
+ }.each do |list, loader|
190
+ list.each do |path|
191
+ path = path.to_s
192
+
193
+ Senv.debug({'loading' => path})
194
+
195
+ if Senv.loaded.has_key?(path)
196
+ Senv.debug({'skipping' => path})
197
+ next
198
+ end
199
+
200
+ Senv.loaded[path] = nil
201
+
202
+ captured =
203
+ Senv.capturing_environment_changes do
204
+ Senv.send(loader, path)
205
+ end
206
+
207
+ changes = captured.changes
208
+
209
+ Senv.debug({path => changes})
210
+
211
+ Senv.loaded[path] = changes
212
+
213
+ captured.apply(Senv.environment)
214
+ end
215
+ end
216
+ end
217
+
218
+ #
219
+ def Senv.loading
220
+ @loading
221
+ end
222
+
223
+ def Senv.loading=(env)
224
+ @loading = env.to_s.strip
225
+ end
226
+
227
+ def Senv.loaded
228
+ @loaded ||= {}
229
+ end
230
+
231
+ def Senv.loaded=(hash)
232
+ @loaded = hash
233
+ end
234
+
235
+ def Senv.environment
236
+ @environment ||= {}
237
+ end
238
+
239
+ def Senv.environment=(hash)
240
+ @environment = hash
241
+ end
242
+
243
+ #
244
+ def Senv.realpath(path)
245
+ Pathname.new(path.to_s).realpath
246
+ end
247
+
248
+ def Senv.expand_path(path)
249
+ (realpath(path) rescue File.expand_path(path)).to_s
250
+ end
251
+
252
+ #
253
+ def Senv.root
254
+ determine_root! unless @root
255
+ @root
256
+ end
257
+
258
+ def Senv.root=(root)
259
+ @root = realpath(root)
260
+ end
261
+
262
+ def Senv.determine_root!
263
+ if ENV['SENV_ROOT']
264
+ Senv.root = ENV['SENV_ROOT']
265
+ return @root
266
+ else
267
+ Senv.search_path.each do |dirname|
268
+ if test(?d, dirname)
269
+ Senv.root = dirname
270
+ return @root
271
+ end
272
+ end
273
+ end
274
+
275
+ msg = "[SENV] no `.senv` directory found via `#{ Senv.search_path.join(' | ') }`"
276
+ Senv.error!(msg)
277
+ end
278
+
279
+ def Senv.directory
280
+ Senv.root.join('.senv')
281
+ end
282
+
283
+ def Senv.search_path
284
+ search_path = []
285
+
286
+ if ENV['SENV_PATH']
287
+ ENV['SENV_PATH'].split(':').each do |path|
288
+ search_path << Senv.expand_path(path).to_s
289
+ end
290
+ else
291
+ Pathname.pwd.realpath.ascend do |path|
292
+ search_path << path.to_s
293
+ end
294
+ end
295
+
296
+ search_path
297
+ end
298
+
299
+ def Senv.key_path
300
+ Senv.directory.join('_key')
301
+ end
302
+
303
+ def Senv.key
304
+ if ENV['SENV_KEY']
305
+ ENV['SENV_KEY']
306
+ else
307
+ if Senv.key_path.exist?
308
+ Senv.key_path.binread.strip
309
+ else
310
+ msg = "Senv.key not found in : #{ Senv.key_path }"
311
+ Senv.error!(msg)
312
+ end
313
+ end
314
+ end
315
+
316
+ def Senv.key=(key)
317
+ ENV['SENV_KEY'] = key.to_s.strip
318
+ end
319
+
320
+ def Senv.key_source
321
+ if ENV['SENV_KEY']
322
+ "ENV['SENV_KEY']"
323
+ else
324
+ Senv.key_path rescue '(no key source)'
325
+ end
326
+ end
327
+
328
+ #
329
+ ENCRYPTED_PATH_RE = Regexp.union(/\.enc(rypted)$/, /\.enc(rypted)?\./)
330
+
331
+ def Senv.is_encrypted?(path)
332
+ path.to_s =~ ENCRYPTED_PATH_RE
333
+ end
334
+
335
+ #
336
+ def Senv.binread(path, options = {})
337
+ data = IO.binread(path)
338
+
339
+ encrypted =
340
+ if options.has_key?('encrypted')
341
+ options['encrypted']
342
+ else
343
+ Senv.is_encrypted?(path)
344
+ end
345
+
346
+ if encrypted
347
+ data =
348
+ begin
349
+ Blowfish.decrypt(Senv.key, data)
350
+ rescue
351
+ abort "could not decrypt `#{ path }` with key `#{ Senv.key }` from `#{ Senv.key_source }`"
352
+ end
353
+ end
354
+
355
+ data
356
+ end
357
+
358
+ def Senv.read(path, options = {})
359
+ Senv.binread(path)
360
+ end
361
+
362
+ def Senv.binwrite(path, data, options = {})
363
+ encrypted =
364
+ if options.has_key?('encrypted')
365
+ options['encrypted']
366
+ else
367
+ Senv.is_encrypted?(path)
368
+ end
369
+
370
+ if encrypted
371
+ data =
372
+ begin
373
+ Blowfish.encrypt(Senv.key, data)
374
+ rescue
375
+ abort "could not encrypt `#{ data.to_s.split("\n").first }...` with key `#{ Senv.key }` from `#{ Senv.key_source }`"
376
+ end
377
+ end
378
+
379
+ IO.binwrite(path, data)
380
+ end
381
+
382
+ def Senv.write(path, data, options = {})
383
+ Senv.binwrite(path, data, options)
384
+ end
385
+
386
+ def Senv.load_lib(path)
387
+ #
388
+ code = Senv.binread(path.to_s)
389
+ binding = ::TOPLEVEL_BINDING
390
+ filename = path.to_s
391
+
392
+ #
393
+ Kernel.eval(code, binding, filename)
394
+ end
395
+
396
+ def Senv.load_config(path)
397
+ #
398
+ erb = Senv.binread(path)
399
+ expanded = ERB.new(erb).result(::TOPLEVEL_BINDING)
400
+ buf = expanded
401
+
402
+ #
403
+ encoded = buf
404
+
405
+ config =
406
+ case
407
+ when path =~ /yml|yaml/
408
+ YAML.load(encoded)
409
+ when path =~ /json/
410
+ JSON.parse(encoded)
411
+ else
412
+ abort "unknown config format in #{ path }"
413
+ end
414
+
415
+ #
416
+ unless config && config.is_a?(Hash)
417
+ abort "[SENV] failed to load #{ path }"
418
+ end
419
+
420
+ #
421
+ config.each do |key, val|
422
+ ENV[key.to_s] = val.to_s
423
+ end
424
+
425
+ #
426
+ config
427
+ end
428
+
429
+ #
430
+ def Senv.debug(*args, &block)
431
+ if args.empty? && block.nil?
432
+ return Senv.debug?
433
+ end
434
+
435
+ return nil unless Senv.debug?
436
+
437
+ lines = []
438
+
439
+ args.each do |arg|
440
+ case
441
+ when arg.is_a?(String)
442
+ lines << arg.strip
443
+ else
444
+ lines << arg.inspect.strip
445
+ end
446
+ end
447
+
448
+ return nil if(lines.empty? && block.nil?)
449
+
450
+ if lines
451
+ lines.each do |line|
452
+ STDERR.puts "# [SENV=#{ Senv.env }] : #{ line }"
453
+ end
454
+ end
455
+
456
+ if block
457
+ return block.call
458
+ else
459
+ true
460
+ end
461
+ end
462
+
463
+ def Senv.debug?
464
+ !!ENV['SENV_DEBUG']
465
+ end
466
+
467
+ def Senv.debug=(arg)
468
+ if arg
469
+ ENV['SENV_DEBUG'] = 'true'
470
+ else
471
+ ENV.delete('SENV_DEBUG')
472
+ end
473
+ end
474
+
475
+ #
476
+ def Senv.senvs
477
+ @senvs ||= Hash.new
478
+ end
479
+
480
+ def Senv.for_senv!(senv)
481
+ senv = senv.to_s
482
+
483
+ IO.popen('-', 'w+') do |io|
484
+ child = io.nil?
485
+
486
+ if child
487
+ Senv.load(senv)
488
+ puts Senv.environment.to_yaml
489
+ exit
490
+ else
491
+ YAML.load(io.read)
492
+ end
493
+ end
494
+ end
495
+
496
+ def Senv.for_senv(senv)
497
+ senv = senv.to_s
498
+
499
+ if Senv.senvs.has_key?(senv)
500
+ return Senv.senvs[senv]
501
+ end
502
+
503
+ Senv.senvs[senv] = Senv.for_senv!(senv)
504
+ end
505
+
506
+ def Senv.get(senv, var)
507
+ Senv.for_senv(senv)[var.to_s]
508
+ end
509
+
510
+ def Senv.get!(senv, var)
511
+ Senv.for_senv!(senv)[var.to_s]
512
+ end
513
+
514
+ #
515
+ module Blowfish
516
+ def cipher(senv, key, data)
517
+ cipher = OpenSSL::Cipher.new('bf-cbc').send(senv)
518
+ cipher.key = Digest::SHA256.digest(key.to_s).slice(0,16)
519
+ cipher.update(data) << cipher.final
520
+ end
521
+
522
+ def encrypt(key, data)
523
+ cipher(:encrypt, key, data)
524
+ end
525
+
526
+ def decrypt(key, text)
527
+ cipher(:decrypt, key, text)
528
+ end
529
+
530
+ def cycle(key, data)
531
+ decrypt(key, encrypt(key, data))
532
+ end
533
+
534
+ def recrypt(old_key, new_key, data)
535
+ encrypt(new_key, decrypt(old_key, data))
536
+ end
537
+
538
+ extend(self)
539
+ end
540
+
541
+ #
542
+ def Senv.capturing_environment_changes(&block)
543
+ EnvChangeTracker.track(&block)
544
+ end
545
+
546
+ class EnvChangeTracker < ::BasicObject
547
+ def initialize(env)
548
+ @env = env
549
+
550
+ @changes = {
551
+ :deleted => [],
552
+ :updated => [],
553
+ :created => [],
554
+ }
555
+
556
+ @change_for = proc do |key, val|
557
+ if @env.has_key?(key)
558
+ case
559
+ when val.nil?
560
+ {:type => :deleted, :info => [key, val]}
561
+ when val.to_s != @env[key].to_s
562
+ {:type => :updated, :info => [key, val]}
563
+ else
564
+ nil
565
+ end
566
+ else
567
+ {:type => :created, :info => [key, val]}
568
+ end
569
+ end
570
+
571
+ @track_change = proc do |key, val|
572
+ change = @change_for[key, val]
573
+
574
+ if change
575
+ @changes[change[:type]].push(change[:info])
576
+ end
577
+ end
578
+ end
579
+
580
+ def changes
581
+ @changes
582
+ end
583
+
584
+ def method_missing(method, *args, &block)
585
+ @env.send(method, *args, &block)
586
+ end
587
+
588
+ def []=(key, val)
589
+ @track_change[key, val]
590
+ @env[key] = val
591
+ end
592
+
593
+ def replace(hash)
594
+ hash.each do |key, val|
595
+ @track_change[key, val]
596
+ end
597
+ @env.replace(hash)
598
+ end
599
+
600
+ def store(key, val)
601
+ @track_change[key, val]
602
+ @env.store(key, val)
603
+ end
604
+
605
+ def delete(key)
606
+ @track_change[key, nil]
607
+ @env.delete(key)
608
+ end
609
+
610
+ def apply(env)
611
+ @changes[:created].each do |k, v|
612
+ env[k] = v
613
+ end
614
+ @changes[:updated].each do |k, v|
615
+ env[k] = v
616
+ end
617
+ @changes[:deleted].each do |k, v|
618
+ env.delete(k)
619
+ end
620
+ @changes
621
+ end
622
+
623
+ THREAD_SAFE = ::Monitor.new
624
+
625
+ def EnvChangeTracker.track(&block)
626
+ THREAD_SAFE.synchronize do
627
+ env = EnvChangeTracker.new(::ENV)
628
+
629
+ ::Object.send(:remove_const, :ENV)
630
+ ::Object.send(:const_set, :ENV, env)
631
+
632
+ begin
633
+ block.call
634
+ env
635
+ ensure
636
+ ::Object.send(:remove_const, :ENV)
637
+ ::Object.send(:const_set, :ENV, env)
638
+ end
639
+ end
640
+ end
641
+ end
642
+
643
+ #
644
+ class ::Pathname
645
+ unless ::Pathname.pwd.respond_to?(:glob)
646
+ def glob(glob, &block)
647
+ paths = []
648
+
649
+ Dir.glob("#{ self }/#{ glob }") do |entry|
650
+ path = Pathname.new(entry)
651
+
652
+ if block
653
+ block.call(path)
654
+ else
655
+ paths.push(path)
656
+ end
657
+ end
658
+
659
+ block ? nil : paths
660
+ end
661
+ end
662
+ end
663
+ end