bolt 2.40.1 → 3.0.1
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 +4 -4
- data/Puppetfile +17 -17
- data/bolt-modules/boltlib/lib/puppet/functions/apply_prep.rb +25 -0
- data/bolt-modules/boltlib/lib/puppet/functions/parallelize.rb +6 -8
- data/bolt-modules/boltlib/lib/puppet/functions/wait_until_available.rb +7 -3
- data/lib/bolt/analytics.rb +3 -2
- data/lib/bolt/applicator.rb +11 -1
- data/lib/bolt/bolt_option_parser.rb +3 -113
- data/lib/bolt/catalog.rb +10 -29
- data/lib/bolt/cli.rb +58 -157
- data/lib/bolt/config.rb +62 -239
- data/lib/bolt/config/options.rb +58 -97
- data/lib/bolt/config/transport/local.rb +1 -0
- data/lib/bolt/config/transport/options.rb +8 -1
- data/lib/bolt/config/transport/orch.rb +1 -0
- data/lib/bolt/executor.rb +15 -5
- data/lib/bolt/inventory.rb +3 -2
- data/lib/bolt/inventory/group.rb +35 -4
- data/lib/bolt/inventory/inventory.rb +1 -1
- data/lib/bolt/logger.rb +115 -11
- data/lib/bolt/module.rb +10 -2
- data/lib/bolt/module_installer.rb +4 -2
- data/lib/bolt/module_installer/resolver.rb +65 -12
- data/lib/bolt/module_installer/specs/forge_spec.rb +8 -2
- data/lib/bolt/module_installer/specs/git_spec.rb +17 -2
- data/lib/bolt/outputter/human.rb +9 -5
- data/lib/bolt/outputter/json.rb +16 -16
- data/lib/bolt/outputter/rainbow.rb +3 -3
- data/lib/bolt/pal.rb +93 -14
- data/lib/bolt/pal/yaml_plan.rb +8 -2
- data/lib/bolt/pal/yaml_plan/evaluator.rb +7 -19
- data/lib/bolt/pal/yaml_plan/step.rb +3 -24
- data/lib/bolt/pal/yaml_plan/step/upload.rb +2 -2
- data/lib/bolt/pal/yaml_plan/transpiler.rb +6 -1
- data/lib/bolt/plugin.rb +3 -3
- data/lib/bolt/plugin/cache.rb +7 -7
- data/lib/bolt/plugin/module.rb +0 -23
- data/lib/bolt/plugin/puppet_connect_data.rb +77 -0
- data/lib/bolt/plugin/puppetdb.rb +1 -1
- data/lib/bolt/project.rb +54 -81
- data/lib/bolt/project_manager.rb +4 -3
- data/lib/bolt/project_manager/module_migrator.rb +6 -5
- data/lib/bolt/rerun.rb +1 -1
- data/lib/bolt/shell/bash.rb +1 -1
- data/lib/bolt/shell/bash/tmpdir.rb +4 -1
- data/lib/bolt/shell/powershell.rb +3 -4
- data/lib/bolt/shell/powershell/snippets.rb +9 -149
- data/lib/bolt/task.rb +1 -1
- data/lib/bolt/transport/docker/connection.rb +2 -2
- data/lib/bolt/transport/local.rb +1 -9
- data/lib/bolt/transport/orch/connection.rb +1 -1
- data/lib/bolt/transport/ssh.rb +1 -2
- data/lib/bolt/transport/ssh/connection.rb +1 -1
- data/lib/bolt/validator.rb +2 -2
- data/lib/bolt/version.rb +1 -1
- data/lib/bolt_server/config.rb +1 -1
- data/lib/bolt_server/schemas/partials/task.json +1 -1
- data/lib/bolt_server/transport_app.rb +3 -2
- data/libexec/bolt_catalog +1 -1
- data/modules/aggregate/plans/count.pp +21 -0
- data/modules/aggregate/plans/targets.pp +21 -0
- data/modules/puppet_connect/plans/test_input_data.pp +31 -0
- data/modules/puppetdb_fact/plans/init.pp +10 -0
- metadata +27 -18
- data/modules/aggregate/plans/nodes.pp +0 -36
data/lib/bolt/logger.rb
CHANGED
@@ -4,9 +4,15 @@ require 'logging'
|
|
4
4
|
|
5
5
|
module Bolt
|
6
6
|
module Logger
|
7
|
-
LEVELS = %w[trace debug info
|
8
|
-
|
9
|
-
|
7
|
+
LEVELS = %w[trace debug info warn error fatal].freeze
|
8
|
+
|
9
|
+
# This module is treated as a global singleton so that multiple classes
|
10
|
+
# in Bolt can log warnings with IDs. Access to the following variables
|
11
|
+
# are controlled by a mutex.
|
12
|
+
@mutex = Mutex.new
|
13
|
+
@warnings = Set.new
|
14
|
+
@disable_warnings = Set.new
|
15
|
+
@message_queue = []
|
10
16
|
|
11
17
|
# This method provides a single point-of-entry to setup logging for both
|
12
18
|
# the CLI and for tests. This is necessary because we define custom log
|
@@ -36,7 +42,7 @@ module Bolt
|
|
36
42
|
end
|
37
43
|
end
|
38
44
|
|
39
|
-
def self.configure(destinations, color)
|
45
|
+
def self.configure(destinations, color, disable_warnings = nil)
|
40
46
|
root_logger = Bolt::Logger.logger(:root)
|
41
47
|
|
42
48
|
root_logger.add_appenders Logging.appenders.stderr(
|
@@ -73,6 +79,16 @@ module Bolt
|
|
73
79
|
|
74
80
|
appender.level = params[:level] if params[:level]
|
75
81
|
end
|
82
|
+
|
83
|
+
# Set the list of disabled warnings and mark the logger as configured.
|
84
|
+
# Log all messages in the message queue and flush the queue.
|
85
|
+
if disable_warnings
|
86
|
+
@mutex.synchronize { @disable_warnings = disable_warnings }
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
def self.configured?
|
91
|
+
Logging.logger[:root].appenders.any?
|
76
92
|
end
|
77
93
|
|
78
94
|
# A helper to ensure the Logging library is always initialized with our
|
@@ -123,18 +139,106 @@ module Bolt
|
|
123
139
|
Logging.reset
|
124
140
|
end
|
125
141
|
|
126
|
-
|
142
|
+
# The following methods are used in place of the Logging.logger
|
143
|
+
# methods of the same name when logging warning messages or logging
|
144
|
+
# any messages prior to the logger being configured. If the logger
|
145
|
+
# is not configured when any of these methods are called, the message
|
146
|
+
# will be added to a queue, otherwise they are logged immediately.
|
147
|
+
# The message queue is flushed by calling #flush_queue, which is
|
148
|
+
# called from Bolt::CLI after configuring the logger.
|
149
|
+
#
|
150
|
+
def self.warn(id, msg)
|
151
|
+
log(type: :warn, msg: "#{msg} [ID: #{id}]", id: id)
|
152
|
+
end
|
153
|
+
|
154
|
+
def self.warn_once(id, msg)
|
155
|
+
log(type: :warn_once, msg: "#{msg} [ID: #{id}]", id: id)
|
156
|
+
end
|
157
|
+
|
158
|
+
def self.deprecate(id, msg)
|
159
|
+
log(type: :deprecate, msg: "#{msg} [ID: #{id}]", id: id)
|
160
|
+
end
|
161
|
+
|
162
|
+
def self.deprecate_once(id, msg)
|
163
|
+
log(type: :deprecate_once, msg: "#{msg} [ID: #{id}]", id: id)
|
164
|
+
end
|
165
|
+
|
166
|
+
def self.debug(msg)
|
167
|
+
log(type: :debug, msg: msg)
|
168
|
+
end
|
169
|
+
|
170
|
+
def self.info(msg)
|
171
|
+
log(type: :info, msg: msg)
|
172
|
+
end
|
173
|
+
|
174
|
+
# Logs a message. If the logger has not been configured, this will queue
|
175
|
+
# the message to be logged later. Once the logger is configured, the
|
176
|
+
# queue will be flushed of all messages and new messages will be logged
|
177
|
+
# immediately.
|
178
|
+
#
|
179
|
+
# Logging with this method is controlled by a mutex, as the Bolt::Logger
|
180
|
+
# module is treated as a global singleton to allow multiple classes
|
181
|
+
# access to its methods.
|
182
|
+
#
|
183
|
+
private_class_method def self.log(type:, msg:, id: nil)
|
184
|
+
@mutex.synchronize do
|
185
|
+
if configured?
|
186
|
+
log_message(type: type, msg: msg, id: id)
|
187
|
+
else
|
188
|
+
@message_queue << { type: type, msg: msg, id: id }
|
189
|
+
end
|
190
|
+
end
|
191
|
+
end
|
192
|
+
|
193
|
+
# Logs all messages in the message queue and then flushes the queue.
|
194
|
+
#
|
195
|
+
def self.flush_queue
|
127
196
|
@mutex.synchronize do
|
128
|
-
@
|
129
|
-
|
130
|
-
@logger.warn(msg)
|
197
|
+
@message_queue.each do |message|
|
198
|
+
log_message(message)
|
131
199
|
end
|
200
|
+
|
201
|
+
@message_queue.clear
|
202
|
+
end
|
203
|
+
end
|
204
|
+
|
205
|
+
# Handles the actual logging of a message.
|
206
|
+
#
|
207
|
+
private_class_method def self.log_message(type:, msg:, id: nil)
|
208
|
+
case type
|
209
|
+
when :warn
|
210
|
+
do_warn(msg, id)
|
211
|
+
when :warn_once
|
212
|
+
do_warn_once(msg, id)
|
213
|
+
when :deprecate
|
214
|
+
do_deprecate(msg, id)
|
215
|
+
when :deprecate_once
|
216
|
+
do_deprecate_once(msg, id)
|
217
|
+
else
|
218
|
+
logger(self).send(type, msg)
|
132
219
|
end
|
133
220
|
end
|
134
221
|
|
135
|
-
|
136
|
-
|
137
|
-
|
222
|
+
# The following methods do the actual warning.
|
223
|
+
#
|
224
|
+
private_class_method def self.do_warn(msg, id)
|
225
|
+
return if @disable_warnings.include?(id)
|
226
|
+
logger(self).warn(msg)
|
227
|
+
end
|
228
|
+
|
229
|
+
private_class_method def self.do_warn_once(msg, id)
|
230
|
+
return unless @warnings.add?(id)
|
231
|
+
do_warn(msg, id)
|
232
|
+
end
|
233
|
+
|
234
|
+
private_class_method def self.do_deprecate(msg, id)
|
235
|
+
@analytics&.event('Warn', 'deprecation', label: id)
|
236
|
+
do_warn(msg, id)
|
237
|
+
end
|
238
|
+
|
239
|
+
private_class_method def self.do_deprecate_once(msg, id)
|
240
|
+
@analytics&.event('Warn', 'deprecation', label: id)
|
241
|
+
do_warn_once(msg, id)
|
138
242
|
end
|
139
243
|
end
|
140
244
|
end
|
data/lib/bolt/module.rb
CHANGED
@@ -6,8 +6,14 @@ module Bolt
|
|
6
6
|
CONTENT_NAME_REGEX = /\A[a-z][a-z0-9_]*(::[a-z][a-z0-9_]*)*\z/.freeze
|
7
7
|
MODULE_NAME_REGEX = /\A[a-z][a-z0-9_]*\z/.freeze
|
8
8
|
|
9
|
-
def self.discover(modulepath)
|
10
|
-
|
9
|
+
def self.discover(modulepath, project)
|
10
|
+
mods = {}
|
11
|
+
|
12
|
+
if project.load_as_module?
|
13
|
+
mods[project.name] = Bolt::Module.new(project.name, project.path.to_s)
|
14
|
+
end
|
15
|
+
|
16
|
+
modulepath.each do |path|
|
11
17
|
next unless File.exist?(path) && File.directory?(path)
|
12
18
|
Dir.children(path)
|
13
19
|
.map { |dir| File.join(path, dir) }
|
@@ -20,6 +26,8 @@ module Bolt
|
|
20
26
|
end
|
21
27
|
end
|
22
28
|
end
|
29
|
+
|
30
|
+
mods
|
23
31
|
end
|
24
32
|
|
25
33
|
attr_reader :name, :path
|
@@ -195,8 +195,10 @@ module Bolt
|
|
195
195
|
@outputter.stop_spin
|
196
196
|
|
197
197
|
# Automatically generate types after installing modules
|
198
|
-
|
199
|
-
|
198
|
+
if ok
|
199
|
+
@outputter.print_action_step("Generating type references")
|
200
|
+
@pal.generate_types(cache: true)
|
201
|
+
end
|
200
202
|
|
201
203
|
@outputter.print_puppetfile_result(ok, path, moduledir)
|
202
204
|
|
@@ -9,14 +9,19 @@ module Bolt
|
|
9
9
|
class Resolver
|
10
10
|
# Resolves module specs and returns a Puppetfile object.
|
11
11
|
#
|
12
|
-
def resolve(specs,
|
12
|
+
def resolve(specs, config = {})
|
13
13
|
require 'puppetfile-resolver'
|
14
14
|
|
15
15
|
# Build the document model from the specs.
|
16
|
-
document
|
16
|
+
document = PuppetfileResolver::Puppetfile::Document.new('')
|
17
|
+
unresolved = []
|
17
18
|
|
18
19
|
specs.specs.each do |spec|
|
19
|
-
|
20
|
+
if spec.resolve
|
21
|
+
document.add_module(spec.to_resolver_module)
|
22
|
+
else
|
23
|
+
unresolved << spec
|
24
|
+
end
|
20
25
|
end
|
21
26
|
|
22
27
|
# Make sure the document model is valid.
|
@@ -38,29 +43,49 @@ module Bolt
|
|
38
43
|
# raised by puppetfile-resolver and re-raising them as Bolt errors.
|
39
44
|
begin
|
40
45
|
result = resolver.resolve(
|
41
|
-
cache:
|
42
|
-
ui:
|
43
|
-
|
44
|
-
|
46
|
+
cache: nil,
|
47
|
+
ui: nil,
|
48
|
+
allow_missing_modules: false,
|
49
|
+
spec_searcher_configuration: spec_searcher_config(config)
|
45
50
|
)
|
46
51
|
rescue StandardError => e
|
47
52
|
raise Bolt::Error.new(e.message, 'bolt/module-resolver-error')
|
48
53
|
end
|
49
54
|
|
50
|
-
#
|
51
|
-
|
55
|
+
# Create the Puppetfile object.
|
56
|
+
generate_puppetfile(specs, result.specifications.values, unresolved)
|
57
|
+
end
|
58
|
+
|
59
|
+
# Creates a puppetfile-resolver config object.
|
60
|
+
#
|
61
|
+
private def spec_searcher_config(config)
|
62
|
+
PuppetfileResolver::SpecSearchers::Configuration.new.tap do |obj|
|
63
|
+
obj.forge.proxy = config.dig('forge', 'proxy') || config.dig('proxy')
|
64
|
+
obj.git.proxy = config.dig('proxy')
|
65
|
+
obj.forge.forge_api = config.dig('forge', 'baseurl')
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
# Creates a Puppetfile object with Module objects created from resolved and
|
70
|
+
# unresolved specs.
|
71
|
+
#
|
72
|
+
private def generate_puppetfile(specs, resolved, unresolved)
|
73
|
+
modules = []
|
74
|
+
|
75
|
+
# Convert the resolved specs into Bolt module objects.
|
76
|
+
resolved.each do |mod|
|
52
77
|
# Skip over anything that isn't a module spec, such as a Puppet spec.
|
53
78
|
next unless mod.is_a? PuppetfileResolver::Models::ModuleSpecification
|
54
79
|
|
55
80
|
case mod.origin
|
56
81
|
when :forge
|
57
|
-
|
82
|
+
modules << Bolt::ModuleInstaller::Puppetfile::ForgeModule.new(
|
58
83
|
"#{mod.owner}/#{mod.name}",
|
59
84
|
mod.version.to_s
|
60
85
|
)
|
61
86
|
when :git
|
62
87
|
spec = specs.specs.find { |s| s.name == mod.name }
|
63
|
-
|
88
|
+
modules << Bolt::ModuleInstaller::Puppetfile::GitModule.new(
|
64
89
|
spec.name,
|
65
90
|
spec.git,
|
66
91
|
spec.sha
|
@@ -68,7 +93,35 @@ module Bolt
|
|
68
93
|
end
|
69
94
|
end
|
70
95
|
|
71
|
-
#
|
96
|
+
# Error if there are any name conflicts between unresolved specs and
|
97
|
+
# resolved modules. r10k will error if a Puppetfile includes duplicate
|
98
|
+
# names, but we error early here to provide a more helpful message.
|
99
|
+
if (name_conflicts = modules.map(&:name) & unresolved.map(&:name)).any?
|
100
|
+
raise Bolt::Error.new(
|
101
|
+
"Detected unresolved module specifications with the same name as a resolved module "\
|
102
|
+
"dependency: #{name_conflicts.join(', ')}. Either remove the unresolved module specification "\
|
103
|
+
"or set the module with the conflicting dependency to not resolve.",
|
104
|
+
"bolt/module-name-conflict-error"
|
105
|
+
)
|
106
|
+
end
|
107
|
+
|
108
|
+
# Convert the unresolved specs into Bolt module objects.
|
109
|
+
unresolved.each do |spec|
|
110
|
+
case spec.type
|
111
|
+
when :forge
|
112
|
+
modules << Bolt::ModuleInstaller::Puppetfile::ForgeModule.new(
|
113
|
+
spec.full_name,
|
114
|
+
spec.version_requirement
|
115
|
+
)
|
116
|
+
when :git
|
117
|
+
modules << Bolt::ModuleInstaller::Puppetfile::GitModule.new(
|
118
|
+
spec.name,
|
119
|
+
spec.git,
|
120
|
+
spec.ref
|
121
|
+
)
|
122
|
+
end
|
123
|
+
end
|
124
|
+
|
72
125
|
Bolt::ModuleInstaller::Puppetfile.new(modules)
|
73
126
|
end
|
74
127
|
end
|
@@ -13,14 +13,20 @@ module Bolt
|
|
13
13
|
class ForgeSpec
|
14
14
|
NAME_REGEX = %r{\A[a-zA-Z0-9]+[-/](?<name>[a-z][a-z0-9_]*)\z}.freeze
|
15
15
|
REQUIRED_KEYS = Set.new(%w[name]).freeze
|
16
|
-
KNOWN_KEYS = Set.new(%w[name version_requirement]).freeze
|
16
|
+
KNOWN_KEYS = Set.new(%w[name resolve version_requirement]).freeze
|
17
17
|
|
18
|
-
attr_reader :full_name, :name, :semantic_version, :type
|
18
|
+
attr_reader :full_name, :name, :resolve, :semantic_version, :type, :version_requirement
|
19
19
|
|
20
20
|
def initialize(init_hash)
|
21
|
+
@resolve = init_hash.key?('resolve') ? init_hash['resolve'] : true
|
21
22
|
@full_name, @name = parse_name(init_hash['name'])
|
22
23
|
@version_requirement, @semantic_version = parse_version_requirement(init_hash['version_requirement'])
|
23
24
|
@type = :forge
|
25
|
+
|
26
|
+
unless @resolve == true || @resolve == false
|
27
|
+
raise Bolt::ValidationError,
|
28
|
+
"Option 'resolve' for module spec #{@full_name} must be a Boolean"
|
29
|
+
end
|
24
30
|
end
|
25
31
|
|
26
32
|
def self.implements?(hash)
|
@@ -13,18 +13,31 @@ module Bolt
|
|
13
13
|
class GitSpec
|
14
14
|
NAME_REGEX = %r{\A(?:[a-zA-Z0-9]+[-/])?(?<name>[a-z][a-z0-9_]*)\z}.freeze
|
15
15
|
REQUIRED_KEYS = Set.new(%w[git ref]).freeze
|
16
|
+
KNOWN_KEYS = Set.new(%w[git name ref resolve]).freeze
|
16
17
|
|
17
|
-
attr_reader :git, :ref, :type
|
18
|
+
attr_reader :git, :ref, :resolve, :type
|
18
19
|
|
19
20
|
def initialize(init_hash)
|
21
|
+
@resolve = init_hash.key?('resolve') ? init_hash['resolve'] : true
|
20
22
|
@name = parse_name(init_hash['name'])
|
21
23
|
@git, @repo = parse_git(init_hash['git'])
|
22
24
|
@ref = init_hash['ref']
|
23
25
|
@type = :git
|
26
|
+
|
27
|
+
if @name.nil? && @resolve == false
|
28
|
+
raise Bolt::ValidationError,
|
29
|
+
"Missing name for Git module specification: #{@git}. Git module specifications "\
|
30
|
+
"must include a 'name' key when 'resolve' is false."
|
31
|
+
end
|
32
|
+
|
33
|
+
unless @resolve == true || @resolve == false
|
34
|
+
raise Bolt::ValidationError,
|
35
|
+
"Option 'resolve' for module spec #{@git} must be a Boolean"
|
36
|
+
end
|
24
37
|
end
|
25
38
|
|
26
39
|
def self.implements?(hash)
|
27
|
-
|
40
|
+
KNOWN_KEYS.superset?(hash.keys.to_set) && REQUIRED_KEYS.subset?(hash.keys.to_set)
|
28
41
|
end
|
29
42
|
|
30
43
|
# Parses the name into owner and name segments, and formats the full
|
@@ -47,6 +60,8 @@ module Bolt
|
|
47
60
|
# Gets the repo from the git URL.
|
48
61
|
#
|
49
62
|
private def parse_git(git)
|
63
|
+
return [git, nil] unless @resolve
|
64
|
+
|
50
65
|
repo = if git.start_with?('git@github.com:')
|
51
66
|
git.split('git@github.com:').last.split('.git').first
|
52
67
|
elsif git.start_with?('https://github.com')
|
data/lib/bolt/outputter/human.rb
CHANGED
@@ -32,8 +32,8 @@ module Bolt
|
|
32
32
|
end
|
33
33
|
|
34
34
|
def start_spin
|
35
|
-
return unless @spin
|
36
|
-
@
|
35
|
+
return unless @spin && @stream.isatty && !@spinning
|
36
|
+
@spinning = true
|
37
37
|
@spin_thread = Thread.new do
|
38
38
|
loop do
|
39
39
|
sleep(0.1)
|
@@ -43,9 +43,9 @@ module Bolt
|
|
43
43
|
end
|
44
44
|
|
45
45
|
def stop_spin
|
46
|
-
return unless @spin
|
46
|
+
return unless @spin && @stream.isatty && @spinning
|
47
|
+
@spinning = false
|
47
48
|
@spin_thread.terminate
|
48
|
-
@spin = false
|
49
49
|
@stream.print("\b")
|
50
50
|
end
|
51
51
|
|
@@ -81,6 +81,10 @@ module Bolt
|
|
81
81
|
print_plan_start(event)
|
82
82
|
when :plan_finish
|
83
83
|
print_plan_finish(event)
|
84
|
+
when :start_spin
|
85
|
+
start_spin
|
86
|
+
when :stop_spin
|
87
|
+
stop_spin
|
84
88
|
end
|
85
89
|
end
|
86
90
|
end
|
@@ -388,7 +392,7 @@ module Bolt
|
|
388
392
|
|
389
393
|
def print_target_info(targets)
|
390
394
|
@stream.puts ::JSON.pretty_generate(
|
391
|
-
|
395
|
+
targets: targets.map(&:detail)
|
392
396
|
)
|
393
397
|
count = "#{targets.count} target#{'s' unless targets.count == 1}"
|
394
398
|
@stream.puts colorize(:green, count)
|
data/lib/bolt/outputter/json.rb
CHANGED
@@ -95,38 +95,38 @@ module Bolt
|
|
95
95
|
end
|
96
96
|
|
97
97
|
def print_puppetfile_result(success, puppetfile, moduledir)
|
98
|
-
@stream.puts({
|
99
|
-
|
100
|
-
|
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
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
105
|
+
inventory: {
|
106
|
+
targets: target_list[:inventory].map(&:name),
|
107
|
+
count: target_list[:inventory].count,
|
108
|
+
file: inventoryfile.to_s
|
109
109
|
},
|
110
|
-
|
111
|
-
|
112
|
-
|
110
|
+
adhoc: {
|
111
|
+
targets: target_list[:adhoc].map(&:name),
|
112
|
+
count: target_list[:adhoc].count
|
113
113
|
},
|
114
|
-
|
115
|
-
|
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
|
-
|
122
|
-
|
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({
|
129
|
-
|
128
|
+
@stream.puts({ groups: groups,
|
129
|
+
count: count }.to_json)
|
130
130
|
end
|
131
131
|
|
132
132
|
def fatal_error(err)
|