chamber 1.0.3 → 2.0.0

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.
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