mattbrictson/tomo

View on GitHub
lib/tomo/ssh/connection_validator.rb

Summary

Maintainability
A
0 mins
Test Coverage
require "forwardable"

module Tomo
  module SSH
    class ConnectionValidator
      MINIMUM_OPENSSH_VERSION = 7.4
      private_constant :MINIMUM_OPENSSH_VERSION

      extend Forwardable

      def initialize(executable, connection)
        @executable = executable
        @connection = connection
      end

      def assert_valid_executable!
        result = begin
          ChildProcess.execute(executable, "-V")
        rescue StandardError => e
          handle_bad_executable(e)
        end

        Tomo.logger.debug(result.output)
        return if result.success? && supported?(result.output)

        raise_unsupported_version(result.output)
      end

      def assert_valid_connection!
        script = Script.new("echo hi", silent: !Tomo.debug?, echo: false, raise_on_error: false)
        res = connection.ssh_subprocess(script, verbose: Tomo.debug?)
        raise_connection_failure(res) if res.exit_status == 255
        raise_unknown_error(res) if res.failure? || res.stdout.chomp != "hi"
      end

      def dump_env
        script = Script.new("env", silent: true, echo: false)
        res = connection.ssh_subprocess(script)
        Tomo.logger.debug("#{host} environment:\n#{res.stdout.strip}")
      end

      private

      def_delegators :connection, :host
      attr_reader :executable, :connection

      def supported?(version)
        version[/OpenSSH_(\d+\.\d+)/i, 1].to_f >= MINIMUM_OPENSSH_VERSION
      end

      def handle_bad_executable(error)
        ExecutableError.raise_with(error, executable:)
      end

      def raise_unsupported_version(ver)
        UnsupportedVersionError.raise_with(
          ver,
          host:,
          command: "#{executable} -V",
          expected_version: "OpenSSH_#{MINIMUM_OPENSSH_VERSION}"
        )
      end

      def raise_connection_failure(result)
        case result.output
        when /Permission denied/i
          PermissionError.raise_with(result.output, host:)
        when /(Could not resolve|Operation timed out|Connection refused)/i
          ConnectionError.raise_with(result.output, host:)
        else
          UnknownError.raise_with(result.output, host:)
        end
      end

      def raise_unknown_error(result)
        UnknownError.raise_with(<<~ERROR.strip, host:)
          Unexpected output from `ssh`. Expected `echo hi` to return "hi" but got:
          #{result.output}
          (exited with code #{result.exit_status})
        ERROR
      end
    end
  end
end