envied 0.7.0 → 0.7.1
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/CHANGELOG.md +8 -0
- data/README.md +5 -50
- data/envied.gemspec +1 -1
- data/examples/extensive_envfile +46 -0
- data/lib/envied.rb +24 -206
- data/lib/envied/coercer.rb +77 -0
- data/lib/envied/configuration.rb +109 -0
- data/lib/envied/templates/heroku-env-check.tt +2 -2
- data/lib/envied/variable.rb +18 -0
- data/lib/envied/version.rb +1 -1
- data/spec/coercer_spec.rb +99 -0
- data/spec/configuration_spec.rb +33 -0
- data/spec/envied_spec.rb +35 -27
- data/spec/variable_spec.rb +15 -0
- metadata +13 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 501198780ddc5bf4762c604f90938baad9258ee4
|
4
|
+
data.tar.gz: 3027a30c7815e8925246e7f14d3e7ffcc7a3bfef
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 826a2dfaa0ac7730c2fcaa14e14ea997b3c5b665774eed3ed91110e5950a29d982c6eb761e783a7eee4be6b3d1629483b068e5a1971b0d9eccabb9dc329c41c2
|
7
|
+
data.tar.gz: ad53e3a25cb8a9c817eb14fb0a0f0a8d815fd8dd87b54dfdf0653f920dd6e8f963014d2efdb9fd56405b8ec67d40f5df36930752c6d7794bdce41d61df8c2556
|
data/CHANGELOG.md
CHANGED
data/README.md
CHANGED
@@ -2,7 +2,7 @@
|
|
2
2
|
|
3
3
|
### TL;DR ensure presence and type of your app's ENV-variables.
|
4
4
|
|
5
|
-
Features:
|
5
|
+
## Features:
|
6
6
|
|
7
7
|
* check for presence and correctness of ENV-variables
|
8
8
|
* access to typed ENV-variables (integers, booleans etc. instead of just strings)
|
@@ -16,6 +16,7 @@ Features:
|
|
16
16
|
* [Types](#types)
|
17
17
|
* [Groups](#groups)
|
18
18
|
* [Defaults](#defaults)
|
19
|
+
* [More examples](#more-examples)
|
19
20
|
* [Rails](#rails)
|
20
21
|
* [Command-line interface](#command-line-interface)
|
21
22
|
* [Testing](#testing)
|
@@ -135,56 +136,10 @@ As a rule of thumb you should only use defaults:
|
|
135
136
|
* for local development
|
136
137
|
* for ENV-variables that your application introduces (i.e. for `ENV['STAFF_EMAILS']` not for `ENV['REDIS_URL']`)
|
137
138
|
|
138
|
-
###
|
139
|
+
### More examples
|
139
140
|
|
140
|
-
|
141
|
-
|
142
|
-
# We allow defaults for local development (and local tests), but want our CI
|
143
|
-
# to mimic our production as much as possible.
|
144
|
-
# New developers that don't have RACK_ENV set, will in this way not be presented with a huge
|
145
|
-
# list of missing variables, as defaults are still enabled.
|
146
|
-
not_production_nor_ci = ->{ !(ENV['RACK_ENV'] == 'production' || ENV['CI']) }
|
147
|
-
enable_defaults!(¬_production_nor_ci)
|
148
|
-
|
149
|
-
# Your code will likely not use ENVied.RACK_ENV (better use Rails.env),
|
150
|
-
# we want it to be present though; heck, we're using it in this file!
|
151
|
-
variable :RACK_ENV
|
152
|
-
|
153
|
-
variable :FORCE_SSL, :Boolean, default: false
|
154
|
-
variable :PORT, :Integer, default: 3000
|
155
|
-
# generate the default value using the value of PORT:
|
156
|
-
variable :PUBLIC_HOST_WITH_PORT, :String, default: proc {|envied| "localhost:#{envied.PORT}" }
|
157
|
-
|
158
|
-
group :production do
|
159
|
-
variable :MAIL_PAAS_USERNAME
|
160
|
-
variable :DATABASE_URL
|
161
|
-
end
|
162
|
-
|
163
|
-
group :ci do
|
164
|
-
# ci-only stuff
|
165
|
-
end
|
166
|
-
|
167
|
-
group :not_ci do
|
168
|
-
# CI needs no puma-threads, and sidekiq-stuff etc.
|
169
|
-
# Define that here:
|
170
|
-
variable :MIN_THREADS, :Integer, default: 1
|
171
|
-
# more...
|
172
|
-
end
|
173
|
-
|
174
|
-
# Depending on our situation, we can now require the groups needed:
|
175
|
-
# At local machines:
|
176
|
-
ENVied.require(:default, :development, :not_ci) or
|
177
|
-
ENVied.require(:default, :test, :not_ci)
|
178
|
-
|
179
|
-
# At the server:
|
180
|
-
ENVied.require(:default, :production, :not_ci)
|
181
|
-
|
182
|
-
# At CI:
|
183
|
-
ENVied.require(:default, :test, :ci)
|
184
|
-
|
185
|
-
# All in one line:
|
186
|
-
ENVied.require(:default, ENV['RACK_ENV'], (ENV['CI'] ? :ci : :not_ci))
|
187
|
-
```
|
141
|
+
* See the [examples](/examples)-folder for a more extensive Envfile
|
142
|
+
* See [the Envfile](https://github.com/eval/bunny_drain/blob/c54d7d977afb5e23a92da7a2fd0d39f6a7e29bf1/Envfile) for the bunndy_drain application
|
188
143
|
|
189
144
|
## Rails
|
190
145
|
|
data/envied.gemspec
CHANGED
@@ -19,7 +19,7 @@ Gem::Specification.new do |spec|
|
|
19
19
|
spec.require_paths = ["lib"]
|
20
20
|
|
21
21
|
spec.required_ruby_version = '>= 1.9.3'
|
22
|
-
spec.add_dependency "
|
22
|
+
spec.add_dependency "coercible", '~> 1.0'
|
23
23
|
spec.add_dependency "rack", "~> 1.4"
|
24
24
|
spec.add_dependency "thor", "~> 0.15"
|
25
25
|
spec.add_development_dependency "bundler", "~> 1.5"
|
@@ -0,0 +1,46 @@
|
|
1
|
+
# -*- mode: ruby -*-¬
|
2
|
+
# We allow defaults for local development (and local tests), but want our CI
|
3
|
+
# to mimic our production as much as possible.
|
4
|
+
# New developers that don't have RACK_ENV set, will in this way not be presented with a huge
|
5
|
+
# list of missing variables, as defaults are still enabled.
|
6
|
+
not_production_nor_ci = ->{ !(ENV['RACK_ENV'] == 'production' || ENV['CI']) }
|
7
|
+
enable_defaults!(¬_production_nor_ci)
|
8
|
+
|
9
|
+
# Your code will likely not use ENVied.RACK_ENV (better use Rails.env),
|
10
|
+
# we want it to be present though; heck, we're using it in this file!
|
11
|
+
variable :RACK_ENV
|
12
|
+
|
13
|
+
variable :FORCE_SSL, :Boolean, default: false
|
14
|
+
variable :PORT, :Integer, default: 3000
|
15
|
+
# generate the default value using the value of PORT:
|
16
|
+
variable :PUBLIC_HOST_WITH_PORT, :String, default: proc {|envied| "localhost:#{envied.PORT}" }
|
17
|
+
|
18
|
+
group :production do
|
19
|
+
variable :MAIL_PAAS_USERNAME
|
20
|
+
variable :DATABASE_URL
|
21
|
+
end
|
22
|
+
|
23
|
+
group :ci do
|
24
|
+
# ci-only stuff
|
25
|
+
end
|
26
|
+
|
27
|
+
group :not_ci do
|
28
|
+
# CI needs no puma-threads, and sidekiq-stuff etc.
|
29
|
+
# Define that here:
|
30
|
+
variable :MIN_THREADS, :Integer, default: 1
|
31
|
+
# more...
|
32
|
+
end
|
33
|
+
|
34
|
+
# Depending on our situation, we can now require the correct groups in our initialization-file:
|
35
|
+
# At local machines:
|
36
|
+
# ENVied.require(:default, :development, :not_ci) or
|
37
|
+
# ENVied.require(:default, :test, :not_ci)
|
38
|
+
|
39
|
+
# At the server:
|
40
|
+
# ENVied.require(:default, :production, :not_ci)
|
41
|
+
|
42
|
+
# At CI:
|
43
|
+
# ENVied.require(:default, :test, :ci)
|
44
|
+
|
45
|
+
# All in one line:
|
46
|
+
# ENVied.require(:default, ENV['RACK_ENV'], (ENV['CI'] ? :ci : :not_ci))
|
data/lib/envied.rb
CHANGED
@@ -1,238 +1,56 @@
|
|
1
1
|
require 'envied/version'
|
2
2
|
require 'envied/cli'
|
3
|
-
require '
|
3
|
+
require 'envied/coercer'
|
4
|
+
require 'envied/variable'
|
5
|
+
require 'envied/configuration'
|
4
6
|
|
5
7
|
class ENVied
|
6
|
-
module Hashable
|
7
|
-
def to_hash
|
8
|
-
require 'rack/utils'
|
9
|
-
::Rack::Utils.parse_nested_query(self)
|
10
|
-
end
|
11
|
-
end
|
12
|
-
|
13
|
-
module Arrayable
|
14
|
-
def to_a
|
15
|
-
self.split(/(?<!\\), ?/).map{|i| i.gsub(/\\,/,',') }
|
16
|
-
end
|
17
|
-
end
|
18
|
-
|
19
|
-
class Configuration
|
20
|
-
include Virtus.model
|
21
|
-
|
22
|
-
def self.variable(name, type = :String, options = {})
|
23
|
-
options = { default: nil, strict: true, group: self.current_group }.merge(options)
|
24
|
-
type = Array if type == :Array
|
25
|
-
attribute(name, type, options)
|
26
|
-
end
|
27
|
-
|
28
|
-
def self.group(name, &block)
|
29
|
-
self.current_group = name.to_sym
|
30
|
-
yield
|
31
|
-
ensure
|
32
|
-
self.current_group = :default
|
33
|
-
end
|
34
|
-
|
35
|
-
def self.enable_defaults
|
36
|
-
(@enable_defaults ||= false).respond_to?(:call) ?
|
37
|
-
@enable_defaults.call :
|
38
|
-
@enable_defaults
|
39
|
-
end
|
40
|
-
|
41
|
-
def self.enable_defaults!(value = nil, &block)
|
42
|
-
value ||= block if block_given?
|
43
|
-
@enable_defaults = value
|
44
|
-
end
|
45
|
-
|
46
|
-
class << self
|
47
|
-
alias_method :defaults_enabled?, :enable_defaults
|
48
|
-
alias_method :enable_defaults=, :enable_defaults!
|
49
|
-
attr_writer :current_group
|
50
|
-
end
|
51
|
-
|
52
|
-
def self.current_group
|
53
|
-
@current_group ||= :default
|
54
|
-
end
|
55
|
-
end
|
56
|
-
|
57
|
-
def self.configuration(options = {}, &block)
|
58
|
-
if block_given?
|
59
|
-
@configuration = build_configuration(&block).tap do |c|
|
60
|
-
options.each {|k, v| c.public_send("#{k}=", v) }
|
61
|
-
end
|
62
|
-
end
|
63
|
-
@configuration ||= build_configuration
|
64
|
-
end
|
65
|
-
|
66
|
-
def self.configure(options = {}, &block)
|
67
|
-
deprecation_warning "ENVied.configure will be deprecated. Please generate an Envfile instead (see the envied command)."
|
68
|
-
configuration(options, &block)
|
69
|
-
end
|
70
|
-
|
71
8
|
class << self
|
72
|
-
|
73
|
-
end
|
74
|
-
|
75
|
-
def self.build_configuration(&block)
|
76
|
-
Class.new(Configuration).tap do |c|
|
77
|
-
c.instance_eval(&block) if block_given?
|
78
|
-
end
|
9
|
+
attr_reader :env, :config
|
79
10
|
end
|
80
11
|
|
81
12
|
def self.require(*groups)
|
82
|
-
|
83
|
-
@
|
84
|
-
|
85
|
-
self.required_groups = groups.map(&:to_sym)
|
86
|
-
else
|
87
|
-
self.required_groups = [:default]
|
88
|
-
end
|
89
|
-
ensure_configured!
|
13
|
+
@config ||= Configuration.load
|
14
|
+
@env ||= EnvProxy.new(@config, groups: required_groups(*groups))
|
15
|
+
|
90
16
|
error_on_missing_variables!
|
91
17
|
error_on_uncoercible_variables!
|
92
|
-
|
93
|
-
_required_variables = required_variables
|
94
|
-
group_configuration = build_configuration do
|
95
|
-
_required_variables.each do |v|
|
96
|
-
@attribute_set << v
|
97
|
-
end
|
98
|
-
end
|
99
|
-
@instance = group_configuration.new(env)
|
100
|
-
end
|
101
|
-
|
102
|
-
def self.springified_require(*args)
|
103
|
-
springify { ENVied.require(*args) }
|
104
|
-
end
|
105
|
-
|
106
|
-
def self.springify(&block)
|
107
|
-
if defined?(Spring) && Spring.respond_to?(:watcher)
|
108
|
-
Spring.after_fork(&block)
|
109
|
-
else
|
110
|
-
block.call
|
111
|
-
end
|
112
|
-
end
|
113
|
-
|
114
|
-
def self.ensure_configured!
|
115
|
-
# Backward compat: load Envfile only when it's present
|
116
|
-
configure_via_envfile if envfile_exist?
|
117
|
-
end
|
118
|
-
|
119
|
-
def self.envfile
|
120
|
-
File.expand_path('Envfile')
|
121
|
-
end
|
122
|
-
|
123
|
-
def self.envfile_exist?
|
124
|
-
File.exist?(envfile)
|
125
|
-
end
|
126
|
-
|
127
|
-
def self.configure_via_envfile
|
128
|
-
configuration { eval(File.read(ENVied.envfile)) }
|
129
18
|
end
|
130
19
|
|
131
20
|
def self.error_on_missing_variables!
|
132
|
-
|
133
|
-
|
134
|
-
end
|
21
|
+
names = env.missing_variables.map(&:name)
|
22
|
+
raise "The following environment variables should be set: #{names * ', '}" if names.any?
|
135
23
|
end
|
136
24
|
|
137
25
|
def self.error_on_uncoercible_variables!
|
138
|
-
|
139
|
-
|
140
|
-
single_error = "ENV['%{name}'] ('%{value}' can't be coerced to %{type})"
|
141
|
-
errors = non_coercible_variables.map do |v|
|
142
|
-
var_type = v.type.to_s.split("::").last
|
143
|
-
single_error % { name: v.name, value: env_value_or_default(v), type: var_type }
|
144
|
-
end.join ", "
|
145
|
-
|
146
|
-
raise "Some ENV-variables are not coercible: #{errors}"
|
26
|
+
errors = env.uncoercible_variables.map do |v|
|
27
|
+
"%{name} ('%{value}' can't be coerced to %{type})" % {name: v.name, value: env.value_to_coerce(v), type: v.type }
|
147
28
|
end
|
29
|
+
raise "The following environment variables are not coercible: #{errors.join(", ")}" if errors.any?
|
148
30
|
end
|
149
31
|
|
150
|
-
def self.
|
151
|
-
|
32
|
+
def self.required_groups(*groups)
|
33
|
+
result = groups.compact
|
34
|
+
result.any? ? result.map(&:to_sym) : [:default]
|
152
35
|
end
|
153
36
|
|
154
|
-
def self.
|
155
|
-
|
156
|
-
|
37
|
+
def self.springify(&block)
|
38
|
+
if spring_enabled?
|
39
|
+
Spring.after_fork(&block)
|
40
|
+
else
|
41
|
+
block.call
|
157
42
|
end
|
158
43
|
end
|
159
44
|
|
160
|
-
def self.
|
161
|
-
|
162
|
-
end
|
163
|
-
|
164
|
-
# Yields the assigned default for the variable.
|
165
|
-
# When defaults are disabled, nil is returned.
|
166
|
-
def self.default_value(variable)
|
167
|
-
defaults_enabled? ? variable.default_value.value : nil
|
168
|
-
end
|
169
|
-
|
170
|
-
# A list of all configured variable names.
|
171
|
-
#
|
172
|
-
# @example
|
173
|
-
# ENVied.required_variable_names
|
174
|
-
# # => [:DATABASE_URL]
|
175
|
-
#
|
176
|
-
# @return [Array<Symbol>] the list of variable names
|
177
|
-
def self.required_variable_names
|
178
|
-
required_variables.map(&:name).map(&:to_sym)
|
179
|
-
end
|
180
|
-
|
181
|
-
def self.required_variables
|
182
|
-
from_required_group = ->(var){ self.required_groups.include?(var.options[:group]) }
|
183
|
-
configured_variables.to_a.keep_if(&from_required_group)
|
184
|
-
end
|
185
|
-
|
186
|
-
def self.configured_variables
|
187
|
-
configuration.attribute_set.dup#.to_a.keep_if(&var_from_required_group)
|
188
|
-
end
|
189
|
-
|
190
|
-
def self.provided_variable_names
|
191
|
-
ENV.keys.map(&:to_sym)
|
192
|
-
end
|
193
|
-
|
194
|
-
def self.non_coercible_variables
|
195
|
-
required_variables.reject(&method(:variable_coercible?))
|
196
|
-
end
|
197
|
-
|
198
|
-
def self.variable_coercible?(variable)
|
199
|
-
var_value = env_value_or_default(variable)
|
200
|
-
return true if var_value.respond_to?(:call)
|
201
|
-
|
202
|
-
!variable.coerce(var_value).nil?
|
203
|
-
rescue Virtus::CoercionError
|
204
|
-
return false
|
205
|
-
end
|
206
|
-
|
207
|
-
def self.missing_variable_names
|
208
|
-
unprovided = required_variable_names - provided_variable_names
|
209
|
-
unprovided -= names_of_required_variables_with_defaults if defaults_enabled?
|
210
|
-
unprovided
|
211
|
-
end
|
212
|
-
|
213
|
-
def self.names_of_required_variables_with_defaults
|
214
|
-
required_variables_with_defaults.map(&:name).map(&:to_sym)
|
215
|
-
end
|
216
|
-
|
217
|
-
def self.required_variables_with_defaults
|
218
|
-
required_variables.map do |v|
|
219
|
-
v unless v.default_value.value.nil?
|
220
|
-
end.compact
|
221
|
-
end
|
222
|
-
|
223
|
-
def self.defaults_enabled?
|
224
|
-
configuration.enable_defaults
|
45
|
+
def self.spring_enabled?
|
46
|
+
defined?(Spring) && Spring.respond_to?(:watcher)
|
225
47
|
end
|
226
48
|
|
227
49
|
def self.method_missing(method, *args, &block)
|
228
|
-
respond_to_missing?(method) ?
|
50
|
+
respond_to_missing?(method) ? (env && env[method.to_s]) : super
|
229
51
|
end
|
230
52
|
|
231
53
|
def self.respond_to_missing?(method, include_private = false)
|
232
|
-
|
233
|
-
end
|
234
|
-
|
235
|
-
def self.deprecation_warning(msg)
|
236
|
-
puts "DEPRECATION WARNING: #{msg}"
|
54
|
+
(env && env.has_key?(method)) || super
|
237
55
|
end
|
238
56
|
end
|
@@ -0,0 +1,77 @@
|
|
1
|
+
require 'coercible'
|
2
|
+
|
3
|
+
# Responsible for all string to type coercions.
|
4
|
+
class ENVied::Coercer
|
5
|
+
module CoercerExts
|
6
|
+
def to_array(str)
|
7
|
+
str.split(/(?<!\\),/).map{|i| i.gsub(/\\,/,',') }
|
8
|
+
end
|
9
|
+
|
10
|
+
def to_hash(str)
|
11
|
+
require 'rack/utils'
|
12
|
+
::Rack::Utils.parse_query(str)
|
13
|
+
end
|
14
|
+
end
|
15
|
+
Coercible::Coercer::String.send(:include, CoercerExts)
|
16
|
+
|
17
|
+
# Coerce strings to specific type.
|
18
|
+
#
|
19
|
+
# @param string [String] the string to be coerced
|
20
|
+
# @param type [#to_sym] the type to coerce to
|
21
|
+
#
|
22
|
+
# @example
|
23
|
+
# ENVied::Coercer.new.coerce('1', :Integer)
|
24
|
+
# # => 1
|
25
|
+
#
|
26
|
+
# @return [type] the coerced string.
|
27
|
+
def coerce(string, type)
|
28
|
+
unless supported_type?(type)
|
29
|
+
raise ArgumentError, "#{type.inspect} is not supported type"
|
30
|
+
end
|
31
|
+
coerce_method_for(type.to_sym)[string]
|
32
|
+
end
|
33
|
+
|
34
|
+
def coerce_method_for(type)
|
35
|
+
return nil unless supported_type?(type)
|
36
|
+
coercer.method("to_#{type.downcase}")
|
37
|
+
end
|
38
|
+
|
39
|
+
def self.supported_types
|
40
|
+
@supported_types ||= begin
|
41
|
+
[:hash, :array, :time, :date, :symbol, :boolean, :integer, :string]
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
# Whether or not Coercer can coerce strings to the provided type.
|
46
|
+
#
|
47
|
+
# @param type [#to_sym] the type (case insensitive)
|
48
|
+
#
|
49
|
+
# @example
|
50
|
+
# ENVied::Coercer.supported_type?('string')
|
51
|
+
# # => true
|
52
|
+
#
|
53
|
+
# @return [Boolean] whether type is supported.
|
54
|
+
def self.supported_type?(type)
|
55
|
+
supported_types.include?(type.to_sym.downcase)
|
56
|
+
end
|
57
|
+
|
58
|
+
def supported_type?(type)
|
59
|
+
self.class.supported_type?(type)
|
60
|
+
end
|
61
|
+
|
62
|
+
def coercer
|
63
|
+
@coercer ||= Coercible::Coercer.new[String]
|
64
|
+
end
|
65
|
+
|
66
|
+
def coerced?(value)
|
67
|
+
!value.kind_of?(String)
|
68
|
+
end
|
69
|
+
|
70
|
+
def coercible?(string, type)
|
71
|
+
return false unless supported_type?(type)
|
72
|
+
coerce(string, type)
|
73
|
+
true
|
74
|
+
rescue Coercible::UnsupportedCoercion
|
75
|
+
false
|
76
|
+
end
|
77
|
+
end
|
@@ -0,0 +1,109 @@
|
|
1
|
+
class ENVied
|
2
|
+
class Configuration
|
3
|
+
attr_reader :current_group, :defaults_enabled
|
4
|
+
|
5
|
+
def initialize(options = {})
|
6
|
+
@defaults_enabled = options.fetch(:enable_defaults, false)
|
7
|
+
end
|
8
|
+
|
9
|
+
def self.load
|
10
|
+
new.tap do |v|
|
11
|
+
v.instance_eval(File.read(File.expand_path('Envfile')))
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
def enable_defaults!(value = nil, &block)
|
16
|
+
@defaults_enabled = (value.nil? ? block : value)
|
17
|
+
end
|
18
|
+
|
19
|
+
def defaults_enabled?
|
20
|
+
@defaults_enabled.respond_to?(:call) ?
|
21
|
+
@defaults_enabled.call :
|
22
|
+
@defaults_enabled
|
23
|
+
end
|
24
|
+
|
25
|
+
def variable(name, type = :String, options = {})
|
26
|
+
options[:group] = current_group if current_group
|
27
|
+
variables << ENVied::Variable.new(name, type, options)
|
28
|
+
end
|
29
|
+
|
30
|
+
def group(name, &block)
|
31
|
+
@current_group = name.to_sym
|
32
|
+
yield
|
33
|
+
ensure
|
34
|
+
@current_group = nil
|
35
|
+
end
|
36
|
+
|
37
|
+
def variables
|
38
|
+
@variables ||= []
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
# Responsible for anything related to the ENV.
|
43
|
+
class EnvProxy
|
44
|
+
attr_reader :config, :coercer, :groups
|
45
|
+
|
46
|
+
def initialize(config, options = {})
|
47
|
+
@config = config
|
48
|
+
@coercer = options.fetch(:coercer, ENVied::Coercer.new)
|
49
|
+
@groups = options.fetch(:groups, [])
|
50
|
+
end
|
51
|
+
|
52
|
+
def missing_variables
|
53
|
+
variables.select(&method(:missing?))
|
54
|
+
end
|
55
|
+
|
56
|
+
def uncoercible_variables
|
57
|
+
variables.reject(&method(:coerced?)).reject(&method(:coercible?))
|
58
|
+
end
|
59
|
+
|
60
|
+
def variables
|
61
|
+
@variables ||= begin
|
62
|
+
config.variables.select {|v| groups.include?(v.group) }
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
def variables_by_name
|
67
|
+
Hash[variables.map {|v| [v.name, v] }]
|
68
|
+
end
|
69
|
+
|
70
|
+
def [](name)
|
71
|
+
coerce(variables_by_name[name.to_sym])
|
72
|
+
end
|
73
|
+
|
74
|
+
def has_key?(name)
|
75
|
+
variables_by_name[name.to_sym]
|
76
|
+
end
|
77
|
+
|
78
|
+
def env_value_of(var)
|
79
|
+
ENV[var.name.to_s]
|
80
|
+
end
|
81
|
+
|
82
|
+
def default_value_of(var)
|
83
|
+
var.default_value(ENVied, var)
|
84
|
+
end
|
85
|
+
|
86
|
+
def value_to_coerce(var)
|
87
|
+
return env_value_of(var) unless env_value_of(var).nil?
|
88
|
+
config.defaults_enabled? ? default_value_of(var) : nil
|
89
|
+
end
|
90
|
+
|
91
|
+
def coerce(var)
|
92
|
+
coerced?(var) ?
|
93
|
+
value_to_coerce(var) :
|
94
|
+
coercer.coerce(value_to_coerce(var), var.type)
|
95
|
+
end
|
96
|
+
|
97
|
+
def coercible?(var)
|
98
|
+
coercer.coercible?(value_to_coerce(var), var.type)
|
99
|
+
end
|
100
|
+
|
101
|
+
def missing?(var)
|
102
|
+
value_to_coerce(var).nil?
|
103
|
+
end
|
104
|
+
|
105
|
+
def coerced?(var)
|
106
|
+
coercer.coerced?(value_to_coerce(var))
|
107
|
+
end
|
108
|
+
end
|
109
|
+
end
|
@@ -3,7 +3,7 @@
|
|
3
3
|
# Check the config of a Heroku app against the defined variables in `Envfile`
|
4
4
|
|
5
5
|
<%- if @app %>
|
6
|
-
HEROKU_APP=<%= @app %> exec heroku config | bundle exec envied check:heroku --groups <%= @groups.join("
|
6
|
+
HEROKU_APP=<%= @app %> exec heroku config | bundle exec envied check:heroku --groups <%= @groups.join(" ") %>
|
7
7
|
<%- else %>
|
8
|
-
exec heroku config | bundle exec envied check:heroku --groups <%= @groups.join("
|
8
|
+
exec heroku config | bundle exec envied check:heroku --groups <%= @groups.join(" ") %>
|
9
9
|
<%- end %>
|
@@ -0,0 +1,18 @@
|
|
1
|
+
class ENVied::Variable
|
2
|
+
attr_reader :name, :type, :group, :default
|
3
|
+
|
4
|
+
def initialize(name, type, options = {})
|
5
|
+
@name = name.to_sym
|
6
|
+
@type = type.to_sym
|
7
|
+
@group = options.fetch(:group, :default).to_sym
|
8
|
+
@default = options[:default]
|
9
|
+
|
10
|
+
#if !@default.is_a? String
|
11
|
+
# raise ArgumentError, "Default values should be strings (variable #{@name})"
|
12
|
+
#end
|
13
|
+
end
|
14
|
+
|
15
|
+
def default_value(*args)
|
16
|
+
default.respond_to?(:call) ? default[*args] : default
|
17
|
+
end
|
18
|
+
end
|
data/lib/envied/version.rb
CHANGED
@@ -0,0 +1,99 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe ENVied::Coercer do
|
4
|
+
it { is_expected.to respond_to :coerce }
|
5
|
+
|
6
|
+
describe '#coerce' do
|
7
|
+
let(:coercer){ described_class.new }
|
8
|
+
|
9
|
+
def coerce_to(type)
|
10
|
+
->(str){ coercer.coerce(str, type) }
|
11
|
+
end
|
12
|
+
|
13
|
+
describe 'string coercion' do
|
14
|
+
let(:coerce){ coerce_to(:String) }
|
15
|
+
|
16
|
+
it 'yields the input untouched' do
|
17
|
+
expect(coerce['1']).to eq '1'
|
18
|
+
expect(coerce[' 1']).to eq ' 1'
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
describe 'integer coercion' do
|
23
|
+
let(:coerce){ coerce_to(:Integer) }
|
24
|
+
|
25
|
+
it 'converts strings to integers' do
|
26
|
+
expect(coerce['1']).to eq 1
|
27
|
+
expect(coerce['-1']).to eq(-1)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
describe 'boolean coercion' do
|
32
|
+
let(:coerce){ coerce_to(:Boolean) }
|
33
|
+
|
34
|
+
it "converts 'true' and 'false'" do
|
35
|
+
expect(coerce['true']).to eq true
|
36
|
+
expect(coerce['false']).to eq false
|
37
|
+
end
|
38
|
+
|
39
|
+
it "converts '1' and '0'" do
|
40
|
+
expect(coerce['1']).to eq true
|
41
|
+
expect(coerce['0']).to eq false
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
describe 'symbol coercion' do
|
46
|
+
let(:coerce){ coerce_to(:Symbol) }
|
47
|
+
|
48
|
+
it 'converts strings to symbols' do
|
49
|
+
expect(coerce['a']).to eq :a
|
50
|
+
expect(coerce['nice_symbol']).to eq :nice_symbol
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
describe 'date coercion' do
|
55
|
+
let(:coerce){ coerce_to(:Date) }
|
56
|
+
|
57
|
+
it 'converts strings to date' do
|
58
|
+
expect(coerce['2014-12-25']).to eq Date.parse('2014-12-25')
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
describe 'time coercion' do
|
63
|
+
let(:coerce){ coerce_to(:Time) }
|
64
|
+
|
65
|
+
it 'converts strings to time' do
|
66
|
+
expect(coerce['4:00']).to eq Time.parse('4:00')
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
describe 'array coercion' do
|
71
|
+
let(:coerce){ coerce_to(:Array) }
|
72
|
+
|
73
|
+
it 'converts strings to array' do
|
74
|
+
{
|
75
|
+
'a,b' => ['a','b'],
|
76
|
+
' a, b' => [' a',' b'],
|
77
|
+
'apples,and\, of course\, pears' => ['apples','and, of course, pears'],
|
78
|
+
}.each do |i, o|
|
79
|
+
expect(coerce[i]).to eq o
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
describe 'hash coercion' do
|
85
|
+
let(:coerce){ coerce_to(:Hash) }
|
86
|
+
|
87
|
+
it 'converts strings to hashes' do
|
88
|
+
{
|
89
|
+
'a=1' => {'a' => '1'},
|
90
|
+
'a=1&b=2' => {'a' => '1', 'b' => '2'},
|
91
|
+
'a=&b=2' => {'a' => '', 'b' => '2'},
|
92
|
+
'a&b=2' => {'a' => nil, 'b' => '2'},
|
93
|
+
}.each do |i, o|
|
94
|
+
expect(coerce[i]).to eq o
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe ENVied::Configuration do
|
4
|
+
it { is_expected.to respond_to :variable }
|
5
|
+
it { is_expected.to respond_to :enable_defaults! }
|
6
|
+
it { is_expected.to respond_to :defaults_enabled? }
|
7
|
+
|
8
|
+
describe '#variable' do
|
9
|
+
it 'results in an added variable' do
|
10
|
+
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
describe 'defaults' do
|
15
|
+
it 'is disabled by default' do
|
16
|
+
expect(subject.defaults_enabled?).to_not be
|
17
|
+
end
|
18
|
+
|
19
|
+
describe '#enable_defaults!' do
|
20
|
+
it 'can be passed a value' do
|
21
|
+
expect {
|
22
|
+
subject.enable_defaults!(true)
|
23
|
+
}.to change { subject.defaults_enabled? }
|
24
|
+
end
|
25
|
+
|
26
|
+
it 'can be passed a block' do
|
27
|
+
expect {
|
28
|
+
subject.enable_defaults! { true }
|
29
|
+
}.to change { subject.defaults_enabled? }.to(true)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
data/spec/envied_spec.rb
CHANGED
@@ -1,10 +1,14 @@
|
|
1
1
|
require 'spec_helper'
|
2
2
|
|
3
3
|
describe ENVied do
|
4
|
-
|
4
|
+
describe 'class' do
|
5
|
+
subject { described_class }
|
5
6
|
|
6
|
-
|
7
|
-
|
7
|
+
it { is_expected.to respond_to :require }
|
8
|
+
end
|
9
|
+
|
10
|
+
describe 'responding to methods that are variables' do
|
11
|
+
end
|
8
12
|
|
9
13
|
before do
|
10
14
|
reset_env
|
@@ -12,7 +16,7 @@ describe ENVied do
|
|
12
16
|
end
|
13
17
|
|
14
18
|
def reset_configuration
|
15
|
-
ENVied.instance_eval { @
|
19
|
+
ENVied.instance_eval { @config = nil }
|
16
20
|
end
|
17
21
|
|
18
22
|
def reset_env
|
@@ -27,16 +31,19 @@ describe ENVied do
|
|
27
31
|
end
|
28
32
|
|
29
33
|
def configure(options = {}, &block)
|
30
|
-
|
34
|
+
ENVied.instance_eval do
|
35
|
+
@config = ENVied::Configuration.new(options).tap{|c| c.instance_eval(&block)}
|
36
|
+
end
|
31
37
|
self
|
32
38
|
end
|
33
39
|
|
34
40
|
def configured_with(hash = {})
|
35
|
-
|
41
|
+
config = ENVied::Configuration.new.tap do |c|
|
36
42
|
hash.each do |name, type|
|
37
|
-
variable(name, *type)
|
43
|
+
c.variable(name, *type)
|
38
44
|
end
|
39
45
|
end
|
46
|
+
ENVied.instance_eval{ @config = config }
|
40
47
|
self
|
41
48
|
end
|
42
49
|
|
@@ -53,14 +60,14 @@ describe ENVied do
|
|
53
60
|
configured_with(a: :Integer).and_ENV({'a' => '1'})
|
54
61
|
described_class.require
|
55
62
|
|
56
|
-
|
63
|
+
expect(described_class).to respond_to :a
|
57
64
|
end
|
58
65
|
|
59
66
|
it 'responds not to unconfigured variables' do
|
60
67
|
unconfigured.and_ENV({'A' => '1'})
|
61
68
|
described_class.require
|
62
69
|
|
63
|
-
|
70
|
+
expect(described_class).to_not respond_to :B
|
64
71
|
end
|
65
72
|
|
66
73
|
context 'ENV contains not all configured variables' do
|
@@ -69,7 +76,7 @@ describe ENVied do
|
|
69
76
|
specify do
|
70
77
|
expect {
|
71
78
|
ENVied.require
|
72
|
-
}.to raise_error(/
|
79
|
+
}.to raise_error(/The following environment variables should be set: a/)
|
73
80
|
end
|
74
81
|
end
|
75
82
|
|
@@ -79,14 +86,14 @@ describe ENVied do
|
|
79
86
|
specify do
|
80
87
|
expect {
|
81
88
|
ENVied.require
|
82
|
-
}.to raise_error(/
|
89
|
+
}.to raise_error(/A \('NaN' can't be coerced to Integer/)
|
83
90
|
end
|
84
91
|
end
|
85
92
|
|
86
93
|
context 'bug: default value "false" is not coercible' do
|
87
94
|
before {
|
88
95
|
configure(enable_defaults: true) do
|
89
|
-
variable :FORCE_SSL, :Boolean, default:
|
96
|
+
variable :FORCE_SSL, :Boolean, default: true
|
90
97
|
end
|
91
98
|
}
|
92
99
|
|
@@ -99,35 +106,36 @@ describe ENVied do
|
|
99
106
|
|
100
107
|
describe 'defaults' do
|
101
108
|
describe 'setting' do
|
102
|
-
subject { described_class.
|
109
|
+
subject { described_class.config }
|
110
|
+
#subject { ENVied::Configuration.new }
|
103
111
|
|
104
|
-
it 'is disabled by default' do
|
105
|
-
|
106
|
-
end
|
112
|
+
#it 'is disabled by default' do
|
113
|
+
# expect(subject.defaults_enabled?).to_not be
|
114
|
+
#end
|
107
115
|
|
108
116
|
it 'can be enabled via #configure' do
|
109
117
|
configure(enable_defaults: true){ }
|
110
118
|
|
111
|
-
expect(subject.
|
119
|
+
expect(subject.defaults_enabled?).to be
|
112
120
|
end
|
113
121
|
|
114
122
|
it 'can be enabled via a configure-block' do
|
115
|
-
configure { self.enable_defaults
|
123
|
+
configure { self.enable_defaults!(true) }
|
116
124
|
|
117
|
-
expect(subject.
|
125
|
+
expect(subject.defaults_enabled?).to be
|
118
126
|
end
|
119
127
|
|
120
128
|
it 'can be assigned a Proc' do
|
121
|
-
configure { self.enable_defaults
|
129
|
+
configure { self.enable_defaults! { true } }
|
122
130
|
|
123
|
-
expect(subject.
|
131
|
+
expect(subject.defaults_enabled?).to be
|
124
132
|
end
|
125
133
|
end
|
126
134
|
|
127
135
|
describe 'assigning' do
|
128
136
|
it 'can be a value' do
|
129
137
|
configure(enable_defaults: true) do
|
130
|
-
variable :A, :Integer, default: 1
|
138
|
+
variable :A, :Integer, default: '1'
|
131
139
|
end
|
132
140
|
described_class.require
|
133
141
|
|
@@ -136,7 +144,7 @@ describe ENVied do
|
|
136
144
|
|
137
145
|
it 'can be a Proc' do
|
138
146
|
configure(enable_defaults: true) do
|
139
|
-
variable :A, :Integer, default: proc { 1 }
|
147
|
+
variable :A, :Integer, default: proc { "1" }
|
140
148
|
end
|
141
149
|
described_class.require
|
142
150
|
|
@@ -145,7 +153,7 @@ describe ENVied do
|
|
145
153
|
|
146
154
|
it 'is ignored if defaults are disabled' do
|
147
155
|
configure(enable_defaults: false) do
|
148
|
-
variable :A, :Integer, default: 1
|
156
|
+
variable :A, :Integer, default: "1"
|
149
157
|
end.and_no_ENV
|
150
158
|
|
151
159
|
expect {
|
@@ -153,9 +161,9 @@ describe ENVied do
|
|
153
161
|
}.to raise_error
|
154
162
|
end
|
155
163
|
|
156
|
-
it 'is
|
164
|
+
it 'is ignored if ENV is provided' do
|
157
165
|
configure(enable_defaults: true) do
|
158
|
-
variable :A, :Integer, default: 1
|
166
|
+
variable :A, :Integer, default: "1"
|
159
167
|
end.and_ENV('A' => '2')
|
160
168
|
described_class.require
|
161
169
|
|
@@ -257,7 +265,7 @@ describe ENVied do
|
|
257
265
|
end
|
258
266
|
|
259
267
|
it 'yields array from string' do
|
260
|
-
expect(ENVied.moar).to eq ['a','b','and, c']
|
268
|
+
expect(ENVied.moar).to eq ['a',' b',' and, c']
|
261
269
|
end
|
262
270
|
end
|
263
271
|
end
|
metadata
CHANGED
@@ -1,17 +1,17 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: envied
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.7.
|
4
|
+
version: 0.7.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Gert Goet
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2014-08-
|
11
|
+
date: 2014-08-28 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
|
-
name:
|
14
|
+
name: coercible
|
15
15
|
requirement: !ruby/object:Gem::Requirement
|
16
16
|
requirements:
|
17
17
|
- - "~>"
|
@@ -112,14 +112,21 @@ files:
|
|
112
112
|
- Rakefile
|
113
113
|
- bin/envied
|
114
114
|
- envied.gemspec
|
115
|
+
- examples/extensive_envfile
|
115
116
|
- lib/envied.rb
|
116
117
|
- lib/envied/cli.rb
|
118
|
+
- lib/envied/coercer.rb
|
119
|
+
- lib/envied/configuration.rb
|
117
120
|
- lib/envied/templates/Envfile.tt
|
118
121
|
- lib/envied/templates/heroku-env-check.tt
|
119
122
|
- lib/envied/templates/rails-initializer.tt
|
123
|
+
- lib/envied/variable.rb
|
120
124
|
- lib/envied/version.rb
|
125
|
+
- spec/coercer_spec.rb
|
126
|
+
- spec/configuration_spec.rb
|
121
127
|
- spec/envied_spec.rb
|
122
128
|
- spec/spec_helper.rb
|
129
|
+
- spec/variable_spec.rb
|
123
130
|
homepage: https://github.com/eval/envied
|
124
131
|
licenses:
|
125
132
|
- MIT
|
@@ -145,5 +152,8 @@ signing_key:
|
|
145
152
|
specification_version: 4
|
146
153
|
summary: Ensure presence and type of ENV-variables
|
147
154
|
test_files:
|
155
|
+
- spec/coercer_spec.rb
|
156
|
+
- spec/configuration_spec.rb
|
148
157
|
- spec/envied_spec.rb
|
149
158
|
- spec/spec_helper.rb
|
159
|
+
- spec/variable_spec.rb
|