Skip to content

Commit

Permalink
Replacing deprecated docker-client with docker-java (#922)
Browse files Browse the repository at this point in the history
* Replacing deprecated docker-client with docker-java.

* Addressing feedback.

* Awaiting for image pull.

---------

Co-authored-by: Samuele Resca <sr7@ad.datcon.co.uk>
  • Loading branch information
2 people authored and pjfanning committed Apr 12, 2024
1 parent 3cc8ace commit 7c18849
Show file tree
Hide file tree
Showing 2 changed files with 104 additions and 50 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -18,23 +18,43 @@ import scala.util.Try
import scala.util.control.NonFatal

import com.typesafe.config.Config
import com.spotify.docker.client.DefaultDockerClient
import com.spotify.docker.client.DockerClient.{ ListContainersParam, LogsParam }
import com.spotify.docker.client.messages.{ ContainerConfig, HostConfig, PortBinding }
import com.github.dockerjava.core.DockerClientConfig
import com.github.dockerjava.api.model.HostConfig;
import com.github.dockerjava.core.DefaultDockerClientConfig
import com.github.dockerjava.api.model.Frame;
import com.github.dockerjava.api.async.ResultCallback;
import org.scalatest.concurrent.Eventually

import org.apache.pekko
import pekko.testkit.PekkoSpec
import pekko.util.ccompat.JavaConverters._
import com.github.dockerjava.api.DockerClient
import com.github.dockerjava.httpclient5.ApacheDockerHttpClient
import com.github.dockerjava.api.command.CreateContainerCmd
import com.github.dockerjava.api.model.PortBinding
import com.github.dockerjava.api.model.Container
import com.github.dockerjava.api.model.Volume
import com.github.dockerjava.core.DockerClientImpl
import com.github.dockerjava.api.model.Bind

abstract class DockerBindDnsService(config: Config) extends PekkoSpec(config) with Eventually {
val client = DefaultDockerClient.fromEnv().build()

val dockerConfig: DockerClientConfig = DefaultDockerClientConfig
.createDefaultConfigBuilder()
.build();

val httpClient: ApacheDockerHttpClient = new ApacheDockerHttpClient.Builder()
.dockerHost(dockerConfig.getDockerHost())
.sslConfig(dockerConfig.getSSLConfig())
.build();

val client: DockerClient = DockerClientImpl.getInstance(dockerConfig, httpClient);

val hostPort: Int

var id: Option[String] = None

def dockerAvailable() = Try(client.ping()).isSuccess
def dockerAvailable() = Try(client.pingCmd().exec()).isSuccess

override def atStartup(): Unit = {
log.info("Running on port port {}", hostPort)
Expand All @@ -43,64 +63,100 @@ abstract class DockerBindDnsService(config: Config) extends PekkoSpec(config) wi
// https://github.com/sameersbn/docker-bind/pull/61
val image = "raboof/bind:9.11.3-20180713-nochown"
try {
client.pull(image)
client
.pullImageCmd(image)
.start()
.awaitCompletion()
} catch {
case NonFatal(_) =>
log.warning(s"Failed to pull docker image [$image], is docker running?")
return
}

val containerConfig = ContainerConfig
.builder()
.image(image)
.env("NO_CHOWN=true")
.cmd("-4") // only listen on ipv4
.hostConfig(
HostConfig
.builder()
.portBindings(Map(
"53/tcp" -> List(PortBinding.of("", hostPort)).asJava,
"53/udp" -> List(PortBinding.of("", hostPort)).asJava).asJava)
.binds(HostConfig.Bind
.from(new java.io.File("actor-tests/src/test/bind/").getAbsolutePath)
.to("/data/bind")
.build())
.build())
.build()

val containerName = "pekko-test-dns-" + getClass.getCanonicalName

val containerCommand: CreateContainerCmd = client
.createContainerCmd(image)
.withName(containerName)
.withEnv("NO_CHOWN=true")
.withCmd("-4")
.withHostConfig(
HostConfig.newHostConfig()
.withPortBindings(
PortBinding.parse(s"$hostPort:53/tcp"),
PortBinding.parse(s"$hostPort:53/udp"))
.withBinds(new Bind(new java.io.File("actor-tests/src/test/bind/").getAbsolutePath,
new Volume("/data/bind"))))

client
.listContainers(ListContainersParam.allContainers())
.listContainersCmd()
.exec()
.asScala
.find(_.names().asScala.exists(_.contains(containerName)))
.foreach(c => {
if ("running" == c.state()) {
client.killContainer(c.id)
.find((c: Container) => c.getNames().exists(_.contains(containerName)))
.foreach((c: Container) => {
if ("running" == c.getState()) {
client.killContainerCmd(c.getId()).exec()
}
client.removeContainer(c.id)
client.removeContainerCmd(c.getId()).exec()
})

val creation = client.createContainer(containerConfig, containerName)
if (creation.warnings() != null)
creation.warnings() should have(size(0))
id = Some(creation.id())
val creation = containerCommand.exec()
if (creation.getWarnings() != null)
creation.getWarnings() should have(size(0))
id = Some(creation.getId())

client.startContainer(creation.id())
client.startContainerCmd(creation.getId()).exec()
val reader = new StringBuilderLogReader

eventually(timeout(25.seconds)) {
client.logs(creation.id(), LogsParam.stderr()).readFully() should include("all zones loaded")
client
.logContainerCmd(creation.getId())
.withStdErr(true)
.exec(reader)

reader.toString should include("all zones loaded")
}
}

def dumpNameserverLogs(): Unit = {
id.foreach(id => log.info("Nameserver std out: {} ", client.logs(id, LogsParam.stdout()).readFully()))
id.foreach(id => log.info("Nameserver std err: {} ", client.logs(id, LogsParam.stderr()).readFully()))
id.foreach(id => {
val reader = new StringBuilderLogReader
client
.logContainerCmd(id)
.withStdOut(true)
.exec(reader)
.awaitCompletion()

log.info("Nameserver std out: {} ", reader.toString())
})
id.foreach(id => {
val reader = new StringBuilderLogReader
client
.logContainerCmd(id)
.withStdErr(true)
.exec(reader)
.awaitCompletion()
log.info("Nameserver std err: {} ", reader.toString())
})
}

override def afterTermination(): Unit = {
super.afterTermination()
id.foreach(client.killContainer)
id.foreach(client.removeContainer)
id.foreach(id => client.killContainerCmd(id).exec())
id.foreach(id => client.removeContainerCmd(id).exec())

client.close()
}
}

class StringBuilderLogReader extends ResultCallback.Adapter[Frame] {

lazy val builder: StringBuilder = new StringBuilder

override def onNext(item: Frame): Unit = {
builder.append(new String(item.getPayload))
super.onNext(item)
}

override def toString(): String = builder.toString()
}
18 changes: 8 additions & 10 deletions project/Dependencies.scala
Original file line number Diff line number Diff line change
Expand Up @@ -164,8 +164,11 @@ object Dependencies {
// in-memory filesystem for file related tests
val jimfs = "com.google.jimfs" % "jimfs" % "1.1" % Test

// docker utils
val dockerClient = "com.spotify" % "docker-client" % "8.16.0" % Test
val dockerClient = Def.setting {
Seq(
"com.github.docker-java" % "docker-java-core" % "3.3.4" % Test,
"com.github.docker-java" % "docker-java-transport-httpclient5" % "3.3.4" % Test)
}

val jackson = Def.setting {
Seq(
Expand Down Expand Up @@ -245,18 +248,13 @@ object Dependencies {
TestDependencies.scalatest.value,
TestDependencies.scalatestJUnit.value,
TestDependencies.scalatestScalaCheck.value,
TestDependencies.bcpkix, // to force TestDependencies.dockerClient to use safe version of this lib
TestDependencies.commonsCodec,
TestDependencies.commonsCompress, // to force TestDependencies.dockerClient to use safe version of this lib
TestDependencies.commonsIo, // to force TestDependencies.dockerClient to use safe version of this lib
TestDependencies.commonsMath,
TestDependencies.httpClient, // to force TestDependencies.dockerClient to use safe version of this lib
TestDependencies.jimfs,
TestDependencies.dockerClient,
Provided.activation // dockerClient needs javax.activation.DataSource in JDK 11+
) ++ {
TestDependencies.jimfs) ++ {
// TestDependencies.dockerClient bring in older versions of libs that have CVEs
TestDependencies.jackson.value
} ++ {
TestDependencies.dockerClient.value
}

val actorTestkitTyped = l ++= Seq(
Expand Down

0 comments on commit 7c18849

Please sign in to comment.