require 'digest/sha1' require 'securerandom' require 'shellwords' # @author Lee Hambley module SSHKit # @author Lee Hambley class Command Failed = Class.new(SSHKit::StandardError) attr_reader :command, :args, :options, :started_at, :started, :exit_status, :full_stdout, :full_stderr, :uuid # Initialize a new Command object # # @param [Array] A list of arguments, the first is considered to be the # command name, with optional variadaric args # @return [Command] An un-started command object with no exit staus, and # nothing in stdin or stdout # def initialize(*args) raise ArgumentError, "Must pass arguments to Command.new" if args.empty? @options = default_options.merge(args.extract_options!) @command = sanitize_command(args.shift) @args = args @options.symbolize_keys! @stdout, @stderr, @full_stdout, @full_stderr = String.new, String.new, String.new, String.new @uuid = Digest::SHA1.hexdigest(SecureRandom.random_bytes(10))[0..7] end def complete? !exit_status.nil? end alias :finished? :complete? def started? started end def started=(new_started) @started_at = Time.now @started = new_started end def success? exit_status.nil? ? false : exit_status.to_i == 0 end alias :successful? :success? def failure? exit_status.to_i > 0 end alias :failed? :failure? def stdout log_reader_deprecation('stdout') @stdout end def stdout=(new_value) log_writer_deprecation('stdout') @stdout = new_value end def stderr log_reader_deprecation('stderr') @stderr end def stderr=(new_value) log_writer_deprecation('stderr') @stderr = new_value end def on_stdout(channel, data) @stdout = data @full_stdout += data call_interaction_handler(:stdout, data, channel) end def on_stderr(channel, data) @stderr = data @full_stderr += data call_interaction_handler(:stderr, data, channel) end def exit_status=(new_exit_status) @finished_at = Time.now @exit_status = new_exit_status if options[:raise_on_non_zero_exit] && exit_status > 0 message = "" message += "#{command} exit status: " + exit_status.to_s + "\n" message += "#{command} stdout: " + (full_stdout.strip.empty? ? "Nothing written" : full_stdout.strip) + "\n" message += "#{command} stderr: " + (full_stderr.strip.empty? ? 'Nothing written' : full_stderr.strip) + "\n" raise Failed, message end end def runtime return nil unless complete? @finished_at - @started_at end def to_hash { command: self.to_s, args: args, options: options, exit_status: exit_status, stdout: full_stdout, stderr: full_stderr, started_at: @started_at, finished_at: @finished_at, runtime: runtime, uuid: uuid, started: started?, finished: finished?, successful: successful?, failed: failed? } end def host options[:host] end def verbosity if (vb = options[:verbosity]) case vb when Symbol then return Logger.const_get(vb.to_s.upcase) when Integer then return vb end else Logger::INFO end end def should_map? !command.match(/\s/) end def within(&_block) return yield unless options[:in] "cd #{self.class.shellescape_except_tilde(options[:in])} && #{yield}" end def environment_hash (SSHKit.config.default_env || {}).merge(options[:env] || {}) end def environment_string environment_hash.collect do |key,value| key_string = key.is_a?(Symbol) ? key.to_s.upcase : key.to_s escaped_value = value.to_s.gsub(/"/, '\"') %{#{key_string}="#{escaped_value}"} end.join(' ') end def with(&_block) env_string = environment_string return yield if env_string.empty? "( export #{env_string} ; #{yield} )" end def user(&_block) return yield unless options[:user] env_string = environment_string "sudo -u #{options[:user].to_s.shellescape} #{env_string + " " unless env_string.empty?}-- sh -c #{yield.shellescape}" end def in_background(&_block) return yield unless options[:run_in_background] "( nohup #{yield} > /dev/null & )" end def umask(&_block) return yield unless SSHKit.config.umask "umask #{SSHKit.config.umask} && #{yield}" end def group(&_block) return yield unless options[:group] "sg #{options[:group].to_s.shellescape} -c #{yield.shellescape}" # We could also use the so-called heredoc format perhaps: #"newgrp #{options[:group]} <