@@ -8,11 +8,12 @@ import play.api.{Configuration, Logger}
88
99import java .nio .file .{Files , Path }
1010import java .time .Duration
11+ import java .util .Collections
1112import java .util .concurrent .{Executors , TimeUnit }
1213import scala .concurrent .blocking
1314import scala .concurrent .duration .FiniteDuration
1415import scala .jdk .CollectionConverters ._
15- import scala .util .Try
16+ import scala .util .{ Failure , Success , Try }
1617
1718class DockerClient (config : Configuration ) {
1819 private lazy val logger : Logger = Logger (getClass.getName)
@@ -32,25 +33,33 @@ class DockerClient(config: Configuration) {
3233 waitResult
3334 }
3435
35- def prepare (image : String , jobDirectory : Path , jobBaseDirectory : Path , dockerJobBaseDirectory : Path , timeout : FiniteDuration ): Try [String ] = Try {
36- logger.info(s " image $image pull result: ${pullImage(image)}" )
37- val containerCmd = underlyingClient
38- .createContainerCmd(image)
39- .withHostConfig(configure(jobDirectory, jobBaseDirectory, dockerJobBaseDirectory))
40- if (Files .exists(jobDirectory.resolve(" input" ).resolve(" cacerts" )))
41- containerCmd.withEnv(s " REQUESTS_CA_BUNDLE=/job/input/cacerts " )
42- val containerResponse = containerCmd.exec()
43- logger.info(
44- s " about to start container ${containerResponse.getId}\n " +
45- s " timeout: ${timeout.toString}\n " +
46- s " image : $image\n " +
47- s " volumes : ${jobDirectory.toAbsolutePath}"
48- )
49- if (containerResponse.getWarnings.nonEmpty) logger.warn(s " ${containerResponse.getWarnings.mkString(" , " )}" )
50- scheduleContainerTimeout(containerResponse.getId, timeout)
51-
52- containerResponse.getId
53- }
36+ def pullImageIfRequired (image : String ): Try [Unit ] =
37+ if (imageExists(image)) Success (())
38+ else {
39+ logger.info(s " pulling image $image ... " )
40+ pullImage(image)
41+ }
42+
43+ def prepare (image : String , jobDirectory : Path , jobBaseDirectory : Path , dockerJobBaseDirectory : Path , timeout : FiniteDuration ): Try [String ] =
44+ pullImageIfRequired(image)
45+ .map { _ =>
46+ val containerCmd = underlyingClient
47+ .createContainerCmd(image)
48+ .withHostConfig(configure(jobDirectory, jobBaseDirectory, dockerJobBaseDirectory))
49+ if (Files .exists(jobDirectory.resolve(" input" ).resolve(" cacerts" )))
50+ containerCmd.withEnv(s " REQUESTS_CA_BUNDLE=/job/input/cacerts " )
51+ val containerResponse = containerCmd.exec()
52+ logger.info(
53+ s " about to start container ${containerResponse.getId}\n " +
54+ s " timeout: ${timeout.toString}\n " +
55+ s " image : $image\n " +
56+ s " volumes : ${jobDirectory.toAbsolutePath}"
57+ )
58+ if (containerResponse.getWarnings.nonEmpty) logger.warn(s " ${containerResponse.getWarnings.mkString(" , " )}" )
59+ scheduleContainerTimeout(containerResponse.getId, timeout)
60+
61+ containerResponse.getId
62+ }
5463
5564 private def configure (jobDirectory : Path , jobBaseDirectory : Path , dockerJobBaseDirectory : Path ): HostConfig = {
5665 val hostConfigMut = HostConfig
@@ -85,27 +94,67 @@ class DockerClient(config: Configuration) {
8594 }
8695
8796 def info : Info = underlyingClient.infoCmd().exec()
88- def pullImage (image : String ): Boolean = blocking {
89- val pullImageResultCbk = underlyingClient // Blocking
90- .pullImageCmd(image)
91- .start()
92- .awaitCompletion()
93- val timeout = config.get[FiniteDuration ](" docker.pullImageTimeout" )
9497
95- pullImageResultCbk.awaitCompletion(timeout.toMillis, TimeUnit .MILLISECONDS )
98+ def pullImage (image : String ): Try [Unit ] = Try {
99+ blocking {
100+ val pullImageResultCbk = underlyingClient // Blocking
101+ .pullImageCmd(image)
102+ .start()
103+ val timeout = config.get[FiniteDuration ](" docker.pullImageTimeout" )
104+
105+ pullImageResultCbk.awaitCompletion(timeout.toMillis, TimeUnit .MILLISECONDS )
106+ ()
107+ }
96108 }
97109
98- def clean ( containerId : String ): Try [ Unit ] = Try {
99- underlyingClient
100- .killContainerCmd(containerId)
101- .exec()
110+ def imageExists ( image : String ): Boolean =
111+ ! underlyingClient.listImagesCmd().withImageNameFilter(image).exec().isEmpty
112+
113+ def getContainerStatus ( containerId : String ) : Option [ String ] =
102114 underlyingClient
103- .removeContainerCmd(containerId )
104- .withForce( true )
115+ .listContainersCmd( )
116+ .withIdFilter( Collections .singletonList(containerId) )
105117 .exec()
106- logger.info(s " removed container $containerId" )
118+ .asScala
119+ .headOption
120+ .map(_.getStatus)
121+
122+ def killContainer (containerId : String ): Try [Unit ] = {
123+ logger.info(s " Killing the container $containerId" )
124+ Try {
125+ underlyingClient
126+ .killContainerCmd(containerId)
127+ .exec()
128+ ()
129+ }.recoverWith {
130+ case error =>
131+ logger.warn(s " Unable to kill the container $containerId" , error)
132+ Failure (error)
133+ }
107134 }
108135
136+ def removeContainer (containerId : String ): Try [Unit ] = {
137+ logger.info(s " Removing the container $containerId" )
138+ Try {
139+ underlyingClient
140+ .removeContainerCmd(containerId)
141+ .withForce(true )
142+ .exec()
143+ ()
144+ }.recoverWith {
145+ case error =>
146+ logger.warn(s " Unable to remove the container $containerId" , error)
147+ Failure (error)
148+ }
149+ }
150+
151+ def clean (containerId : String ): Try [Unit ] =
152+ getContainerStatus(containerId).fold[Try [Unit ]](Success (())) { status =>
153+ if (status != " exited" && status != " dead" )
154+ killContainer(containerId)
155+ removeContainer(containerId)
156+ }
157+
109158 def getLogs (containerId : String ): Try [String ] = Try {
110159 val stringBuilder = new StringBuilder ()
111160 val callback = new DockerLogsStringBuilder (stringBuilder)
0 commit comments