blob: 1b332133c97679569ee9fffc9a805e7c4b8eddf4 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2015 Red Hat.
* 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 static org.eclipse.linuxtools.docker.core.EnumDockerConnectionSettings.BINDING_MODE;
import static org.eclipse.linuxtools.docker.core.EnumDockerConnectionSettings.TCP_CERT_PATH;
import static org.eclipse.linuxtools.docker.core.EnumDockerConnectionSettings.TCP_HOST;
import static org.eclipse.linuxtools.docker.core.EnumDockerConnectionSettings.TCP_TLS_VERIFY;
import static org.eclipse.linuxtools.docker.core.EnumDockerConnectionSettings.UNIX_SOCKET;
import static org.eclipse.linuxtools.docker.core.EnumDockerConnectionSettings.UNIX_SOCKET_PATH;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Platform;
import org.eclipse.core.runtime.Status;
import org.eclipse.linuxtools.docker.core.Activator;
import org.eclipse.linuxtools.docker.core.DockerException;
import org.eclipse.linuxtools.docker.core.EnumDockerConnectionSettings;
import org.eclipse.linuxtools.docker.core.IDockerConnection;
import org.eclipse.linuxtools.docker.core.IDockerConnectionInfo;
import org.eclipse.linuxtools.docker.core.IDockerConnectionSettings;
import org.eclipse.linuxtools.docker.core.IDockerConnectionSettingsFinder;
import org.eclipse.linuxtools.docker.core.Messages;
import com.spotify.docker.client.DockerCertificateException;
import com.spotify.docker.client.DockerClient;
import jnr.unixsocket.UnixSocketAddress;
import jnr.unixsocket.UnixSocketChannel;
/**
* A utility class that looks for candidate {@link IDockerConnection}s on the
* host system.
*/
public class DefaultDockerConnectionSettingsFinder
implements IDockerConnectionSettingsFinder {
public static final String DOCKER_CERT_PATH = "DOCKER_CERT_PATH"; //$NON-NLS-1$
public static final String DOCKER_TLS_VERIFY = "DOCKER_TLS_VERIFY"; //$NON-NLS-1$
public static final String DOCKER_TLS_VERIFY_TRUE = "1"; //$NON-NLS-1$
public static final String DOCKER_HOST = "DOCKER_HOST"; //$NON-NLS-1$
@Override
@Deprecated
public List<IDockerConnectionSettings> findConnectionSettings() {
final List<IDockerConnectionSettings> availableConnectionSettings = new ArrayList<>();
final IDockerConnectionSettings defaultsWithUnixSocket = defaultsWithUnixSocket();
if (defaultsWithUnixSocket != null) {
availableConnectionSettings.add(defaultsWithUnixSocket);
}
final IDockerConnectionSettings defaultsWithSystemEnv = defaultsWithSystemEnv();
if (defaultsWithSystemEnv != null) {
availableConnectionSettings.add(defaultsWithSystemEnv);
}
final IDockerConnectionSettings defaultsWithShellEnv = defaultsWithShellEnv();
if (defaultsWithShellEnv != null) {
availableConnectionSettings.add(defaultsWithShellEnv);
}
// now that we have connection settings, let's ping them and retrieve the connection info.
for (IDockerConnectionSettings connectionSettings : availableConnectionSettings) {
switch(connectionSettings.getType()) {
case UNIX_SOCKET_CONNECTION:
final UnixSocketConnectionSettings unixSocketConnectionSettings = (UnixSocketConnectionSettings) connectionSettings;
final DockerConnection unixSocketConnection = new DockerConnection.Builder()
.unixSocketConnection(unixSocketConnectionSettings);
resolveDockerName(unixSocketConnectionSettings,
unixSocketConnection);
break;
case TCP_CONNECTION:
final TCPConnectionSettings tcpConnectionSettings = (TCPConnectionSettings) connectionSettings;
final DockerConnection tcpConnection = new DockerConnection.Builder()
.tcpConnection(tcpConnectionSettings);
resolveDockerName(tcpConnectionSettings, tcpConnection);
break;
}
}
return availableConnectionSettings;
}
@Override
public IDockerConnectionSettings findDefaultConnectionSettings() {
final IDockerConnectionSettings defaultsWithUnixSocket = defaultsWithUnixSocket();
if (defaultsWithUnixSocket != null) {
return defaultsWithUnixSocket;
}
final IDockerConnectionSettings defaultsWithSystemEnv = defaultsWithSystemEnv();
if (defaultsWithSystemEnv != null) {
return defaultsWithSystemEnv;
}
final IDockerConnectionSettings defaultsWithShellEnv = defaultsWithShellEnv();
if (defaultsWithShellEnv != null) {
return defaultsWithShellEnv;
}
return null;
}
private void resolveDockerName(
final BaseConnectionSettings connectionSettings,
final DockerConnection connection) {
try {
connection.open(false);
final IDockerConnectionInfo info = connection.getInfo();
if (info != null) {
connection.setName(info.getName());
connectionSettings.setSettingsResolved(true);
}
} catch (DockerException e) {
// ignore and keep 'settingsResolved' as false
connectionSettings.setSettingsResolved(false);
} finally {
connection.close();
}
}
@Override
public String resolveConnectionName(
final IDockerConnectionSettings connectionSettings) {
if (connectionSettings == null) {
return null;
}
try {
final DockerClient client = new DockerClientFactory()
.getClient(connectionSettings);
if (client != null) {
return client.info().name();
}
} catch (DockerCertificateException
| com.spotify.docker.client.DockerException
| InterruptedException e) {
// ignore and return null
}
return null;
}
/**
* Checks if there is a Unix socket available at the given location
*
* @return {@code IDockerConnectionSettings} if the Unix socket exists and
* is readable and writable, {@code null} otherwise.
*/
public IDockerConnectionSettings defaultsWithUnixSocket() {
final File unixSocketFile = new File("/var/run/docker.sock"); //$NON-NLS-1$
if (unixSocketFile.exists() && unixSocketFile.canRead()
&& unixSocketFile.canWrite()) {
final UnixSocketAddress address = new UnixSocketAddress(
unixSocketFile);
try (final UnixSocketChannel channel = UnixSocketChannel
.open(address)) {
// assume socket works
return new UnixSocketConnectionSettings(
unixSocketFile.getAbsolutePath());
} catch (IOException e) {
// do nothing, just assume socket did not work.
}
}
return null;
}
/**
* Checks if there are {@code DOCKER_xxx} environment variables in the
* current Eclipse process.
*
* @return {@code IDockerConnectionSettings} if the {@code DOCKER_xxx}
* environment variables exist, {@code null} otherwise.
*/
public IDockerConnectionSettings defaultsWithSystemEnv() {
final String dockerHostEnv = System.getenv(DOCKER_HOST);
if (dockerHostEnv != null) {
final String pathToCertificates = System.getenv(DOCKER_CERT_PATH);
return new TCPConnectionSettings(dockerHostEnv,
pathToCertificates);
}
return null;
}
/**
* Checks if there are {@code DOCKER_xxx} environment variables when running
* a script in a shell.
*
* @return {@code IDockerConnectionSettings} if the {@code DOCKER_xxx}
* environment variables exist, {@code null} otherwise.
*/
public IDockerConnectionSettings defaultsWithShellEnv() {
try {
final String connectionSettingsDetectionScriptName = getConnectionSettingsDetectionScriptName();
if (connectionSettingsDetectionScriptName == null) {
Activator.log(new Status(IStatus.ERROR, Activator.PLUGIN_ID,
Messages.Docker_No_Settings_Description_Script));
return null;
}
final File connectionSettingsDetectionScript = getConnectionSettingsDetectionScript(
connectionSettingsDetectionScriptName);
final String[] cmdArray = getConnectionSettingsDetectionCommandArray(
connectionSettingsDetectionScript);
final Process process = Runtime.getRuntime().exec(cmdArray);
process.waitFor();
final int exitValue = process.exitValue();
if (exitValue == 0) {
final InputStream processInputStream = process.getInputStream();
// read content from process input stream
final Properties dockerSettings = new Properties();
dockerSettings.load(processInputStream);
return createDockerConnectionSettings(dockerSettings);
} else {
// log what happened if the process did not end as expected
// an exit value of 1 should indicate no connection found
if (exitValue != 1) {
final InputStream processErrorStream = process
.getErrorStream();
final String errorMessage = streamToString(
processErrorStream);
Activator.log(new Status(IStatus.ERROR, Activator.PLUGIN_ID,
errorMessage));
}
}
} catch (IOException | IllegalArgumentException
| InterruptedException e) {
Activator.log(new Status(IStatus.ERROR, Activator.PLUGIN_ID,
Messages.Retrieve_Default_Settings_Failure, e));
}
return null;
}
/**
* Creates connection settings from the given {@code docerSettings}, or
* <code>null</code> if the settings did not contain a property with the
* {@code DOCKER_HOST} key.
*
* @param dockerSettings
* the connection settings
* @return the {@link IDockerConnectionSettings} or <code>null</code> if the
* settings are invalid.
*/
public IDockerConnectionSettings createDockerConnectionSettings(
final Properties dockerSettings) {
final Object dockerHostEnvVariable = dockerSettings.get(DOCKER_HOST);
final Object dockerCertPathEnvVariable = dockerSettings
.get(DOCKER_CERT_PATH);
// at least 'dockerHostEnvVariable' should be not null
if (dockerHostEnvVariable == null) {
return null;
}
return new TCPConnectionSettings(
dockerHostEnvVariable.toString(),
dockerCertPathEnvVariable != null
? dockerCertPathEnvVariable.toString() : null);
}
/**
* @param script
* the script to execute
* @return the OS-specific command to run the connection settings detection
* script or <code>null</code> if the current OS is not supported.
*/
private String[] getConnectionSettingsDetectionCommandArray(
final File script) {
if (Platform.getOS().equals(Platform.OS_WIN32)) {
return new String[] { "cmd.exe", "/C", //$NON-NLS-1$ //$NON-NLS-2$
script.getAbsolutePath() };
} else {
return new String[] { script.getAbsolutePath() };
}
}
/**
* Finds the script file in the data directory of the bundle given its
* name, or creates it from the 'resources' dir in the bundle if it was
* not found in the data dir.
*
* @param scriptName
* the name of the script to load in the data dir or in the
* 'resources' dir in the bundle
* @return the script {@link File}
*/
private File getConnectionSettingsDetectionScript(
final String scriptName) {
final File script = Activator.getDefault().getBundle()
.getDataFile(scriptName);
// if the script file does not exist or is outdated.
if (script != null
&& (!script.exists() || script.lastModified() < Activator
.getDefault().getBundle().getLastModified())) {
try (final FileOutputStream output = new FileOutputStream(
script);
final InputStream is = DockerConnection.class
.getResourceAsStream(
"/resources/" + scriptName)) { //$NON-NLS-1$
byte[] buff = new byte[1024];
int n;
while ((n = is.read(buff)) > 0) {
output.write(buff, 0, n);
}
script.setExecutable(true);
} catch (IOException e) {
Activator.logErrorMessage(e.getMessage());
}
}
return script;
}
/**
* @return the name of the script to run, depending on the OS (Windows, MAc,
* *Nix) or <code>null</code> if the current OS is not supported.
*/
private String getConnectionSettingsDetectionScriptName() {
if (SystemUtils.isLinux()) {
return "script.sh";//$NON-NLS-1$
} else if (SystemUtils.isMac()) {
return "script-macosx.sh";//$NON-NLS-1$
} else if (SystemUtils.isWindows()) {
return "script.bat"; //$NON-NLS-1$
}
return null;
}
private String streamToString(InputStream stream) {
BufferedReader buff = new BufferedReader(
new InputStreamReader(stream));
StringBuffer res = new StringBuffer();
String line = ""; //$NON-NLS-1$
try {
while ((line = buff.readLine()) != null) {
res.append(System.getProperty("line.separator")); //$NON-NLS-1$
res.append(line);
}
buff.close();
} catch (IOException e) {
}
return res.length() > 0 ? res.substring(1) : "";
}
public static class Defaults {
public static final String DEFAULT_UNIX_SOCKET_PATH = "unix:///var/run/docker.sock"; //$NON-NLS-1$
private boolean settingsResolved;
private String name = null;
private final Map<EnumDockerConnectionSettings, Object> settings = new HashMap<>();
public boolean isSettingsResolved() {
return settingsResolved;
}
public String getName() {
return name;
}
/**
* @return the default binding mode that was found, or UNIX_SOCKET if
* the property was not was not found.
*/
public EnumDockerConnectionSettings getBindingMode() {
if (settings.containsKey(BINDING_MODE)) {
return (EnumDockerConnectionSettings) settings
.get(BINDING_MODE);
}
return UNIX_SOCKET;
}
/**
* @return the path to the Unix socket, or {@code null} if if the
* property was not was not found.
*/
public String getUnixSocketPath() {
return (String) settings.get(UNIX_SOCKET_PATH);
}
/**
* @return the TCP host, or {@code null} if none was found.
*/
public String getTcpHost() {
return (String) settings.get(TCP_HOST);
}
/**
* @return the TLS_VERIFY {@link Boolean} flag, or {@code false} if the
* property was not was not found.
*/
public boolean getTcpTlsVerify() {
if (settings.containsKey(TCP_TLS_VERIFY)) {
return (Boolean) settings.get(TCP_TLS_VERIFY);
}
return false;
}
/**
* @return the path to the TCP certificates, or {@code null} if the
* property was not was found.
*/
public String getTcpCertPath() {
return (String) settings.get(TCP_CERT_PATH);
}
}
}