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 +7 -0
- data/README.md +158 -0
- data/lib/valid-env.rb +44 -0
- data/lib/valid-env/boolean_validator.rb +15 -0
- data/lib/valid-env/dsl_methods.rb +118 -0
- data/lib/valid-env/env_var.rb +47 -0
- data/lib/valid-env/optional_env_var.rb +10 -0
- data/lib/valid-env/presence_validator.rb +15 -0
- data/lib/valid-env/required_env_var.rb +10 -0
- metadata +72 -0
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,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
|
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:
|