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
@@ -0,0 +1,25 @@
1
+ require 'chamber/commands/base'
2
+ require 'chamber/commands/securable'
3
+ require 'chamber/commands/heroku'
4
+
5
+ module Chamber
6
+ module Commands
7
+ module Heroku
8
+ class Push < Chamber::Commands::Base
9
+ include Chamber::Commands::Securable
10
+ include Chamber::Commands::Heroku
11
+
12
+ def call
13
+ securable_environment_variables.each do |key, value|
14
+ if dry_run
15
+ shell.say_status 'push', key, :blue
16
+ else
17
+ shell.say_status 'push', key, :green
18
+ shell.say heroku(%Q{config:set #{key}=#{value}})
19
+ end
20
+ end
21
+ end
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,73 @@
1
+ require 'openssl'
2
+ require 'chamber/commands/base'
3
+
4
+ module Chamber
5
+ module Commands
6
+ class Initialize < Chamber::Commands::Base
7
+
8
+ def initialize(options = {})
9
+ super
10
+
11
+ self.basepath = options[:basepath]
12
+ end
13
+
14
+ def call
15
+ shell.create_file private_key_filepath, rsa_private_key.to_pem
16
+ shell.create_file public_key_filepath, rsa_public_key.to_pem
17
+
18
+ `chmod 600 #{private_key_filepath}`
19
+ `chmod 644 #{public_key_filepath}`
20
+
21
+ unless ::File.read(gitignore_filepath).match(/^.chamber.pem$/)
22
+ shell.append_to_file gitignore_filepath, private_key_filepath.basename
23
+ end
24
+
25
+ shell.copy_file settings_template_filepath, settings_filepath
26
+ end
27
+
28
+ def self.call(options = {})
29
+ self.new(options).call
30
+ end
31
+
32
+ protected
33
+
34
+ attr_accessor :basepath
35
+
36
+ def settings_template_filepath
37
+ @settings_template_filepath ||= templates_path + 'settings.yml'
38
+ end
39
+
40
+ def templates_path
41
+ @templates_path ||= Pathname.new(::File.expand_path('../../../../templates', __FILE__))
42
+ end
43
+
44
+ def settings_filepath
45
+ @settings_filepath ||= basepath + 'settings.yml'
46
+ end
47
+
48
+ def gitignore_filepath
49
+ @gitignore_filepath ||= rootpath + '.gitignore'
50
+ end
51
+
52
+ def private_key_filepath
53
+ @private_key_filepath ||= rootpath + '.chamber.pem'
54
+ end
55
+
56
+ def public_key_filepath
57
+ @public_key_filepath ||= rootpath + '.chamber.pub.pem'
58
+ end
59
+
60
+ def rsa_key
61
+ @rsa_key ||= OpenSSL::PKey::RSA.new(2048)
62
+ end
63
+
64
+ def rsa_private_key
65
+ rsa_key
66
+ end
67
+
68
+ def rsa_public_key
69
+ rsa_key.public_key
70
+ end
71
+ end
72
+ end
73
+ end
@@ -0,0 +1,48 @@
1
+ require 'shellwords'
2
+ require 'chamber/instance'
3
+
4
+ module Chamber
5
+ module Commands
6
+ module Securable
7
+
8
+ def initialize(options = {})
9
+ super
10
+
11
+ ignored_settings_options = options.
12
+ merge(files: ignored_settings_filepaths).
13
+ reject { |k, v| k == 'basepath' }
14
+ self.ignored_settings_instance = Chamber::Instance.new(ignored_settings_options)
15
+ self.all_settings_instance = Chamber::Instance.new(options)
16
+ self.only_sensitive = options[:only_sensitive]
17
+ end
18
+
19
+ protected
20
+
21
+ attr_accessor :only_sensitive,
22
+ :ignored_settings_instance,
23
+ :all_settings_instance
24
+
25
+ def securable_environment_variables
26
+ if only_sensitive
27
+ secured_settings.to_environment
28
+ else
29
+ all_settings.to_environment
30
+ end
31
+ end
32
+
33
+ def secured_settings
34
+ ignored_settings_instance.settings.merge(all_settings.secured)
35
+ end
36
+
37
+ def all_settings
38
+ all_settings_instance.settings
39
+ end
40
+
41
+ def ignored_settings_filepaths
42
+ shell_escaped_chamber_filenames = chamber.filenames.map { |filename| Shellwords.escape(filename) }
43
+
44
+ `git ls-files --other --ignored --exclude-from=.gitignore | sed -e "s|^|#{Shellwords.escape(rootpath)}/|" | grep --colour=never -E '#{shell_escaped_chamber_filenames.join('|')}'`.split("\n")
45
+ end
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,16 @@
1
+ require 'chamber/commands/base'
2
+
3
+ module Chamber
4
+ module Commands
5
+ class Secure < Chamber::Commands::Base
6
+
7
+ def initialize(options = {})
8
+ super(options.merge(namespaces: ['*']))
9
+ end
10
+
11
+ def call
12
+ chamber.secure
13
+ end
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,23 @@
1
+ require 'pp'
2
+ require 'chamber/commands/base'
3
+
4
+ module Chamber
5
+ module Commands
6
+ class Show < Chamber::Commands::Base
7
+
8
+ def initialize(options = {})
9
+ super
10
+
11
+ self.as_env = options[:as_env]
12
+ end
13
+
14
+ def call
15
+ as_env ? chamber.to_s(pair_separator: "\n") : PP.pp(chamber.to_hash, StringIO.new, 60).string.chomp
16
+ end
17
+
18
+ protected
19
+
20
+ attr_accessor :as_env
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,14 @@
1
+ require 'bundler'
2
+
3
+ module Chamber
4
+ module Commands
5
+ module Travis
6
+
7
+ protected
8
+
9
+ def travis_encrypt(command)
10
+ Bundler.with_clean_env { `travis encrypt --add 'env.global' #{command}` }
11
+ end
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,35 @@
1
+ require 'chamber/commands/base'
2
+ require 'chamber/commands/travis'
3
+ require 'chamber/commands/securable'
4
+
5
+ module Chamber
6
+ module Commands
7
+ module Travis
8
+ class Secure < Chamber::Commands::Base
9
+ include Chamber::Commands::Travis
10
+ include Chamber::Commands::Securable
11
+
12
+ def call
13
+ securable_environment_variables.each do |key, value|
14
+ if dry_run
15
+ shell.say_status 'encrypt', key, :blue
16
+ else
17
+ command = first_environment_variable?(key) ? '--override' : '--append'
18
+
19
+ shell.say_status 'encrypt', key, :green
20
+ travis_encrypt("#{command} #{key}=#{value}")
21
+ end
22
+ end
23
+ end
24
+
25
+ protected
26
+
27
+ def first_environment_variable?(key)
28
+ @first_environment_key ||= securable_environment_variables.first[0]
29
+
30
+ @first_environment_key == key
31
+ end
32
+ end
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,34 @@
1
+ module Chamber
2
+ class Configuration
3
+ attr_accessor :basepath,
4
+ :decryption_key,
5
+ :encryption_key,
6
+ :files,
7
+ :namespaces
8
+
9
+ def initialize(options = {})
10
+ self.basepath = options[:basepath] || ''
11
+ self.namespaces = options[:namespaces] || []
12
+ self.decryption_key = options[:decryption_key]
13
+ self.encryption_key = options[:encryption_key]
14
+ self.files = options[:files] || [
15
+ self.basepath + 'credentials*.yml',
16
+ self.basepath + 'settings*.yml',
17
+ self.basepath + 'settings' ]
18
+ end
19
+
20
+ def to_hash
21
+ {
22
+ basepath: self.basepath,
23
+ decryption_key: self.decryption_key,
24
+ encryption_key: self.encryption_key,
25
+ files: self.files,
26
+ namespaces: self.namespaces,
27
+ }
28
+ end
29
+
30
+ def basepath=(pathlike)
31
+ @basepath = pathlike == '' ? '' : Pathname.new(::File.expand_path(pathlike))
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,23 @@
1
+ require 'hashie/mash'
2
+
3
+ module Chamber
4
+ module Environmentable
5
+ def with_environment(settings, parent_keys, hash_block, value_block)
6
+ environment_hash = Hashie::Mash.new
7
+
8
+ settings.each_pair do |key, value|
9
+ environment_keys = parent_keys.dup.push(key)
10
+
11
+ if value.respond_to? :each_pair
12
+ environment_hash.merge!(hash_block.call(key, value, environment_keys))
13
+ else
14
+ environment_key = environment_keys.join('_').upcase
15
+
16
+ environment_hash.merge!(value_block.call(key, value, environment_key))
17
+ end
18
+ end
19
+
20
+ environment_hash
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,6 @@
1
+ module Chamber
2
+ module Errors
3
+ class UndecryptableValueError < RuntimeError
4
+ end
5
+ end
6
+ end
data/lib/chamber/file.rb CHANGED
@@ -6,7 +6,7 @@ require 'erb'
6
6
  # Internal: Represents a single file containing settings information in a given
7
7
  # file set.
8
8
  #
9
- class Chamber
9
+ module Chamber
10
10
  class File < Pathname
11
11
 
12
12
  ###
@@ -37,7 +37,9 @@ class File < Pathname
37
37
  # # => <Chamber::File>
38
38
  #
39
39
  def initialize(options = {})
40
- self.namespaces = options[:namespaces] || {}
40
+ self.namespaces = options[:namespaces] || {}
41
+ self.decryption_key = options[:decryption_key]
42
+ self.encryption_key = options[:encryption_key]
41
43
 
42
44
  super options.fetch(:path)
43
45
  end
@@ -62,13 +64,23 @@ class File < Pathname
62
64
  # ```
63
65
  #
64
66
  def to_settings
65
- @data ||= Settings.new(settings: file_contents_hash,
66
- namespaces: namespaces)
67
+ @data ||= Settings.new(settings: file_contents_hash,
68
+ namespaces: namespaces,
69
+ decryption_key: decryption_key,
70
+ encryption_key: encryption_key)
71
+ end
72
+
73
+ def secure
74
+ secure_settings = to_settings.secure
75
+
76
+ ::File.open(self, 'w') { |file| file.write YAML.dump(secure_settings.to_hash) }
67
77
  end
68
78
 
69
79
  protected
70
80
 
71
- attr_accessor :namespaces
81
+ attr_accessor :namespaces,
82
+ :decryption_key,
83
+ :encryption_key
72
84
 
73
85
  private
74
86
 
@@ -107,12 +107,14 @@ require 'chamber/settings'
107
107
  # FileSet.new(files: '/tmp/settings/*.json',
108
108
  # namespaces: %w{blue green})
109
109
  #
110
- class Chamber
110
+ module Chamber
111
111
  class FileSet
112
112
 
113
113
  def initialize(options = {})
114
- self.namespaces = options.fetch(:namespaces, {})
115
- self.paths = options.fetch(:files)
114
+ self.namespaces = options[:namespaces] || {}
115
+ self.decryption_key = options[:decryption_key]
116
+ self.encryption_key = options[:encryption_key]
117
+ self.paths = options.fetch(:files)
116
118
  end
117
119
 
118
120
  ###
@@ -161,21 +163,23 @@ class FileSet
161
163
  # # => <Chamber::Settings>
162
164
  #
163
165
  def to_settings
164
- clean_settings = Settings.new(:namespaces => namespaces)
165
-
166
- files.each_with_object(clean_settings) do |file, settings|
167
- if block_given?
168
- yield file.to_settings
169
- else
170
- settings.merge!(file.to_settings)
166
+ files.reduce(Settings.new) do |settings, file|
167
+ settings.merge(file.to_settings).tap do |merged|
168
+ yield merged if block_given?
171
169
  end
172
170
  end
173
171
  end
174
172
 
173
+ def secure
174
+ files.each(&:secure)
175
+ end
176
+
175
177
  protected
176
178
 
177
179
  attr_reader :namespaces,
178
180
  :paths
181
+ attr_accessor :decryption_key,
182
+ :encryption_key
179
183
 
180
184
  ###
181
185
  # Internal: Allows the paths for the FileSet to be set. It can either be an
@@ -210,8 +214,10 @@ class FileSet
210
214
  current_glob_files = Pathname.glob(glob)
211
215
  relevant_glob_files = relevant_files & current_glob_files
212
216
 
213
- relevant_glob_files.map! { |file| File.new( path: file,
214
- namespaces: namespaces) }
217
+ relevant_glob_files.map! { |file| File.new( path: file,
218
+ namespaces: namespaces,
219
+ decryption_key: decryption_key,
220
+ encryption_key: encryption_key) }
215
221
 
216
222
  sorted_relevant_files += relevant_glob_files
217
223
  end
@@ -0,0 +1,41 @@
1
+ module Chamber
2
+ module Filters
3
+ class BooleanConversionFilter
4
+
5
+ def initialize(options = {})
6
+ self.data = options.fetch(:data).dup
7
+ end
8
+
9
+ def self.execute(options = {})
10
+ self.new(options).send(:execute)
11
+ end
12
+
13
+ protected
14
+
15
+ attr_accessor :data
16
+
17
+ def execute(settings = data)
18
+ settings.each_pair do |key, value|
19
+ if value.respond_to? :each_pair
20
+ execute(value)
21
+ else
22
+ break if value.nil?
23
+
24
+ settings[key] = if value.is_a? String
25
+ case value
26
+ when 'false', 'f', 'no'
27
+ false
28
+ when 'true', 't', 'yes'
29
+ true
30
+ else
31
+ value
32
+ end
33
+ else
34
+ value
35
+ end
36
+ end
37
+ end
38
+ end
39
+ end
40
+ end
41
+ end