class Raykit::Command
Functionality to support executing and logging system commands
def self.get_script_commands(hash)
def self.get_script_commands(hash) commands = [] if hash.key?("script") hash["script"].each do |cmd| commands << cmd end end hash.each do |_k, v| next unless v.is_a?(Hash) subcommands = get_script_commands(v) subcommands.each do |c| commands << c end end commands end
def self.parse(json)
def self.parse(json) cmd = Command.new("") cmd.from_hash(JSON.parse(json)) cmd end
def self.parse_yaml_commands(yaml)
def self.parse_yaml_commands(yaml) # commands=Array.new() data = YAML.safe_load(yaml) commands = get_script_commands(data) end
def details
def details puts " exit_code: " + @exitstatus.to_s if @output.length > 0 @output.lines.each do |line| puts " " + line end end if @error.length > 0 @error.lines.each do |line| puts " " + line end end self end
def details_on_failure
def details_on_failure if @exitstatus != 0 details end self end
def elapsed_str
def elapsed_str if elapsed < 1.0 end "#{format("%.0f", elapsed)}s" end
def from_hash(hash)
def from_hash(hash) @command = hash["command"] @directory = hash["directory"] @timeout = hash["timeout"] @start_time = hash["start_time"] @elapsed = hash["elapsed"] @output = hash["output"] @error = hash["error"] @exitstatus = hash["exitstatus"] @user = hash["user"] if hash.include?("user") @machine = hash["machine"] if hash.include?("machine") end
def init_defaults
def init_defaults @timeout = 0 @directory = Dir.pwd @output = +"" @error = +"" @exitstatus = 0 @user = Environment.user @machine = Environment.machine @logging_enabled = true end
def initialize(command)
def initialize(command) #def initialize(command, timeout = 0, success_log_level = Logger::DEBUG, logging_enabled = true) timeout = 0 success_log_level = nil logging_enabled = false @@commands = [] init_defaults @success_log_level = success_log_level @logging_enabled = logging_enabled @command = command @timeout = timeout #t = Time.now @elapsed = 0 #run if @command.length.positive? self end
def log
def log # --- Rolling File JSON ----- log = Logger.new("#{Raykit::Environment.log_dir}/Raykit.Commands.txt", "daily") log.formatter = proc do |_severity, _datetime, _progname, msg| "#{msg}\n" end secrets = Secrets.new msg = secrets.hide(@command) # "?"# self.summary(false) level = "Verbose" level = "Warning" if @exitstatus != 0 event = Raykit::LogEvent.new(level, msg, { "Timeout" => @timeout, "Directory" => @directory, "Output" => @output, "Error" => @error, "ExitStatus" => @exitstatus, "Elapsed" => elapsed_str, }) log.info event.to_json # --------------------------- begin json = JSON.generate(to_hash) log_filename = "#{Environment.get_dev_dir("log")}/Raykit.Command/#{SecureRandom.uuid}.json" log_dir = File.dirname(log_filename) FileUtils.mkdir_p(log_dir) unless Dir.exist?(log_dir) File.open(log_filename, "w") { |f| f.write(json) } if !@exitstatus.nil? && @exitstatus.zero? LOG.log("Raykit.Command", Logger::Severity::INFO, json) else LOG.log("Raykit.Command", Logger::Severity::ERROR, json) end rescue JSON::GeneratorError => e puts to_hash.to_s puts e.to_s json = JSON.generate(to_hash) end end
def log_to_file(filename)
def log_to_file(filename) File.delete(filename) if File.exists?(filename) File.open(filename, "w") { |f| f.puts output f.puts error } self end
def run
def run # puts '---running---' @start_time = Time.now @elapsed = 0 timer = Timer.new if @timeout.zero? @output, @error, process_status = Open3.capture3(@command) @exitstatus = process_status.exitstatus else # ================================================= #puts "@timeout is #{@timeout}" Open3.popen3(@command, chdir: @directory) do |_stdin, stdout, stderr, thread| tick = 1 pid = thread.pid start = Time.now while ((Time.now - start) < @timeout) && thread.alive? Kernel.select([stdout, stderr], nil, nil, tick) begin @output << stdout.read_nonblock(BUFFER_SIZE) #@error << stderr.read_nonblock(BUFFER_SIZE) rescue IO::WaitReadable rescue EOFError #puts "rescue block entered" @exitstatus = thread.value.exitstatus until stdout.eof? @output << stdout.read_nonblock(BUFFER_SIZE) end until stderr.eof? @error << stderr.read_nonblock(BUFFER_SIZE) end break end #begin # @output << stdout.read_nonblock(BUFFER_SIZE) # @error << stderr.read_nonblock(BUFFER_SIZE) #rescue IO::WaitReadable #rescue EOFError # @exitstatus = thread.value.exitstatus # until stdout.eof? # @output << stdout.read_nonblock(BUFFER_SIZE) # end # until stderr.eof? # @error << stderr.read_nonblock(BUFFER_SIZE) # end # break #end end sleep 1 if thread.alive? if Gem.win_platform? `taskkill /f /pid #{pid}` else Process.kill("TERM", pid) end @exitstatus = 5 @error = +"timed out" else @exitstatus = thread.value.exitstatus end end # ================================================= end @elapsed = timer.elapsed if @logging_enabled log if @exitstatus != 0 to_log_event.to_seq else # puts '---logging---' unless @success_log_level.nil? e = to_log_event e["Level"] = "Verbose" if @success_log_level == "Verbose" e["Level"] = "Debug" if @success_log_level == Logger::DEBUG e["Level"] = "Information" if @success_log_level == Logger::INFO e["Level"] = "Warning" if @elapsed > 60 * 2 e.to_seq end end end self end
def save
def save filename = "#{Environment.get_dev_dir("log")}/Commands/#{SecureRandom.uuid}" log_dir = File.dirname(filename) FileUtils.mkdir_p(log_dir) unless Dir.exist?(log_dir) File.open(filename, "w") do |f| f.write(JSON.pretty_generate(to_hash)) end self end
def set_timeout(timeout)
def set_timeout(timeout) @timeout = timeout self end
def summary(show_directory = false)
def summary(show_directory = false) checkmark = "\u2713" # warning="\u26A0" error = "\u0058" symbol = Rainbow(checkmark.encode("utf-8")).green symbol = Rainbow(error.encode("utf-8")).red if @exitstatus != 0 cmd = "#{Rainbow(SECRETS.hide(@command)).yellow}" if show_directory puts "#{symbol} #{cmd} " + Rainbow("#{elapsed_str}").cyan puts Rainbow(" #{@directory}").cyan else puts "#{symbol} #{Rainbow(SECRETS.hide(@command)).yellow} " + Rainbow("#{elapsed_str}").cyan #puts "#{symbol} #{Rainbow(SECRETS.hide(@command)).yellow} (#{elapsed_str})" end self end
def to_hash
def to_hash hash = {} hash[:command] = @command hash[:directory] = @directory hash[:timeout] = @timeout hash[:start_time] = @start_time hash[:elapsed] = @elapsed hash[:output] = @output.force_encoding("ISO-8859-1").encode("UTF-8") hash[:error] = @error.force_encoding("ISO-8859-1").encode("UTF-8") hash[:exitstatus] = @exitstatus hash[:user] = @user hash[:machine] = @machine hash[:context] = "Raykit.Command" hash end
def to_log_event
def to_log_event secrets = Secrets.new msg = secrets.hide(@command) level = "Verbose" level = "Warning" if @exitstatus != 0 output = @output error = @error output = @output[-1000..-1] if @output.length > 1200 error = @error[-1000..-1] if @error.length > 1200 Raykit::LogEvent.new(level, msg, { "SourceContext" => "Raykit::Command", "Category" => "Command", "Timeout" => @timeout, "Directory" => @directory, "Output" => output, "Error" => error, "ExitStatus" => @exitstatus, "Elapsed" => elapsed_str, "ElapsedSeconds" => @elapsed, }) end
def to_markdown
def to_markdown checkmark = "\u2713" error = "\u0058" symbol = checkmark.encode("utf-8") symbol = error.encode("utf-8") if @exitstatus != 0 cmd = "#{SECRETS.hide(@command)}" md = "#{symbol} #{SECRETS.hide(@command)} (#{elapsed_str})" md end