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

automately.core.services.container.ContainerService Maven / Gradle / Ivy

There is a newer version: 1.8.8
Show newest version
package automately.core.services.container;

import automately.core.data.User;
import automately.core.file.VirtualFile;
import automately.core.file.VirtualFileSystem;
import automately.core.services.core.AutomatelyService;
import com.hazelcast.core.IMap;
import com.jcraft.jsch.*;
import com.spotify.docker.client.*;
import com.spotify.docker.client.messages.*;
import io.jsync.app.core.Cluster;
import io.jsync.app.core.Logger;
import io.jsync.buffer.Buffer;
import io.jsync.json.JsonObject;
import io.jsync.utils.CryptoUtils;
import org.apache.commons.lang.NullArgumentException;
import org.apache.commons.lang3.ArrayUtils;

import java.io.*;
import java.net.InetAddress;
import java.net.InterfaceAddress;
import java.net.NetworkInterface;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.attribute.PosixFilePermission;
import java.util.*;

import static automately.core.util.file.FileUtil.purgeDirectory;

/**
 * The ContainerService is an Automately Service and an API that can be used to
 * utilize docker containers within the host machine. It is designed to interact with
 * docker on the local machine. This will change in the future.
 */
public class ContainerService extends AutomatelyService {

    public static boolean BIND_RANDOM = true;
    private static String BROADCAST_ADDRESS = "0.0.0.0";

    // This provides the build directive for the default
    // docker image. This is very important because we need
    // certain things to be set in the vm for proper support.
    private static String[] DEFAULT_DOCKER_DATA = {
            "FROM ubuntu:latest",
            "MAINTAINER Tony Rice ",
            "LABEL version=\"0.0.8\"",
            "\n",
            "RUN apt-get update && apt-get install -y \\",
            "   openssh-server \\",
            "   software-properties-common \\",
            "   python-software-properties \\",
            "   nano \\",
            "   git \\",
            "   curl \\",
            "   apt-transport-https \\",
            "   && mkdir /var/run/sshd \\",
            "   && add-apt-repository -y ppa:webupd8team/java && \\",
            "   apt-get update -y -q && \\",
            "   (echo oracle-java8-installer shared/accepted-oracle-license-v1-1 select true | /usr/bin/debconf-set-selections) && \\",
            "   apt-get install -y oracle-java8-installer && \\",
            "   apt-get upgrade -y && (curl -sL https://deb.nodesource.com/setup_4.x | sudo -E bash -) && \\",
            "   (curl https://dl-ssl.google.com/linux/linux_signing_key.pub | apt-key add -) && \\",
            "   (curl https://storage.googleapis.com/download.dartlang.org/linux/debian/dart_stable.list > /etc/apt/sources.list.d/dart_stable.list) && \\",
            "   apt-get update -y -q && \\",
            "   apt-get install -y nodejs dart php5-cli php5-mcrypt php5-mysql\n\n",
            "RUN sed -i 's/PermitRootLogin without-password/PermitRootLogin yes/' /etc/ssh/sshd_config",
            "RUN echo \"export VISIBLE=now\" >> /etc/profile",
            "ENV NOTVISIBLE \"in users profile\"",
            "\n",
            "EXPOSE 22",
            "CMD [\"/usr/sbin/sshd\", \"-D\"]"
    };

    public static String DEFAULT_IMAGE = "automately-ubuntu";

    private static DockerClient docker;
    private static Path containerPath = Paths.get("/var/run/automately/container/");
    private static Logger logger;

    private static IMap userContainers = null;
    private static Cluster cluster = null;
    private static Set localContainers = new LinkedHashSet<>();

    private static void checkInitialized() {
        if (!initialized()) {
            throw new RuntimeException("The default DockerClient has not been initialized.");
        }
    }

    @Override
    public void start(Cluster owner) {

        cluster = owner;
        logger = owner.logger();

        try {

            if (cluster.config().isDebug()) {
                containerPath = Paths.get("tmp/container/");
            }

            logger.info("Verifying access to " + containerPath.toAbsolutePath().toString());

            if (!Files.isWritable(containerPath) && Files.exists(containerPath)) {
                logger.fatal("Cannot write to " + containerPath.toAbsolutePath().toString());
                return;
            }

            if (!Files.exists(containerPath)) {
                Files.createDirectories(containerPath);
            }

            docker = DefaultDockerClient.builder()
                    .uri(DefaultDockerClient.DEFAULT_UNIX_ENDPOINT)
                    .build();

            if (!docker.ping().equals("OK")) {
                logger.fatal("Could not create the default DockerClient. (Ping Failed)");
            }

            // TODO detect default bridge

            logger.info("Searching for the default (docker0) docker \"bridge\" interface...");

            NetworkInterface bridgeInterface = null;

            Enumeration nets = NetworkInterface.getNetworkInterfaces();
            for (NetworkInterface iface : Collections.list(nets)){
                if(iface.getDisplayName().matches("^docker0$")){
                    logger.info("Found the default (docker0) docker \"bridge\" interface...");
                    bridgeInterface = iface;
                    break;
                }
            }

            if(bridgeInterface == null){
                logger.fatal("Could not find the default (docker0) docker \"bridge\" interface!");
                return;
            }

            logger.info("Using the docker \"bridge\" interface: " + bridgeInterface);

            logger.info("Searching for the host address for the \"bridge\" interface...");

            InetAddress addr = null;

            for(InterfaceAddress ifaceaddr : bridgeInterface.getInterfaceAddresses()){
                if(ifaceaddr.getBroadcast() != null){
                    // this means it is not an ipv6 address
                    addr = ifaceaddr.getAddress();
                    logger.info("Found the host address for the host address for the \"bridge\" interface: "  + addr.getCanonicalHostName());
                    break;
                }
            }

            if(addr == null){
                logger.fatal("Could not find the host address for the \"bridge\" interface!");
                return;
            }

            logger.info("Using the host address: " + addr);

            String broadcastAddress;

            JsonObject containerConfig = coreConfig().getObject("docker", new JsonObject());

            if(!containerConfig.containsField("broadcast_address")){

                logger.info("Creating default configuration...");

                containerConfig.putString("broadcast_address", "0.0.0.0");
                containerConfig.putBoolean("broadcast_all", false);

                coreConfig().putObject("docker", containerConfig);
                cluster.config().save();
            }

            broadcastAddress = containerConfig.getString("broadcast_address", "0.0.0.0");

            // Attempt to retrieve an actual address
            if(broadcastAddress.equals("0.0.0.0") &&
                    !containerConfig.getBoolean("broadcast_all", false)){

                // There should be a default interface defined
                logger.info("Searching for the default \"broadcast\" interface and address...");

                nets = NetworkInterface.getNetworkInterfaces();
                i:
                for (NetworkInterface iface : Collections.list(nets)){
                    if(!iface.isLoopback() && !iface.getDisplayName().matches("^docker\\d+$")){
                        for(InterfaceAddress ifaceaddr : iface.getInterfaceAddresses()){
                            if(ifaceaddr.getBroadcast() != null){
                                broadcastAddress = ifaceaddr.getAddress().getHostAddress();
                                logger.info("Found possible default \"broadcast\" address: " + broadcastAddress + " (can be changed by updated \"broadcast_address\")");
                                containerConfig.putString("broadcast_address", broadcastAddress);
                                coreConfig().putObject("docker", containerConfig);
                                cluster.config().save();
                                break i;
                            }
                        }
                    }
                }
            }

            // The broadcast address is used to bind ports
            // exposed on the container. This address
            // Should be accessible via other containers
            BROADCAST_ADDRESS = broadcastAddress;

            userContainers = cluster().data().persistentMap(ContainerService.class.getCanonicalName() + ".user.containers");

            logger.info("Building the default Docker image...");

            io.jsync.buffer.Buffer dockerData = new Buffer(String.join("\n", DEFAULT_DOCKER_DATA));

            buildImage(DEFAULT_IMAGE, dockerData);

            logger.info("Cleaning up old containers...");

            for(String containerId : userContainers.keySet()){
                JsonObject containerData = userContainers.get(containerId);
                if(containerData.getString("broadcast_address", "").equals(BROADCAST_ADDRESS)){
                    logger.info("Killing \"" + containerId + "\"...");
                    try {
                        docker.killContainer(containerId);
                        docker.removeContainer(containerId, true);
                    } catch (Exception ignored){
                        // We want to catch this just in case the container doesn't exist
                    }
                    userContainers.remove(containerId);
                }
            }

        } catch (IOException | DockerException | InterruptedException e) {
            e.printStackTrace();
            docker = null;
            logger.fatal("Could not create the default DockerClient: " + e.getMessage());
        }
    }

    @Override
    public void stop() {
        try {
            if (initialized()) {
                for(SecureContainer container : localContainers){
                    try {
                        if(!container.killed() || container.running()){
                            container.kill();
                        }
                    } catch (Exception ignored){}
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            docker = null;
        }
    }

    @Override
    public String name() {
        return getClass().getCanonicalName();
    }

    /**
     * This method returns true if the ContainerService has been initialized on this cluster.
     * @return
     */
    public static boolean initialized() {
        return docker != null && userContainers != null && cluster != null;
    }

    /**
     * buildImage() is used to build and store docker images from raw data.
     * @param imageName the name if the image you wish to build
     * @param dockerFileData the data of the Dockerfile you are building
     */
    public static void buildImage(String imageName, io.jsync.buffer.Buffer dockerFileData){
        checkInitialized();

        try {

            Path tmpDocker = Paths.get("/tmp/" + imageName + "_tmp");

            if (Files.exists(tmpDocker)) {
                purgeDirectory(tmpDocker);
            }

            Files.createDirectory(tmpDocker);

            Path dockerFile = tmpDocker.resolve("Dockerfile");

            Files.write(dockerFile, dockerFileData.getBytes());

            // This should definitely not be fatal
            if (!Files.exists(tmpDocker)) {
                logger.fatal("Could not create the file " + dockerFile);
                return;
            }

            logger.info("Building the docker image \"" + imageName + "\"...");

            docker.build(tmpDocker, imageName, msg -> {
                if (msg.stream() != null && !msg.stream().isEmpty()) {
                    logger.info(msg.stream().trim());
                }
            });

            logger.info("The docker image \"" + imageName + "\" has been built.");

        } catch (Exception e) {
            // Todo throw a proper error
            e.printStackTrace();
        }
    }

    public static SecureContainer create(String containerName, User user, String imageName, boolean killOnRestart, String[] exposedPorts, String[] pathBindings) {

        checkInitialized();

        try {

            containerName = containerName.trim();

            /**
             * Begin container file setup.
             */

            String userHash = CryptoUtils.calculateHmacSHA1(user.username + user.token() + user.created.toString(), user.username + containerName);

            Path userPath = containerPath.resolve(userHash + "/");

            if (!Files.exists(userPath)) {
                Files.createDirectory(userPath);
            }

            /**
             * End container file setup.
             */

            // This directories are created by default just
            // in case we want to connect to a container via
            // ssh
            pathBindings = ArrayUtils.addAll(pathBindings,  "ssh/:/root/.ssh/");

            Set directoryBindings = new LinkedHashSet<>();
            Set finalBinds = new LinkedHashSet<>();

            for(String bind : pathBindings){
                Path bindPath = userPath.resolve(bind.split(":")[0].trim());
                String bindTo = bind.split(":")[1].trim();
                boolean readOnly = bind.split(":").length > 2 && bind.split(":")[2].equals("ro");

                // Todo make better
                if (!Files.exists(bindPath)) {
                    Files.createDirectory(bindPath);
                }

                String fullBind = bindPath.toAbsolutePath().toAbsolutePath() + ":" + bindTo;
                if(readOnly){
                    fullBind += ":ro";
                }

                finalBinds.add(fullBind);
                directoryBindings.add(bindTo);
            }

            // TODO Implement custom port bindings and such
            String[] ports = {"22"};

            ports = ArrayUtils.addAll(ports, exposedPorts);

            Map> portBindings = new HashMap<>();

            for (String port : ports) {
                List hostPorts = new ArrayList<>();
                String newPort = "";
                if (!BIND_RANDOM) {
                    newPort = port;
                }
                hostPorts.add(PortBinding.of(BROADCAST_ADDRESS, newPort));
                portBindings.put(port, hostPorts);
            }

            long memoryLimit = ((long) (5242880))/2;

            HostConfig hostConfig = HostConfig.builder()
                    .memory(memoryLimit) // TODO fix this
                    .binds(finalBinds.toArray(new String[finalBinds.size()]))
                    .portBindings(portBindings)
                    .privileged(false) // This must be always false for security reasons (may change in the future)
                    .networkMode("bridge") // TODO firewall hosts
                    .build();

            if(imageName == null || imageName.isEmpty()){
                imageName = DEFAULT_IMAGE; // Using the Default Container
            }

            Iterator iterator = docker.listImages(DockerClient.ListImagesParam.allImages()).iterator();

            boolean imageFound = false;
            while (iterator.hasNext()){
                Image image = iterator.next();
                if(image.repoTags().contains(imageName)){
                    imageFound = true;
                    break;
                }
            }

            if(!imageFound){
                logger.info("Attempting to pull the image \"" + imageName + "\" from the default Docker repository.");
                docker.pull(imageName, p -> {
                    logger.info(p.toString());
                });
            }

            ContainerConfig containerConfig = ContainerConfig.builder().image(imageName)
                    .memory(memoryLimit)
                    .volumes(directoryBindings).hostConfig(hostConfig)
                    .exposedPorts(ports).build();

            ContainerCreation creation;

            creation = docker.createContainer(containerConfig);

            JsonObject containerData = new JsonObject();
            containerData.putString("userToken", user.token());
            containerData.putString("id", creation.id());
            containerData.putString("host", cluster.hazelcast().getCluster().getLocalMember().getUuid());
            // This is stored so we can access ports
            containerData.putString("broadcast_address", BROADCAST_ADDRESS);

            containerData.putBoolean("kill_on_restart", killOnRestart);

            SecureContainer container = new SecureContainer(containerName, creation.id(), user, userPath);

            userContainers.put(creation.id(), containerData);

            localContainers.add(container);

            return container;
        } catch (Exception e) {
            if(cluster.config().isDebug()){
                e.printStackTrace();
            }
            throw new RuntimeException("Failed to create a new SecureContainer.", e);
        }
    }

    public static SecureContainer create(String containerName, User user, String imageName, boolean killOnRestart, String[] exposedPorts){
        return create(containerName, user, imageName, killOnRestart, exposedPorts, null);
    }

    public static SecureContainer create(String containerName, User user, String imageName, boolean killOnRestart){
        return create(containerName, user, imageName, killOnRestart, null, null);
    }

    public static SecureContainer create(String containerName, User user, String imageName){
        return create(containerName, user, imageName, true, null, null);
    }

    public static SecureContainer connect(User user, String containerName, String containerId) {
        checkInitialized();
        try {
            for (Container container : docker.listContainers(DockerClient.ListContainersParam.allContainers())) {
                if (container.id().equals(containerId)) {
                    // TODO Ensure the user directory exists
                    containerName = containerName.trim();

                    String userHash = CryptoUtils.calculateHmacSHA1(user.username + user.token() + user.created.toString(), user.username + containerId);
                    Path userPath = containerPath.resolve(userHash + "/");
                    if (!Files.exists(userPath)) {
                        // Ensure the container is killed
                        docker.killContainer(containerId);
                        return null;
                    }

                    SecureContainer nContainer = new SecureContainer(containerName, containerId, user, containerPath.resolve(userHash + "/"));

                    JsonObject containerData = userContainers.get(containerId);

                    if(containerData == null){
                        containerData = new JsonObject();
                    }

                    containerData.putString("userToken", user.token());
                    containerData.putString("id", containerId);
                    containerData.putString("host", cluster.hazelcast().getCluster().getLocalMember().getUuid());

                    userContainers.put(containerId, containerData);

                    localContainers.add(nContainer);

                    return nContainer;
                }
            }
        } catch (DockerException | InterruptedException e) {
            throw new RuntimeException(e);
        }
        return null;
    }

    public static class SecureContainer {

        private static JSch ssh;
        private String containerId;
        private User user;
        private boolean initialized = false;
        private PortBinding shellPort = null;
        private PortBinding webPort = null;
        private String defaultWebPort = "8080";
        private boolean connected = false;
        private Session session;
        private Path dataPath = null;
        private String name;
        private boolean killed = false;

        protected SecureContainer(String name, String containerId, User user, Path dataPath) {
            if (containerId == null) {
                throw new NullPointerException();
            }
            this.containerId = containerId;
            this.dataPath = dataPath;
            this.user = user;
            this.name = name;
        }

        public String id() {
            return containerId;
        }

        public String name(){
            return this.name;
        }

        public PortBinding shellPort(){
            return shellPort;
        }

        public PortBinding webPort(){
            return webPort;
        }

        public void setDefaultWebPort(Number webPort){
            if(webPort == null){
                throw new NullArgumentException("webPort cannot be null");
            }
            if(webPort.intValue() <= 0){
                throw new IllegalArgumentException("webPort must be greater than 0");
            }
            defaultWebPort = String.valueOf(webPort.intValue());
        }

        public void uploadPath(String srcPath, String dest) {

            if(killed){
                throw new RuntimeException("This container has been killed.");
            }

            if (!initialized()) {
                throw new RuntimeException("This container does not seem to be initialized.");
            }

            try {

                Session session = ssh.getSession("root", shellPort.hostIp(), Integer.parseInt(shellPort.hostPort()));

                session.setConfig("StrictHostKeyChecking", "no");
                session.connect();

                Channel channel = session.openChannel("sftp");

                channel.connect();
                ChannelSftp channelSftp = (ChannelSftp) channel;

                String uploadPath = VirtualFileSystem.getPathAlias(dest);

                srcPath = VirtualFileSystem.getPathAlias(srcPath);

                channelSftp.mkdir(uploadPath);

                channelSftp.cd(uploadPath);

                for(VirtualFile file : VirtualFileSystem.getUserFiles(user, srcPath, 0, 0, true, false, true)){
                    String destFile = uploadPath + file.pathAlias.replace(srcPath, "") + file.name;
                    io.jsync.buffer.Buffer buffer = VirtualFileSystem.readFileData(file);
                    if(buffer != null){
                        ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(buffer.getBytes());
                        channelSftp.put(byteArrayInputStream, destFile);
                    }
                }

                channelSftp.exit();
                session.disconnect();

            } catch (Exception e) {
                e.printStackTrace();
                throw new RuntimeException("Failed connect to a secure shell session.", e);
            }
        }

        public void uploadFile(String srcFile, String dest) {

            if(killed){
                throw new RuntimeException("This container has been killed.");
            }

            if (!initialized()) {
                throw new RuntimeException("This container does not seem to be initialized.");
            }

            try {

                Session session = ssh.getSession("root", shellPort.hostIp(), Integer.parseInt(shellPort.hostPort()));

                session.setConfig("StrictHostKeyChecking", "no");
                session.connect();

                Channel channel = session.openChannel("sftp");

                channel.connect();
                ChannelSftp channelSftp = (ChannelSftp) channel;

                String uploadPath = VirtualFileSystem.getPathAlias(dest);

                String srcPath = VirtualFileSystem.getPathAlias(srcFile);

                channelSftp.mkdir(uploadPath);

                channelSftp.cd(uploadPath);

                VirtualFile file = VirtualFileSystem.getUserFile(user, srcPath);

                if(file != null){
                    String destFile = uploadPath + file.pathAlias.replace(srcPath, "") + file.name;
                    io.jsync.buffer.Buffer buffer = VirtualFileSystem.readFileData(file);
                    if(buffer != null){
                        ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(buffer.getBytes());
                        channelSftp.put(byteArrayInputStream, destFile);
                    }
                }

                channelSftp.exit();
                session.disconnect();

            } catch (Exception e) {
                throw new RuntimeException("Failed connect to a secure shell session.", e);
            }
        }

        public boolean running() {
            try {
                return docker.inspectContainer(containerId).state().running();
            } catch (DockerException | InterruptedException ignored) {
            }
            return false;
        }

        public boolean paused() {
            try {
                return docker.inspectContainer(containerId).state().paused();
            } catch (DockerException | InterruptedException ignored) {
            }
            return false;
        }

        public boolean restarting() {
            try {
                return docker.inspectContainer(containerId).state().restarting();
            } catch (DockerException | InterruptedException ignored) {
            }
            return false;
        }

        public boolean killed(){
            return killed;
        }

        public boolean oomKilled() {
            try {
                return docker.inspectContainer(containerId).state().oomKilled();
            } catch (DockerException | InterruptedException ignored) {
            }
            return false;
        }

        public int exitCode() {
            try {
                return docker.inspectContainer(containerId).state().exitCode();
            } catch (DockerException | InterruptedException ignored) {
            }
            return -1;
        }

        public String error() {
            try {
                return docker.inspectContainer(containerId).state().error();
            } catch (DockerException | InterruptedException ignored) {
            }
            return "";
        }

        public ExecState exec(String... command) {
            return exec(true, null, null, command);
        }

        public ExecState exec(boolean waitForFinish, OutputStream stdout, OutputStream stderr, String... command) {
            try {

                if(killed){
                    throw new RuntimeException("This container has been killed.");
                }

                if (!initialized()) {
                    throw new RuntimeException("This container does not seem to be initialized.");
                }

                logger.info("Container Exec (" + containerId + ") " + Arrays.toString(command));

                String execId = docker.execCreate(containerId, command, DockerClient.ExecParameter.STDOUT, DockerClient.ExecParameter.STDERR);

                if (waitForFinish) {

                    LogStream logStream = docker.execStart(execId);

                    if(stdout != null && stderr != null){
                        logStream.attach(stdout, stderr);
                    }

                    while (docker.execInspect(execId).running()) {
                        Thread.sleep(5);
                    }

                    // A timeout to make the exec wait a little bit
                    Thread.sleep(150);

                    String out = logStream.readFully().trim();
                    if (!out.isEmpty()) {
                        logger.info("Container Exec Result (" + containerId + ") " + out);
                    }
                    return docker.execInspect(execId);
                } else {
                    docker.execStart(execId);
                    Thread.sleep(150);
                    return docker.execInspect(execId);
                }

            } catch (IOException | DockerException | InterruptedException e) {
               throw new RuntimeException(e);
            }
        }

        public void disconnect(){

            if(killed){
                throw new RuntimeException("This container has been killed.");
            }

            if (!initialized()) {
                throw new RuntimeException("This container does not seem to be initialized.");
            }

            if(!connected()){
                throw new RuntimeException("You are not currently connected to a shell session.");
            }

            try {
                session.disconnect();
                session = null;
                connected = false;
            } catch (Exception ignored){};
        }

        public boolean connected() {
            return (session != null && session.isConnected()) && connected;
        }

        public void connect(InputStream in, OutputStream out) {
            if (connected()) {
                throw new RuntimeException("You are already connected to a secure session.");
            }

            if(killed){
                throw new RuntimeException("This container has been killed.");
            }

            if (!initialized()) {
                throw new RuntimeException("This container does not seem to be initialized.");
            }

            try {

                session = ssh.getSession("root", shellPort.hostIp(), Integer.parseInt(shellPort.hostPort()));

                session.setConfig("StrictHostKeyChecking", "no");
                session.connect();

                Channel channel = session.openChannel("shell");

                channel.setInputStream(in);
                channel.setOutputStream(out);

                channel.connect();

                connected = true;

            } catch (Exception e) {
                e.printStackTrace();
                session = null;
                connected = false;
                throw new RuntimeException("Failed connect to a secure shell session.", e);
            }
        }

        public boolean initialized() {
            return initialized && shellPort != null;
        }

        public void initialize() {
            try {

                if(killed){
                    throw new RuntimeException("This container has been killed.");
                }

                if (initialized()) {
                    throw new RuntimeException("Cannot call initialize() when the container is already initialized.");
                }

                if (!running()) {
                    docker.startContainer(containerId);

                    while (!running() && error().isEmpty() && !paused()) {
                        Thread.sleep(15);
                    }
                }

                initialized = running();

                if (!docker.inspectContainer(containerId).networkSettings().ports().containsKey("22") || !docker.inspectContainer(containerId).networkSettings().ports().get("22").iterator().hasNext()) {
                    throw new RuntimeException("There was an error initializing the SecureContainer \"" + containerId + "\".");
                }

                shellPort = docker.inspectContainer(containerId).networkSettings().ports().get("22").iterator().next();

                // For right now the only port we want exposed is what we consider the "web port"
                if (docker.inspectContainer(containerId).networkSettings().ports().containsKey(defaultWebPort) &&
                        docker.inspectContainer(containerId).networkSettings().ports().get(defaultWebPort).iterator().hasNext()) {
                    webPort = docker.inspectContainer(containerId).networkSettings().ports().get(defaultWebPort).iterator().next();
                }

                // TODO update container data

                JsonObject containerData = userContainers.get(containerId);

                if(containerData == null){
                    containerData = new JsonObject();
                }

                containerData.putString("userToken", user.token());
                containerData.putString("id", containerId);
                containerData.putString("host", cluster.hazelcast().getCluster().getLocalMember().getUuid());

                containerData.putString("shell_port", shellPort.hostPort());
                containerData.putString("shell_host", shellPort.hostIp());

                if(webPort != null){
                    containerData.putString("web_port", webPort.hostPort());
                    containerData.putString("web_host", webPort.hostIp());
                }

                logger.info("Storing container data: " + containerData.encode());

                userContainers.put(containerId, containerData);

                if (ssh == null) {
                    ssh = new JSch();
                }

                Path pkeyFile = dataPath.resolve("ssh/id_rsa");
                Path pukeyFile = dataPath.resolve("ssh/id_rsa.pub");
                Path akeyFile = dataPath.resolve("ssh/authorized_keys");

                // If it already exists we do not need to create it.
                if (!(Files.exists(akeyFile) && Files.exists(pkeyFile))) {

                    KeyPair kpair = KeyPair.genKeyPair(ssh, KeyPair.RSA);

                    // Write the private key to a secure directory and set the permissions
                    String pkeyPath = pkeyFile.toAbsolutePath().toString();

                    kpair.writePrivateKey(pkeyPath);
                    kpair.writePublicKey(pukeyFile.toAbsolutePath().toString(), "Auth Key");
                    kpair.writePublicKey(akeyFile.toAbsolutePath().toString(), "Auth Key");

                    if (!Files.exists(pkeyFile)) {
                        throw new FileNotFoundException(pkeyFile.toAbsolutePath().toString());
                    }

                    Set keyPerms = new LinkedHashSet<>();
                    keyPerms.add(PosixFilePermission.OWNER_READ);
                    keyPerms.add(PosixFilePermission.GROUP_READ);
                    keyPerms.add(PosixFilePermission.OWNER_WRITE);
                    keyPerms.add(PosixFilePermission.OWNER_WRITE);

                    Files.setPosixFilePermissions(pkeyFile, keyPerms);
                    Files.setPosixFilePermissions(pukeyFile, keyPerms);
                    Files.setPosixFilePermissions(akeyFile, keyPerms);

                    kpair.dispose();

                    // We want to change the permissions
                    exec("chown", "-R", "root:root", "/root/.ssh/");
                }

                // We must change the permissions by running exec within the VM just in case
                // We don't have the correct permissions. (TODO change this)
                exec("chmod", "0755", "/root/.ssh/id_rsa");
                exec("chmod", "0755", "/root/.ssh/id_rsa.pub");

                KeyPair kp = KeyPair.load(ssh, pkeyFile.toAbsolutePath().toString());

                ssh.addIdentity(user.token(), kp.forSSHAgent(), null, null);

                exec("chmod", "0600", "/root/.ssh/id_rsa");
                exec("chmod", "0600", "/root/.ssh/id_rsa.pub");

            } catch (DockerException | InterruptedException | IOException | JSchException e) {
                e.printStackTrace();
                throw new RuntimeException("There was an issue while attempting to initialize this container.", e);
            }
        }

        public void kill() {
            try {
                if (connected()) {
                    session.disconnect();
                }
                docker.killContainer(containerId);
                docker.removeContainer(containerId, true);
            } catch (Exception ignored) {
            } finally {
                initialized = false;
                shellPort = null;
                session = null;
                ssh = null;
                connected = false;
                killed = true;
            }
        }
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy