chamber 1.0.3 → 2.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +288 -173
- data/bin/chamber +2 -288
- data/lib/chamber.rb +19 -67
- data/lib/chamber/binary/heroku.rb +59 -0
- data/lib/chamber/binary/runner.rb +94 -0
- data/lib/chamber/binary/travis.rb +23 -0
- data/lib/chamber/commands/base.rb +33 -0
- data/lib/chamber/commands/comparable.rb +37 -0
- data/lib/chamber/commands/compare.rb +46 -0
- data/lib/chamber/commands/context_resolver.rb +72 -0
- data/lib/chamber/commands/files.rb +12 -0
- data/lib/chamber/commands/heroku.rb +30 -0
- data/lib/chamber/commands/heroku/clear.rb +25 -0
- data/lib/chamber/commands/heroku/compare.rb +31 -0
- data/lib/chamber/commands/heroku/pull.rb +30 -0
- data/lib/chamber/commands/heroku/push.rb +25 -0
- data/lib/chamber/commands/initialize.rb +73 -0
- data/lib/chamber/commands/securable.rb +48 -0
- data/lib/chamber/commands/secure.rb +16 -0
- data/lib/chamber/commands/show.rb +23 -0
- data/lib/chamber/commands/travis.rb +14 -0
- data/lib/chamber/commands/travis/secure.rb +35 -0
- data/lib/chamber/configuration.rb +34 -0
- data/lib/chamber/environmentable.rb +23 -0
- data/lib/chamber/errors/undecryptable_value_error.rb +6 -0
- data/lib/chamber/file.rb +17 -5
- data/lib/chamber/file_set.rb +18 -12
- data/lib/chamber/filters/boolean_conversion_filter.rb +41 -0
- data/lib/chamber/filters/decryption_filter.rb +69 -0
- data/lib/chamber/filters/encryption_filter.rb +57 -0
- data/lib/chamber/filters/environment_filter.rb +75 -0
- data/lib/chamber/filters/namespace_filter.rb +37 -0
- data/lib/chamber/filters/secure_filter.rb +39 -0
- data/lib/chamber/instance.rb +40 -0
- data/lib/chamber/namespace_set.rb +55 -16
- data/lib/chamber/rails/railtie.rb +1 -1
- data/lib/chamber/settings.rb +103 -42
- data/lib/chamber/system_environment.rb +3 -93
- data/lib/chamber/version.rb +2 -2
- data/spec/fixtures/settings.yml +27 -0
- data/spec/lib/chamber/commands/context_resolver_spec.rb +106 -0
- data/spec/lib/chamber/commands/files_spec.rb +19 -0
- data/spec/lib/chamber/commands/heroku/clear_spec.rb +11 -0
- data/spec/lib/chamber/commands/heroku/compare_spec.rb +11 -0
- data/spec/lib/chamber/commands/heroku/pull_spec.rb +11 -0
- data/spec/lib/chamber/commands/heroku/push_spec.rb +11 -0
- data/spec/lib/chamber/commands/secure_spec.rb +29 -0
- data/spec/lib/chamber/commands/show_spec.rb +43 -0
- data/spec/lib/chamber/file_set_spec.rb +1 -1
- data/spec/lib/chamber/file_spec.rb +32 -9
- data/spec/lib/chamber/filters/boolean_conversion_filter_spec.rb +44 -0
- data/spec/lib/chamber/filters/decryption_filter_spec.rb +55 -0
- data/spec/lib/chamber/filters/encryption_filter_spec.rb +48 -0
- data/spec/lib/chamber/filters/environment_filter_spec.rb +35 -0
- data/spec/lib/chamber/filters/namespace_filter_spec.rb +73 -0
- data/spec/lib/chamber/filters/secure_filter_spec.rb +36 -0
- data/spec/lib/chamber/namespace_set_spec.rb +61 -18
- data/spec/lib/chamber/settings_spec.rb +99 -23
- data/spec/lib/chamber/system_environment_spec.rb +1 -71
- data/spec/lib/chamber_spec.rb +40 -26
- data/spec/rails-2-test/config.ru +0 -0
- data/spec/rails-2-test/config/application.rb +5 -0
- data/spec/rails-2-test/script/console +0 -0
- data/spec/rails-3-test/config.ru +0 -0
- data/spec/rails-3-test/config/application.rb +5 -0
- data/spec/rails-3-test/script/rails +0 -0
- data/spec/rails-4-test/bin/rails +0 -0
- data/spec/rails-4-test/config.ru +0 -0
- data/spec/rails-4-test/config/application.rb +5 -0
- data/spec/spec_key +27 -0
- data/spec/spec_key.pub +9 -0
- metadata +85 -4
@@ -0,0 +1,69 @@
|
|
1
|
+
require 'openssl'
|
2
|
+
require 'base64'
|
3
|
+
require 'hashie/mash'
|
4
|
+
require 'chamber/errors/undecryptable_value_error'
|
5
|
+
|
6
|
+
module Chamber
|
7
|
+
module Filters
|
8
|
+
class DecryptionFilter
|
9
|
+
SECURE_KEY_TOKEN = %r{\A_secure_}
|
10
|
+
BASE64_STRING_PATTERN = %r{\A[A-Za-z0-9\+\/]{342}==\z}
|
11
|
+
|
12
|
+
def initialize(options = {})
|
13
|
+
self.decryption_key = options.fetch(:decryption_key, nil)
|
14
|
+
self.data = options.fetch(:data).dup
|
15
|
+
end
|
16
|
+
|
17
|
+
def self.execute(options = {})
|
18
|
+
self.new(options).send(:execute)
|
19
|
+
end
|
20
|
+
|
21
|
+
protected
|
22
|
+
|
23
|
+
attr_accessor :data
|
24
|
+
attr_reader :decryption_key
|
25
|
+
|
26
|
+
def execute(raw_data = data)
|
27
|
+
settings = Hashie::Mash.new
|
28
|
+
|
29
|
+
raw_data.each_pair do |key, value|
|
30
|
+
if value.respond_to? :each_pair
|
31
|
+
value = execute(value)
|
32
|
+
elsif value.respond_to? :match
|
33
|
+
if key.match(SECURE_KEY_TOKEN)
|
34
|
+
key = key.to_s.sub(SECURE_KEY_TOKEN, '')
|
35
|
+
value = if value.match(BASE64_STRING_PATTERN)
|
36
|
+
raise Errors::UndecryptableValueError.new("#{key} appears to need decrypting but the decryption key is not available.") if decryption_key.nil?
|
37
|
+
|
38
|
+
decoded_string = Base64.strict_decode64(value)
|
39
|
+
decryption_key.private_decrypt(decoded_string)
|
40
|
+
else
|
41
|
+
warn "WARNING: It appears that you would like to keep your information for #{key} secure, however the value for that setting does not appear to be encrypted. Make sure you run 'chamber settings secure' before committing."
|
42
|
+
|
43
|
+
value
|
44
|
+
end
|
45
|
+
else
|
46
|
+
key = key.to_s
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
settings[key] = value
|
51
|
+
end
|
52
|
+
|
53
|
+
settings
|
54
|
+
end
|
55
|
+
|
56
|
+
def decryption_key=(keyish)
|
57
|
+
return @decryption_key = nil if keyish.nil?
|
58
|
+
|
59
|
+
key_content = if ::File.readable?(::File.expand_path(keyish))
|
60
|
+
::File.read(::File.expand_path(keyish))
|
61
|
+
else
|
62
|
+
keyish
|
63
|
+
end
|
64
|
+
|
65
|
+
@decryption_key = OpenSSL::PKey::RSA.new(key_content)
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
@@ -0,0 +1,57 @@
|
|
1
|
+
require 'openssl'
|
2
|
+
require 'base64'
|
3
|
+
require 'hashie/mash'
|
4
|
+
|
5
|
+
module Chamber
|
6
|
+
module Filters
|
7
|
+
class EncryptionFilter
|
8
|
+
SECURE_KEY_TOKEN = %r{\A_secure_}
|
9
|
+
BASE64_STRING_PATTERN = %r{\A[A-Za-z0-9\+\/]{342}==\z}
|
10
|
+
|
11
|
+
def initialize(options = {})
|
12
|
+
self.encryption_key = options.fetch(:encryption_key, nil)
|
13
|
+
self.data = options.fetch(:data).dup
|
14
|
+
end
|
15
|
+
|
16
|
+
def self.execute(options = {})
|
17
|
+
self.new(options).send(:execute)
|
18
|
+
end
|
19
|
+
|
20
|
+
protected
|
21
|
+
|
22
|
+
attr_accessor :data
|
23
|
+
attr_reader :encryption_key
|
24
|
+
|
25
|
+
def execute(raw_data = data)
|
26
|
+
settings = Hashie::Mash.new
|
27
|
+
|
28
|
+
raw_data.each_pair do |key, value|
|
29
|
+
if value.respond_to? :each_pair
|
30
|
+
value = execute(value)
|
31
|
+
elsif value.respond_to? :match
|
32
|
+
if key.match(SECURE_KEY_TOKEN) && !value.match(BASE64_STRING_PATTERN)
|
33
|
+
encrypted_string = encryption_key.public_encrypt(value)
|
34
|
+
value = Base64.strict_encode64(encrypted_string)
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
settings[key] = value
|
39
|
+
end
|
40
|
+
|
41
|
+
settings
|
42
|
+
end
|
43
|
+
|
44
|
+
def encryption_key=(keyish)
|
45
|
+
return @encryption_key = nil if keyish.nil?
|
46
|
+
|
47
|
+
key_content = if ::File.readable?(::File.expand_path(keyish))
|
48
|
+
::File.read(::File.expand_path(keyish))
|
49
|
+
else
|
50
|
+
keyish
|
51
|
+
end
|
52
|
+
|
53
|
+
@encryption_key = OpenSSL::PKey::RSA.new(key_content)
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
@@ -0,0 +1,75 @@
|
|
1
|
+
require 'chamber/environmentable'
|
2
|
+
|
3
|
+
module Chamber
|
4
|
+
module Filters
|
5
|
+
class EnvironmentFilter
|
6
|
+
include Environmentable
|
7
|
+
|
8
|
+
def initialize(options = {})
|
9
|
+
self.data = options.fetch(:data)
|
10
|
+
end
|
11
|
+
|
12
|
+
###
|
13
|
+
# Internal: Allows the existing environment to be injected into the passed in
|
14
|
+
# hash. The hash that is passed in is *not* modified, instead a new hash is
|
15
|
+
# returned.
|
16
|
+
#
|
17
|
+
# Examples:
|
18
|
+
#
|
19
|
+
# ###
|
20
|
+
# # Injects the current environment variables
|
21
|
+
# #
|
22
|
+
# ENV['LEVEL_ONE_1_LEVEL_TWO_1'] = 'env value 1'
|
23
|
+
# ENV['LEVEL_ONE_1_LEVEL_TWO_2_LEVEL_THREE_1'] = 'env value 2'
|
24
|
+
#
|
25
|
+
# EnvironmentFilter.execute(
|
26
|
+
# level_one_1: {
|
27
|
+
# level_two_1: 'value 1',
|
28
|
+
# level_two_2: {
|
29
|
+
# level_three_1: 'value 2' } } )
|
30
|
+
#
|
31
|
+
# # => {
|
32
|
+
# 'level_one_1' => {
|
33
|
+
# 'level_two_1' => 'env value 1',
|
34
|
+
# 'level_two_2' => {
|
35
|
+
# 'level_three_1' => 'env value 2',
|
36
|
+
# }
|
37
|
+
#
|
38
|
+
# ###
|
39
|
+
# # Can inject environment variables if said variables are prefixed
|
40
|
+
# #
|
41
|
+
# ENV['PREFIX_LEVEL_TWO_1'] = 'env value 1'
|
42
|
+
# ENV['PREFIX_LEVEL_TWO_2'] = 'env value 2'
|
43
|
+
#
|
44
|
+
# EnvironmentFilter.execute({
|
45
|
+
# level_two_1: 'value 1',
|
46
|
+
# level_two_2: 'value 2'
|
47
|
+
# },
|
48
|
+
# ['prefix'])
|
49
|
+
#
|
50
|
+
# # => {
|
51
|
+
# 'level_two_1' => 'env value 1',
|
52
|
+
# 'level_two_2' => 'env value 2',
|
53
|
+
# }
|
54
|
+
#
|
55
|
+
#
|
56
|
+
def self.execute(options = {})
|
57
|
+
self.new(options).send(:execute)
|
58
|
+
end
|
59
|
+
|
60
|
+
protected
|
61
|
+
|
62
|
+
attr_accessor :data
|
63
|
+
|
64
|
+
def execute(settings = data, parent_keys = [])
|
65
|
+
with_environment(settings, parent_keys,
|
66
|
+
->(key, value, environment_keys) do
|
67
|
+
{ key => execute(value, environment_keys) }
|
68
|
+
end,
|
69
|
+
->(key, value, environment_key) do
|
70
|
+
{ key => (ENV[environment_key] || value) }
|
71
|
+
end)
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
require 'hashie/mash'
|
2
|
+
|
3
|
+
module Chamber
|
4
|
+
module Filters
|
5
|
+
class NamespaceFilter
|
6
|
+
def initialize(options = {})
|
7
|
+
self.data = Hashie::Mash.new(options.fetch(:data))
|
8
|
+
self.namespaces = options.fetch(:namespaces)
|
9
|
+
end
|
10
|
+
|
11
|
+
def self.execute(options = {})
|
12
|
+
self.new(options).send(:execute)
|
13
|
+
end
|
14
|
+
|
15
|
+
protected
|
16
|
+
|
17
|
+
attr_accessor :data,
|
18
|
+
:namespaces
|
19
|
+
|
20
|
+
def execute
|
21
|
+
if data_is_namespaced?
|
22
|
+
namespaces.each_with_object(Hashie::Mash.new) do |namespace, filtered_data|
|
23
|
+
filtered_data.merge!(data[namespace]) if data[namespace]
|
24
|
+
end
|
25
|
+
else
|
26
|
+
Hashie::Mash.new(data)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
private
|
31
|
+
|
32
|
+
def data_is_namespaced?
|
33
|
+
@data_is_namespaced ||= data.keys.any? { |key| namespaces.include? key.to_s }
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
require 'hashie/mash'
|
2
|
+
|
3
|
+
module Chamber
|
4
|
+
module Filters
|
5
|
+
class SecureFilter
|
6
|
+
SECURE_KEY_TOKEN = %r{\A_secure_}
|
7
|
+
|
8
|
+
def initialize(options = {})
|
9
|
+
self.data = Hashie::Mash.new(options.fetch(:data))
|
10
|
+
end
|
11
|
+
|
12
|
+
def self.execute(options = {})
|
13
|
+
self.new(options).send(:execute)
|
14
|
+
end
|
15
|
+
|
16
|
+
protected
|
17
|
+
|
18
|
+
attr_accessor :data
|
19
|
+
|
20
|
+
def execute(raw_data = data)
|
21
|
+
settings = Hashie::Mash.new
|
22
|
+
|
23
|
+
raw_data.each_pair do |key, value|
|
24
|
+
secure_value = if value.respond_to? :each_pair
|
25
|
+
execute(value)
|
26
|
+
elsif key.respond_to? :match
|
27
|
+
if key.match(SECURE_KEY_TOKEN)
|
28
|
+
value
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
settings[key] = secure_value unless secure_value.nil?
|
33
|
+
end
|
34
|
+
|
35
|
+
settings
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
require 'chamber/configuration'
|
2
|
+
require 'chamber/file_set'
|
3
|
+
|
4
|
+
module Chamber
|
5
|
+
class Instance
|
6
|
+
attr_accessor :configuration,
|
7
|
+
:files
|
8
|
+
|
9
|
+
def initialize(options = {})
|
10
|
+
self.configuration = Configuration.new options
|
11
|
+
self.files = FileSet.new configuration.to_hash
|
12
|
+
end
|
13
|
+
|
14
|
+
def settings
|
15
|
+
@settings ||= files.to_settings { |settings| @settings = settings }
|
16
|
+
end
|
17
|
+
|
18
|
+
def filenames
|
19
|
+
files.filenames
|
20
|
+
end
|
21
|
+
|
22
|
+
def secure
|
23
|
+
files.secure
|
24
|
+
end
|
25
|
+
|
26
|
+
def to_s(options = {})
|
27
|
+
settings.to_s(options)
|
28
|
+
end
|
29
|
+
|
30
|
+
def method_missing(name, *args)
|
31
|
+
return settings.public_send(name, *args) if settings.respond_to?(name)
|
32
|
+
|
33
|
+
super
|
34
|
+
end
|
35
|
+
|
36
|
+
def respond_to_missing?(name, include_private = false)
|
37
|
+
settings.respond_to?(name, include_private)
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
@@ -8,7 +8,7 @@ require 'set'
|
|
8
8
|
# a NamespaceSet from either an array-like or hash-like object and the ability
|
9
9
|
# to allow callables to be passed which will then be executed.
|
10
10
|
#
|
11
|
-
|
11
|
+
module Chamber
|
12
12
|
class NamespaceSet
|
13
13
|
include Enumerable
|
14
14
|
|
@@ -16,7 +16,21 @@ class NamespaceSet
|
|
16
16
|
# Internal: Creates a new NamespaceSet from arrays, hashes and sets.
|
17
17
|
#
|
18
18
|
def initialize(raw_namespaces = {})
|
19
|
-
self.
|
19
|
+
self.raw_namespaces = raw_namespaces
|
20
|
+
end
|
21
|
+
|
22
|
+
###
|
23
|
+
# Internal: Allows for more compact NamespaceSet creation by giving a list of
|
24
|
+
# namespace values.
|
25
|
+
#
|
26
|
+
# Examples:
|
27
|
+
#
|
28
|
+
# NamespaceSet['development', -> { ENV['HOST'] }]
|
29
|
+
#
|
30
|
+
# Returns a new NamespaceSet
|
31
|
+
#
|
32
|
+
def self.[](*namespace_values)
|
33
|
+
self.new(namespace_values)
|
20
34
|
end
|
21
35
|
|
22
36
|
###
|
@@ -78,7 +92,7 @@ class NamespaceSet
|
|
78
92
|
# Returns a Boolean
|
79
93
|
#
|
80
94
|
def ==(other)
|
81
|
-
self.
|
95
|
+
self.to_a.eql? other.to_a
|
82
96
|
end
|
83
97
|
|
84
98
|
###
|
@@ -88,15 +102,13 @@ class NamespaceSet
|
|
88
102
|
# Returns a Boolean
|
89
103
|
#
|
90
104
|
def eql?(other)
|
91
|
-
other.is_a?(
|
105
|
+
other.is_a?( NamespaceSet) &&
|
92
106
|
self.namespaces == other.namespaces
|
93
107
|
end
|
94
108
|
|
95
109
|
protected
|
96
110
|
|
97
|
-
|
98
|
-
@namespaces ||= Set.new
|
99
|
-
end
|
111
|
+
attr_accessor :raw_namespaces
|
100
112
|
|
101
113
|
###
|
102
114
|
# Internal: Sets the namespaces for the set from a variety of objects and
|
@@ -108,6 +120,7 @@ class NamespaceSet
|
|
108
120
|
#
|
109
121
|
# # Can be set to an array
|
110
122
|
# namespace_set.namespaces = %w{namespace_value_1 namespace_value_2}
|
123
|
+
# namespace_set.namespaces
|
111
124
|
# # => ['namespace_value_1', 'namespace_value_2']
|
112
125
|
#
|
113
126
|
# # Can be set to a hash
|
@@ -116,6 +129,16 @@ class NamespaceSet
|
|
116
129
|
# namespace_set.namespaces
|
117
130
|
# # => ['development', 'my host']
|
118
131
|
#
|
132
|
+
# # Can be set to a NamespaceSet
|
133
|
+
# namespace_set.namespaces = NamespaceSet.new('development')
|
134
|
+
# namespace_set.namespaces
|
135
|
+
# # => ['development']
|
136
|
+
#
|
137
|
+
# # Can be set to a single value
|
138
|
+
# namespace_set.namespaces = 'development'
|
139
|
+
# namespace_set.namespaces
|
140
|
+
# # => ['development']
|
141
|
+
#
|
119
142
|
# # Can be set to a callable
|
120
143
|
# namespace_set.namespaces = { environment: -> { 'called' } }
|
121
144
|
# namespace_set.namespaces
|
@@ -126,16 +149,32 @@ class NamespaceSet
|
|
126
149
|
# namespace_set.namespaces
|
127
150
|
# # => ['namespace_value']
|
128
151
|
#
|
129
|
-
def namespaces
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
152
|
+
def namespaces
|
153
|
+
@namespaces ||= Set.new namespace_values.map do |value|
|
154
|
+
(value.respond_to?(:call) ? value.call : value).to_s
|
155
|
+
end
|
156
|
+
end
|
157
|
+
|
158
|
+
def raw_namespaces=(raw_namespaces)
|
159
|
+
@raw_namespaces = if raw_namespaces.is_a? NamespaceSet
|
160
|
+
raw_namespaces.to_ary
|
161
|
+
else
|
162
|
+
raw_namespaces
|
163
|
+
end
|
164
|
+
end
|
135
165
|
|
136
|
-
|
137
|
-
|
138
|
-
|
166
|
+
private
|
167
|
+
|
168
|
+
def namespace_values
|
169
|
+
if raw_namespaces.respond_to? :map
|
170
|
+
if raw_namespaces.respond_to? :values
|
171
|
+
raw_namespaces.values
|
172
|
+
else
|
173
|
+
raw_namespaces
|
174
|
+
end
|
175
|
+
else
|
176
|
+
[raw_namespaces]
|
177
|
+
end
|
139
178
|
end
|
140
179
|
end
|
141
180
|
end
|