Docker Engine API はその名の通り Docker の API である。基本的に docker
コマンドはこの API を使って実装されているので、この API を使えばコマンドでできることがすべてできる(僕の理解では)。
Develop with Docker Engine API | Docker Documentation
この API は Unix Domain Socket を使って通信している。サンプルでは Python, Go, curl でのサンプルがあるが、Unix Domain Socket を使えればどの言語でも実装できる。Scala ではalpakkaが Unix Domain Socket をサポートしているので、これを使う。
Unix Domain Socket • Alpakka Documentation
これでおしまいかというと、Unix Domain Socket は HTTP を喋ることを想定されていないので、どうにかして喋らせる必要がある。ここで書かれてるみたいに頑張ってもいいのだけど、なんかうまい方法ないかなぁと思って探してたら見つかった。
ちょっと改造したものがこちら。
import java.net.InetSocketAddress
import java.nio.file.FileSystems
import scala.concurrent.ExecutionContext
import scala.concurrent.Future
import akka.actor.ActorSystem
import akka.http.scaladsl._
import akka.http.scaladsl.settings.ClientConnectionSettings
import akka.stream.alpakka.unixdomainsocket.scaladsl.UnixDomainSocket
import akka.stream.scaladsl._
import akka.util.ByteString
import cats.effect.Async
// copy from https://github.com/akka/akka-http/issues/2139#issuecomment-413535497
object DockerSockTransport extends ClientTransport {
lazy val path: java.nio.file.Path =
FileSystems.getDefault().getPath("/var/run/docker.sock")
override def connectTo(host: String, port: Int, settings: ClientConnectionSettings)(implicit
system: ActorSystem
): Flow[ByteString, ByteString, Future[Http.OutgoingConnection]] = {
// ignore everything for now
UnixDomainSocket().outgoingConnection(path).mapMaterializedValue { _ =>
// Seems that the UnixDomainSocket.OutgoingConnection is never completed?
// It works anyway if we just assume it is completed
// instantly
Future.successful(
Http.OutgoingConnection(
InetSocketAddress.createUnresolved(host, port),
InetSocketAddress.createUnresolved(host, port)
)
)
}
}
}
lazy val settings = ConnectionPoolSettings(system).withTransport(DockerSockTransport)
def request[F, A](path: String)(implicit um: Unmarshaller[HttpResponse, A], ec: ExecutionContext, f: Async[F]): F[A] = {
f.async[A] { cb: (Either[Throwable, A] => Unit) =>
Http()
.singleRequest(HttpRequest(uri = s"http://localhost/${path}"), settings = settings)
.flatMap(res => Unmarshal(res).to[A])
.onComplete {
case Success(v) => cb(Right(v))
case Failure(exception) => cb(Left(exception))
}
}
}
catsの文脈で操作したかったので多少変えてるが、基本的にはサンプルと変わりない。一応動くという感じなので、もしかしたら常用していると問題がわかるかもしれない。