
com.whisk.docker.impl.spotify.SpotifyDockerCommandExecutor.scala Maven / Gradle / Ivy
The newest version!
package com.whisk.docker.impl.spotify
import java.nio.charset.StandardCharsets
import java.util
import java.util.Collections
import java.util.concurrent.TimeUnit
import java.util.function.Consumer
import com.google.common.io.Closeables
import com.spotify.docker.client.DockerClient.{AttachParameter, RemoveContainerParam}
import com.spotify.docker.client.exceptions.ContainerNotFoundException
import com.spotify.docker.client.messages.{ContainerConfig, HostConfig, PortBinding}
import com.spotify.docker.client.{DockerClient, LogMessage}
import com.whisk.docker._
import scala.collection.JavaConverters._
import scala.concurrent.duration.{Duration, FiniteDuration}
import scala.concurrent.{ExecutionContext, Future, Promise}
class SpotifyDockerCommandExecutor(override val host: String, client: DockerClient)
extends DockerCommandExecutor {
override def createContainer(spec: DockerContainer)(
implicit ec: ExecutionContext, timeout: Duration): Future[String] = {
val portBindings: Map[String, util.List[PortBinding]] = spec.bindPorts.map {
case (guestPort, DockerPortMapping(Some(hostPort), address)) =>
guestPort.toString -> Collections.singletonList(PortBinding.of(address, hostPort))
case (guestPort, DockerPortMapping(None, address)) =>
guestPort.toString -> Collections.singletonList(PortBinding.randomPort(address))
}
val binds: Seq[String] = spec.volumeMappings.map { volumeMapping =>
val rw = if (volumeMapping.rw) ":rw" else ""
volumeMapping.host + ":" + volumeMapping.container + rw
}
val hostConfig = {
val hostConfigBase =
HostConfig.builder().portBindings(portBindings.asJava).binds(binds.asJava)
val links = spec.links.map {
case ContainerLink(container, alias) => s"${container.name.get}:$alias"
}
val hostConfigBuilder =
if (links.isEmpty) hostConfigBase else hostConfigBase.links(links.asJava)
hostConfigBuilder
.withOption(spec.networkMode) {
case (config, networkMode) => config.networkMode(networkMode)
}
.withOption(spec.hostConfig.flatMap(_.tmpfs)) {
case (config, value) => config.tmpfs(value.asJava)
}
.withOption(spec.hostConfig.flatMap(_.memory)) {
case (config, memory) => config.memory(memory)
}
.withOption(spec.hostConfig.flatMap(_.memoryReservation)) {
case (config, reservation) => config.memoryReservation(reservation)
}
.withOption(spec.hostConfig.map(_.privileged)) {
case (config, privileged) => config.privileged(privileged)
}
.build()
}
val containerConfig = ContainerConfig
.builder()
.image(spec.image)
.hostConfig(hostConfig)
.exposedPorts(spec.bindPorts.map(_._1.toString).toSeq: _*)
.tty(spec.tty)
.attachStdin(spec.stdinOpen)
.env(spec.env: _*)
.withOption(spec.user) { case (config, user) => config.user(user) }
.withOption(spec.hostname) { case (config, hostname) => config.hostname(hostname) }
.withOption(spec.command) { case (config, command) => config.cmd(command: _*) }
.withOption(spec.entrypoint) {
case (config, entrypoint) => config.entrypoint(entrypoint: _*)
}
.build()
val creation = PerishableFuture(
spec.name.fold(client.createContainer(containerConfig))(
client.createContainer(containerConfig, _))
)
creation.map(_.id)
}
override def startContainer(id: String)(implicit ec: ExecutionContext, timeout: Duration): Future[Unit] = {
PerishableFuture(client.startContainer(id))
}
override def inspectContainer(id: String)(
implicit ec: ExecutionContext, timeout: Duration): Future[Option[InspectContainerResult]] = {
def inspect() =
PerishableFuture(client.inspectContainer(id))
.flatMap { info =>
val networkPorts = Option(info.networkSettings().ports())
networkPorts match {
case Some(p) =>
val ports = info
.networkSettings()
.ports()
.asScala
.collect {
case (cPort, bindings) if Option(bindings).exists(!_.isEmpty) =>
val binds = bindings.asScala
.map(b => com.whisk.docker.PortBinding(b.hostIp(), b.hostPort().toInt))
.toList
ContainerPort.parse(cPort) -> binds
}
.toMap
val addresses: Iterable[String] = for {
networks <- Option(info.networkSettings().networks()).map(_.asScala).toSeq
(key, network) <- networks
ip <- Option(network.ipAddress)
} yield {
ip
}
Future.successful(
Some(
InspectContainerResult(info.state().running(),
ports,
info.name(),
addresses.toSeq)))
case None =>
Future.failed(new Exception("can't extract ports"))
}
}
.recover {
case t: ContainerNotFoundException =>
None
}
RetryUtils.looped(inspect(), attempts = 5, delay = FiniteDuration(1, TimeUnit.SECONDS))
}
override def withLogStreamLines(id: String, withErr: Boolean)(
f: String => Unit)(implicit docker: DockerCommandExecutor, ec: ExecutionContext, timeout: Duration): Unit = {
val baseParams = List(AttachParameter.STDOUT, AttachParameter.STREAM, AttachParameter.LOGS)
val logParams = if (withErr) AttachParameter.STDERR :: baseParams else baseParams
val streamF = PerishableFuture(client.attachContainer(id, logParams: _*))
streamF.flatMap { stream =>
PerishableFuture {
stream.forEachRemaining(new Consumer[LogMessage] {
override def accept(t: LogMessage): Unit = {
val str = StandardCharsets.US_ASCII.decode(t.content()).toString
f(s"[$id] $str")
}
})
}
}
}
override def withLogStreamLinesRequirement(id: String, withErr: Boolean)(f: (String) => Boolean)(
implicit docker: DockerCommandExecutor,
ec: ExecutionContext, timeout: Duration): Future[Unit] = {
val baseParams = List(AttachParameter.STDOUT, AttachParameter.STREAM, AttachParameter.LOGS)
val logParams = if (withErr) AttachParameter.STDERR :: baseParams else baseParams
val streamF = PerishableFuture(client.attachContainer(id, logParams: _*))
streamF.flatMap { stream =>
val p = Promise[Unit]()
PerishableFuture {
stream.forEachRemaining(new Consumer[LogMessage] {
override def accept(t: LogMessage): Unit = {
val str = StandardCharsets.US_ASCII.decode(t.content()).toString
if (f(str)) {
p.trySuccess(())
Closeables.close(stream, true)
}
}
})
}
p.future
}
}
override def listImages()(implicit ec: ExecutionContext, timeout: Duration): Future[Set[String]] = {
PerishableFuture(
client
.listImages()
.asScala
.flatMap(img => Option(img.repoTags()).map(_.asScala).getOrElse(Seq.empty))
.toSet)
}
override def pullImage(image: String)(implicit ec: ExecutionContext, timeout: Duration): Future[Unit] = {
PerishableFuture(client.pull(image))
}
override def remove(id: String, force: Boolean, removeVolumes: Boolean)(
implicit ec: ExecutionContext, timeout: Duration): Future[Unit] = {
PerishableFuture(
client.removeContainer(id,
RemoveContainerParam.forceKill(force),
RemoveContainerParam.removeVolumes(removeVolumes)))
}
override def close(): Unit = {
Closeables.close(client, true)
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy