class NoradSpecRunner::RemoteTask

Class to run Rspec tests over SSH

Constants

SSH_TIMEOUT

Attributes

cisco_enable_pw[R]
disable_sudo[R]
host[R]
obj[R]
platform[R]
results_file[R]
ssh_port[R]
sshkey[R]
tests_parent_dir[R]
username[R]

Public Class Methods

new(encoded_key, options) click to toggle source
# File lib/norad_spec_runner/remote_task.rb, line 16
def initialize(encoded_key, options)
  @host = options.fetch(:host)
  @sshkey = options.fetch(:sshkey)
  @cisco_enable_pw = options.fetch(:cisco_enable_pw, '')
  @disable_sudo = options.fetch(:disable_sudo, 'true')
  # Decode the key and store
  File.open(@sshkey, "w") do |f|
    f.write Base64.decode64(encoded_key)
  end
  @ssh_port = options.fetch(:port, nil)
  @username = options.fetch(:username, nil)
  @tests_parent_dir = ENV.fetch('TESTS_PARENT_DIR', '/')
  tests = options.fetch(:tests)
  detect_os = options.fetch(:detect_os, false)
  @obj = RSpec::Core::RakeTask.new(@host) do |_|
    true
  end

  @results_file = options.fetch(:results_file)
  @obj.pattern = detect_os ? autodetect_test_pattern(tests) : tests
  @obj.rspec_opts = build_rspec_opts(options)
rescue Exception => e
  write_error_to_results_file e.message
end

Public Instance Methods

run() click to toggle source
# File lib/norad_spec_runner/remote_task.rb, line 41
def run
  pstderr = STDERR.dup
  ftmp = Tempfile.open('eout')
  FileUtils.touch results_file
  return if platform.nil? && unable_to_ssh?

  ENV['AUDIT_HOST'] = host
  ENV['AUDIT_USERNAME'] = username
  ENV['AUDIT_SSHKEY'] = sshkey
  ENV['AUDIT_SSH_PORT'] = ssh_port
  ENV['AUDIT_CISCO_ENABLE_PASSWORD'] = cisco_enable_pw
  ENV['AUDIT_DISABLE_SUDO'] = disable_sudo.to_s

  # Capture STDERR for SSH related errors
  STDERR.reopen(ftmp)
  obj.run_task(true)
rescue SystemExit => e
  ftmp.rewind
  err = ftmp.read
  ftmp.close
  p err
  # We land here even on successful run (SystemExit exception), only report error if stderr is not empty
  if err =~ /Please set sudo password to Specinfra.configuration.sudo_password|set :request_pty, true/
    write_error_to_results_file "SSH user #{username} requires SUDO permission with NOPASSWD: option set."
  elsif not err.empty?
    write_error_to_results_file err
  end
rescue Exception => e
  # Unknown exception!
  write_error_to_results_file e.message
ensure
  STDERR.reopen pstderr
end

Private Instance Methods

autodetect_test_pattern(tests) click to toggle source
# File lib/norad_spec_runner/remote_task.rb, line 87
def autodetect_test_pattern(tests)
  pattern = select_spec_for_os(tests)
  puts "Trying to run #{pattern}"
  pattern
end
build_rspec_opts(options) click to toggle source
# File lib/norad_spec_runner/remote_task.rb, line 143
def build_rspec_opts(options)
  sub_tests = options.fetch(:sub_tests)
  rspec_opts = [ '--format NoradSpecRunner::CustomJsonFormatter',
                 "-r #{__dir__}/example.rb",
                 "-r #{__dir__}/formatter.rb",
                 "-o #{@results_file}"]
  options.fetch(:tags).split(',').each do |tag|
    rspec_opts << "--tag=#{tag}"
  end
  # --tag and --example do not work together so
  # if there is a tag disable example option
  if options.fetch(:tags) == ''
    rspec_opts << "-e #{sub_tests}"
  end
  rspec_opts.join(' ')
end
select_spec_for_os(test_name) click to toggle source
# File lib/norad_spec_runner/remote_task.rb, line 93
def select_spec_for_os(test_name)
  # Explicitly need this test case first
  if target_is_ios_device?(start_ssh_session)
    test_klass = SecTest
  elsif target_is_linux?(start_ssh_session)
    test_klass = LinuxSecTest
  else
    test_klass = SecTest
  end
  test_klass.new(tests_parent_dir, test_name, platform).test_to_run
end
start_ssh_session() click to toggle source
# File lib/norad_spec_runner/remote_task.rb, line 83
def start_ssh_session
  Net::SSH.start(host, username, port: ssh_port, timeout: SSH_TIMEOUT, auth_methods: ['publickey'], keys: [sshkey] )
end
target_is_ios_device?(ssh_session) click to toggle source
# File lib/norad_spec_runner/remote_task.rb, line 105
def target_is_ios_device?(ssh_session)
  out = ssh_session.exec!('show version')
  if out =~ /Cisco ((?:\w+\s?){1,6}) Software.+Version ([[:alnum:][:punct:]]+)/
    @platform = $1.gsub(/\s/, '_').downcase
  end
  !@platform.nil?
end
target_is_linux?(ssh_session) click to toggle source
# File lib/norad_spec_runner/remote_task.rb, line 113
def target_is_linux?(ssh_session)
  spec_infra_backend = Specinfra::Backend::Ssh.new
  spec_infra_backend.set_config(:ssh, ssh_session)
  spec_infra_backend.set_config(:ssh_options, { user: username })
  spec_infra_backend.set_config(:request_pty, true)
  Specinfra::Helper::DetectOs.subclasses.each do |c|
    detector = c.new(spec_infra_backend)
    res = detector.detect
    if res && res[:family]
      res[:arch] ||= spec_infra_backend.run_command('uname -m').stdout.strip
      @platform = res[:family] # we can optionally add release here if we want
      break
    end
  end
  !@platform.nil?
end
unable_to_ssh?() click to toggle source

Check if given host/ip is reachable and we can ssh as root If not, then create empty log file for that host and return false.

# File lib/norad_spec_runner/remote_task.rb, line 132
def unable_to_ssh?
  session = start_ssh_session
  session.exec('ls')
  session.close()
  false
rescue Net::SSH::AuthenticationFailed, Net::SSH::ConnectionTimeout, Net::SSH::Timeout, Net::SSH::Disconnect, Net::SSH::Exception, Errno::ECONNREFUSED, Errno::EHOSTUNREACH => e
  p e
  write_error_to_results_file e.message
  true
end
write_error_to_results_file(error) click to toggle source
# File lib/norad_spec_runner/remote_task.rb, line 77
def write_error_to_results_file(error)
  File.open(results_file, "w") do |f|
    f.write "!! NORAD SPECS SSH ERROR !!\nError: #{error}\n"
  end
end