bolt 1.23.0 → 1.24.0

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of bolt might be problematic. Click here for more details.

checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: c0387b7b6348b7003f02a2433a92e90c73464ac6a7d1cf135c847142bc44cb52
4
- data.tar.gz: 502dc3c14c15925ff40604763b4cf5dc1e83d222e37b6c0a47cb5827f696e236
3
+ metadata.gz: cc4db252a26ca2d1f05bddfa9b4affaece04baf3d2324e37b29e7f3579301640
4
+ data.tar.gz: 963c29e99b6389113a6ed9e8bc1bbe5209d5b7f9a1d0492f0d56af5a88d25f17
5
5
  SHA512:
6
- metadata.gz: 8c89fe321fc1c14d00cc011fa346241aa140d5da3cce8f6ea9624e46df8e35a61a5cc4055371d94b61b2d53c46868f1a8dcbdf69ac1f05e065955b65ad36c3dd
7
- data.tar.gz: 4a1ed6da115e53825f75f5a27a7452e57037874d0665244d8bf2a35aa7ab591765bbc8a879d3bbcf8737b20500b29dca1a60f7a46860bc22a71047f48a248fe9
6
+ metadata.gz: 11d6586b4ee6b03471749c629fc1ac2c342025b707b5bf443044f7f01ee930d1ab1c678655e827452c800df149e998d0b256e4f11e5bae194cd88eba6eb5847b
7
+ data.tar.gz: 9775e66c5a65810e72e129787976f98d7725f64a293ccf80237372c90b8db149ca94d991afbc0779d26dab228e3c9a72ac884ad32b1d180c33a03582c57aeab0
@@ -6,6 +6,7 @@ require 'bolt/error'
6
6
  #
7
7
  # **NOTE:** Not available in apply block
8
8
  Puppet::Functions.create_function(:run_plan, Puppet::Functions::InternalFunction) do
9
+ # Run a plan
9
10
  # @param plan_name The plan to run.
10
11
  # @param named_args Arguments to the plan. Can also include additional options: '_catch_errors', '_run_as'.
11
12
  # @return [PlanResult] The result of running the plan. Undef if plan does not explicitly return results.
@@ -18,6 +19,31 @@ Puppet::Functions.create_function(:run_plan, Puppet::Functions::InternalFunction
18
19
  return_type 'Boltlib::PlanResult'
19
20
  end
20
21
 
22
+ # Run a plan, specifying $nodes as a positional argument.
23
+ # @param plan_name The plan to run.
24
+ # @param named_args Arguments to the plan. Can also include additional options: '_catch_errors', '_run_as'.
25
+ # @param targets A pattern identifying zero or more targets. See {get_targets} for accepted patterns.
26
+ # @return [PlanResult] The result of running the plan. Undef if plan does not explicitly return results.
27
+ # @example Run a plan
28
+ # run_plan('canary', $nodes, 'command' => 'false')
29
+ dispatch :run_plan_with_targetspec do
30
+ scope_param
31
+ param 'String', :plan_name
32
+ param 'Boltlib::TargetSpec', :targets
33
+ optional_param 'Hash', :named_args
34
+ return_type 'Boltlib::PlanResult'
35
+ end
36
+
37
+ def run_plan_with_targetspec(scope, plan_name, targets, named_args = {})
38
+ unless named_args['nodes'].nil?
39
+ raise ArgumentError,
40
+ "A plan's 'nodes' parameter may be specified as the second positional argument to " \
41
+ "run_plan(), but in that case 'nodes' must not be specified in the named arguments " \
42
+ "hash."
43
+ end
44
+ run_plan(scope, plan_name, named_args.merge('nodes' => targets))
45
+ end
46
+
21
47
  def run_plan(scope, plan_name, named_args = {})
22
48
  unless Puppet[:tasks]
23
49
  raise Puppet::ParseErrorWithIssue
@@ -6,6 +6,79 @@ require 'optparse'
6
6
 
7
7
  module Bolt
8
8
  class BoltOptionParser < OptionParser
9
+ OPTIONS = { inventory: %w[nodes targets query rerun description],
10
+ authentication: %w[user password private-key host-key-check ssl ssl-verify],
11
+ escalation: %w[run-as sudo-password],
12
+ run_context: %w[concurrency inventoryfile save-rerun],
13
+ global_config_setters: %w[modulepath boltdir configfile],
14
+ transports: %w[transport connect-timeout tty],
15
+ display: %w[format color verbose trace],
16
+ global: %w[help version debug] }.freeze
17
+
18
+ ACTION_OPTS = OPTIONS.values.flatten.freeze
19
+
20
+ def get_help_text(subcommand, action = nil)
21
+ case subcommand
22
+ when 'apply'
23
+ { flags: ACTION_OPTS + %w[noop execute compile-concurrency],
24
+ banner: APPLY_HELP }
25
+ when 'command'
26
+ { flags: ACTION_OPTS,
27
+ banner: COMMAND_HELP }
28
+ when 'file'
29
+ { flags: ACTION_OPTS + %w[tmpdir],
30
+ banner: FILE_HELP }
31
+ when 'plan'
32
+ case action
33
+ when 'convert'
34
+ { flags: OPTIONS[:global] + OPTIONS[:global_config_setters],
35
+ banner: PLAN_CONVERT_HELP }
36
+ when 'show'
37
+ { flags: OPTIONS[:global] + OPTIONS[:global_config_setters],
38
+ banner: PLAN_SHOW_HELP }
39
+ when 'run'
40
+ { flags: ACTION_OPTS + %w[params compile-concurrency tmpdir],
41
+ banner: PLAN_RUN_HELP }
42
+ else
43
+ { flags: ACTION_OPTS + %w[params compile-concurrency tmpdir],
44
+ banner: PLAN_HELP }
45
+ end
46
+ when 'puppetfile'
47
+ case action
48
+ when 'install'
49
+ { flags: OPTIONS[:global] + OPTIONS[:global_config_setters],
50
+ banner: PUPPETFILE_INSTALL_HELP }
51
+ when 'show-modules'
52
+ { flags: OPTIONS[:global] + OPTIONS[:global_config_setters],
53
+ banner: PUPPETFILE_SHOWMODULES_HELP }
54
+ else
55
+ { flags: OPTIONS[:global] + OPTIONS[:global_config_setters],
56
+ banner: PUPPETFILE_HELP }
57
+ end
58
+ when 'script'
59
+ { flags: ACTION_OPTS + %w[tmpdir],
60
+ banner: SCRIPT_HELP }
61
+ when 'secret'
62
+ { flags: OPTIONS[:global] + OPTIONS[:global_config_setters],
63
+ banner: SECRET_HELP }
64
+ when 'task'
65
+ case action
66
+ when 'show'
67
+ { flags: OPTIONS[:global] + OPTIONS[:global_config_setters],
68
+ banner: TASK_SHOW_HELP }
69
+ when 'run'
70
+ { flags: ACTION_OPTS + %w[params tmpdir],
71
+ banner: TASK_RUN_HELP }
72
+ else
73
+ { flags: ACTION_OPTS + %w[params tmpdir],
74
+ banner: TASK_HELP }
75
+ end
76
+ else
77
+ { flags: OPTIONS[:global],
78
+ banner: BANNER }
79
+ end
80
+ end
81
+
9
82
  def self.examples(cmd, desc)
10
83
  <<-EXAMP
11
84
  #{desc} a Windows host via WinRM, providing for the password
@@ -18,7 +91,7 @@ EXAMP
18
91
  end
19
92
 
20
93
  BANNER = <<-HELP
21
- Usage: bolt <subcommand> <action> [options]
94
+ Usage: bolt <subcommand> <action>
22
95
 
23
96
  Available subcommands:
24
97
  bolt command run <command> Run a command remotely
@@ -34,19 +107,41 @@ Available subcommands:
34
107
  bolt apply <manifest> Apply Puppet manifest code
35
108
  bolt puppetfile install Install modules from a Puppetfile into a Boltdir
36
109
  bolt puppetfile show-modules List modules available to Bolt
110
+ bolt secret createkeys Create new encryption keys
111
+ bolt secret encrypt <plaintext> Encrypt a value
112
+ bolt secret decrypt <encrypted> Decrypt a value
37
113
 
38
114
  Run `bolt <subcommand> --help` to view specific examples.
39
115
 
40
- where [options] are:
116
+ Available options are:
41
117
  HELP
42
118
 
43
119
  TASK_HELP = <<-HELP
44
- Usage: bolt task <action> <task> [options] [parameters]
120
+ Usage: bolt task <action> <task> [parameters]
45
121
 
46
122
  Available actions are:
47
123
  show Show list of available tasks
48
124
  show <task> Show documentation for task
49
- run Run a Puppet task
125
+ run <task> Run a Puppet task
126
+
127
+ Parameters are of the form <parameter>=<value>.
128
+
129
+ #{examples('task run facts', 'run facter on')}
130
+ Available options are:
131
+ HELP
132
+
133
+ TASK_SHOW_HELP = <<-HELP
134
+ Usage: bolt task show <task>
135
+
136
+ Available actions are:
137
+ show Show list of available tasks
138
+ show <task> Show documentation for task
139
+
140
+ Available options are:
141
+ HELP
142
+
143
+ TASK_RUN_HELP = <<-HELP
144
+ Usage: bolt task run <task> [parameters]
50
145
 
51
146
  Parameters are of the form <parameter>=<value>.
52
147
 
@@ -55,7 +150,7 @@ Available options are:
55
150
  HELP
56
151
 
57
152
  COMMAND_HELP = <<-HELP
58
- Usage: bolt command <action> <command> [options]
153
+ Usage: bolt command <action> <command>
59
154
 
60
155
  Available actions are:
61
156
  run Run a command remotely
@@ -65,7 +160,7 @@ Available options are:
65
160
  HELP
66
161
 
67
162
  SCRIPT_HELP = <<-HELP
68
- Usage: bolt script <action> <script> [[arg1] ... [argN]] [options]
163
+ Usage: bolt script <action> <script> [[arg1] ... [argN]]
69
164
 
70
165
  Available actions are:
71
166
  run Upload a local script and run it remotely
@@ -75,7 +170,7 @@ Available options are:
75
170
  HELP
76
171
 
77
172
  PLAN_HELP = <<-HELP
78
- Usage: bolt plan <action> <plan> [options] [parameters]
173
+ Usage: bolt plan <action> <plan> [parameters]
79
174
 
80
175
  Available actions are:
81
176
  convert <plan_path> Convert a YAML plan to a Puppet plan
@@ -85,12 +180,37 @@ Available actions are:
85
180
 
86
181
  Parameters are of the form <parameter>=<value>.
87
182
 
183
+ #{examples('plan run canary command=hostname', 'run the canary plan on')}
184
+ Available options are:
185
+ HELP
186
+
187
+ PLAN_CONVERT_HELP = <<-HELP
188
+ Usage: bolt plan convert <plan_path>
189
+
190
+ Available options are:
191
+ HELP
192
+
193
+ PLAN_SHOW_HELP = <<-HELP
194
+ Usage: bolt plan show <plan>
195
+
196
+ Available actions are:
197
+ show Show list of available plans
198
+ show <plan> Show details for plan
199
+
200
+ Available options are:
201
+ HELP
202
+
203
+ PLAN_RUN_HELP = <<-HELP
204
+ Usage: bolt plan run <plan> [parameters]
205
+
206
+ Parameters are of the form <parameter>=<value>.
207
+
88
208
  #{examples('plan run canary command=hostname', 'run the canary plan on')}
89
209
  Available options are:
90
210
  HELP
91
211
 
92
212
  FILE_HELP = <<-HELP
93
- Usage: bolt file <action> [options]
213
+ Usage: bolt file <action>
94
214
 
95
215
  Available actions are:
96
216
  upload <src> <dest> Upload local file or directory <src> to <dest> on each node
@@ -100,7 +220,7 @@ Available options are:
100
220
  HELP
101
221
 
102
222
  PUPPETFILE_HELP = <<-HELP
103
- Usage: bolt puppetfile <action> [options]
223
+ Usage: bolt puppetfile <action>
104
224
 
105
225
  Available actions are:
106
226
  install Install modules from a Puppetfile into a Boltdir
@@ -109,60 +229,75 @@ Available actions are:
109
229
  Install modules into the local Boltdir
110
230
  bolt puppetfile install
111
231
 
232
+ Available options are:
233
+ HELP
234
+
235
+ PUPPETFILE_INSTALL_HELP = <<-HELP
236
+ Usage: bolt puppetfile install
237
+
238
+ Install modules into the local Boltdir
239
+ bolt puppetfile install
240
+
241
+ Available options are:
242
+ HELP
243
+
244
+ PUPPETFILE_SHOWMODULES_HELP = <<-HELP
245
+ Usage: bolt puppetfile show-modules
246
+
112
247
  Available options are:
113
248
  HELP
114
249
 
115
250
  APPLY_HELP = <<-HELP
116
- Usage: bolt apply <manifest.pp> [options]
251
+ Usage: bolt apply <manifest.pp>
117
252
 
118
253
  #{examples('apply site.pp', 'apply a manifest on')}
119
254
  bolt apply site.pp --nodes foo.example.com,bar.example.com
255
+
256
+ Available options are:
120
257
  HELP
121
258
 
122
- # A helper mixin for OptionParser::Switch instances which will allow
123
- # us to show/hide particular switch in the help message produced by
124
- # the OptionParser#help method on demand.
125
- module SwitchHider
126
- attr_accessor :hide
259
+ SECRET_HELP = <<~SECRET_HELP
260
+ Manage secrets for inventory and hiera data.
127
261
 
128
- def summarize(*args)
129
- return self if hide
130
- super
131
- end
132
- end
262
+ Available actions are:
263
+ createkeys Create new encryption keys
264
+ encrypt Encrypt a value
265
+ decrypt Decrypt a value
266
+ Available options are:
267
+ SECRET_HELP
133
268
 
134
269
  def initialize(options)
135
270
  super()
136
271
 
137
272
  @options = options
138
273
 
139
- @nodes = define('-n', '--nodes NODES',
140
- 'Alias for --targets') do |nodes|
274
+ define('-n', '--nodes NODES',
275
+ 'Alias for --targets') do |nodes|
141
276
  @options [:nodes] ||= []
142
277
  @options[:nodes] << get_arg_input(nodes)
143
- end.extend(SwitchHider)
144
- @targets = define('-t', '--targets TARGETS',
145
- 'Identifies the targets of command.',
146
- 'Enter a comma-separated list of target URIs or group names.',
147
- "Or read a target list from an input file '@<file>' or stdin '-'.",
148
- 'Example: --targets localhost,node_group,ssh://nix.com:23,winrm://windows.puppet.com',
149
- 'URI format is [protocol://]host[:port]',
150
- "SSH is the default protocol; may be #{TRANSPORTS.keys.join(', ')}",
151
- 'For Windows targets, specify the winrm:// protocol if it has not be configured',
152
- 'For SSH, port defaults to `22`',
153
- 'For WinRM, port defaults to `5985` or `5986` based on the --[no-]ssl setting') do |targets|
278
+ end
279
+ define('-t', '--targets TARGETS',
280
+ 'Identifies the targets of command.',
281
+ 'Enter a comma-separated list of target URIs or group names.',
282
+ "Or read a target list from an input file '@<file>' or stdin '-'.",
283
+ 'Example: --targets localhost,node_group,ssh://nix.com:23,winrm://windows.puppet.com',
284
+ 'URI format is [protocol://]host[:port]',
285
+ "SSH is the default protocol; may be #{TRANSPORTS.keys.join(', ')}",
286
+ 'For Windows targets, specify the winrm:// protocol if it has not be configured',
287
+ 'For SSH, port defaults to `22`',
288
+ 'For WinRM, port defaults to `5985` or `5986` based on the --[no-]ssl setting') do |targets|
154
289
  @options[:targets] ||= []
155
290
  @options[:targets] << get_arg_input(targets)
156
- end.extend(SwitchHider)
157
- @query = define('-q', '--query QUERY', 'Query PuppetDB to determine the targets') do |query|
291
+ end
292
+ define('-q', '--query QUERY', 'Query PuppetDB to determine the targets') do |query|
158
293
  @options[:query] = query
159
- end.extend(SwitchHider)
160
- @rerun = define('--rerun FILTER', 'Retry on nodes from the last run',
161
- "'all' all nodes that were part of the last run.",
162
- "'failure' nodes that failed in the last run.",
163
- "'success' nodes that succeeded in the last run.") do |rerun|
294
+ end
295
+ define('--rerun FILTER', 'Retry on nodes from the last run',
296
+ "'all' all nodes that were part of the last run.",
297
+ "'failure' nodes that failed in the last run.",
298
+ "'success' nodes that succeeded in the last run.") do |rerun|
164
299
  @options[:rerun] = rerun
165
- end.extend(SwitchHider)
300
+ end
166
301
  define('--noop', 'Execute a task that supports it in noop mode') do |_|
167
302
  @options[:noop] = true
168
303
  end
@@ -174,12 +309,12 @@ Usage: bolt apply <manifest.pp> [options]
174
309
  "Parameters to a task or plan as json, a json file '@<file>', or on stdin '-'") do |params|
175
310
  @options[:task_options] = parse_params(params)
176
311
  end
177
- @execute = define('-e', '--execute CODE',
178
- "Puppet manifest code to apply to the targets") do |code|
312
+ define('-e', '--execute CODE',
313
+ "Puppet manifest code to apply to the targets") do |code|
179
314
  @options[:code] = code
180
- end.extend(SwitchHider)
315
+ end
181
316
 
182
- separator 'Authentication:'
317
+ separator "\nAuthentication:"
183
318
  define('-u', '--user USER', 'User to authenticate as') do |user|
184
319
  @options[:user] = user
185
320
  end
@@ -206,7 +341,7 @@ Usage: bolt apply <manifest.pp> [options]
206
341
  @options[:'ssl-verify'] = ssl_verify
207
342
  end
208
343
 
209
- separator 'Escalation:'
344
+ separator "\nEscalation:"
210
345
  define('--run-as USER', 'User to run as using privilege escalation') do |user|
211
346
  @options[:'run-as'] = user
212
347
  end
@@ -221,7 +356,7 @@ Usage: bolt apply <manifest.pp> [options]
221
356
  end
222
357
  end
223
358
 
224
- separator 'Run context:'
359
+ separator "\nRun context:"
225
360
  define('-c', '--concurrency CONCURRENCY', Integer,
226
361
  'Maximum number of simultaneous connections (default: 100)') do |concurrency|
227
362
  @options[:concurrency] = concurrency
@@ -256,7 +391,7 @@ Usage: bolt apply <manifest.pp> [options]
256
391
  @options[:'save-rerun'] = save
257
392
  end
258
393
 
259
- separator 'Transports:'
394
+ separator "\nTransports:"
260
395
  define('--transport TRANSPORT', TRANSPORTS.keys.map(&:to_s),
261
396
  "Specify a default transport: #{TRANSPORTS.keys.join(', ')}") do |t|
262
397
  @options[:transport] = t
@@ -271,65 +406,52 @@ Usage: bolt apply <manifest.pp> [options]
271
406
  @options[:tmpdir] = tmpdir
272
407
  end
273
408
 
274
- separator 'Display:'
409
+ separator "\nDisplay:"
275
410
  define('--format FORMAT', 'Output format to use: human or json') do |format|
276
411
  @options[:format] = format
277
412
  end
278
413
  define('--[no-]color', 'Whether to show output in color') do |color|
279
414
  @options[:color] = color
280
415
  end
281
- define('-h', '--help', 'Display help') do |_|
282
- @options[:help] = true
283
- end
284
416
  define('-v', '--[no-]verbose', 'Display verbose logging') do |value|
285
417
  @options[:verbose] = value
286
418
  end
287
- define('--debug', 'Display debug logging') do |_|
288
- @options[:debug] = true
289
- end
290
419
  define('--trace', 'Display error stack traces') do |_|
291
420
  @options[:trace] = true
292
421
  end
422
+
423
+ separator "\nGlobal:"
424
+ define('-h', '--help', 'Display help') do |_|
425
+ @options[:help] = true
426
+ end
293
427
  define('--version', 'Display the version') do |_|
294
428
  puts Bolt::VERSION
295
429
  raise Bolt::CLIExit
296
430
  end
297
-
298
- update
431
+ define('--debug', 'Display debug logging') do |_|
432
+ @options[:debug] = true
433
+ end
299
434
  end
300
435
 
301
- def hide_target_opts(toggle = true)
302
- @nodes.hide = @query.hide = @rerun.hide = @targets.hide = toggle
436
+ def remove_excluded_opts(option_list)
437
+ # Remove any options that are not available for the specified subcommand
438
+ top.list.delete_if do |opt|
439
+ opt.respond_to?(:switch_name) && !option_list.include?(opt.switch_name)
440
+ end
441
+ # Remove any separators if all options of that type have been removed
442
+ top.list.delete_if do |opt|
443
+ i = top.list.index(opt)
444
+ opt.is_a?(String) && top.list[i + 1].is_a?(String)
445
+ end
303
446
  end
304
447
 
305
448
  def update
306
- # show the --nodes, --query, and --rerun switches by default
307
- hide_target_opts(false)
308
- # Don't show the --execute switch except for `apply`
309
- @execute.hide = true
310
-
449
+ help_text = get_help_text(@options[:subcommand], @options[:action])
311
450
  # Update the banner according to the subcommand
312
- self.banner = case @options[:subcommand]
313
- when 'plan'
314
- PLAN_HELP
315
- when 'command'
316
- COMMAND_HELP
317
- when 'script'
318
- SCRIPT_HELP
319
- when 'task'
320
- TASK_HELP
321
- when 'file'
322
- FILE_HELP
323
- when 'puppetfile'
324
- # Don't show targeting options for puppetfile
325
- hide_target_opts
326
- PUPPETFILE_HELP
327
- when 'apply'
328
- @execute.hide = false
329
- APPLY_HELP
330
- else
331
- BANNER
332
- end
451
+ self.banner = help_text[:banner]
452
+ # Builds the option list for the specified subcommand and removes all excluded
453
+ # options from the help text
454
+ remove_excluded_opts(help_text[:flags])
333
455
  end
334
456
 
335
457
  def parse_params(params)
data/lib/bolt/cli.rb CHANGED
@@ -23,6 +23,7 @@ require 'bolt/plugin'
23
23
  require 'bolt/pal'
24
24
  require 'bolt/target'
25
25
  require 'bolt/version'
26
+ require 'bolt/secret'
26
27
 
27
28
  module Bolt
28
29
  class CLIExit < StandardError; end
@@ -33,6 +34,7 @@ module Bolt
33
34
  'plan' => %w[show run convert],
34
35
  'file' => %w[upload],
35
36
  'puppetfile' => %w[install show-modules],
37
+ 'secret' => %w[encrypt decrypt createkeys],
36
38
  'apply' => %w[] }.freeze
37
39
 
38
40
  attr_reader :config, :options
@@ -51,7 +53,7 @@ module Bolt
51
53
  end
52
54
  private :inventory
53
55
 
54
- def help?(parser, remaining)
56
+ def help?(remaining)
55
57
  # Set the subcommand
56
58
  options[:subcommand] = remaining.shift
57
59
 
@@ -60,8 +62,12 @@ module Bolt
60
62
  options[:subcommand] = remaining.shift
61
63
  end
62
64
 
63
- # Update the parser for the new subcommand
64
- parser.update
65
+ # This section handles parsing non-flag options which are
66
+ # subcommand specific rather then part of the config
67
+ actions = COMMANDS[options[:subcommand]]
68
+ if actions && !actions.empty?
69
+ options[:action] = remaining.shift
70
+ end
65
71
 
66
72
  options[:help]
67
73
  end
@@ -72,17 +78,13 @@ module Bolt
72
78
 
73
79
  # This part aims to handle both `bolt <mode> --help` and `bolt help <mode>`.
74
80
  remaining = handle_parser_errors { parser.permute(@argv) } unless @argv.empty?
75
- if @argv.empty? || help?(parser, remaining)
81
+ if @argv.empty? || help?(remaining)
82
+ # Update the parser for the subcommand (or lack thereof)
83
+ parser.update
76
84
  puts parser.help
77
85
  raise Bolt::CLIExit
78
86
  end
79
87
 
80
- # This section handles parsing non-flag options which are
81
- # subcommand specific rather then part of the config
82
- actions = COMMANDS[options[:subcommand]]
83
- if actions && !actions.empty?
84
- options[:action] = remaining.shift
85
- end
86
88
  options[:object] = remaining.shift
87
89
 
88
90
  task_options, remaining = remaining.partition { |s| s =~ /.+=/ }
@@ -120,6 +122,7 @@ module Bolt
120
122
  # options[:target_args] will contain a string/array version of the targetting options this is passed to plans
121
123
  # options[:targets] will contain a resolved set of Target objects
122
124
  unless options[:subcommand] == 'puppetfile' ||
125
+ options[:subcommand] == 'secret' ||
123
126
  options[:action] == 'show' ||
124
127
  options[:action] == 'convert'
125
128
 
@@ -311,6 +314,8 @@ module Bolt
311
314
  code = run_plan(options[:object], options[:task_options], options[:target_args], options)
312
315
  when 'puppetfile'
313
316
  code = install_puppetfile(@config.puppetfile_config, @config.puppetfile, @config.modulepath)
317
+ when 'secret'
318
+ code = Bolt::Secret.execute(plugins, outputter, options)
314
319
  when 'apply'
315
320
  if options[:object]
316
321
  validate_file('manifest', options[:object])
data/lib/bolt/config.rb CHANGED
@@ -33,7 +33,7 @@ module Bolt
33
33
  class Config
34
34
  attr_accessor :concurrency, :format, :trace, :log, :puppetdb, :color, :save_rerun,
35
35
  :transport, :transports, :inventoryfile, :compile_concurrency, :boltdir,
36
- :puppetfile_config
36
+ :puppetfile_config, :plugins
37
37
  attr_writer :modulepath
38
38
 
39
39
  TRANSPORT_OPTIONS = %i[password run-as sudo-password extensions
@@ -70,6 +70,7 @@ module Bolt
70
70
  @color = true
71
71
  @save_rerun = true
72
72
  @puppetfile_config = {}
73
+ @plugins = {}
73
74
 
74
75
  # add an entry for the default console logger
75
76
  @log = { 'console' => {} }
@@ -158,6 +159,9 @@ module Bolt
158
159
 
159
160
  @save_rerun = data['save-rerun'] if data.key?('save-rerun')
160
161
 
162
+ # Plugins are only settable from config not inventory so we can overwrite
163
+ @plugins = data['plugins'] if data.key?('plugins')
164
+
161
165
  %w[concurrency format puppetdb color transport].each do |key|
162
166
  send("#{key}=", data[key]) if data.key?(key)
163
167
  end
@@ -93,7 +93,10 @@ module Bolt
93
93
  unless (plugin = @plugins.by_name(value['_plugin']))
94
94
  raise ValidationError.new("unkown plugin: #{value['_plugin'].inspect}", nil)
95
95
  end
96
- plugin.new.inventory_config_lookup(value)
96
+ plugin.validate_inventory_config_lookup(value) if plugin.respond_to?(:validate_inventory_config_lookup)
97
+ Concurrent::Delay.new do
98
+ plugin.inventory_config_lookup(value)
99
+ end
97
100
  else
98
101
  value
99
102
  end
@@ -124,7 +124,8 @@ module Bolt
124
124
  def config_plugin(data)
125
125
  Bolt::Util.walk_vals(data) do |val|
126
126
  if val.is_a?(Concurrent::Delay)
127
- val.value
127
+ # We should raise any error from the delay now
128
+ val.value!
128
129
  else
129
130
  val
130
131
  end
data/lib/bolt/plugin.rb CHANGED
@@ -2,6 +2,7 @@
2
2
 
3
3
  require 'bolt/plugin/puppetdb'
4
4
  require 'bolt/plugin/terraform'
5
+ require 'bolt/plugin/pkcs7'
5
6
  require 'bolt/plugin/prompt'
6
7
 
7
8
  module Bolt
@@ -10,7 +11,8 @@ module Bolt
10
11
  plugins = new(config)
11
12
  plugins.add_plugin(Bolt::Plugin::Puppetdb.new(pdb_client))
12
13
  plugins.add_plugin(Bolt::Plugin::Terraform.new)
13
- plugins.add_plugin(Bolt::Plugin::Prompt)
14
+ plugins.add_plugin(Bolt::Plugin::Prompt.new)
15
+ plugins.add_plugin(Bolt::Plugin::Pkcs7.new(config.boltdir.path, config.plugins['pkcs7'] || {}))
14
16
  plugins
15
17
  end
16
18
 
@@ -0,0 +1,100 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'bolt/secret/base'
4
+ require 'fileutils'
5
+
6
+ module Bolt
7
+ class Plugin
8
+ class Pkcs7 < Bolt::Secret::Base
9
+ def self.validate_config(config)
10
+ known_keys = ['private-key', 'public-key', 'keysize']
11
+ known_keys.each do |key|
12
+ unless key.is_a? String
13
+ raise Bolt::ValidationError, "Invalid config for pkcs7 plugin: '#{key}' is not a String"
14
+ end
15
+ end
16
+
17
+ config.keys.each do |key|
18
+ unless known_keys.include?(key)
19
+ raise Bolt::ValidationError, "Unpexpected key in pkcs7 plugin config: #{key}"
20
+ end
21
+ end
22
+ end
23
+
24
+ def name
25
+ 'pkcs7'
26
+ end
27
+
28
+ def initialize(boltdir, options)
29
+ self.class.validate_config(options)
30
+ require 'openssl'
31
+ @boltdir = boltdir
32
+ @options = options || {}
33
+ @logger = Logging.logger[self]
34
+ end
35
+
36
+ def private_key_path
37
+ path = @options['private-key'] || 'keys/private_key.pkcs7.pem'
38
+ path = File.absolute_path(path, @boltdir)
39
+ @logger.debug("Using private-key: #{path}")
40
+ path
41
+ end
42
+
43
+ def private_key
44
+ @private_key ||= OpenSSL::PKey::RSA.new(File.read(private_key_path))
45
+ end
46
+
47
+ def public_key_path
48
+ path = @options['public-key'] || 'keys/public_key.pkcs7.pem'
49
+ path = File.absolute_path(path, @boltdir)
50
+ @logger.debug("Using public-key: #{path}")
51
+ path
52
+ end
53
+
54
+ def public_key
55
+ @public_key ||= OpenSSL::X509::Certificate.new(File.read(public_key_path))
56
+ end
57
+
58
+ def keysize
59
+ @options['keysize'] || 2048
60
+ end
61
+
62
+ # The following implementations are intended to be compatible with hiera-eyaml
63
+ def encrypt_value(plaintext)
64
+ cipher = OpenSSL::Cipher::AES.new(256, :CBC)
65
+ OpenSSL::PKCS7.encrypt([public_key], plaintext, cipher, OpenSSL::PKCS7::BINARY).to_der
66
+ end
67
+
68
+ def decrypt_value(ciphertext)
69
+ pkcs7 = OpenSSL::PKCS7.new(ciphertext)
70
+ pkcs7.decrypt(private_key, public_key)
71
+ end
72
+
73
+ def secret_createkeys
74
+ key = OpenSSL::PKey::RSA.new(keysize)
75
+
76
+ cert = OpenSSL::X509::Certificate.new
77
+ cert.subject = OpenSSL::X509::Name.parse('/')
78
+ cert.serial = 1
79
+ cert.version = 2
80
+ cert.not_before = Time.now
81
+ cert.not_after = Time.now + 50 * 365 * 24 * 60 * 60
82
+ cert.public_key = key.public_key
83
+ cert.sign(key, OpenSSL::Digest.new('SHA512'))
84
+
85
+ @logger.warn("Overwriting private-key '#{private_key_path}'") if File.exist?(private_key_path)
86
+ @logger.warn("Overwriting public-key '#{public_key_path}'") if File.exist?(public_key_path)
87
+
88
+ private_keydir = File.dirname(private_key_path)
89
+ FileUtils.mkdir_p(private_keydir) unless File.exist?(private_keydir)
90
+ FileUtils.touch(private_key_path)
91
+ File.chmod(0o600, private_key_path)
92
+ File.write(private_key_path, key.to_pem)
93
+
94
+ public_keydir = File.dirname(public_key_path)
95
+ FileUtils.mkdir_p(public_keydir) unless File.exist?(public_keydir)
96
+ File.write(public_key_path, cert.to_pem)
97
+ end
98
+ end
99
+ end
100
+ end
@@ -9,7 +9,7 @@ module Bolt
9
9
  @logger = Logging.logger[self]
10
10
  end
11
11
 
12
- def self.name
12
+ def name
13
13
  'prompt'
14
14
  end
15
15
 
@@ -17,15 +17,15 @@ module Bolt
17
17
  ['inventory_config_lookup']
18
18
  end
19
19
 
20
- def inventory_config_lookup(opts)
20
+ def validate_inventory_config_lookup(opts)
21
21
  raise Bolt::ValidationError, "Prompt requires a 'message'" unless opts['message']
22
- # Return a delay to only be evaluated when needed
23
- Concurrent::Delay.new do
24
- STDOUT.print "#{opts['message']}:"
25
- value = STDIN.noecho(&:gets).chomp
26
- STDOUT.puts
27
- value
28
- end
22
+ end
23
+
24
+ def inventory_config_lookup(opts)
25
+ STDOUT.print "#{opts['message']}:"
26
+ value = STDIN.noecho(&:gets).chomp
27
+ STDOUT.puts
28
+ value
29
29
  end
30
30
  end
31
31
  end
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Bolt
4
+ class Secret
5
+ def self.execute(plugins, outputter, options)
6
+ enc = plugins.by_name('pkcs7')
7
+ case options[:action]
8
+ when 'createkeys'
9
+ enc.secret_createkeys
10
+ when 'encrypt'
11
+ outputter.print_message(enc.secret_encrypt('plaintext-value' => options[:object]))
12
+ when 'decrypt'
13
+ outputter.print_message(enc.secret_decrypt('encrypted-value' => options[:object]))
14
+ end
15
+
16
+ 0
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,41 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Bolt
4
+ class Secret
5
+ class Base
6
+ def hooks
7
+ %w[inventory_config_lookup encrypt decrypt create_keys]
8
+ end
9
+
10
+ def encode(raw)
11
+ coded = Base64.encode64(raw).strip
12
+ "ENC[#{name.upcase},#{coded}]"
13
+ end
14
+
15
+ def decode(code)
16
+ format = %r{\AENC\[(?<plugin>\w+),(?<encoded>[\w\s+-=/]+)\]\s*\z}
17
+ match = format.match(code)
18
+
19
+ raise Bolt::ValidationError, "Could not parse as an encrypted value: #{code}" unless match
20
+
21
+ raw = Base64.decode64(match[:encoded])
22
+ [raw, match[:plugin]]
23
+ end
24
+
25
+ def secret_encrypt(opts)
26
+ encrypted = encrypt_value(opts['plaintext-value'])
27
+ encode(encrypted)
28
+ end
29
+
30
+ def secret_decrypt(opts)
31
+ raw, _plugin = decode(opts['encrypted-value'])
32
+ decrypt_value(raw)
33
+ end
34
+ alias inventory_config_lookup secret_decrypt
35
+
36
+ def validate_inventory_config_lookup(opts)
37
+ decode(opts['encrypted-value'])
38
+ end
39
+ end
40
+ end
41
+ end
data/lib/bolt/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Bolt
4
- VERSION = '1.23.0'
4
+ VERSION = '1.24.0'
5
5
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: bolt
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.23.0
4
+ version: 1.24.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Puppet
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2019-06-14 00:00:00.000000000 Z
11
+ date: 2019-06-21 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: addressable
@@ -274,28 +274,28 @@ dependencies:
274
274
  requirements:
275
275
  - - "~>"
276
276
  - !ruby/object:Gem::Version
277
- version: '2.6'
277
+ version: '2.7'
278
278
  type: :development
279
279
  prerelease: false
280
280
  version_requirements: !ruby/object:Gem::Requirement
281
281
  requirements:
282
282
  - - "~>"
283
283
  - !ruby/object:Gem::Version
284
- version: '2.6'
284
+ version: '2.7'
285
285
  - !ruby/object:Gem::Dependency
286
286
  name: rake
287
287
  requirement: !ruby/object:Gem::Requirement
288
288
  requirements:
289
289
  - - "~>"
290
290
  - !ruby/object:Gem::Version
291
- version: '10.0'
291
+ version: '12.0'
292
292
  type: :development
293
293
  prerelease: false
294
294
  version_requirements: !ruby/object:Gem::Requirement
295
295
  requirements:
296
296
  - - "~>"
297
297
  - !ruby/object:Gem::Version
298
- version: '10.0'
298
+ version: '12.0'
299
299
  - !ruby/object:Gem::Dependency
300
300
  name: rspec
301
301
  requirement: !ruby/object:Gem::Requirement
@@ -389,6 +389,7 @@ files:
389
389
  - lib/bolt/pal/yaml_plan/transpiler.rb
390
390
  - lib/bolt/plan_result.rb
391
391
  - lib/bolt/plugin.rb
392
+ - lib/bolt/plugin/pkcs7.rb
392
393
  - lib/bolt/plugin/prompt.rb
393
394
  - lib/bolt/plugin/puppetdb.rb
394
395
  - lib/bolt/plugin/terraform.rb
@@ -399,6 +400,8 @@ files:
399
400
  - lib/bolt/rerun.rb
400
401
  - lib/bolt/result.rb
401
402
  - lib/bolt/result_set.rb
403
+ - lib/bolt/secret.rb
404
+ - lib/bolt/secret/base.rb
402
405
  - lib/bolt/target.rb
403
406
  - lib/bolt/task.rb
404
407
  - lib/bolt/task/puppet_server.rb