Repository: 7mind/izumi
Pull request #1100 (2020-06-05 15:38:43)

Fix Java 1.8 compatibility for PostreSqlProtocolCheck


Patch:
diff --git a/.azure-dockerstep.yml b/.azure-dockerstep.yml
index 374542ce6..f71c812e0 100644
--- a/.azure-dockerstep.yml
+++ b/.azure-dockerstep.yml
@@ -8,7 +8,6 @@ jobs:
             ${{ pair.key }}: ${{ pair.value }}
         pool:
           vmImage: 'ubuntu-latest'
-        container: septimalmind/izumi-env:jdk11-7
         steps:
           - task: CacheBeta@0
             inputs:
diff --git a/distage/distage-core-api/src/main/scala/izumi/distage/model/reflection/SafeType.scala b/distage/distage-core-api/src/main/scala/izumi/distage/model/reflection/SafeType.scala
index ac0f9e1d6..234802aea 100644
--- a/distage/distage-core-api/src/main/scala/izumi/distage/model/reflection/SafeType.scala
+++ b/distage/distage-core-api/src/main/scala/izumi/distage/model/reflection/SafeType.scala
@@ -4,10 +4,10 @@ import izumi.fundamentals.platform.functional.Identity
 import izumi.reflect.macrortti.LightTypeTag
 import izumi.reflect.{Tag, TagK, WeakTag}
 
-final case class SafeType private(
-                                   tag: LightTypeTag,
-                                   /*private[distage] val */ cls: Class[_],
-                                 ) {
+final case class SafeType private (
+  tag: LightTypeTag,
+  /*private[distage] val */ cls: Class[_],
+) {
   override final lazy val hashCode: Int = tag.hashCode()
   override final def toString: String = tag.repr
 
@@ -29,8 +29,10 @@ final case class SafeType private(
   }
 
   final def hasPreciseClass: Boolean = {
-    try tag.shortName == cls.getSimpleName catch {
-      case i: InternalError if i.getMessage == "Malformed class name" => false
+    try tag.shortName == cls.getSimpleName
+    catch {
+      case i: InternalError if i.getMessage == "Malformed class name" =>
+        tag.shortName == cls.getName.reverse.takeWhile(c => c != '$' && c != '.').reverse
     }
   }
 }
diff --git a/distage/distage-framework-docker/src/main/scala/izumi/distage/docker/DockerContainer.scala b/distage/distage-framework-docker/src/main/scala/izumi/distage/docker/DockerContainer.scala
index c76b28fb7..5c104b563 100644
--- a/distage/distage-framework-docker/src/main/scala/izumi/distage/docker/DockerContainer.scala
+++ b/distage/distage-framework-docker/src/main/scala/izumi/distage/docker/DockerContainer.scala
@@ -55,10 +55,8 @@ object DockerContainer {
       */
     def modifyConfig(
       modify: ProviderMagnet[Docker.ContainerConfig[T] => Docker.ContainerConfig[T]]
-    )(implicit tag1: distage.Tag[ContainerResource[F, T]],
-      tag2: distage.Tag[Docker.ContainerConfig[T]],
+    )(implicit tag: distage.Tag[ContainerResource[F, T]]
     ): ProviderMagnet[ContainerResource[F, T]] = {
-      tag2.discard()
       self.zip(modify).map {
         case (that, f) =>
           import that._
diff --git a/distage/distage-framework-docker/src/main/scala/izumi/distage/docker/examples/PostgresDocker.scala b/distage/distage-framework-docker/src/main/scala/izumi/distage/docker/examples/PostgresDocker.scala
index 724110e10..124781848 100644
--- a/distage/distage-framework-docker/src/main/scala/izumi/distage/docker/examples/PostgresDocker.scala
+++ b/distage/distage-framework-docker/src/main/scala/izumi/distage/docker/examples/PostgresDocker.scala
@@ -14,7 +14,7 @@ object PostgresDocker extends ContainerDef {
       image = "library/postgres:12.2",
       ports = Seq(primaryPort),
       env = Map("POSTGRES_PASSWORD" -> "postgres"),
-      healthCheck = ContainerHealthCheck.postgreSqlProtocolCheck,
+      healthCheck = ContainerHealthCheck.postgreSqlProtocolCheck(primaryPort, "postgres", "postgres"),
     )
   }
 }
diff --git a/distage/distage-framework-docker/src/main/scala/izumi/distage/docker/healthcheck/ContainerHealthCheck.scala b/distage/distage-framework-docker/src/main/scala/izumi/distage/docker/healthcheck/ContainerHealthCheck.scala
index 3df927f73..d143f1b39 100644
--- a/distage/distage-framework-docker/src/main/scala/izumi/distage/docker/healthcheck/ContainerHealthCheck.scala
+++ b/distage/distage-framework-docker/src/main/scala/izumi/distage/docker/healthcheck/ContainerHealthCheck.scala
@@ -3,23 +3,23 @@ package izumi.distage.docker.healthcheck
 import izumi.distage.docker.Docker._
 import izumi.distage.docker.DockerContainer
 import izumi.distage.docker.healthcheck.ContainerHealthCheck.HealthCheckResult
-import izumi.distage.docker.healthcheck.ContainerHealthCheck.HealthCheckResult.AvailableOnPorts
 import izumi.fundamentals.collections.nonempty.NonEmptyList
 import izumi.logstage.api.IzLogger
 
-import scala.reflect.ClassTag
-
 trait ContainerHealthCheck[Tag] {
   def check(logger: IzLogger, container: DockerContainer[Tag]): HealthCheckResult
 
   final def ++(next: HealthCheckResult => ContainerHealthCheck[Tag]): ContainerHealthCheck[Tag] = this combine next
-  final def combine(next: HealthCheckResult => ContainerHealthCheck[Tag]): ContainerHealthCheck[Tag] = combineOn(next)
-  final def combineOn[T: ClassTag](next: T => ContainerHealthCheck[Tag]): ContainerHealthCheck[Tag] = {
+  final def combine(next: HealthCheckResult => ContainerHealthCheck[Tag]): ContainerHealthCheck[Tag] = {
+    (logger: IzLogger, container: DockerContainer[Tag]) =>
+      next(check(logger, container)).check(logger, container)
+  }
+  final def combineOnPorts(next: HealthCheckResult.AvailableOnPorts => ContainerHealthCheck[Tag]): ContainerHealthCheck[Tag] = {
     (logger: IzLogger, container: DockerContainer[Tag]) =>
       check(logger, container) match {
-        case thisCheck: T =>
-          next(thisCheck).check(logger, container) match {
-            case HealthCheckResult.Available => thisCheck
+        case thisCheckResult: HealthCheckResult.AvailableOnPorts =>
+          next(thisCheckResult).check(logger, container) match {
+            case HealthCheckResult.Available => thisCheckResult
             case other => other
           }
         case other => other
@@ -28,6 +28,14 @@ trait ContainerHealthCheck[Tag] {
 }
 
 object ContainerHealthCheck {
+  def portCheck[T]: ContainerHealthCheck[T] = new TCPContainerHealthCheck[T]
+  def httpGetCheck[T](port: DockerPort): ContainerHealthCheck[T] = portCheck.combineOnPorts(new HttpGetCheck(_, port, useHttps = false))
+  def httpsGetCheck[T](port: DockerPort): ContainerHealthCheck[T] = portCheck.combineOnPorts(new HttpGetCheck(_, port, useHttps = true))
+  def postgreSqlProtocolCheck[T](port: DockerPort, user: String, password: String): ContainerHealthCheck[T] =
+    portCheck.combineOnPorts(new PostgreSqlProtocolCheck(_, port, user, password))
+
+  def succeed[T]: ContainerHealthCheck[T] = (_, _) => HealthCheckResult.Available
+
   sealed trait HealthCheckResult
   object HealthCheckResult {
     case object Available extends HealthCheckResult
@@ -40,17 +48,21 @@ object ContainerHealthCheck {
     ) extends HealthCheckResult
   }
 
-  final case class UnavailablePorts(unavailablePorts: Map[DockerPort, List[(AvailablePort, Option[Throwable])]])
+  final case class UnavailablePorts(unavailablePorts: Map[DockerPort, List[(AvailablePort, Option[Throwable])]]) extends AnyVal {
+    override def toString: String = s"unavailable=$unavailablePorts"
+  }
+
+  final case class VerifiedContainerConnectivity(availablePorts: Map[DockerPort, NonEmptyList[AvailablePort]]) extends AnyVal {
+    def apply(port: DockerPort): NonEmptyList[AvailablePort] = availablePorts(port)
+    def first(port: DockerPort): AvailablePort = availablePorts(port).head
+
+    def get(port: DockerPort): Option[NonEmptyList[AvailablePort]] = availablePorts.get(port)
+    def firstOption(port: DockerPort): Option[AvailablePort] = availablePorts.get(port).map(_.head)
 
-  final case class VerifiedContainerConnectivity(availablePorts: Map[DockerPort, NonEmptyList[AvailablePort]]) {
     override def toString: String = s"accessible=$availablePorts"
   }
   object VerifiedContainerConnectivity {
     def empty: VerifiedContainerConnectivity = VerifiedContainerConnectivity(Map.empty)
   }
 
-  def ignore[T]: ContainerHealthCheck[T] = (_, _) => HealthCheckResult.Available
-  def portCheck[T]: ContainerHealthCheck[T] = new TCPContainerHealthCheck[T]
-  def postgreSqlProtocolCheck[T]: ContainerHealthCheck[T] = portCheck.combineOn[AvailableOnPorts](new PostgreSqlProtocolCheck(_))
-  def httpGetCheck[T](port: DockerPort): ContainerHealthCheck[T] = portCheck.combineOn[AvailableOnPorts](new HttpGetCheck(_, port))
 }
diff --git a/distage/distage-framework-docker/src/main/scala/izumi/distage/docker/healthcheck/ContainerHealthCheckBase.scala b/distage/distage-framework-docker/src/main/scala/izumi/distage/docker/healthcheck/ContainerHealthCheckBase.scala
index dba04768a..c147f8d8d 100644
--- a/distage/distage-framework-docker/src/main/scala/izumi/distage/docker/healthcheck/ContainerHealthCheckBase.scala
+++ b/distage/distage-framework-docker/src/main/scala/izumi/distage/docker/healthcheck/ContainerHealthCheckBase.scala
@@ -9,7 +9,7 @@ import izumi.fundamentals.platform.strings.IzString._
 import izumi.logstage.api.IzLogger
 
 abstract class ContainerHealthCheckBase[Tag] extends ContainerHealthCheck[Tag] {
-  override final def check(logger: IzLogger, container: DockerContainer[Tag]): HealthCheckResult = {
+  override final def check(logger: IzLogger, container: DockerContainer[Tag]): HealthCheckResult.AvailableOnPorts = {
 
     val tcpPorts: Map[DockerPort.TCPBase, NonEmptyList[ServicePort]] =
       container
diff --git a/distage/distage-framework-docker/src/main/scala/izumi/distage/docker/healthcheck/HttpGetCheck.scala b/distage/distage-framework-docker/src/main/scala/izumi/distage/docker/healthcheck/HttpGetCheck.scala
index d11d34f63..00c770048 100644
--- a/distage/distage-framework-docker/src/main/scala/izumi/distage/docker/healthcheck/HttpGetCheck.scala
+++ b/distage/distage-framework-docker/src/main/scala/izumi/distage/docker/healthcheck/HttpGetCheck.scala
@@ -10,12 +10,11 @@ import izumi.logstage.api.IzLogger
 final class HttpGetCheck[Tag](
   portStatus: HealthCheckResult.AvailableOnPorts,
   port: DockerPort,
-  useHttps: Boolean = false
+  useHttps: Boolean,
 ) extends ContainerHealthCheck[Tag] {
   override def check(logger: IzLogger, container: DockerContainer[Tag]): ContainerHealthCheck.HealthCheckResult = {
-    portStatus.availablePorts.availablePorts.get(port) match {
-      case Some(value) if portStatus.requiredPortsAccessible =>
-        val availablePort = value.head
+    portStatus.availablePorts.firstOption(port) match {
+      case Some(availablePort) if portStatus.requiredPortsAccessible =>
         val protocol = if (useHttps) "https" else "http"
         val url = new URL(s"$protocol://${availablePort.hostV4}:${availablePort.port}")
         logger.info(s"Checking docker port $port via $url for $container. Will try to establish HTTP connection.")
@@ -28,12 +27,12 @@ final class HttpGetCheck[Tag](
             logger.info(s"HTTP connection was successfully established with $port.")
             ContainerHealthCheck.HealthCheckResult.Available
           } else {
-            logger.debug(s"Cannot establish HTTP connection with $port. Wrong protocol.")
+            logger.info(s"Cannot establish HTTP connection with $port. Wrong protocol.")
             ContainerHealthCheck.HealthCheckResult.Unavailable
           }
         } catch {
           case t: Throwable =>
-            logger.debug(s"Cannot establish HTTP connection with $port due to ${t.getMessage -> "failure"}")
+            logger.warn(s"Cannot establish HTTP connection with $port due to ${t.getMessage -> "failure"}")
             ContainerHealthCheck.HealthCheckResult.Unavailable
         }
       case _ => ContainerHealthCheck.HealthCheckResult.Unavailable
diff --git a/distage/distage-framework-docker/src/main/scala/izumi/distage/docker/healthcheck/PostgreSqlProtocolCheck.scala b/distage/distage-framework-docker/src/main/scala/izumi/distage/docker/healthcheck/PostgreSqlProtocolCheck.scala
index 75794ca26..3e51f7f67 100644
--- a/distage/distage-framework-docker/src/main/scala/izumi/distage/docker/healthcheck/PostgreSqlProtocolCheck.scala
+++ b/distage/distage-framework-docker/src/main/scala/izumi/distage/docker/healthcheck/PostgreSqlProtocolCheck.scala
@@ -1,7 +1,7 @@
 package izumi.distage.docker.healthcheck
 
 import java.net.{InetSocketAddress, Socket}
-import java.nio.ByteBuffer
+import java.nio.{Buffer, ByteBuffer}
 
 import izumi.distage.docker.Docker.DockerPort
 import izumi.distage.docker.DockerContainer
@@ -11,35 +11,38 @@ import izumi.logstage.api.IzLogger
 
 final class PostgreSqlProtocolCheck[Tag](
   portStatus: HealthCheckResult.AvailableOnPorts,
-  port: DockerPort = DockerPort.TCP(5432),
-  userName: String = "postgres",
-  databaseName: String = "postgres",
+  port: DockerPort,
+  userName: String,
+  databaseName: String,
 ) extends ContainerHealthCheck[Tag] {
   override def check(logger: IzLogger, container: DockerContainer[Tag]): ContainerHealthCheck.HealthCheckResult = {
-    portStatus.availablePorts.availablePorts.get(port) match {
-      case Some(value) if portStatus.requiredPortsAccessible =>
+    portStatus.availablePorts.firstOption(port) match {
+      case Some(availablePort) if portStatus.requiredPortsAccessible =>
         val startupMessage = genStartupMessage()
         val socket = new Socket()
         try {
-          val availablePort = value.head
           socket.connect(new InetSocketAddress(availablePort.hostV4, availablePort.port), container.containerConfig.portProbeTimeout.toMillis.toInt)
           logger.info(s"Checking PostgreSQL protocol on $port for $container. ${startupMessage.toIterable.toHex -> "Startup message"}.")
           val out = socket.getOutputStream
           val in = socket.getInputStream
           out.write(startupMessage)
-          val messageType = new String(in.readNBytes(1))
+          val messageType = {
+            val outByte = Array[Byte](0)
+            in.read(outByte, 0, 1)
+            new String(outByte)
+          }
           // first byte of response message should be `R` char
           // every authentication message from PostgreSQL starts with `R`
           if (messageType == "R") {
             logger.info(s"PostgreSQL protocol on $port is available.")
             ContainerHealthCheck.HealthCheckResult.Available
           } else {
-            logger.debug(s"PostgreSQL protocol on $port unavailable due to unknown message type: $messageType.")
+            logger.info(s"PostgreSQL protocol on $port unavailable due to unknown message type: $messageType.")
             ContainerHealthCheck.HealthCheckResult.Unavailable
           }
         } catch {
           case t: Throwable =>
-            logger.debug(s"PostgreSQL protocol on $port unavailable due to unexpected exception. ${t.getMessage -> "Failure"}")
+            logger.warn(s"PostgreSQL protocol on $port unavailable due to unexpected exception. ${t.getMessage -> "Failure"}")
             ContainerHealthCheck.HealthCheckResult.Unavailable
         } finally {
           socket.close()
@@ -60,18 +63,20 @@ final class PostgreSqlProtocolCheck[Tag](
     * https://www.postgresql.org/docs/9.5/protocol-message-formats.html
     */
   private def genStartupMessage(): Array[Byte] = {
-    def write(buffer: ByteBuffer, message: String): ByteBuffer = {
-      val currentPos = buffer.position()
+    def write(buffer: ByteBuffer, message: String): Unit = {
+      val currentPos = (buffer: Buffer).position()
       buffer.put(message.getBytes())
-      buffer.position(currentPos + message.length)
+      (buffer: Buffer).position(currentPos + message.length)
       buffer.put(0.toByte)
-      buffer.position(currentPos + message.length + 1)
+      (buffer: Buffer).position(currentPos + message.length + 1)
     }
     // <size int32> <protocol int32> <user const str> <0> <username str> <0> <database const str> <0> <database str> <0> <0>
     val size = 4 + 4 + 4 + 1 + userName.length + 1 + 8 + 1 + databaseName.length + 1 + 1
     val buffer = ByteBuffer.allocate(size)
-    buffer.position(0).putInt(size) // size
-    buffer.position(4).putInt(196608) // protocol
+    (buffer: Buffer).position(0)
+    buffer.putInt(size) // size
+    (buffer: Buffer).position(4)
+    buffer.putInt(196608) // protocol
     write(buffer, "user")
     write(buffer, userName)
     write(buffer, "database")
diff --git a/distage/distage-framework-docker/src/main/scala/izumi/distage/docker/healthcheck/TCPContainerHealthCheck.scala b/distage/distage-framework-docker/src/main/scala/izumi/distage/docker/healthcheck/TCPContainerHealthCheck.scala
index 22e6858be..5678aab31 100644
--- a/distage/distage-framework-docker/src/main/scala/izumi/distage/docker/healthcheck/TCPContainerHealthCheck.scala
+++ b/distage/distage-framework-docker/src/main/scala/izumi/distage/docker/healthcheck/TCPContainerHealthCheck.scala
@@ -11,10 +11,14 @@ import izumi.fundamentals.platform.integration.{PortCheck, ResourceCheck}
 import izumi.fundamentals.platform.strings.IzString._
 import izumi.logstage.api.IzLogger
 
-
 class TCPContainerHealthCheck[Tag] extends ContainerHealthCheckBase[Tag] {
 
-  override protected def perform(logger: IzLogger, container: DockerContainer[Tag], tcpPorts: Map[DockerPort.TCPBase, NonEmptyList[ServicePort]], udpPorts: Map[DockerPort.UDPBase, NonEmptyList[ServicePort]]): HealthCheckResult.AvailableOnPorts = {
+  override protected def perform(
+    logger: IzLogger,
+    container: DockerContainer[Tag],
+    tcpPorts: Map[DockerPort.TCPBase, NonEmptyList[ServicePort]],
+    udpPorts: Map[DockerPort.UDPBase, NonEmptyList[ServicePort]],
+  ): HealthCheckResult.AvailableOnPorts = {
     val check = new PortCheck(container.containerConfig.portProbeTimeout)
 
     val dockerHostCandidates = findDockerHostCandidates(container, tcpPorts)
@@ -26,7 +30,6 @@ class TCPContainerHealthCheck[Tag] extends ContainerHealthCheckBase[Tag] {
 
     logger.debug(s"going to check ports on $container: ${allCandidates.map { case PortCandidate(k, v) => s"if $k is available at $v" }.niceList() -> "port mappings"}")
 
-
     val checks = allCandidates.map {
       case PortCandidate(dp, ap) =>
         check.checkPort(ap.hostV4, ap.port, s"open port ${ap.hostV4}:${ap.port} for ${container.id}") match {
@@ -48,7 +51,8 @@ class TCPContainerHealthCheck[Tag] extends ContainerHealthCheckBase[Tag] {
           case (dp, ap) =>
             (dp: DockerPort, ap.toList)
         }
-        .toMap)
+        .toMap
+    )
 
     val available = VerifiedContainerConnectivity(
       good
@@ -66,8 +70,7 @@ class TCPContainerHealthCheck[Tag] extends ContainerHealthCheckBase[Tag] {
       available,
       errors,
       udpPorts.keySet.map(p => p: DockerPort),
-      tcpPortsGood(container, available)
+      tcpPortsGood(container, available),
     )
   }
 }
-
diff --git a/fundamentals/fundamentals-platform/src/main/scala/izumi/fundamentals/platform/exceptions/IzThrowable.scala b/fundamentals/fundamentals-platform/src/main/scala/izumi/fundamentals/platform/exceptions/IzThrowable.scala
index 05ae9b146..e0b8acc8d 100644
--- a/fundamentals/fundamentals-platform/src/main/scala/izumi/fundamentals/platform/exceptions/IzThrowable.scala
+++ b/fundamentals/fundamentals-platform/src/main/scala/izumi/fundamentals/platform/exceptions/IzThrowable.scala
@@ -68,4 +68,3 @@ class IzThrowable(t: Throwable, acceptedPackages: Set[String]) {
 object IzThrowable {
   implicit def toRichThrowable(throwable: Throwable): IzThrowable = new IzThrowable(throwable, Set.empty)
 }
-


Test patch:
diff --git a/distage/distage-framework-docker/src/test/scala/izumi/distage/testkit/docker/fixtures/DockerPlugin.scala b/distage/distage-framework-docker/src/test/scala/izumi/distage/testkit/docker/fixtures/DockerPlugin.scala
index 80126c3bd..dd104fe50 100644
--- a/distage/distage-framework-docker/src/test/scala/izumi/distage/testkit/docker/fixtures/DockerPlugin.scala
+++ b/distage/distage-framework-docker/src/test/scala/izumi/distage/testkit/docker/fixtures/DockerPlugin.scala
@@ -41,17 +41,17 @@ object DockerPlugin extends PluginDef {
 
   make[AvailablePort].named("mq").tagged(Env.Test).from {
     cs: ElasticMQDocker.Container =>
-      cs.availablePorts.availablePorts(ElasticMQDocker.primaryPort).head
+      cs.availablePorts.first(ElasticMQDocker.primaryPort)
   }
 
   make[AvailablePort].named("cs").tagged(Env.Test).from {
     cs: CassandraDocker.Container =>
-      cs.availablePorts.availablePorts(CassandraDocker.primaryPort).head
+      cs.availablePorts.first(CassandraDocker.primaryPort)
   }
 
   make[AvailablePort].named("kafka").tagged(Env.Test).from {
     kafka: KafkaDocker.Container =>
-      kafka.availablePorts.availablePorts(KafkaDocker.primaryPort).head
+      kafka.availablePorts.first(KafkaDocker.primaryPort)
   }
 
   // this container will start once `DynamoContainer` is up and running
@@ -62,11 +62,11 @@ object DockerPlugin extends PluginDef {
   // these lines are for test scope
   make[AvailablePort].named("pg").tagged(Env.Test).from {
     pg: PostgresDocker.Container =>
-      pg.availablePorts.availablePorts(PostgresDocker.primaryPort).head
+      pg.availablePorts.first(PostgresDocker.primaryPort)
   }
   make[AvailablePort].named("ddb").tagged(Env.Test).from {
     dn: DynamoDocker.Container =>
-      dn.availablePorts.availablePorts(DynamoDocker.primaryPort).head
+      dn.availablePorts.first(DynamoDocker.primaryPort)
   }
 
   // and this one is for production