class Mixlib::ShellOut

def error!

::ShellCommandFailed::: via +invalid!+
=== Raises
nil::: always returns nil when it does not raise
=== Returns
If #error? is true, calls +invalid!+, which raises an Exception.
def error!
  invalid!("Expected process to exit with #{valid_exit_codes.inspect}, but received '#{exitstatus}'") if error?
end

def error?

otherwise.
+true+ if +exitstatus+ is not in the list of +valid_exit_codes+, false
=== Returns
Checks the +exitstatus+ against the set of +valid_exit_codes+.
def error?
  !Array(valid_exit_codes).include?(exitstatus)
end

def exitstatus

`kill -9`).
running or died without setting an exit status (e.g., terminated by
The exit status of the subprocess. Will be nil if the command is still
def exitstatus
  @status && @status.exitstatus
end

def format_for_exception

results when the command exited with an unexpected status.
showing the exact command executed. Used by +invalid!+ to show command
Creates a String showing the output of the command, including a banner
def format_for_exception
  msg = ""
  msg << "#{@terminate_reason}\n" if @terminate_reason
  msg << "---- Begin output of #{command} ----\n"
  msg << "STDOUT: #{stdout.strip}\n"
  msg << "STDERR: #{stderr.strip}\n"
  msg << "---- End output of #{command} ----\n"
  msg << "Ran #{command} returned #{status.exitstatus}" if status
  msg
end

def gid

TODO migrate to shellout/unix.rb
given as a group name, it is converted to a gid by Etc.getgrnam
The gid that the subprocess will switch to. If the group attribute is
def gid
  return group.kind_of?(Integer) ? group : Etc.getgrnam(group.to_s).gid if group
  return Etc.getpwuid(uid).gid if using_login?
  nil
end

def initialize(*command_args)

cmd.run_command # etc.
cmd = Mixlib::ShellOut.new("apachectl", "start", :user => 'www', :env => nil, :cwd => '/tmp')
Run a command as the +www+ user with no extra ENV settings from +/tmp+
find.error!
# Raise an exception if it didn't exit with 0
puts "error messages" + find.stderr
# find(1) prints diagnostic info to STDERR:
puts find.stdout
# If all went well, the results are on +stdout+
find.run_command
find = Mixlib::ShellOut.new("find . -name '*.rb'")
Invoke find(1) to search for .rb files:
=== Examples:
variables etc) as done by the OS in an actual login
* +login+: Whether to simulate a login (set secondary groups, primary group, environment
long-running commands.
the parent's stdout so that users may observe the progress of
child process. Generally this is used to copy data from the child to
operator +<<+) that will receive data as ShellOut reads it from the
* +live_stream+: An IO or Logger-like object (must respond to the append
default).
is a safe value, though on newer Linux systems the capacity is 64k by
data should not exceed the system's default pipe capacity (4096 bytes
launched. The child's stdin stream will be a pipe, so the size of input
written to the child process' stdin stream before the process is
* +input+: A String of data to be passed to the subcommand. This is
seconds. Note: the stdlib Timeout library is not used.
receiving any output (i.e., IO.select returned nil). Default is 600
total amount of time that ShellOut waited on the child process without
child process before raising an Exception. This is calculated as the
* +timeout+: a Numeric value for the number of seconds to wait on the
is run.
* +environment+: a Hash of environment variables to set before the command
+run_command+.
subprocess. This only has an effect if you call +error!+ after
* +returns+: one or more Integer values to use as valid exit codes for the
be treated as an octal integer
be sure to use two leading zeros so it's parsed as Octal. A string will
* +umask+: a umask to set before running the command. If given as an Integer,
* +cwd+: the directory to chdir to before running the command
* +group+: the group the command should run as. works similarly to +user+
with Etc.getpwnam
used as a uid. A string is treated as a username and resolved to a uid
* +user+: the user the commmand should run as. if an integer is given, it is
to exec and used as an options hash. The following options are available:
If the last argument is a Hash, it is removed from the list of args passed
=== Options:
options Hash.
explanation of how arguments are evaluated. The last argument can be an
as arguments to Kernel.exec. See the Kernel.exec documentation for more
Takes a single command, or a list of command fragments. These are used
=== Arguments:
def initialize(*command_args)
  @stdout, @stderr, @process_status = "", "", ""
  @live_stdout = @live_stderr = nil
  @input = nil
  @log_level = :debug
  @log_tag = nil
  @environment = {}
  @cwd = nil
  @valid_exit_codes = [0]
  @terminate_reason = nil
  @timeout = nil
  @elevated = false
  if command_args.last.is_a?(Hash)
    parse_options(command_args.pop)
  end
  @command = command_args.size == 1 ? command_args.first : command_args
end

def inspect

def inspect
  "<#{self.class.name}##{object_id}: command: '#{@command}' process_status: #{@status.inspect} " +
    "stdout: '#{stdout.strip}' stderr: '#{stderr.strip}' child_pid: #{@child_pid.inspect} " +
    "environment: #{@environment.inspect} timeout: #{timeout} user: #{@user} group: #{@group} working_dir: #{@cwd} >"
end

def invalid!(msg = nil)

ShellCommandFailed always
=== Raises
is highly encouraged.
default explanation is very generic, providing a more informative message
+msg+: A String to use as the basis of the exception message. The
=== Arguments
command's stdout, stderr, and exitstatus to the exception message.
Raises a ShellCommandFailed exception, appending the
def invalid!(msg = nil)
  msg ||= "Command produced unexpected results"
  raise ShellCommandFailed, msg + "\n" + format_for_exception
end

def live_stream

Returns the stream that both is being used by both live_stdout and live_stderr, or nil
def live_stream
  live_stdout == live_stderr ? live_stdout : nil
end

def live_stream=(stream)

the subprocess is running.
stdout and stderr from the subprocess will be copied to the same stream as
A shortcut for setting both live_stdout and live_stderr, so that both the
def live_stream=(stream)
  @live_stdout = @live_stderr = stream
end

def parse_options(opts)

def parse_options(opts)
  opts.each do |option, setting|
    case option.to_s
    when "cwd"
      self.cwd = setting
    when "domain"
      self.domain = setting
    when "password"
      self.password = setting
    when "user"
      self.user = setting
      self.with_logon = setting
    when "group"
      self.group = setting
    when "umask"
      self.umask = setting
    when "timeout"
      self.timeout = setting
    when "returns"
      self.valid_exit_codes = Array(setting)
    when "live_stream"
      self.live_stdout = self.live_stderr = setting
    when "live_stdout"
      self.live_stdout = setting
    when "live_stderr"
      self.live_stderr = setting
    when "input"
      self.input = setting
    when "logger"
      self.logger = setting
    when "log_level"
      self.log_level = setting
    when "log_tag"
      self.log_tag = setting
    when "environment", "env"
      if setting
        self.environment = Hash[setting.map { |(k, v)| [k.to_s, v] }]
      else
        self.environment = {}
      end
    when "login"
      self.login = setting
    when "elevated"
      self.elevated = setting
    else
      raise InvalidCommandOption, "option '#{option.inspect}' is not a valid option for #{self.class.name}"
    end
  end
  validate_options(opts)
end

def run_command

within +timeout+ seconds (default: 600s)
* CommandTimeout when the command does not complete
in the current $PATH)
* Errno::ENOENT when the command is not available on the system (or not
* Errno::EACCES when you are not privileged to execute the command
=== Raises
populated with results of the command
returns +self+; +stdout+, +stderr+, +status+, and +exitstatus+ will be
=== Returns
to +stdout+ and +stderr+, and saving its exit status object to +status+
Run the command, writing the command's standard out and standard error
def run_command
  if logger
    log_message = (log_tag.nil? ? "" : "#{@log_tag} ") << "sh(#{@command})"
    logger.send(log_level, log_message)
  end
  super
end

def timeout

def timeout
  @timeout || DEFAULT_READ_TIMEOUT
end

def uid

TODO migrate to shellout/unix.rb
given as a username, it is converted to a uid by Etc.getpwnam
The uid that the subprocess will switch to. If the user attribute was
def uid
  return nil unless user
  user.kind_of?(Integer) ? user : Etc.getpwnam(user.to_s).uid
end

def umask=(new_umask)

will be converted to an integer by String#oct.
Set the umask that the subprocess will have. If given as a string, it
def umask=(new_umask)
  @umask = (new_umask.respond_to?(:oct) ? new_umask.oct : new_umask.to_i) & 007777
end

def validate_options(opts)

def validate_options(opts)
  if login && !user
    raise InvalidCommandOption, "cannot set login without specifying a user"
  end
  super
end