Customizing the runner
The runner is responsible for stating the native processes and wiring all the redirections together. The default
implementation is called JVMProcessRunner
.
There are use cases when providing a custom runner makes sense. One such use case could be to launch external processes within a docker container in case of running on a development machine (for example from tests), while running them directly in production, when the whole service is running within the container.
We can implement this scenario by using JVMProcessRunner
in production and a custom DockerizedProcessRunner
in tests,
where we define the latter as follows:
import java.nio.file.Path
import java.util.UUID
case class DockerImage(name: String)
case class DockerContainer(name: String)
case class DockerProcessInfo[DockerProcessInfo](container: DockerContainer, dockerProcessInfo: DockerProcessInfo)
class DockerizedProcessRunner[Info](processRunner: ProcessRunner[Info],
mountedDirectory: Path,
workingDirectory: Path,
image: DockerImage)
extends ProcessRunner[DockerProcessInfo[Info]] {
override def startProcess[O, E](process: Process[O, E]): ZIO[Any, ProxError, RunningProcess[O, E, DockerProcessInfo[Info]]] = {
for {
container <- generateContainerName
runningProcess <- processRunner
.startProcess(wrapInDocker(process, container))
} yield runningProcess.mapInfo(info => DockerProcessInfo(container, info))
}
override def startProcessGroup[O, E](processGroup: ProcessGroup[O, E]): ZIO[Any, ProxError, RunningProcessGroup[O, E, DockerProcessInfo[Info]]] = {
ZIO.foreach(processGroup.originalProcesses.toVector)(key => generateContainerName.map(c => key -> c)).flatMap { keyAndNames =>
val nameMap = keyAndNames.toMap
val names = keyAndNames.map(_._2)
val modifiedProcessGroup = processGroup.map(new ProcessGroup.Mapper[O, E] {
def mapFirst[P <: Process[ZStream[Any, ProxError, Byte], E]](process: P): P = wrapInDocker(process, names.head).asInstanceOf[P]
def mapInnerWithIdx[P <: Process.UnboundIProcess[ZStream[Any, ProxError, Byte], E]](process: P, idx: Int): P =
wrapInDocker(process, names(idx)).asInstanceOf[P]
def mapLast[P <: Process.UnboundIProcess[O, E]](process: P): P = wrapInDocker(process, names.last).asInstanceOf[P]
})
processRunner.startProcessGroup(modifiedProcessGroup)
.map(_.mapInfo { case (key, info) => DockerProcessInfo(nameMap(key), info) })
}
}
private def generateContainerName: ZIO[Any, ProxError, DockerContainer] =
ZIO.attempt(DockerContainer(UUID.randomUUID().toString)).mapError(UnknownProxError)
private def wrapInDocker[O, E](process: Process[O, E], container: DockerContainer): Process[O, E] = {
val envVars = process.environmentVariables.flatMap { case (key, value) => List("-e", s"$key=$value") }.toList
process.withCommand("docker").withArguments(
"run" ::
"--name" :: container.name ::
"-v" :: mountedDirectory.toString ::
"-w" :: workingDirectory.toString ::
envVars :::
List(image.name, process.command) :::
process.arguments
)
}
}