envoku 0.2.0 → 0.3.0
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.
- checksums.yaml +4 -4
- data/.simplecov +3 -3
- data/README.md +31 -6
- data/lib/envoku.rb +28 -9
- data/lib/envoku/adapters/s3.rb +38 -11
- data/lib/envoku/feature.rb +27 -5
- data/lib/envoku/logger.rb +23 -0
- data/lib/envoku/utils.rb +27 -0
- data/lib/envoku/version.rb +1 -1
- data/lib/tasks/envoku.rake +2 -1
- metadata +4 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: df8eba959ce87373465892dd6c02893c3ed62a24
|
4
|
+
data.tar.gz: 590152bb2183123ba32ff460276cec05993ac7db
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 9e7ab915b62d44ba83a0db455d6b38182e7a17d5c4b11e230a4dbab2eab496a712794d8977e1db68f980d8c1574d94798ada72f4df29bebe795bead0f369afdc
|
7
|
+
data.tar.gz: 847edf89174d7f138277aa5e56711daf812863abbf03201abe87801df9d07d63ff2739f516e855a3cb8d436edade4e2acf95d1b1a9f7ccd88014806444149ed8
|
data/.simplecov
CHANGED
@@ -2,10 +2,10 @@ require "codeclimate-test-reporter"
|
|
2
2
|
require 'codecov'
|
3
3
|
|
4
4
|
SimpleCov.formatter = SimpleCov::Formatter::MultiFormatter.new([
|
5
|
-
CodeClimate::TestReporter::Formatter,
|
6
|
-
SimpleCov::Formatter::Codecov,
|
5
|
+
(CodeClimate::TestReporter::Formatter if ENV['CODECLIMATE_REPO_TOKEN']),
|
6
|
+
(SimpleCov::Formatter::Codecov if ENV['CODECOV_TOKEN']),
|
7
7
|
SimpleCov::Formatter::HTMLFormatter,
|
8
|
-
])
|
8
|
+
].compact)
|
9
9
|
SimpleCov.start do
|
10
10
|
add_filter "spec"
|
11
11
|
add_group "Adapters", "lib/envoku/adapters/"
|
data/README.md
CHANGED
@@ -6,7 +6,9 @@
|
|
6
6
|
[](https://codeclimate.com/github/marcqualie/envoku-ruby)
|
7
7
|
[](https://codeclimate.com/github/marcqualie/envoku-ruby)
|
8
8
|
|
9
|
-
|
9
|
+
**WIP:** This gem is currently under active development. Until **1.0** version backwards-incompatible changes may be introduced with each **0.x** minor version.
|
10
|
+
|
11
|
+
Configuration and feature management that is pre-loaded into your application at boot-time.
|
10
12
|
|
11
13
|
|
12
14
|
## Installation
|
@@ -19,21 +21,23 @@ gem 'envoku'
|
|
19
21
|
|
20
22
|
And then execute:
|
21
23
|
|
22
|
-
$ bundle
|
23
|
-
|
24
|
-
Or install it yourself as:
|
25
|
-
|
26
|
-
$ gem install envoku
|
24
|
+
$ bundle install
|
27
25
|
|
28
26
|
|
29
27
|
## Usage
|
30
28
|
|
31
29
|
When Envoku is loaded it automatically uses Dotenv in the background to preload and other configuration mechanisms you may have to make transitioning seamless.
|
32
30
|
|
31
|
+
|
33
32
|
### Rails
|
34
33
|
|
35
34
|
Envoku automatically pre-loads itself during `before_configuration` phase so no configuration is required.
|
36
35
|
|
36
|
+
### Sinatra
|
37
|
+
|
38
|
+
Use the plain ruby method as early as possible, before any `ENV` variables are accessed. Usually at the top of `app.rb`.
|
39
|
+
|
40
|
+
|
37
41
|
### Plain ruby
|
38
42
|
|
39
43
|
Run the following code before you need access to the environment variables
|
@@ -45,6 +49,12 @@ Envoku.load
|
|
45
49
|
|
46
50
|
## Features
|
47
51
|
|
52
|
+
Feature are defined as standard environment variables as YAML objects in the following format:
|
53
|
+
|
54
|
+
``` shell
|
55
|
+
ENVOKU_FEATURE_FEATURE1="description: 'Example feature', attribute1: 'something'"
|
56
|
+
```
|
57
|
+
|
48
58
|
``` ruby
|
49
59
|
all_features = Envoku::Feature.all
|
50
60
|
feature = Envoku::Feature.new('FEATURE1')
|
@@ -52,11 +62,19 @@ feature.enabled? # global for all resources
|
|
52
62
|
feature.enabled_for?(current_user) # does current_user have this feature enabled
|
53
63
|
feature.enable_for!(current_user) # enable feature for current_user
|
54
64
|
feature.disable_for!(current_user) # disable feature for current_user
|
65
|
+
feature.attributes # {"attribute1" => "something"}
|
66
|
+
feature.permitted_for?('User') # whether or not this feature is designed for a resource
|
55
67
|
resource.feature_enabled?('FEATURE1')
|
56
68
|
resource.toggle_feature!('FEATURE1')
|
57
69
|
resource.features_enabled
|
58
70
|
```
|
59
71
|
|
72
|
+
By default, all features can be used against all resources. To restrict a feature to a certain resource:
|
73
|
+
|
74
|
+
``` shell
|
75
|
+
ENVOKU_FEATURE_FEATURE1="permitted_resources: 'Organization,User'"
|
76
|
+
```
|
77
|
+
|
60
78
|
Per-resource features are stored in Redis via the following keys:
|
61
79
|
|
62
80
|
```
|
@@ -65,6 +83,13 @@ Per-resource features are stored in Redis via the following keys:
|
|
65
83
|
```
|
66
84
|
|
67
85
|
|
86
|
+
## Todo
|
87
|
+
|
88
|
+
- ~~Logging~~
|
89
|
+
- ~~Show warning when toggling features for non-permitted resources~~
|
90
|
+
- Add `Envoku.configure {}` functionality
|
91
|
+
|
92
|
+
|
68
93
|
## Development
|
69
94
|
|
70
95
|
After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
|
data/lib/envoku.rb
CHANGED
@@ -1,32 +1,51 @@
|
|
1
1
|
require "redis"
|
2
|
+
require "logger"
|
2
3
|
|
3
4
|
require "envoku/version"
|
4
5
|
require "envoku/adapters/s3"
|
5
6
|
require "envoku/feature"
|
7
|
+
require "envoku/logger"
|
6
8
|
require "envoku/resource"
|
9
|
+
require "envoku/utils"
|
10
|
+
|
11
|
+
Dotenv.load("#{Dir.pwd}/.env") if defined?(Dotenv) && ENV['RAILS_ENV'] != "test"
|
7
12
|
|
8
13
|
require "envoku/rails" if defined?(Rails)
|
9
14
|
|
10
15
|
module Envoku
|
11
16
|
|
17
|
+
URL = Envoku::Utils.parsed_url
|
18
|
+
URI = Envoku::Utils.parsed_uri
|
19
|
+
|
12
20
|
module_function
|
13
21
|
|
14
22
|
def load(options = {})
|
15
|
-
|
23
|
+
Envoku.logger.info("load using S3 adapter")
|
24
|
+
instance = Envoku::Adapters::S3.new(options)
|
16
25
|
instance.load
|
17
26
|
end
|
18
27
|
|
19
|
-
def
|
20
|
-
|
21
|
-
|
22
|
-
|
28
|
+
def get_all
|
29
|
+
adapter = Envoku::Adapters::S3.new
|
30
|
+
adapter.load
|
31
|
+
adapter.get_all
|
23
32
|
end
|
24
33
|
|
25
|
-
def
|
26
|
-
|
34
|
+
def get(key)
|
35
|
+
adapter = Envoku::Adapters::S3.new
|
36
|
+
adapter.load
|
37
|
+
adapter.get(key)
|
27
38
|
end
|
28
39
|
|
29
|
-
def
|
30
|
-
|
40
|
+
def set(key, value)
|
41
|
+
adapter = Envoku::Adapters::S3.new
|
42
|
+
adapter.load
|
43
|
+
adapter.set(key, value)
|
44
|
+
end
|
45
|
+
|
46
|
+
def redis
|
47
|
+
@redis ||= ::Redis.new(
|
48
|
+
url: (ENV['ENVOKU_REDIS_URL'] || ENV['REDIS_URL']),
|
49
|
+
)
|
31
50
|
end
|
32
51
|
end
|
data/lib/envoku/adapters/s3.rb
CHANGED
@@ -12,7 +12,7 @@ module Envoku
|
|
12
12
|
|
13
13
|
attr_reader :options
|
14
14
|
|
15
|
-
def initialize
|
15
|
+
def initialize(custom_options = {})
|
16
16
|
default_options = {
|
17
17
|
filename: nil,
|
18
18
|
bucket_name: nil,
|
@@ -21,19 +21,47 @@ module Envoku
|
|
21
21
|
}
|
22
22
|
@options = OpenStruct.new default_options.merge(custom_options)
|
23
23
|
@local_file_name = "/tmp/envoku-#{SecureRandom.hex 16}.env"
|
24
|
+
@data = {}
|
24
25
|
end
|
25
26
|
|
26
27
|
def load
|
27
|
-
|
28
|
+
Envoku.logger.debug("Loading via S3 Adapter")
|
28
29
|
apply_environment_options
|
29
30
|
return unless options.bucket_name && options.filename && options.access_key_id && options.secret_access_key
|
31
|
+
Envoku.logger.debug("Downloading \"#{options.bucket_name}/#{options.filename}\" from S3")
|
30
32
|
FileUtils.rm @local_file_name if File.exists? @local_file_name
|
31
33
|
return unless clone_s3_file
|
32
|
-
|
34
|
+
@_env_before = ENV.to_h
|
35
|
+
Envoku.logger.debug("Applying ENV vars from S3")
|
36
|
+
@data = Dotenv.load(@local_file_name) || {}
|
37
|
+
@_env_after = ENV.to_h
|
38
|
+
# TODO: Abstract the env diff to adapter base
|
39
|
+
@_env_after.each do |key, value|
|
40
|
+
if !@_env_before.has_key?(key)
|
41
|
+
Envoku.logger.debug("- ADD #{key}")
|
42
|
+
# @data[key] = value
|
43
|
+
elsif @_env_before[key] != value
|
44
|
+
Envoku.logger.debug("- MOD #{key}")
|
45
|
+
# @data[key] = value
|
46
|
+
end
|
47
|
+
end
|
33
48
|
FileUtils.rm @local_file_name
|
34
49
|
ENV['ENVOKU_REFRESHED_AT'] = Time.now.to_s
|
35
50
|
end
|
36
51
|
|
52
|
+
def get_all
|
53
|
+
@data
|
54
|
+
end
|
55
|
+
|
56
|
+
def get(key)
|
57
|
+
@data[key]
|
58
|
+
end
|
59
|
+
|
60
|
+
def set(key, value)
|
61
|
+
# TODO: Not yet implemented
|
62
|
+
@data[key] = value
|
63
|
+
end
|
64
|
+
|
37
65
|
private
|
38
66
|
|
39
67
|
def clone_s3_file
|
@@ -48,7 +76,7 @@ module Envoku
|
|
48
76
|
"Expires=#{s3_expires_at.to_i}",
|
49
77
|
"Signature=#{CGI.escape Base64.encode64(hmac).strip}"
|
50
78
|
]
|
51
|
-
s3_signed_uri = URI.parse "#{s3_direct_url}?#{query_params.join('&')}"
|
79
|
+
s3_signed_uri = ::URI.parse "#{s3_direct_url}?#{query_params.join('&')}"
|
52
80
|
s3_response = Net::HTTP.get_response s3_signed_uri
|
53
81
|
if s3_response.is_a? Net::HTTPSuccess
|
54
82
|
File.write @local_file_name, s3_response.body
|
@@ -56,13 +84,12 @@ module Envoku
|
|
56
84
|
end
|
57
85
|
|
58
86
|
def apply_environment_options
|
59
|
-
if
|
60
|
-
|
61
|
-
|
62
|
-
@options.
|
63
|
-
@options.
|
64
|
-
@options.
|
65
|
-
@options.secret_access_key ||= envoku_uri.password
|
87
|
+
if Envoku::URL
|
88
|
+
return nil unless Envoku::URI && Envoku::URI.host && Envoku::URI.path && Envoku::URI.user && Envoku::URI.password
|
89
|
+
@options.filename ||= Envoku::URI.path[1..-1]
|
90
|
+
@options.bucket_name ||= Envoku::URI.host
|
91
|
+
@options.access_key_id ||= Envoku::URI.user
|
92
|
+
@options.secret_access_key ||= ::URI.unescape(Envoku::URI.password)
|
66
93
|
else
|
67
94
|
@options.filename ||= ENV['ENVOKU_FILENAME']
|
68
95
|
@options.bucket_name ||= ENV['ENVOKU_BUCKET']
|
data/lib/envoku/feature.rb
CHANGED
@@ -1,12 +1,24 @@
|
|
1
1
|
require 'yaml'
|
2
2
|
|
3
3
|
module Envoku
|
4
|
+
|
5
|
+
module_function
|
6
|
+
|
7
|
+
def feature_enabled_for?(feature_name, resource)
|
8
|
+
Feature.new(feature_name).enabled_for?(resource)
|
9
|
+
end
|
10
|
+
|
11
|
+
def features_enabled_for(resource)
|
12
|
+
redis.smembers("#{Feature::REDIS_NAMESPACE}#{resource.class.name}:#{resource.id}")
|
13
|
+
end
|
14
|
+
|
4
15
|
class Feature
|
5
16
|
ENV_NAMESPACE = "ENVOKU_FEATURE_"
|
6
17
|
REDIS_NAMESPACE = "envoku:features:"
|
7
18
|
|
8
19
|
attr_reader :name
|
9
20
|
attr_reader :description
|
21
|
+
attr_reader :attributes
|
10
22
|
|
11
23
|
def self.all
|
12
24
|
keys = ENV.select { |key, value| key.index(ENV_NAMESPACE) == 0 }
|
@@ -18,13 +30,15 @@ module Envoku
|
|
18
30
|
def initialize(name)
|
19
31
|
@name = name
|
20
32
|
@enabled = false
|
33
|
+
@attributes = {}
|
34
|
+
@description = nil
|
21
35
|
var_string = ENV["#{ENV_NAMESPACE}#{name}"]
|
22
36
|
return nil unless var_string
|
23
|
-
attributes = ::YAML.safe_load(var_string) rescue {}
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
@enabled
|
37
|
+
@attributes = ::YAML.safe_load(var_string) rescue {}
|
38
|
+
@description = @attributes['description']
|
39
|
+
@enabled = !!@attributes['enabled']
|
40
|
+
@attributes.delete('description')
|
41
|
+
@attributes.delete('enabled')
|
28
42
|
end
|
29
43
|
|
30
44
|
def enabled?
|
@@ -37,6 +51,7 @@ module Envoku
|
|
37
51
|
end
|
38
52
|
|
39
53
|
def enable_for!(resource)
|
54
|
+
Envoku.logger.warn("feature #{name} is not permitted for #{resource.class.name}") unless permitted_for?(resource.class.name)
|
40
55
|
Envoku.redis.multi do
|
41
56
|
Envoku.redis.sadd("#{REDIS_NAMESPACE}#{@name}:#{resource.class.name}", resource.id.to_s)
|
42
57
|
Envoku.redis.sadd("#{REDIS_NAMESPACE}#{resource.class.name}:#{resource.id}", @name)
|
@@ -44,6 +59,7 @@ module Envoku
|
|
44
59
|
end
|
45
60
|
|
46
61
|
def disable_for!(resource)
|
62
|
+
Envoku.logger.warn("feature #{name} is not permitted for #{resource.class.name}") unless permitted_for?(resource.class.name)
|
47
63
|
Envoku.redis.multi do
|
48
64
|
Envoku.redis.del("#{REDIS_NAMESPACE}#{@name}:#{resource.class.name}")
|
49
65
|
Envoku.redis.del("#{REDIS_NAMESPACE}#{resource.class.name}:#{resource.id}")
|
@@ -54,6 +70,12 @@ module Envoku
|
|
54
70
|
enabled_for?(resource) ? disable_for!(resource) : enable_for!(resource)
|
55
71
|
end
|
56
72
|
|
73
|
+
def permitted_for?(resource_klass)
|
74
|
+
return true unless @attributes.has_key?('permitted_resources')
|
75
|
+
resource_klass = resource_klass.class.name unless resource_klass.is_a?(String)
|
76
|
+
@attributes['permitted_resources'].split(',').include?(resource_klass)
|
77
|
+
end
|
78
|
+
|
57
79
|
def resources
|
58
80
|
list = []
|
59
81
|
feature_prefix = "#{REDIS_NAMESPACE}#{@name}"
|
@@ -0,0 +1,23 @@
|
|
1
|
+
require "logger"
|
2
|
+
|
3
|
+
module Envoku
|
4
|
+
|
5
|
+
module_function
|
6
|
+
|
7
|
+
def logger
|
8
|
+
@_logger ||= Envoku::Logger.new(STDOUT)
|
9
|
+
end
|
10
|
+
|
11
|
+
def logger=(logger)
|
12
|
+
@_logger = logger
|
13
|
+
end
|
14
|
+
|
15
|
+
class Logger < ::Logger
|
16
|
+
def initialize(*args)
|
17
|
+
super
|
18
|
+
@progname = "envoku"
|
19
|
+
log_level = (ENV["ENVOKU_LOG_LEVEL"] || ENV["LOG_LEVEL"] || "").upcase
|
20
|
+
@level = ["DEBUG", "INFO", "WARN", "ERROR", "FATAL", "UNKNOWN"].index(log_level) != nil ? Logger.const_get(log_level) : Logger::INFO
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
data/lib/envoku/utils.rb
ADDED
@@ -0,0 +1,27 @@
|
|
1
|
+
require "uri"
|
2
|
+
|
3
|
+
module Envoku
|
4
|
+
module Utils
|
5
|
+
|
6
|
+
module_function
|
7
|
+
|
8
|
+
def parsed_url
|
9
|
+
return nil if ENV['ENVOKU_URL'] == nil || ENV['ENVOKU_URL'] == ""
|
10
|
+
ENV['ENVOKU_URL']
|
11
|
+
end
|
12
|
+
|
13
|
+
def parsed_uri
|
14
|
+
return nil unless parsed_url
|
15
|
+
parser = ::URI::RFC2396_Parser.new
|
16
|
+
uri = parser.parse(parsed_url)
|
17
|
+
uri
|
18
|
+
rescue Exception => error
|
19
|
+
Envoku.logger.error("URI Parse Error: URL = #{parsed_url || '[not set]'}")
|
20
|
+
Envoku.logger.error(" #{error.message}")
|
21
|
+
(0..2).each do |index|
|
22
|
+
Envoku.logger.error(" #{error.backtrace[index]}")
|
23
|
+
end
|
24
|
+
nil
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
data/lib/envoku/version.rb
CHANGED
data/lib/tasks/envoku.rake
CHANGED
@@ -9,7 +9,8 @@ namespace :envoku do
|
|
9
9
|
|
10
10
|
desc "Show Envoku Info"
|
11
11
|
task :info => :load do
|
12
|
-
puts "URL: #{
|
12
|
+
puts "URL: #{Envoku::URL || '[not set]'}"
|
13
|
+
puts "URI: #{Envoku::URI.to_yaml}"
|
13
14
|
end
|
14
15
|
|
15
16
|
desc "List Envoku Features"
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: envoku
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.3.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Marc Qualie
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2016-
|
11
|
+
date: 2016-11-03 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: dotenv
|
@@ -132,8 +132,10 @@ files:
|
|
132
132
|
- lib/envoku/adapters.rb
|
133
133
|
- lib/envoku/adapters/s3.rb
|
134
134
|
- lib/envoku/feature.rb
|
135
|
+
- lib/envoku/logger.rb
|
135
136
|
- lib/envoku/rails.rb
|
136
137
|
- lib/envoku/resource.rb
|
138
|
+
- lib/envoku/utils.rb
|
137
139
|
- lib/envoku/version.rb
|
138
140
|
- lib/tasks/envoku.rake
|
139
141
|
- script/console
|