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

org.opencb.commons.utils.DockerUtils Maven / Gradle / Ivy

The newest version!
package org.opencb.commons.utils;

import org.apache.commons.lang3.StringUtils;
import org.opencb.commons.exec.Command;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.IOException;
import java.nio.file.Paths;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class DockerUtils {


    private static final Logger LOGGER = LoggerFactory.getLogger(DockerUtils.class);
    private static final String MAPPING_PATH = "--mount type=bind,source=${PATH},target=";
    private static final String PATH_KEY = "${PATH}";
    private static final String MAPPED_PATH = "/data/input";

    /**
     * Create the command line to execute the docker image.
     *
     * @param image         Docker image name
     * @param inputBindings Array of bind mounts for docker input volumes (source-target)
     * @param outputBinding Bind mount for docker output volume (source-target)
     * @param cmdParams     Image command parameters
     * @param dockerParams  Docker parameters
     * @return The command line
     * @throws IOException IO exception
     */
    public static String buildCommandLine(String image, List> inputBindings,
                                          AbstractMap.SimpleEntry outputBinding, String cmdParams,
                                          Map dockerParams) throws IOException {
        return buildCommandLine(image, inputBindings, Collections.singletonList(outputBinding), cmdParams, dockerParams);
    }

    /**
     * Create the command line to execute the docker image.
     *
     * @param image         Docker image name
     * @param inputBindings  Array of bind mounts for docker input volumes (source-target)
     * @param outputBindings Array of bind mount for docker output volume (source-target)
     * @param cmdParams     Image command parameters
     * @param dockerParams  Docker parameters
     * @return The command line
     * @throws IOException IO exception
     */
    public static String buildCommandLine(String image, List> inputBindings,
                                          List> outputBindings, String cmdParams,
                                          Map dockerParams) throws IOException {
        // Sanity check
        if (outputBindings == null || outputBindings.isEmpty()) {
            throw new IllegalArgumentException("Missing output binding(s)");
        }

        // Docker run
        StringBuilder commandLine = new StringBuilder("docker run --rm ");

        // Docker params
        boolean setUser = true;
        if (dockerParams != null) {
            if (dockerParams.containsKey("user")) {
                setUser = false;
            }
            for (String key : dockerParams.keySet()) {
                if (key.equals("user") && StringUtils.isEmpty(dockerParams.get("user"))) {
                    // User wants to disable user setting
                    continue;
                }
                if (!key.startsWith("-")) {
                    commandLine.append("--");
                }
                commandLine.append(key).append(" ");
                if (StringUtils.isNotEmpty(dockerParams.get(key))) {
                    commandLine.append(dockerParams.get(key)).append(" ");
                }
            }
        }

        if (setUser) {
            // User: array of two strings, the first string, the user; the second, the group
            String[] user = FileUtils.getUserAndGroup(Paths.get(outputBindings.get(0).getKey()), true);
            commandLine.append("--user ").append(user[0]).append(":").append(user[1]).append(" ");
        }

        if (inputBindings != null) {
            // Mount management (bindings)
            Set inputBindingSet = new HashSet<>();
            for (AbstractMap.SimpleEntry binding : inputBindings) {
                if (!inputBindingSet.contains(binding.getKey())) {
                    commandLine.append("--mount type=bind,source=\"").append(binding.getKey()).append("\",target=\"")
                            .append(binding.getValue()).append("\",readonly ");
                    inputBindingSet.add(binding.getKey());
                }
            }
        }
        for (AbstractMap.SimpleEntry outputBinding : outputBindings) {
            commandLine.append("--mount type=bind,source=\"").append(outputBinding.getKey()).append("\",target=\"")
                    .append(outputBinding.getValue()).append("\" ");
        }

        // Docker image and version
        commandLine.append(image).append(" ");

        // Image command params
        commandLine.append(cmdParams);
        return commandLine.toString();
    }

    public static String buildMountPathsCommandLine(String image, String entryPoint) {
        return buildMountPathsCommandLine(image, entryPoint, Collections.emptyList());
    }

    public static String buildMountPathsCommandLine(String image, String entryPoint, List dockerOpts) {
        String res = "docker run ";
        if (dockerOpts != null && !dockerOpts.isEmpty()) {
            res += StringUtils.join(dockerOpts, " ") + " ";
        }

        String prefix = entryPoint;
        String suffix = "";
        if (prefix.contains(">")) {
            prefix = entryPoint.substring(0, entryPoint.indexOf(">"));
            suffix = entryPoint.substring(entryPoint.indexOf(">"));
        }

        List paths = getPaths(prefix);
        String mappedPaths = "";
        for (int i = 0; i < paths.size(); i++) {
            mappedPaths += MAPPING_PATH.replace(PATH_KEY, paths.get(i)) + MAPPED_PATH + i + "/ ";
            prefix = prefix.replace(paths.get(i), MAPPED_PATH + i + "/");
        }
        res += mappedPaths + image + " " + prefix + suffix;
        return res;
    }

    private static List getPaths(String entryPoint) {
        // clean '//' because it's not valid path and the regex extracts to '/'
        entryPoint = entryPoint.replace("//", "/");
        Set res = new HashSet<>();
        String regex = "((\\/([A-z0-9-_+\\.]+\\/)+)|(\\/))";
        Pattern regexPattern = Pattern.compile(regex);
        Matcher match = regexPattern.matcher(entryPoint);
        Set aux = new HashSet<>();
        while (match.find()) {
            aux.add(match.group(1));
            res.add(match.group(1));
        }
        //Search for paths contained in other paths, to return only the root path
        for (String path : aux) {
            for (String path2 : aux) {
                if (path.contains(path2) && !path2.equals(path)) {
                    res.remove(path);
                }
            }
        }
        return new ArrayList<>(res);
    }


    /**
     * Create and run the command line to execute the docker image.
     *
     * @param image         Docker image name
     * @param inputBindings Array of bind mounts for docker input volumes (source-target)
     * @param outputBinding Bind mount for docker output volume (source-target)
     * @param cmdParams     Image command parameters
     * @param dockerParams  Docker parameters
     * @return The command line
     * @throws IOException IO exception
     */
    public static String run(String image, List> inputBindings,
                             AbstractMap.SimpleEntry outputBinding, String cmdParams,
                             Map dockerParams) throws IOException {
        return run(image, inputBindings, outputBinding != null ? Collections.singletonList(outputBinding) : null, cmdParams, dockerParams);
    }

    /**
     * Create and run the command line to execute the docker image.
     *
     * @param image         Docker image name
     * @param inputBindings Array of bind mounts for docker input volumes (source-target)
     * @param outputBindings Array of bind mount for docker output volume (source-target)
     * @param cmdParams     Image command parameters
     * @param dockerParams  Docker parameters
     * @return The command line
     * @throws IOException IO exception
     */
    public static String run(String image, List> inputBindings,
                             List> outputBindings, String cmdParams,
                             Map dockerParams) throws IOException {
        checkDockerDaemonAlive();

        String commandLine = buildCommandLine(image, inputBindings, outputBindings, cmdParams, dockerParams);

        LOGGER.info("Run docker command line");
        LOGGER.info("============================");
        LOGGER.info(commandLine);
        LOGGER.info("============================");

        // Execute command
        Command cmd = new Command(commandLine);
        cmd.run();
        if (cmd.getExitValue() != 0) {
            String stderr = cmd.getError();
            int maxOutputContext = 1024;
            if (stderr.length() > maxOutputContext) {
                int length = stderr.length();
                stderr = " ... (length: " + length + ") " + stderr.substring(length - maxOutputContext);
            }
            String stdout = cmd.getOutput();
            if (stdout.length() > maxOutputContext) {
                int length = stdout.length();
                stdout = " ... (length: " + length + ") " + stdout.substring(length - maxOutputContext);
            }
            throw new IOException("Docker command failed with exit value " + cmd.getExitValue()
                    + ": stdout:" + stdout + " stderr:" + stderr);
        }

        return commandLine;
    }


    public static void checkDockerDaemonAlive() throws IOException {
        int maxAttempts = 12;
        for (int i = 0; i < maxAttempts; i++) {
            Command command = new Command("docker stats --no-stream");
            command.run();
            if (command.getExitValue() == 0) {
                // Docker is alive
                if (i != 0) {
                    LOGGER.info("Docker daemon up and running!");
                }
                return;
            }
            LOGGER.info("Waiting for docker to start... (sleep 5s) [" + i + "/" + maxAttempts + "]");
            try {
                Thread.sleep(5000);
            } catch (InterruptedException e) {
                throw new IOException(e);
            }
        }
        throw new IOException("Docker daemon is not available on this node!");
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy