blob: 66275d9b47783f9b0084e572415e3f33d597c4c4 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2015, 2019 Red Hat Inc. and others.
*
* This program and the accompanying materials are made
* available under the terms of the Eclipse Public License 2.0
* which is available at https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*
* Contributors:
* Red Hat - Initial Contribution
*******************************************************************************/
package org.eclipse.linuxtools.docker.ui.launch;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.Closeable;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.OutputStream;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.nio.file.attribute.PosixFilePermission;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
import java.util.TreeSet;
import org.apache.commons.compress.archivers.tar.TarArchiveEntry;
import org.apache.commons.compress.archivers.tar.TarArchiveInputStream;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Path;
import org.eclipse.core.runtime.Platform;
import org.eclipse.core.runtime.Status;
import org.eclipse.core.runtime.jobs.Job;
import org.eclipse.jface.dialogs.MessageDialog;
import org.eclipse.linuxtools.docker.core.DockerConnectionManager;
import org.eclipse.linuxtools.docker.core.DockerException;
import org.eclipse.linuxtools.docker.core.IDockerConnection;
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.IDockerHostConfig;
import org.eclipse.linuxtools.docker.core.IDockerImage;
import org.eclipse.linuxtools.docker.core.IDockerImageInfo;
import org.eclipse.linuxtools.docker.core.IDockerPortBinding;
import org.eclipse.linuxtools.docker.core.IDockerVolume;
import org.eclipse.linuxtools.docker.ui.Activator;
import org.eclipse.linuxtools.internal.docker.core.DockerConnection;
import org.eclipse.linuxtools.internal.docker.core.DockerConsoleOutputStream;
import org.eclipse.linuxtools.internal.docker.core.DockerContainerConfig;
import org.eclipse.linuxtools.internal.docker.core.DockerHostConfig;
import org.eclipse.linuxtools.internal.docker.core.DockerPortBinding;
import org.eclipse.linuxtools.internal.docker.core.IConsoleListener;
import org.eclipse.linuxtools.internal.docker.ui.consoles.ConsoleOutputStream;
import org.eclipse.linuxtools.internal.docker.ui.consoles.RunConsole;
import org.eclipse.linuxtools.internal.docker.ui.launch.ContainerCommandProcess;
import org.eclipse.linuxtools.internal.docker.ui.launch.LaunchConfigurationUtils;
import org.eclipse.linuxtools.internal.docker.ui.views.DVMessages;
import org.eclipse.linuxtools.internal.docker.ui.wizards.DataVolumeModel;
import org.eclipse.swt.custom.CTabFolder;
import org.eclipse.swt.custom.CTabItem;
import org.eclipse.swt.widgets.Display;
import org.eclipse.ui.IViewPart;
import org.eclipse.ui.IWorkbenchPage;
import org.eclipse.ui.PlatformUI;
public class ContainerLauncher {
private static final String ERROR_CREATING_CONTAINER = "ContainerCreateError.msg"; //$NON-NLS-1$
private static final String ERROR_LAUNCHING_CONTAINER = "ContainerLaunchError.msg"; //$NON-NLS-1$
private static final String ERROR_NO_CONNECTIONS = "ContainerNoConnections.msg"; //$NON-NLS-1$
private static final String ERROR_NO_CONNECTION_WITH_URI = "ContainerNoConnectionWithURI.msg"; //$NON-NLS-1$
private static final String DIRFILE_NAME = "copiedVolumes"; //$NON-NLS-1$
private static RunConsole console;
private static Map<IProject, ID> fidMap = new HashMap<>();
private static Object lockObject = new Object();
private static Map<String, Map<String, Set<String>>> copiedVolumesMap = null;
private static Map<String, Map<String, Set<String>>> copyingVolumesMap = null;
private static Set<PosixFilePermission> toPerms(int mode) {
Set<PosixFilePermission> perms = new HashSet<>();
if ((mode & 0400) != 0) {
perms.add(PosixFilePermission.OWNER_READ);
}
if ((mode & 0200) != 0) {
perms.add(PosixFilePermission.OWNER_WRITE);
}
if ((mode & 0100) != 0) {
perms.add(PosixFilePermission.OWNER_EXECUTE);
}
if ((mode & 0040) != 0) {
perms.add(PosixFilePermission.GROUP_READ);
}
if ((mode & 0020) != 0) {
perms.add(PosixFilePermission.GROUP_WRITE);
}
if ((mode & 0010) != 0) {
perms.add(PosixFilePermission.GROUP_EXECUTE);
}
if ((mode & 0004) != 0) {
perms.add(PosixFilePermission.OTHERS_READ);
}
if ((mode & 0002) != 0) {
perms.add(PosixFilePermission.OTHERS_WRITE);
}
if ((mode & 0001) != 0) {
perms.add(PosixFilePermission.OTHERS_EXECUTE);
}
return perms;
}
private class CopyVolumesJob extends Job {
private static final String COPY_VOLUMES_JOB_TITLE = "ContainerLaunch.copyVolumesJob.title"; //$NON-NLS-1$
private static final String COPY_VOLUMES_DESC = "ContainerLaunch.copyVolumesJob.desc"; //$NON-NLS-1$
private static final String COPY_VOLUMES_TASK = "ContainerLaunch.copyVolumesJob.task"; //$NON-NLS-1$
private static final String ERROR_COPYING_VOLUME = "ContainerLaunch.copyVolumesJob.error"; //$NON-NLS-1$
private final Map<String, String> volumes;
private final IDockerConnection connection;
private final String containerId;
public CopyVolumesJob(Map<String, String> volumes,
IDockerConnection connection,
String containerId) {
super(Messages.getString(COPY_VOLUMES_JOB_TITLE));
this.volumes = volumes;
this.connection = connection;
this.containerId = containerId;
}
@Override
protected IStatus run(IProgressMonitor monitor) {
monitor.beginTask(
Messages.getFormattedString(COPY_VOLUMES_DESC, containerId),
volumes.size());
Iterator<String> iterator = volumes.keySet().iterator();
IStatus status = Status.OK_STATUS;
// for each remote volume, copy from host to Container volume
while (iterator.hasNext()) {
if (monitor.isCanceled()) {
monitor.done();
return Status.CANCEL_STATUS;
}
String hostDirectory = iterator.next();
String containerDirectory = volumes.get(hostDirectory);
if (!containerDirectory.endsWith("/")) { //$NON-NLS-1$
containerDirectory = containerDirectory + "/"; //$NON-NLS-1$
}
if (!hostDirectory.endsWith("/")) { //$NON-NLS-1$
hostDirectory = hostDirectory + "/"; //$NON-NLS-1$
}
monitor.setTaskName(Messages
.getFormattedString(COPY_VOLUMES_TASK, hostDirectory));
try {
((DockerConnection) connection).copyToContainer(
hostDirectory, containerId, containerDirectory);
monitor.worked(1);
} catch (DockerException | InterruptedException
| IOException e) {
monitor.done();
final String dir = hostDirectory;
Display.getDefault().syncExec(() -> MessageDialog.openError(
PlatformUI.getWorkbench().getActiveWorkbenchWindow()
.getShell(),
Messages.getFormattedString(ERROR_COPYING_VOLUME,
new String[] { dir, containerId }),
e.getMessage()));
status = new Status(IStatus.ERROR, Activator.PLUGIN_ID,
e.getMessage());
} finally {
monitor.done();
}
}
return status;
}
}
/**
* A blocking input stream that waits until data is available.
*/
private class BlockingInputStream extends InputStream {
private InputStream in;
public BlockingInputStream(InputStream in) {
this.in = in;
}
@Override
public int read() throws IOException {
return in.read();
}
}
private class CopyVolumesFromImageJob extends Job {
private static final String COPY_VOLUMES_FROM_JOB_TITLE = "ContainerLaunch.copyVolumesFromJob.title"; //$NON-NLS-1$
private static final String COPY_VOLUMES_FROM_DESC = "ContainerLaunch.copyVolumesFromJob.desc"; //$NON-NLS-1$
private static final String COPY_VOLUMES_FROM_TASK = "ContainerLaunch.copyVolumesFromJob.task"; //$NON-NLS-1$
private final List<String> volumes;
private final List<String> excludedDirs;
private final IDockerConnection connection;
private final String image;
private final IPath target;
private Set<String> dirList;
private Set<String> copyingList;
public CopyVolumesFromImageJob(
IDockerConnection connection,
String image, List<String> volumes, List<String> excludedDirs,
IPath target) {
super(Messages.getString(COPY_VOLUMES_FROM_JOB_TITLE));
this.volumes = volumes;
this.excludedDirs = excludedDirs;
this.connection = connection;
this.image = image;
this.target = target;
Map<String, Set<String>> dirMap = null;
Map<String, Set<String>> copyingMap = null;
synchronized (lockObject) {
String uri = connection.getUri();
dirMap = copiedVolumesMap.get(uri);
if (dirMap == null) {
dirMap = new HashMap<>();
copiedVolumesMap.put(uri, dirMap);
}
dirList = dirMap.get(image);
if (dirList == null) {
dirList = new LinkedHashSet<>();
dirMap.put(image, dirList);
}
copyingMap = copyingVolumesMap.get(uri);
if (copyingMap == null) {
copyingMap = new HashMap<>();
copyingVolumesMap.put(uri, copyingMap);
}
copyingList = copyingMap.get(image);
if (copyingList == null) {
copyingList = new LinkedHashSet<>();
copyingMap.put(image, copyingList);
}
}
}
@Override
protected IStatus run(final IProgressMonitor monitor) {
monitor.beginTask(Messages.getFormattedString(COPY_VOLUMES_FROM_DESC, image), volumes.size());
String containerId = null;
String currentVolume = null;
boolean isWin = Platform.getOS().equals(Platform.OS_WIN32);
// keep a list of already copied/being copied volumes so we can skip them and
// wait at the end to
// make sure if another job was copying them, it has finished
List<String> alreadyCopiedList = new ArrayList<>();
try {
IDockerImage dockerImage = ((DockerConnection) connection).getImageByTag(image);
// if there is a .image_id file, check the image id to ensure
// the user hasn't loaded a new version which may have
// different header files installed.
IPath imageFilePath = target.append(".image_id"); //$NON-NLS-1$
File imageFile = imageFilePath.toFile();
boolean needImageIdFile = !imageFile.exists();
if (!needImageIdFile) {
try (FileReader reader = new FileReader(imageFile);
BufferedReader bufferReader = new BufferedReader(reader);) {
String imageId = bufferReader.readLine();
if (!dockerImage.id().equals(imageId)) {
// if image id has changed...all bets are off
// and we must reload all directories
synchronized (lockObject) {
dirList.clear();
copyingList.clear();
}
needImageIdFile = true;
}
} catch (IOException e) {
// ignore
}
}
if (needImageIdFile) {
try (FileWriter writer = new FileWriter(imageFile);
BufferedWriter bufferedWriter = new BufferedWriter(writer);) {
bufferedWriter.write(dockerImage.id());
bufferedWriter.newLine();
synchronized (lockObject) {
dirList.clear();
copyingList.clear();
}
} catch (IOException e) {
// ignore
}
}
// check if we have anything to copy
boolean somethingToCopy = false;
synchronized (lockObject) {
for (String volume : volumes) {
boolean excluded = false;
for (String dir : excludedDirs) {
if (volume.equals(dir)
|| (volume.startsWith(dir) && volume.charAt(dir.length()) == File.separatorChar)) {
excluded = true;
break;
}
}
if (excluded) {
continue;
}
boolean alreadyCopied = false;
for (String path : dirList) {
if (volume.equals(path) || (volume.startsWith(path)
&& volume.charAt(path.length()) == File.separatorChar)) {
if (!copyingList.contains(path)) {
alreadyCopied = true;
break;
}
}
}
if (!alreadyCopied) {
somethingToCopy = true;
break;
}
}
}
// if nothing to copy, don't waste time creating a Container
if (!somethingToCopy) {
monitor.done();
return Status.OK_STATUS;
}
// create base container to use for copying
DockerContainerConfig.Builder builder = new DockerContainerConfig.Builder().cmd("/bin/sh").image(image); //$NON-NLS-1$
IDockerContainerConfig config = builder.build();
DockerHostConfig.Builder hostBuilder = new DockerHostConfig.Builder();
IDockerHostConfig hostConfig = hostBuilder.build();
containerId = ((DockerConnection) connection).createContainer(config, hostConfig, null);
// copy each volume if it exists and is not copied over yet
for (String volume : volumes) {
currentVolume = volume;
if (monitor.isCanceled()) {
monitor.done();
return Status.CANCEL_STATUS;
}
// don't bother copying files from project
if (volume.contains("${ProjName}")) { //$NON-NLS-1$
monitor.worked(1);
continue;
}
// don't copy directories that are excluded
boolean excluded = false;
for (String dir : excludedDirs) {
if (volume.equals(dir)
|| (volume.startsWith(dir) && volume.charAt(dir.length()) == File.separatorChar)) {
excluded = true;
break;
}
}
if (excluded) {
monitor.worked(1);
continue;
}
// if we have already copied the directory either directly
// or as part of a parent directory copy, then skip to next
// volume.
String alreadyCopied = null;
synchronized (lockObject) {
for (String path : dirList) {
if (volume.equals(path) || (volume.startsWith(path)
&& volume.charAt(path.length()) == File.separatorChar)) {
alreadyCopied = path;
if (!dirList.contains(volume)) {
dirList.add(volume);
}
break;
}
}
}
// if we found a match, make sure it is finished copying
// before continuing
if (alreadyCopied != null) {
alreadyCopiedList.add(alreadyCopied);
monitor.worked(1);
continue;
}
// synchronize on the volume so others can wait until copy
// is completed
// instead of returning too fast and the headers won't be
// there
synchronized (volume) {
try (Closeable token = ((DockerConnection) connection).getOperationToken()) {
monitor.setTaskName(Messages.getFormattedString(COPY_VOLUMES_FROM_TASK, volume));
monitor.worked(1);
InputStream in = ((DockerConnection) connection).copyContainer(token, containerId, volume);
synchronized (lockObject) {
if (!dirList.contains(volume)) {
dirList.add(volume);
copyingList.add(volume);
} else {
continue;
}
}
/*
* The input stream from copyContainer might be incomplete or non-blocking so we
* should wrap it in a stream that is guaranteed to block until data is
* available.
*/
try (TarArchiveInputStream k = new TarArchiveInputStream(new BlockingInputStream(in))) {
TarArchiveEntry te = null;
target.toFile().mkdirs();
IPath currDir = target.append(volume).removeLastSegments(1);
currDir.toFile().mkdirs();
while ((te = k.getNextTarEntry()) != null) {
long size = te.getSize();
IPath path = currDir;
path = path.append(te.getName());
File f = new File(path.toOSString());
int mode = te.getMode();
if (te.isDirectory()) {
f.mkdir();
if (!isWin && !te.isSymbolicLink()) {
Files.setPosixFilePermissions(Paths.get(path.toOSString()), toPerms(mode));
}
continue;
} else {
if (".project".equals(te.getName())) { //$NON-NLS-1$
continue;
}
f.createNewFile();
if (!isWin && !te.isSymbolicLink()) {
Files.setPosixFilePermissions(Paths.get(path.toOSString()), toPerms(mode));
}
}
try (FileOutputStream os = new FileOutputStream(f)) {
int bufferSize = ((int) size > 4096 ? 4096 : (int) size);
byte[] barray = new byte[bufferSize];
int result = -1;
while ((result = k.read(barray, 0, bufferSize)) > -1) {
if (monitor.isCanceled()) {
monitor.done();
k.close();
os.close();
return Status.CANCEL_STATUS;
}
os.write(barray, 0, result);
}
}
}
}
// remove from copying list so subsequent jobs might
// know that the volume
// is fully copied
synchronized (lockObject) {
copyingList.remove(currentVolume);
}
} catch (final DockerException e) {
// ignore
synchronized (lockObject) {
copyingList.remove(currentVolume);
dirList.remove(currentVolume);
}
}
}
}
} catch (InterruptedException e) {
// do nothing
} catch (IOException | DockerException e) {
if (currentVolume != null) {
synchronized (lockObject) {
if (copyingList != null) {
copyingList.remove(currentVolume);
}
if (dirList != null) {
dirList.remove(currentVolume);
}
}
}
Activator.log(e);
} finally {
// remove the container used for copying
if (containerId != null) {
try {
((DockerConnection) connection).removeContainer(containerId);
} catch (DockerException | InterruptedException e) {
// ignore
}
}
for (String copiedVolume : alreadyCopiedList) {
synchronized (copiedVolume) {
// do something so synchronization will occur
synchronized (lockObject) {
if (!dirList.contains(copiedVolume)) {
dirList.add(copiedVolume);
}
}
}
}
monitor.done();
}
return Status.OK_STATUS;
}
}
@Override
protected void finalize() throws Throwable {
synchronized (lockObject) {
if (copiedVolumesMap != null) {
IPath pluginPath = Platform.getStateLocation(
Platform.getBundle(Activator.PLUGIN_ID));
IPath path = pluginPath.append(DIRFILE_NAME);
File dirFile = path.toFile();
FileOutputStream f = new FileOutputStream(dirFile);
try (ObjectOutputStream oos = new ObjectOutputStream(f)) {
oos.writeObject(copiedVolumesMap);
}
}
}
super.finalize();
}
public ContainerLauncher() {
initialize();
}
@SuppressWarnings("unchecked")
private void initialize() {
synchronized (lockObject) {
if (copiedVolumesMap == null) {
IPath pluginPath = Platform.getStateLocation(
Platform.getBundle(Activator.PLUGIN_ID));
IPath path = pluginPath.append(DIRFILE_NAME);
File dirFile = path.toFile();
if (dirFile.exists()) {
try (FileInputStream f = new FileInputStream(dirFile)) {
try (ObjectInputStream ois = new ObjectInputStream(f)) {
copiedVolumesMap = (Map<String, Map<String, Set<String>>>) ois
.readObject();
} catch (ClassNotFoundException
| FileNotFoundException e) {
// should never happen so print stack trace
e.printStackTrace();
}
} catch (IOException e) {
// will handle this below
}
}
}
if (copiedVolumesMap == null) {
copiedVolumesMap = new HashMap<>();
}
if (copyingVolumesMap == null) {
copyingVolumesMap = new HashMap<>();
}
}
}
/**
* Perform a launch of a command in a container and output stdout/stderr to
* console.
*
* @param id
* - id of caller to use to distinguish console owner
* @param listener
* - optional listener of the run console
* @param connectionUri
* - the specified connection to use
* @param image
* - the image to use
* @param command
* - command to run
* @param commandDir
* - directory command requires or null
* @param workingDir
* - working directory or null
* @param additionalDirs
* - additional directories to mount or null
* @param origEnv
* - original environment if we are appending to our existing
* environment
* @param envMap
* - map of environment variable settings
* @param ports
* - ports to expose
* @param keep
* - keep container after running
* @param stdinSupport
* - true if stdin support is required, false otherwise
*/
public void launch(String id, IContainerLaunchListener listener,
final String connectionUri,
String image, String command, String commandDir, String workingDir,
List<String> additionalDirs, Map<String, String> origEnv,
Map<String, String> envMap, List<String> ports, boolean keep,
boolean stdinSupport) {
launch(id, listener, connectionUri, image, command, commandDir,
workingDir, additionalDirs, origEnv, envMap, ports, keep,
stdinSupport, false);
}
/**
* Perform a launch of a command in a container and output stdout/stderr to
* console.
*
* @param id
* - id of caller to use to distinguish console owner
* @param listener
* - optional listener of the run console
* @param connectionUri
* - the specified connection to use
* @param image
* - the image to use
* @param command
* - command to run
* @param commandDir
* - directory command requires or null
* @param workingDir
* - working directory or null
* @param additionalDirs
* - additional directories to mount or null
* @param origEnv
* - original environment if we are appending to our existing
* environment
* @param envMap
* - map of environment variable settings
* @param ports
* - ports to expose
* @param keep
* - keep container after running
* @param stdinSupport
* - true if stdin support is required, false otherwise
* @param privilegedMode
* - true if privileged mode is required, false otherwise
* @since 2.1
*/
public void launch(String id, IContainerLaunchListener listener,
final String connectionUri, String image, String command,
String commandDir, String workingDir, List<String> additionalDirs,
Map<String, String> origEnv, Map<String, String> envMap,
List<String> ports, boolean keep, boolean stdinSupport,
boolean privilegedMode) {
launch(id, listener, connectionUri, image, command, commandDir,
workingDir, additionalDirs, origEnv, envMap, ports, keep,
stdinSupport, privilegedMode, null);
}
/**
* Perform a launch of a command in a container and output stdout/stderr to
* console.
*
* @param id
* - id of caller to use to distinguish console owner
* @param listener
* - optional listener of the run console
* @param connectionUri
* - the specified connection to use
* @param image
* - the image to use
* @param command
* - command to run
* @param commandDir
* - directory command requires or null
* @param workingDir
* - working directory or null
* @param additionalDirs
* - additional directories to mount or null
* @param origEnv
* - original environment if we are appending to our existing
* environment
* @param envMap
* - map of environment variable settings
* @param ports
* - ports to expose
* @param keep
* - keep container after running
* @param stdinSupport
* - true if stdin support is required, false otherwise
* @param privilegedMode
* - true if privileged mode is required, false otherwise
* @param labels
* - Map of labels for the container
* @since 2.2
*/
public void launch(String id, IContainerLaunchListener listener,
final String connectionUri, String image, String command,
String commandDir, String workingDir, List<String> additionalDirs,
Map<String, String> origEnv, Map<String, String> envMap,
List<String> ports, boolean keep, boolean stdinSupport,
boolean privilegedMode, Map<String, String> labels) {
launch(id, listener, connectionUri, image, command, commandDir,
workingDir, additionalDirs, origEnv, envMap, ports, keep,
stdinSupport, privilegedMode, labels, null);
}
// The following class allows us to use internal IConsoleListeners in
// docker.core
// but still use the public IRunConsoleListeners API here without requiring
// a minor release.
private class RunConsoleListenerBridge implements IConsoleListener {
private IRunConsoleListener listener;
public RunConsoleListenerBridge(IRunConsoleListener listener) {
this.listener = listener;
}
@Override
public void newOutput(String output) {
listener.newOutput(output);
}
}
/**
* Perform a launch of a command in a container and output stdout/stderr to
* console.
*
* @param id
* - id of caller to use to distinguish console owner
* @param listener
* - optional listener of the run console
* @param connectionUri
* - the specified connection to use
* @param image
* - the image to use
* @param command
* - command to run
* @param commandDir
* - directory command requires or null
* @param workingDir
* - working directory or null
* @param additionalDirs
* - additional directories to mount or null
* @param origEnv
* - original environment if we are appending to our existing
* environment
* @param envMap
* - map of environment variable settings
* @param ports
* - ports to expose
* @param keep
* - keep container after running
* @param stdinSupport
* - true if stdin support is required, false otherwise
* @param privilegedMode
* - true if privileged mode is required, false otherwise
* @param labels
* - Map of labels for the container
* @param seccomp
* - seccomp profile
* @since 3.0
*/
public void launch(String id, IContainerLaunchListener listener,
final String connectionUri, String image, String command,
@SuppressWarnings("unused") String commandDir, String workingDir,
List<String> additionalDirs,
Map<String, String> origEnv, Map<String, String> envMap,
List<String> ports, boolean keep, boolean stdinSupport,
boolean privilegedMode, Map<String, String> labels,
String seccomp) {
final List<String> cmdList = getCmdList(command);
launch(id, listener, connectionUri, image, cmdList, workingDir,
additionalDirs, origEnv, envMap, ports, keep, stdinSupport,
privilegedMode, labels, seccomp);
}
/**
* Perform a launch of a command in a container and output stdout/stderr to
* console.
*
* @param id
* - id of caller to use to distinguish console owner
* @param listener
* - optional listener of the run console
* @param connectionUri
* - the specified connection to use
* @param image
* - the image to use
* @param cmdList
* - command to run as list of String
* @param workingDir
* - working directory or null
* @param additionalDirs
* - additional directories to mount or null
* @param origEnv
* - original environment if we are appending to our existing
* environment
* @param envMap
* - map of environment variable settings
* @param ports
* - ports to expose
* @param keep
* - keep container after running
* @param stdinSupport
* - true if stdin support is required, false otherwise
* @param privilegedMode
* - true if privileged mode is required, false otherwise
* @param labels
* - Map of labels for the container
* @param seccomp
* - seccomp profile
* @since 4.0
*/
public void launch(String id, IContainerLaunchListener listener,
final String connectionUri, String image, List<String> cmdList,
String workingDir, List<String> additionalDirs,
Map<String, String> origEnv, Map<String, String> envMap,
List<String> ports, boolean keep, boolean stdinSupport,
boolean privilegedMode, Map<String, String> labels,
String seccomp) {
final String LAUNCH_TITLE = "ContainerLaunch.title"; //$NON-NLS-1$
final String LAUNCH_EXITED_TITLE = "ContainerLaunchExited.title"; //$NON-NLS-1$
final List<String> env = new ArrayList<>();
env.addAll(toList(origEnv));
env.addAll(toList(envMap));
final Set<String> exposedPorts = new HashSet<>();
final Map<String, List<IDockerPortBinding>> portBindingsMap = new HashMap<>();
if (ports != null) {
for (String port : ports) {
port = port.trim();
if (port.length() > 0) {
String[] segments = port.split(":"); //$NON-NLS-1$
if (segments.length == 1) { // containerPort
exposedPorts.add(segments[0]);
portBindingsMap
.put(segments[0],
Arrays.asList((IDockerPortBinding) new DockerPortBinding(
"", ""))); //$NON-NLS-1$ //$NON-NLS-2$
} else if (segments.length == 2) { // hostPort:containerPort
exposedPorts.add(segments[1]);
portBindingsMap
.put(segments[1],
Arrays.asList((IDockerPortBinding) new DockerPortBinding(
"", segments[0]))); //$NON-NLS-1$ //$NON-NLS-2$
} else if (segments.length == 3) { // either
// ip:hostPort:containerPort
// or ip::containerPort
exposedPorts.add(segments[1]);
if (segments[1].isEmpty()) {
portBindingsMap
.put(segments[2],
Arrays.asList((IDockerPortBinding) new DockerPortBinding(
"", segments[0]))); //$NON-NLS-1$ //$NON-NLS-2$
} else {
portBindingsMap
.put(segments[2],
Arrays.asList((IDockerPortBinding) new DockerPortBinding(
segments[0], segments[1]))); //$NON-NLS-1$ //$NON-NLS-2$
}
}
}
}
}
// Note we only pass volumes to the config if we have a
// remote daemon. Local mounted volumes are passed
// via the HostConfig binds setting
DockerContainerConfig.Builder builder = new DockerContainerConfig.Builder()
.env(env)
.openStdin(stdinSupport)
.cmd(cmdList)
.image(image)
.workingDir(workingDir);
// Ugly hack...we want CDT gdbserver to run in the terminal so we look
// for its
// ContainerListener class and set tty=true in that case...this avoids a
// minor release and we can later add a new launch method with the tty
// option
if (listener != null && listener.getClass().getName().equals(
"org.eclipse.cdt.internal.docker.launcher.ContainerLaunchConfigurationDelegate$StartGdbServerJob")) {
builder = builder.tty(true);
}
// add any exposed ports as needed
if (exposedPorts.size() > 0)
builder = builder.exposedPorts(exposedPorts);
// add any labels if specified
if (labels != null)
builder = builder.labels(labels);
if (!DockerConnectionManager.getInstance().hasConnections()) {
Display.getDefault()
.syncExec(() -> MessageDialog.openError(
PlatformUI.getWorkbench().getActiveWorkbenchWindow()
.getShell(),
DVMessages.getString(ERROR_LAUNCHING_CONTAINER),
DVMessages.getString(ERROR_NO_CONNECTIONS)));
return;
}
// Try and use the specified connection that was used before,
// otherwise, open an error
final IDockerConnection connection = DockerConnectionManager
.getInstance().getConnectionByUri(connectionUri);
if (connection == null) {
Display.getDefault()
.syncExec(() -> MessageDialog.openError(
PlatformUI.getWorkbench().getActiveWorkbenchWindow()
.getShell(),
DVMessages.getString(ERROR_LAUNCHING_CONTAINER),
DVMessages.getFormattedString(
ERROR_NO_CONNECTION_WITH_URI,
connectionUri)));
return;
}
// if connection is not open, force it to be by fetching images
if (!connection.isOpen()) {
connection.getImages();
}
IDockerImageInfo imageInfo = connection.getImageInfo(image);
if (imageInfo == null) {
Display.getDefault()
.syncExec(() -> MessageDialog.openError(
PlatformUI.getWorkbench().getActiveWorkbenchWindow().getShell(),
DVMessages.getString(ERROR_LAUNCHING_CONTAINER),
Messages.getFormattedString("ContainerLaunch.imageNotFound.error", image)));
return;
}
IDockerContainerConfig imageConfig = imageInfo.config();
if (imageConfig != null && imageConfig.entrypoint() != null) {
builder = builder.entryPoint(imageConfig.entrypoint());
}
DockerHostConfig.Builder hostBuilder = new DockerHostConfig.Builder()
.privileged(privilegedMode);
// specify seccomp profile if caller has provided one - needed to use
// ptrace with gdbserver
if (seccomp != null) {
hostBuilder.securityOpt(seccomp);
}
final Map<String, String> remoteVolumes = new HashMap<>();
if (!((DockerConnection) connection).isLocal()) {
@SuppressWarnings("rawtypes")
final Map<String,Map> volumes = new HashMap<>();
// if using remote daemon, we have to
// handle volume mounting differently.
// Instead we mount empty volumes and copy
// the host data over before starting.
if (additionalDirs != null) {
for (String dir : additionalDirs) {
remoteVolumes.put(dir, dir);
volumes.put(dir, new HashMap<>());
}
}
if (workingDir != null) {
remoteVolumes.put(workingDir, workingDir); // $NON-NLS-1$
volumes.put(workingDir, new HashMap<>());
}
builder = builder.volumes(volumes);
} else {
// Running daemon on local host.
// Add mounts for any directories we need to run the executable.
// When we add mount points, we need entries of the form:
// hostname:mountname:Z.
// In our case, we want all directories mounted as-is so the
// executable will run as the user expects.
final List<String> volumes = new ArrayList<>();
if (additionalDirs != null) {
for (String dir : additionalDirs) {
volumes.add(dir + ":" + dir + ":Z"); //$NON-NLS-1$ //$NON-NLS-2$
}
}
if (workingDir != null) {
volumes.add(workingDir + ":" + workingDir + ":Z"); //$NON-NLS-1$ //$NON-NLS-2$
}
hostBuilder = hostBuilder.binds(volumes);
}
final DockerContainerConfig config = builder.build();
// add any port bindings if specified
if (portBindingsMap.size() > 0)
hostBuilder = hostBuilder.portBindings(portBindingsMap);
final IDockerHostConfig hostConfig = hostBuilder.build();
final String imageName = image;
final boolean keepContainer = keep;
final String consoleId = id;
final IContainerLaunchListener containerListener = listener;
Thread t = new Thread(() -> {
// create the container
String containerId = null;
try {
containerId = ((DockerConnection) connection)
.createContainer(config, hostConfig, null);
if (!((DockerConnection) connection).isLocal()) {
// if daemon is remote, we need to copy
// data over from the host.
if (!remoteVolumes.isEmpty()) {
CopyVolumesJob job = new CopyVolumesJob(remoteVolumes,
connection, containerId);
job.schedule();
job.join();
if (job.getResult() != Status.OK_STATUS)
return;
}
}
if (config.tty()) {
// We need tty support to handle issue with Docker daemon
// not always outputting in time (e.g. we might get an
// output line after the process has exited which can be
// too late to show or it might get displayed in a wrong
// order in relation to other output. We also want the
// output to ultimately show up in the Console View.
OutputStream stream = null;
RunConsole oldConsole = getConsole();
final RunConsole rc = RunConsole.findConsole(containerId,
consoleId);
setConsole(rc);
rc.clearConsole();
if (oldConsole != null)
RunConsole.removeConsole(oldConsole);
Display.getDefault().syncExec(() -> rc.setTitle(Messages
.getFormattedString(LAUNCH_TITLE, new String[] {
cmdList.get(0), imageName })));
if (rc != null) {
stream = rc.getOutputStream();
}
// We want terminal support, but we want to output to the
// RunConsole.
// To do this, we create a DockerConsoleOutputStream which
// we
// hook into the TM Terminal via stdout and stderr output
// listeners.
// These listeners will output to the
// DockerConsoleOutputStream which
// will in turn output to the RunConsole. See
// DockerConnection.openTerminal().
DockerConsoleOutputStream out = new DockerConsoleOutputStream(
stream);
RunConsole.attachToTerminal(connection, containerId, out);
if (containerListener != null) {
out.addConsoleListener(new RunConsoleListenerBridge(
containerListener));
}
((DockerConnection) connection).startContainer(containerId,
null, null);
IDockerContainerInfo info = ((DockerConnection) connection)
.getContainerInfo(containerId);
if (containerListener != null) {
containerListener.containerInfo(info);
}
// Wait for the container to finish
final IDockerContainerExit status = ((DockerConnection) connection)
.waitForContainer(containerId);
Display.getDefault().syncExec(() -> {
rc.setTitle(
Messages.getFormattedString(LAUNCH_EXITED_TITLE,
new String[] {
status.statusCode().toString(),
cmdList.get(0), imageName }));
rc.showConsole();
// We used a TM Terminal to receive the output of the
// session and
// then sent the output to the RunConsole. Remove the
// terminal
// tab that got created now that we are finished and all
// data is shown
// in Console View.
IWorkbenchPage page = PlatformUI.getWorkbench()
.getActiveWorkbenchWindow().getActivePage();
IViewPart terminalView = page.findView(
"org.eclipse.tm.terminal.view.ui.TerminalsView");
CTabFolder ctabfolder = terminalView
.getAdapter(CTabFolder.class);
if (ctabfolder != null) {
CTabItem[] items = ctabfolder.getItems();
for (CTabItem item : items) {
if (item.getText().endsWith(info.name())) {
item.dispose();
break;
}
}
}
});
// Let any container listener know that the container is
// finished
if (containerListener != null)
containerListener.done();
if (!keepContainer) {
((DockerConnection) connection)
.removeContainer(containerId);
}
} else {
OutputStream stream = null;
RunConsole oldConsole = getConsole();
final RunConsole rc = RunConsole.findConsole(containerId,
consoleId);
setConsole(rc);
rc.clearConsole();
if (oldConsole != null)
RunConsole.removeConsole(oldConsole);
Display.getDefault().syncExec(() -> rc.setTitle(Messages
.getFormattedString(LAUNCH_TITLE, new String[] {
cmdList.get(0), imageName })));
// if (!rc.isAttached()) {
rc.attachToConsole(connection, containerId);
// }
if (rc != null) {
stream = rc.getOutputStream();
if (containerListener != null) {
((ConsoleOutputStream) stream)
.addConsoleListener(containerListener);
}
}
// Create a unique logging thread id which has container id
// and console id
String loggingId = containerId + "." + consoleId;
((DockerConnection) connection).startContainer(containerId,
loggingId, stream);
if (rc != null)
rc.showConsole();
if (containerListener != null) {
IDockerContainerInfo info = ((DockerConnection) connection)
.getContainerInfo(containerId);
containerListener.containerInfo(info);
}
// Wait for the container to finish
final IDockerContainerExit status = ((DockerConnection) connection)
.waitForContainer(containerId);
Display.getDefault().syncExec(() -> {
rc.setTitle(
Messages.getFormattedString(LAUNCH_EXITED_TITLE,
new String[] {
status.statusCode().toString(),
cmdList.get(0), imageName }));
rc.showConsole();
});
// Let any container listener know that the container is
// finished
if (containerListener != null)
containerListener.done();
if (!keepContainer) {
// Drain the logging thread before we remove the
// container (we need to use the logging id)
Thread.sleep(1000);
((DockerConnection) connection)
.stopLoggingThread(loggingId);
// Look for any Display Log console that the user may
// have opened which would be
// separate and make sure it is removed as well
RunConsole rc2 = RunConsole
.findConsole(((DockerConnection) connection)
.getContainer(containerId));
if (rc2 != null)
RunConsole.removeConsole(rc2);
((DockerConnection) connection)
.removeContainer(containerId);
}
}
} catch (final DockerException e2) {
// error in creation, try and remove Container if possible
if (!keepContainer && containerId != null) {
try {
((DockerConnection) connection)
.removeContainer(containerId);
} catch (DockerException | InterruptedException e1) {
// ignore exception
}
}
Display.getDefault().syncExec(() -> MessageDialog.openError(
PlatformUI.getWorkbench().getActiveWorkbenchWindow()
.getShell(),
DVMessages.getFormattedString(ERROR_CREATING_CONTAINER,
imageName),
e2.getMessage()));
} catch (InterruptedException e3) {
// for now
// do nothing
}
((DockerConnection) connection).getContainers(true);
});
t.start();
}
private class ID {
private Integer uid;
private Integer gid;
public ID(Integer uid, Integer gid) {
this.uid = uid;
this.gid = gid;
}
public Integer getuid() {
return uid;
}
public Integer getgid() {
return gid;
}
}
/**
* Fetch directories from Container and place them in a specified location.
*
* @param connectionUri
* - uri of connection to use
* @param imageName
* - name of image to use
* @param containerDirs
* - list of directories to copy
* @param hostDir
* - host directory to copy directories to
* @return 0 if successful, -1 if failure occurred
*
* @since 3.0
*/
public int fetchContainerDirs(String connectionUri, String imageName,
List<String> containerDirs, IPath hostDir) {
// Try and use the specified connection that was used before,
// otherwise, open an error
final IDockerConnection connection = DockerConnectionManager
.getInstance().getConnectionByUri(connectionUri);
if (connection == null) {
Display.getDefault()
.syncExec(() -> MessageDialog.openError(
PlatformUI.getWorkbench().getActiveWorkbenchWindow()
.getShell(),
DVMessages.getString(ERROR_LAUNCHING_CONTAINER),
DVMessages.getFormattedString(
ERROR_NO_CONNECTION_WITH_URI,
connectionUri)));
return -1;
}
CopyVolumesFromImageJob job = new CopyVolumesFromImageJob(connection,
imageName, containerDirs, new ArrayList<String>(), hostDir);
job.schedule();
return 0;
}
/**
* Fetch directories from Container and place them in a specified location.
*
* @param connectionUri
* - uri of connection to use
* @param imageName
* - name of image to use
* @param containerDirs
* - list of directories to copy
* @param excludedDirs
* - list of directories not to copy
* @param hostDir
* - host directory to copy directories to
* @return 0 if successful, -1 if failure occurred
*
* @since 4.0
*/
public int fetchContainerDirs(String connectionUri, String imageName,
List<String> containerDirs, List<String> excludedDirs, IPath hostDir) {
// Try and use the specified connection that was used before,
// otherwise, open an error
final IDockerConnection connection = DockerConnectionManager
.getInstance().getConnectionByUri(connectionUri);
if (connection == null) {
Display.getDefault()
.syncExec(() -> MessageDialog.openError(
PlatformUI.getWorkbench().getActiveWorkbenchWindow()
.getShell(),
DVMessages.getString(ERROR_LAUNCHING_CONTAINER),
DVMessages.getFormattedString(
ERROR_NO_CONNECTION_WITH_URI,
connectionUri)));
return -1;
}
CopyVolumesFromImageJob job = new CopyVolumesFromImageJob(connection,
imageName, containerDirs, excludedDirs, hostDir);
job.schedule();
return 0;
}
/**
* Fetch directories from Container and place them in a specified location.
* This method will wait for copy job to complete before returning.
*
* @param connectionUri
* - uri of connection to use
* @param imageName
* - name of image to use
* @param containerDirs
* - list of directories to copy
* @param hostDir
* - host directory to copy directories to
* @return 0 if successful, -1 if failure occurred
*
* @since 4.0
*/
public int fetchContainerDirsSync(String connectionUri, String imageName,
List<String> containerDirs, IPath hostDir) {
// Try and use the specified connection that was used before,
// otherwise, open an error
final IDockerConnection connection = DockerConnectionManager
.getInstance().getConnectionByUri(connectionUri);
if (connection == null) {
Display.getDefault()
.syncExec(() -> MessageDialog.openError(
PlatformUI.getWorkbench().getActiveWorkbenchWindow()
.getShell(),
DVMessages.getString(ERROR_LAUNCHING_CONTAINER),
DVMessages.getFormattedString(
ERROR_NO_CONNECTION_WITH_URI,
connectionUri)));
return -1;
}
CopyVolumesFromImageJob job = new CopyVolumesFromImageJob(connection,
imageName, containerDirs, new ArrayList<String>(), hostDir);
job.schedule();
try {
job.join();
} catch (InterruptedException e) {
return -1;
}
return 0;
}
/**
* Fetch directories from Container and place them in a specified location.
* This method will wait for copy job to complete before returning.
*
* @param connectionUri
* - uri of connection to use
* @param imageName
* - name of image to use
* @param containerDirs
* - list of directories to copy
* @param hostDir
* - host directory to copy directories to
* @return 0 if successful, -1 if failure occurred
*
* @since 4.0
*/
public int fetchContainerDirsSync(String connectionUri, String imageName,
List<String> containerDirs, List<String> excludedDirs,
IPath hostDir) {
// Try and use the specified connection that was used before,
// otherwise, open an error
final IDockerConnection connection = DockerConnectionManager
.getInstance().getConnectionByUri(connectionUri);
if (connection == null) {
Display.getDefault()
.syncExec(() -> MessageDialog.openError(
PlatformUI.getWorkbench().getActiveWorkbenchWindow()
.getShell(),
DVMessages.getString(ERROR_LAUNCHING_CONTAINER),
DVMessages.getFormattedString(
ERROR_NO_CONNECTION_WITH_URI,
connectionUri)));
return -1;
}
CopyVolumesFromImageJob job = new CopyVolumesFromImageJob(connection,
imageName, containerDirs, excludedDirs, hostDir);
job.schedule();
try {
job.join();
} catch (InterruptedException e) {
return -1;
}
return 0;
}
/**
* Create a Process to run an arbitrary command in a Container with uid of
* caller so any files created are accessible to user.
*
* @param connectionName
* - uri of connection to use
* @param imageName
* - name of image to use
* @param project
* - Eclipse project
* @param errMsgHolder
* - holder for any error messages
* @param command
* - command to run
* @param commandDir
* - directory path to command (unused)
* @param workingDir
* - where to run command
* @param additionalDirs
* - additional directories to mount
* @param origEnv
* - original environment if we are appending to existing
* @param envMap
* - new environment
* @param supportStdin
* - support using stdin
* @param privilegedMode
* - run in privileged mode
* @param labels
* - labels to apply to Container
* @param keepContainer
* - boolean whether to keep Container when done
* @return Process that can be used to check for completion and for routing
* stdout/stderr
*
* @since 3.0
*/
public Process runCommand(String connectionName, String imageName, IProject project,
IErrorMessageHolder errMsgHolder, String command,
@SuppressWarnings("unused") String commandDir,
String workingDir,
List<String> additionalDirs, Map<String, String> origEnv,
Properties envMap, boolean supportStdin,
boolean privilegedMode, HashMap<String, String> labels,
boolean keepContainer) {
final List<String> cmdList = getCmdList(command);
return runCommand(connectionName, imageName, project, errMsgHolder,
cmdList, workingDir, additionalDirs, origEnv, envMap,
supportStdin, privilegedMode, labels, keepContainer);
}
/**
* Create a Process to run an arbitrary command in a Container with uid of
* caller so any files created are accessible to user.
*
* @param connectionName
* - uri of connection to use
* @param imageName
* - name of image to use
* @param project
* - Eclipse project
* @param errMsgHolder
* - holder for any error messages
* @param cmdList
* - command to run as list of String
* @param workingDir
* - where to run command
* @param additionalDirs
* - additional directories to mount
* @param origEnv
* - original environment if we are appending to existing
* @param envMap
* - new environment
* @param supportStdin
* - support using stdin
* @param privilegedMode
* - run in privileged mode
* @param labels
* - labels to apply to Container
* @param keepContainer
* - boolean whether to keep Container when done
* @return Process that can be used to check for completion and for routing
* stdout/stderr
*
* @since 4.0
*/
public Process runCommand(String connectionName, String imageName,
IProject project, IErrorMessageHolder errMsgHolder,
List<String> cmdList,
String workingDir, List<String> additionalDirs,
Map<String, String> origEnv, Properties envMap,
boolean supportStdin, boolean privilegedMode,
HashMap<String, String> labels, boolean keepContainer) {
Integer uid = null;
Integer gid = null;
// For Unix, make sure that the user id is passed with the run
// so any output files are accessible by this end-user
String os = System.getProperty("os.name"); //$NON-NLS-1$
if (os.indexOf("nux") > 0) { //$NON-NLS-1$
// first try and see if we have already run a command on this
// project
ID ugid = fidMap.get(project);
if (ugid == null) {
try {
uid = (Integer) Files.getAttribute(
project.getLocation().toFile().toPath(),
"unix:uid"); //$NON-NLS-1$
gid = (Integer) Files.getAttribute(
project.getLocation().toFile().toPath(),
"unix:gid"); //$NON-NLS-1$
ugid = new ID(uid, gid);
// store the uid for possible later usage
fidMap.put(project, ugid);
} catch (IOException e) {
// do nothing...leave as null
} // $NON-NLS-1$
} else {
uid = ugid.getuid();
gid = ugid.getgid();
}
}
final List<String> env = new ArrayList<>();
env.addAll(toList(origEnv));
env.addAll(toList(envMap));
final Map<String, List<IDockerPortBinding>> portBindingsMap = new HashMap<>();
IDockerConnection[] connections = DockerConnectionManager
.getInstance().getConnections();
if (connections == null || connections.length == 0) {
errMsgHolder.setErrorMessage(
Messages.getString("ContainerLaunch.noConnections.error")); //$NON-NLS-1$
return null;
}
IDockerConnection connection = null;
for (IDockerConnection c : connections) {
if (c.getUri().equals(connectionName)) {
connection = c;
break;
}
}
if (connection == null) {
errMsgHolder.setErrorMessage(Messages.getFormattedString(
"ContainerLaunch.connectionNotFound.error", //$NON-NLS-1$
connectionName));
return null;
}
List<IDockerImage> images = connection.getImages();
if (images.isEmpty()) {
errMsgHolder.setErrorMessage(
Messages.getString("ContainerLaunch.noImages.error")); //$NON-NLS-1$
return null;
}
IDockerImageInfo info = connection.getImageInfo(imageName);
if (info == null) {
errMsgHolder.setErrorMessage(Messages.getFormattedString(
"ContainerLaunch.imageNotFound.error", imageName)); //$NON-NLS-1$
return null;
}
DockerContainerConfig.Builder builder = new DockerContainerConfig.Builder()
.openStdin(supportStdin).cmd(cmdList).image(imageName)
.workingDir(workingDir);
// preserve any entry point specified in the image
if (info.containerConfig() != null) {
List<String> entrypoint = info.containerConfig().entrypoint();
if (entrypoint != null && !entrypoint.isEmpty()) {
builder = builder.entryPoint(entrypoint);
}
}
// switch to user id and group id for Linux so output is accessible
if (uid != null) {
String id = uid.toString();
if (gid != null)
id += ":" + gid.toString(); //$NON-NLS-1$
builder = builder.user(id);
}
// TODO: add group id here when supported by DockerHostConfig.Builder
// add any labels if specified
if (labels != null)
builder = builder.labels(labels);
DockerHostConfig.Builder hostBuilder = new DockerHostConfig.Builder()
.privileged(privilegedMode);
// Note we only pass volumes to the config if we have a
// remote daemon. Local mounted volumes are passed
// via the HostConfig binds setting
@SuppressWarnings("rawtypes")
final Map<String, Map> remoteVolumes = new HashMap<>();
final Map<String, String> remoteDataVolumes = new HashMap<>();
final Set<String> readOnlyVolumes = new TreeSet<>();
if (!((DockerConnection) connection).isLocal()) {
// if using remote daemon, we have to
// handle volume mounting differently.
// Instead we mount empty volumes and copy
// the host data over before starting.
if (additionalDirs != null) {
for (String dir : additionalDirs) {
IPath p = new Path(dir).removeTrailingSeparator();
remoteVolumes.put(p.toPortableString(), new HashMap<>());
remoteDataVolumes.put(p.toPortableString(),
p.toPortableString());
if (dir.contains(":")) { //$NON-NLS-1$
DataVolumeModel dvm = DataVolumeModel.parseString(dir);
switch (dvm.getMountType()) {
case HOST_FILE_SYSTEM:
dir = dvm.getHostPathMount();
remoteDataVolumes.put(dir, dvm.getContainerMount());
// keep track of read-only volumes so we don't copy
// these
// back after command completion
if (dvm.isReadOnly()) {
readOnlyVolumes.add(dir);
}
break;
default:
continue;
}
}
}
}
if (workingDir != null) {
IPath p = new Path(workingDir).removeTrailingSeparator();
remoteVolumes.put(p.toPortableString(), new HashMap<>());
remoteDataVolumes.put(p.toPortableString(),
p.toPortableString());
}
// Check volumes known by the connection to see if user has specified a
// volume_name:container_dir as an additional dir. In such a case, we
// don't need to copy data over and can simply bind to the volume.
try {
final List<String> volumes = new ArrayList<>();
List<IDockerVolume> volumeList = ((DockerConnection) connection).getVolumes();
for (IDockerVolume volume : volumeList) {
String name = volume.name();
String containerDir = remoteDataVolumes.get(name);
if (containerDir != null) {
if (readOnlyVolumes.contains(name)) {
volumes.add(name + ":" + containerDir + ":Z,ro"); //$NON-NLS-1$ //$NON-NLS-2$
readOnlyVolumes.remove(name);
} else {
volumes.add(name + ":" + containerDir + ":Z"); //$NON-NLS-1$ //$NON-NLS-2$
}
remoteVolumes.remove(name);
remoteDataVolumes.remove(name);
IPath p = new Path(containerDir);
// if the working dir is a subdirectory of the volume mount, we don't need to
// copy it
// forward or back
if (workingDir != null) {
IPath working = new Path(workingDir);
if (p.isPrefixOf(working)) {
String s = working.removeTrailingSeparator().toPortableString();
remoteVolumes.remove(s);
remoteDataVolumes.remove(s);
}
}
}
}
// bind any volumes we found above
if (!volumes.isEmpty()) {
hostBuilder = hostBuilder.binds(volumes);
}
} catch (DockerException e) {
Activator.log(e);
}
if (!remoteVolumes.isEmpty()) {
builder = builder.volumes(remoteVolumes);
}
} else {
// Running daemon on local host.
// Add mounts for any directories we need to run the executable.
// When we add mount points, we need entries of the form:
// hostname:mountname:Z.
// In our case, we want all directories mounted as-is so the
// executable will run as the user expects.
final Set<String> volumes = new TreeSet<>();
final List<String> volumesFrom = new ArrayList<>();
if (additionalDirs != null) {
for (String dir : additionalDirs) {
IPath p = new Path(dir).removeTrailingSeparator();
if (dir.contains(":")) { //$NON-NLS-1$
DataVolumeModel dvm = DataVolumeModel.parseString(dir);
switch (dvm.getMountType()) {
case HOST_FILE_SYSTEM:
String bind = LaunchConfigurationUtils
.convertToUnixPath(dvm.getHostPathMount())
+ ':' + dvm.getContainerPath() + ":Z"; //$NON-NLS-1$ //$NON-NLS-2$
if (dvm.isReadOnly()) {
bind += ",ro"; //$NON-NLS-1$
}
volumes.add(bind);
break;
case CONTAINER:
volumesFrom.add(dvm.getContainerMount());
break;
default:
break;
}
} else {
volumes.add(p.toPortableString() + ":" //$NON-NLS-1$
+ p.toPortableString() + ":Z"); //$NON-NLS-1$
}
}
}
if (workingDir != null) {
IPath p = new Path(workingDir).removeTrailingSeparator();
volumes.add(p.toPortableString() + ":" + p.toPortableString() //$NON-NLS-1$
+ ":Z"); //$NON-NLS-1$
}
List<String> volumeList = new ArrayList<>(volumes);
hostBuilder = hostBuilder.binds(volumeList);
if (!volumesFrom.isEmpty()) {
hostBuilder = hostBuilder.volumesFrom(volumesFrom);
}
}
final DockerContainerConfig config = builder.build();
// add any port bindings if specified
if (portBindingsMap.size() > 0)
hostBuilder = hostBuilder.portBindings(portBindingsMap);
final IDockerHostConfig hostConfig = hostBuilder.build();
// create the container
String containerId = null;
try {
containerId = ((DockerConnection) connection)
.createContainer(config, hostConfig, null);
// Add delay after creating container to fix bug 546505
Thread.sleep(100);
} catch (DockerException | InterruptedException e) {
errMsgHolder.setErrorMessage(e.getMessage());
return null;
}
final String id = containerId;
final IDockerConnection conn = connection;
if (!((DockerConnection) conn).isLocal()) {
// if daemon is remote, we need to copy
// data over from the host.
if (!remoteVolumes.isEmpty()) {
CopyVolumesJob job = new CopyVolumesJob(remoteDataVolumes, conn,
id);
job.schedule();
try {
job.join();
} catch (InterruptedException e) {
// ignore
}
}
}
// remove all read-only remote volumes from our list of remote
// volumes so they won't be copied back on command completion
for (String readonly : readOnlyVolumes) {
remoteDataVolumes.remove(readonly);
}
return new ContainerCommandProcess(connection, imageName, containerId,
null,
remoteDataVolumes,
keepContainer);
}
/**
* Clean up the container used for launching
*
* @param connectionUri
* the URI of the connection used
* @param info
* the container info
*/
public void cleanup(String connectionUri, IDockerContainerInfo info) {
if (!DockerConnectionManager.getInstance().hasConnections()) {
return;
}
// Try and find the specified connection
final IDockerConnection connection = DockerConnectionManager
.getInstance().getConnectionByUri(connectionUri);
if (connection == null) {
return;
}
try {
connection.killContainer(info.id());
} catch (DockerException | InterruptedException e) {
// do nothing
}
}
/**
* Get the reusable run console for running C/C++ executables in containers.
*
* @return
*/
private RunConsole getConsole() {
return console;
}
private void setConsole(RunConsole cons) {
console = cons;
}
/**
* Take the command string and parse it into a list of strings.
*
* @param s
* @return list of strings
*/
private List<String> getCmdList(String s) {
ArrayList<String> list = new ArrayList<>();
int length = s.length();
boolean insideQuote1 = false; // single-quote
boolean insideQuote2 = false; // double-quote
boolean escaped = false;
StringBuilder buffer = new StringBuilder();
// Parse the string and break it up into chunks that are
// separated by white-space or are quoted. Ignore characters
// that have been escaped, including the escape character.
for (int i = 0; i < length; ++i) {
char c = s.charAt(i);
if (escaped) {
buffer.append(c);
escaped = false;
}
switch (c) {
case '\'':
if (!insideQuote2)
insideQuote1 = insideQuote1 ^ true;
else
buffer.append(c);
break;
case '\"':
if (!insideQuote1)
insideQuote2 = insideQuote2 ^ true;
else
buffer.append(c);
break;
case '\\':
escaped = true;
break;
case ' ':
case '\t':
case '\r':
case '\n':
if (insideQuote1 || insideQuote2)
buffer.append(c);
else {
String item = buffer.toString();
buffer.setLength(0);
if (item.length() > 0)
list.add(item);
}
break;
default:
buffer.append(c);
break;
}
}
// add last item of string that will be in the buffer
String item = buffer.toString();
if (item.length() > 0)
list.add(item);
return list;
}
/**
* Convert map of environment variables to a {@link List} of KEY=VALUE
* String
*
* @param variables
* the entries to manipulate
* @return the concatenated key/values for each given variable entry
*/
private List<String> toList(
@SuppressWarnings("rawtypes") final Map variables) {
final List<String> result = new ArrayList<>();
if (variables != null) {
@SuppressWarnings({ "unchecked", "rawtypes" })
Set<Map.Entry> entries = variables.entrySet();
for (@SuppressWarnings("rawtypes")
Map.Entry entry : entries) {
final String key = (String) entry.getKey();
final String value = (String) entry.getValue();
final String envEntry = key + "=" + value; //$NON-NLS-1$
result.add(envEntry);
}
}
return result;
}
/**
* Get set of volumes that have been copied from Container to Host as part
* of fetchContainerDirs method.
*
* @param connectionName
* - uri of connection used
* @param imageName
* - name of image used
* @return set of paths copied from Container to Host
*
* @since 3.0
*/
public Set<String> getCopiedVolumes(String connectionName,
String imageName) {
Set<String> copiedSet = new HashSet<>();
if (copiedVolumesMap != null) {
Map<String, Set<String>> connectionMap = copiedVolumesMap
.get(connectionName);
if (connectionMap != null) {
Set<String> imageSet = connectionMap.get(imageName);
if (imageSet != null) {
copiedSet = imageSet;
}
}
}
return copiedSet;
}
}