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