/******************************************************************************* * Copyright (c) 2014, 2017 Red Hat Inc. and others. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * Red Hat - Initial Contribution *******************************************************************************/ package org.eclipse.linuxtools.internal.docker.core; import java.io.Closeable; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.net.InetAddress; import java.net.NetworkInterface; import java.net.SocketException; import java.net.UnknownHostException; import java.nio.ByteBuffer; import java.nio.channels.WritableByteChannel; import java.nio.file.FileSystems; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.Comparator; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.Set; import javax.ws.rs.ProcessingException; import org.eclipse.core.runtime.IPath; import org.eclipse.core.runtime.IProgressMonitor; import org.eclipse.core.runtime.IStatus; import org.eclipse.core.runtime.ListenerList; import org.eclipse.core.runtime.Path; import org.eclipse.core.runtime.Status; import org.eclipse.core.runtime.jobs.Job; import org.eclipse.core.runtime.preferences.IEclipsePreferences; import org.eclipse.core.runtime.preferences.InstanceScope; import org.eclipse.equinox.security.storage.EncodingUtils; import org.eclipse.equinox.security.storage.ISecurePreferences; import org.eclipse.equinox.security.storage.SecurePreferencesFactory; import org.eclipse.equinox.security.storage.StorageException; import org.eclipse.linuxtools.docker.core.Activator; import org.eclipse.linuxtools.docker.core.DockerConnectionManager; import org.eclipse.linuxtools.docker.core.DockerContainerNotFoundException; import org.eclipse.linuxtools.docker.core.DockerException; import org.eclipse.linuxtools.docker.core.DockerOpenConnectionException; import org.eclipse.linuxtools.docker.core.DockerPingConnectionException; import org.eclipse.linuxtools.docker.core.EnumDockerConnectionState; import org.eclipse.linuxtools.docker.core.EnumDockerLoggingStatus; import org.eclipse.linuxtools.docker.core.IDockerConfParameter; import org.eclipse.linuxtools.docker.core.IDockerConnection; import org.eclipse.linuxtools.docker.core.IDockerConnection2; import org.eclipse.linuxtools.docker.core.IDockerConnectionInfo; import org.eclipse.linuxtools.docker.core.IDockerConnectionSettings; import org.eclipse.linuxtools.docker.core.IDockerConnectionSettings.BindingType; import org.eclipse.linuxtools.docker.core.IDockerContainer; import org.eclipse.linuxtools.docker.core.IDockerContainerConfig; import org.eclipse.linuxtools.docker.core.IDockerContainerExit; import org.eclipse.linuxtools.docker.core.IDockerContainerInfo; import org.eclipse.linuxtools.docker.core.IDockerContainerListener; import org.eclipse.linuxtools.docker.core.IDockerHostConfig; import org.eclipse.linuxtools.docker.core.IDockerImage; import org.eclipse.linuxtools.docker.core.IDockerImageBuildOptions; import org.eclipse.linuxtools.docker.core.IDockerImageHierarchyNode; import org.eclipse.linuxtools.docker.core.IDockerImageInfo; import org.eclipse.linuxtools.docker.core.IDockerImageListener; import org.eclipse.linuxtools.docker.core.IDockerImageSearchResult; import org.eclipse.linuxtools.docker.core.IDockerIpamConfig; import org.eclipse.linuxtools.docker.core.IDockerNetwork; import org.eclipse.linuxtools.docker.core.IDockerNetworkConfig; import org.eclipse.linuxtools.docker.core.IDockerNetworkCreation; import org.eclipse.linuxtools.docker.core.IDockerPortBinding; import org.eclipse.linuxtools.docker.core.IDockerProgressHandler; import org.eclipse.linuxtools.docker.core.IDockerVersion; import org.eclipse.linuxtools.docker.core.ILogger; import org.eclipse.linuxtools.docker.core.IRegistryAccount; import org.eclipse.linuxtools.docker.core.Messages; import org.eclipse.linuxtools.internal.docker.core.DockerImage.DockerImageQualifier; import org.eclipse.osgi.util.NLS; import org.eclipse.tm.terminal.view.core.TerminalServiceFactory; import org.eclipse.tm.terminal.view.core.interfaces.ITerminalService; import org.eclipse.tm.terminal.view.core.interfaces.constants.ITerminalsConnectorConstants; import com.spotify.docker.client.DockerClient; import com.spotify.docker.client.DockerClient.AttachParameter; import com.spotify.docker.client.DockerClient.BuildParam; import com.spotify.docker.client.DockerClient.ExecCreateParam; import com.spotify.docker.client.DockerClient.LogsParam; import com.spotify.docker.client.LogStream; import com.spotify.docker.client.exceptions.ContainerNotFoundException; import com.spotify.docker.client.exceptions.DockerCertificateException; import com.spotify.docker.client.exceptions.DockerTimeoutException; import com.spotify.docker.client.messages.AuthConfig; import com.spotify.docker.client.messages.Container; import com.spotify.docker.client.messages.ContainerConfig; import com.spotify.docker.client.messages.ContainerCreation; import com.spotify.docker.client.messages.ContainerExit; import com.spotify.docker.client.messages.ContainerInfo; import com.spotify.docker.client.messages.ExecCreation; import com.spotify.docker.client.messages.HostConfig; import com.spotify.docker.client.messages.HostConfig.LxcConfParameter; import com.spotify.docker.client.messages.Image; import com.spotify.docker.client.messages.ImageInfo; import com.spotify.docker.client.messages.ImageSearchResult; import com.spotify.docker.client.messages.Info; import com.spotify.docker.client.messages.Ipam; import com.spotify.docker.client.messages.Network; import com.spotify.docker.client.messages.NetworkConfig; import com.spotify.docker.client.messages.PortBinding; import com.spotify.docker.client.messages.Version; /** * A connection to a Docker daemon. The connection may rely on Unix Socket or TCP connection (using the REST API). * All low-level communication is delegated to a wrapped {@link DockerClient}. * * */ public class DockerConnection implements IDockerConnection, IDockerConnection2, Closeable { // Builder allowing different binding modes (unix socket vs TCP connection) public static class Builder { private String name; public Builder name(final String name) { this.name = name; return this; } /** * Creates a new {@link DockerConnection} using a Unix socket. * * @param unixSocketConnectionSettings * the connection settings. * @return a new {@link DockerConnection} */ public DockerConnection unixSocketConnection( final UnixSocketConnectionSettings unixSocketConnectionSettings) { return new DockerConnection(name, unixSocketConnectionSettings, null, null); } /** * Creates a {@link DockerConnection} using a TCP connection. * * @param tcpConnectionSettings * the {@link TCPConnectionSettings} * @return a new {@link DockerConnection} */ public DockerConnection tcpConnection( final TCPConnectionSettings tcpConnectionSettings) { return new DockerConnection(name, tcpConnectionSettings, null, null); } } private String name; private IDockerConnectionSettings connectionSettings; @SuppressWarnings("unused") private IDockerConnectionInfo connectionInfo; private final String username; private final Object imageLock = new Object(); private final Object containerLock = new Object(); private final Object actionLock = new Object(); private final Object clientLock = new Object(); private DockerClientFactory dockerClientFactory = new DockerClientFactory(); private DockerClient client; private Map<String, Job> actionJobs; private Map<String, LogThread> loggingThreads = new HashMap<>(); // containers sorted by name private List<IDockerContainer> containers; // containers indexed by id private Map<String, IDockerContainer> containersById = new HashMap<>(); // flag to indicate if the state of the connection to the Docker daemon private EnumDockerConnectionState state = EnumDockerConnectionState.UNKNOWN; private List<IDockerImage> images; private Boolean isLocalConnection; ListenerList<IDockerContainerListener> containerListeners; ListenerList<IDockerImageListener> imageListeners; /** * Constructor for a Unix socket based connection */ private DockerConnection(final String name, final UnixSocketConnectionSettings connectionSettings, final String username, final String password) { this.name = name; this.connectionSettings = connectionSettings; this.username = username; storePassword(connectionSettings.getPath(), username, password); } /** * Constructor for a TCP-based connection */ private DockerConnection(final String name, final TCPConnectionSettings connectionSettings, final String username, final String password) { this.name = name; this.connectionSettings = connectionSettings; this.username = username; storePassword(connectionSettings.getHost(), username, password); // Add the container refresh manager to watch the containers list DockerContainerRefreshManager dcrm = DockerContainerRefreshManager .getInstance(); addContainerListener(dcrm); } private void storePassword(String uri, String username, String passwd) { ISecurePreferences root = SecurePreferencesFactory.getDefault(); String key = DockerConnection.getPreferencesKey(uri, username); ISecurePreferences node = root.node(key); try { if (passwd != null && !passwd.equals("")) //$NON-NLS-1$ node.put("password", passwd, true /* encrypt */); //$NON-NLS-1$ } catch (StorageException e) { Activator.log(e); } } public static String getPreferencesKey(String uri, String username) { String key = "/org/eclipse/linuxtools/docker/core/"; //$NON-NLS-1$ key += uri + "/" + username; //$NON-NLS-1$ return EncodingUtils.encodeSlashes(key); } @Override public boolean isOpen() { return this.client != null && this.state == EnumDockerConnectionState.ESTABLISHED; } @Override public void open(boolean registerContainerRefreshManager) throws DockerException { // synchronized block to avoid concurrent attempts to open a connection // to the same Docker daemon synchronized (this) { if (this.client == null) { try { setClient(dockerClientFactory .getClient(this.connectionSettings)); if (registerContainerRefreshManager) { // Add the container refresh manager to watch the // containers // list DockerContainerRefreshManager dcrm = DockerContainerRefreshManager .getInstance(); addContainerListener(dcrm); } } catch (DockerCertificateException e) { setState(EnumDockerConnectionState.CLOSED); throw new DockerOpenConnectionException( NLS .bind(Messages.Open_Connection_Failure, this.name, this.getUri()), e); } } // then try to ping the Docker daemon to verify the connection ping(); } } public DockerClient getClient() { return client; } public void setClient(final DockerClient client) { this.client = client; try { this.connectionInfo = getInfo(); } catch (Exception e) { // ignore for now as this seems to occur too often and we always // check the value of connectionInfo before using } } /** * Change the default {@link DockerClientFactory} * * @param dockerClientFactory * the new {@link DockerClientFactory} to use when opening a * connection. */ public void setDockerClientFactory( final DockerClientFactory dockerClientFactory) { this.dockerClientFactory = dockerClientFactory; } @Override public EnumDockerConnectionState getState() { return this.state; } public void setState(final EnumDockerConnectionState state) { this.state = state; switch (state) { case UNKNOWN: case CLOSED: this.images = Collections.emptyList(); this.containers = Collections.emptyList(); this.containersById = new HashMap<>(); notifyContainerListeners(this.containers); notifyImageListeners(this.images); break; case ESTABLISHED: this.getContainers(true); this.getImages(true); notifyContainerListeners(this.containers); notifyImageListeners(this.images); break; } } @Override public void ping() throws DockerException { try { if (this.client != null) { this.client.ping(); } else { throw new DockerPingConnectionException(NLS.bind( Messages.Docker_Daemon_Ping_Failure, this.getName())); } setState(EnumDockerConnectionState.ESTABLISHED); } catch (com.spotify.docker.client.exceptions.DockerException | InterruptedException e) { setState(EnumDockerConnectionState.CLOSED); throw new DockerPingConnectionException(NLS.bind( Messages.Docker_Daemon_Ping_Failure, this.getName()), e); } } @Override protected void finalize() throws Throwable { close(); super.finalize(); } @Override public void close() { // stop and remove all logging threads for (String key : loggingThreads.keySet()) { LogThread t = loggingThreads.get(key); if (t != null) { t.kill(); } loggingThreads.remove(key); } synchronized (clientLock) { if (this.client != null) { this.client.close(); this.client = null; } setState(EnumDockerConnectionState.CLOSED); } } @Override public IDockerConnectionInfo getInfo() throws DockerException { if (this.client == null) { return null; } try { final Info info = this.client.info(); final Version version = this.client.version(); return new DockerConnectionInfo(info, version); } catch (com.spotify.docker.client.exceptions.DockerRequestException e) { throw new DockerException(e.message()); } catch (com.spotify.docker.client.exceptions.DockerException | InterruptedException e) { throw new DockerException(Messages.Docker_General_Info_Failure, e); } } @Override public String getName() { if (name != null && !name.isEmpty()) { return name; } return Messages.Unnamed; } @Override public boolean setName(final String name) { if (!this.name.equals(name)) { this.name = name; return true; } return false; } @Override public String getUri() { if (this.connectionSettings .getType() == BindingType.UNIX_SOCKET_CONNECTION) { return ((UnixSocketConnectionSettings) this.connectionSettings) .getPath(); } else { return ((TCPConnectionSettings) this.connectionSettings).getHost(); } } @Override public IDockerConnectionSettings getSettings() { return this.connectionSettings; } @Override public boolean setSettings( final IDockerConnectionSettings connectionSettings) { if (!this.connectionSettings.equals(connectionSettings)) { // make sure no other operation using the underneath client occurs // while switching the connection settings. synchronized (clientLock) { this.connectionSettings = connectionSettings; if (this.client != null) { this.client.close(); } this.state = EnumDockerConnectionState.UNKNOWN; this.client = null; new Job(NLS.bind(Messages.Open_Connection, this.getUri())) { @Override protected IStatus run(IProgressMonitor monitor) { try { open(true); ping(); } catch (DockerException e) { Activator.logErrorMessage( NLS.bind( Messages.Docker_Daemon_Ping_Failure, this.getName()), e); return Status.CANCEL_STATUS; } return Status.OK_STATUS; } }; } // getContainers(true); // getImages(true); return true; } return false; } @Override public String getUsername() { return username; } @Override public IDockerVersion getVersion() throws DockerException { try { Version version = client.version(); return new DockerVersion(this, version); } catch (com.spotify.docker.client.exceptions.DockerException | InterruptedException e) { throw new DockerException(Messages.Docker_General_Info_Failure, e); } } @Override public void addContainerListener(IDockerContainerListener listener) { if (containerListeners == null) containerListeners = new ListenerList<>(ListenerList.IDENTITY); containerListeners.add(listener); } @Override public void removeContainerListener(IDockerContainerListener listener) { if (containerListeners != null) { containerListeners.remove(listener); } } /** * Get a copy of the client to use in parallel threads for long-standing * operations such as logging or waiting until finished. The user of the * copy should close it when the operation is complete. * * @return copy of client * @throws DockerException * @throws DockerCertificateException * @see DockerConnection#open(boolean) */ private DockerClient getClientCopy() throws DockerException { try { return dockerClientFactory.getClient(this.connectionSettings); } catch (DockerCertificateException e) { throw new DockerException(NLS.bind(Messages.Open_Connection_Failure, this.name, this.getUri())); } } // TODO: we might need something more fine grained, to indicate which // container changed, was added or was removed, so we can refresh the UI // accordingly. public void notifyContainerListeners(List<IDockerContainer> list) { if (containerListeners != null) { for (IDockerContainerListener listener : containerListeners) { listener.listChanged(this, list); } } } /** * @return an fixed-size list of all {@link IDockerContainerListener} */ // TODO: include in IDockerConnection API public List<IDockerContainerListener> getContainerListeners() { if (this.containerListeners == null) { return Collections.emptyList(); } final IDockerContainerListener[] result = new IDockerContainerListener[this.containerListeners .size()]; final Object[] listeners = containerListeners.getListeners(); for (int i = 0; i < listeners.length; i++) { result[i] = (IDockerContainerListener) listeners[i]; } return Arrays.asList(result); } public Job getActionJob(String id) { synchronized (actionLock) { Job j = null; if (actionJobs != null) { return actionJobs.get(id); } return j; } } public void registerActionJob(String id, Job j) { synchronized (actionLock) { if (actionJobs == null) actionJobs = new HashMap<>(); actionJobs.put(id, j); } } public void removeActionJob(String id, Job j) { synchronized (actionLock) { if (actionJobs != null && actionJobs.get(id) == j) actionJobs.remove(id); } } @Override public List<IDockerContainer> getContainers() { return getContainers(false); } @Override public List<IDockerContainer> getContainers(final boolean force) { if (this.state == EnumDockerConnectionState.CLOSED) { return Collections.emptyList(); } else if (this.state == EnumDockerConnectionState.UNKNOWN) { try { open(true); getContainers(force); } catch (DockerException e) { Activator.log(e); } } else if (!isContainersLoaded() || force) { try { return listContainers(); } catch (DockerException e) { Activator.log(e); } } return this.containers; } @Override public boolean isContainersLoaded() { return this.containers != null; } /** * Class to perform logging of a container run to a given output stream * (usually a console stream). * */ private class LogThread extends AbstractKillableThread implements ILogger { private String id; private DockerClient copyClient; private OutputStream outputStream; private boolean follow; public LogThread(String id, DockerClient copyClient, boolean follow) { this.id = id; this.copyClient = copyClient; this.follow = follow; } @Override public LogThread clone() { return new LogThread(id, copyClient, follow); } @Override public void setOutputStream(OutputStream stream) { outputStream = stream; } @Override public void execute() throws InterruptedException, IOException { LogStream stream = null; try { // Add timestamps to log based on user preference IEclipsePreferences preferences = InstanceScope.INSTANCE .getNode("org.eclipse.linuxtools.docker.ui"); //$NON-NLS-1$ boolean timestamps = preferences.getBoolean( "logTimestamp", true); //$NON-NLS-1$ if (timestamps) stream = copyClient.logs(id, LogsParam.follow(), LogsParam.stdout(), LogsParam.stderr(), LogsParam.timestamps()); else stream = copyClient.logs(id, LogsParam.follow(), LogsParam.stdout(), LogsParam.stderr()); // First time through, don't sleep before showing log data int delayTime = 100; do { Thread.sleep(delayTime); // Second time in loop and following, pause a second to // allow other threads to do meaningful work delayTime = 500; while (stream.hasNext()) { ByteBuffer b = stream.next().content(); byte[] bytes = new byte[b.remaining()]; b.get(bytes); if (outputStream != null) outputStream.write(bytes); } } while (follow && !stop); listContainers(); } catch (com.spotify.docker.client.exceptions.DockerRequestException e) { Activator.logErrorMessage( ProcessMessages.getString("Monitor_Logs_Exception"), e); //$NON-NLS-1$ throw new InterruptedException(); } catch (com.spotify.docker.client.exceptions.DockerException | IOException e) { Activator.logErrorMessage( ProcessMessages.getString("Monitor_Logs_Exception"), e); //$NON-NLS-1$ throw new InterruptedException(); } catch (InterruptedException e) { kill = true; Thread.currentThread().interrupt(); } catch (Exception e) { Activator.logErrorMessage( ProcessMessages.getString("Monitor_Logs_Exception"), e); //$NON-NLS-1$ } finally { follow = false; copyClient.close(); // we are done with copyClient..dispose if (stream != null) stream.close(); if (outputStream != null) outputStream.close(); } } } private List<IDockerContainer> listContainers() throws DockerException { final Map<String, IDockerContainer> updatedContainersById = new HashMap<>(); List<IDockerContainer> sortedContainers; synchronized (containerLock) { try { final List<Container> nativeContainers = new ArrayList<>(); synchronized (clientLock) { // Check that client is not null as this connection may have // been closed but there is an async request to update the // containers list left in the queue if (client == null) { // in that case the list becomes empty, which is fine is // there's no client. return Collections.emptyList(); } nativeContainers.addAll(client.listContainers( DockerClient.ListContainersParam.allContainers())); } // We have a list of containers. Now, we translate them to our // own // core format in case we decide to change the underlying engine // in the future. for (Container nativeContainer : nativeContainers) { // For containers that have exited, make sure we aren't // tracking // them with a logging thread. if (nativeContainer.status() != null && nativeContainer .status().startsWith(Messages.Exited_specifier)) { synchronized (loggingThreads) { if (loggingThreads .containsKey(nativeContainer.id())) { loggingThreads.get(nativeContainer.id()) .requestStop(); loggingThreads.remove(nativeContainer.id()); } } } // skip containers that are being removed if (nativeContainer.status() != null && nativeContainer.status().equals( Messages.Removal_In_Progress_specifier)) { continue; } // re-use info from existing container with same id if (this.containers != null && this.containersById .containsKey(nativeContainer.id())) { final IDockerContainer container = this.containersById .get(nativeContainer.id()); updatedContainersById.put(nativeContainer.id(), new DockerContainer(this, nativeContainer, container.info())); } else { updatedContainersById.put(nativeContainer.id(), new DockerContainer(this, nativeContainer)); } } } catch (DockerTimeoutException e) { if (isOpen()) { Activator.log( new Status(IStatus.WARNING, Activator.PLUGIN_ID, Messages.Docker_Connection_Timeout, e)); close(); } } catch (com.spotify.docker.client.exceptions.DockerException | InterruptedException e) { if (isOpen() && e.getCause() != null && e.getCause().getCause() != null && e.getCause() .getCause() instanceof ProcessingException) { close(); } else { throw new DockerException(e.getMessage()); } } finally { this.containersById = updatedContainersById; sortedContainers = sort( updatedContainersById.values(), (container, otherContainer) -> container.name() .compareTo(otherContainer.name())); this.containers = sortedContainers; } } // perform notification outside of containerLock so we don't have a View // causing a deadlock // TODO: we should probably notify the listeners only if the containers // list changed. notifyContainerListeners(sortedContainers); return sortedContainers; } public Set<String> getContainerIdsWithLabels(Map<String, String> labels) throws DockerException { Set<String> labelSet = new HashSet<>(); try { final List<Container> nativeContainers = new ArrayList<>(); synchronized (clientLock) { // Check that client is not null as this connection may have // been closed but there is an async request to filter the // containers list left in the queue if (client == null) { // in that case the list becomes empty, which is fine is // there's no client. return Collections.emptySet(); } DockerClient clientCopy = getClientCopy(); DockerClient.ListContainersParam[] parms = new DockerClient.ListContainersParam[2]; parms[0] = DockerClient.ListContainersParam.allContainers(); // DockerClient doesn't support multiple labels with its // ListContainersParam so we have // to do a kludge and put in control chars ourselves and pretend // we have a label with no value. String separator = ""; //$NON-NLS-1$ StringBuffer labelString = new StringBuffer(); for (Entry<String, String> entry : labels.entrySet()) { labelString.append(separator); if (entry.getValue() == null || "".equals(entry.getValue())) //$NON-NLS-1$ labelString.append(entry.getKey()); else { labelString.append( entry.getKey() + "=" + entry.getValue()); //$NON-NLS-1$ } separator = "\",\""; //$NON-NLS-1$ } parms[1] = DockerClient.ListContainersParam .withLabel(labelString.toString()); nativeContainers.addAll(clientCopy.listContainers(parms)); } // We have a list of containers with labels. Now, we create a Set of // ids which contain those labels to use in filtering a list of // Containers for (Container nativeContainer : nativeContainers) { labelSet.add(nativeContainer.id()); } } catch (DockerTimeoutException e) { if (isOpen()) { Activator.log(new Status(IStatus.WARNING, Activator.PLUGIN_ID, Messages.Docker_Connection_Timeout, e)); close(); } } catch (com.spotify.docker.client.exceptions.DockerException | InterruptedException e) { if (isOpen() && e.getCause() != null && e.getCause().getCause() != null && e.getCause().getCause() instanceof ProcessingException) { close(); } else { throw new DockerException(e.getMessage()); } } return labelSet; } /** * Sorts the given values using the given comparator and returns the result * in a {@link List} * * @param values * the values to sort * @param comparator * the comparator to use * @return the list of sorted values */ private <T> List<T> sort(final Collection<T> values, final Comparator<T> comparator) { final List<T> result = new ArrayList<>(values); Collections.sort(result, comparator); return result; } @Override public IDockerContainer getContainer(String id) { List<IDockerContainer> containers = getContainers(); for (IDockerContainer container : containers) { if (container.id().equals(id)) { return container; } } return null; } @Override public IDockerContainerInfo getContainerInfo(final String id) { try { final ContainerInfo info = client.inspectContainer(id); return new DockerContainerInfo(info); } catch (com.spotify.docker.client.exceptions.DockerRequestException e) { Activator.logErrorMessage( ProcessMessages.getString("Container_Info_Exception"), e); //$NON-NLS-1$ return null; } catch (com.spotify.docker.client.exceptions.DockerException | InterruptedException e) { Activator.log(new Status(IStatus.ERROR, Activator.PLUGIN_ID, ProcessMessages.getFormattedString( "Container_Inspect_Exception", id), //$NON-NLS-1$ e)); return null; } } @Override public IDockerImageInfo getImageInfo(String id) { if (this.client == null) { return null; } try { final ImageInfo info = this.client.inspectImage(id); return new DockerImageInfo(info); } catch (com.spotify.docker.client.exceptions.DockerRequestException e) { Activator.logErrorMessage( ProcessMessages.getString("Image_Info_Exception"), e); //$NON-NLS-1$ return null; } catch (com.spotify.docker.client.exceptions.DockerException | InterruptedException e) { Activator.log(new Status(IStatus.ERROR, Activator.PLUGIN_ID, ProcessMessages.getFormattedString( "Image_Inspect_Exception", id), //$NON-NLS-1$ e)); return null; } } @Override public void addImageListener(IDockerImageListener listener) { if (imageListeners == null) imageListeners = new ListenerList<>(ListenerList.IDENTITY); imageListeners.add(listener); } @Override public void removeImageListener(IDockerImageListener listener) { if (imageListeners != null) { imageListeners.remove(listener); } } public void notifyImageListeners(List<IDockerImage> list) { if (imageListeners != null) { for (IDockerImageListener listener : imageListeners) { listener.listChanged(this, list); } } } /** * @return an fixed-size list of all {@link IDockerImageListener} */ // TODO: include in IDockerConnection API public List<IDockerImageListener> getImageListeners() { if (this.imageListeners == null) { return Collections.emptyList(); } final IDockerImageListener[] result = new IDockerImageListener[this.imageListeners .size()]; final Object[] listeners = imageListeners.getListeners(); for (int i = 0; i < listeners.length; i++) { result[i] = (IDockerImageListener) listeners[i]; } return Arrays.asList(result); } @Override public List<IDockerImage> getImages() { return getImages(false); } /** * @return the {@link IDockerImage} identified by the given {@code id} or * <code>null</code> if none was found. * @param id * the {@link IDockerImage} id */ // TODO: declare the method in the interface to make it part of the API. public IDockerImage getImage(String id) { List<IDockerImage> images = getImages(); for (IDockerImage image : images) { if (image.id().equals(id)) { return image; } } return null; } @Override public List<IDockerImage> getImages(final boolean force) { List<IDockerImage> latestImages; synchronized (imageLock) { latestImages = this.images; } if (this.state == EnumDockerConnectionState.CLOSED) { return Collections.emptyList(); } else if (this.state == EnumDockerConnectionState.UNKNOWN) { try { open(true); latestImages = getImages(force); } catch (DockerException e) { Activator.log(e); } } else if (!isImagesLoaded() || force) { try { latestImages = listImages(); } catch (DockerException e) { synchronized (imageLock) { this.images = Collections.emptyList(); } Activator.log(e); } } return latestImages; } @Override public boolean isImagesLoaded() { return this.images != null; } // TODO: remove this method from the API @Override public List<IDockerImage> listImages() throws DockerException { final List<IDockerImage> tempImages = new ArrayList<>(); synchronized (imageLock) { try { final List<Image> nativeImages = new ArrayList<>(); synchronized (clientLock) { // Check that client is not null as this connection may have // been closed but there is an async request to update the // containers list left in the queue if (client == null) { // in that case the list becomes empty, which is fine is // there's no client. return Collections.emptyList(); } nativeImages.addAll(client.listImages( DockerClient.ListImagesParam.allImages())); } // We have a list of images. Now, we translate them to our own // core format in case we decide to change the underlying engine // in the future. We also look for intermediate and dangling // images. for (Image nativeImage : nativeImages) { final DockerImageQualifier imageQualifier = resolveQualifier( nativeImage, nativeImages); // return one IDockerImage per raw image final List<String> repoTags = (nativeImage .repoTags() != null) ? new ArrayList<>(nativeImage.repoTags()) : new ArrayList<>(); Collections.sort(repoTags); if (repoTags.isEmpty()) { repoTags.add("<none>:<none>"); //$NON-NLS-1$ } final String repo = DockerImage .extractRepo(repoTags.get(0)); final List<String> tags = Arrays .asList(DockerImage.extractTag(repoTags.get(0))); tempImages.add(new DockerImage(this, repoTags, repo, tags, nativeImage.id(), nativeImage.parentId(), nativeImage.created(), nativeImage.size(), nativeImage.virtualSize(), imageQualifier)); } } catch (com.spotify.docker.client.exceptions.DockerTimeoutException e) { if (isOpen()) { Activator.log( new Status(IStatus.WARNING, Activator.PLUGIN_ID, Messages.Docker_Connection_Timeout, e)); close(); } } catch (com.spotify.docker.client.exceptions.DockerRequestException e) { throw new DockerException(e.message()); } catch (com.spotify.docker.client.exceptions.DockerException | InterruptedException e) { if (isOpen() && e.getCause() != null && e.getCause().getCause() != null && e.getCause() .getCause() instanceof ProcessingException) { close(); } else { throw new DockerException( NLS.bind(Messages.List_Docker_Images_Failure, this.getName()), e); } } finally { this.images = tempImages; } } // Perform notification outside of lock so that listener doesn't cause a // deadlock to occur notifyImageListeners(tempImages); return tempImages; } /** * Resolves the {@link DockerImageQualifier} for the given * {@code nativeImage} in the context of all {@code nativeImages} * * @param nativeImage * the image to analyze * @param nativeImages * all known images * @return the corresponding {@link DockerImageQualifier} */ private static DockerImageQualifier resolveQualifier( final Image nativeImage, final List<Image> nativeImages) { final boolean hasTag = !(nativeImage.repoTags() == null || (nativeImage.repoTags().size() == 1 && nativeImage.repoTags().contains("<none>:<none>"))); //$NON-NLS-1$ final boolean hasChildImage = nativeImages.stream() .anyMatch(i -> nativeImage.id().equals(i.parentId())); // imtermediate image if (!hasTag && hasChildImage) { return DockerImageQualifier.INTERMEDIATE; } // dangling image if (!hasTag && !hasChildImage) { return DockerImageQualifier.DANGLING; } return DockerImageQualifier.TOP_LEVEL; } @Override public boolean hasImage(final String repository, final String tag) { for (IDockerImage image : getImages()) { for (String repoTag : image.repoTags()) { String tagExpr = (tag != null && !tag.isEmpty()) ? ":" + tag //$NON-NLS-1$ : ""; //$NON-NLS-1$ if (repoTag.equals(repository + tagExpr)) { return true; } } } return false; } public IDockerImage getImageByTag(final String tag) { for (IDockerImage image : getImages()) { for (String repoTag : image.repoTags()) { if (repoTag.equals(tag)) { return image; } } } return null; } @Override public IDockerProgressHandler getDefaultBuildImageProgressHandler( String image, int lines) { return new DefaultImageBuildProgressHandler(this, image, lines); } @Override public IDockerProgressHandler getDefaultPullImageProgressHandler( String image) { return new DefaultImagePullProgressHandler(this, image); } @Override public IDockerProgressHandler getDefaultPushImageProgressHandler( String image) { return new DefaultImagePushProgressHandler(this, image); } @Override public void pullImage(final String id, final IDockerProgressHandler handler) throws DockerException, InterruptedException { try { DockerProgressHandler d = new DockerProgressHandler(handler); client.pull(id, d); listImages(); } catch (com.spotify.docker.client.exceptions.DockerRequestException e) { throw new DockerException(e.message()); } catch (com.spotify.docker.client.exceptions.DockerException e) { DockerException f = new DockerException(e); throw f; } } @Override public void pullImage(final String imageId, final IRegistryAccount info, final IDockerProgressHandler handler) throws DockerException, InterruptedException, DockerCertificateException { try { final DockerClient client = dockerClientFactory .getClient(this.connectionSettings, info); final DockerProgressHandler d = new DockerProgressHandler(handler); client.pull(imageId, d); listImages(); } catch (com.spotify.docker.client.exceptions.DockerRequestException e) { throw new DockerException(e.message()); } catch (com.spotify.docker.client.exceptions.DockerException e) { DockerException f = new DockerException(e); throw f; } } @Override public List<IDockerImageSearchResult> searchImages(final String term) throws DockerException { try { final List<ImageSearchResult> searchResults = client.searchImages(term); final List<IDockerImageSearchResult> results = new ArrayList<>(); for(ImageSearchResult r : searchResults) { if (r.getName().contains(term)) { results.add(new DockerImageSearchResult(r.getDescription(), r.isOfficial(), r.isAutomated(), r.getName(), r.getStarCount())); } } return results; } catch (com.spotify.docker.client.exceptions.DockerException | InterruptedException e) { throw new DockerException(e); } } @Override public void pushImage(final String name, final IDockerProgressHandler handler) throws DockerException, InterruptedException { try { DockerProgressHandler d = new DockerProgressHandler(handler); client.push(name, d); } catch (com.spotify.docker.client.exceptions.DockerRequestException e) { throw new DockerException(e.message()); } catch (com.spotify.docker.client.exceptions.DockerException e) { DockerException f = new DockerException(e); throw f; } } @Override public void pushImage(final String name, final IRegistryAccount info, final IDockerProgressHandler handler) throws DockerException, InterruptedException { try { final DockerClient client = dockerClientFactory .getClient(this.connectionSettings, info); final DockerProgressHandler d = new DockerProgressHandler(handler); client.push(name, d); } catch (com.spotify.docker.client.exceptions.DockerRequestException e) { throw new DockerException(e.message()); } catch (com.spotify.docker.client.exceptions.DockerException | DockerCertificateException e) { DockerException f = new DockerException(e); throw f; } } @Override public void removeImage(final String name) throws DockerException, InterruptedException { try { client.removeImage(name, true, false); } catch (com.spotify.docker.client.exceptions.DockerRequestException e) { throw new DockerException(e.message()); } catch (com.spotify.docker.client.exceptions.DockerException e) { DockerException f = new DockerException(e); throw f; } } @Override public void removeTag(final String tag) throws DockerException, InterruptedException { try { client.removeImage(tag, false, false); } catch (com.spotify.docker.client.exceptions.DockerRequestException e) { throw new DockerException(e.message()); } catch (com.spotify.docker.client.exceptions.DockerException e) { DockerException f = new DockerException(e); throw f; } } @Override public void tagImage(final String name, final String newTag) throws DockerException, InterruptedException { tagImage(name, newTag, false); } /** * Adds a tag to an existing image while specifying the <code>force</code> * flag. * * @param name * the image id * @param newTag * the new tag to add to the given image * @param force * the {@code force} flag to force the operation if * <code>true</code>. * @throws DockerException * in case of underlying problem (server error) * @throws InterruptedException * if the thread was interrupted */ // TODO: add to the API in version 3.0.0 public void tagImage(final String name, final String newTag, final boolean force) throws DockerException, InterruptedException { try { client.tag(name, newTag, force); } catch (com.spotify.docker.client.exceptions.DockerRequestException e) { throw new DockerException(e.message()); } catch (com.spotify.docker.client.exceptions.DockerException e) { DockerException f = new DockerException(e); throw f; } } @Override public String buildImage(final IPath path, final IDockerProgressHandler handler) throws DockerException, InterruptedException { try { final DockerProgressHandler d = new DockerProgressHandler(handler); final java.nio.file.Path p = FileSystems.getDefault() .getPath(path.makeAbsolute().toOSString()); String res = getClientCopy().build(p, d, BuildParam.create("forcerm", "true")); //$NON-NLS-1$ //$NON-NLS-2$ return res; } catch (com.spotify.docker.client.exceptions.DockerRequestException e) { throw new DockerException(e.message()); } catch (com.spotify.docker.client.exceptions.DockerException | IOException e) { DockerException f = new DockerException(e); throw f; } } @Override public String buildImage(final IPath path, final String name, final IDockerProgressHandler handler) throws DockerException, InterruptedException { try { DockerProgressHandler d = new DockerProgressHandler(handler); java.nio.file.Path p = FileSystems.getDefault().getPath( path.makeAbsolute().toOSString()); String res = getClientCopy().build(p, name, d, BuildParam.create("forcerm", "true")); //$NON-NLS-1$ $NON-NLS-2$ return res; } catch (com.spotify.docker.client.exceptions.DockerRequestException e) { throw new DockerException(e.message()); } catch (com.spotify.docker.client.exceptions.DockerException | IOException e) { DockerException f = new DockerException(e); throw f; } } /** * Builds an {@link IDockerImage} * * @param path * path to the build context * @param name * optional name and tag of the image to build * @param handler * progress handler * @param buildOptions * build options * @return the id of the {@link IDockerImage} that was build * @throws DockerException * if building image failed * @throws InterruptedException * if the thread was interrupted */ // TODO: add this method in the public interface public String buildImage(final IPath path, final String name, final IDockerProgressHandler handler, final Map<String, Object> buildOptions) throws DockerException, InterruptedException { try { final DockerProgressHandler d = new DockerProgressHandler(handler); final java.nio.file.Path p = FileSystems.getDefault() .getPath(path.makeAbsolute().toOSString()); String res = getClientCopy().build(p, name, d, getBuildParameters(buildOptions)); return res; } catch (com.spotify.docker.client.exceptions.DockerRequestException e) { throw new DockerException(e.message()); } catch (com.spotify.docker.client.exceptions.DockerException | IOException e) { DockerException f = new DockerException(e); throw f; } } /** * Converts the given {@link Map} of build options into an array of * {@link BuildParameter} when the build options are set a value different * from the default value. * * @param buildOptions * the build options * @return an array of relevant {@link BuildParameter}, an empty array if * the given {@code buildOptions} is empty or <code>null</code>. */ private BuildParam[] getBuildParameters( final Map<String, Object> buildOptions) { if (buildOptions == null) { return new BuildParam[0]; } final List<BuildParam> buildParameters = new ArrayList<>(); for (Entry<String, Object> entry : buildOptions.entrySet()) { final Object optionName = entry.getKey(); final Object optionValue = entry.getValue(); if (optionName.equals(IDockerImageBuildOptions.QUIET_BUILD) && optionValue.equals(true)) { buildParameters.add(BuildParam.create("q", "true")); //$NON-NLS-1$ $NON-NLS-2$ } else if (optionName.equals(IDockerImageBuildOptions.NO_CACHE) && optionValue.equals(true)) { buildParameters.add(BuildParam.create("nocache", "true")); //$NON-NLS-1$ $NON-NLS-2$ } else if (optionName .equals(IDockerImageBuildOptions.RM_INTERMEDIATE_CONTAINERS) && optionValue.equals(false)) { buildParameters.add(BuildParam.create("rm", "false")); //$NON-NLS-1$ $NON-NLS-2$ } else if (optionName .equals(IDockerImageBuildOptions.FORCE_RM_INTERMEDIATE_CONTAINERS) && optionValue.equals(true)) { buildParameters.add(BuildParam.create("forcerm", "true")); //$NON-NLS-1$ $NON-NLS-2$ } } return buildParameters.toArray(new BuildParam[0]); } public void save() { // Currently we have to save all clouds instead of just this one DockerConnectionManager.getInstance().saveConnections(); } @Override public String createContainer(final IDockerContainerConfig c, final IDockerHostConfig hc) throws DockerException, InterruptedException { return createContainer(c, hc, null); } @Override public String createContainer(final IDockerContainerConfig c, IDockerHostConfig hc, final String containerName) throws DockerException, InterruptedException { try { HostConfig.Builder hbuilder = HostConfig.builder() .containerIDFile(hc.containerIDFile()) .publishAllPorts(hc.publishAllPorts()) .privileged(hc.privileged()).networkMode(hc.networkMode()); if (hc.binds() != null) hbuilder.binds(hc.binds()); if (hc.dns() != null) hbuilder.dns(hc.dns()); if (hc.dnsSearch() != null) hbuilder.dnsSearch(hc.dnsSearch()); if (hc.links() != null) hbuilder.links(hc.links()); if (hc.lxcConf() != null) { List<IDockerConfParameter> lxcconf = hc.lxcConf(); ArrayList<LxcConfParameter> lxcreal = new ArrayList<>(); for (IDockerConfParameter param : lxcconf) { lxcreal.add(new LxcConfParameter(param.key(), param.value())); } hbuilder.lxcConf(lxcreal); } if (hc.portBindings() != null) { Map<String, List<IDockerPortBinding>> bindings = hc .portBindings(); HashMap<String, List<PortBinding>> realBindings = new HashMap<>(); for (Entry<String, List<IDockerPortBinding>> entry : bindings .entrySet()) { String key = entry.getKey(); List<IDockerPortBinding> bindingList = entry.getValue(); ArrayList<PortBinding> newList = new ArrayList<>(); for (IDockerPortBinding binding : bindingList) { newList.add(PortBinding.of(binding.hostIp(), binding.hostPort())); } realBindings.put(key, newList); } hbuilder.portBindings(realBindings); } if (hc.volumesFrom() != null) { hbuilder.volumesFrom(hc.volumesFrom()); } if (hc.securityOpt() != null) { hbuilder.securityOpt(hc.securityOpt()); } // FIXME: add the 'memory()' method in the IDockerHostConfig // interface if (((DockerHostConfig) hc).memory() != null) { hbuilder.memory(((DockerHostConfig) hc).memory()); } // FIXME: add the 'cpuShares()' method in the IDockerHostConfig // interface if (((DockerHostConfig) hc).cpuShares() != null && ((DockerHostConfig) hc).cpuShares().longValue() > 0) { hbuilder.cpuShares(((DockerHostConfig) hc).cpuShares()); } ContainerConfig.Builder builder = ContainerConfig.builder() .hostname(c.hostname()).domainname(c.domainname()) .user(c.user()).attachStdin(c.attachStdin()) .attachStdout(c.attachStdout()) .attachStderr(c.attachStderr()).tty(c.tty()) .openStdin(c.openStdin()).stdinOnce(c.stdinOnce()) .cmd(c.cmd()).image(c.image()) .hostConfig(hbuilder.build()) .workingDir(c.workingDir()) .labels(c.labels()) .networkDisabled(c.networkDisabled()); // For those fields that are Collections and not set, they will be null. // We can't use their values to set the builder's fields as they are // expecting non-null Collections to copy over. In those cases, we just // don't set those fields in the builder. if (c.portSpecs() != null) { builder = builder.portSpecs(c.portSpecs()); } if (c.exposedPorts() != null) { builder = builder.exposedPorts(c.exposedPorts()); } if (c.env() != null) { builder = builder.env(c.env()); } if (c.volumes() != null) { builder = builder.volumes(c.volumes()); } if (c.entrypoint() != null) { builder = builder.entrypoint(c.entrypoint()); } if (c.onBuild() != null) { builder = builder.onBuild(c.onBuild()); } // create container with default random name if an empty/null // containerName argument was passed final ContainerCreation creation = client .createContainer(builder.build(), (containerName != null && !containerName.isEmpty()) ? containerName : null); final String id = creation.id(); // force a refresh of the current containers to include the new one listContainers(); return id; } catch (ContainerNotFoundException e) { throw new DockerContainerNotFoundException(e); } catch (com.spotify.docker.client.exceptions.DockerRequestException e) { throw new DockerException(e.message()); } catch (com.spotify.docker.client.exceptions.DockerException e) { throw new DockerException(e); } } @Override public void stopContainer(final String id) throws DockerException, InterruptedException { try { // stop container or kill after 10 seconds client.stopContainer(id, 10); // allow up to 10 seconds to stop synchronized (loggingThreads) { if (loggingThreads.containsKey(id)) { loggingThreads.get(id).kill(); loggingThreads.remove(id); } } // list of containers needs to be updated once the given container is stopped, to reflect it new state. listContainers(); } catch (ContainerNotFoundException e) { throw new DockerContainerNotFoundException(e); } catch (com.spotify.docker.client.exceptions.DockerRequestException e) { throw new DockerException(e.message()); } catch (com.spotify.docker.client.exceptions.DockerException e) { throw new DockerException(e.getMessage(), e.getCause()); } } @Override public void killContainer(final String id) throws DockerException, InterruptedException { try { // kill container client.killContainer(id); synchronized (loggingThreads) { if (loggingThreads.containsKey(id)) { loggingThreads.get(id).kill(); loggingThreads.remove(id); } } listContainers(); // update container list } catch (ContainerNotFoundException e) { throw new DockerContainerNotFoundException(e); } catch (com.spotify.docker.client.exceptions.DockerRequestException e) { // Permit kill to fail silently even on non-running containers } catch (com.spotify.docker.client.exceptions.DockerException e) { throw new DockerException(e.getMessage(), e.getCause()); } } @Override public void pauseContainer(final String id) throws DockerException, InterruptedException { try { // pause container client.pauseContainer(id); listContainers(); // update container list } catch (ContainerNotFoundException e) { throw new DockerContainerNotFoundException(e); } catch (com.spotify.docker.client.exceptions.DockerRequestException e) { throw new DockerException(e.message()); } catch (com.spotify.docker.client.exceptions.DockerException e) { throw new DockerException(e.getMessage(), e.getCause()); } } @Override public void unpauseContainer(final String id, final OutputStream stream) throws DockerException, InterruptedException { try { // unpause container client.unpauseContainer(id); if (stream != null) { synchronized (loggingThreads) { LogThread t = loggingThreads.get(id); if (t == null || !t.isAlive()) { t = new LogThread(id, getClientCopy(), true); loggingThreads.put(id, t); t.setOutputStream(stream); t.start(); } else { // we aren't going to use the stream given...close it try { stream.close(); } catch (IOException e) { // do nothing...we tried to close the stream } } } } listContainers(); // update container list } catch (ContainerNotFoundException e) { throw new DockerContainerNotFoundException(e); } catch (com.spotify.docker.client.exceptions.DockerRequestException e) { throw new DockerException(e.message()); } catch (com.spotify.docker.client.exceptions.DockerException e) { throw new DockerException(e.getMessage(), e.getCause()); } } @Override public void removeContainer(final String id) throws DockerException, InterruptedException { try { // kill container client.removeContainer(id); listContainers(); // update container list } catch (ContainerNotFoundException e) { throw new DockerContainerNotFoundException(e); } catch (com.spotify.docker.client.exceptions.DockerRequestException e) { throw new DockerException(e.message()); } catch (com.spotify.docker.client.exceptions.DockerException e) { throw new DockerException(e.getMessage(), e.getCause()); } } private String getCmdString(IDockerContainerInfo info) { if (info == null) { return ""; } List<String> cmd = info.config().cmd(); StringBuffer b = new StringBuffer(); cmd.stream().forEach(s -> b.append(s + " ")); b.deleteCharAt(b.length() - 1); return b.toString(); } @Override public void startContainer(final String id, final OutputStream stream) throws DockerException, InterruptedException { final IDockerContainerInfo containerInfo = getContainerInfo(id); if (containerInfo == null) { throw new DockerException(DockerMessages .getFormattedString("DockerContainerNotFound.error", id)); //$NON-NLS-1$ } try { // start container client.startContainer(id); // Log the started container if a stream is provided if (stream != null && containerInfo != null && containerInfo.config() != null && !containerInfo.config().tty()) { // display logs for container synchronized (loggingThreads) { LogThread t = loggingThreads.get(id); if (t == null || !t.isAlive()) { t = new LogThread(id, getClientCopy(), true); loggingThreads.put(id, t); t.setOutputStream(stream); t.start(); } } } // list of containers needs to be refreshed once the container started, to reflect it new state. listContainers(); } catch (ContainerNotFoundException e) { // if we get here, it means that the command failed...the actual // message is buried in the throwable cause and isn't actually // clearly stated so report there was a problem starting the command throw new DockerException(DockerMessages.getFormattedString( "DockerStartContainer.error", getCmdString(containerInfo))); //$NON-NLS-1$ } catch (com.spotify.docker.client.exceptions.DockerRequestException e) { throw new DockerException(e.message()); } catch (com.spotify.docker.client.exceptions.DockerException e) { throw new DockerException(e.getMessage(), e.getCause()); } } @Override public void startContainer(String id, String loggingId, OutputStream stream) throws DockerException, InterruptedException { final IDockerContainerInfo containerInfo = getContainerInfo(id); if (containerInfo == null) { throw new DockerException(DockerMessages .getFormattedString("DockerContainerNotFound.error", id)); //$NON-NLS-1$ } try { // start container with host config client.startContainer(id); // Log the started container based on user preference // Log the started container based on user preference // Log the started container based on user preference IEclipsePreferences preferences = InstanceScope.INSTANCE .getNode("org.eclipse.linuxtools.docker.ui"); //$NON-NLS-1$ boolean autoLog = preferences.getBoolean("autoLogOnStart", true); //$NON-NLS-1$ if (autoLog && !containerInfo.config().tty()) { synchronized (loggingThreads) { LogThread t = loggingThreads.get(loggingId); if (t == null || !t.isAlive()) { t = new LogThread(id, getClientCopy(), true); loggingThreads.put(loggingId, t); t.setOutputStream(stream); t.start(); } } } // update container list listContainers(); } catch (ContainerNotFoundException e) { // if we get here, it means that the command failed...the actual // message is buried in the throwable cause and isn't actually // clearly stated so report there was a problem starting the command throw new DockerException(DockerMessages.getFormattedString( "DockerStartContainer.error", getCmdString(containerInfo))); //$NON-NLS-1$ } catch (com.spotify.docker.client.exceptions.DockerRequestException e) { throw new DockerException(e.message()); } catch (com.spotify.docker.client.exceptions.DockerException e) { throw new DockerException(e); } } @Override public void restartContainer(String id, int secondsToWait) throws DockerException, InterruptedException { restartContainer(id, secondsToWait, null); } public void restartContainer(final String id, int secondsToWait, final OutputStream stream) throws DockerException, InterruptedException { try { // restart container client.restartContainer(id, secondsToWait); // Log the started container if a stream is provided final IDockerContainerInfo containerInfo = getContainerInfo(id); if (stream != null && containerInfo != null && containerInfo.config() != null && !containerInfo.config().tty()) { // display logs for container synchronized (loggingThreads) { LogThread t = loggingThreads.get(id); if (t == null || !t.isAlive()) { t = new LogThread(id, getClientCopy(), true); loggingThreads.put(id, t); t.setOutputStream(stream); t.start(); } } } // list of containers needs to be refreshed once the container // started, to reflect it new state. listContainers(); } catch (ContainerNotFoundException e) { throw new DockerContainerNotFoundException(e); } catch (com.spotify.docker.client.exceptions.DockerRequestException e) { throw new DockerException(e.message()); } catch (com.spotify.docker.client.exceptions.DockerException e) { throw new DockerException(e.getMessage(), e.getCause()); } } @Override public void commitContainer(final String id, final String repo, final String tag, final String comment, final String author) throws DockerException { ContainerInfo info; try { info = client.inspectContainer(id); client.commitContainer(id, repo, tag, info.config(), comment, author); // update images list // FIXME: are we refreshing the list of images twice ? listImages(); getImages(true); } catch (com.spotify.docker.client.exceptions.DockerRequestException e) { throw new DockerException(e.message()); } catch (com.spotify.docker.client.exceptions.DockerException | InterruptedException e) { throw new DockerException(e.getMessage(), e.getCause()); } } @Override public InputStream copyContainer(final String id, final String path) throws DockerException, InterruptedException { InputStream stream; try { DockerClient copy = getClientCopy(); stream = copy.archiveContainer(id, path); } catch (com.spotify.docker.client.exceptions.DockerException e) { throw new DockerException(e.getMessage(), e.getCause()); } return stream; } public boolean isLocal() { if (isLocalConnection != null) return isLocalConnection.booleanValue(); isLocalConnection = new Boolean(false); if (connectionSettings .getType() == BindingType.UNIX_SOCKET_CONNECTION) { isLocalConnection = new Boolean(true); } else if (connectionSettings.getType() == BindingType.TCP_CONNECTION) { TCPConnectionSettings settings = (TCPConnectionSettings) connectionSettings; try { InetAddress addr = InetAddress.getByName(settings.getAddr()); if (addr.isAnyLocalAddress() || addr.isLoopbackAddress()) { isLocalConnection = new Boolean(true); } else { // Check if the address is defined on any interface try { isLocalConnection = new Boolean(NetworkInterface .getByInetAddress(addr) != null); } catch (SocketException e) { isLocalConnection = new Boolean(false); } } } catch (UnknownHostException e) { // should not happen Activator.log(e); } } return isLocalConnection.booleanValue(); } @Override public void copyToContainer(final String directory, final String id, final String path) throws DockerException, InterruptedException, IOException { try { DockerClient copy = getClientCopy(); java.nio.file.Path dirPath = FileSystems.getDefault() .getPath(directory); copy.copyToContainer(dirPath, id, path); copy.close(); /* dispose of client copy now that we are done */ } catch (com.spotify.docker.client.exceptions.DockerException e) { throw new DockerException(e.getMessage(), e.getCause()); } } @Override public int auth(IRegistryAccount cfg) throws DockerException, InterruptedException { try { AuthConfig authConfig = AuthConfig.builder() .username(new String(cfg.getUsername())) .password(cfg.getPassword() != null ? new String(cfg.getPassword()) : null) .email(new String(cfg.getEmail())) .serverAddress(new String(cfg.getServerAddress())).build(); return client.auth(authConfig); } catch (com.spotify.docker.client.exceptions.DockerException e) { throw new DockerException(e.getMessage(), e.getCause()); } } public EnumDockerLoggingStatus loggingStatus(final String id) { synchronized (loggingThreads) { LogThread t = loggingThreads.get(id); if (t == null) return EnumDockerLoggingStatus.LOGGING_NONE; if (t.isAlive()) return EnumDockerLoggingStatus.LOGGING_ACTIVE; return EnumDockerLoggingStatus.LOGGING_COMPLETE; } } @Override public void stopLoggingThread(final String id) { synchronized (loggingThreads) { LogThread t = loggingThreads.get(id); if (t != null) t.requestStop(); } while (loggingStatus(id) == EnumDockerLoggingStatus.LOGGING_ACTIVE) { try { Thread.sleep(1000); } catch (InterruptedException e) { Activator.log(e); } } } @Override public void logContainer(final String id, final OutputStream stream) throws DockerException, InterruptedException { try { // Figure out if we are logging a running container or not // Pass that info to see whether the LogThread should just terminate // or keep running synchronized (loggingThreads) { ContainerInfo info = client.inspectContainer(id); LogThread t = loggingThreads.get(id); if (t == null || !t.isAlive()) { t = new LogThread(id, getClientCopy(), info.state() .running()); loggingThreads.put(id, t); t.setOutputStream(stream); t.start(); } else { // we aren't going to use the stream given...close it try { stream.close(); } catch (IOException e) { // do nothing...we tried to close the stream } } } } catch (ContainerNotFoundException e) { throw new DockerContainerNotFoundException(e); } catch (com.spotify.docker.client.exceptions.DockerRequestException e) { throw new DockerException(e.message()); } catch (com.spotify.docker.client.exceptions.DockerException e) { throw new DockerException(e.getMessage(), e.getCause()); } } @Override public void attachLog(final String id, final OutputStream out, final OutputStream err) throws DockerException, InterruptedException, IOException { DockerClient copyClient; try { copyClient = getClientCopy(); LogStream stream = copyClient.logs(id, LogsParam.follow(), LogsParam.stdout(), LogsParam.stderr()); stream.attach(out, err); stream.close(); } catch (com.spotify.docker.client.exceptions.DockerException e) { throw new DockerException(e.getMessage(), e.getCause()); } } @Override public IDockerContainerExit waitForContainer(final String id) throws DockerException, InterruptedException { try { // wait for container to exit DockerClient copy = getClientCopy(); ContainerExit x = copy.waitContainer(id); DockerContainerExit exit = new DockerContainerExit(x.statusCode()); listContainers(); // update container list copy.close(); // dispose of copy now we are finished return exit; } catch (ContainerNotFoundException e) { throw new DockerContainerNotFoundException(e); } catch (com.spotify.docker.client.exceptions.DockerRequestException e) { throw new DockerException(e.message()); } catch (com.spotify.docker.client.exceptions.DockerException e) { throw new DockerException(e.getMessage(), e.getCause()); } } public void attachCommand(final String id, final InputStream in, @SuppressWarnings("unused") final OutputStream out) throws DockerException { final byte[] prevCmd = new byte[1024]; try { final LogStream pty_stream = client.attachContainer(id, AttachParameter.STDIN, AttachParameter.STDOUT, AttachParameter.STDERR, AttachParameter.STREAM, AttachParameter.LOGS); final IDockerContainerInfo info = getContainerInfo(id); final boolean isTtyEnabled = info.config().tty(); final boolean isOpenStdin = info.config().openStdin(); if (isTtyEnabled) { openTerminal(pty_stream, info.name()); } // Data from the given input stream // Written to container's STDIN Thread t_in = new Thread(() -> { byte[] buff = new byte[1024]; int n; try { WritableByteChannel pty_out = HttpHijackWorkaround .getOutputStream(pty_stream, getUri()); while ((n = in.read(buff)) != -1 && getContainerInfo(id).state().running()) { synchronized (prevCmd) { pty_out.write(ByteBuffer.wrap(buff, 0, n)); for (int i = 0; i < prevCmd.length; i++) { prevCmd[i] = buff[i]; } } buff = new byte[1024]; } } catch (Exception e) { } }); if (!isTtyEnabled && isOpenStdin) { t_in.start(); } } catch (Exception e) { throw new DockerException(e.getMessage(), e.getCause()); } } @SuppressWarnings("unused") public List<ContainerFileProxy> readContainerDirectory(final String id, final String path) throws DockerException { List<ContainerFileProxy> childList = new ArrayList<>(); try { DockerClient copyClient = getClientCopy(); final ExecCreation execCreation = copyClient.execCreate(id, new String[] { "/bin/sh", "-c", "ls -l -F -L -Q " + path }, //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ ExecCreateParam.attachStdout(), ExecCreateParam.attachStderr()); final String execId = execCreation.id(); final LogStream pty_stream = copyClient.execStart(execId); try { while (pty_stream.hasNext()) { ByteBuffer b = pty_stream.next().content(); byte[] buffer = new byte[b.remaining()]; b.get(buffer); String s = new String(buffer); String[] lines = s.split("\\r?\\n"); //$NON-NLS-1$ for (String line : lines) { if (line.trim().startsWith("total")) //$NON-NLS-1$ continue; // ignore the total line String[] token = line.split("\\s+"); //$NON-NLS-1$ boolean isDirectory = token[0].startsWith("d"); //$NON-NLS-1$ boolean isLink = token[0].startsWith("l"); //$NON-NLS-1$ if (token.length > 8) { // last token depends on whether we have a link or not String link = null; if (isLink) { String linkname = token[token.length - 1]; if (linkname.endsWith("/")) { //$NON-NLS-1$ linkname = linkname.substring(0, linkname.length() - 1); isDirectory = true; } IPath linkPath = new Path(path); linkPath = linkPath.append(linkname); link = linkPath.toString(); String name = token[token.length - 3]; childList.add(new ContainerFileProxy(path, name, isDirectory, isLink, link)); } else { String name = token[token.length - 1]; // remove quotes and any indicator char name = name.substring(1, name.length() - (name.endsWith("\"") ? 1 : 2)); childList.add(new ContainerFileProxy(path, name, isDirectory)); } } } } } finally { if (pty_stream != null) pty_stream.close(); if (copyClient != null) copyClient.close(); } } catch (Exception e) { // e.printStackTrace(); } return childList; } public void execShell(final String id) throws DockerException { try { final ExecCreation execCreation = client.execCreate(id, new String[] { "/bin/sh" }, //$NON-NLS-1$ ExecCreateParam.attachStdout(), ExecCreateParam.attachStderr(), ExecCreateParam.attachStdin(), ExecCreateParam.tty()); final String execId = execCreation.id(); final LogStream pty_stream = client.execStart(execId, DockerClient.ExecStartParameter.TTY); final IDockerContainerInfo info = getContainerInfo(id); openTerminal(pty_stream, info.name() + " [shell]"); //$NON-NLS-1$ } catch (Exception e) { throw new DockerException(e.getMessage(), e.getCause()); } } private void openTerminal(LogStream pty_stream, String name) throws DockerException { try { OutputStream tout = noBlockingOutputStream(HttpHijackWorkaround.getOutputStream(pty_stream, getUri())); InputStream tin = HttpHijackWorkaround.getInputStream(pty_stream); // org.eclipse.tm.terminal.connector.ssh.controls.SshWizardConfigurationPanel Map<String, Object> properties = new HashMap<>(); properties.put(ITerminalsConnectorConstants.PROP_DELEGATE_ID, "org.eclipse.tm.terminal.connector.streams.launcher.streams"); //$NON-NLS-1$ properties.put( ITerminalsConnectorConstants.PROP_TERMINAL_CONNECTOR_ID, "org.eclipse.tm.terminal.connector.streams.StreamsConnector"); //$NON-NLS-1$ properties.put(ITerminalsConnectorConstants.PROP_TITLE, name); properties.put(ITerminalsConnectorConstants.PROP_LOCAL_ECHO, false); properties.put(ITerminalsConnectorConstants.PROP_FORCE_NEW, true); properties.put(ITerminalsConnectorConstants.PROP_STREAMS_STDIN, tout); properties.put(ITerminalsConnectorConstants.PROP_STREAMS_STDOUT, tin); properties.put(ITerminalsConnectorConstants.PROP_DATA, pty_stream); /* * The JVM will call finalize() on 'pty_stream' (LogStream) * since we hold no references to it (although we do hold * references to one of its heavily nested fields. The * LogStream overrides finalize() to close the stream being * used so we must preserve a reference to it. */ properties.put("PREVENT_JVM_GC_FINALIZE", pty_stream); //$NON-NLS-1$ ITerminalService service = TerminalServiceFactory.getService(); service.openConsole(properties, null); } catch (Exception e) { throw new DockerException(e.getMessage(), e.getCause()); } } @Override public String getTcpCertPath() { if (this.connectionSettings.getType() == BindingType.TCP_CONNECTION) { return ((TCPConnectionSettings) this.connectionSettings) .getPathToCertificates(); } return null; } @Override public String toString() { return name; } public static OutputStream noBlockingOutputStream(final WritableByteChannel out) { return new OutputStream() { @Override public synchronized void write(int i) throws IOException { byte b[] = new byte[1]; b[0] = (byte) i; write(b); } @Override public synchronized void write(byte[] b, int off, int len) throws IOException { if (len == 0) { return; } ByteBuffer buff = ByteBuffer.wrap(b, off, len); while (buff.remaining() > 0) { out.write(buff); } } @Override public void close() throws IOException { out.close(); } }; } @Override public IDockerNetworkCreation createNetwork(IDockerNetworkConfig cfg) throws DockerException, InterruptedException { try { Ipam.Builder ipamBuilder = Ipam.builder() .driver(cfg.ipam().driver()); List<IDockerIpamConfig> ipamCfgs = cfg.ipam().config(); for (IDockerIpamConfig ipamCfg : ipamCfgs) { ipamBuilder = ipamBuilder.config(ipamCfg.subnet(), ipamCfg.ipRange(), ipamCfg.gateway()); } Ipam ipam = ipamBuilder.build(); NetworkConfig.Builder networkConfigBuilder = NetworkConfig.builder() .name(cfg.name()).driver(cfg.driver()).ipam(ipam); Map<String, String> cfgOptions = cfg.options(); for (Entry<String, String> entry : cfgOptions.entrySet()) { networkConfigBuilder.option(entry.getKey(), entry.getValue()); } NetworkConfig networkConfig = networkConfigBuilder.build(); com.spotify.docker.client.messages.NetworkCreation creation = client .createNetwork(networkConfig); return new DockerNetworkCreation(creation); } catch (com.spotify.docker.client.exceptions.DockerException e) { throw new DockerException(e.getMessage(), e.getCause()); } } @Override public IDockerNetwork inspectNetwork(String networkId) throws DockerException, InterruptedException { try { Network n = client.inspectNetwork(networkId); return new DockerNetwork(n); } catch (com.spotify.docker.client.exceptions.DockerException e) { throw new DockerException(e.getMessage(), e.getCause()); } } @Override public List<IDockerNetwork> listNetworks() throws DockerException, InterruptedException { try { List<Network> networkList = client.listNetworks(); ArrayList<IDockerNetwork> networks = new ArrayList<>(); for (Network n : networkList) { networks.add(new DockerNetwork(n)); } return networks; } catch (com.spotify.docker.client.exceptions.DockerException e) { throw new DockerException(e.getMessage(), e.getCause()); } } @Override public void removeNetwork(String networkId) throws DockerException, InterruptedException { try { client.removeNetwork(networkId); } catch (com.spotify.docker.client.exceptions.DockerException e) { throw new DockerException(e.getMessage(), e.getCause()); } } @Override public void connectNetwork(String id, String networkId) throws DockerException, InterruptedException { try { client.connectToNetwork(id, networkId); } catch (com.spotify.docker.client.exceptions.DockerException e) { throw new DockerException(e.getMessage(), e.getCause()); } } @Override public void disconnectNetwork(String id, String networkId) throws DockerException, InterruptedException { try { client.disconnectFromNetwork(id, networkId); } catch (com.spotify.docker.client.exceptions.DockerException e) { throw new DockerException(e.getMessage(), e.getCause()); } } @Override public boolean equals(Object other) { if (other instanceof IDockerConnection) { return getSettings() .equals(((IDockerConnection) other).getSettings()); } return false; } @Override public int hashCode() { return getSettings().hashCode(); } @Override public IDockerImageHierarchyNode resolveImageHierarchy( final IDockerImage selectedImage) { return DockerImageHierarchyNodeUtils.resolveImageHierarchy(this.images, this.containers, selectedImage); } @Override public IDockerImageHierarchyNode resolveImageHierarchy( final IDockerContainer selectedContainer) { return DockerImageHierarchyNodeUtils.resolveImageHierarchy(this.images, selectedContainer); } }