org.fabrician.enabler.DockerClient.java Source code

Java tutorial

Introduction

Here is the source code for org.fabrician.enabler.DockerClient.java

Source

/*
 * Copyright (c) 2014 TIBCO Software Inc. All Rights Reserved.
 *
 * Use is subject to the terms of the TIBCO license terms accompanying the download of this code.
 * In most instances, the license terms are contained in a file named license.txt.
 */
package org.fabrician.enabler;

import static com.google.common.util.concurrent.MoreExecutors.sameThreadExecutor;

import java.io.Closeable;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.Collection;
import java.util.List;
import java.util.Properties;
import java.util.Set;
import java.util.logging.Logger;

import org.fabrician.enabler.predicates.ContainerPredicates;
import org.jclouds.Constants;
import org.jclouds.ContextBuilder;
import org.jclouds.concurrent.config.ExecutorServiceModule;
import org.jclouds.docker.DockerApi;
import org.jclouds.docker.domain.Config;
import org.jclouds.docker.domain.Container;
import org.jclouds.docker.domain.HostConfig;
import org.jclouds.docker.domain.Image;
import org.jclouds.docker.features.RemoteApi;
import org.jclouds.docker.options.BuildOptions;
import org.jclouds.docker.options.ListContainerOptions;
import org.jclouds.docker.options.RemoveContainerOptions;

import com.datasynapse.commons.util.StringUtils;
import com.datasynapse.fabric.util.ContainerUtils;
import com.google.common.base.Charsets;
import com.google.common.base.Optional;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Multimap;
import com.google.common.io.CharStreams;
import com.google.common.io.Closeables;
import com.google.inject.Module;

/**
 * A simplified helper client around JCloud Docker.
 * 
 */
public class DockerClient implements Closeable {

    public static final int DEFAULT_REMOTE_PORT = 2375;
    public static final String DEFAULT_REMOTE_HOST = "127.0.0.1";
    public static final String PROVIDER_NAME = "docker";
    private final String dockerHostname;
    private final Integer dockerPort;
    private DockerApi dockerApi = null;
    private final Set<Module> modules = ImmutableSet
            .<Module>of(new ExecutorServiceModule(sameThreadExecutor(), sameThreadExecutor()));
    private Logger logger = ContainerUtils.getLogger(this);

    public static DockerClient getInstance() {
        DockerClient doc = new DockerClient(null, null);
        doc.init();
        return doc;
    }

    public static DockerClient getInstance(final String dockerHostname, final Integer dockerPort) {
        DockerClient doc = new DockerClient(dockerHostname, dockerPort);
        doc.init();
        return doc;
    }

    private DockerClient(String dockerHostname, Integer dockerPort) {
        if (StringUtils.isEmptyOrBlank(dockerHostname)) {
            dockerHostname = DEFAULT_REMOTE_HOST;
        }
        if (dockerPort == null) {
            dockerPort = DEFAULT_REMOTE_PORT;
        }
        this.dockerHostname = dockerHostname;
        this.dockerPort = dockerPort;
    }

    private void init() {
        this.dockerApi = getApi("http://" + this.dockerHostname + ":" + this.dockerPort);
    }

    RemoteApi remoteApi() {
        return this.dockerApi.getRemoteApi();
    }

    private DockerApi getApi(final String url) {
        return ContextBuilder.newBuilder(PROVIDER_NAME)//
                .credentials("clientid", "apikey")//
                .endpoint(url)//
                .modules(modules)//
                .overrides(setupOverides())//
                .buildApi(DockerApi.class);
    }

    private Properties setupOverides() {
        Properties props = new Properties();
        props.setProperty(Constants.PROPERTY_TRUST_ALL_CERTS, "true"); // accepts all HTTPS certs
        props.setProperty(Constants.PROPERTY_RELAX_HOSTNAME, "true"); // don't validate host name
        props.setProperty(Constants.PROPERTY_MAX_RETRIES, "15"); // how times to retry a command
        return props;
    }

    public Optional<Image> buildImagePortable(File dockerFile, BuildOptions options) {
        InputStream is = null;
        try {
            is = remoteApi().build(dockerFile, options);
            String msg = readStream("while building image from Dockerfile @ [" + dockerFile + "]", is);
            logger.info(msg);
            String tag = getFirstValue(options.buildQueryParameters(), "tag").or("N/A");
            return inspectImage(tag);
        } catch (Exception ex) {
            Closeables.closeQuietly(is);
        }
        return Optional.absent();
    }

    public Optional<Image> buildImage(File buildContextDir, BuildOptions options) {
        //TODO: we need to build a tar payload from the Docker build context
        return null;
    }

    /**
     * Inspect an image residing on the Docker host
     * 
     * @param imageIdentity
     *            the identity of image. This can be id("f0fe5d88bf998abaf4bd61a8d9ba0d637cc9a5dc33183c9de783e6782aa68e36") or tag. ex. "ubuntu:precise","jamtur01/nginx:latest"
     * @return Optional<Image>
     */
    public Optional<Image> inspectImage(String imageIdentity) {
        try {
            return Optional.fromNullable(remoteApi().inspectImage(imageIdentity));
        } catch (Exception ex) {
            logger.info(ex.getMessage());
        }
        return Optional.absent();
    }

    /**
     * Check if an image exists
     * 
     * @param imageIdentity
     *            the identity of image. This can be id("f0fe5d88bf998abaf4bd61a8d9ba0d637cc9a5dc33183c9de783e6782aa68e36") or tag. ex. "ubuntu:precise","jamtur01/nginx:latest"
     * @return true if exists;false otherwise
     */
    public boolean isImageExist(String imageIdentity) {
        return inspectImage(imageIdentity).isPresent();
    }

    /**
     * Search for an image cached on Docker host
     * 
     * @param id
     *            id of the image. ex. "822a01ae9a156790e516b034972c54f038bddf55437f562060420716dafe72fa"
     * @return an Image
     */
    public Optional<Image> searchImageById(String id) {
        for (Image img : listAllImages()) {
            if (id.equals(img.getId())) {
                return Optional.of(img);
            }
        }
        return Optional.absent();
    }

    /**
     * Search for an image cached on the Docker host
     * 
     * @param tag
     *            repo tag. ex "ubuntu:precise","jamtur01/nginx:latest"
     * @return an Image
     */
    public Optional<Image> searchImageByTag(String tag) {
        for (Image img : listAllImages()) {
            for (String t : img.getRepoTags()) {
                if (tag.equals(t)) {
                    return Optional.of(img);
                }
            }
        }
        return Optional.absent();
    }

    /**
     * List all images cached on the Docker host
     * 
     * @return a set of Images
     */
    public Set<Image> listAllImages() {
        Set<Image> images = remoteApi().listImages();
        return images;
    }

    /**
     * Delete an image using its tag
     * 
     * @param imageTag
     *            tag of an image. ex "ubuntu:precise","jamtur01/nginx:latest"
     * @return true if delete successful;false otherwise
     */
    public boolean deleteImageByTag(String imageTag) {
        Optional<Image> img = searchImageByTag(imageTag);
        if (img.isPresent()) {
            List<String> tags = img.get().getRepoTags();
            String imageId = img.get().getId();
            // need to delete all repo tags associated with an image before it can be deleted totally; the last one will remove the image totally.
            for (String t : tags) {
                InputStream is = remoteApi().deleteImage(t);
                String msg = readStream("while deleting image [" + imageId + "] tag by [" + t + "]", is);
                logger.info(msg);
            }
        }
        // validate deletion is successful
        img = searchImageByTag(imageTag);
        return !img.isPresent();
    }

    /**
     * Delete an image using its id
     * 
     * @param imageId
     *            id of image. ex. "822a01ae9a156790e516b034972c54f038bddf55437f562060420716dafe72fa"
     * @return true if delete is successful; false otherwise
     */
    public boolean deleteImageById(String imageId) {
        Optional<Image> img = searchImageById(imageId);
        if (img.isPresent()) {
            List<String> tags = img.get().getRepoTags();
            // need to delete all repo tags associated with an image before it can be deleted totally; the last one will remove the image totally.
            for (String t : tags) {
                InputStream is = remoteApi().deleteImage(t);
                String msg = readStream("while deleting image [" + imageId + "] tag by [" + t + "]", is);
                logger.info(msg);
            }
        }

        // validate deletion is successful
        img = searchImageById(imageId);
        return !img.isPresent();
    }

    /**
     * Create a container on the Docker host. Note: A created container is not running!
     * 
     * @param containerName
     *            the name to give the container. ex. "my-container1".
     * @param config
     *            the configuration of container to create
     * @return a Optional<Container> object
     */
    public Optional<Container> createContainer(String containerName, Config config) {
        try {
            Container c = remoteApi().createContainer(containerName, config);
            return Optional.of(c);
        } catch (Exception ex) {
            logger.severe("Docker container [" + containerName + "] failed :" + ex.getMessage());
        }
        return Optional.absent();
    }

    /**
     * Start a created container on the Docker host.
     * 
     * @param containerIdentity
     *            the identity of the container ex. id("be7dcbf09d17c6605fdde7d66846dd98e3ba06579d4c9ff1cf8fa682be90db2e") or name("my_container1")
     * @param hostConfig
     *            the host binding parameters when container is started
     * @return true if container is starting or already running. User must poll to see if container reach state "running==true".
     */
    public boolean startContainer(String containerIdentity, HostConfig hostConfig) {
        if (isContainerRunning(containerIdentity)) {
            return true;
        }
        try {
            if (hostConfig == null) {
                remoteApi().startContainer(containerIdentity);
            } else {
                remoteApi().startContainer(containerIdentity, hostConfig);
            }
        } catch (Exception ex) {
            logger.severe("while starting  Docker container [" + containerIdentity + "] :" + ex.getMessage());
        }
        return true;
    }

    /**
     * Stop a running container on the Docker host.
     * 
     * @param containerIdentity
     *            the identity of the container ex. id("be7dcbf09d17c6605fdde7d66846dd98e3ba06579d4c9ff1cf8fa682be90db2e") or name("my_container1")
     * @return true if container is stopping or already stopped. User must poll to see if container reach state "running==false"
     */
    public boolean stopContainer(String containerIdentity) {
        if (isContainerStopped(containerIdentity)) {
            return true;
        }
        try {
            remoteApi().stopContainer(containerIdentity);
            Optional<Container> c = inspectContainer(containerIdentity);
            if (c.isPresent()) {
                ContainerPredicates.awaitStopped(remoteApi()).apply(c.get());
            }
        } catch (Exception ex) {
            logger.severe("while stopping  Docker container [" + containerIdentity + "] :" + ex.getMessage());
        }
        return true;
    }

    /**
     * Remove a container from the Docker host
     * 
     * @param containerIdentity
     *            the identity of the container ex. id("be7dcbf09d17c6605fdde7d66846dd98e3ba06579d4c9ff1cf8fa682be90db2e" or name("my_container1")
     * @param options
     *            removal options
     * @return true if container is being removed or already removed
     */
    public boolean removeContainer(String containerIdentity, RemoveContainerOptions options) {
        if (!isContainerExist(containerIdentity)) {
            return true;
        }
        if (stopContainer(containerIdentity)) {
            try {
                if (options == null) {
                    remoteApi().removeContainer(containerIdentity);
                } else {
                    remoteApi().removeContainer(containerIdentity, options);
                }
            } catch (Exception ex) {
                logger.severe("while removing  Docker container [" + containerIdentity + "] :" + ex.getMessage());
            }
        }
        return true;
    }

    /**
     * Search a container by id on the Docker host
     * 
     * @param containerIdentity
     *            identity of a container. ex. id("be7dcbf09d17c6605fdde7d66846dd98e3ba06579d4c9ff1cf8fa682be90db2e", name("my_container1")
     * 
     * @param runningOnly
     *            true if only running containers should be checked;false otherwise
     * @return a Container
     */
    public Optional<Container> searchContainer(String containerIdentity, boolean runningOnly) {
        Optional<Container> c = inspectContainer(containerIdentity);
        if (!c.isPresent()) {
            return Optional.absent();
        }
        if (runningOnly) {
            if (c.get().getState().isRunning()) {
                return c;
            } else {
                return Optional.absent();
            }
        } else {
            return c;
        }
    }

    /**
     * List only running containers on the Docker host
     * 
     * @return a set of running containers
     */
    public Set<Container> listRunningContainers() {
        Set<Container> conts = remoteApi().listContainers();
        return conts;
    }

    /**
     * List all containers on the Docker host
     * 
     * @return
     */
    public Set<Container> listAllContainers() {
        Set<Container> conts = remoteApi().listContainers(ListContainerOptions.Builder.all(true));
        return conts;
    }

    /**
     * Inspect a container
     * 
     * @param containerIdentity
     *            the identity of container to inspect. This can be id("3f35f4a383b8c3b848ec775f91d7ac6e69c1f6dd234624768f98af25b375ee7a") or name("my_container1")
     * @return Optional<Container>
     */
    public Optional<Container> inspectContainer(String containerIdentity) {
        try {
            return Optional.fromNullable(remoteApi().inspectContainer(containerIdentity));
        } catch (Exception ex) {
            logger.info(ex.getMessage());
        }
        return Optional.absent();
    }

    /**
     * Check if a container exist
     * 
     * @param containerIdentity
     *            the identity of container to inspect. This can be id("3f35f4a383b8c3b848ec775f91d7ac6e69c1f6dd234624768f98af25b375ee7a") or name("my_container1")
     * @return true if exists; false otherwise
     */
    public boolean isContainerExist(String containerIdentity) {
        return inspectContainer(containerIdentity).isPresent();
    }

    public boolean isContainerRunning(String containerIdentity) {
        Optional<Container> c = inspectContainer(containerIdentity);
        if (!c.isPresent()) {
            return false;
        }
        return c.get().getState().isRunning();
    }

    public boolean isContainerStopped(String containerIdentity) {
        return !isContainerRunning(containerIdentity);
    }

    @Override
    public void close() {
        try {
            Closeables.close(dockerApi, true);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    private String readStream(String context, InputStream is) {
        String result = "";
        try {
            result = CharStreams.toString(new InputStreamReader(is, Charsets.UTF_8));
        } catch (IOException ex) {
            throw new RuntimeException(context, ex);
        } finally {
            Closeables.closeQuietly(is);
        }
        return result;
    }

    private Optional<String> getFirstValue(Multimap<String, String> map, String key) {
        Collection<String> values = map.get(key);
        return Optional.fromNullable((values != null && values.size() >= 1) ? values.iterator().next() : null);
    }

}