All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.codacy.plugins.runners.BinaryDockerRunner.scala Maven / Gradle / Ivy

The newest version!
package com.codacy.plugins.runners

import com.codacy.plugins.utils.{CommandRunner, Log}
import play.api.libs.json.Reads

import scala.concurrent.duration.Duration
import scala.concurrent.{Await, ExecutionContext, Future, TimeoutException}
import scala.util.{Failure, Properties, Success, Try}
import com.codacy.plugins.parser.ToolOutputParser

object BinaryDockerRunner {
  case class Config(containerMemoryLimit: Option[String] = Some("3000000000"))
}

class BinaryDockerRunner[T](docker: IDocker, config: BinaryDockerRunner.Config = BinaryDockerRunner.Config())
    extends DockerRunner[T] {

  def imageExistsLocally(): Boolean = {
    Log.time(s"Verifying if docker image ${docker.dockerImage} exists locally.") {
      val verificationCmd =
        List(DockerConstants.dockerBin, "inspect", "--format='{{.Config.Image}}'", "--type=image", docker.dockerImage)
      val res = CommandRunner.exec(verificationCmd) match {
        case Success(res) => res.nonEmpty
        case Failure(_)   => false
      }
      if (!res) {
        Log.debug(s"${docker.dockerImage} doesn't exist locally.")
      }
      res
    }
  }

  def pull(timeout: Duration = DockerRunner.defaultPullTimeout): Unit = {
    val cmd = List(DockerConstants.dockerBin, "pull", docker.dockerImage)
    Log.time(s"Pulling docker image ${docker.dockerImage}.") {
      val result = CommandRunner.execAsync(cmd)

      Try(Await.result(result.exitCode, timeout)).failed.foreach { _ =>
        Log.info(s"Docker ${docker.dockerImage} timed out after ${timeout.toSeconds} seconds. Destroying process.")
        result.destroy()
        Log.info(s"Finished destroying process of docker ${docker.dockerImage}")
        ()
      }
    }
  }

  def run(dockerConfig: DockerConfig)(implicit resultRds: Reads[T]): Try[List[T]] = {
    if (!imageExistsLocally()) pull()

    val dockerCmd = getDockerCmd(dockerConfig)

    val commandResult = Log.time(s"Running docker image ${docker.dockerImage}.") {
      val result = CommandRunner.execAsync(dockerCmd)
      Try(Await.result(result.exitCode, dockerConfig.awaitResponseTimeout)) match {
        case Success(code) =>
          Success((code, result.stdout, result.stderr))

        case Failure(e) =>
          Future {
            Log.info(
              s"Docker ${docker.dockerImage} timed out after ${dockerConfig.awaitResponseTimeout.toSeconds} seconds. Destroying process.")
            result.destroy()
            Log.info(s"Finished destroying process of docker ${docker.dockerImage}")
          }(ExecutionContext.global)
          Failure(e)
      }
    }

    commandResult.flatMap {
      case (0, result, _) =>
        Success(ToolOutputParser.parseDockerOutputLines[T](result))

      case (exitCode, stdout, stderr) =>
        val output =
          s"""
             |Docker exited with code $exitCode
             |stdout: ${stdout.mkString(Properties.lineSeparator)}
             |stderr: ${stderr.mkString(Properties.lineSeparator)}
           """.stripMargin

        if (exitCode == 2) {
          Failure(new TimeoutException(output))
        } else {
          Failure(new Throwable(output))
        }
    }
  }

  private def asArgsLists(dockerConfig: DockerConfig,
                          config: BinaryDockerRunner.Config): (List[String], List[String]) = {
    val vols = dockerConfig.volumes.flatMap(volume => List("-v", s"${volume.hostPath}:${volume.dockerPath}"))
    val entrypoint = dockerConfig.entry.fold(List.empty[String])(entrypoint => List(s"--entrypoint=$entrypoint"))
    val autoRemoveCmd = if (dockerConfig.autoRemove) List("--rm=true") else List.empty[String]
    val memory = config.containerMemoryLimit.toList.flatMap(memory => List("-m", memory))
    val envVars = dockerConfig.envVars.flatMap(v => List("-e", v.toString))

    val args = vols ++ entrypoint ++ memory ++ envVars

    val postArgs = autoRemoveCmd ++ dockerConfig.commands
    (args, postArgs)
  }

  private def getDockerCmd(dockerConfig: DockerConfig): List[String] = {

    val (args, postArgs) = asArgsLists(dockerConfig, config)
    val cmd = docker.baseCmd.getOrElse {
      if (docker.needsCompilation || dockerConfig.canUseInternet) {
        DockerConstants.dockerRunNonStrictCmd
      } else {
        DockerConstants.dockerRunCmd
      }
    }

    (cmd.split(" ") ++ args ++ List(docker.dockerImage) ++ postArgs).toList
  }

}

object DockerConstants {
  private val rmOpts = sys.props.get("codacy.tests.noremove").map(_ => List.empty).getOrElse(List("--rm=true"))

  val dockerBin: String = sys.env.getOrElse("CODACY_DOCKER_BIN", "docker")
  val dockerStrictFlags: String = Seq("--net=none", "--cap-drop=ALL").mkString(" ")
  val dockerRunNonDaemonCmd: String =
    Seq(dockerBin, "run", dockerStrictFlags, "--privileged=false", "--user=docker").mkString(" ")
  val dockerRunCmd: String = (dockerRunNonDaemonCmd +: rmOpts).mkString(" ")
  val dockerRunNonStrictCmd: String =
    (Seq(dockerBin, "run", s"--user=${DockerRunner.DockerUsername}") ++ rmOpts).mkString(" ")
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy