<commit_before>package gridscale

import scala.util.Try

package object ssh {

  import cats._
  import cats.implicits._
  import gridscale.ssh.sshj.{ SFTPClient, SSHClient }
  import gridscale.authentication._
  import freedsl.dsl._
  import freedsl.system._
  import squants._
  import time.TimeConversions._
  import gridscale.tools.cache._

  object SSH {

    object Authentication {

      implicit def userPassword = new Authentication[UserPassword] {
        override def authenticate(t: UserPassword, sshClient: SSHClient): Try[Unit] =
          sshClient.authPassword(t.user, t.password)
      }

      implicit def key = new Authentication[PrivateKey] {
        override def authenticate(t: PrivateKey, sshClient: SSHClient): Try[Unit] =
          sshClient.authPrivateKey(t)
      }

    }

    trait Authentication[T] {
      def authenticate(t: T, sshClient: SSHClient): util.Try[Unit]
    }

    def interpreter = new Interpreter[Id] {

      def client(server: SSHServer) = {
        val ssh =
          util.Try {
            val ssh = new SSHClient
            // disable strict host key checking
            ssh.disableHostChecking()
            ssh.useCompression()
            ssh.setConnectTimeout(server.timeout.millis.toInt)
            ssh.setTimeout(server.timeout.millis.toInt)
            ssh.connect(server.host, server.port)
            ssh
          }.toEither.leftMap { t ⇒ ConnectionError(s"Error connecting to $server", t) }

        def authenticate(ssh: SSHClient) = {
          server.authenticate(ssh) match {
            case util.Success(_) ⇒ util.Right(ssh)
            case util.Failure(e) ⇒
              ssh.disconnect()
              util.Left(AuthenticationException(s"Error authenticating to $server", e))
          }
        }

        for {
          client ← ssh
          a ← authenticate(client)
        } yield a
      }

      val clientCache = KeyValueCache(client)

      def interpret[_] = {
        case execute(server, s) ⇒
          for {
            c ← clientCache.get(server)
            r ← SSHClient.exec(c, s).toEither.leftMap(t ⇒ ExecutionError(s"Error executing $s on $server", t))
          } yield r

        case sftp(server, f) ⇒
          for {
            c ← clientCache.get(server)
            r ← SSHClient.sftp(c, f).flatten.toEither.leftMap(t ⇒ SFTPError(s"Error in sftp transfer on $server", t))
          } yield r

        case readFile(server, path, f) ⇒
          for {
            c ← clientCache.get(server)
            res ← SSHClient.sftp(c, s ⇒ s.readAheadFileInputStream(path).map(f)).flatten.toEither.leftMap(t ⇒ SFTPError(s"Error in sftp transfer on $server", t))
          } yield res

        case wrongReturnCode(server, command, executionResult) ⇒ Left(ReturnCodeError(server, command, executionResult))
      }

      override def terminate = Right {
        clientCache.values.flatMap(_.right.toOption).foreach(_.close())
        clientCache.clear()
        ()
      }
    }

    case class ConnectionError(message: String, t: Throwable) extends Exception(message, t) with Error
    case class ExecutionError(message: String, t: Throwable) extends Exception(message, t) with Error
    case class SFTPError(message: String, t: Throwable) extends Exception(message, t) with Error
    case class ReturnCodeError(server: String, command: String, executionResult: ExecutionResult) extends Error {
      override def toString = ExecutionResult.error(command, executionResult) + " on server $server"
    }

  }

  @dsl trait SSH[M[_]] {
    def execute(server: SSHServer, s: String): M[ExecutionResult]
    def sftp[T](server: SSHServer, f: SFTPClient ⇒ util.Try[T]): M[T]
    def readFile[T](server: SSHServer, path: String, f: java.io.InputStream ⇒ T): M[T]
    def wrongReturnCode(server: String, command: String, executionResult: ExecutionResult): M[Unit]
  }

  object SSHServer {
    def apply[A: SSH.Authentication](host: String, port: Int = 22, timeout: Time = 1 minutes)(authentication: A): SSHServer =
      SSHServer(host, port, timeout, (sshClient: SSHClient) ⇒ implicitly[SSH.Authentication[A]].authenticate(authentication, sshClient))
  }
<commit_msg>Extend exception to get stack trace.<commit_after>package gridscale

import scala.util.Try

package object ssh {

  import cats._
  import cats.implicits._
  import gridscale.ssh.sshj.{ SFTPClient, SSHClient }
  import gridscale.authentication._
  import freedsl.dsl._
  import freedsl.system._
  import squants._
  import time.TimeConversions._
  import gridscale.tools.cache._

  object SSH {

    object Authentication {

      implicit def userPassword = new Authentication[UserPassword] {
        override def authenticate(t: UserPassword, sshClient: SSHClient): Try[Unit] =
          sshClient.authPassword(t.user, t.password)
      }

      implicit def key = new Authentication[PrivateKey] {
        override def authenticate(t: PrivateKey, sshClient: SSHClient): Try[Unit] =
          sshClient.authPrivateKey(t)
      }

    }

    trait Authentication[T] {
      def authenticate(t: T, sshClient: SSHClient): util.Try[Unit]
    }

    def interpreter = new Interpreter[Id] {

      def client(server: SSHServer) = {
        val ssh =
          util.Try {
            val ssh = new SSHClient
            // disable strict host key checking
            ssh.disableHostChecking()
            ssh.useCompression()
            ssh.setConnectTimeout(server.timeout.millis.toInt)
            ssh.setTimeout(server.timeout.millis.toInt)
            ssh.connect(server.host, server.port)
            ssh
          }.toEither.leftMap { t ⇒ ConnectionError(s"Error connecting to $server", t) }

        def authenticate(ssh: SSHClient) = {
          server.authenticate(ssh) match {
            case util.Success(_) ⇒ util.Right(ssh)
            case util.Failure(e) ⇒
              ssh.disconnect()
              util.Left(AuthenticationException(s"Error authenticating to $server", e))
          }
        }

        for {
          client ← ssh
          a ← authenticate(client)
        } yield a
      }

      val clientCache = KeyValueCache(client)

      def interpret[_] = {
        case execute(server, s) ⇒
          for {
            c ← clientCache.get(server)
            r ← SSHClient.exec(c, s).toEither.leftMap(t ⇒ ExecutionError(s"Error executing $s on $server", t))
          } yield r

        case sftp(server, f) ⇒
          for {
            c ← clientCache.get(server)
            r ← SSHClient.sftp(c, f).flatten.toEither.leftMap(t ⇒ SFTPError(s"Error in sftp transfer on $server", t))
          } yield r

        case readFile(server, path, f) ⇒
          for {
            c ← clientCache.get(server)
            res ← SSHClient.sftp(c, s ⇒ s.readAheadFileInputStream(path).map(f)).flatten.toEither.leftMap(t ⇒ SFTPError(s"Error in sftp transfer on $server", t))
          } yield res

        case wrongReturnCode(server, command, executionResult) ⇒ Left(ReturnCodeError(server, command, executionResult))
      }

      override def terminate = Right {
        clientCache.values.flatMap(_.right.toOption).foreach(_.close())
        clientCache.clear()
        ()
      }
    }

    case class ConnectionError(message: String, t: Throwable) extends Exception(message, t) with Error
    case class ExecutionError(message: String, t: Throwable) extends Exception(message, t) with Error
    case class SFTPError(message: String, t: Throwable) extends Exception(message, t) with Error
    case class ReturnCodeError(server: String, command: String, executionResult: ExecutionResult) extends Exception with Error {
      override def toString = ExecutionResult.error(command, executionResult) + " on server $server"
    }

  }

  @dsl trait SSH[M[_]] {
    def execute(server: SSHServer, s: String): M[ExecutionResult]
    def sftp[T](server: SSHServer, f: SFTPClient ⇒ util.Try[T]): M[T]
    def readFile[T](server: SSHServer, path: String, f: java.io.InputStream ⇒ T): M[T]
    def wrongReturnCode(server: String, command: String, executionResult: ExecutionResult): M[Unit]
  }

  object SSHServer {
    def apply[A: SSH.Authentication](host: String, port: Int = 22, timeout: Time = 1 minutes)(authentication: A): SSHServer =
      SSHServer(host, port, timeout, (sshClient: SSHClient) ⇒ implicitly[SSH.Authentication[A]].authenticate(authentication, sshClient))
  }

{'fraction_non_alphanumeric': 0.10455089820359281, 'fraction_numerical': 0.000718562874251497, 'mean_word_length': 2.661113546690048, 'pattern_counts': {'":': 0, '<': 3, '<?xml version=': 0, '>': 3, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '1784297', 'n_tokens_mistral': 2595, 'n_tokens_neox': 2464, 'n_words': 653}