secret_config 0.8.0 → 0.10.3

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.
File without changes
@@ -97,5 +97,5 @@ module SecretConfig
97
97
  end
98
98
 
99
99
  @check_env_var = true
100
- @filters = [/password/i, /key\Z/i, /passphrase/i]
100
+ @filters = [/password/i, /key\Z/i, /passphrase/i, /secret/i, /pwd\Z/i]
101
101
  end
@@ -8,11 +8,29 @@ require "irb"
8
8
 
9
9
  module SecretConfig
10
10
  class CLI
11
- attr_reader :path, :region, :provider,
12
- :export, :no_filter,
11
+ module Colors
12
+ CLEAR = "\e[0m".freeze
13
+ BOLD = "\e[1m".freeze
14
+ BLACK = "\e[30m".freeze
15
+ RED = "\e[31m".freeze
16
+ GREEN = "\e[32m".freeze
17
+ YELLOW = "\e[33m".freeze
18
+ BLUE = "\e[34m".freeze
19
+ MAGENTA = "\e[35m".freeze
20
+ CYAN = "\e[36m".freeze
21
+ WHITE = "\e[37m".freeze
22
+
23
+ TITLE = "\e[1m".freeze
24
+ KEY = "\e[36m".freeze
25
+ REMOVE = "\e[31m".freeze
26
+ ADD = "\e[32m".freeze
27
+ end
28
+
29
+ attr_reader :path, :provider, :file_name,
30
+ :export, :no_filter, :interpolate,
13
31
  :import, :key_id, :key_alias, :random_size, :prune, :force,
14
32
  :diff_path, :import_path,
15
- :fetch_key, :delete_key, :set_key, :set_value, :delete_path,
33
+ :fetch_key, :delete_key, :set_key, :set_value, :delete_tree,
16
34
  :copy_path, :diff,
17
35
  :console,
18
36
  :show_version
@@ -29,7 +47,6 @@ module SecretConfig
29
47
  @path = nil
30
48
  @key_id = nil
31
49
  @key_alias = nil
32
- @region = ENV["AWS_REGION"]
33
50
  @provider = :ssm
34
51
  @random_size = 32
35
52
  @no_filter = false
@@ -43,10 +60,11 @@ module SecretConfig
43
60
  @set_value = nil
44
61
  @fetch_key = nil
45
62
  @delete_key = nil
46
- @delete_path = nil
63
+ @delete_tree = nil
47
64
  @diff_path = nil
48
65
  @import_path = nil
49
66
  @force = false
67
+ @interpolate = false
50
68
 
51
69
  if argv.empty?
52
70
  puts parser
@@ -58,27 +76,32 @@ module SecretConfig
58
76
  def run!
59
77
  if show_version
60
78
  puts "Secret Config v#{VERSION}"
61
- puts "Region: #{region}"
62
79
  elsif console
63
80
  run_console
64
81
  elsif export
65
- run_export(export, path, filtered: !no_filter)
82
+ raise(ArgumentError, "--path option is not valid for --export") if path
83
+
84
+ run_export(export, file_name || STDOUT, filtered: !no_filter)
66
85
  elsif import
67
- run_import(import, path, prune, force)
68
- elsif import_path
69
- run_import_path(import_path, path, prune, force)
86
+ if path
87
+ run_import_path(import, path, prune, force)
88
+ else
89
+ run_import(import, file_name || STDIN, prune, force)
90
+ end
70
91
  elsif diff
71
- run_diff(diff, path)
72
- elsif diff_path
73
- run_diff_path(diff_path, path)
92
+ if path
93
+ run_diff_path(diff, path)
94
+ else
95
+ run_diff(diff, file_name || STDIN)
96
+ end
74
97
  elsif set_key
75
98
  run_set(set_key, set_value)
76
99
  elsif fetch_key
77
100
  run_fetch(fetch_key)
78
101
  elsif delete_key
79
102
  run_delete(delete_key)
80
- elsif delete_path
81
- run_delete_path(delete_path)
103
+ elsif delete_tree
104
+ run_delete_tree(delete_tree)
82
105
  else
83
106
  puts parser
84
107
  end
@@ -91,27 +114,27 @@ module SecretConfig
91
114
 
92
115
  For more information, see: https://rocketjob.github.io/secret_config/
93
116
 
94
- secret_config [options]
117
+ secret-config [options]
95
118
  BANNER
96
119
 
97
- opts.on "-e", "--export [FILE_NAME]", "Export configuration to a file or stdout if no file_name supplied. --path SOURCE_PATH is required." do |file_name|
98
- @export = file_name || STDOUT
120
+ opts.on "-e", "--export SOURCE_PATH", "Export configuration. Use --file to specify the file name, otherwise stdout is used." do |path|
121
+ @export = path
99
122
  end
100
123
 
101
- opts.on "-i", "--import [FILE_NAME]", "Import configuration from a file or stdin if no file_name supplied. --path TARGET_PATH is required." do |file_name|
102
- @import = file_name || STDIN
124
+ opts.on "-i", "--import TARGET_PATH", "Import configuration. Use --file to specify the file name, --path for the SOURCE_PATH, otherwise stdin is used." do |path|
125
+ @import = path
103
126
  end
104
127
 
105
- opts.on "--import-path SOURCE_PATH", "Import configuration from the configuration on another path. --path TARGET_PATH is required." do |path|
106
- @import_path = path
128
+ opts.on "-f", "--file FILE_NAME", "Import/Export/Diff to/from this file." do |file_name|
129
+ @file_name = file_name
107
130
  end
108
131
 
109
- opts.on "--diff [FILE_NAME]", "Compare configuration from a file or stdin if no file_name supplied. --path TARGET_PATH is required." do |file_name|
110
- @diff = file_name
132
+ opts.on "-p", "--path PATH", "Import/Export/Diff to/from this path." do |path|
133
+ @path = path
111
134
  end
112
135
 
113
- opts.on "--diff-path SOURCE_PATH", "Diff configuration with the configuration on another path. --path TARGET_PATH is required." do |path|
114
- @diff_path = path
136
+ opts.on "--diff TARGET_PATH", "Compare configuration to this path. Use --file to specify the source file name, --path for the SOURCE_PATH, otherwise stdin is used." do |file_name|
137
+ @diff = file_name
115
138
  end
116
139
 
117
140
  opts.on "-s", "--set KEY=VALUE", "Set one key to value. Example: --set mysql/database=localhost" do |param|
@@ -121,59 +144,55 @@ module SecretConfig
121
144
  end
122
145
  end
123
146
 
124
- opts.on "-f", "--fetch KEY", "Fetch the value for one setting. Example: --get mysql/database. " do |key|
147
+ opts.on "-f", "--fetch KEY", "Fetch the value for one setting. Example: --fetch mysql/database." do |key|
125
148
  @fetch_key = key
126
149
  end
127
150
 
128
- opts.on "-d", "--delete KEY", "Delete one specific key. See --delete-path to delete all keys under a specific path " do |key|
151
+ opts.on "-d", "--delete KEY", "Delete one specific key." do |key|
129
152
  @delete_key = key
130
153
  end
131
154
 
132
- opts.on "-r", "--delete-path PATH", "Recursively delete all keys under the specified path.. " do |path|
133
- @delete_path = path
155
+ opts.on "-r", "--delete-tree PATH", "Recursively delete all keys under the specified path." do |path|
156
+ @delete_tree = path
134
157
  end
135
158
 
136
159
  opts.on "-c", "--console", "Start interactive console." do
137
160
  @console = true
138
161
  end
139
162
 
140
- opts.on "-p", "--path PATH", "Path in central configuration to use." do |path|
141
- @path = path
142
- end
143
-
144
163
  opts.on "--provider PROVIDER", "Provider to use. [ssm | file]. Default: ssm" do |provider|
145
164
  @provider = provider.to_sym
146
165
  end
147
166
 
148
- opts.on "--no-filter", "Do not filter passwords and keys." do
167
+ opts.on "--no-filter", "For --export only. Do not filter passwords and keys." do
149
168
  @no_filter = true
150
169
  end
151
170
 
152
- opts.on "--prune", "During import delete all existing keys for which there is no key in the import file. Only applies to --import and --import-path." do
171
+ opts.on "--interpolate", "For --export only. Evaluate string interpolation and __import__." do
172
+ @interpolate = true
173
+ end
174
+
175
+ opts.on "--prune", "For --import only. During import delete all existing keys for which there is no key in the import file. Only works with --import." do
153
176
  @prune = true
154
177
  end
155
178
 
156
- opts.on "--force", "During import overwrite all values, not just the changed ones. Useful for changing the KMS key. Only applies to --import and --import-path." do
179
+ opts.on "--force", "For --import only. Overwrite all values, not just the changed ones. Useful for changing the KMS key." do
157
180
  @force = true
158
181
  end
159
182
 
160
- opts.on "--key_id KEY_ID", "Encrypt config settings with this AWS KMS key id. Default: AWS Default key." do |key_id|
183
+ opts.on "--key_id KEY_ID", "For --import only. Encrypt config settings with this AWS KMS key id. Default: AWS Default key." do |key_id|
161
184
  @key_id = key_id
162
185
  end
163
186
 
164
- opts.on "--key_alias KEY_ALIAS", "Encrypt config settings with this AWS KMS alias." do |key_alias|
187
+ opts.on "--key_alias KEY_ALIAS", "For --import only. Encrypt config settings with this AWS KMS alias." do |key_alias|
165
188
  @key_alias = key_alias
166
189
  end
167
190
 
168
- opts.on "--region REGION", "AWS Region to use. Default: AWS_REGION env var." do |region|
169
- @region = region
170
- end
171
-
172
- opts.on "--random_size INTEGER", Integer, "Size to use when generating random values. Whenever #{RANDOM} is encountered during an import. Default: 32" do |random_size|
191
+ opts.on "--random_size INTEGER", Integer, "For --import only. Size to use when generating random values when $(random) is encountered in the source. Default: 32" do |random_size|
173
192
  @random_size = random_size
174
193
  end
175
194
 
176
- opts.on "-v", "--version", "Display Symmetric Encryption version." do
195
+ opts.on "-v", "--version", "Display Secret Config version." do
177
196
  @show_version = true
178
197
  end
179
198
 
@@ -191,64 +210,68 @@ module SecretConfig
191
210
  begin
192
211
  case provider
193
212
  when :ssm
194
- Providers::Ssm.new(key_id: key_id, key_alias: key_alias)
213
+ if key_alias
214
+ Providers::Ssm.new(key_alias: key_alias)
215
+ elsif key_id
216
+ Providers::Ssm.new(key_id: key_id)
217
+ else
218
+ Providers::Ssm.new
219
+ end
195
220
  else
196
221
  raise ArgumentError, "Invalid provider: #{provider}"
197
222
  end
198
223
  end
199
224
  end
200
225
 
201
- def run_export(file_name, path, filtered: true)
202
- raise(ArgumentError, "Missing required option --path") unless path
226
+ def run_export(source_path, file_name, filtered: true)
227
+ puts("Exporting #{provider}:#{source_path} to #{file_name}") if file_name.is_a?(String)
203
228
 
204
- config = fetch_config(path, filtered: filtered)
229
+ config = fetch_config(source_path, filtered: filtered)
205
230
  write_config_file(file_name, config)
206
-
207
- puts("Exported #{path} from #{provider} to #{file_name}") if file_name.is_a?(String)
208
231
  end
209
232
 
210
- def run_import(file_name, path, prune, force)
211
- raise(ArgumentError, "Missing required option --path") unless path
212
-
233
+ def run_import(target_path, file_name, prune, force)
234
+ puts "#{Colors::TITLE}--- #{provider}:#{target_path}"
235
+ puts "+++ #{file_name}#{Colors::CLEAR}"
213
236
  config = read_config_file(file_name)
214
- import_config(config, path, prune, force)
215
-
216
- puts("Imported #{file_name} to #{path} on provider: #{provider}") if file_name.is_a?(String)
237
+ import_config(config, target_path, prune, force)
217
238
  end
218
239
 
219
- def run_import_path(source_path, path, prune, force)
220
- raise(ArgumentError, "Missing required option --path") unless path
240
+ def run_import_path(target_path, source_path, prune, force)
241
+ puts "#{Colors::TITLE}--- #{provider}:#{target_path}"
242
+ puts "+++ #{provider}:#{source_path}#{Colors::CLEAR}"
221
243
 
222
244
  config = fetch_config(source_path, filtered: false)
223
- import_config(config, path, prune, force)
245
+ import_config(config, target_path, prune, force)
224
246
 
225
- puts("Imported #{source_path} to #{path} on provider: #{provider}")
247
+ puts("Imported #{target_path} from #{source_path} on provider: #{provider}")
226
248
  end
227
249
 
228
- def run_diff(file_name, path)
229
- raise(ArgumentError, "Missing required option --path") unless path
250
+ def run_diff(target_path, file_name)
251
+ source_config = read_config_file(file_name)
252
+ source = Utils.flatten(source_config, target_path)
230
253
 
231
- file_config = read_config_file(file_name)
232
- file = Utils.flatten(file_config, path)
254
+ target_config = fetch_config(target_path, filtered: false)
255
+ target = Utils.flatten(target_config, target_path)
233
256
 
234
- registry_config = fetch_config(path, filtered: false)
235
- registry = Utils.flatten(registry_config, path)
236
-
237
- puts("Comparing #{file_name} to #{path} on provider: #{provider}") if file_name.is_a?(String)
238
- diff_config(file, registry)
257
+ if file_name.is_a?(String)
258
+ puts "#{Colors::TITLE}--- #{provider}:#{target_path}"
259
+ puts "+++ #{file_name}#{Colors::CLEAR}"
260
+ end
261
+ diff_config(target, source)
239
262
  end
240
263
 
241
- def run_diff_path(source_path, path)
242
- raise(ArgumentError, "Missing required option --path") unless path
243
-
264
+ def run_diff_path(target_path, source_path)
244
265
  source_config = fetch_config(source_path, filtered: false)
245
266
  source = Utils.flatten(source_config)
246
267
 
247
- target_config = fetch_config(path, filtered: false)
268
+ target_config = fetch_config(target_path, filtered: false)
248
269
  target = Utils.flatten(target_config)
249
270
 
250
- puts("Comparing #{source_path} to #{path} on provider: #{provider}")
251
- diff_config(source, target)
271
+ puts "#{Colors::TITLE}--- #{provider}:#{target_path}"
272
+ puts "+++ #{provider}:#{source_path}#{Colors::CLEAR}"
273
+
274
+ diff_config(target, source)
252
275
  end
253
276
 
254
277
  def run_console
@@ -256,14 +279,18 @@ module SecretConfig
256
279
  end
257
280
 
258
281
  def run_delete(key)
282
+ puts "#{Colors::TITLE}--- #{provider}:#{path}"
283
+ puts "#{Colors::REMOVE}- #{key}#{Colors::CLEAR}"
259
284
  provider_instance.delete(key)
260
285
  end
261
286
 
262
- def run_delete_path(path)
287
+ def run_delete_tree(path)
263
288
  source_config = fetch_config(path)
264
- source = Utils.flatten(source_config, path)
289
+ puts "#{Colors::TITLE}--- #{provider}:#{path}#{Colors::CLEAR}"
290
+
291
+ source = Utils.flatten(source_config, path)
265
292
  source.each_key do |key|
266
- puts("Deleting #{key}")
293
+ puts "#{Colors::REMOVE}- #{key}#{Colors::CLEAR}"
267
294
  provider_instance.delete(key)
268
295
  end
269
296
  end
@@ -277,8 +304,8 @@ module SecretConfig
277
304
  provider_instance.set(key, value)
278
305
  end
279
306
 
280
- def current_values
281
- @current_values ||= Utils.flatten(fetch_config(path, filtered: false), path)
307
+ def current_values(path)
308
+ Utils.flatten(fetch_config(path, filtered: false), path)
282
309
  end
283
310
 
284
311
  def read_config_file(file_name)
@@ -306,38 +333,53 @@ module SecretConfig
306
333
  # Ignore filtered values
307
334
  next
308
335
  end
309
- puts "Setting: #{key}"
336
+
337
+ if current_values.key?(key)
338
+ puts "#{Colors::KEY}* #{key}#{Colors::CLEAR}"
339
+ else
340
+ puts "#{Colors::ADD}+ #{key}#{Colors::CLEAR}"
341
+ end
342
+
310
343
  provider_instance.set(key, value)
311
344
  end
312
345
  end
313
346
 
314
347
  def fetch_config(path, filtered: true)
315
- registry = Registry.new(path: path, provider: provider_instance)
348
+ registry = Registry.new(path: path, provider: provider_instance, interpolate: interpolate)
316
349
  config = filtered ? registry.configuration : registry.configuration(filters: nil)
317
350
  sort_hash_by_key!(config)
318
351
  end
319
352
 
320
353
  # Diffs two configs and displays the results
321
- def diff_config(source, target)
354
+ def diff_config(target, source)
322
355
  (source.keys + target.keys).sort.uniq.each do |key|
323
356
  if target.key?(key)
324
357
  if source.key?(key)
325
358
  value = source[key].to_s
326
359
  # Ignore filtered values
327
- puts "* #{key}: #{target[key]} => #{source[key]}" if (value != target[key].to_s) && (value != FILTERED)
360
+ if (value != target[key].to_s) && (value != FILTERED)
361
+ puts "#{Colors::KEY}#{key}:"
362
+ puts "#{Colors::REMOVE}#{prefix_lines('- ', target[key])}"
363
+ puts "#{Colors::ADD}#{prefix_lines('+ ', source[key])}#{Colors::CLEAR}\n\n"
364
+ end
328
365
  else
329
- puts "- #{key}"
366
+ puts "#{Colors::KEY}#{key}:"
367
+ puts "#{Colors::REMOVE}#{prefix_lines('- ', target[key])}\n\n"
330
368
  end
331
369
  elsif source.key?(key)
332
- puts "+ #{key}: #{source[key]}"
370
+ puts "#{Colors::KEY}#{key}:"
371
+ puts "#{Colors::ADD}#{prefix_lines('+ ', source[key])}#{Colors::CLEAR}\n\n"
333
372
  end
334
373
  end
335
374
  end
336
375
 
337
- def import_config(config, path, prune, force)
338
- raise(ArgumentError, "Missing required option --path") unless path
376
+ def prefix_lines(prefix, value)
377
+ value.to_s.lines.collect { |line| "#{prefix}#{line}" }.join("")
378
+ end
339
379
 
340
- delete_keys = prune ? current_values.keys - Utils.flatten(config, path).keys : []
380
+ def import_config(config, path, prune, force)
381
+ current = current_values(path)
382
+ delete_keys = prune ? current.keys - Utils.flatten(config, path).keys : []
341
383
 
342
384
  unless delete_keys.empty?
343
385
  puts "Going to delete the following keys:"
@@ -345,10 +387,10 @@ module SecretConfig
345
387
  sleep(5)
346
388
  end
347
389
 
348
- set_config(config, path, force ? {} : current_values)
390
+ set_config(config, path, force ? {} : current)
349
391
 
350
392
  delete_keys.each do |key|
351
- puts "Deleting: #{key}"
393
+ puts "#{Colors::REMOVE}- #{key}#{Colors::CLEAR}"
352
394
  provider_instance.delete(key)
353
395
  end
354
396
  end
@@ -5,31 +5,45 @@ module SecretConfig
5
5
  def_delegator :registry, :refresh!
6
6
 
7
7
  def initialize(path, registry)
8
+ raise(ArgumentError, "path cannot be nil") if path.nil?
9
+
8
10
  @path = path
9
11
  @registry = registry
10
12
  end
11
13
 
12
14
  def fetch(sub_path, **options)
15
+ raise(ArgumentError, "sub_path cannot be nil") if sub_path.nil?
16
+
13
17
  registry.fetch(join_path(sub_path), **options)
14
18
  end
15
19
 
16
20
  def [](sub_path)
21
+ raise(ArgumentError, "sub_path cannot be nil") if sub_path.nil?
22
+
17
23
  registry[join_path(sub_path)]
18
24
  end
19
25
 
20
26
  def []=(sub_path, value)
27
+ raise(ArgumentError, "sub_path cannot be nil") if sub_path.nil?
28
+
21
29
  registry[join_path(sub_path)] = value
22
30
  end
23
31
 
24
32
  def key?(sub_path)
33
+ raise(ArgumentError, "sub_path cannot be nil") if sub_path.nil?
34
+
25
35
  registry.key?(join_path(sub_path))
26
36
  end
27
37
 
28
38
  def set(sub_path, value)
39
+ raise(ArgumentError, "sub_path cannot be nil") if sub_path.nil?
40
+
29
41
  registry.set(join_path(sub_path), value)
30
42
  end
31
43
 
32
44
  def delete(sub_path)
45
+ raise(ArgumentError, "sub_path cannot be nil") if sub_path.nil?
46
+
33
47
  registry.delete(join_path(sub_path))
34
48
  end
35
49