#!/usr/bin/env ruby -W:no-deprecated require 'socket' require 'yaml' require 'thread' begin require 'prometheus/client' require 'prometheus/client/push' require 'thor' require 'rufus-scheduler' rescue Gem::GemNotFoundException $stderr.puts "Loadind error, it's like you try to run Splash, with a lake of dependencies." $stderr.puts "If you run on RVM, please run with rvmsudo and not with sudo." $stderr.puts "If problem is percistant, please, proceed to new install and Setup." end require 'yaml' require 'splash/constants' require 'splash/helpers' require 'splash/config' require 'splash/templates' require 'splash/backends' require 'splash/transports' require 'splash/commands' require 'splash/logs' require 'splash/orchestrator' require 'splash/controller' #inhibit warning : due to prometheus-client call to URI.encode warning $-w = nil include Splash::Helpers module CLISplash class Commands < Thor include Splash::Config include Splash::Backends desc "execute NAME", "run for command/sequence or ack result" long_desc <<-LONGDESC execute command or sequence or ack result with --no-trace prevent storing execution trace in configured backend (see config file) with --ack, notify errorcode=0 to Prometheus PushGateway with --no-notify, bypass Prometheus notification with --no-callback, never execute callback (:on_failure, :on_success) never follow sequences LONGDESC option :trace, :type => :boolean, :default => true option :ack, :type => :boolean, negate: false option :notify, :type => :boolean, :default => true option :callback, :type => :boolean, :default => true def execute(name) if is_root? then command = Splash::CommandWrapper::new(name) command.ack if options[:ack] command.call_and_notify trace: options[:trace], notify: options[:notify], callback: options[:callback] else $stderr.puts "Command wrapping need to be run as root" exit 60 end end desc "treeview", "Show commands sequence tree" def treeview(command, depht = 0) puts "Command : #{command.to_s}" if depht == 0 cmd = get_config.commands[command.to_sym] if cmd[:on_failure] then print " " * depht + " " puts "* on failure => #{cmd[:on_failure]}" treeview(cmd[:on_failure], depht+2) end if cmd[:on_success] then print " " * depht + " " puts "* on success => #{cmd[:on_success]}" treeview(cmd[:on_success],depht+2) end end desc "list", "Show configured commands" long_desc <<-LONGDESC Show configured commands with --detail, show command details LONGDESC option :detail, :type => :boolean def list puts "Splash configured commands :" list = get_config.commands puts 'No configured commands found' if list.keys.empty? list.keys.each do |command| puts " * #{command.to_s}" if options[:detail] then puts " - command line : '#{list[command][:command]}'" puts " - command description : '#{list[command][:desc]}'" puts " - command failure callback : '#{list[command.to_sym][:on_failure]}'" if list[command.to_sym][:on_failure] puts " - command success callback : '#{list[command.to_sym][:on_success]}'" if list[command.to_sym][:on_success] end end end desc "show COMMAND", "Show specific configured command COMMAND" def show(command) list = get_config.commands if list.keys.include? command.to_sym then puts "Splash command : #{command}" puts " - command line : '#{list[command.to_sym][:command]}'" puts " - command description : '#{list[command.to_sym][:desc]}'" puts " - command failure callback : '#{list[command.to_sym][:on_failure]}'" if list[command.to_sym][:on_failure] puts " - command success callback : '#{list[command.to_sym][:on_success]}'" if list[command.to_sym][:on_success] else $stderr.puts "Command not configured" exit 50 end end desc "lastrun COMMAND", "Show last running result for specific configured command COMMAND" long_desc <<-LONGDESC Show last running result for specific configured command COMMAND with --hostname , an other Splash monitored server (only with Redis backend configured) LONGDESC option :hostname, :type => :string def lastrun(command) backend = get_backend :execution_trace redis = (backend.class == Splash::Backends::Redis)? true : false if not redis and options[:hostname] then $stderr.puts "Remote execution report request only possible with Redis backend" end list = get_config.commands if list.keys.include? command.to_sym then print "Splash command #{command} previous execution report:\n\n" req = { :key => command} req[:hostname] = options[:hostname] if options[:hostname] if backend.exist? req then print backend.get req else puts "Command not already runned." end else $stderr.puts "Command not configured" exit 50 end end desc "getreportlist COMMAND", "list all executions report results " long_desc <<-LONGDESC Show configured commands with --pattern , search type string, wilcard * (group) ? (char) with --hostname , an other Splash monitored server (only with Redis backend configured) with --all, get all execution report for all servers (only with Redis backend configured) --all and --hostname are exclusives LONGDESC option :pattern, :type => :string option :hostname, :type => :string option :all, :type => :boolean, :negate => false def getreportlist if options[:hostname] and options[:all] then $stderr.puts "--all option imcompatible with --hostname" exit 40 end backend = get_backend :execution_trace redis = (backend.class == Splash::Backends::Redis)? true : false if not redis and (options[:hostname] or options[:all]) then $stderr.puts "Remote execution report request only possible with Redis backend" exit 40 end pattern = (options[:pattern])? options[:pattern] : '*' if options[:all] then res = backend.listall pattern elsif options[:hostname] res = backend.list pattern, options[:hostname] else res = backend.list pattern end print "List of Executions reports :\n\n" puts "Not reports found" if res.empty? res.each do |item| if options[:all] host,command = item.split('#') puts " * Command : #{command} @ host : #{host}" else puts " * Command : #{item}" end end end end class CLIController < Thor include Splash::LogsMonitor::DaemonController include Splash::Transports option :foreground, :type => :boolean desc "start", "Starting Logs Monitor Daemon" def start errorcode = run_as_root :startdaemon exit errorcode end desc "stop", "Stopping Logs Monitor Daemon" def stop errorcode = run_as_root :stopdaemon exit errorcode end desc "status", "Logs Monitor Daemon status" def status errorcode = run_as_root :statusdaemon exit errorcode end desc "ping HOSTNAME", "send a ping to HOSTNAME daemon over transport (need an active tranport), Typicallly RabbitMQ" def ping(hostname=Socket.gethostname) puts "ctrl+c for interrupt" queue = "splash.#{Socket.gethostname}.returncli" order = {:verb => :ping, :payload => {:hostname => Socket.gethostname}, :return_to => queue} lock = Mutex.new condition = ConditionVariable.new begin get_default_subscriber(queue: queue).subscribe(timeout: 10) do |delivery_info, properties, payload| puts YAML::load(payload) lock.synchronize { condition.signal } end get_default_client.publish queue: "splash.#{hostname}.input", message: order.to_yaml lock.synchronize { condition.wait(lock) } rescue Interrupt puts "Splash : ping : Interrupted by user. " exit 33 end end end class Config < Thor include Splash::Config include Splash::Helpers desc "setup", "Setup installation fo Splash" long_desc <<-LONGDESC Setup installation fo Splash with --preserve, preserve from reinstallation of the config LONGDESC option :preserve, :type => :boolean def setup errorcode = run_as_root :setupsplash exit errorcode end desc "sanitycheck", "Verify installation fo Splash" def sanitycheck errorcode = run_as_root :checkconfig exit errorcode end desc "version", "display current Splash version" def version config = get_config puts "Splash version : #{config.version}, Author : #{config.author}" puts config.copyright end end class Logs < Thor include Splash::Config desc "analyse", "analyze logs in config" def analyse results = Splash::LogScanner::new results.analyse puts "SPlash Configured logs status :" full_status = true results.output.each do |result| status = (result[:status] == :clean)? "OK": "KO" puts " * Log : #{result[:log]} : [#{status}]" puts " - Detected pattern : #{result[:pattern]}" puts " - detailled Status : #{result[:status].to_s}" puts " count = #{result[:count]}" if result[:status] == :matched puts " nb lines = #{result[:lines]}" if result[:status] != :missing full_status = false unless result[:status] == :clean end display_status = (full_status)? "OK": "KO" puts "Global Status : [#{display_status}]" end desc "monitor", "monitor logs in config" def monitor result = Splash::LogScanner::new result.analyse result.notify end desc "show LOG", "show configured log monitoring for LOG" def show(log) log_record_set = get_config.logs.select{|item| item[:log] == log } unless log_record_set.empty? then record = log_record_set.first puts "Splash log monitor : #{record[:log]}" puts " -> pattern : /#{record[:pattern]}/" else $stderr.puts "log not configured" exit 50 end end desc "list", "Show configured logs monitoring" long_desc <<-LONGDESC Show configured logs monitoring with --detail, show logs monitor details LONGDESC option :detail, :type => :boolean def list puts "Splash configured log monitoring :" log_record_set = get_config.logs puts 'No configured commands found' if log_record_set.empty? log_record_set.each do |record| puts " * log monitor : #{record[:log]}" if options[:detail] then puts " -> pattern : /#{record[:pattern]}/" end end end end end class CLI < Thor def self.exit_on_failure? true end include CLISplash desc "commands SUBCOMMAND ...ARGS", "Managing commands/batchs supervision" subcommand "commands", Commands desc "logs SUBCOMMAND ...ARGS", "Managing Files/Logs supervision" subcommand "logs", Logs desc "daemon SUBCOMMAND ...ARGS", "Logs monitor daemon contoller" subcommand "daemon", CLIController desc "config SUBCOMMAND ...ARGS", "config tools for Splash" subcommand "config", Config end CLI.start(ARGV)