diff --git a/guides/targets.yaml b/guides/targets.yaml index 99d42e3c5..6716eda50 100644 --- a/guides/targets.yaml +++ b/guides/targets.yaml @@ -16,6 +16,8 @@ guide: | The 'targets' option accepts a comma-separated list of target URIs or group names, or can read a target list from an input file '@' or stdin '-'. + The input can also be a JSON array of target names or a Bolt result object + with an 'items' array where each item has a 'target' key. URIs can be specified with the format [protocol://][user@]host[:port]. To learn more about available protocols and their defaults, run 'bolt guide transports'. diff --git a/lib/bolt/cli.rb b/lib/bolt/cli.rb index e1182cc03..0458d3729 100644 --- a/lib/bolt/cli.rb +++ b/lib/bolt/cli.rb @@ -675,7 +675,8 @@ def execute(options) end # Process the target list by turning a PuppetDB query or rerun mode into a - # list of target names. + # list of target names. Try to parse each options[:targets] entry as JSON + # Array/Hash, return the value as-is if failed. # # @param plugins [Bolt::Plugin] The Plugin instance. # @param rerun [Bolt::Rerun] The Rerun instance. @@ -688,7 +689,25 @@ def execute(options) elsif options[:rerun] rerun.get_targets(options[:rerun]) elsif options[:targets] - options[:targets] + options[:targets].map do |target| + parsed = JSON.parse(target) + case parsed + when Array + parsed + when Hash + items = parsed['items'] + if items.is_a?(Array) + items.map { |item| item['target'] } + else + raise Bolt::Error.new("Expected a JSON array or an object with an 'items' array, got #{parsed.class}", + 'bolt/invalid-targets') + end + else + target + end + rescue JSON::ParserError + target + end end end diff --git a/spec/unit/cli_spec.rb b/spec/unit/cli_spec.rb index 9857776f3..2f23812da 100644 --- a/spec/unit/cli_spec.rb +++ b/spec/unit/cli_spec.rb @@ -606,6 +606,36 @@ end end + describe 'target processing' do + let(:options) { { subcommand: 'command', action: 'run', object: 'whoami', targets: targets } } + + it 'parses a JSON array from targets' do + expect(application).to receive(:run_command).with(anything, [%w[host1 host2]]) + cli.execute({ subcommand: 'command', action: 'run', object: 'whoami', + targets: ['["host1","host2"]'] }) + end + + it 'parses a bolt result JSON with items' do + expect(application).to receive(:run_command) + .with(anything, [%w[host1 host2]]) + cli.execute({ subcommand: 'command', action: 'run', object: 'whoami', + targets: ['{"items":[{"target":"host1"},{"target":"host2"}]}'] }) + end + + it 'passes unrecognized JSON through as raw string' do + expect(application).to receive(:run_command).with(anything, ['42']) + cli.execute({ subcommand: 'command', action: 'run', object: 'whoami', + targets: ['42'] }) + end + + it 'errors when JSON is an object without items' do + expect do + cli.execute({ subcommand: 'command', action: 'run', object: 'whoami', + targets: ['{"foo":"bar"}'] }) + end.to raise_error(Bolt::Error, /Expected a JSON array or an object/) + end + end + describe 'bundled content' do before(:each) do allow(pal).to receive(:list_plans).and_return([%w[plan description]])