bolt 2.44.0 → 3.4.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.

Files changed (80) hide show
  1. checksums.yaml +4 -4
  2. data/Puppetfile +11 -9
  3. data/bolt-modules/boltlib/lib/puppet/functions/add_facts.rb +1 -1
  4. data/bolt-modules/boltlib/lib/puppet/functions/apply_prep.rb +25 -0
  5. data/bolt-modules/boltlib/lib/puppet/functions/run_command.rb +20 -2
  6. data/bolt-modules/boltlib/lib/puppet/functions/run_plan.rb +2 -2
  7. data/bolt-modules/boltlib/lib/puppet/functions/run_script.rb +44 -5
  8. data/bolt-modules/boltlib/lib/puppet/functions/upload_file.rb +1 -1
  9. data/bolt-modules/boltlib/lib/puppet/functions/wait_until_available.rb +7 -3
  10. data/bolt-modules/file/lib/puppet/functions/file/read.rb +3 -2
  11. data/bolt-modules/prompt/lib/puppet/functions/prompt.rb +20 -2
  12. data/bolt-modules/prompt/lib/puppet/functions/prompt/menu.rb +103 -0
  13. data/lib/bolt/apply_result.rb +1 -1
  14. data/lib/bolt/bolt_option_parser.rb +9 -123
  15. data/lib/bolt/cli.rb +125 -127
  16. data/lib/bolt/config.rb +39 -214
  17. data/lib/bolt/config/options.rb +34 -125
  18. data/lib/bolt/config/transport/local.rb +1 -0
  19. data/lib/bolt/config/transport/lxd.rb +23 -0
  20. data/lib/bolt/config/transport/options.rb +9 -2
  21. data/lib/bolt/executor.rb +20 -5
  22. data/lib/bolt/logger.rb +9 -1
  23. data/lib/bolt/module_installer.rb +2 -2
  24. data/lib/bolt/module_installer/puppetfile.rb +2 -2
  25. data/lib/bolt/module_installer/specs/forge_spec.rb +2 -2
  26. data/lib/bolt/module_installer/specs/git_spec.rb +2 -2
  27. data/lib/bolt/node/output.rb +14 -4
  28. data/lib/bolt/outputter/human.rb +52 -24
  29. data/lib/bolt/outputter/json.rb +16 -16
  30. data/lib/bolt/pal.rb +26 -5
  31. data/lib/bolt/pal/yaml_plan.rb +1 -2
  32. data/lib/bolt/pal/yaml_plan/evaluator.rb +5 -153
  33. data/lib/bolt/pal/yaml_plan/step.rb +91 -52
  34. data/lib/bolt/pal/yaml_plan/step/command.rb +21 -13
  35. data/lib/bolt/pal/yaml_plan/step/download.rb +15 -16
  36. data/lib/bolt/pal/yaml_plan/step/eval.rb +11 -11
  37. data/lib/bolt/pal/yaml_plan/step/message.rb +13 -4
  38. data/lib/bolt/pal/yaml_plan/step/plan.rb +19 -15
  39. data/lib/bolt/pal/yaml_plan/step/resources.rb +82 -21
  40. data/lib/bolt/pal/yaml_plan/step/script.rb +36 -17
  41. data/lib/bolt/pal/yaml_plan/step/task.rb +19 -16
  42. data/lib/bolt/pal/yaml_plan/step/upload.rb +16 -17
  43. data/lib/bolt/pal/yaml_plan/transpiler.rb +3 -3
  44. data/lib/bolt/plan_creator.rb +1 -1
  45. data/lib/bolt/plugin/module.rb +0 -23
  46. data/lib/bolt/plugin/puppet_connect_data.rb +45 -3
  47. data/lib/bolt/project.rb +16 -56
  48. data/lib/bolt/project_manager.rb +5 -4
  49. data/lib/bolt/project_manager/module_migrator.rb +7 -6
  50. data/lib/bolt/result.rb +10 -11
  51. data/lib/bolt/shell.rb +16 -0
  52. data/lib/bolt/shell/bash.rb +61 -31
  53. data/lib/bolt/shell/bash/tmpdir.rb +2 -2
  54. data/lib/bolt/shell/powershell.rb +35 -14
  55. data/lib/bolt/shell/powershell/snippets.rb +37 -150
  56. data/lib/bolt/task.rb +1 -1
  57. data/lib/bolt/transport/base.rb +0 -9
  58. data/lib/bolt/transport/docker.rb +1 -125
  59. data/lib/bolt/transport/docker/connection.rb +86 -161
  60. data/lib/bolt/transport/local.rb +1 -9
  61. data/lib/bolt/transport/lxd.rb +26 -0
  62. data/lib/bolt/transport/lxd/connection.rb +99 -0
  63. data/lib/bolt/transport/orch.rb +13 -5
  64. data/lib/bolt/transport/ssh/connection.rb +1 -1
  65. data/lib/bolt/transport/winrm/connection.rb +1 -1
  66. data/lib/bolt/util.rb +8 -0
  67. data/lib/bolt/version.rb +1 -1
  68. data/lib/bolt_server/transport_app.rb +61 -33
  69. data/lib/bolt_spec/bolt_context.rb +9 -4
  70. data/lib/bolt_spec/plans.rb +1 -109
  71. data/lib/bolt_spec/plans/action_stubs.rb +1 -1
  72. data/lib/bolt_spec/plans/action_stubs/command_stub.rb +8 -1
  73. data/lib/bolt_spec/plans/action_stubs/script_stub.rb +8 -1
  74. data/lib/bolt_spec/plans/mock_executor.rb +4 -0
  75. data/modules/aggregate/plans/count.pp +21 -0
  76. data/modules/aggregate/plans/targets.pp +21 -0
  77. data/modules/puppet_connect/plans/test_input_data.pp +67 -0
  78. data/modules/puppetdb_fact/plans/init.pp +10 -0
  79. metadata +7 -3
  80. data/modules/aggregate/plans/nodes.pp +0 -36
@@ -17,6 +17,7 @@ module Bolt
17
17
  OPTIONS = WINDOWS_OPTIONS.dup.concat(RUN_AS_OPTIONS).sort.freeze
18
18
 
19
19
  DEFAULTS = {
20
+ 'bundled-ruby' => true,
20
21
  'cleanup' => true
21
22
  }.freeze
22
23
 
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'bolt/error'
4
+ require 'bolt/config/transport/base'
5
+
6
+ module Bolt
7
+ class Config
8
+ module Transport
9
+ class LXD < Base
10
+ OPTIONS = %w[
11
+ cleanup
12
+ remote
13
+ tmpdir
14
+ ].freeze
15
+
16
+ DEFAULTS = {
17
+ 'cleanup' => true,
18
+ 'remote' => 'local'
19
+ }.freeze
20
+ end
21
+ end
22
+ end
23
+ end
@@ -21,7 +21,7 @@ module Bolt
21
21
  type: [TrueClass, FalseClass],
22
22
  _plugin: false,
23
23
  _example: true,
24
- _default: false
24
+ _default: true
25
25
  },
26
26
  "cacert" => {
27
27
  type: String,
@@ -32,7 +32,7 @@ module Bolt
32
32
  "cleanup" => {
33
33
  type: [TrueClass, FalseClass],
34
34
  description: "Whether to clean up temporary files created on targets. When running commands on a target, "\
35
- "Bolt may create temporary files. After completing the command, these files are "\
35
+ "Bolt might create temporary files. After completing the command, these files are "\
36
36
  "automatically deleted. This value can be set to 'false' if you wish to leave these "\
37
37
  "temporary files on the target.",
38
38
  _plugin: true,
@@ -266,6 +266,13 @@ module Bolt
266
266
  _plugin: true,
267
267
  _example: "BOLT.PRODUCTION"
268
268
  },
269
+ "remote" => {
270
+ type: String,
271
+ description: "The LXD remote host to use.",
272
+ _default: "local",
273
+ _plugin: false,
274
+ _example: 'myremote'
275
+ },
269
276
  "run-as" => {
270
277
  type: String,
271
278
  description: "The user to run commands as after login. The run-as user must be different than the "\
data/lib/bolt/executor.rb CHANGED
@@ -4,6 +4,7 @@
4
4
  require 'English'
5
5
  require 'json'
6
6
  require 'logging'
7
+ require 'pathname'
7
8
  require 'set'
8
9
  require 'bolt/analytics'
9
10
  require 'bolt/result'
@@ -15,6 +16,7 @@ require 'bolt/transport/ssh'
15
16
  require 'bolt/transport/winrm'
16
17
  require 'bolt/transport/orch'
17
18
  require 'bolt/transport/local'
19
+ require 'bolt/transport/lxd'
18
20
  require 'bolt/transport/docker'
19
21
  require 'bolt/transport/remote'
20
22
  require 'bolt/yarn'
@@ -25,6 +27,7 @@ module Bolt
25
27
  winrm: Bolt::Transport::WinRM,
26
28
  pcp: Bolt::Transport::Orch,
27
29
  local: Bolt::Transport::Local,
30
+ lxd: Bolt::Transport::LXD,
28
31
  docker: Bolt::Transport::Docker,
29
32
  remote: Bolt::Transport::Remote
30
33
  }.freeze
@@ -39,7 +42,6 @@ module Bolt
39
42
  modified_concurrency = false)
40
43
  # lazy-load expensive gem code
41
44
  require 'concurrent'
42
-
43
45
  @analytics = analytics
44
46
  @logger = Bolt::Logger.logger(self)
45
47
 
@@ -121,8 +123,8 @@ module Bolt
121
123
  def queue_execute(targets)
122
124
  if @warn_concurrency && targets.length > @concurrency
123
125
  @warn_concurrency = false
124
- msg = "The ulimit is low, which may cause file limit issues. Default concurrency has been set to "\
125
- "'#{@concurrency}' to mitigate those issues, which may cause Bolt to run slow. "\
126
+ msg = "The ulimit is low, which might cause file limit issues. Default concurrency has been set to "\
127
+ "'#{@concurrency}' to mitigate those issues, which might cause Bolt to run slow. "\
126
128
  "Disable this warning by configuring ulimit using 'ulimit -n <limit>' in your shell "\
127
129
  "configuration, or by configuring Bolt's concurrency. "\
128
130
  "See https://puppet.com/docs/bolt/latest/bolt_known_issues.html for details."
@@ -231,6 +233,11 @@ module Bolt
231
233
  @analytics.report_bundled_content(mode, name)
232
234
  end
233
235
 
236
+ def report_file_source(plan_function, source)
237
+ label = Pathname.new(source).absolute? ? 'absolute' : 'module'
238
+ @analytics&.event('Plan', plan_function, label: label)
239
+ end
240
+
234
241
  def report_apply(statement_count, resource_counts)
235
242
  data = { statement_count: statement_count }
236
243
 
@@ -477,22 +484,30 @@ module Bolt
477
484
  end
478
485
 
479
486
  def prompt(prompt, options)
480
- @prompting = true
481
487
  unless $stdin.tty?
488
+ return options[:default] if options[:default]
482
489
  raise Bolt::Error.new('STDIN is not a tty, unable to prompt', 'bolt/no-tty-error')
483
490
  end
484
491
 
485
- $stderr.print("#{prompt}: ")
492
+ @prompting = true
493
+
494
+ if options[:default] && !options[:sensitive]
495
+ $stderr.print("#{prompt} [#{options[:default]}]: ")
496
+ else
497
+ $stderr.print("#{prompt}: ")
498
+ end
486
499
 
487
500
  value = if options[:sensitive]
488
501
  $stdin.noecho(&:gets).to_s.chomp
489
502
  else
490
503
  $stdin.gets.to_s.chomp
491
504
  end
505
+
492
506
  @prompting = false
493
507
 
494
508
  $stderr.puts if options[:sensitive]
495
509
 
510
+ value = options[:default] if value.empty?
496
511
  value
497
512
  end
498
513
 
data/lib/bolt/logger.rb CHANGED
@@ -4,7 +4,7 @@ require 'logging'
4
4
 
5
5
  module Bolt
6
6
  module Logger
7
- LEVELS = %w[trace debug info notice warn error fatal].freeze
7
+ LEVELS = %w[trace debug info warn error fatal].freeze
8
8
 
9
9
  # This module is treated as a global singleton so that multiple classes
10
10
  # in Bolt can log warnings with IDs. Access to the following variables
@@ -91,6 +91,14 @@ module Bolt
91
91
  Logging.logger[:root].appenders.any?
92
92
  end
93
93
 
94
+ def self.stream
95
+ @stream
96
+ end
97
+
98
+ def self.stream=(stream)
99
+ @stream = stream
100
+ end
101
+
94
102
  # A helper to ensure the Logging library is always initialized with our
95
103
  # custom log levels before retrieving a Logger instance.
96
104
  def self.logger(name)
@@ -45,7 +45,7 @@ module Bolt
45
45
  # specss. If that fails, fall back to resolving from project specs.
46
46
  # This prevents Bolt from modifying installed modules unless there is
47
47
  # a version conflict.
48
- @outputter.print_action_step("Resolving module dependencies, this may take a moment")
48
+ @outputter.print_action_step("Resolving module dependencies, this might take a moment")
49
49
 
50
50
  @outputter.start_spin
51
51
  begin
@@ -156,7 +156,7 @@ module Bolt
156
156
  # If forcibly installing or if there is no Puppetfile, resolve
157
157
  # and write a Puppetfile.
158
158
  if force || !path.exist?
159
- @outputter.print_action_step("Resolving module dependencies, this may take a moment")
159
+ @outputter.print_action_step("Resolving module dependencies, this might take a moment")
160
160
 
161
161
  # This doesn't use the block as it's more testable to just mock *_spin
162
162
  @outputter.start_spin
@@ -36,7 +36,7 @@ module Bolt
36
36
  raise Bolt::ValidationError, <<~MSG
37
37
  Unable to parse Puppetfile #{path}:
38
38
  #{parsed.validation_errors.join("\n\n")}.
39
- This may not be a Puppetfile managed by Bolt.
39
+ This Puppetfile might not be managed by Bolt.
40
40
  MSG
41
41
  end
42
42
 
@@ -106,7 +106,7 @@ module Bolt
106
106
 
107
107
  #{unsatisfied_specs.map(&:to_hash).to_yaml.lines.drop(1).join.chomp}
108
108
 
109
- This may not be a Puppetfile managed by Bolt. To forcibly overwrite the
109
+ This Puppetfile might not be managed by Bolt. To forcibly overwrite the
110
110
  Puppetfile, run '#{command}'.
111
111
  MESSAGE
112
112
 
@@ -39,8 +39,8 @@ module Bolt
39
39
  unless (match = name.match(NAME_REGEX))
40
40
  raise Bolt::ValidationError,
41
41
  "Invalid name for Forge module specification: #{name}. Name must match "\
42
- "'owner/name'. Owner segment may only include letters or digits. Name "\
43
- "segment must start with a lowercase letter and may only include lowercase "\
42
+ "'owner/name'. Owner segment can only include letters or digits. Name "\
43
+ "segment must start with a lowercase letter and can only include lowercase "\
44
44
  "letters, digits, and underscores."
45
45
  end
46
46
 
@@ -49,8 +49,8 @@ module Bolt
49
49
  unless (match = name.match(NAME_REGEX))
50
50
  raise Bolt::ValidationError,
51
51
  "Invalid name for Git module specification: #{name}. Name must match "\
52
- "'name' or 'owner/name'. Owner segment may only include letters or digits. "\
53
- "Name segment must start with a lowercase letter and may only include "\
52
+ "'name' or 'owner/name'. Owner segment can only include letters or digits. "\
53
+ "Name segment must start with a lowercase letter and can only include "\
54
54
  "lowercase letters, digits, and underscores."
55
55
  end
56
56
 
@@ -6,13 +6,23 @@ require 'bolt/result'
6
6
  module Bolt
7
7
  class Node
8
8
  class Output
9
- attr_reader :stdout, :stderr
9
+ attr_reader :stderr, :stdout, :merged_output
10
10
  attr_accessor :exit_code
11
11
 
12
12
  def initialize
13
- @stdout = StringIO.new
14
- @stderr = StringIO.new
15
- @exit_code = 'unknown'
13
+ @stdout = StringIO.new
14
+ @stderr = StringIO.new
15
+ @merged_output = StringIO.new
16
+ @exit_code = 'unknown'
17
+ end
18
+
19
+ def to_h
20
+ {
21
+ 'stdout' => @stdout.string,
22
+ 'stderr' => @stderr.string,
23
+ 'merged_output' => @merged_output.string,
24
+ 'exit_code' => @exit_code
25
+ }
16
26
  end
17
27
  end
18
28
  end
@@ -53,10 +53,21 @@ module Bolt
53
53
  string.sub(/\s\z/, '')
54
54
  end
55
55
 
56
+ # Wraps a string to the specified width. Lines only wrap
57
+ # at whitespace.
58
+ #
56
59
  def wrap(string, width = 80)
60
+ return string unless string.is_a?(String)
57
61
  string.gsub(/(.{1,#{width}})(\s+|\Z)/, "\\1\n")
58
62
  end
59
63
 
64
+ # Trims a string to a specified width, adding an ellipsis if it's longer.
65
+ #
66
+ def truncate(string, width = 80)
67
+ return string unless string.is_a?(String) && string.length > width
68
+ string.lines.first[0...width].gsub(/\s\w+\s*$/, '...')
69
+ end
70
+
60
71
  def handle_event(event)
61
72
  case event[:type]
62
73
  when :enable_default_output
@@ -131,16 +142,9 @@ module Bolt
131
142
  end
132
143
 
133
144
  # Use special handling if the result looks like a command or script result
134
- if result.generic_value.keys == %w[stdout stderr exit_code]
145
+ if result.generic_value.keys == %w[stdout stderr merged_output exit_code]
135
146
  safe_value = result.safe_value
136
- unless safe_value['stdout'].strip.empty?
137
- @stream.puts(indent(2, "STDOUT:"))
138
- @stream.puts(indent(4, safe_value['stdout']))
139
- end
140
- unless safe_value['stderr'].strip.empty?
141
- @stream.puts(indent(2, "STDERR:"))
142
- @stream.puts(indent(4, safe_value['stderr']))
143
- end
147
+ @stream.puts(indent(2, safe_value['merged_output'])) unless safe_value['merged_output'].strip.empty?
144
148
  elsif result.generic_value.any?
145
149
  @stream.puts(indent(2, ::JSON.pretty_generate(result.generic_value)))
146
150
  end
@@ -218,11 +222,11 @@ module Bolt
218
222
  @stream.puts total_msg
219
223
  end
220
224
 
221
- def print_table(results, padding_left = 0, padding_right = 3)
225
+ def format_table(results, padding_left = 0, padding_right = 3)
222
226
  # lazy-load expensive gem code
223
227
  require 'terminal-table'
224
228
 
225
- @stream.puts Terminal::Table.new(
229
+ Terminal::Table.new(
226
230
  rows: results,
227
231
  style: {
228
232
  border_x: '',
@@ -238,10 +242,22 @@ module Bolt
238
242
 
239
243
  def print_tasks(tasks, modulepath)
240
244
  command = Bolt::Util.powershell? ? 'Get-BoltTask -Task <TASK NAME>' : 'bolt task show <TASK NAME>'
241
- tasks.any? ? print_table(tasks) : print_message('No available tasks')
242
- print_message("\nMODULEPATH:\n#{modulepath.join(File::PATH_SEPARATOR)}\n"\
243
- "\nUse '#{command}' to view "\
244
- "details and parameters for a specific task.")
245
+
246
+ tasks = tasks.map do |name, description|
247
+ description = truncate(description, 72)
248
+ [name, description]
249
+ end
250
+
251
+ @stream.puts colorize(:cyan, 'Tasks')
252
+ @stream.puts tasks.any? ? format_table(tasks, 2) : indent(2, 'No available tasks')
253
+ @stream.puts
254
+
255
+ @stream.puts colorize(:cyan, 'Modulepath')
256
+ @stream.puts indent(2, modulepath.join(File::PATH_SEPARATOR))
257
+ @stream.puts
258
+
259
+ @stream.puts colorize(:cyan, 'Additional information')
260
+ @stream.puts indent(2, "Use '#{command}' to view details and parameters for a specific task.")
245
261
  end
246
262
 
247
263
  # @param [Hash] task A hash representing the task
@@ -259,7 +275,7 @@ module Bolt
259
275
  pretty_params << "- #{k}: #{v['type'] || 'Any'}\n"
260
276
  pretty_params << " Default: #{v['default'].inspect}\n" if v.key?('default')
261
277
  pretty_params << " #{v['description']}\n" if v['description']
262
- usage << if v['type'].start_with?("Optional")
278
+ usage << if v['type']&.start_with?("Optional")
263
279
  " [#{k}=<value>]"
264
280
  else
265
281
  " #{k}=<value>"
@@ -267,7 +283,7 @@ module Bolt
267
283
  end
268
284
 
269
285
  if task.supports_noop
270
- usage << Bolt::Util.powershell? ? '[-Noop]' : '[--noop]'
286
+ usage << (Bolt::Util.powershell? ? ' [-Noop]' : ' [--noop]')
271
287
  end
272
288
 
273
289
  task_info << "\n#{task.name}"
@@ -322,10 +338,22 @@ module Bolt
322
338
 
323
339
  def print_plans(plans, modulepath)
324
340
  command = Bolt::Util.powershell? ? 'Get-BoltPlan -Name <PLAN NAME>' : 'bolt plan show <PLAN NAME>'
325
- plans.any? ? print_table(plans) : print_message('No available plans')
326
- print_message("\nMODULEPATH:\n#{modulepath.join(File::PATH_SEPARATOR)}\n"\
327
- "\nUse '#{command}' to view "\
328
- "details and parameters for a specific plan.")
341
+
342
+ plans = plans.map do |name, description|
343
+ description = truncate(description, 72)
344
+ [name, description]
345
+ end
346
+
347
+ @stream.puts colorize(:cyan, 'Plans')
348
+ @stream.puts plans.any? ? format_table(plans, 2) : indent(2, 'No available plans')
349
+ @stream.puts
350
+
351
+ @stream.puts colorize(:cyan, 'Modulepath')
352
+ @stream.puts indent(2, modulepath.join(File::PATH_SEPARATOR))
353
+ @stream.puts
354
+
355
+ @stream.puts colorize(:cyan, 'Additional information')
356
+ @stream.puts indent(2, "Use '#{command}' to view details and parameters for a specific plan.")
329
357
  end
330
358
 
331
359
  def print_topics(topics)
@@ -359,7 +387,7 @@ module Bolt
359
387
  [m[:name], version]
360
388
  end
361
389
 
362
- print_table(module_info, 2, 1)
390
+ @stream.puts format_table(module_info, 2, 1)
363
391
  end
364
392
 
365
393
  @stream.write("\n")
@@ -374,7 +402,7 @@ module Bolt
374
402
  targets += target_list[:adhoc].map { |target| [target.name, adhoc] }
375
403
 
376
404
  if targets.any?
377
- print_table(targets, 0, 2)
405
+ @stream.puts format_table(targets, 0, 2)
378
406
  @stream.puts
379
407
  end
380
408
 
@@ -392,7 +420,7 @@ module Bolt
392
420
 
393
421
  def print_target_info(targets)
394
422
  @stream.puts ::JSON.pretty_generate(
395
- "targets": targets.map(&:detail)
423
+ targets: targets.map(&:detail)
396
424
  )
397
425
  count = "#{targets.count} target#{'s' unless targets.count == 1}"
398
426
  @stream.puts colorize(:green, count)
@@ -95,38 +95,38 @@ module Bolt
95
95
  end
96
96
 
97
97
  def print_puppetfile_result(success, puppetfile, moduledir)
98
- @stream.puts({ "success": success,
99
- "puppetfile": puppetfile,
100
- "moduledir": moduledir.to_s }.to_json)
98
+ @stream.puts({ success: success,
99
+ puppetfile: puppetfile,
100
+ moduledir: moduledir.to_s }.to_json)
101
101
  end
102
102
 
103
103
  def print_targets(target_list, inventoryfile)
104
104
  @stream.puts ::JSON.pretty_generate(
105
- "inventory": {
106
- "targets": target_list[:inventory].map(&:name),
107
- "count": target_list[:inventory].count,
108
- "file": inventoryfile.to_s
105
+ inventory: {
106
+ targets: target_list[:inventory].map(&:name),
107
+ count: target_list[:inventory].count,
108
+ file: inventoryfile.to_s
109
109
  },
110
- "adhoc": {
111
- "targets": target_list[:adhoc].map(&:name),
112
- "count": target_list[:adhoc].count
110
+ adhoc: {
111
+ targets: target_list[:adhoc].map(&:name),
112
+ count: target_list[:adhoc].count
113
113
  },
114
- "targets": target_list.values.flatten.map(&:name),
115
- "count": target_list.values.flatten.count
114
+ targets: target_list.values.flatten.map(&:name),
115
+ count: target_list.values.flatten.count
116
116
  )
117
117
  end
118
118
 
119
119
  def print_target_info(targets)
120
120
  @stream.puts ::JSON.pretty_generate(
121
- "targets": targets.map(&:detail),
122
- "count": targets.count
121
+ targets: targets.map(&:detail),
122
+ count: targets.count
123
123
  )
124
124
  end
125
125
 
126
126
  def print_groups(groups)
127
127
  count = groups.count
128
- @stream.puts({ "groups": groups,
129
- "count": count }.to_json)
128
+ @stream.puts({ groups: groups,
129
+ count: count }.to_json)
130
130
  end
131
131
 
132
132
  def fatal_error(err)