class TungstenUtil

Attributes

extra_arguments[RW]
remaining_arguments[RW]

Public Class Methods

new() click to toggle source
Calls superclass method
# File lib/tungsten/exec.rb, line 6
def initialize()
  super()
  
  @log_results = false
  @forward_results = false
  @forward_results_level = Logger::INFO
end

Public Instance Methods

_is_localhost?(hostname) click to toggle source
# File lib/tungsten/exec.rb, line 456
def _is_localhost?(hostname)
  case hostname
  when DEFAULTS
    return false
  when hostname()
    return true
  when "localhost"
    return true
  when "127.0.0.1"
    return true
  when "::1"
    return true
  else
    ip_addresses = get_ip_addresses(hostname)
    if ip_addresses == false
      return false
    end

    debug("Search ifconfig for #{ip_addresses.join(', ')}")
    ipparsed = IPParse.new().get_interfaces()
    ipparsed.each{
      |iface, addresses|

      begin
        # Do a string comparison so that we only match the address portion
        addresses.each{
          |type, details|
          if ip_addresses.include?(details[:address])
            return true
          end
        }
      rescue ArgumentError
      end
    }

    return false
  end
end
cmd(command, ignore_fail = false, stdin_method = nil, stdout_method = nil, stderr_method = nil) click to toggle source

Run the {command} and return a string of STDOUT

# File lib/continuent-tools-core.rb, line 84
def cmd(command, ignore_fail = false, stdin_method = nil, stdout_method = nil, stderr_method = nil)
  errors = ""
  result = ""
  threads = []
  
  debug("Execute `#{command}`")
  status = Open4::popen4("export LANG=en_US; #{command}") do |pid, stdin, stdout, stderr|
    if stdin_method != nil
      threads << Thread.new{
        stdin_method.call(stdin)
        stdin.close
      }
    else
      stdin.close
    end
    
    threads << Thread.new{
      while data = stdout.gets()
        if data.to_s() != ""
          result+=data
          
          if data != "" && forward_cmd_results?()
            write(data, (parse_log_level(data) || get_forward_log_level()), nil, false)
          end
          
          if stdout_method != nil
            stdout_method.call(data)
          end
        end
      end
    }
    threads << Thread.new{
      while edata = stderr.gets()
        if edata.to_s() != ""
          errors+=edata
          
          if edata != "" && forward_cmd_results?()
            write(edata, (parse_log_level(edata) || get_forward_log_level()), nil, false)
          end

          if stderr_method != nil
            stderr_method.call(edata)
          end
        end
      end
    }
    
    threads.each{|t| t.join() }
  end
  
  result.strip!()
  errors.strip!()
  
  original_errors = errors
  rc = status.exitstatus
  if errors == ""
    errors = "No STDERR"
  else
    errors = "Errors: #{errors}"
  end

  if log_cmd_results?()
    debug("RC: #{rc}, Result: #{result}, #{errors}")
  elsif forward_cmd_results?()
    debug("RC: #{rc}, Result length #{result.length}, Errors length #{original_errors.length}")
  else
    debug("RC: #{rc}, Result length #{result.length}, #{errors}")
  end
  if rc != 0 && ! ignore_fail
    raise CommandError.new(command, rc, result, original_errors)
  end

  return result
end
cmd_result(command, ignore_fail = false) click to toggle source

Run the {command} and return a string of STDOUT

# File lib/tungsten/exec.rb, line 47
def cmd_result(command, ignore_fail = false)
  errors = ""
  result = ""
  threads = []
  
  debug("Execute `#{command}`")
  status = Open4::popen4("export LANG=en_US; #{command}") do |pid, stdin, stdout, stderr|
    stdin.close 
    
    threads << Thread.new{
      while data = stdout.gets()
        if data.to_s() != ""
          result+=data
          
          if data != "" && forward_cmd_results?()
            write(data, (parse_log_level(data) || get_forward_log_level()), nil, false)
          end
        end
      end
    }
    threads << Thread.new{
      while edata = stderr.gets()
        if edata.to_s() != ""
          errors+=edata
          
          if edata != "" && forward_cmd_results?()
            write(edata, (parse_log_level(edata) || get_forward_log_level()), nil, false)
          end
        end
      end
    }
    
    threads.each{|t| t.join() }
  end
  
  result.strip!()
  errors.strip!()
  
  original_errors = errors
  rc = status.exitstatus
  if errors == ""
    errors = "No STDERR"
  else
    errors = "Errors: #{errors}"
  end

  if log_cmd_results?()
    debug("RC: #{rc}, Result: #{result}, #{errors}")
  elsif forward_cmd_results?()
    debug("RC: #{rc}, Result length #{result.length}, Errors length #{original_errors.length}")
  else
    debug("RC: #{rc}, Result length #{result.length}, #{errors}")
  end
  if rc != 0 && ! ignore_fail
    raise CommandError.new(command, rc, result, original_errors)
  end

  return result
end
cmd_stderr(command, ignore_fail = false, &block) click to toggle source

Run the {command} and run {&block} for each line of STDERR

# File lib/tungsten/exec.rb, line 180
def cmd_stderr(command, ignore_fail = false, &block)
  errors = ""
  result = ""
  threads = []
  
  debug("Execute `#{command}`")
  status = Open4::popen4("export LANG=en_US; #{command}") do |pid, stdin, stdout, stderr|
    stdin.close 
    
    threads << Thread.new{
      while data = stdout.gets()
        if data.to_s() != ""
          result+=data
          
          if data != "" && forward_cmd_results?()
            write(data, (parse_log_level(data) || get_forward_log_level()), nil, false)
          end
        end
      end
    }
    threads << Thread.new{
      while edata = stderr.gets()
        if edata.to_s() != ""
          errors+=edata
          
          if edata != "" && forward_cmd_results?()
            write(edata, (parse_log_level(edata) || get_forward_log_level()), nil, false)
          end
          
          block.call(edata)
        end
      end
    }
    
    threads.each{|t| t.join() }
  end
  
  result.strip!()
  errors.strip!()
  
  original_errors = errors
  rc = status.exitstatus
  if errors == ""
    errors = "No STDERR"
  else
    errors = "Errors: #{errors}"
  end

  if log_cmd_results?()
    debug("RC: #{rc}, Result: #{result}, #{errors}")
  elsif forward_cmd_results?()
    debug("RC: #{rc}, Result length #{result.length}, Errors length #{original_errors.length}")
  else
    debug("RC: #{rc}, Result length #{result.length}, #{errors}")
  end
  if rc != 0 && ! ignore_fail
    raise CommandError.new(command, rc, result, original_errors)
  end

  return
end
cmd_stdout(command, ignore_fail = false, &block) click to toggle source

Run the {command} and run {&block} for each line of STDOUT

# File lib/tungsten/exec.rb, line 117
def cmd_stdout(command, ignore_fail = false, &block)
  errors = ""
  result = ""
  threads = []
  
  debug("Execute `#{command}`")
  status = Open4::popen4("export LANG=en_US; #{command}") do |pid, stdin, stdout, stderr|
    stdin.close 
    
    threads << Thread.new{
      while data = stdout.gets()
        if data.to_s() != ""
          result+=data
          
          if data != "" && forward_cmd_results?()
            write(data, (parse_log_level(data) || get_forward_log_level()), nil, false)
          end

          block.call(data)
        end
      end
    }
    threads << Thread.new{
      while edata = stderr.gets()
        if edata.to_s() != ""
          errors+=edata
          
          if edata != "" && forward_cmd_results?()
            write(edata, (parse_log_level(edata) || get_forward_log_level()), nil, false)
          end
        end
      end
    }
    
    threads.each{|t| t.join() }
  end
  
  result.strip!()
  errors.strip!()
  
  original_errors = errors
  rc = status.exitstatus
  if errors == ""
    errors = "No STDERR"
  else
    errors = "Errors: #{errors}"
  end

  if log_cmd_results?()
    debug("RC: #{rc}, Result: #{result}, #{errors}")
  elsif forward_cmd_results?()
    debug("RC: #{rc}, Result length #{result.length}, Errors length #{original_errors.length}")
  else
    debug("RC: #{rc}, Result length #{result.length}, #{errors}")
  end
  if rc != 0 && ! ignore_fail
    raise CommandError.new(command, rc, result, original_errors)
  end

  return
end
convert_ini_array_to_hash(options) click to toggle source

Convert the information returned by parse_ini_file into a nested hash of values

# File lib/tungsten/util.rb, line 602
def convert_ini_array_to_hash(options)
  hash = {}
  
  options.each{
    |section,lines|
    unless hash.has_key?(section)
      hash[section] = {}
    end
    
    lines.each{
      |line|
      parts = line.split("=")
      
      # The first part is the argument name
      argument = parts.shift()
      # Any remaining values will be returned as a single string
      # A nil value means there was no value after the '='
      if parts.size() == 0
        v = nil
      else
        v = parts.join("=")
      end
      
      # Return repeat arguments as an array of values
      if hash[section].has_key?(argument)
        unless hash[section][argument].is_a?(Array)
          hash[section][argument] = [hash[section][argument]]
        end
        
        hash[section][argument] << v
      else
        hash[section][argument] = v
      end
    }
  }
  
  return hash
end
debug(message, hostname = nil) click to toggle source
# File lib/tungsten/util.rb, line 257
def debug(message, hostname = nil)
  if message.is_a?(StandardError)
    message = message.to_s() + ":\n" + message.backtrace.join("\n")
  end
  write(message, Logger::DEBUG, hostname)
end
detect_terminal_size() click to toggle source

Returns [width, height] of terminal when detected, nil if not detected. Think of this as a simpler version of Highline’s Highline::SystemExtensions.terminal_size()

# File lib/tungsten/util.rb, line 415
def detect_terminal_size
  unless @terminal_size
    if (ENV['COLUMNS'] =~ /^\d+$/) && (ENV['LINES'] =~ /^\d+$/)
      @terminal_size = [ENV['COLUMNS'].to_i, ENV['LINES'].to_i]
    elsif (RUBY_PLATFORM =~ /java/ || (!STDIN.tty? && ENV['TERM'])) && command_exists?('tput')
      @terminal_size = [`tput cols`.to_i, `tput lines`.to_i]
    elsif STDIN.tty? && command_exists?('stty')
      @terminal_size = `stty size`.scan(/\d+/).map { |s| s.to_i }.reverse
    else
      @terminal_size = [80, 30]
    end
  end
  
  return @terminal_size
rescue => e
  [80, 30]
end
display_help() click to toggle source
# File lib/tungsten/util.rb, line 95
def display_help
  write_header("Global Options", nil)
  output_usage_line("--directory", "Use this installed Tungsten directory as the base for all operations")
  output_usage_line("--quiet, -q")
  output_usage_line("--info, -i")
  output_usage_line("--notice, -n")
  output_usage_line("--verbose, -v")
  output_usage_line("--help, -h", "Display this message")
  output_usage_line("--json", "Provide return code and logging messages as a JSON object after the script finishes")
  output_usage_line("--net-ssh-option=key=value", "Set the Net::SSH option for remote system calls", nil, nil, "Valid options can be found at http://net-ssh.github.com/ssh/v2/api/classes/Net/SSH.html#M000002")
end
display_help?() click to toggle source
# File lib/tungsten/util.rb, line 91
def display_help?
  (@display_help == true)
end
enable_log_level?(level=Logger::INFO) click to toggle source
# File lib/tungsten/util.rb, line 307
def enable_log_level?(level=Logger::INFO)
  if level == nil
    true
  elsif level < @logger_threshold
    false
  else
    true
  end
end
enable_output?() click to toggle source
# File lib/tungsten/util.rb, line 124
def enable_output?
  true
end
error(message, hostname = nil) click to toggle source
# File lib/tungsten/util.rb, line 248
def error(message, hostname = nil)
  write(message, Logger::ERROR, hostname)
end
exception(e, hostname = nil) click to toggle source
# File lib/tungsten/util.rb, line 252
def exception(e, hostname = nil)
  error(e.to_s(), hostname)
  debug(e, hostname)
end
exit(code = 0) click to toggle source
# File lib/tungsten/util.rb, line 80
def exit(code = 0)
  if @json_interface == true
    puts JSON.pretty_generate({
      "rc" => code,
      "messages" => @json_message_cache
    })
  end
  
  Kernel.exit(code)
end
force?() click to toggle source
# File lib/tungsten/util.rb, line 172
def force?
  @force
end
force_output(content) click to toggle source
# File lib/tungsten/util.rb, line 132
def force_output(content)
  log(content)
  
  puts(content)
  $stdout.flush()
end
forward_cmd_results?(v = nil, level = Logger::INFO) click to toggle source
# File lib/tungsten/exec.rb, line 29
def forward_cmd_results?(v = nil, level = Logger::INFO)
  if v != nil
    @forward_results = v
    @forward_results_level = level
  end
  
  if @forward_results == true
    true
  else
    false
  end
end
get_autocomplete_arguments() click to toggle source
# File lib/tungsten/util.rb, line 107
def get_autocomplete_arguments
  [
    '--directory',
    '--quiet', '-q',
    '--info', '-i',
    '--notice', '-n',
    '--verbose', '-v',
    '--help', '-h',
    '--json',
    '--net-ssh-option='
  ]
end
get_base_path() click to toggle source
# File lib/tungsten/util.rb, line 120
def get_base_path
  File.expand_path("#{File.dirname(__FILE__)}/../../../..")
end
get_forward_log_level() click to toggle source
# File lib/tungsten/exec.rb, line 42
def get_forward_log_level
  @forward_results_level
end
get_ip_addresses(hostname) click to toggle source
# File lib/tungsten/exec.rb, line 495
def get_ip_addresses(hostname)
  begin
    if hostname == DEFAULTS
      return false
    end
    
    ip_addresses = Timeout.timeout(5) {
      Resolv.getaddresses(hostname)
    }
    ip_addresses.delete_if{|ip| ip.to_s() == ""}
    
    if ip_addresses.length == 0
      begin
        ping_result = cmd_result("ping -c1 #{hostname} 2>/dev/null | grep PING")
        matches = ping_result.match("[0-9]+.[0-9]+.[0-9]+.[0-9]+")
        if matches && matches.size() > 0
          return [matches[0]]
        end
      rescue CommandError
      end
      
      warning "Unable to determine the IP addresses for '#{hostname}'"
      return false
    end
    
    return ip_addresses
  rescue Timeout::Error
    warning "Unable to lookup #{hostname} because of a DNS timeout"
    return false
  rescue => e
    warning "Unable to determine the IP addresses for '#{hostname}'"
    return false
  end
end
get_log_level() click to toggle source
# File lib/tungsten/util.rb, line 317
def get_log_level
  @logger_threshold
end
get_log_level_prefix(level=Logger::INFO, hostname = nil) click to toggle source
# File lib/tungsten/util.rb, line 264
def get_log_level_prefix(level=Logger::INFO, hostname = nil)
  case level
  when Logger::ERROR then prefix = "ERROR"
  when Logger::WARN then prefix = "WARN "
  when Logger::DEBUG then prefix = "DEBUG"
  when Logger::NOTICE then prefix = "NOTE "
  else
    prefix = "INFO "
  end
  
  if hostname == nil
    "#{prefix} >> "
  else
    "#{prefix} >> #{hostname} >> "
  end
end
get_ssh_command_options() click to toggle source
# File lib/tungsten/exec.rb, line 378
def get_ssh_command_options
  opts = ["-A", "-oStrictHostKeyChecking=no"]
  @ssh_options.each{
    |k,v|
    opts << "-o#{k.to_s()}=#{v}"
  }
  return opts.join(" ")
end
get_ssh_options() click to toggle source
# File lib/tungsten/exec.rb, line 374
def get_ssh_options
  @ssh_options
end
get_ssh_user(user = nil) click to toggle source
# File lib/tungsten/exec.rb, line 409
def get_ssh_user(user = nil)
  ssh_options = get_ssh_options
  if ssh_options.has_key?(:user) && ssh_options[:user].to_s != ""
    ssh_options[:user]
  else
    user
  end
end
get_tungsten_command_options() click to toggle source
# File lib/tungsten/exec.rb, line 387
def get_tungsten_command_options
  opts = []
  
  case get_log_level()
  when Logger::INFO then opts << "-i"
  when Logger::NOTICE then opts << "-n"
  when Logger::WARN then opts << "-q"
  when Logger::DEBUG then opts << "-v"
  end
  
  @ssh_options.each{
    |k,v|
    opts << "--net-ssh-option=#{k.to_s()}=#{v}"
  }
  
  if @force == true
    opts << "--force"
  end
  
  return opts.join(" ")
end
hostname() click to toggle source
# File lib/tungsten/exec.rb, line 443
def hostname
  `hostname 2>/dev/null`.chomp
end
info(message, hostname = nil) click to toggle source
# File lib/tungsten/util.rb, line 236
def info(message, hostname = nil)
  write(message, Logger::INFO, hostname)
end
is_localhost?(hostname) click to toggle source
# File lib/tungsten/exec.rb, line 447
def is_localhost?(hostname)
  @_is_localhost_cache ||= {}
  unless @_is_localhost_cache.has_key?(hostname)
    @_is_localhost_cache[hostname] = _is_localhost?(hostname)
  end

  return @_is_localhost_cache[hostname]
end
is_real_hostname?(hostname) click to toggle source
# File lib/tungsten/exec.rb, line 530
def is_real_hostname?(hostname)
  begin
    ip_addresses = Timeout.timeout(5) {
      Resolv.getaddresses(hostname)
    }
    ip_addresses.delete_if{|ip| ip.to_s() == ""}

    if ip_addresses.length == 0
      begin
        ping_result = cmd_result("ping -c1 #{hostname} 2>/dev/null | grep PING")
        matches = ping_result.match("[0-9]+.[0-9]+.[0-9]+.[0-9]+")
        if matches && matches.size() > 0
          ip_addresses = [matches[0]]
        end
      rescue CommandError
      end
    end
  rescue Timeout::Error
  rescue
  end
  
  if ip_addresses.size() == 0
    return false
  else
    return true
  end
end
is_valid?() click to toggle source
# File lib/tungsten/util.rb, line 176
def is_valid?
  (@num_errors == 0 || @force == true)
end
limit_file_permissions(path) click to toggle source
# File lib/tungsten/util.rb, line 157
def limit_file_permissions(path)
  if File.exists?(path)
    # Get values like 0660 or 0700 for the log and umask
    log_perms = sprintf("%o", File.stat(path).mode)[-4,4].to_i(8)
    max_perms = sprintf("%04d", 777-sprintf("%o", File.umask()).to_i()).to_i(8)
    # Calculate the target permissions for @log and set them
    set_to_perms = log_perms & max_perms
    File.chmod(sprintf("%o", set_to_perms).to_i(8), path)
  end
end
log(content = nil) click to toggle source
# File lib/tungsten/util.rb, line 139
def log(content = nil)
  if content == nil
    @log
  else
    @log.puts DateTime.now.to_s + " " + content
    @log.flush
  end
end
log_cmd_results?(v = nil) click to toggle source

Disable debug logging of command output

# File lib/tungsten/exec.rb, line 15
def log_cmd_results?(v = nil)
  if v != nil
    @log_results = v
  end
  
  if @log_results == nil && forward_cmd_results?() == true
    false
  elsif @log_results == false
    false
  else
    true
  end
end
mkdir_if_absent(dirname) click to toggle source

Create a directory if it is absent.

# File lib/tungsten/util.rb, line 521
def mkdir_if_absent(dirname)
  if dirname == nil
    return
  end
  
  if File.exists?(dirname)
    if File.directory?(dirname)
      debug("Found directory, no need to create: #{dirname}")
    else
      raise "Directory already exists as a file: #{dirname}"
    end
  else
    debug("Creating missing directory: #{dirname}")
    cmd_result("mkdir -p #{dirname}")
  end
end
notice(message, hostname = nil) click to toggle source
# File lib/tungsten/util.rb, line 240
def notice(message, hostname = nil)
  write(message, Logger::NOTICE, hostname)
end
output(content) click to toggle source
# File lib/tungsten/util.rb, line 128
def output(content)
  write(content, nil)
end
output_usage_line(argument, msg = "", default = nil, max_line = nil, additional_help = "") click to toggle source

Display information about the argument formatting {msg} so all lines appear formatted correctly

# File lib/tungsten/util.rb, line 435
def output_usage_line(argument, msg = "", default = nil, max_line = nil, additional_help = "")
  if max_line == nil
    max_line = detect_terminal_size()[0]-5
  end

  if msg.is_a?(String)
    msg = msg.split("\n").join(" ")
  else
    msg = msg.to_s()
  end

  msg = msg.gsub(/^\s+/, "").gsub(/\s+$/, $/)

  if default.to_s() != ""
    if msg != ""
      msg += " "
    end

    msg += "[#{default}]"
  end

  if argument.length > 28 || (argument.length + msg.length > max_line)
    output(argument)

    wrapped_lines(msg, 29).each{
      |line|
      output(line)
    }
  else
    output(format("%-29s", argument) + " " + msg)
  end

  if additional_help.to_s != ""
    additional_help = additional_help.split("\n").map!{
      |line|
      line.strip()
    }.join(' ')
    additional_help.split("<br>").each{
      |line|
      output_usage_line("", line, nil, max_line)
    }
  end
end
parse_ini_file(file, convert_to_hash = true) click to toggle source

Read an INI file and return a hash of the arguments for each section

# File lib/tungsten/util.rb, line 549
def parse_ini_file(file, convert_to_hash = true)
  unless defined?(IniParse)
    require 'tungsten/iniparse'
  end
  
  unless File.exists?(file)
    return
  end
  
  # Read each section and turn the lines into an array of arguments as
  # they would appear in the INI file
  options = {}
  ini = IniParse.open(file)
  ini.each{
    |section|
    key = section.key
    
    # Initialize the array only if it doesn't exist
    # Doing otherwise would overwrite old sections
    unless options.has_key?(key)
      options[key] = []
    end
    
    section.each{
      |line|
      unless line.is_a?(Array)
        values = [line]
      else
        values = line
      end
    
      values.each{
        |value|
        if value.value == nil
          options[key] << "#{value.key}"
        else
          options[key] << "#{value.key}=#{value.value.to_s()}"
        end
      }
    }
  }
  
  # Most users will want a Hash, but this will allow the main
  # TungstenScript class to see the parameters in order
  if convert_to_hash == true
    return convert_ini_array_to_hash(options)
  else
    return options
  end
end
parse_log_level(line) click to toggle source

Override the method imported from tpm to include ‘>>’ to avoid detecting an error that wasn’t thrown by TungstenScript

# File lib/continuent-tools-core.rb, line 25
def parse_log_level(line)
  prefix = line[0,8]
  
  case prefix.strip
  when "ERROR >>" then Logger::ERROR
  when "WARN  >>" then Logger::WARN
  when "DEBUG >>" then Logger::DEBUG
  when "NOTE  >>" then Logger::NOTICE
  when "INFO  >>" then Logger::INFO
  else
    nil
  end
end
pluralize(array, singular, plural = nil) click to toggle source
# File lib/tungsten/util.rb, line 538
def pluralize(array, singular, plural = nil)
  if plural == nil
    singular
  elsif array.size() > 1
    plural
  else
    singular
  end
end
reset_errors() click to toggle source
# File lib/tungsten/util.rb, line 168
def reset_errors
  @num_errors = 0
end
run_option_parser(opts, arguments = nil, allow_invalid_options = true, invalid_option_prefix = nil) click to toggle source

Run the OptionParser object against @remaining_arguments or the {arguments} passed in. If no {arguments} value is given, the function will read from @remaining_arguments and update it with any remaining values

# File lib/tungsten/util.rb, line 339
def run_option_parser(opts, arguments = nil, allow_invalid_options = true, invalid_option_prefix = nil)
  if arguments == nil
    use_remaining_arguments = true
    arguments = @remaining_arguments
  else
    use_remaining_arguments = false
  end
  
  # Collect the list of options and remove the first two that are
  # created by default
  option_lists = opts.stack()
  option_lists.shift()
  option_lists.shift()
  
  option_lists.each{
    |ol|
    
    ol.short.keys().each{
      |arg|
      if @previous_option_arguments.has_key?(arg)
        error("The -#{arg} argument has already been captured")
      end
      @previous_option_arguments[arg] = true
    }
    ol.long.keys().each{
      |arg|
      if @previous_option_arguments.has_key?(arg)
        error("The --#{arg} argument has already been captured")
      end
      @previous_option_arguments[arg] = true
    }
  }
  
  remainder = []
  while arguments.size() > 0
    begin
      arguments = opts.order!(arguments)
      
      # The next argument does not have a dash so the OptionParser
      # ignores it, we will add it to the stack and continue
      if arguments.size() > 0 && (arguments[0] =~ /-.*/) != 0
        remainder << arguments.shift()
      end
    rescue OptionParser::InvalidOption => io
      if allow_invalid_options
        # Prepend the invalid option onto the arguments array
        remainder = remainder + io.recover([])
      
        # The next argument does not have a dash so the OptionParser
        # ignores it, we will add it to the stack and continue
        if arguments.size() > 0 && (arguments[0] =~ /-.*/) != 0
          remainder << arguments.shift()
        end
      else
        if invalid_option_prefix != nil
          io.reason = invalid_option_prefix
        end
        
        raise io
      end
    rescue => e
      exception(e)
      raise "Argument parsing failed: #{e.to_s()}"
    end
  end
  
  if use_remaining_arguments == true
    @remaining_arguments = remainder
    return nil
  else
    return remainder
  end
end
scp_download(remote_file, local_file, host, user) click to toggle source
# File lib/continuent-tools-core.rb, line 214
def scp_download(remote_file, local_file, host, user)
  if is_localhost?(host) && user == whoami()
    debug("Copy #{remote_file} to #{local_file}")
    return FileUtils.cp(remote_file, local_file)
  end

  begin
    exists = ssh_result("if [ -f #{remote_file} ]; then echo 0; else echo 1; fi", host, user)
    if exists == "1"
      raise MessageError.new("Unable to download '#{remote_file}' because the file does not exist on #{host}")
    end
  rescue CommandError
    raise MessageError.new("Unable to check if '#{remote_file}' exists on #{host}")
  end

  self.synchronize() {
    unless defined?(Net::SCP)
      begin
        require "openssl"
      rescue LoadError
        raise("Unable to find the Ruby openssl library. Try installing the openssl package for your version of Ruby (libopenssl-ruby#{RUBY_VERSION[0,3]}).")
      end
      require 'net/scp'
    end
  }

  ssh_user = get_ssh_user(user)
  if user != ssh_user
    debug("SCP user changed to #{ssh_user}")
  end

  connection_error = "Net::SCP was unable to copy to #{host}:#{remote_file} #{local_file} as #{ssh_user}.  Check that #{host} is online, #{ssh_user} exists and your SSH private keyfile or ssh-agent settings. Try adding --net-ssh-option=port=<SSH port number> if you are using an SSH port other than 22.  Review http://docs.continuent.com/helpwithsshandtpm for more help on diagnosing SSH problems."
  debug("Copy #{host}:#{remote_file} to #{local_file} as #{ssh_user}")
  begin
    Net::SCP.download!(host, ssh_user, remote_file, local_file, get_ssh_options)

    return true
  rescue Errno::ENOENT => ee
    raise MessageError.new("Net::SCP was unable to find a private key to use for SSH authenticaton. Try creating a private keyfile or setting up ssh-agent.")
  rescue OpenSSL::PKey::RSAError
    raise MessageError.new(connection_error)
  rescue Net::SSH::AuthenticationFailed
    raise MessageError.new(connection_error)
  rescue Errno::ECONNREFUSED, Errno::EHOSTUNREACH
    raise MessageError.new(connection_error)
  rescue Timeout::Error
    raise MessageError.new(connection_error)
  rescue Exception => e
    raise RemoteCommandError.new(user, host, "scp #{ssh_user}@#{host}:#{remote_file} #{local_file}", nil, '')
  end
end
scp_result(local_file, remote_file, host, user) click to toggle source
# File lib/continuent-tools-core.rb, line 159
def scp_result(local_file, remote_file, host, user)
  unless File.file?(local_file)
    debug("Unable to copy '#{local_file}' because it doesn't exist")
    raise MessageError.new("Unable to copy '#{local_file}' because it doesn't exist")
  end

  if is_localhost?(host) && 
      user == whoami()
    debug("Copy #{local_file} to #{remote_file}")
    return FileUtils.cp(local_file, remote_file)
  end

  self.synchronize() {
    unless defined?(Net::SCP)
      begin
        require "openssl"
      rescue LoadError
        raise("Unable to find the Ruby openssl library. Try installing the openssl package for your version of Ruby (libopenssl-ruby#{RUBY_VERSION[0,3]}).")
      end
      require 'net/scp'
    end
  }

  ssh_user = get_ssh_user(user)
  if user != ssh_user
    debug("SCP user changed to #{ssh_user}")
  end

  connection_error = "Net::SCP was unable to copy #{local_file} to #{host}:#{remote_file} as #{ssh_user}.  Check that #{host} is online, #{ssh_user} exists and your SSH private keyfile or ssh-agent settings. Try adding --net-ssh-option=port=<SSH port number> if you are using an SSH port other than 22.  Review http://docs.continuent.com/helpwithsshandtpm for more help on diagnosing SSH problems."
  debug("Copy #{local_file} to #{host}:#{remote_file} as #{ssh_user}")
  begin
    Net::SCP.start(host, ssh_user, get_ssh_options) do |scp|
      scp.upload!(local_file, remote_file, get_ssh_options)
    end

    if user != ssh_user
      ssh_result("sudo -n chown -R #{user} #{remote_file}", host, ssh_user)
    end

    return true
  rescue Errno::ENOENT => ee
    raise MessageError.new("Net::SCP was unable to find a private key to use for SSH authenticaton. Try creating a private keyfile or setting up ssh-agent.")
  rescue OpenSSL::PKey::RSAError
    raise MessageError.new(connection_error)
  rescue Net::SSH::AuthenticationFailed
    raise MessageError.new(connection_error)
  rescue Errno::ECONNREFUSED, Errno::EHOSTUNREACH
    raise MessageError.new(connection_error)
  rescue Timeout::Error
    raise MessageError.new(connection_error)
  rescue Exception => e
    raise RemoteCommandError.new(user, host, "scp #{local_file} #{ssh_user}@#{host}:#{remote_file}", nil, '')
  end
end
set_log_level(level=Logger::INFO) click to toggle source
# File lib/tungsten/util.rb, line 321
def set_log_level(level=Logger::INFO)
  @logger_threshold = level
end
set_log_path(path) click to toggle source
# File lib/tungsten/util.rb, line 148
def set_log_path(path)
  TU.mkdir_if_absent(File.dirname(path))
  old_log = @log
  old_log.rewind()
  
  @log = File.open(path, "w")
  @log.puts(old_log.read())
end
split_log_content(content) click to toggle source

Split a log line into the log level and actual message This is used when forwarding log messages from a remote commmand

# File lib/tungsten/util.rb, line 297
def split_log_content(content)
  level = parse_log_level(content)
  if level != nil
    prefix = get_log_level_prefix(level)
    content = content[prefix.length, content.length]
  end
  
  return [level, content]
end
ssh_init_profile_script() click to toggle source
# File lib/tungsten/exec.rb, line 418
def ssh_init_profile_script
  if @ssh_init_profile_script == nil
    init_profile_script_parts = []
    [
      "$HOME/.bash_profile",
      "$HOME/.bash_login",
      "$HOME/.profile"
    ].each{
      |filename|

      if init_profile_script_parts.size() == 0
        init_profile_script_parts << "if"
      else
        init_profile_script_parts << "elif"
      end

      init_profile_script_parts << "[ -f #{filename} ]; then . #{filename};"
    }
    init_profile_script_parts << "fi;"
    @ssh_init_profile_script = init_profile_script_parts.join(" ")
  end

  return @ssh_init_profile_script
end
ssh_result(command, host, user) click to toggle source

Run the {command} on {host} as {user} Return a string of STDOUT

# File lib/tungsten/exec.rb, line 274
def ssh_result(command, host, user)
  if host == DEFAULTS
    debug("Unable to run '#{command}' because '#{host}' is not valid")
    raise RemoteCommandError.new(user, host, command, nil, '')
  end

  # Run the command outside of SSH if possible
  if is_localhost?(host) && 
      user == whoami()
    return cmd_result(command)
  end

  unless defined?(Net::SSH)
    begin
      require "openssl"
    rescue LoadError
      raise("Unable to find the Ruby openssl library. Try installing the openssl package for your version of Ruby (libopenssl-ruby#{RUBY_VERSION[0,3]}).")
    end
    require 'net/ssh'
  end
  
  ssh_user = get_ssh_user(user)
  if user != ssh_user
    debug("SSH user changed to #{ssh_user}")
    command = command.tr('"', '\"')
    command = "echo \"#{command}\" | sudo -u #{user} -i"
  end

  debug("Execute `#{command}` on #{host} as #{user}")
  result = ""
  rc = nil
  exit_signal=nil

  connection_error = "Net::SSH was unable to connect to #{host} as #{ssh_user}.  Check that #{host} is online, #{ssh_user} exists and your SSH private keyfile or ssh-agent settings. Try adding --net-ssh-option=port=<SSH port number> if you are using an SSH port other than 22.  Review http://docs.continuent.com/helpwithsshandtpm for more help on diagnosing SSH problems."
  begin
    Net::SSH.start(host, ssh_user, get_ssh_options()) {
      |ssh|
      stdout_data = ""

      ssh.open_channel do |channel|
        channel.exec(". /etc/profile; #{ssh_init_profile_script()} export LANG=en_US; export LC_ALL=\"en_US.UTF-8\"; #{command}") do |ch, success|
          channel.on_data do |ch,data|
            stdout_data+=data
            
            if data != "" && forward_cmd_results?()
              log_level,log_data = split_log_content(data)
              write(log_data, (log_level || get_forward_log_level()), host)
            end
          end

          channel.on_extended_data do |ch,type,data|
            data = data.chomp
            
            if data != "" && forward_cmd_results?()
              log_level,log_data = split_log_content(data)
              write(log_data, (log_level || get_forward_log_level()), host)
            end
          end

          channel.on_request("exit-status") do |ch,data|
            rc = data.read_long
          end

          channel.on_request("exit-signal") do |ch, data|
            exit_signal = data.read_long
          end
        end
      end
      ssh.loop
      result = stdout_data.to_s.chomp
    }
  rescue Errno::ENOENT => ee
    raise MessageError.new("Net::SSH was unable to find a private key to use for SSH authenticaton. Try creating a private keyfile or setting up ssh-agent.")
  rescue OpenSSL::PKey::RSAError
    raise MessageError.new(connection_error)
  rescue Net::SSH::AuthenticationFailed
    raise MessageError.new(connection_error)
  rescue Errno::ECONNREFUSED, Errno::EHOSTUNREACH, Errno::EHOSTDOWN
    raise MessageError.new(connection_error)
  rescue Timeout::Error
    raise MessageError.new(connection_error)
  rescue NotImplementedError => nie
    raise MessageError.new(nie.message + ". Try modifying your ~/.ssh/config file to define values for Cipher and Ciphers that do not include this algorithm.  The supported encryption algorithms are #{Net::SSH::Transport::CipherFactory::SSH_TO_OSSL.keys().delete_if{|e| e == "none"}.join(", ")}.")
  rescue => e
    raise e
  end

  if rc != 0
    raise RemoteCommandError.new(user, host, command, rc, result)
  else
    if log_cmd_results?()
      debug("RC: #{rc}, Result: #{result}")
    else
      debug("RC: #{rc}, Result length #{result.length}")
    end
  end

  return result
end
test_ssh(host, user) click to toggle source

Run a standard check to see if SSH connectivity to the host works

# File lib/tungsten/exec.rb, line 243
def test_ssh(host, user)
  begin
    login_result = ssh_result("whoami", host, user)
    
    if login_result != user
      if login_result=~ /#{user}/
          error "SSH to #{host} as #{user} is returning more than one line."
          error "Ensure that the .bashrc and .bash_profile files are not printing messages on #{host} with out using a test like this. if [ \"$PS1\" ]; then echo \"Your message here\"; fi"
          error "If you are using the 'Banner' argument in /etc/ssh/sshd_config, try putting the contents into /etc/motd"
      else
        error "Unable to SSH to #{host} as #{user}."

        if is_localhost?(host)
          error "Try running the command as #{user}"
        else
          error "Ensure that the host is running and that you can login as #{user} via SSH using key authentication"
        end
      end
      
      return false
    else
      return true
    end
  rescue => e
    exception(e)
    return false
  end
end
to_identifier(str) click to toggle source
# File lib/tungsten/util.rb, line 516
def to_identifier(str)
  str.tr('.', '_').tr('-', '_').tr('/', '_').tr('\\', '_').downcase()
end
tungsten_cmd_result(command) click to toggle source

A wrapper for running another Tungsten script. This will automatically forward messages to the console and add any TungstenScript options to the command.

# File lib/continuent-tools-core.rb, line 42
def tungsten_cmd_result(command)
  original_fwd_state = forward_cmd_results?()
  begin
    if TI
      prefix = "export CONTINUENT_ROOT=#{TI.root()}; "
    else
      prefix = ""
    end
    
    forward_cmd_results?(true)
    return cmd_result("#{prefix}#{command} #{get_tungsten_command_options()}")
  ensure
    forward_cmd_results?(original_fwd_state)
  end
end
tungsten_ssh_result(command, host, user) click to toggle source

A wrapper for running another Tungsten script on a remote host. This will automatically forward messages to the console and add any TungstenScript options to the command.

# File lib/continuent-tools-core.rb, line 61
def tungsten_ssh_result(command, host, user)
  # Run the command outside of SSH if possible
  if is_localhost?(host) && 
      user == whoami()
    return tungsten_cmd_result(command)
  end
  
  original_fwd_state = forward_cmd_results?()
  begin
    if TI
      prefix = "export CONTINUENT_ROOT=#{TI.root()}; "
    else
      prefix = ""
    end
    
    forward_cmd_results?(true)
    return ssh_result("#{prefix}#{command} #{get_tungsten_command_options()}", host, user)
  ensure
    forward_cmd_results?(original_fwd_state)
  end
end
warning(message, hostname = nil) click to toggle source
# File lib/tungsten/util.rb, line 244
def warning(message, hostname = nil)
  write(message, Logger::WARN, hostname)
end
which(cmd) click to toggle source

Find out the full executable path or return nil if this is not executable.

# File lib/tungsten/exec.rb, line 560
def which(cmd)
  if ! cmd
    nil
  else 
    path = cmd_result("which #{cmd} 2>/dev/null", true)
    path.chomp!
    if File.executable?(path)
      path
    else
      nil
    end
  end
end
whoami() click to toggle source
# File lib/tungsten/util.rb, line 325
def whoami
  if ENV['USER']
    ENV['USER']
  elsif ENV['LOGNAME']
    ENV['LOGNAME']
  else
    `whoami 2>/dev/null`.chomp
  end
end
wrapped_lines(msg, offset = 0, max_line = nil) click to toggle source

Break {msg} into lines that have {offset} leading spaces and do not exceed {max_line}

# File lib/tungsten/util.rb, line 481
def wrapped_lines(msg, offset = 0, max_line = nil)
  if max_line == nil
    max_line = detect_terminal_size()[0]-5
  end
  if offset == 0
    default_line = ""
  else
    line_format = "%-#{offset}s"
    default_line = format(line_format, " ")
  end
  
  lines = []
  words = msg.split(' ')

  force_add_word = true
  line = default_line.dup()
  while words.length() > 0
    if !force_add_word && line.length() + words[0].length() > max_line
      lines << line
      line = default_line.dup()
      force_add_word = true
    else
      if line == ""
        line = words.shift()
      else
        line += " " + words.shift()
      end
      force_add_word = false
    end
  end
  lines << line
  
  return lines
end
write(content="", level=Logger::INFO, hostname = nil, add_prefix = true) click to toggle source
# File lib/tungsten/util.rb, line 180
def write(content="", level=Logger::INFO, hostname = nil, add_prefix = true)
  if content.is_a?(Array)
    content.each{
      |c|
      write(c, level, hostname, add_prefix)
    }
    return
  end
  
  # Attempt to determine the level for this message based on it's content
  # If it is forwarded from another Tungsten script it will have a prefix
  # so we know to use stdout or stderr
  if level == nil
    level = parse_log_level(content)
  end
  
  if level == Logger::ERROR
    @error_mutex.synchronize do
      @num_errors = @num_errors + 1
    end
    
    if force?()
      level = Logger::WARN
    end
  end
  
  unless content == "" || level == nil || add_prefix == false
    content = "#{get_log_level_prefix(level, hostname)}#{content}"
  end
  
  log(content)
  
  if enable_log_level?(level)
    if @json_interface == true
      @json_message_cache << content
    else
      if enable_output?()
        if level != nil && level > Logger::NOTICE
          $stdout.puts(content)
          $stdout.flush()
        else
          $stdout.puts(content)
          $stdout.flush()
        end
      end
    end
  end
end
write_header(content, level=Logger::INFO) click to toggle source

Write a header

# File lib/tungsten/util.rb, line 230
def write_header(content, level=Logger::INFO)
  write("#####################################################################", level, nil, false)
  write("# #{content}", level, nil, false)
  write("#####################################################################", level, nil, false)
end