valid-env 0.0.3

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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 63903d28f996a2290d7399fe50c20b2487b42749
4
+ data.tar.gz: 8f630092e6acfa9a2b0e4f13ebb660b4129ba39f
5
+ SHA512:
6
+ metadata.gz: 2ec38fa7a2e6480456acdabb283aa9ef7a8f455a03775cdc959663a03e46b138b96b99480c7dac2eecf23d29b300b01f41f34a1de3c12a162b25fdbe373c2e58
7
+ data.tar.gz: 956c20d70ff91d4bb91be3dd1acafe0773a2e500b6c1c8ff3d79ed25eb5e22afd31256580267d751af9989ef18e317a665618773a21f6a22d7218c662c5a7cf9
data/README.md ADDED
@@ -0,0 +1,158 @@
1
+ # valid-env
2
+ ## It validates environment variables
3
+
4
+ We often encounter situations where our apps make use of environment variables, but sometimes only under relatively rare circumstances. It's very easy to deploy an app to a new environment, and then discover some hours (or days!) later that an important environment variable wasn't set, and some small part of the app is broken. Often the error message is cryptic.
5
+
6
+ The valid-env gem provides a way to list and document not only the variables that are required, but also the relationships between them. It provides a quick way to list what's needed, and also will fail fast if the app is run when a required value is missing.
7
+
8
+ ## Installation
9
+
10
+ ```
11
+ $ gem install valid-env
12
+ ```
13
+
14
+ ## Use
15
+
16
+ ### Required variables
17
+
18
+ You've built an app that requires an environment variable, `APP_NAME`, is defined -- but that value isn't used until your code has already been running for some time.
19
+
20
+ Using valid-env, you can define and wrap your environment:
21
+
22
+ ```ruby
23
+ class MyEnv < ValidEnv
24
+ required :APP_NAME
25
+ end
26
+ ```
27
+
28
+ **In the initialization of your app, call `MyEnv.validate!`.** If `APP_NAME` is missing, this will throw a clear, readable error that explains the variable must be set.
29
+
30
+ and then, replace all references to `ENV['APP_NAME']` with either
31
+
32
+ ```ruby
33
+ MyEnv.APP_NAME # returns the raw env variable
34
+ MyEnv.app_name # returns the coerced env variable value
35
+ ```
36
+
37
+ Having both of these forms is more interesting when handling booleans.
38
+
39
+ ### Booleans
40
+
41
+ Without valid-env, boolean environment variables can be a pain -- thoughtlessly changing a "true" to a "false" may not change behavior, because both are parsed as strings.
42
+
43
+ ValidEnv provides a coerced value, which is falsy when the env variable is "false":
44
+
45
+
46
+ ```ruby
47
+ class MyEnv < ValidEnv
48
+ required :BODGE_ENABLED, :boolean
49
+ end
50
+
51
+ # ...
52
+
53
+ MyEnv.FOO_ENABLED # returns the raw env variable (a string)
54
+ MyEnv.foo_enabled? # returns a boolean
55
+ ```
56
+
57
+ Note in the second reader method generated there is an appended '?'.
58
+
59
+ ### Optional variables
60
+
61
+ Even if a variable isn't required, it's nice to document that it *can* exist. For this, valid-env offers the `optional` keyword:
62
+
63
+ ```ruby
64
+ class MyEnv < ValidEnv
65
+ optional :APP_NAME
66
+ end
67
+ ```
68
+
69
+ ### Conditionally required variables
70
+
71
+ Some variables are required only if another variable is set.
72
+
73
+ ```ruby
74
+ class MyEnv < ValidEnv
75
+ optional :BASIC_AUTH_REQUIRED, :boolean, default: false
76
+ required :BASIC_HTTP_USERNAME, if: :basic_auth_required?
77
+ required :BASIC_HTTP_PASSWORD, if: :basic_auth_required?
78
+ end
79
+ ```
80
+
81
+ MyEnv will only complain that `BASIC_HTTP_USERNAME` is missing if `BASIC_AUTH_REQUIRED` has been set to true.
82
+
83
+ ### Custom validations
84
+
85
+ valid-env is built using ActiveModel::Validations; you can write your own validation methods if none of the above are suitable.
86
+
87
+ ```ruby
88
+ class MyEnv < ValidEnv
89
+ validate :at_least_one_form_of_auth
90
+
91
+ required :PASSWORD_AUTH_ENABLED, :boolean
92
+ required :OAUTH_ENABLED, :boolean
93
+
94
+ def at_least_one_form_of_auth
95
+ has_auth = oauth_enabled? || password_auth_enabled?
96
+ unless has_auth
97
+ errors.add(:base, <<-ERR)
98
+ Expected at least one form of authentication to be enabled,
99
+ but none were. Possible forms: OAUTH, PASSWORD_AUTH
100
+ ERR
101
+ end
102
+ end
103
+ end
104
+ ```
105
+
106
+ ### Describing the configuration
107
+
108
+ `MyEnv.describe` will print out a clear list of what variables are required, which are optional, etc.
109
+
110
+ ### A full example
111
+
112
+ ```ruby
113
+ require 'valid-env'
114
+
115
+ class MyEnv < ValidEnv
116
+ validate :validate_at_least_one_form_of_auth
117
+
118
+ # App
119
+ required :APP_NAME
120
+ required :PASSWORD_AUTH_ENABLED, :boolean
121
+ optional :PING_URL
122
+ required :RAILS_ENV
123
+
124
+ # Amazon S3
125
+ required :S3_URL
126
+ required :S3_BUCKET
127
+ required :AWS_ACCESS_KEY_ID
128
+ required :AWS_SECRET_ACCESS_KEY
129
+ required :AWS_REGION
130
+
131
+ # Basic Auth
132
+ optional :BASIC_AUTH_REQUIRED, :boolean, default: false
133
+ required :BASIC_HTTP_USERNAME, if: :basic_auth_required?
134
+ required :BASIC_HTTP_PASSWORD, if: :basic_auth_required?
135
+
136
+ # OAuth
137
+ required :OAUTH_ENABLED, :boolean
138
+ required :OAUTH_SIGNUP_URL, if: :oauth_enabled?
139
+
140
+ required :OTHER_API_URL, if: :staging_or_production?
141
+
142
+ private
143
+
144
+ def staging_or_production?
145
+ %w(staging production).include? rails_env
146
+ end
147
+
148
+ def validate_at_least_one_form_of_auth
149
+ has_auth = oauth_enabled? || password_auth_enabled?
150
+ unless has_auth
151
+ errors.add(:base, <<-ERR)
152
+ Expected at least one form of authentication to be enabled,
153
+ but none were. Possible forms: OAUTH, PASSWORD_AUTH
154
+ ERR
155
+ end
156
+ end
157
+ end
158
+ ```
data/lib/valid-env.rb ADDED
@@ -0,0 +1,44 @@
1
+ require 'active_model'
2
+ require 'active_support/core_ext/string/strip'
3
+
4
+ require File.dirname(__FILE__) + '/valid-env/dsl_methods'
5
+ # ValidEnv is the class responsible for specifying which environment variables
6
+ # the application expects to interact with. It is so the application can
7
+ # validate it's environment during boot.
8
+ class ValidEnv
9
+ extend DslMethods
10
+ include ActiveModel::Validations
11
+
12
+ class Error < ::StandardError ; end
13
+ class InvalidEnvironment < Error ; end
14
+ class MissingEnvVarRegistration < Error ; end
15
+
16
+ def self.describe
17
+ registered_env_vars.each do |key, env_var|
18
+ puts env_var.to_s
19
+ end
20
+ end
21
+
22
+ def self.validate!
23
+ instance.validate!
24
+ end
25
+
26
+ def validate!
27
+ unless valid?
28
+ error_message = "Environment validation failed:\n\n"
29
+ errors.full_messages.each do |error|
30
+ error_message << "* #{error}\n"
31
+ end
32
+ error_message << "\n"
33
+ fail InvalidEnvironment, error_message
34
+ end
35
+ end
36
+ end
37
+
38
+ require File.dirname(__FILE__) + '/valid-env/env_var'
39
+ require File.dirname(__FILE__) + '/valid-env/optional_env_var'
40
+ require File.dirname(__FILE__) + '/valid-env/required_env_var'
41
+ require File.dirname(__FILE__) + '/valid-env/boolean_validator'
42
+ require File.dirname(__FILE__) + '/valid-env/presence_validator'
43
+
44
+
@@ -0,0 +1,15 @@
1
+ class ValidEnv
2
+ class BooleanValidator < ActiveModel::EachValidator
3
+ def validate_each(record, attribute, value)
4
+ if value
5
+ if !['true', '1', 'false', '0'].include?(value)
6
+ message = options[:message] || "Environment Variable: #{attribute} was expected to be set to a boolean value, but was set to #{value.inspect}. Allowed boolean values are true (true, 1), or false (false, 0)."
7
+ record.errors.add :base, message
8
+ end
9
+ else
10
+ message = options[:message] || "Environment Variable: #{attribute} was expected to be set to a boolean, but was not set. Allowed boolean values are true (true, 1), or false (false, 0)."
11
+ record.errors.add :base, message
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,118 @@
1
+ class ValidEnv
2
+ # DslMethods houses the DSL methods for registering ENV variables that the
3
+ # application knows about.
4
+ module DslMethods
5
+ # Expose a singleton-like interface for accessing an instance of ValidEnv
6
+ # since we only need one.
7
+ #
8
+ # Note: Don't use Singleton from Ruby standard library since that prevents
9
+ # us from creating ValidEnv instances in corresponding specs/tests.
10
+ def instance
11
+ @instance ||= new
12
+ end
13
+
14
+ # +optional+ registers an optional ENV var with the given key.
15
+ #
16
+ # == Examples
17
+ #
18
+ # optional :FOO
19
+ # optional :FOO, :boolean, default: false
20
+ def optional(key, type = nil, default: nil)
21
+ optional_env_var = OptionalEnvVar.new(
22
+ key,
23
+ type,
24
+ default: default
25
+ )
26
+ register_env_var(optional_env_var)
27
+ end
28
+
29
+ # +required+ registers a required ENV var with the given key. It supports
30
+ # ActiveModel#validate options.
31
+ #
32
+ # == Examples
33
+ #
34
+ # required :BAR
35
+ # required :BAR, :boolean
36
+ # required :BAR, :boolean, if: :some_other_value?
37
+ #
38
+ def required(key, *args)
39
+ options = args.extract_options!
40
+ type = args.first
41
+ default_value = options[:default]
42
+ if_method = options[:if]
43
+
44
+ additional_details = "if #{if_method}" if if_method
45
+ required_env_var = RequiredEnvVar.new(
46
+ key,
47
+ type,
48
+ default: default_value,
49
+ additional_details: additional_details
50
+ )
51
+ register_env_var(required_env_var)
52
+
53
+ validation_args = required_env_var.boolean? ? { boolean: true } : { presence: true }
54
+
55
+ validation_args[:if] = if_method if if_method
56
+ validates key, **validation_args
57
+ end
58
+
59
+ # Override method_missing to perform a look-up on ValidEnv.instance. Every
60
+ # method_missing call is assumed to be an ENV var lookup.
61
+ #
62
+ # ValidEnv.foo_enabled? -> ValidEnv.instance.foo_enabled?
63
+ #
64
+ # If ValidEnv.instance does not respond to the given method a
65
+ # MissingEnvVarRegistration error will be raised.
66
+ def method_missing(method, *args, &blk)
67
+ if instance.respond_to?(method)
68
+ instance.send(method, *args, &blk)
69
+ else
70
+ method = method.to_s
71
+ fail MissingEnvVarRegistration, <<-ERROR_MSG.strip_heredoc
72
+ undefined method #{method.inspect} for #{self}. Is the
73
+ #{method.upcase} env var registered in #{self}?
74
+ ERROR_MSG
75
+ end
76
+ end
77
+
78
+ # Returns the registered list of environment variables.
79
+ def registered_env_vars
80
+ @registered_env_vars = @registered_env_vars || {}
81
+ end
82
+
83
+ protected
84
+
85
+ # +register_env_var+ registers a ValidEnv::EnvVar instance and defines
86
+ # reader method(s) for it.
87
+ #
88
+ # Say there's a boolean EnvVar with a key of 'FOO_ENABLED'. Registering it
89
+ # will generate two methods:
90
+ #
91
+ # * FOO_ENABLED - a reader method that returns the raw value of the ENV variable
92
+ # * foo_enabled? - a query method that returns the boolean value
93
+ #
94
+ # Next, Say there's a EnvVar without a type with a key of 'BAR'.
95
+ # Registering it will generate two methods:
96
+ #
97
+ # * BAR - a reader method that returns the raw value of the ENV variable
98
+ # * bar - a query method that returns raw value
99
+ #
100
+ def register_env_var(env_var)
101
+ registered_env_vars[env_var.key] = env_var
102
+
103
+ # ValidEnv#APP_NAME
104
+ reader_method_name = env_var.key
105
+ define_method(reader_method_name) do
106
+ env_var.raw_value_from_env
107
+ end
108
+
109
+ # ValidEnv#app_name
110
+ # ValidEnv#orcid_enabled? for boolean
111
+ reader_method_name = "#{env_var.key.downcase}"
112
+ reader_method_name << "?" if env_var.boolean?
113
+ define_method(reader_method_name) do
114
+ env_var.value
115
+ end
116
+ end
117
+ end
118
+ end
@@ -0,0 +1,47 @@
1
+ class ValidEnv
2
+ class EnvVar
3
+ attr_reader :key, :default_value, :additional_details
4
+
5
+ def initialize(key, type = nil, default: nil, additional_details: nil)
6
+ @key = key.to_s
7
+ @type = type
8
+ @default_value = default
9
+ @additional_details = additional_details
10
+ end
11
+
12
+ def ==(other)
13
+ other.is_a?(self.class) &&
14
+ other.key == key
15
+ end
16
+
17
+ def value
18
+ if raw_value_from_env.nil?
19
+ default_value
20
+ elsif boolean?
21
+ converted_boolean_value
22
+ else
23
+ raw_value_from_env
24
+ end
25
+ end
26
+
27
+ def raw_value_from_env
28
+ ENV[@key]
29
+ end
30
+
31
+ def boolean?
32
+ @type == :boolean
33
+ end
34
+
35
+ def to_s
36
+ msg = "Environment Variable: #{key}"
37
+ msg << " (#{additional_details})" if additional_details
38
+ msg
39
+ end
40
+
41
+ private
42
+
43
+ def converted_boolean_value
44
+ ['true', '1'].include?(raw_value_from_env.downcase)
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,10 @@
1
+ class ValidEnv
2
+ class OptionalEnvVar < EnvVar
3
+ def to_s
4
+ msg = "Environment Variable: #{key} (optional"
5
+ msg << " #{additional_details}" if additional_details
6
+ msg << ")"
7
+ msg
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,15 @@
1
+ class ValidEnv
2
+ class PresenceValidator < ActiveModel::EachValidator
3
+ def validate_each(record, attribute, value)
4
+ if value
5
+ if value.empty?
6
+ message = options[:message] || "Environment Variable: #{attribute} was expected to have a value, but was set to nothing."
7
+ record.errors.add :base, message
8
+ end
9
+ else
10
+ message = options[:message] || "Environment Variable: #{attribute} was expected to be set, but was not."
11
+ record.errors.add :base, message
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,10 @@
1
+ class ValidEnv
2
+ class RequiredEnvVar < EnvVar
3
+ def to_s
4
+ msg = "Environment Variable: #{key} (required"
5
+ msg << " #{additional_details}" if additional_details
6
+ msg << ")"
7
+ msg
8
+ end
9
+ end
10
+ end
metadata ADDED
@@ -0,0 +1,72 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: valid-env
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.3
5
+ platform: ruby
6
+ authors:
7
+ - Zach Dennis
8
+ - Sara Gibbons
9
+ - Sam Bleckley
10
+ - Mutually Human
11
+ autorequire:
12
+ bindir: bin
13
+ cert_chain: []
14
+ date: 2016-06-13 00:00:00.000000000 Z
15
+ dependencies:
16
+ - !ruby/object:Gem::Dependency
17
+ name: activemodel
18
+ requirement: !ruby/object:Gem::Requirement
19
+ requirements:
20
+ - - "~>"
21
+ - !ruby/object:Gem::Version
22
+ version: '4.2'
23
+ type: :runtime
24
+ prerelease: false
25
+ version_requirements: !ruby/object:Gem::Requirement
26
+ requirements:
27
+ - - "~>"
28
+ - !ruby/object:Gem::Version
29
+ version: '4.2'
30
+ description: Valid-env allows you to specify the environment variables that are required,
31
+ those that are optional, and those that depend on one another, and will throw a
32
+ useful error message immediately if something is missing.
33
+ email:
34
+ - zdennis@mutuallyhuman.com
35
+ executables: []
36
+ extensions: []
37
+ extra_rdoc_files: []
38
+ files:
39
+ - README.md
40
+ - lib/valid-env.rb
41
+ - lib/valid-env/boolean_validator.rb
42
+ - lib/valid-env/dsl_methods.rb
43
+ - lib/valid-env/env_var.rb
44
+ - lib/valid-env/optional_env_var.rb
45
+ - lib/valid-env/presence_validator.rb
46
+ - lib/valid-env/required_env_var.rb
47
+ homepage: http://github.com/mhs/valid-env
48
+ licenses:
49
+ - MIT
50
+ metadata: {}
51
+ post_install_message:
52
+ rdoc_options: []
53
+ require_paths:
54
+ - lib
55
+ required_ruby_version: !ruby/object:Gem::Requirement
56
+ requirements:
57
+ - - ">="
58
+ - !ruby/object:Gem::Version
59
+ version: '0'
60
+ required_rubygems_version: !ruby/object:Gem::Requirement
61
+ requirements:
62
+ - - ">="
63
+ - !ruby/object:Gem::Version
64
+ version: 1.3.6
65
+ requirements: []
66
+ rubyforge_project:
67
+ rubygems_version: 2.4.5.1
68
+ signing_key:
69
+ specification_version: 4
70
+ summary: Ensures the environment contains the variables your code needs
71
+ test_files: []
72
+ has_rdoc: