conf_conf 1.0.2 → 2.0.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.gitignore +4 -0
- data/README.md +227 -39
- data/bin/conf_conf +136 -0
- data/conf_conf.gemspec +18 -10
- data/lib/conf_conf.rb +51 -42
- data/lib/conf_conf/cli.rb +9 -0
- data/lib/conf_conf/cli/developers.rb +48 -0
- data/lib/conf_conf/cli/environments.rb +61 -0
- data/lib/conf_conf/cli/root.rb +72 -0
- data/lib/conf_conf/cli/variables.rb +85 -0
- data/lib/conf_conf/configuration.rb +57 -0
- data/lib/conf_conf/project.rb +20 -0
- data/lib/conf_conf/project/developer.rb +38 -0
- data/lib/conf_conf/project/developers.rb +48 -0
- data/lib/conf_conf/project/environment.rb +53 -0
- data/lib/conf_conf/project/environment/storage.rb +58 -0
- data/lib/conf_conf/project/environments.rb +24 -0
- data/lib/conf_conf/remote.rb +40 -0
- data/lib/conf_conf/user.rb +38 -0
- data/spec/project/developers_spec.rb +25 -0
- data/spec/project/environment/storage_spec.rb +91 -0
- data/spec/project/environment_spec.rb +21 -0
- data/spec/spec_helper.rb +163 -0
- metadata +126 -9
- data/spec/conf_conf_spec.rb +0 -68
data/conf_conf.gemspec
CHANGED
@@ -3,15 +3,23 @@ lib = File.expand_path('../lib', __FILE__)
|
|
3
3
|
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
4
|
|
5
5
|
Gem::Specification.new do |s|
|
6
|
-
s.name
|
7
|
-
s.version
|
8
|
-
s.licenses
|
9
|
-
s.authors
|
10
|
-
s.email
|
11
|
-
s.homepage
|
12
|
-
s.description
|
13
|
-
s.summary
|
14
|
-
s.files
|
6
|
+
s.name = 'conf_conf'
|
7
|
+
s.version = '2.0.2'
|
8
|
+
s.licenses = ['MIT']
|
9
|
+
s.authors = ['James Kassemi']
|
10
|
+
s.email = ['jkassemi@gmail.com']
|
11
|
+
s.homepage = 'https://github.com/jkassemi/conf_conf'
|
12
|
+
s.description = 'Verify correctness of environment variables'
|
13
|
+
s.summary = 'A simple pattern and utility for verifying the correctness of the environment variables at application boot so we can fail fast when there\'s a configuration problem.'
|
14
|
+
s.files = `git ls-files`.split($/)
|
15
|
+
s.executables = ['conf_conf']
|
15
16
|
|
16
|
-
s.
|
17
|
+
s.add_runtime_dependency 'httpi', '~> 2.2.4'
|
18
|
+
s.add_runtime_dependency 'thor', '~> 0.19.1'
|
19
|
+
s.add_runtime_dependency 'colorize', '~> 0.7.3'
|
20
|
+
s.add_runtime_dependency 'highline', '~> 1.6.21'
|
21
|
+
s.add_runtime_dependency 'rbnacl-libsodium', '~> 0.5.0.1'
|
22
|
+
s.add_runtime_dependency 'multi_json', '~> 1.10.1'
|
23
|
+
s.add_runtime_dependency 'dotenv', '~> 0.11.1'
|
24
|
+
s.add_development_dependency 'rspec', '~> 3.0'
|
17
25
|
end
|
data/lib/conf_conf.rb
CHANGED
@@ -1,68 +1,77 @@
|
|
1
|
+
require 'multi_json'
|
1
2
|
require 'ostruct'
|
2
3
|
|
3
|
-
|
4
|
+
require 'conf_conf/project'
|
5
|
+
require 'conf_conf/configuration'
|
6
|
+
require 'conf_conf/project/developer'
|
7
|
+
require 'conf_conf/project/developers'
|
8
|
+
require 'conf_conf/project/environment'
|
9
|
+
require 'conf_conf/project/environment/storage'
|
10
|
+
require 'conf_conf/project/environments'
|
4
11
|
|
5
12
|
module ConfConf
|
13
|
+
VERSION = '2.0.2'
|
14
|
+
|
6
15
|
class MissingConfigurationValueError < StandardError; end;
|
16
|
+
class InconsistentConfigurationError < StandardError
|
17
|
+
attr_reader :inconsistencies
|
7
18
|
|
8
|
-
|
9
|
-
|
10
|
-
OpenStruct.new(Configuration.new(&block).parsed_values)
|
19
|
+
def initialize(inconsistencies)
|
20
|
+
@inconsistencies = inconsistencies
|
11
21
|
end
|
22
|
+
end
|
12
23
|
|
13
|
-
|
14
|
-
|
15
|
-
|
24
|
+
class << self
|
25
|
+
#TODO: This could be cleaned up considerably.
|
26
|
+
def configuration(environment_name=nil, &block)
|
27
|
+
if environment_name
|
28
|
+
# Load ENV
|
29
|
+
project = ConfConf::Project.new
|
30
|
+
environment = project.environments[environment_name]
|
16
31
|
|
17
|
-
|
18
|
-
|
32
|
+
environment.variables.each do |name, value|
|
33
|
+
ENV[name] = value
|
34
|
+
end
|
19
35
|
end
|
20
|
-
end
|
21
|
-
end
|
22
36
|
|
23
|
-
|
24
|
-
|
37
|
+
# Run configuration block, if given
|
38
|
+
configuration = ConfConf::Configuration.new
|
25
39
|
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
40
|
+
if block
|
41
|
+
configuration.run(block)
|
42
|
+
references = configuration.references
|
43
|
+
else
|
44
|
+
references = {}
|
45
|
+
end
|
30
46
|
|
31
|
-
|
32
|
-
|
33
|
-
|
47
|
+
if environment_name
|
48
|
+
# Find references to variables that aren't defaulted here
|
49
|
+
inconsistencies = project.inconsistencies(environment)
|
34
50
|
|
35
|
-
|
36
|
-
|
51
|
+
inconsistencies.each do |inconsistency|
|
52
|
+
if references[inconsistency] && references[inconsistency].default_value?
|
53
|
+
inconsistencies.delete(inconsistency)
|
54
|
+
end
|
55
|
+
end
|
37
56
|
|
38
|
-
|
39
|
-
|
57
|
+
if inconsistencies.length > 0
|
58
|
+
raise ConfConf::InconsistentConfigurationError.new(inconsistencies)
|
59
|
+
end
|
40
60
|
end
|
41
61
|
|
42
|
-
|
62
|
+
OpenStruct.new(configuration.parsed_values)
|
43
63
|
end
|
44
|
-
end
|
45
64
|
|
46
|
-
|
47
|
-
|
48
|
-
environment_value || default_value
|
49
|
-
end
|
65
|
+
def rails_configuration(environment_name=nil, &block)
|
66
|
+
configuration = configuration(environment_name, block)
|
50
67
|
|
51
|
-
|
52
|
-
|
53
|
-
if options.has_key? :default
|
54
|
-
options[:default]
|
55
|
-
else
|
56
|
-
raise MissingConfigurationValueError.new("Please set #{environment_key} or supply a default value")
|
68
|
+
configuration.parsed_values.each do |name, value|
|
69
|
+
Rails.configuration.send("#{key}=", value)
|
57
70
|
end
|
58
71
|
end
|
59
72
|
|
60
|
-
def
|
61
|
-
|
62
|
-
end
|
63
|
-
|
64
|
-
def environment_key
|
65
|
-
options[:from] || key.to_s.upcase
|
73
|
+
def load(environment_name)
|
74
|
+
configuration(environment_name)
|
66
75
|
end
|
67
76
|
end
|
68
77
|
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
class ConfConf::CLI::Developers < Thor
|
2
|
+
desc 'key', 'Your developer key'
|
3
|
+
def key
|
4
|
+
developer = ConfConf::Project::Developer.current
|
5
|
+
|
6
|
+
puts MultiJson.dump(developer.pretty_public_key, pretty: true)
|
7
|
+
end
|
8
|
+
|
9
|
+
desc 'permit <key>', 'Give <key> access to environment configurations'
|
10
|
+
def permit(key)
|
11
|
+
project = ConfConf::Project.new
|
12
|
+
developer = ConfConf::Project::Developer.new(key)
|
13
|
+
developers = project.developers
|
14
|
+
|
15
|
+
developers.add(developer)
|
16
|
+
developers.save
|
17
|
+
|
18
|
+
project.environments.to_a.each do |environment|
|
19
|
+
environment.save
|
20
|
+
end
|
21
|
+
|
22
|
+
puts MultiJson.dump(developers.keys.to_a, pretty: true)
|
23
|
+
end
|
24
|
+
|
25
|
+
desc 'revoke <key>', 'Deny <key> access to the environment configurations'
|
26
|
+
def revoke(key)
|
27
|
+
project = ConfConf::Project.new
|
28
|
+
developer = ConfConf::Project::Developer.new(key)
|
29
|
+
developers = project.developers
|
30
|
+
|
31
|
+
developers.remove(developer)
|
32
|
+
developers.save
|
33
|
+
|
34
|
+
project.environments.to_a.each do |environment|
|
35
|
+
environment.save
|
36
|
+
end
|
37
|
+
|
38
|
+
puts MultiJson.dump(developers.keys.to_a, pretty: true)
|
39
|
+
end
|
40
|
+
|
41
|
+
desc 'list', 'List of keys with access to the environment configurations'
|
42
|
+
def list
|
43
|
+
project = ConfConf::Project.new
|
44
|
+
developers = project.developers
|
45
|
+
|
46
|
+
puts MultiJson.dump(developers.keys.to_a, pretty: true)
|
47
|
+
end
|
48
|
+
end
|
@@ -0,0 +1,61 @@
|
|
1
|
+
module ConfConf::CLI
|
2
|
+
class Environments < Thor
|
3
|
+
desc 'list', 'Show environments'
|
4
|
+
def list
|
5
|
+
project = ConfConf::Project.new
|
6
|
+
puts MultiJson.dump(project.environments.to_a.collect(&:name), pretty: true)
|
7
|
+
end
|
8
|
+
|
9
|
+
desc 'add <environment>', 'Set up new environment'
|
10
|
+
long_desc <<-LONG
|
11
|
+
> $ conf_conf env add staging
|
12
|
+
LONG
|
13
|
+
def add(environment_name)
|
14
|
+
project = ConfConf::Project.new
|
15
|
+
environment = project.environments[environment_name]
|
16
|
+
environment.save
|
17
|
+
|
18
|
+
puts MultiJson.dump(project.environments.to_a.collect(&:name), pretty: true)
|
19
|
+
end
|
20
|
+
|
21
|
+
desc 'remove <environment>', 'Removes an existing environment'
|
22
|
+
def remove(environment_name)
|
23
|
+
project = ConfConf::Project.new
|
24
|
+
project.environments.remove(environment_name)
|
25
|
+
|
26
|
+
puts MultiJson.dump(project.environments.to_a.collect(&:name), pretty: true)
|
27
|
+
end
|
28
|
+
|
29
|
+
desc 'check (<environment>)', 'Check environment consistency'
|
30
|
+
def check(environment_name=nil)
|
31
|
+
project = ConfConf::Project.new
|
32
|
+
|
33
|
+
all_environment_variable_names = Set.new
|
34
|
+
|
35
|
+
project.environments.to_a.each do |environment|
|
36
|
+
all_environment_variable_names += environment.variables.keys
|
37
|
+
end
|
38
|
+
|
39
|
+
if environment_name.nil?
|
40
|
+
environments = project.environments.to_a
|
41
|
+
else
|
42
|
+
environments = [project.environments[environment_name]]
|
43
|
+
end
|
44
|
+
|
45
|
+
environment_warnings = {}
|
46
|
+
|
47
|
+
environments.each do |environment|
|
48
|
+
diff = all_environment_variable_names - environment.variables.keys
|
49
|
+
|
50
|
+
if diff.length > 0
|
51
|
+
diff.each do |key|
|
52
|
+
environment_warnings[environment.name] ||= {:missing => []}
|
53
|
+
environment_warnings[environment.name][:missing] << key
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
puts MultiJson.dump(environment_warnings, pretty: true)
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
@@ -0,0 +1,72 @@
|
|
1
|
+
module ConfConf
|
2
|
+
module CLI
|
3
|
+
class Root < Thor
|
4
|
+
desc 'environments', 'Manage available environments'
|
5
|
+
subcommand 'environments', ConfConf::CLI::Environments
|
6
|
+
|
7
|
+
desc 'variables', 'Configure environment variables'
|
8
|
+
subcommand 'variables', ConfConf::CLI::Variables
|
9
|
+
|
10
|
+
desc 'developers', 'Configure access credentials'
|
11
|
+
subcommand 'developers', ConfConf::CLI::Developers
|
12
|
+
|
13
|
+
desc 'init', 'Initialize conf_conf project'
|
14
|
+
long_desc <<-LONG
|
15
|
+
> $ conf_conf init
|
16
|
+
|
17
|
+
Initialize this system user's ~/.conf_conf.json
|
18
|
+
LONG
|
19
|
+
def init
|
20
|
+
account = ConfConf::Project::Developer.current
|
21
|
+
FileUtils.mkdir_p('config/conf_conf')
|
22
|
+
FileUtils.mkdir_p('config/conf_conf/environments')
|
23
|
+
puts MultiJson.dump(account: account, pretty: true)
|
24
|
+
end
|
25
|
+
|
26
|
+
desc 'export <environment>', '.env compatible export for <environment>'
|
27
|
+
def export(environment_name)
|
28
|
+
project = ConfConf::Project.new
|
29
|
+
environment = project.environments[environment_name]
|
30
|
+
|
31
|
+
environment.variables.each do |variable_name, variable_value|
|
32
|
+
puts "#{variable_name}=#{variable_value}"
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
desc 'import <environment>', 'Import from .env into <environment>'
|
37
|
+
option :dotenv, default: true
|
38
|
+
def import(environment_name)
|
39
|
+
require 'dotenv'
|
40
|
+
|
41
|
+
project = ConfConf::Project.new
|
42
|
+
environment = project.environments[environment_name]
|
43
|
+
dotenv_environment = Dotenv::Environment.new('.env')
|
44
|
+
|
45
|
+
dotenv_environment.each do |k,v|
|
46
|
+
environment.set(k, v)
|
47
|
+
end
|
48
|
+
|
49
|
+
environment.save
|
50
|
+
puts MultiJson.dump(environment.variables, pretty: true)
|
51
|
+
end
|
52
|
+
|
53
|
+
desc 'info', 'Summary of configurations'
|
54
|
+
def info
|
55
|
+
project = ConfConf::Project.new
|
56
|
+
|
57
|
+
summary = {}
|
58
|
+
summary[:environments] = project.environments.to_a.collect(&:name)
|
59
|
+
summary[:variables] = {}
|
60
|
+
|
61
|
+
project.environments.to_a.each do |environment|
|
62
|
+
environment.variables.each do |variable_name, variable_value|
|
63
|
+
summary[:variables][variable_name] ||= []
|
64
|
+
summary[:variables][variable_name] << environment.name
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
puts MultiJson.dump(summary, pretty: true)
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
@@ -0,0 +1,85 @@
|
|
1
|
+
class ConfConf::CLI::Variables < Thor
|
2
|
+
desc 'set', 'Set configuration values in the given environment'
|
3
|
+
option :env, default: false, desc: 'environment to set the config var'
|
4
|
+
long_desc <<-LONG
|
5
|
+
Sets Config Values in the given Environment.
|
6
|
+
|
7
|
+
Examples:
|
8
|
+
|
9
|
+
> $ conf_conf set X=1
|
10
|
+
|
11
|
+
Sets the Config Key `X` with Config Value `1` for all Environments.
|
12
|
+
|
13
|
+
> $ conf_conf set --env=production X=1
|
14
|
+
|
15
|
+
Sets the Config Key `X` with Config Value `1` for `production` Environment.
|
16
|
+
Sets the Config Key `X` with the Config Value `1` for the
|
17
|
+
`production` Environment.
|
18
|
+
|
19
|
+
> $ conf_conf set --env=production X=1 Y=2 Z=3
|
20
|
+
|
21
|
+
Sets the Config Keys `X`, `Y`, and `Z` with the Config
|
22
|
+
Values `1`, `2`, and `3`, respectively. Config Values are saved to
|
23
|
+
the `production` Environment.
|
24
|
+
LONG
|
25
|
+
def set(*env_variable_args)
|
26
|
+
project = ConfConf::Project.new
|
27
|
+
developers = project.developers
|
28
|
+
|
29
|
+
developer = ConfConf::Project::Developer.current
|
30
|
+
developers.add(developer)
|
31
|
+
|
32
|
+
if options[:env]
|
33
|
+
environments = [project.environments[options[:env]]]
|
34
|
+
else
|
35
|
+
environments = project.environments.to_a
|
36
|
+
end
|
37
|
+
|
38
|
+
return if environments.length == 0
|
39
|
+
|
40
|
+
env_variable_args.each do |env_variable|
|
41
|
+
config_key, config_value = env_variable.split("=").map(&:strip)
|
42
|
+
|
43
|
+
environments.each do |environment|
|
44
|
+
environment.set(config_key, config_value)
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
environments.map(&:save)
|
49
|
+
end
|
50
|
+
|
51
|
+
desc 'remove', 'Remove a configuration value'
|
52
|
+
option :env, default: 'development', desc: 'environment to remove the config var from'
|
53
|
+
long_desc <<-LONG
|
54
|
+
Examples:
|
55
|
+
|
56
|
+
> $ conf_conf remove X
|
57
|
+
|
58
|
+
Removes the `X` Environment Variable
|
59
|
+
|
60
|
+
> $ conf_conf remove --env=production X
|
61
|
+
|
62
|
+
Removes `X` from production only
|
63
|
+
LONG
|
64
|
+
def remove(*name_args)
|
65
|
+
project = ConfConf::Project.new
|
66
|
+
|
67
|
+
all_environments = project.environments.to_a
|
68
|
+
|
69
|
+
if options[:env]
|
70
|
+
environments = [project.environments[options[:env]]]
|
71
|
+
else
|
72
|
+
environments = all_environments
|
73
|
+
end
|
74
|
+
|
75
|
+
return if environments.length == 0
|
76
|
+
|
77
|
+
name_args.each do |name|
|
78
|
+
environments.each do |environment|
|
79
|
+
environment.remove(name)
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
environments.map(&:save)
|
84
|
+
end
|
85
|
+
end
|
@@ -0,0 +1,57 @@
|
|
1
|
+
module ConfConf
|
2
|
+
class Configuration
|
3
|
+
attr_reader :parsed_values
|
4
|
+
attr_reader :references
|
5
|
+
|
6
|
+
def initialize
|
7
|
+
@parsed_values = {}
|
8
|
+
@references = {}
|
9
|
+
end
|
10
|
+
|
11
|
+
def run(block)
|
12
|
+
instance_eval(&block)
|
13
|
+
end
|
14
|
+
|
15
|
+
def config(key, options={})
|
16
|
+
reference = Reference.new(key, options)
|
17
|
+
@references[reference.environment_key] = reference
|
18
|
+
|
19
|
+
value = reference.value
|
20
|
+
|
21
|
+
if block_given?
|
22
|
+
value = yield(value)
|
23
|
+
end
|
24
|
+
|
25
|
+
@parsed_values[key] = value
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
private
|
30
|
+
class Reference < Struct.new(:key, :options)
|
31
|
+
def value
|
32
|
+
environment_value || default_value
|
33
|
+
end
|
34
|
+
|
35
|
+
def default_value?
|
36
|
+
options[:default]
|
37
|
+
end
|
38
|
+
|
39
|
+
def environment_key
|
40
|
+
options[:from] || key.to_s.upcase
|
41
|
+
end
|
42
|
+
|
43
|
+
private
|
44
|
+
def default_value
|
45
|
+
if options.has_key? :default
|
46
|
+
options[:default]
|
47
|
+
else
|
48
|
+
raise ConfConf::MissingConfigurationValueError.new("Please set #{environment_key} or supply a default value")
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
def environment_value
|
53
|
+
ENV[environment_key]
|
54
|
+
end
|
55
|
+
|
56
|
+
end
|
57
|
+
end
|