class ForemanMaintain::Utils::CommandRunner

Wrapper around running a command

Attributes

command[R]
logger[R]

Public Class Methods

new(logger, command, options) click to toggle source
# File lib/foreman_maintain/utils/command_runner.rb, line 10
def initialize(logger, command, options)
  options.validate_options!(:stdin, :hidden_patterns, :interactive, :valid_exit_statuses)
  options[:valid_exit_statuses] ||= [0]
  @logger = logger
  @command = command
  @stdin = options[:stdin]
  @hidden_patterns = Array(options[:hidden_patterns]).compact
  @interactive = options[:interactive]
  @options = options
  @valid_exit_statuses = options[:valid_exit_statuses]
  raise ArgumentError, 'Can not pass stdin for interactive command' if @interactive && @stdin
end

Public Instance Methods

execution_error() click to toggle source
# File lib/foreman_maintain/utils/command_runner.rb, line 51
def execution_error
  raise Error::ExecutionError.new(hide_strings(@command),
    exit_status,
    hide_strings(@stdin),
    @interactive ? nil : hide_strings(@output))
end
exit_status() click to toggle source
# File lib/foreman_maintain/utils/command_runner.rb, line 42
def exit_status
  raise 'Command not yet executed' unless defined? @exit_status
  @exit_status
end
interactive?() click to toggle source
# File lib/foreman_maintain/utils/command_runner.rb, line 33
def interactive?
  @interactive
end
output() click to toggle source
# File lib/foreman_maintain/utils/command_runner.rb, line 37
def output
  raise 'Command not yet executed' unless defined? @output
  @output
end
run() click to toggle source
# File lib/foreman_maintain/utils/command_runner.rb, line 23
def run
  logger&.debug(hide_strings("Running command #{@command} with stdin #{@stdin.inspect}"))
  if @interactive
    run_interactively
  else
    run_non_interactively
  end
  logger&.debug("output of the command:\n #{hide_strings(output)}")
end
success?() click to toggle source
# File lib/foreman_maintain/utils/command_runner.rb, line 47
def success?
  @valid_exit_statuses.include? exit_status
end

Private Instance Methods

full_command() click to toggle source
# File lib/foreman_maintain/utils/command_runner.rb, line 94
def full_command
  "#{@command} 2>&1"
end
hide_strings(string) click to toggle source
# File lib/foreman_maintain/utils/command_runner.rb, line 98
def hide_strings(string)
  return unless string
  @hidden_patterns.reduce(string) do |result, hidden_pattern|
    result.gsub(hidden_pattern, '[FILTERED]')
  end
end
run_interactively() click to toggle source
# File lib/foreman_maintain/utils/command_runner.rb, line 60
def run_interactively
  # use tmp files to capture output and exit status of the command when
  # running interactively
  log_file = Tempfile.open('captured-output')
  exit_file = Tempfile.open('captured-exit-code')
  Kernel.system(
    "stdbuf -oL -eL bash -c '#{full_command}; echo $? > #{exit_file.path}'"\
    "| tee -i #{log_file.path}"
  )
  File.open(log_file.path) { |f| @output = f.read }
  File.open(exit_file.path) do |f|
    exit_status = f.read.strip
    @exit_status = if exit_status.empty?
                     256
                   else
                     exit_status.to_i
                   end
  end
ensure
  log_file.close
  exit_file.close
end
run_non_interactively() click to toggle source
# File lib/foreman_maintain/utils/command_runner.rb, line 83
def run_non_interactively
  IO.popen(full_command, 'r+') do |f|
    if @stdin
      f.puts(@stdin)
      f.close_write
    end
    @output = f.read.strip
  end
  @exit_status = $CHILD_STATUS.exitstatus
end