package fr.ens.biologie.genomique.eoulsan.util.process; import static com.google.common.base.Preconditions.checkArgument; import static com.google.common.base.Preconditions.checkNotNull; import static fr.ens.biologie.genomique.eoulsan.EoulsanLogger.getLogger; import java.io.File; import java.io.IOException; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.Map; import fr.ens.biologie.genomique.eoulsan.EoulsanLogger; import fr.ens.biologie.genomique.eoulsan.util.ProcessUtils; import fr.ens.biologie.genomique.eoulsan.util.SystemUtils; /** * This class define a Docker image instance using the Docker command line. * @author Laurent Jourdren * @since 2.0 */ public class FallBackDockerImageInstance extends AbstractSimpleProcess implements DockerImageInstance { private final String dockerImage; private final int userUid; private final int userGid; @Override public AdvancedProcess start(final List<String> commandLine, final File executionDirectory, final Map<String, String> environmentVariables, final File temporaryDirectory, final File stdoutFile, final File stderrFile, final boolean redirectErrorStream, final File... filesUsed) throws IOException { checkNotNull(commandLine, "commandLine argument cannot be null"); checkNotNull(stdoutFile, "stdoutFile argument cannot be null"); checkNotNull(stderrFile, "stderrFile argument cannot be null"); EoulsanLogger.getLogger().fine(getClass().getName() + " : commandLine=" + commandLine + ", executionDirectory=" + executionDirectory + ", environmentVariables=" + environmentVariables + ", temporaryDirectory=" + temporaryDirectory + ", stdoutFile=" + stdoutFile + ", stderrFile=" + stderrFile + ", redirectErrorStream="+redirectErrorStream + ", filesUsed" + Arrays.toString(filesUsed)); if (executionDirectory != null) { checkArgument(executionDirectory.isDirectory(), "execution directory does not exists or is not a directory: " + executionDirectory.getAbsolutePath()); } // Pull image if needed pullImageIfNotExists(); final List<String> command = new ArrayList<>(); command.add("docker"); command.add("run"); // File/directories to mount List<File> directoriesToBind = new ArrayList<>(); if (filesUsed != null) { directoriesToBind.addAll(Arrays.asList(filesUsed)); } // Execution directory if (executionDirectory != null) { directoriesToBind.add(executionDirectory); command.add("--workdir"); command.add(executionDirectory.getAbsolutePath()); } // Environment variables if (environmentVariables != null) { for (Map.Entry<String, String> e : environmentVariables.entrySet()) { command.add("--env"); command.add(e.getKey() + '=' + e.getValue()); } } // Temporary directory if (temporaryDirectory != null && temporaryDirectory.isDirectory()) { directoriesToBind.add(temporaryDirectory); } // Bind directories toBind(command, directoriesToBind); // Set the UID and GID of the docker process if (this.userUid >= 0 && this.userGid >= 0) { command.add("--user"); command.add(this.userUid + ":" + this.userGid); } // Remove container at the end of the execution command.add("--rm"); // Docker image to use command.add(this.dockerImage); command.addAll(commandLine); // Redirect outputs final ProcessBuilder pb = new ProcessBuilder(command); pb.redirectOutput(stdoutFile); pb.redirectErrorStream(redirectErrorStream); if (!redirectErrorStream) { pb.redirectError(stderrFile); } final Process process = pb.start(); return new AdvancedProcess() { @Override public int waitFor() throws IOException { try { return process.waitFor(); } catch (InterruptedException e) { throw new IOException(e); } } }; } /** * Add the volume arguments to the Docker command line. * @param command the command line * @param directories the share directory to add */ private static void toBind(final List<String> command, final List<File> directories) { for (File directory : directories) { if (directory != null) { command.add("--volume"); command.add( directory.getAbsolutePath() + ':' + directory.getAbsolutePath()); } } } @Override public void pullImageIfNotExists() throws IOException { String images = ProcessUtils.execToString( "docker images | tr -s ' ' | cut -f 1,2 -d ' ' | tr ' ' :"); for (String image : images.split("\n")) { if (this.dockerImage.equals(image)) { // Nothing to do, the image has been already download return; } } getLogger().fine("Pull Docker image: " + this.dockerImage); Process p = new ProcessBuilder("docker", "pull", this.dockerImage).start(); int exitCode; try { exitCode = p.waitFor(); } catch (InterruptedException e) { throw new IOException( "Error while pulling Docker image: " + this.dockerImage); } if (exitCode != 0) { throw new IOException( "Error while pulling Docker image: " + this.dockerImage); } } @Override public void pullImageIfNotExists(final ProgressHandler progress) throws IOException { if (progress != null) { progress.update(0); } pullImageIfNotExists(); if (progress != null) { progress.update(1); } } // // Constructor // /** * Constructor. * @param dockerImage Docker image */ FallBackDockerImageInstance(final String dockerImage) { checkNotNull(dockerImage, "dockerImage argument cannot be null"); EoulsanLogger.getLogger().fine(getClass().getName()+" docker image used: "+ dockerImage); this.dockerImage = dockerImage; this.userUid = SystemUtils.uid(); this.userGid = SystemUtils.gid(); } }