class TrainSH::Cli

Constants

EXIT_COMMANDS
INTERACTIVE_COMMANDS

Public Class Methods

exit_on_failure?() click to toggle source

add_runtime_options!

# File lib/trainsh/cli.rb, line 24
def self.exit_on_failure?
  true
end

Public Instance Methods

__detect() click to toggle source

TODO def __detect_url(url)

url || kitchen_url || ENV['target']

end

# File lib/trainsh/cli.rb, line 53
def __detect
  raise 'No session in __detect' unless session

  platform = session.platform

  say format('Platform: %<title>s %<release>s (%<arch>s)',
             title:   platform.title,
             release: platform.release,
             arch:    platform.arch)
  say format('Hierarchy: %<hierarchy>s',
             hierarchy: platform.family_hierarchy.reverse.join('/'))
  say 'Measuring ping over connection...'

  # Idle command will also trigger discovery commands on first run
  session.run_idle

  say format('Ping: %<ping>dms', ping: session.ping) if session.ping
end
__disconnect() click to toggle source
# File lib/trainsh/cli.rb, line 42
def __disconnect
  session.close

  say 'Disconnected'
end
__print_version() click to toggle source
# File lib/trainsh/cli.rb, line 30
def __print_version
  say "#{TrainSH::PRODUCT} #{TrainSH::VERSION} (Ruby #{RUBY_VERSION}-#{RUBY_PLATFORM})"
end
auto_complete(partial) click to toggle source
# File lib/trainsh/cli.rb, line 157
def auto_complete(partial)
  choices = []

  choices.concat(builtin_commands.map { |cmd| "!#{cmd.tr('_', '-')}" })
  choices.concat(sessions.map { |session_id| "@#{session_id}" })
  choices.concat %w[!!!]

  choices.filter { |choice| choice.start_with? partial }
end
connect(url) click to toggle source
# File lib/trainsh/cli.rb, line 185
def connect(url)
  exit unless use_session(url)

  say format('Connected to %<url>s', url: session.url).bold
  say 'Running platform detection...'
  __detect

  # History persistence (TODO: Extract)
  user_conf_dir = File.join(ENV['HOME'], TrainSH::USER_CONF_DIR)
  history_file = File.join(user_conf_dir, 'history')
  FileUtils.mkdir_p(user_conf_dir)
  FileUtils.touch(history_file)
  File.readlines(history_file).each { |line| Readline::HISTORY.push line.strip }
  at_exit {
    history_file = File.join(user_conf_dir, 'history')
    File.open(history_file, 'w') { |f|
      f.write Readline::HISTORY.to_a.join("\n")
    }
  }

  # Catch Ctrl-C and exit cleanly
  stty_save = `stty -g`.chomp
  trap('INT') do
    puts '^C'
    system('stty', stty_save)
    exit
  end

  # Autocompletion
  Readline.completion_proc = method(:auto_complete).to_proc
  Readline.completion_append_character = ' '

  while (input = Readline.readline(prompt, true))
    if input.empty?
      Readline::HISTORY.pop
      next
    end

    Readline::HISTORY.pop if input.start_with? '!history'

    break if exit_command? input
    next if interactive_command? input

    execute input
  end
end
detect(url) click to toggle source
# File lib/trainsh/cli.rb, line 238
def detect(url)
  exit unless use_session(url)
  __detect
end
execute(input) click to toggle source
# File lib/trainsh/cli.rb, line 78
def execute(input)
  case input[0]
  when '.'
    execute_locally input[1..]
  when '!'
    execute_builtin input[1..]
  when '@'
    execute_via_session input[1..]
  else
    execute_via_train input
  end
end
execute_builtin(input) click to toggle source
# File lib/trainsh/cli.rb, line 104
def execute_builtin(input)
  cmd, *args = input.split

  ruby_cmd = cmd.tr('-', '_')

  if builtin_commands.include? ruby_cmd
    send("#{BUILTIN_PREFIX}#{ruby_cmd}".to_sym, *args)
  else
    say format('Unknown built-in "%<cmd>s"', cmd: cmd)
  end
end
execute_locally(input) click to toggle source
# File lib/trainsh/cli.rb, line 91
def execute_locally(input)
  system(input)
end
execute_via_session(input) click to toggle source
# File lib/trainsh/cli.rb, line 116
def execute_via_session(input)
  session_id, *data = input.split

  session_id = validate_session_id(session_id)
  if session_id.nil?
    say 'Expecting valid session id, e.g. `!session 2`'.red
    return
  end

  input = data.join(' ')
  if input.empty?
    say 'Specify command to execute, e.g. `@0 ls`'
    return
  end

  execute_via_train(input, session_id)
end
execute_via_train(input, session_id = current_session_id) click to toggle source
# File lib/trainsh/cli.rb, line 95
def execute_via_train(input, session_id = current_session_id)
  return if interactive_command? input

  command_result = @sessions[session_id].run(input)

  say command_result.stdout unless command_result.stdout && command_result.stdout.empty?
  say command_result.stderr.red unless command_result.stderr && command_result.stderr.empty?
end
exit_command?(cmd) click to toggle source
# File lib/trainsh/cli.rb, line 140
def exit_command?(cmd)
  EXIT_COMMANDS.include? cmd
end
interactive_command?(cmd) click to toggle source
# File lib/trainsh/cli.rb, line 134
def interactive_command?(cmd)
  return unless INTERACTIVE_COMMANDS.any? { |banned| cmd.start_with? banned }

  say 'Cannot execute interactive commands on non-tty sessions'.red
end
list_transports() click to toggle source
# File lib/trainsh/cli.rb, line 251
def list_transports
  # TODO: Train only lazy-loads and does not have a full registry
  #       https://github.com/inspec/train/blob/be90ca53ea1c1e8aa7439c504fbee86f4b399d83/lib/train.rb#L38-L61

  # TODO: Filter for only "OS" transports as well
  transports = %w[local ssh winrm docker]

  say "Available transports: #{transports.sort.join(', ')}"
end
prompt() click to toggle source
# File lib/trainsh/cli.rb, line 144
def prompt
  exitcode = current_session.exitcode
  exitcode_prefix = exitcode.zero? ? 'OK '.green : format('E%02d ', exitcode).red

  format(::TrainSH::PROMPT,
         exitcode:        exitcode,
         exitcode_prefix: exitcode_prefix,
         session_id:      current_session_id,
         backend:         current_session.backend || 'unknown',
         host:            current_session.host || 'unknown',
         path:            current_session.pwd || '?')
end
target_detectors() click to toggle source
# File lib/trainsh/cli.rb, line 72
def target_detectors
  Dir[File.join(__dir__, 'lib', '*.rb')].sort.each { |file| require file }

  TrainSH::Detectors::TargetDetector.descendants
end