module MCollective class Application class Playbook < Application description "Choria Playbook Runner" usage <<-USAGE mco playbook [OPTIONS] The ACTION can be one of the following: run - run the playbook as your local user The PLAYBOOK Puppet Plan describing the tasks to perform Passing --help as well as a PLAYBOOK argument will show flags and help related to the specific playbook. Any inputs to the playbook should be given on the CLI. USAGE exclude_argument_sections "common", "filter", "rpc" def pre_parse_modulepath words = Shellwords.shellwords(ARGV.join(" ")) words.each_with_index do |word, idx| if word == "--modulepath" configuration[:__modulepath] = words[idx + 1] break end end end # Playbook should be given right after the command, this finds the value after the command # # @return [String,nil] def pre_parse_find_playbook commands = Regexp.union(valid_commands) cmd_idx = ARGV.index {|a| a.match(commands)} return nil unless cmd_idx pb = ARGV[cmd_idx + 1] pb if pb =~ /\A([a-z][a-z0-9_]*)?(::[a-z][a-z0-9_]*)*\Z/ end # Creates an instance of the plan runner # # @param plan [String] the name of a plan # @return [Util::BoltSupport::PlanRunner] def runner(plan, loglevel=nil) require "mcollective/util/bolt_support" plan_runner = Util::BoltSupport::PlanRunner.new( plan, configuration[:__tmpdir], configuration[:__modulepath], configuration[:__loglevel] || "info" ) unless plan_runner.exist? warn("Cannot find supplied Playbook %s" % plan) $stderr.puts warn("Module Path:") $stderr.puts if configuration[:__modulepath] warn(Util.align_text(configuration[:__modulepath].split(":").join("\n"))) else warn(Util.align_text("Puppet Default")) end exit(1) end plan_runner end # Adds the playbook inputs as CLI options before starting the app def run pre_parse_modulepath Dir.mktmpdir("choria") do |dir| configuration[:__tmpdir] = dir if playbook_name = pre_parse_find_playbook configuration[:__playbook] = playbook_name runner(playbook_name).add_cli_options(self, false) end # Hackily done here to force it below the playbook options self.class.option :__json_input, :arguments => ["--input INPUT"], :description => "JSON input to pass to the task", :required => false, :type => String self.class.option :__modulepath, :arguments => ["--modulepath PATH"], :description => "Path to find Puppet module when using the Playbook DSL", :type => String self.class.option :__loglevel, :arguments => ["--loglevel LEVEL"], :description => "Override the loglevel set in the playbook (emerg alert crit err warning notice info verbose debug)", :type => String, :validate => ->(level) { %w[emerg alert crit err warning notice info verbose debug].include?(level) } super end end def post_option_parser(configuration) if ARGV.length >= 1 configuration[:__command] = ARGV.shift else abort("Please specify a command, valid commands are: %s" % valid_commands.join(", ")) end if input = configuration[:__json_input] result = {} if input.start_with?("@") input.sub!("@", "") result = JSON.parse(File.read(input)) if input.end_with?("json") result = YAML.safe_load(File.read(input)) if input.end_with?("yaml") else result = JSON.parse(input) end configuration.merge!(result) end end # Validates the configuration # # @return [void] def validate_configuration(configuration) abort("Please specify a playbook to run") unless configuration[:__playbook] configuration[:__loglevel] = "debug" if options[:verbose] && !configuration.include?(:loglevel) end # List of valid commands this application respond to # # @return [Array] like `plan` and `run` def valid_commands methods.grep(/_command$/).map {|c| c.to_s.gsub("_command", "")} end def run_command pb_config = configuration.clone pb_config.delete_if {|k, _| k.to_s.start_with?("__")} pb = runner(configuration[:__playbook], configuration[:__loglevel]) run_plan(pb, pb_config) end def run_plan(playbook, pb_config) startime = Time.now success = true result = playbook.run!(pb_config) rescue success = false ensure disconnect endtime = Time.now color = :green msg = "OK" unless success color = :red msg = "FAILED" end puts puts "Playbook %s ran in %.2f seconds: %s" % [ Util.colorize(:bold, configuration[:__playbook]), endtime - startime, Util.colorize(color, msg) ] if result puts puts "Result: " puts if result.instance_of?(String) puts result else puts Util.align_text(JSON.pretty_generate(result), 10000) end puts end success ? exit(0) : exit(1) end def main send("%s_command" % configuration[:__command]) end end end end