envoku 0.2.0 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 12cb3de23b25c163a12dc7320786b5e8aef7e6c0
4
- data.tar.gz: ec871654478e09acec6316f18249e274fff24848
3
+ metadata.gz: df8eba959ce87373465892dd6c02893c3ed62a24
4
+ data.tar.gz: 590152bb2183123ba32ff460276cec05993ac7db
5
5
  SHA512:
6
- metadata.gz: 8cbf3e539206de10ccedb8f7b264320435ab131d0241adc5dc6eb6c36c17c0b89b6b29a0b1918920badf0add8c631777f510b1530f49b048603933a876be93c2
7
- data.tar.gz: 7f501e221cfc0bd53b87aa77a0b1ba87cc792e1daef744f423fc65626e2b447bae169ee193cc468beb25c8b2801720c1de032d430a44b94a32a4739ac77bcff2
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
  [![Code Climate](https://codeclimate.com/github/marcqualie/envoku-ruby/badges/gpa.svg)](https://codeclimate.com/github/marcqualie/envoku-ruby)
7
7
  [![Issue Count](https://codeclimate.com/github/marcqualie/envoku-ruby/badges/issue_count.svg)](https://codeclimate.com/github/marcqualie/envoku-ruby)
8
8
 
9
- Configuration storage that can be loaded into your application transparently at boot-time.
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.
@@ -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
- instance = Envoku::Adapters::S3.new options
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 redis
20
- @redis ||= ::Redis.new(
21
- url: (ENV['ENVOKU_REDIS_URL'] || ENV['REDIS_URL']),
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 feature_enabled_for?(feature_name, resource)
26
- Feature.new(feature_name).enabled_for?(resource)
34
+ def get(key)
35
+ adapter = Envoku::Adapters::S3.new
36
+ adapter.load
37
+ adapter.get(key)
27
38
  end
28
39
 
29
- def features_enabled_for(resource)
30
- redis.smembers("#{Feature::REDIS_NAMESPACE}#{resource.class.name}:#{resource.id}")
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
@@ -12,7 +12,7 @@ module Envoku
12
12
 
13
13
  attr_reader :options
14
14
 
15
- def initialize custom_options = {}
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
- Dotenv.load
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
- Dotenv.load @local_file_name
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 ENV['ENVOKU_URL']
60
- envoku_uri = URI.parse(ENV['ENVOKU_URL'])
61
- return nil unless envoku_uri.host && envoku_uri.path && envoku_uri.user && envoku_uri.password
62
- @options.filename ||= envoku_uri.path[1..-1]
63
- @options.bucket_name ||= envoku_uri.host
64
- @options.access_key_id ||= envoku_uri.user
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']
@@ -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
- attributes.each do |key, value|
25
- instance_variable_set(:"@#{key}", value)
26
- end
27
- @enabled = !!@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
@@ -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
@@ -1,3 +1,3 @@
1
1
  module Envoku
2
- VERSION = "0.2.0"
2
+ VERSION = "0.3.0"
3
3
  end
@@ -9,7 +9,8 @@ namespace :envoku do
9
9
 
10
10
  desc "Show Envoku Info"
11
11
  task :info => :load do
12
- puts "URL: #{ENV['ENVOKU_URL'] ? ENV['ENVOKU_URL'] : "[not set]"}"
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.2.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-10-14 00:00:00.000000000 Z
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