class TungstenUtil
Attributes
Public Class Methods
# 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
# 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
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
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
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
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 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
# 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
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
# 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
# File lib/tungsten/util.rb, line 91 def display_help? (@display_help == true) end
# 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
# File lib/tungsten/util.rb, line 124 def enable_output? true end
# File lib/tungsten/util.rb, line 248 def error(message, hostname = nil) write(message, Logger::ERROR, hostname) end
# File lib/tungsten/util.rb, line 252 def exception(e, hostname = nil) error(e.to_s(), hostname) debug(e, hostname) end
# 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
# File lib/tungsten/util.rb, line 172 def force? @force end
# File lib/tungsten/util.rb, line 132 def force_output(content) log(content) puts(content) $stdout.flush() end
# 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
# 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
# File lib/tungsten/util.rb, line 120 def get_base_path File.expand_path("#{File.dirname(__FILE__)}/../../../..") end
# File lib/tungsten/exec.rb, line 42 def get_forward_log_level @forward_results_level end
# 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
# File lib/tungsten/util.rb, line 317 def get_log_level @logger_threshold end
# 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
# 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
# File lib/tungsten/exec.rb, line 374 def get_ssh_options @ssh_options end
# 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
# 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
# File lib/tungsten/exec.rb, line 443 def hostname `hostname 2>/dev/null`.chomp end
# File lib/tungsten/util.rb, line 236 def info(message, hostname = nil) write(message, Logger::INFO, hostname) end
# 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
# 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
# File lib/tungsten/util.rb, line 176 def is_valid? (@num_errors == 0 || @force == true) end
# 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
# 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
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
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
# File lib/tungsten/util.rb, line 240 def notice(message, hostname = nil) write(message, Logger::NOTICE, hostname) end
# File lib/tungsten/util.rb, line 128 def output(content) write(content, nil) end
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
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
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
# 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
# File lib/tungsten/util.rb, line 168 def reset_errors @num_errors = 0 end
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
# 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
# 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
# File lib/tungsten/util.rb, line 321 def set_log_level(level=Logger::INFO) @logger_threshold = level end
# 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 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
# 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
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
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
# File lib/tungsten/util.rb, line 516 def to_identifier(str) str.tr('.', '_').tr('-', '_').tr('/', '_').tr('\\', '_').downcase() end
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
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
# File lib/tungsten/util.rb, line 244 def warning(message, hostname = nil) write(message, Logger::WARN, hostname) end
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
# 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
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
# 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 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