lib/choria/colt/cli.rb in choria-colt-0.2.0 vs lib/choria/colt/cli.rb in choria-colt-0.3.0
- old
+ new
@@ -1,42 +1,52 @@
require 'choria/colt'
+require 'choria/colt/cli/formatter'
require 'choria/colt/cli/thor'
require 'json'
-require 'logger'
+require 'tty/logger'
module Choria
class Colt
class CLI < Thor
class Tasks < Thor
+ class_option :log_level,
+ desc: 'Set log level for CLI',
+ default: 'info'
# BOLT: desc 'run <task name> [parameters] {--targets TARGETS | --query QUERY | --rerun FILTER} [options]', 'Run a Bolt task'
desc 'run <task name> [parameters] --targets TARGETS [options]', 'Run a Bolt task'
long_desc <<~DESC
Run a task on the specified targets.
Parameters take the form parameter=value.
DESC
option :targets,
aliases: ['--target', '-t'],
- desc: 'Identifies the targets of the command.',
- required: true
- def run(*args)
+ desc: 'Identifies the targets of the command.'
+ option :targets_with_classes,
+ aliases: ['--targets-with-class', '-C'],
+ desc: 'Select the targets which have the specified Puppet classes.'
+ def run(*args) # rubocop:disable Metrics/AbcSize
input = extract_task_parameters_from_args(args)
raise Thor::Error, 'Task name is required' if args.empty?
raise Thor::Error, "Too many arguments: #{args}" unless args.count == 1
+ raise Thor::Error, 'Flag --targets or --targets-with-class is required' if options['targets'].nil? && options['targets_with_classes'].nil?
+
task_name = args.shift
- targets = options['targets'].split ','
+ targets = options['targets']&.split(',')
targets = nil if options['targets'] == 'all'
- results = colt.run_bolt_task task_name, input: input, targets: targets
+ targets_with_classes = options['targets_with_classes']&.split(',')
- File.write 'last_run.json', JSON.pretty_generate(results)
+ results = colt.run_bolt_task task_name, input: input, targets: targets, targets_with_classes: targets_with_classes do |result|
+ $stdout.puts formatter.process_result(result)
+ end
- show_results(results)
+ File.write 'last_run.json', JSON.pretty_generate(results)
rescue Choria::Orchestrator::Error => e
raise Thor::Error, "#{e.class}: #{e}"
end
desc 'show [task name] [options]', 'Show available tasks and task documentation'
@@ -66,60 +76,86 @@
else
tasks_names.each { |task_name| show_task_details(task_name, tasks) }
end
end
- no_commands do
+ no_commands do # rubocop:disable Metrics/BlockLength
def colt
- @colt ||= Choria::Colt.new logger: Logger.new($stdout)
+ @colt ||= Choria::Colt.new logger: logger
end
+ def logger
+ @logger ||= TTY::Logger.new do |config|
+ config.handlers = [
+ [:console, { output: $stderr, level: options['log_level'].to_sym }],
+ [:stream, { output: File.open('colt-debug.log', 'a'), level: :debug }],
+ ]
+ config.metadata = %i[date time]
+ end
+ end
+
+ def formatter
+ @formatter ||= Formatter.new(colored: $stdout.tty?)
+ end
+
def extract_task_parameters_from_args(args)
parameters = args.grep(/^\w+=/)
args.reject! { |arg| arg =~ /^\w+=/ }
parameters.map do |parameter|
- key, value = parameter.split('=')
+ key, value = parameter.split('=', 2)
+
+ # TODO: Convert to boolean only if the expected type of parameter is boolean
+ # TODO: Support String to integer convertion
+ # TODO: Support @notation from parameter and/or whole input
+ value = true if value == 'true'
+ value = false if value == 'false'
+
[key, value]
end.to_h
end
def show_tasks_summary(tasks)
tasks.reject! { |_task, metadata| metadata['metadata']['private'] }
puts <<~OUTPUT
- Tasks
+ #{pastel.title 'Tasks'}
#{tasks.map { |task, metadata| "#{task}#{' ' * (60 - task.size)}#{metadata['metadata']['description']}" }.join("\n").gsub(/^/, ' ')}
OUTPUT
end
def show_task_details(task_name, tasks)
metadata = tasks[task_name]
puts <<~OUTPUT
- Task: '#{task_name}'
+ #{pastel.title "Task: #{task_name}"}
#{metadata['metadata']['description']}
- Parameters:
- #{JSON.pretty_generate(metadata['metadata']['parameters']).gsub(/^/, ' ')}
+ #{pastel.title 'Parameters'}
+ #{format_task_parameters(metadata['metadata']['parameters']).gsub(/^/, ' ')}
OUTPUT
end
- def show_results(results)
- results.each { |result| show_result(result) }
+ def format_task_parameters(parameters)
+ parameters.map do |parameter, metadata|
+ output = <<~OUTPUT
+ #{pastel.parameter(parameter)} #{pastel.parameter_type metadata['type']}
+ #{metadata['description']}
+ OUTPUT
+ output += " Default: #{metadata['default']}" unless metadata['default'].nil?
+ output
+ end.join "\n"
end
- def show_result(result)
- return show_generic_output(result) unless result.dig(:result, '_output').nil? || (result.dig(:result, 'exit_code') != 0)
-
- $stdout.puts JSON.pretty_generate(result)
+ def pastel
+ @pastel ||= _pastel
end
- def show_generic_output(result)
- target = result[:sender]
-
- output = result.dig(:result, '_output')
- $stdout.puts "'#{target}':"
- output.split("\n").each { |line| $stdout.puts(" #{line}") }
+ def _pastel
+ pastel = Pastel.new(enabled: $stdout.tty?)
+ pastel.alias_color(:title, :cyan)
+ pastel.alias_color(:parameter, :yellow)
+ pastel.alias_color(:parameter_type, :bright_white)
+ pastel
end
end
end
desc 'tasks', 'Show and run Bolt tasks.'