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.
- checksums.yaml +4 -4
- data/README.md +17 -800
- data/bin/{secret_config → secret-config} +0 -0
- data/lib/secret_config.rb +1 -1
- data/lib/secret_config/cli.rb +134 -92
- data/lib/secret_config/config.rb +14 -0
- data/lib/secret_config/errors.rb +3 -0
- data/lib/secret_config/parser.rb +11 -11
- data/lib/secret_config/providers/file.rb +2 -2
- data/lib/secret_config/registry.rb +10 -10
- data/lib/secret_config/setting_interpolator.rb +19 -12
- data/lib/secret_config/string_interpolator.rb +7 -7
- data/lib/secret_config/utils.rb +1 -1
- data/lib/secret_config/version.rb +1 -1
- data/test/config/application.yml +4 -4
- data/test/parser_test.rb +2 -2
- data/test/providers/file_test.rb +1 -1
- data/test/providers/ssm_test.rb +1 -1
- data/test/registry_test.rb +7 -2
- data/test/secret_config_test.rb +0 -1
- data/test/setting_interpolator_test.rb +34 -27
- data/test/test_helper.rb +1 -1
- metadata +12 -12
File without changes
|
data/lib/secret_config.rb
CHANGED
data/lib/secret_config/cli.rb
CHANGED
@@ -8,11 +8,29 @@ require "irb"
|
|
8
8
|
|
9
9
|
module SecretConfig
|
10
10
|
class CLI
|
11
|
-
|
12
|
-
|
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, :
|
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
|
-
@
|
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
|
-
|
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
|
-
|
68
|
-
|
69
|
-
|
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
|
-
|
72
|
-
|
73
|
-
|
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
|
81
|
-
|
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
|
-
|
117
|
+
secret-config [options]
|
95
118
|
BANNER
|
96
119
|
|
97
|
-
opts.on "-e", "--export
|
98
|
-
@export =
|
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
|
102
|
-
@import =
|
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 "
|
106
|
-
@
|
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 "--
|
110
|
-
@
|
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
|
114
|
-
@
|
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: --
|
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.
|
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-
|
133
|
-
@
|
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 "--
|
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", "
|
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 "--
|
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
|
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
|
-
|
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(
|
202
|
-
|
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(
|
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(
|
211
|
-
|
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,
|
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(
|
220
|
-
|
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,
|
245
|
+
import_config(config, target_path, prune, force)
|
224
246
|
|
225
|
-
puts("Imported #{
|
247
|
+
puts("Imported #{target_path} from #{source_path} on provider: #{provider}")
|
226
248
|
end
|
227
249
|
|
228
|
-
def run_diff(
|
229
|
-
|
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
|
-
|
232
|
-
|
254
|
+
target_config = fetch_config(target_path, filtered: false)
|
255
|
+
target = Utils.flatten(target_config, target_path)
|
233
256
|
|
234
|
-
|
235
|
-
|
236
|
-
|
237
|
-
|
238
|
-
diff_config(
|
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(
|
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(
|
268
|
+
target_config = fetch_config(target_path, filtered: false)
|
248
269
|
target = Utils.flatten(target_config)
|
249
270
|
|
250
|
-
puts
|
251
|
-
|
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
|
287
|
+
def run_delete_tree(path)
|
263
288
|
source_config = fetch_config(path)
|
264
|
-
|
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
|
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
|
-
|
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
|
-
|
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(
|
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
|
-
|
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 "
|
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 "
|
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
|
338
|
-
|
376
|
+
def prefix_lines(prefix, value)
|
377
|
+
value.to_s.lines.collect { |line| "#{prefix}#{line}" }.join("")
|
378
|
+
end
|
339
379
|
|
340
|
-
|
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 ? {} :
|
390
|
+
set_config(config, path, force ? {} : current)
|
349
391
|
|
350
392
|
delete_keys.each do |key|
|
351
|
-
puts "
|
393
|
+
puts "#{Colors::REMOVE}- #{key}#{Colors::CLEAR}"
|
352
394
|
provider_instance.delete(key)
|
353
395
|
end
|
354
396
|
end
|
data/lib/secret_config/config.rb
CHANGED
@@ -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
|
|