class Mixlib::ShellOut
def error!
=== 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?
+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
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&.exitstatus end
def format_for_exception
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 return "Command execution failed. STDOUT/STDERR suppressed for sensitive resource" if sensitive 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
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.is_a?(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 = 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 command 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 @sensitive = 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)
=== 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
def live_stream live_stdout == live_stderr ? live_stdout : nil end
def live_stream=(stream)
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 when "sensitive" self.sensitive = 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
* 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
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.is_a?(Integer) ? user : Etc.getpwnam(user.to_s).uid end
def umask=(new_umask)
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