module Unix::Exec

Public Instance Methods

add_env_var(key, val) click to toggle source

Add the provided key/val to the current ssh environment @param [String] key The key to add the value to @param [String] val The value for the key @example

host.add_env_var('PATH', '/usr/bin:PATH')
# File lib/beaker/host/unix/exec.rb, line 214
def add_env_var key, val
  key = key.to_s
  env_file = self[:ssh_env_file]
  escaped_val = Regexp.escape(val).gsub('/', '\/').gsub(';', '\;')
  # see if the key/value pair already exists
  if exec(Beaker::Command.new("grep ^#{key}=.*#{escaped_val} #{env_file}"), :accept_all_exit_codes => true).exit_code == 0
    return # nothing to do here, key value pair already exists
  # see if the key already exists
  elsif exec(Beaker::Command.new("grep ^#{key}= #{env_file}"), :accept_all_exit_codes => true).exit_code == 0
    exec(Beaker::SedCommand.new(self['platform'], "s/^#{key}=/#{key}=#{escaped_val}:/", env_file))
  else
    exec(Beaker::Command.new("echo \"#{key}=#{val}\" >> #{env_file}"))
  end

  # update the profile.d to current state
  # match it to the contents of ssh_env_file
  mirror_env_to_profile_d(env_file)
end
append_commands(_command = '', user_ac = '', _opts = {}) click to toggle source

Gets the specific append commands as needed for this host

@param [String] command Command to be executed @param [String] user_ac List of user-specified commands to append @param [Hash] opts optional parameters

@return [String] Command string as needed for this host

# File lib/beaker/host/unix/exec.rb, line 376
def append_commands(_command = '', user_ac = '', _opts = {})
  user_ac
end
clear_env_var(key) click to toggle source

Delete the environment variable from the current ssh environment @param [String] key The key to delete @example

host.clear_env_var('PATH')
# File lib/beaker/host/unix/exec.rb, line 266
def clear_env_var key
  key = key.to_s
  env_file = self[:ssh_env_file]
  # remove entire line
  exec(Beaker::SedCommand.new(self['platform'], "/^#{key}=.*$/d", env_file))
  # update the profile.d to current state
  # match it to the contents of ssh_env_file
  mirror_env_to_profile_d(env_file)
end
delete_env_var(key, val) click to toggle source

Delete the provided key/val from the current ssh environment @param [String] key The key to delete the value from @param [String] val The value to delete for the key @example

host.delete_env_var('PATH', '/usr/bin:PATH')
# File lib/beaker/host/unix/exec.rb, line 238
def delete_env_var key, val
  key = key.to_s
  env_file = self[:ssh_env_file]
  val = Regexp.escape(val).gsub('/', '\/').gsub(';', '\;')
  # if the key only has that single value remove the entire line
  exec(Beaker::SedCommand.new(self['platform'], "/#{key}=#{val}$/d", env_file))
  # value in middle of list
  exec(Beaker::SedCommand.new(self['platform'], "s/#{key}=\\(.*\\)[;:]#{val}/#{key}=\\1/", env_file))
  # value in start of list
  exec(Beaker::SedCommand.new(self['platform'], "s/#{key}=#{val}[;:]/#{key}=/", env_file))
  # update the profile.d to current state
  # match it to the contents of ssh_env_file
  mirror_env_to_profile_d(env_file)
end
echo(msg, abs = true) click to toggle source
# File lib/beaker/host/unix/exec.rb, line 115
def echo(msg, abs = true)
  (abs ? '/bin/echo' : 'echo') + " #{msg}"
end
enable_remote_rsyslog(server = 'rsyslog.ops.puppetlabs.net', port = 514) click to toggle source
# File lib/beaker/host/unix/exec.rb, line 417
def enable_remote_rsyslog(server = 'rsyslog.ops.puppetlabs.net', port = 514)
  if !self['platform'].include?('ubuntu')
    @logger.warn "Enabling rsyslog is only implemented for ubuntu hosts"
    return
  end
  commands = [
    "echo '*.* @#{server}:#{port}' >> /etc/rsyslog.d/51-sendrsyslogs.conf",
    'systemctl restart rsyslog',
  ]
  commands.each do |command|
    exec(Beaker::Command.new(command))
  end
  true
end
environment_string(env) click to toggle source

Construct the environment string for this command

@param [Hash{String=>String}] env An optional Hash containing

key-value pairs to be treated
as environment variables that
should be set for the duration
of the puppet command.

@return [String] Returns a string containing command line arguments that

will ensure the environment is correctly set for the
given host.
# File lib/beaker/host/unix/exec.rb, line 328
def environment_string env
  return '' if env.empty?

  env_array = self.environment_variable_string_pair_array(env)
  environment_string = env_array.join(' ')
  "env #{environment_string}"
end
environment_variable_string_pair_array(env) click to toggle source
# File lib/beaker/host/unix/exec.rb, line 336
def environment_variable_string_pair_array env
  env_array = []
  env.each_key do |key|
    val = env[key]
    val = if val.is_a?(Array)
            val.join(':')
          else
            val.to_s
          end
    # doing this for the key itself & the upcase'd version allows us to remain
    # backwards compatible
    # TODO: (Next Major Version) get rid of upcase'd version
    key_str = key.to_s
    keys = [key_str]
    keys << key_str.upcase if key_str.upcase != key_str
    keys.each do |env_key|
      env_array << "#{env_key}=\"#{val}\""
    end
  end
  env_array
end
get_env_var(key) click to toggle source

Return the value of a specific env var @param [String] key The key to look for @example

host.get_env_var('path')
# File lib/beaker/host/unix/exec.rb, line 257
def get_env_var key
  key = key.to_s
  exec(Beaker::Command.new("env | grep ^#{key}="), :accept_all_exit_codes => true).stdout.chomp
end
get_ip() click to toggle source
# File lib/beaker/host/unix/exec.rb, line 137
def get_ip
  if self['platform'].include?('solaris') || self['platform'].include?('osx')
    execute("ifconfig -a inet| awk '/broadcast/ {print $2}' | cut -d/ -f1 | head -1").strip
  else

    pipe_cmd = "#{self['hypervisor']}".include?('vagrant') ? 'tail' : 'head'
    execute("ip a | awk '/global/{print$2}' | cut -d/ -f1 | #{pipe_cmd} -1").strip
  end
end
mirror_env_to_profile_d(env_file) click to toggle source

Converts the provided environment file to a new shell script in /etc/profile.d, then sources that file. This is for sles based hosts. @param [String] env_file The ssh environment file to read from

# File lib/beaker/host/unix/exec.rb, line 189
def mirror_env_to_profile_d env_file
  if /opensuse|sles-/.match?(self[:platform])
    @logger.debug("mirroring environment to /etc/profile.d on opensuse/sles platform host")
    cur_env = exec(Beaker::Command.new("cat #{env_file}")).stdout
    shell_env = ''
    cur_env.each_line do |env_line|
      shell_env << "export #{env_line}"
    end
    # here doc it over
    exec(Beaker::Command.new("cat << EOF > #{self[:profile_d_env_file]}\n#{shell_env}EOF"))
    # set permissions
    exec(Beaker::Command.new("chmod +x #{self[:profile_d_env_file]}"))
    # keep it current
    exec(Beaker::Command.new("source #{self[:profile_d_env_file]}"))
  else
    # noop
    @logger.debug("will not mirror environment to /etc/profile.d on non-sles platform host")
  end
end
mkdir_p(dir) click to toggle source

Create the provided directory structure on the host @param [String] dir The directory structure to create on the host @return [Boolean] True, if directory construction succeeded, otherwise False

# File lib/beaker/host/unix/exec.rb, line 150
def mkdir_p dir
  cmd = "mkdir -p #{dir}"
  result = exec(Beaker::Command.new(cmd), :acceptable_exit_codes => [0, 1])
  result.exit_code == 0
end
modified_at(file, timestamp = nil) click to toggle source

Update ModifiedDate on a file @param [String] file Path to the file @param [String] timestamp Timestamp to set

# File lib/beaker/host/unix/exec.rb, line 126
def modified_at(file, timestamp = nil)
  require 'date'
  time = timestamp ? DateTime.parse("#{timestamp}") : DateTime.now
  timestamp = time.strftime('%Y%m%d%H%M')
  execute("/bin/touch -mt #{timestamp} #{file}")
end
mv(orig, dest, rm = true) click to toggle source

Move the origin to destination. The destination is removed prior to moving. @param [String] orig The origin path @param [String] dest the destination path @param [Boolean] rm Remove the destination prior to move

# File lib/beaker/host/unix/exec.rb, line 166
def mv orig, dest, rm = true
  rm_rf dest unless !rm
  execute("mv #{orig} #{dest}")
end
path() click to toggle source
# File lib/beaker/host/unix/exec.rb, line 133
def path
  '/bin:/usr/bin'
end
ping(target, attempts = 5) click to toggle source

Attempt to ping the provided target hostname @param [String] target The hostname to ping @param [Integer] attempts Amount of times to attempt ping before giving up @return [Boolean] true of ping successful, overwise false

# File lib/beaker/host/unix/exec.rb, line 175
def ping target, attempts = 5
  try = 0
  while try < attempts
    result = exec(Beaker::Command.new("ping -c 1 #{target}"), :accept_all_exit_codes => true)
    return true if result.exit_code == 0

    try += 1
  end
  result.exit_code == 0
end
prepend_commands(_command = '', user_pc = '', _opts = {}) click to toggle source

Gets the specific prepend commands as needed for this host

@param [String] command Command to be executed @param [String] user_pc List of user-specified commands to prepend @param [Hash] opts optional parameters

@return [String] Command string as needed for this host

# File lib/beaker/host/unix/exec.rb, line 365
def prepend_commands(_command = '', user_pc = '', _opts = {})
  user_pc
end
reboot(wait_time = 10, max_connection_tries = 9, uptime_retries = 18) click to toggle source

Reboots the host, comparing uptime values to verify success @param [Integer] wait_time How long to wait after sending the reboot

command before attempting to check in on the host

@param [Integer] max_connection_tries How many times to retry connecting to

host after reboot. Note that there is an fibbonacci
backoff when attempting retries so the time spent
waiting on this can grow quickly.

@param [Integer] uptime_retries How many times to check to see if the value of the uptime has reset.

Will throw an exception RebootFailure if it fails

# File lib/beaker/host/unix/exec.rb, line 14
def reboot(wait_time = 10, max_connection_tries = 9, uptime_retries = 18)
  require 'time'

  attempts = 0

  # Some systems don't support 'last -F reboot' but it has second granularity
  boot_time_cmd = 'last -F reboot || who -b'

  # Try to match all of the common formats for 'last' and 'who'
  current_year = Time.now.strftime("%Y")
  boot_time_regex = Regexp.new(%{((?:#{(Date::ABBR_DAYNAMES + Date::ABBR_MONTHNAMES).compact.join('|')}|#{current_year}).+?(\\d+:\\d+)+?(?::(\\d+).+?#{current_year})?)})

  original_boot_time_str = nil
  original_boot_time_line = nil
  begin
    attempts += 1
    # Number of seconds to sleep before rebooting.
    reboot_sleep = 1

    original_boot_time_str = exec(Beaker::Command.new(boot_time_cmd), { :max_connection_tries => max_connection_tries, :silent => true }).stdout
    original_boot_time_line = original_boot_time_str.lines.grep(/boot/).first

    raise Beaker::Host::RebootWarning, "Could not find system boot time using '#{boot_time_cmd}': '#{original_boot_time_str}'" unless original_boot_time_line

    original_boot_time_matches = original_boot_time_line.scan(boot_time_regex).last

    raise Beaker::Host::RebootWarning, "Found no valid times in '#{original_boot_time_line}'" unless original_boot_time_matches

    original_boot_time = Time.parse(original_boot_time_matches.first)

    reboot_sleep = (61 - Time.now.strftime("%S").to_i) unless original_boot_time_matches.last

    @logger.notify("Sleeping #{reboot_sleep} seconds before rebooting")

    sleep(reboot_sleep)

    exec(Beaker::Command.new('/bin/systemctl reboot -i || reboot || /sbin/shutdown -r now'), :expect_connection_failure => true)
  rescue ArgumentError => e
    raise Beaker::Host::RebootFailure, "Unable to parse time: #{e.message}"
  rescue Beaker::Host::RebootWarning => e
    raise if attempts > uptime_retries

    @logger.warn(e.message)
    @logger.warn("Retrying #{uptime_retries - attempts} more times.")
    retry
  rescue StandardError => e
    raise if attempts > uptime_retries

    @logger.warn("Unexpected Exception: #{e.message}")
    @logger.warn("Retrying #{uptime_retries - attempts} more times.")
    @logger.warn(e.backtrace[0, 3].join("\n"))
    @logger.debug(e.backtrace.join("\n"))
    retry
  end

  attempts = 0
  begin
    attempts += 1

    # give the host a little time to shutdown
    @logger.debug("Waiting #{wait_time} for host to shut down.")
    sleep wait_time

    # Accept all exit codes because this may fail due to the parallel nature of systemd
    current_boot_time_str = exec(Beaker::Command.new(boot_time_cmd), { :max_connection_tries => max_connection_tries, :silent => true, :accept_all_exit_codes => true }).stdout
    current_boot_time_line = current_boot_time_str.lines.grep(/boot/).first

    raise Beaker::Host::RebootWarning, "Could not find system boot time using '#{boot_time_cmd}': '#{current_boot_time_str}'" unless current_boot_time_line

    current_boot_time_matches = current_boot_time_line.scan(boot_time_regex).last

    raise Beaker::Host::RebootWarning, "Found no valid times in '#{current_boot_time_line}'" unless current_boot_time_matches

    current_boot_time = Time.parse(current_boot_time_matches.first)

    @logger.debug("Original Boot Time: #{original_boot_time}")
    @logger.debug("Current Boot Time: #{current_boot_time}")

    # If this is *exactly* the same then there is really no good way to detect a reboot
    raise Beaker::Host::RebootFailure, "Boot time did not reset. Reboot appears to have failed." if current_boot_time == original_boot_time
  rescue ArgumentError => e
    raise Beaker::Host::RebootFailure, "Unable to parse time: #{e.message}"
  rescue Beaker::Host::RebootFailure => e
    raise
  rescue Beaker::Host::RebootWarning => e
    raise if attempts > uptime_retries

    @logger.warn(e.message)
    @logger.warn("Retrying #{uptime_retries - attempts} more times.")
    retry
  rescue StandardError => e
    raise if attempts > uptime_retries

    @logger.warn("Unexpected Exception: #{e.message}")
    @logger.warn("Retrying #{uptime_retries - attempts} more times.")
    @logger.warn(e.backtrace[0, 3].join("\n"))
    @logger.debug(e.backtrace.join("\n"))
    retry
  end
end
rm_rf(path) click to toggle source

Recursively remove the path provided @param [String] path The path to remove

# File lib/beaker/host/unix/exec.rb, line 158
def rm_rf path
  execute("rm -rf #{path}")
end
selinux_enabled?() click to toggle source

 Checks if selinux is enabled

@return [Boolean] true if selinux is enabled, false otherwise

# File lib/beaker/host/unix/exec.rb, line 413
def selinux_enabled?
  exec(Beaker::Command.new("sudo selinuxenabled"), :accept_all_exit_codes => true).exit_code == 0
end
ssh_permit_user_environment() click to toggle source

Sets the PermitUserEnvironment setting & restarts the SSH service.

@api private @return [Result] result of the command restarting the SSH service

(from {#ssh_service_restart}).
# File lib/beaker/host/unix/exec.rb, line 301
def ssh_permit_user_environment
  case self['platform']
  when /amazon|debian|ubuntu|archlinux|el-|centos|fedora|redhat|oracle|scientific|opensuse|sles|solaris/
    directory = tmpdir
    exec(Beaker::Command.new("echo 'PermitUserEnvironment yes' | cat - /etc/ssh/sshd_config > #{directory}/sshd_config.permit"))
    exec(Beaker::Command.new("mv #{directory}/sshd_config.permit /etc/ssh/sshd_config"))
    exec(Beaker::Command.new("echo '' >/etc/environment")) if self['platform'].include?('ubuntu-')
  when /(free|open)bsd/
    exec(Beaker::Command.new("sudo perl -pi -e 's/^#?PermitUserEnvironment no/PermitUserEnvironment yes/' /etc/ssh/sshd_config"), { :pty => true })
  else
    raise ArgumentError, "Unsupported Platform: '#{self['platform']}'"
  end

  ssh_service_restart
end
ssh_service_restart() click to toggle source

Restarts the SSH service.

@return [Result] result of restarting the SSH service

# File lib/beaker/host/unix/exec.rb, line 279
def ssh_service_restart
  case self['platform']
  when /debian|ubuntu/
    exec(Beaker::Command.new("systemctl restart ssh"))
  when /(el|centos|redhat|oracle|scientific)-[0-6]/
    exec(Beaker::Command.new("/sbin/service sshd restart"))
  when /solaris/
    exec(Beaker::Command.new("svcadm restart svc:/network/ssh:default"))
  when /(free|open)bsd/
    exec(Beaker::Command.new("sudo /etc/rc.d/sshd restart"))
  when /opensuse|sles/
    exec(Beaker::Command.new("/usr/sbin/rcsshd restart"))
  else
    exec(Beaker::Command.new("systemctl restart sshd.service"))
  end
end
ssh_set_user_environment(env) click to toggle source

Fills the user SSH environment file.

@param [Hash{String=>String}] env Environment variables to set on the system,

in the form of a hash of String variable
names to their corresponding String values.

@api private @return nil

# File lib/beaker/host/unix/exec.rb, line 388
def ssh_set_user_environment(env)
  # ensure that ~/.ssh/environment exists
  ssh_env_file_dir = Pathname.new(self[:ssh_env_file]).dirname
  mkdir_p(ssh_env_file_dir)
  exec(Beaker::Command.new("chmod 0600 #{ssh_env_file_dir}"))
  exec(Beaker::Command.new("touch #{self[:ssh_env_file]}"))
  # add the constructed env vars to this host
  add_env_var('PATH', '$PATH')
  if self['platform'].variant == 'openbsd'
    arch = self['platform'].arch
    arch = 'amd64' if %w[x64 x86_64].include?(arch)
    add_env_var('PKG_PATH', "http://ftp.openbsd.org/pub/OpenBSD/#{self['platform'].version}/packages/#{arch}/")
  elsif self['platform'].include?('solaris-10')
    add_env_var('PATH', '/opt/csw/bin')
  end

  # add the env var set to this test host
  env.each_pair do |var, value|
    add_env_var(var, value)
  end
end
touch(file, abs = true) click to toggle source
# File lib/beaker/host/unix/exec.rb, line 119
def touch(file, abs = true)
  (abs ? '/bin/touch' : 'touch') + " #{file}"
end
which(command) click to toggle source

First path it finds for the command executable @param [String] command The command executable to search for

@return [String] Path to the searched executable or empty string if not found

@example

host.which('ruby')
# File lib/beaker/host/unix/exec.rb, line 439
def which(command)
  unless @which_command
    if execute('type -P true', :accept_all_exit_codes => true).empty?
      raise ArgumentError, "Could not find suitable 'which' command" if execute('which true', :accept_all_exit_codes => true).empty?

      @which_command = 'which'

    else
      @which_command = 'type -P'
    end
  end

  result = execute("#{@which_command} #{command}", :accept_all_exit_codes => true)
  return '' if result.empty?

  result
end