chamber 1.0.3 → 2.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (73) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +288 -173
  3. data/bin/chamber +2 -288
  4. data/lib/chamber.rb +19 -67
  5. data/lib/chamber/binary/heroku.rb +59 -0
  6. data/lib/chamber/binary/runner.rb +94 -0
  7. data/lib/chamber/binary/travis.rb +23 -0
  8. data/lib/chamber/commands/base.rb +33 -0
  9. data/lib/chamber/commands/comparable.rb +37 -0
  10. data/lib/chamber/commands/compare.rb +46 -0
  11. data/lib/chamber/commands/context_resolver.rb +72 -0
  12. data/lib/chamber/commands/files.rb +12 -0
  13. data/lib/chamber/commands/heroku.rb +30 -0
  14. data/lib/chamber/commands/heroku/clear.rb +25 -0
  15. data/lib/chamber/commands/heroku/compare.rb +31 -0
  16. data/lib/chamber/commands/heroku/pull.rb +30 -0
  17. data/lib/chamber/commands/heroku/push.rb +25 -0
  18. data/lib/chamber/commands/initialize.rb +73 -0
  19. data/lib/chamber/commands/securable.rb +48 -0
  20. data/lib/chamber/commands/secure.rb +16 -0
  21. data/lib/chamber/commands/show.rb +23 -0
  22. data/lib/chamber/commands/travis.rb +14 -0
  23. data/lib/chamber/commands/travis/secure.rb +35 -0
  24. data/lib/chamber/configuration.rb +34 -0
  25. data/lib/chamber/environmentable.rb +23 -0
  26. data/lib/chamber/errors/undecryptable_value_error.rb +6 -0
  27. data/lib/chamber/file.rb +17 -5
  28. data/lib/chamber/file_set.rb +18 -12
  29. data/lib/chamber/filters/boolean_conversion_filter.rb +41 -0
  30. data/lib/chamber/filters/decryption_filter.rb +69 -0
  31. data/lib/chamber/filters/encryption_filter.rb +57 -0
  32. data/lib/chamber/filters/environment_filter.rb +75 -0
  33. data/lib/chamber/filters/namespace_filter.rb +37 -0
  34. data/lib/chamber/filters/secure_filter.rb +39 -0
  35. data/lib/chamber/instance.rb +40 -0
  36. data/lib/chamber/namespace_set.rb +55 -16
  37. data/lib/chamber/rails/railtie.rb +1 -1
  38. data/lib/chamber/settings.rb +103 -42
  39. data/lib/chamber/system_environment.rb +3 -93
  40. data/lib/chamber/version.rb +2 -2
  41. data/spec/fixtures/settings.yml +27 -0
  42. data/spec/lib/chamber/commands/context_resolver_spec.rb +106 -0
  43. data/spec/lib/chamber/commands/files_spec.rb +19 -0
  44. data/spec/lib/chamber/commands/heroku/clear_spec.rb +11 -0
  45. data/spec/lib/chamber/commands/heroku/compare_spec.rb +11 -0
  46. data/spec/lib/chamber/commands/heroku/pull_spec.rb +11 -0
  47. data/spec/lib/chamber/commands/heroku/push_spec.rb +11 -0
  48. data/spec/lib/chamber/commands/secure_spec.rb +29 -0
  49. data/spec/lib/chamber/commands/show_spec.rb +43 -0
  50. data/spec/lib/chamber/file_set_spec.rb +1 -1
  51. data/spec/lib/chamber/file_spec.rb +32 -9
  52. data/spec/lib/chamber/filters/boolean_conversion_filter_spec.rb +44 -0
  53. data/spec/lib/chamber/filters/decryption_filter_spec.rb +55 -0
  54. data/spec/lib/chamber/filters/encryption_filter_spec.rb +48 -0
  55. data/spec/lib/chamber/filters/environment_filter_spec.rb +35 -0
  56. data/spec/lib/chamber/filters/namespace_filter_spec.rb +73 -0
  57. data/spec/lib/chamber/filters/secure_filter_spec.rb +36 -0
  58. data/spec/lib/chamber/namespace_set_spec.rb +61 -18
  59. data/spec/lib/chamber/settings_spec.rb +99 -23
  60. data/spec/lib/chamber/system_environment_spec.rb +1 -71
  61. data/spec/lib/chamber_spec.rb +40 -26
  62. data/spec/rails-2-test/config.ru +0 -0
  63. data/spec/rails-2-test/config/application.rb +5 -0
  64. data/spec/rails-2-test/script/console +0 -0
  65. data/spec/rails-3-test/config.ru +0 -0
  66. data/spec/rails-3-test/config/application.rb +5 -0
  67. data/spec/rails-3-test/script/rails +0 -0
  68. data/spec/rails-4-test/bin/rails +0 -0
  69. data/spec/rails-4-test/config.ru +0 -0
  70. data/spec/rails-4-test/config/application.rb +5 -0
  71. data/spec/spec_key +27 -0
  72. data/spec/spec_key.pub +9 -0
  73. metadata +85 -4
@@ -1,4 +1,4 @@
1
- class Chamber
1
+ module Chamber
2
2
  module Rails
3
3
  class Railtie < ::Rails::Railtie
4
4
  initializer 'chamber.load', before: :load_environment_config do
@@ -1,18 +1,34 @@
1
1
  require 'hashie/mash'
2
2
  require 'chamber/system_environment'
3
3
  require 'chamber/namespace_set'
4
+ require 'chamber/filters/namespace_filter'
5
+ require 'chamber/filters/encryption_filter'
6
+ require 'chamber/filters/decryption_filter'
7
+ require 'chamber/filters/environment_filter'
8
+ require 'chamber/filters/boolean_conversion_filter'
9
+ require 'chamber/filters/secure_filter'
4
10
 
5
11
  ###
6
12
  # Internal: Represents the base settings storage needed for Chamber.
7
13
  #
8
- class Chamber
14
+ module Chamber
9
15
  class Settings
10
16
 
11
17
  attr_reader :namespaces
12
18
 
13
19
  def initialize(options = {})
14
- self.namespaces = options.fetch(:namespaces, NamespaceSet.new)
15
- self.data = options.fetch(:settings, Hashie::Mash.new)
20
+ self.namespaces = options[:namespaces] || []
21
+ self.raw_data = options[:settings] || {}
22
+ self.decryption_key = options[:decryption_key]
23
+ self.encryption_key = options[:encryption_key]
24
+ self.pre_filters = options[:pre_filters] || [
25
+ Filters::NamespaceFilter,
26
+ ]
27
+ self.post_filters = options[:post_filters] || [
28
+ Filters::DecryptionFilter,
29
+ Filters::EnvironmentFilter,
30
+ Filters::BooleanConversionFilter,
31
+ ]
16
32
  end
17
33
 
18
34
  ###
@@ -68,14 +84,14 @@ class Settings
68
84
  # Internal: Merges a Settings object with another Settings object or
69
85
  # a hash-like object.
70
86
  #
71
- # Also, if merging Settings, it will merge the namespaces as well.
87
+ # Also, if merging Settings, it will merge all other Settings data as well.
72
88
  #
73
89
  # Example:
74
90
  #
75
91
  # settings = Settings.new settings: { my_setting: 'my value' }
76
92
  # other_settings = Settings.new settings: { my_other_setting: 'my other value' }
77
93
  #
78
- # settings.merge! other_settings
94
+ # settings.merge other_settings
79
95
  #
80
96
  # settings
81
97
  # # => {
@@ -83,70 +99,115 @@ class Settings
83
99
  # 'my_other_setting' => 'my other value',
84
100
  # }
85
101
  #
102
+ # Returns a new Settings object
103
+ #
104
+ def merge(other)
105
+ other_settings = if other.is_a? Settings
106
+ other
107
+ elsif other.is_a? Hash
108
+ Settings.new(settings: other)
109
+ end
110
+
111
+ Settings.new(
112
+ encryption_key: encryption_key || other_settings.encryption_key,
113
+ decryption_key: decryption_key || other_settings.decryption_key,
114
+ namespaces: (namespaces + other_settings.namespaces),
115
+ settings: raw_data.merge(other_settings.raw_data))
116
+ end
117
+
118
+ ###
119
+ # Internal: Returns the Settings data as a Hash for easy manipulation.
120
+ # Changes made to the hash will *not* be reflected in the original Settings
121
+ # object.
122
+ #
86
123
  # Returns a Hash
87
124
  #
88
- def merge!(other)
89
- self.data = data.merge(other.to_hash)
90
- self.namespaces = (namespaces + other.namespaces) if other.respond_to? :namespaces
125
+ def to_hash
126
+ data.to_hash
127
+ end
128
+
129
+ ###
130
+ # Internal: Determines whether a Settings is equal to another hash-like
131
+ # object.
132
+ #
133
+ # Returns a Boolean
134
+ #
135
+ def ==(other)
136
+ self.to_hash == other.to_hash
91
137
  end
92
138
 
139
+ ###
140
+ # Internal: Determines whether a Settings is equal to another Settings.
141
+ #
142
+ # Returns a Boolean
143
+ #
93
144
  def eql?(other)
94
145
  other.is_a?( Chamber::Settings) &&
95
146
  self.data == other.data &&
96
147
  self.namespaces == other.namespaces
97
148
  end
98
149
 
99
- def to_hash
100
- data
150
+ def secured
151
+ Settings.new( metadata.merge(
152
+ settings: raw_data,
153
+ pre_filters: [Filters::SecureFilter]))
101
154
  end
102
155
 
103
- def method_missing(name, *args)
104
- return data.public_send(name, *args) if data.respond_to?(name)
105
-
106
- super
107
- end
108
-
109
- def respond_to_missing?(name, include_private = false)
110
- data.respond_to?(name, include_private)
156
+ def secure
157
+ Settings.new( metadata.merge(
158
+ settings: @raw_data,
159
+ pre_filters: [Filters::EncryptionFilter],
160
+ post_filters: [] ))
111
161
  end
112
162
 
113
163
  protected
114
164
 
115
- attr_reader :raw_data
116
- attr_writer :namespaces
165
+ attr_accessor :pre_filters,
166
+ :post_filters,
167
+ :encryption_key,
168
+ :decryption_key,
169
+ :raw_data
117
170
 
118
- def data
119
- @data ||= Hashie::Mash.new
171
+ def raw_data=(new_raw_data)
172
+ @raw_data = Hashie::Mash.new(new_raw_data)
120
173
  end
121
174
 
122
- def data=(raw_data)
123
- @raw_data = Hashie::Mash.new(raw_data)
124
-
125
- namespace_checked_data = if data_is_namespaced?
126
- namespace_filtered_data
127
- else
128
- self.raw_data
129
- end
175
+ def namespaces=(raw_namespaces)
176
+ @namespaces = NamespaceSet.new(raw_namespaces)
177
+ end
130
178
 
131
- @data = SystemEnvironment.inject_into(namespace_checked_data)
179
+ def raw_data
180
+ @filtered_raw_data ||= pre_filters.reduce(@raw_data) do |filtered_data, filter|
181
+ filter.execute({data: filtered_data}.
182
+ merge(metadata))
183
+ end
132
184
  end
133
185
 
134
- private
186
+ def data
187
+ @data ||= post_filters.reduce(raw_data) do |filtered_data, filter|
188
+ filter.execute({data: filtered_data}.
189
+ merge(metadata))
190
+ end
191
+ end
135
192
 
136
- def data_is_namespaced?
137
- @data_is_namespaced ||= raw_data.keys.any? { |key| namespaces.include? key }
193
+ def metadata
194
+ {
195
+ namespaces: self.namespaces,
196
+ decryption_key: self.decryption_key,
197
+ encryption_key: self.encryption_key,
198
+ }
138
199
  end
139
200
 
140
- def namespace_filtered_data
141
- @namespace_filtered_data ||= -> do
142
- data = Hashie::Mash.new
201
+ public
202
+
203
+ def method_missing(name, *args)
204
+ return data.public_send(name, *args) if data.respond_to?(name)
143
205
 
144
- namespaces.each do |namespace|
145
- data.merge!(raw_data[namespace]) if raw_data[namespace]
146
- end
206
+ super
207
+ end
147
208
 
148
- data
149
- end.call
209
+ def respond_to_missing?(name, include_private = false)
210
+ data.respond_to?(name, include_private)
150
211
  end
151
212
  end
152
213
  end
@@ -1,65 +1,12 @@
1
- require 'hashie/mash'
1
+ require 'chamber/environmentable'
2
2
 
3
3
  ###
4
4
  # Internal: Gives access to the existing environment for importing/exporting
5
5
  # values.
6
6
  #
7
- class Chamber
7
+ module Chamber
8
8
  module SystemEnvironment
9
-
10
- ###
11
- # Internal: Allows the existing environment to be injected into the passed in
12
- # hash. The hash that is passed in is *not* modified, instead a new hash is
13
- # returned.
14
- #
15
- # Examples:
16
- #
17
- # ###
18
- # # Injects the current environment variables
19
- # #
20
- # ENV['LEVEL_ONE_1_LEVEL_TWO_1'] = 'env value 1'
21
- # ENV['LEVEL_ONE_1_LEVEL_TWO_2_LEVEL_THREE_1'] = 'env value 2'
22
- #
23
- # SystemEnvironment.inject_into(
24
- # level_one_1: {
25
- # level_two_1: 'value 1',
26
- # level_two_2: {
27
- # level_three_1: 'value 2' } } )
28
- #
29
- # # => {
30
- # 'level_one_1' => {
31
- # 'level_two_1' => 'env value 1',
32
- # 'level_two_2' => {
33
- # 'level_three_1' => 'env value 2',
34
- # }
35
- #
36
- # ###
37
- # # Can inject environment variables if said variables are prefixed
38
- # #
39
- # ENV['PREFIX_LEVEL_TWO_1'] = 'env value 1'
40
- # ENV['PREFIX_LEVEL_TWO_2'] = 'env value 2'
41
- #
42
- # SystemEnvironment.inject_into({
43
- # level_two_1: 'value 1',
44
- # level_two_2: 'value 2'
45
- # },
46
- # ['prefix'])
47
- #
48
- # # => {
49
- # 'level_two_1' => 'env value 1',
50
- # 'level_two_2' => 'env value 2',
51
- # }
52
- #
53
- #
54
- def self.inject_into(settings = {}, parent_keys = [])
55
- with_environment(settings, parent_keys,
56
- ->(key, value, environment_keys) do
57
- { key => inject_into(value, environment_keys) }
58
- end,
59
- ->(key, value, environment_key) do
60
- { key => convert_value(ENV[environment_key] || value) }
61
- end)
62
- end
9
+ extend Environmentable
63
10
 
64
11
  ###
65
12
  # Internal: Allows the environment variable-compatible variables to be
@@ -104,42 +51,5 @@ module SystemEnvironment
104
51
  { environment_key => value.to_s }
105
52
  end)
106
53
  end
107
-
108
- private
109
-
110
- def self.with_environment(settings, parent_keys, hash_block, value_block)
111
- environment_hash = Hashie::Mash.new
112
-
113
- settings.each_pair do |key, value|
114
- environment_keys = parent_keys.dup.push(key)
115
-
116
- if value.respond_to? :each_pair
117
- environment_hash.merge!(hash_block.call(key, value, environment_keys))
118
- else
119
- environment_key = environment_keys.join('_').upcase
120
-
121
- environment_hash.merge!(value_block.call(key, value, environment_key))
122
- end
123
- end
124
-
125
- environment_hash
126
- end
127
-
128
- def self.convert_value(value)
129
- return nil if value.nil?
130
-
131
- if value.is_a? String
132
- case value
133
- when 'false', 'f', 'no'
134
- false
135
- when 'true', 't', 'yes'
136
- true
137
- else
138
- value
139
- end
140
- else
141
- value
142
- end
143
- end
144
54
  end
145
55
  end
@@ -1,3 +1,3 @@
1
- class Chamber
2
- VERSION = '1.0.3'
1
+ module Chamber
2
+ VERSION = '2.0.0'
3
3
  end
@@ -0,0 +1,27 @@
1
+ ---
2
+ development:
3
+ my_setting: my_dev_value
4
+ my_boolean: 'false'
5
+ my_dynamic_setting: 3
6
+ another_level:
7
+ setting_one: 1
8
+ setting_two: 4
9
+ level_three:
10
+ an_array:
11
+ - item 8
12
+ - item 2
13
+ - item 0
14
+ a_scalar: hello
15
+ test:
16
+ my_setting: my_value
17
+ my_boolean: 'false'
18
+ my_dynamic_setting: 2
19
+ another_level:
20
+ setting_one: 1
21
+ setting_two: 2
22
+ level_three:
23
+ an_array:
24
+ - item 1
25
+ - item 2
26
+ - item 3
27
+ a_scalar: hello
@@ -0,0 +1,106 @@
1
+ require 'rspectacular'
2
+ require 'chamber/commands/context_resolver'
3
+
4
+ module Chamber
5
+ module Commands
6
+ describe ContextResolver do
7
+ let(:rails_2_path) { ::File.expand_path('../../../../rails-2-test', __FILE__) }
8
+ let(:rails_3_path) { ::File.expand_path('../../../../rails-3-test', __FILE__) }
9
+ let(:rails_4_path) { ::File.expand_path('../../../../rails-4-test', __FILE__) }
10
+
11
+ it 'does not attempt to do any resolution if all valid options are passed in' do
12
+ options = ContextResolver.resolve(basepath: 'my_path',
13
+ namespaces: 'ns')
14
+
15
+ expect(options[:basepath]).to eql 'my_path'
16
+ expect(options[:files]).to be_nil
17
+ expect(options[:namespaces]).to eql 'ns'
18
+ end
19
+
20
+ it 'does not attempt to do any resolution if files are passed in in place of a basepath' do
21
+ options = ContextResolver.resolve(files: 'my_files',
22
+ namespaces: 'ns')
23
+
24
+ expect(options[:files]).to eql 'my_files'
25
+ expect(options[:namespaces]).to eql 'ns'
26
+ end
27
+
28
+ it 'defaults the basepath to the rootpath if none is explicitly set' do
29
+ options = ContextResolver.resolve(rootpath: './app',
30
+ namespaces: 'ns')
31
+
32
+ expect(options[:basepath].to_s).to eql './app'
33
+ end
34
+
35
+ it 'sets the rootpath to the current working directory if none is passed in' do
36
+ allow(Pathname).to receive(:pwd).
37
+ and_return('my_dir')
38
+
39
+ options = ContextResolver.resolve
40
+
41
+ expect(options[:rootpath].to_s).to eql 'my_dir'
42
+ end
43
+
44
+ it 'sets the encryption key to the default if not passed in' do
45
+ options = ContextResolver.resolve(rootpath: rails_3_path)
46
+
47
+ expect(options[:encryption_key].to_s).to include 'rails-3-test/.chamber.pub.pem'
48
+ end
49
+
50
+ it 'sets the decryption key to the default if not passed in' do
51
+ options = ContextResolver.resolve(rootpath: rails_3_path)
52
+
53
+ expect(options[:decryption_key].to_s).to include 'rails-3-test/.chamber.pem'
54
+ end
55
+
56
+ it 'does not set the encryption key if the keyfile does not exist' do
57
+ options = ContextResolver.resolve(rootpath: './app')
58
+
59
+ expect(options[:encryption_key]).to be_nil
60
+ end
61
+
62
+ it 'does not set the decryption key if the keyfile does not exist' do
63
+ options = ContextResolver.resolve(rootpath: './app')
64
+
65
+ expect(options[:decryption_key]).to be_nil
66
+ end
67
+
68
+ it 'sets the information to a Rails preset even if it is not pointing to a Rails app' do
69
+ options = ContextResolver.resolve(rootpath: './app',
70
+ preset: 'rails')
71
+
72
+ expect(options[:basepath].to_s).to include './app/config'
73
+ expect(options[:namespaces]).to eql []
74
+ end
75
+
76
+ it 'sets the information to a Rails preset when the rootpath is a Rails app' do
77
+ options = ContextResolver.resolve(rootpath: rails_3_path,
78
+ preset: 'rails')
79
+
80
+ expect(options[:basepath].to_s).to include 'rails-3-test/config'
81
+ expect(options[:namespaces]).to eql ['development']
82
+ end
83
+
84
+ it 'sets the basepath if inside a Rails 2 project' do
85
+ options = ContextResolver.resolve(rootpath: rails_2_path)
86
+
87
+ expect(options[:basepath].to_s).to include 'rails-2-test/config'
88
+ expect(options[:namespaces]).to eql ['development']
89
+ end
90
+
91
+ it 'sets the basepath if inside a Rails 3 project' do
92
+ options = ContextResolver.resolve(rootpath: rails_3_path)
93
+
94
+ expect(options[:basepath].to_s).to include 'rails-3-test/config'
95
+ expect(options[:namespaces]).to eql ['development']
96
+ end
97
+
98
+ it 'sets the basepath if inside a Rails 4 project' do
99
+ options = ContextResolver.resolve(rootpath: rails_4_path)
100
+
101
+ expect(options[:basepath].to_s).to include 'rails-4-test/config'
102
+ expect(options[:namespaces]).to eql ['development']
103
+ end
104
+ end
105
+ end
106
+ end