blob: 960ad04b7e70216b6a987438cb56d51e9e465735 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2014, 2018 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.internal.docker.ui.wizards;
import static org.eclipse.linuxtools.docker.core.EnumDockerConnectionSettings.UNIX_SOCKET;
import java.io.File;
import java.lang.reflect.InvocationTargetException;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.StringTokenizer;
import java.util.stream.Collectors;
import org.eclipse.core.databinding.DataBindingContext;
import org.eclipse.core.databinding.beans.BeanProperties;
import org.eclipse.core.databinding.observable.IChangeListener;
import org.eclipse.core.databinding.observable.IObservable;
import org.eclipse.core.databinding.observable.list.IObservableList;
import org.eclipse.core.databinding.observable.list.WritableList;
import org.eclipse.core.databinding.observable.value.IObservableValue;
import org.eclipse.core.databinding.observable.value.IValueChangeListener;
import org.eclipse.core.databinding.validation.MultiValidator;
import org.eclipse.core.databinding.validation.ValidationStatus;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.jface.databinding.swt.WidgetProperties;
import org.eclipse.jface.databinding.wizard.WizardPageSupport;
import org.eclipse.jface.dialogs.MessageDialog;
import org.eclipse.jface.layout.GridDataFactory;
import org.eclipse.jface.layout.GridLayoutFactory;
import org.eclipse.jface.viewers.ILabelProvider;
import org.eclipse.jface.viewers.ILabelProviderListener;
import org.eclipse.jface.viewers.IStructuredContentProvider;
import org.eclipse.jface.viewers.Viewer;
import org.eclipse.jface.wizard.WizardPage;
import org.eclipse.linuxtools.docker.core.DockerConnectionManager;
import org.eclipse.linuxtools.docker.core.DockerException;
import org.eclipse.linuxtools.docker.core.EnumDockerConnectionSettings;
import org.eclipse.linuxtools.docker.core.IDockerConnectionSettings;
import org.eclipse.linuxtools.docker.core.IDockerConnectionSettings.BindingType;
import org.eclipse.linuxtools.docker.ui.Activator;
import org.eclipse.linuxtools.internal.docker.core.DefaultDockerConnectionSettingsFinder;
import org.eclipse.linuxtools.internal.docker.core.DockerConnection;
import org.eclipse.linuxtools.internal.docker.core.DockerMachine;
import org.eclipse.linuxtools.internal.docker.core.TCPConnectionSettings;
import org.eclipse.linuxtools.internal.docker.core.UnixSocketConnectionSettings;
import org.eclipse.linuxtools.internal.docker.ui.SWTImagesFactory;
import org.eclipse.linuxtools.internal.docker.ui.preferences.PreferenceConstants;
import org.eclipse.swt.SWT;
import org.eclipse.swt.custom.ScrolledComposite;
import org.eclipse.swt.dnd.Clipboard;
import org.eclipse.swt.dnd.DND;
import org.eclipse.swt.dnd.TextTransfer;
import org.eclipse.swt.events.SelectionAdapter;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.events.SelectionListener;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.widgets.Button;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.DirectoryDialog;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.FileDialog;
import org.eclipse.swt.widgets.Group;
import org.eclipse.swt.widgets.Label;
import org.eclipse.swt.widgets.Text;
import org.eclipse.ui.PlatformUI;
import org.eclipse.ui.dialogs.ListDialog;
import org.eclipse.ui.dialogs.PreferencesUtil;
import com.spotify.docker.client.exceptions.DockerCertificateException;
/**
* {@link WizardPage} to input the settings to connect to a Docker
* engine/daemon.
*
*/
public class NewDockerConnectionPage extends WizardPage {
private static final String DOCKER_MACHINE_PREFERENCE_PAGE_ID = "org.eclipse.linuxtools.docker.ui.preferences.DockerMachinePreferencePage"; //$NON-NLS-1$
private final DataBindingContext dbc;
private final NewDockerConnectionPageModel model;
/**
* Constructor.
*/
public NewDockerConnectionPage() {
super("DockerConnectionPage", //$NON-NLS-1$
WizardMessages.getString("DockerConnectionPage.title"), //$NON-NLS-1$
SWTImagesFactory.DESC_BANNER_REPOSITORY);
setMessage(WizardMessages.getString("DockerConnectionPage.msg")); //$NON-NLS-1$
this.model = new NewDockerConnectionPageModel();
this.dbc = new DataBindingContext();
}
@Override
public void createControl(final Composite parent) {
final ScrolledComposite scrollTop = new ScrolledComposite(parent,
SWT.H_SCROLL | SWT.V_SCROLL);
scrollTop.setExpandVertical(true);
scrollTop.setExpandHorizontal(true);
final Composite container = new Composite(scrollTop, SWT.NONE);
GridLayoutFactory.fillDefaults().numColumns(1).applyTo(container);
GridDataFactory.fillDefaults().align(SWT.FILL, SWT.FILL)
.applyTo(container);
createConnectionSettingsContainer(container);
// attach the Databinding context status to this wizard page.
WizardPageSupport.create(this, this.dbc);
Clipboard clip = new Clipboard(Display.getCurrent());
String content = (String) clip.getContents(TextTransfer.getInstance(),
DND.SELECTION_CLIPBOARD);
// DOCKER_HOST is the minimal property needed
if (content != null && content
.contains(DefaultDockerConnectionSettingsFinder.DOCKER_HOST)) {
retrieveConnectionSettings(content);
} else {
content = (String) clip.getContents(TextTransfer.getInstance(),
DND.CLIPBOARD);
if (content != null && content.contains(
DefaultDockerConnectionSettingsFinder.DOCKER_HOST)) {
retrieveConnectionSettings(content);
} else {
retrieveDefaultConnectionSettings();
}
}
scrollTop.setContent(container);
Point point = container.computeSize(SWT.DEFAULT, SWT.DEFAULT);
scrollTop.setSize(point);
scrollTop.setMinSize(point);
setControl(container);
}
@Override
public void dispose() {
if (dbc != null) {
dbc.dispose();
}
super.dispose();
}
/**
* Creates the connection settings container, where the user can choose how
* to connect to the docker daemon (using sockets or TCP with SSL - or not)
*
* @param parent
* the parent container (ie, the main container in the preference
* page)
*/
private void createConnectionSettingsContainer(final Composite parent) {
final int COLUMNS = 3;
final int INDENT = 20;
final Composite container = new Composite(parent, SWT.NONE);
GridDataFactory.fillDefaults().align(SWT.FILL, SWT.FILL).span(1, 1)
.grab(true, false).applyTo(container);
GridLayoutFactory.fillDefaults().numColumns(COLUMNS).margins(6, 6)
.spacing(10, 2).applyTo(container);
// Connection name
final Label connectionNameLabel = new Label(container, SWT.NONE);
connectionNameLabel.setText(
WizardMessages.getString("DockerConnectionPage.nameLabel")); //$NON-NLS-1$
GridDataFactory.fillDefaults().align(SWT.FILL, SWT.CENTER)
.applyTo(connectionNameLabel);
final Text connectionNameText = new Text(container, SWT.BORDER);
connectionNameText.setToolTipText(
WizardMessages.getString("DockerConnectionPage.nameTooltip")); //$NON-NLS-1$
GridDataFactory.fillDefaults().align(SWT.FILL, SWT.CENTER)
.grab(true, false).span(1, 1).applyTo(connectionNameText);
// the 'Search' button
final Button searchButton = new Button(container, SWT.NONE);
searchButton.setText(
WizardMessages.getString("DockerConnectionPage.searchButton")); //$NON-NLS-1$
GridDataFactory.fillDefaults().align(SWT.FILL, SWT.CENTER).span(1, 1)
.align(SWT.BEGINNING, SWT.CENTER).grab(false, false)
.applyTo(searchButton);
searchButton.addSelectionListener(onSearchButtonSelection());
// custom settings checkbox
final Button customConnectionSettingsButton = new Button(container,
SWT.CHECK);
customConnectionSettingsButton.setText(
WizardMessages.getString("DockerConnectionPage.customLabel")); //$NON-NLS-1$
GridDataFactory.fillDefaults().align(SWT.FILL, SWT.CENTER)
.grab(true, false).indent(0, 10).span(COLUMNS, 1)
.applyTo(customConnectionSettingsButton);
final Group customSettingsGroup = new Group(container, SWT.NONE);
GridDataFactory.fillDefaults().align(SWT.FILL, SWT.FILL)
.span(COLUMNS, 1).grab(true, false)
.applyTo(customSettingsGroup);
GridLayoutFactory.fillDefaults().numColumns(COLUMNS).margins(6, 6)
.spacing(10, 2).applyTo(customSettingsGroup);
// Unix socket
final Button unixSocketBindingModeButton = new Button(
customSettingsGroup, SWT.RADIO);
unixSocketBindingModeButton.setText(
WizardMessages.getString("DockerConnectionPage.unixSocket")); //$NON-NLS-1$
GridDataFactory.fillDefaults().align(SWT.FILL, SWT.CENTER)
.span(COLUMNS, 1).applyTo(unixSocketBindingModeButton);
final Label unixSocketPathLabel = new Label(customSettingsGroup,
SWT.NONE);
unixSocketPathLabel.setText(
WizardMessages.getString("DockerConnectionPage.location")); //$NON-NLS-1$
GridDataFactory.fillDefaults().align(SWT.FILL, SWT.CENTER)
.indent(INDENT, 0).applyTo(unixSocketPathLabel);
final Text unixSocketPathText = new Text(customSettingsGroup,
SWT.BORDER);
unixSocketPathText.setToolTipText(WizardMessages
.getString("DockerConnectionPage.unixPathTooltip")); //$NON-NLS-1$
GridDataFactory.fillDefaults().align(SWT.FILL, SWT.CENTER)
.grab(true, false).applyTo(unixSocketPathText);
final Button unixSocketPathBrowseButton = new Button(
customSettingsGroup, SWT.BUTTON1);
unixSocketPathBrowseButton.setText(
WizardMessages.getString("DockerConnectionPage.browseButton")); //$NON-NLS-1$
unixSocketPathBrowseButton
.addSelectionListener(onBrowseUnixSocketPath());
GridDataFactory.fillDefaults().align(SWT.FILL, SWT.CENTER)
.applyTo(unixSocketPathBrowseButton);
// TCP connection
final Button tcpConnectionBindingModeButton = new Button(
customSettingsGroup, SWT.RADIO);
tcpConnectionBindingModeButton.setText(
WizardMessages.getString("DockerConnectionPage.tcpConnection")); //$NON-NLS-1$
GridDataFactory.fillDefaults().align(SWT.FILL, SWT.CENTER)
.span(COLUMNS, 1).applyTo(tcpConnectionBindingModeButton);
final Label tcpHostLabel = new Label(customSettingsGroup, SWT.NONE);
tcpHostLabel.setText(
WizardMessages.getString("DockerConnectionPage.hostLabel")); //$NON-NLS-1$
GridDataFactory.fillDefaults().align(SWT.FILL, SWT.CENTER)
.indent(INDENT, 0).applyTo(tcpHostLabel);
final Text tcpHostText = new Text(customSettingsGroup, SWT.BORDER);
GridDataFactory.fillDefaults().align(SWT.FILL, SWT.CENTER).span(2, 1)
.grab(true, false).applyTo(tcpHostText);
final Button tcpAuthButton = new Button(customSettingsGroup, SWT.CHECK);
tcpAuthButton.setText(
WizardMessages.getString("DockerConnectionPage.tcpAuthButton")); //$NON-NLS-1$
GridDataFactory.fillDefaults().align(SWT.FILL, SWT.CENTER)
.indent(INDENT, 0).span(3, 1).applyTo(tcpAuthButton);
final Label tcpCertPathLabel = new Label(customSettingsGroup, SWT.NONE);
tcpCertPathLabel.setText(
WizardMessages.getString("DockerConnectionPage.tcpPathLabel")); //$NON-NLS-1$
tcpCertPathLabel.setToolTipText(WizardMessages
.getString("DockerConnectionPage.tcpPathTooltip")); //$NON-NLS-1$
GridDataFactory.fillDefaults().align(SWT.FILL, SWT.CENTER)
.indent(INDENT * 2, 0).applyTo(tcpCertPathLabel);
final Text tcpCertPathText = new Text(customSettingsGroup, SWT.BORDER);
GridDataFactory.fillDefaults().align(SWT.FILL, SWT.CENTER)
.grab(true, false).applyTo(tcpCertPathText);
final Button tcpCertPathBrowseButton = new Button(customSettingsGroup,
SWT.BUTTON1);
tcpCertPathBrowseButton.setText(
WizardMessages.getString("DockerConnectionPage.browseButton")); //$NON-NLS-1$
tcpCertPathBrowseButton.addSelectionListener(onBrowseTcpCertPath());
GridDataFactory.fillDefaults().align(SWT.FILL, SWT.CENTER)
.applyTo(tcpCertPathBrowseButton);
// the 'test connection' button
final Button testConnectionButton = new Button(container, SWT.NONE);
testConnectionButton.setText(WizardMessages
.getString("DockerConnectionPage.testConnection")); //$NON-NLS-1$
GridDataFactory.fillDefaults().align(SWT.FILL, SWT.CENTER).span(3, 1)
.align(SWT.END, SWT.CENTER).applyTo(testConnectionButton);
testConnectionButton
.addSelectionListener(onTestConnectionButtonSelection());
// observe
@SuppressWarnings("unchecked")
final IObservableValue<String> connectionNameModelObservable = BeanProperties
.value(NewDockerConnectionPageModel.class,
NewDockerConnectionPageModel.CONNECTION_NAME)
.observe(model);
@SuppressWarnings("unchecked")
final IObservableValue<Boolean> unixSocketBindingModeModelObservable = BeanProperties
.value(NewDockerConnectionPageModel.class,
NewDockerConnectionPageModel.UNIX_SOCKET_BINDING_MODE)
.observe(model);
@SuppressWarnings("unchecked")
final IObservableValue<String> unixSocketPathModelObservable = BeanProperties
.value(NewDockerConnectionPageModel.class,
NewDockerConnectionPageModel.UNIX_SOCKET_PATH)
.observe(model);
@SuppressWarnings("unchecked")
final IObservableValue<Boolean> customConnectionSettingsModelObservable = BeanProperties
.value(NewDockerConnectionPageModel.class,
NewDockerConnectionPageModel.CUSTOM_SETTINGS)
.observe(model);
@SuppressWarnings("unchecked")
final IObservableValue<Boolean> tcpConnectionBindingModeModelObservable = BeanProperties
.value(NewDockerConnectionPageModel.class,
NewDockerConnectionPageModel.TCP_CONNECTION_BINDING_MODE)
.observe(model);
@SuppressWarnings("unchecked")
final IObservableValue<String> tcpCertPathModelObservable = BeanProperties
.value(NewDockerConnectionPageModel.class,
NewDockerConnectionPageModel.TCP_CERT_PATH)
.observe(model);
@SuppressWarnings("unchecked")
final IObservableValue<Boolean> tcpTlsVerifyModelObservable = BeanProperties
.value(NewDockerConnectionPageModel.class,
NewDockerConnectionPageModel.TCP_TLS_VERIFY)
.observe(model);
@SuppressWarnings("unchecked")
final IObservableValue<String> tcpHostModelObservable = BeanProperties
.value(NewDockerConnectionPageModel.class,
NewDockerConnectionPageModel.TCP_HOST)
.observe(model);
// group controls to easily enable/disable them
final Control[] bindingModeSelectionControls = new Control[] {
unixSocketBindingModeButton, tcpConnectionBindingModeButton };
final Control[] unixSocketControls = new Control[] { unixSocketPathText,
unixSocketPathLabel, unixSocketPathBrowseButton };
final Control[] tcpConnectionControls = new Control[] { tcpHostText,
tcpHostLabel, tcpAuthButton };
final Control[] tcpAuthControls = new Control[] { tcpCertPathText,
tcpCertPathLabel, tcpCertPathBrowseButton };
customConnectionSettingsModelObservable
.addValueChangeListener(onCustomConnectionSettingsSelection(
bindingModeSelectionControls, unixSocketControls,
tcpAuthControls, tcpConnectionControls));
unixSocketBindingModeModelObservable.addChangeListener(
onUnixSocketBindingSelection(unixSocketControls));
tcpConnectionBindingModeModelObservable.addChangeListener(
onTcpConnectionBindingSelection(tcpConnectionControls,
tcpAuthControls));
tcpTlsVerifyModelObservable
.addValueChangeListener(onTcpAuthSelection(tcpAuthControls));
// bind controls to model
dbc.bindValue(
WidgetProperties.text(SWT.Modify).observe(connectionNameText),
connectionNameModelObservable);
dbc.bindValue(
WidgetProperties.selection()
.observe(customConnectionSettingsButton),
customConnectionSettingsModelObservable);
dbc.bindValue(
WidgetProperties.selection()
.observe(unixSocketBindingModeButton),
unixSocketBindingModeModelObservable);
dbc.bindValue(
WidgetProperties.text(SWT.Modify).observe(unixSocketPathText),
unixSocketPathModelObservable);
dbc.bindValue(
WidgetProperties.selection()
.observe(tcpConnectionBindingModeButton),
tcpConnectionBindingModeModelObservable);
dbc.bindValue(WidgetProperties.text(SWT.Modify).observe(tcpHostText),
tcpHostModelObservable);
dbc.bindValue(WidgetProperties.selection().observe(tcpAuthButton),
tcpTlsVerifyModelObservable);
dbc.bindValue(
WidgetProperties.text(SWT.Modify).observe(tcpCertPathText),
tcpCertPathModelObservable);
// validations will be performed when the user changes the value
// only, not at the dialog opening
dbc.addValidationStatusProvider(
new UnixSocketValidator(connectionNameModelObservable,
unixSocketBindingModeModelObservable,
unixSocketPathModelObservable));
dbc.addValidationStatusProvider(
new TcpHostValidator(connectionNameModelObservable,
tcpConnectionBindingModeModelObservable,
tcpHostModelObservable));
dbc.addValidationStatusProvider(new TcpCertificatesValidator(
tcpConnectionBindingModeModelObservable,
tcpTlsVerifyModelObservable, tcpCertPathModelObservable));
// give focus to connectionName text at first
connectionNameText.setFocus();
// set widgets initial state
updateWidgetsState(bindingModeSelectionControls, unixSocketControls,
tcpConnectionControls, tcpAuthControls);
}
private SelectionListener onBrowseUnixSocketPath() {
return SelectionListener.widgetSelectedAdapter(e -> {
final FileDialog fileDialog = new FileDialog(getShell());
final String selectedPath = fileDialog.open();
if (selectedPath != null) {
model.setUnixSocketPath("unix://" + selectedPath); //$NON-NLS-1$
}
});
}
private SelectionListener onBrowseTcpCertPath() {
return SelectionListener.widgetSelectedAdapter(e -> {
final DirectoryDialog directoryDialog = new DirectoryDialog(
getShell());
directoryDialog.setFilterPath(model.getTcpCertPath());
final String selectedPath = directoryDialog.open();
if (selectedPath != null) {
model.setTcpCertPath(selectedPath);
}
});
}
/**
* Sets the default settings by looking for the:
* <ul>
* <li>a Unix socket at /var/run/docker.sock</li>
* <li>the following environment variables:
* <ul>
* <li>DOCKER_HOST</li>
* <li>DOCKER_CERT_PATH</li>
* <li>DOCKER_TLS_VERIFY</li>
* </ul>
* </li>
* </ul>
* and sets the default connection settings accordingly.
*/
private void retrieveDefaultConnectionSettings() {
// let's run this in a job and show the progress in the wizard
// progressbar
try {
getWizard().getContainer().run(true, true, monitor -> {
monitor.beginTask(
WizardMessages
.getString("DockerConnectionPage.retrieveTask"), //$NON-NLS-1$
1);
final DockerConnectionManager dockerConnectionManager = DockerConnectionManager
.getInstance();
final IDockerConnectionSettings defaultConnectionSettings = dockerConnectionManager
.findDefaultConnectionSettings();
if (defaultConnectionSettings != null) {
model.setCustomSettings(
!defaultConnectionSettings.isSettingsResolved());
model.setConnectionName(dockerConnectionManager
.resolveConnectionName(defaultConnectionSettings));
switch (defaultConnectionSettings.getType()) {
case TCP_CONNECTION:
final TCPConnectionSettings tcpConnectionSettings = (TCPConnectionSettings) defaultConnectionSettings;
model.setTcpConnectionBindingMode(true);
model.setTcpCertPath(
tcpConnectionSettings.getPathToCertificates());
model.setTcpTLSVerify(
tcpConnectionSettings.isTlsVerify());
model.setTcpHost(tcpConnectionSettings.getHost());
break;
case UNIX_SOCKET_CONNECTION:
model.setUnixSocketBindingMode(true);
final UnixSocketConnectionSettings unixSocketConnectionSettings = (UnixSocketConnectionSettings) defaultConnectionSettings;
model.setUnixSocketPath(
unixSocketConnectionSettings.getPath());
break;
}
} else {
// fall-back to custom settings, suggesting a
// Unix Socket connection to the user.
model.setCustomSettings(true);
model.setUnixSocketBindingMode(true);
}
monitor.done();
});
} catch (InvocationTargetException | InterruptedException e) {
Activator.log(e);
}
}
private void retrieveConnectionSettings(String content) {
final String EQUAL = "="; //$NON-NLS-1$
StringTokenizer tok = new StringTokenizer(content);
while (tok.hasMoreTokens()) {
String line = tok.nextToken();
String[] tokens;
if (line.startsWith(
DefaultDockerConnectionSettingsFinder.DOCKER_HOST)) {
tokens = line.split(EQUAL);
if (tokens.length == 2) {
String host = tokens[1].replaceAll("\"", ""); //$NON-NLS-1$ //$NON-NLS-2$
if (host.startsWith("unix")) { //$NON-NLS-1$
model.setUnixSocketBindingMode(true);
model.setUnixSocketPath(host);
} else {
model.setTcpConnectionBindingMode(true);
model.setTcpHost(host);
}
model.setCustomSettings(true);
}
} else if (line.startsWith(
DefaultDockerConnectionSettingsFinder.DOCKER_CERT_PATH)) {
tokens = line.split(EQUAL);
if (tokens.length == 2) {
model.setTcpCertPath(tokens[1].replaceAll("\"", "")); //$NON-NLS-1$ //$NON-NLS-2$
}
} else if (line.startsWith(
DefaultDockerConnectionSettingsFinder.DOCKER_TLS_VERIFY)) {
tokens = line.split(EQUAL);
if (tokens.length == 2) {
model.setTcpTLSVerify(
DefaultDockerConnectionSettingsFinder.DOCKER_TLS_VERIFY_TRUE
.equals(tokens[1].replaceAll("\"", ""))); //$NON-NLS-1$ //$NON-NLS-2$
}
}
}
}
private void updateWidgetsState(
final Control[] bindingModeSelectionControls,
final Control[] unixSocketControls,
final Control[] tcpConnectionControls,
final Control[] tcpAuthControls) {
setWidgetsEnabled(model.isCustomSettings()
&& model.isTcpConnectionBindingMode() && model.isTcpTLSVerify(),
tcpAuthControls);
setWidgetsEnabled(
model.isCustomSettings() && model.isTcpConnectionBindingMode(),
tcpConnectionControls);
setWidgetsEnabled(
model.isCustomSettings() && model.isUnixSocketBindingMode(),
unixSocketControls);
setWidgetsEnabled(model.isCustomSettings(),
bindingModeSelectionControls);
}
private IValueChangeListener<Boolean> onCustomConnectionSettingsSelection(
final Control[] bindingModeSelectionControls,
final Control[] unixSocketControls, final Control[] tcpAuthControls,
final Control[] tcpConnectionControls) {
return event -> updateWidgetsState(bindingModeSelectionControls,
unixSocketControls, tcpConnectionControls, tcpAuthControls);
}
private IChangeListener onUnixSocketBindingSelection(
final Control[] unixSocketControls) {
return event -> setWidgetsEnabled(
model.isCustomSettings() && model.isUnixSocketBindingMode(),
unixSocketControls);
}
private IChangeListener onTcpConnectionBindingSelection(
final Control[] tcpConnectionControls,
final Control[] tcpAuthControls) {
return event -> {
setWidgetsEnabled(model.isCustomSettings()
&& model.isTcpConnectionBindingMode()
&& model.isTcpTLSVerify(), tcpAuthControls);
// and give focus to the first given control (if applicable)
setWidgetsEnabled(
model.isCustomSettings()
&& model.isTcpConnectionBindingMode(),
tcpConnectionControls);
};
}
private IValueChangeListener<Boolean> onTcpAuthSelection(
final Control[] tcpAuthControls) {
return event -> setWidgetsEnabled(model.isCustomSettings()
&& model.isTcpConnectionBindingMode() && model.isTcpTLSVerify(),
tcpAuthControls);
}
private void setWidgetsEnabled(final boolean enabled,
final Control... controls) {
for (Control control : controls) {
control.setEnabled(enabled);
}
// set the focus on the fist element of the group.
if (enabled) {
for (Control control : controls) {
if (control instanceof Text) {
control.setFocus();
break;
}
}
}
}
/**
* Verifies that the given connection settings work by trying to connect to
* the target Docker daemon
*
* @return
*/
private SelectionListener onTestConnectionButtonSelection() {
return new SelectionAdapter() {
@Override
public void widgetSelected(final SelectionEvent event) {
try {
getWizard().getContainer().run(true, false, monitor -> {
monitor.beginTask(WizardMessages.getString(
"DockerConnectionPage.pingTask"), //$NON-NLS-1$
IProgressMonitor.UNKNOWN);
try (DockerConnection dockerConnection = getDockerConnection()) {
dockerConnection.open(false);
dockerConnection.ping();
// ping succeeded
displaySuccessDialog();
} catch (DockerException e) {
// only log if there's an underlying cause, but not
// as an error
// because there's no need to report such problems
// in Eclipse.org AERI
if (e.getCause() != null) {
displayErrorDialog(e.getCause());
} else {
displayErrorDialog(e);
}
}
});
} catch (InvocationTargetException | InterruptedException o_O) {
Activator.log(o_O);
}
}
private void displaySuccessDialog() {
displayDialog(
WizardMessages
.getString("DockerConnectionPage.success"), //$NON-NLS-1$
WizardMessages
.getString("DockerConnectionPage.pingSuccess"), //$NON-NLS-1$
SWT.ICON_INFORMATION, new String[] { WizardMessages
.getString("DockerConnectionPage.ok") } //$NON-NLS-1$
);
}
private void displayErrorDialog(final Throwable cause) {
displayDialog(
WizardMessages
.getString("DockerConnectionPage.failure"), //$NON-NLS-1$
WizardMessages
.getFormattedString(
"DockerConnectionPage.pingFailure", //$NON-NLS-1$
cause.getMessage()),
SWT.ICON_ERROR, new String[] { WizardMessages
.getString("DockerConnectionPage.ok") } //$NON-NLS-1$
);
}
private void displayDialog(final String dialogTitle,
final String dialogMessage, final int icon,
final String[] buttonLabels) {
Display.getDefault()
.syncExec(() -> new MessageDialog(
PlatformUI.getWorkbench()
.getActiveWorkbenchWindow().getShell(),
dialogTitle, null, dialogMessage, icon,
buttonLabels, 0).open());
}
};
}
private SelectionListener onSearchButtonSelection() {
return new SelectionAdapter() {
private String getVMDriverInstallDir() {
return Activator.getDefault().getPreferenceStore().getString(
PreferenceConstants.VM_DRIVER_INSTALLATION_DIRECTORY);
}
private String getDockerMachineInstallDir() {
return Activator.getDefault().getPreferenceStore().getString(
PreferenceConstants.DOCKER_MACHINE_INSTALLATION_DIRECTORY);
}
@Override
public void widgetSelected(SelectionEvent e) {
if (!checkDockerMachineInstallDirs()) {
return;
}
final String dockerMachineInstallDir = getDockerMachineInstallDir();
final String vmDriverInstallDir = getVMDriverInstallDir();
final Map<String, Boolean> allMachineStates = retrieveDockerMachineNames(
dockerMachineInstallDir, vmDriverInstallDir);
final List<String> activeMachineNames = allMachineStates
.entrySet().stream()
.filter((machineEntry) -> machineEntry.getValue()
.booleanValue())
.map((machineEntry) -> machineEntry.getKey())
.collect(Collectors.toList());
if (activeMachineNames.size() > 0) {
ListDialog connPrompt = new ListDialog(getShell());
connPrompt.setContentProvider(
new ConnectionSelectionContentProvider());
connPrompt.setLabelProvider(
new ConnectionSelectionLabelProvider());
connPrompt.setTitle(WizardMessages.getString(
"DockerConnectionPage.searchDialog.title")); //$NON-NLS-1$
connPrompt.setMessage(WizardMessages.getString(
"DockerConnectionPage.searchDialog.message")); //$NON-NLS-1$
connPrompt.setInput(
activeMachineNames.toArray(new String[0]));
if (connPrompt.open() == 0
&& connPrompt.getResult().length > 0) {
final String name = ((String) connPrompt
.getResult()[0]);
model.setBindingMode(
EnumDockerConnectionSettings.TCP_CONNECTION);
model.setConnectionName(name);
model.setUnixSocketPath(null);
try {
final String host = DockerMachine.getInstance()
.getHost(name, dockerMachineInstallDir,
vmDriverInstallDir);
model.setTcpHost(host);
} catch (DockerException e1) {
Activator.log(e1);
}
try {
final String certPath = DockerMachine.getInstance()
.getCertPath(name, dockerMachineInstallDir,
vmDriverInstallDir);
if (certPath != null) {
model.setTcpTLSVerify(true);
model.setTcpCertPath(certPath);
} else {
model.setTcpTLSVerify(false);
model.setTcpCertPath(null);
}
} catch (DockerException e1) {
Activator.log(e1);
}
}
} else {
if (allMachineStates.size() == 1) {
MessageDialog.openInformation(getShell(),
WizardMessages.getString(
"DockerConnectionPage.searchDialog.discovery.title"), //$NON-NLS-1$
WizardMessages.getFormattedString(
"DockerConnectionPage.searchDialog.discovery.innactive.single", //$NON-NLS-1$
allMachineStates.entrySet().iterator()
.next().getKey()));
} else if (allMachineStates.size() > 1) {
final String allMachineNames = allMachineStates
.entrySet().stream()
.map((machineEntry) -> machineEntry.getKey())
.collect(Collectors.joining(", "));
MessageDialog.openInformation(getShell(),
WizardMessages.getString(
"DockerConnectionPage.searchDialog.discovery.title"), //$NON-NLS-1$
WizardMessages.getFormattedString(
"DockerConnectionPage.searchDialog.discovery.innactive.multiple", //$NON-NLS-1$
allMachineNames));
} else {
MessageDialog.openInformation(getShell(),
WizardMessages.getString(
"DockerConnectionPage.searchDialog.discovery.title"), //$NON-NLS-1$
WizardMessages.getString(
"DockerConnectionPage.searchDialog.discovery.empty")); //$NON-NLS-1$
}
}
}
/**
* Verifies that the install dirs for Docker Machine and its driver
* are valid, otherwise offers to open the preference page to fix
* the settings.
*
* @return <code>true</code> if the settings are valid,
* <code>false</code> otherwise.
*/
private boolean checkDockerMachineInstallDirs() {
if (!validDockerMachineInstallDirs()) {
final boolean confirm = MessageDialog.openConfirm(
getShell(),
WizardMessages
.getString("DockerMachineSupport.title"), //$NON-NLS-1$
WizardMessages
.getString("DockerMachineSupport.message")); //$NON-NLS-1$
if (confirm) {
PreferencesUtil.createPreferenceDialogOn(getShell(),
DOCKER_MACHINE_PREFERENCE_PAGE_ID,
new String[] {
DOCKER_MACHINE_PREFERENCE_PAGE_ID },
null).open();
// after user input data, check again
if (!validDockerMachineInstallDirs()) {
MessageDialog.openError(getShell(),
WizardMessages.getString(
"DockerMachineSupport.title"), //$NON-NLS-1$
WizardMessages.getString(
"DockerMachineSupport.message.invalid")); //$NON-NLS-1$
return false;
}
} else {
return false;
}
}
return true;
}
/**
* @return <code>true</code> if the install dirs for Docker Machine
* and its drivers are valid, <code>false</code> otherwise.
*/
private boolean validDockerMachineInstallDirs() {
return DockerMachine.getInstance()
.checkPathToDockerMachine(getDockerMachineInstallDir())
&& !getVMDriverInstallDir().equals(""); //$NON-NLS-1$
}
/**
* Retrieves the docker machine names along with a boolean flag to
* indicate if it is running or not.
*
* @param dockerMachineInstallDir
* @param vmDriverInstallDir
* @return
*/
private Map<String, Boolean> retrieveDockerMachineNames(
final String dockerMachineInstallDir,
final String vmDriverInstallDir) {
try {
final String[] dmNames = DockerMachine.getInstance()
.getNames(dockerMachineInstallDir);
return java.util.stream.Stream.of(dmNames)
.collect(Collectors.toMap(name -> name, name -> {
try {
return DockerMachine.getInstance().getHost(
name, dockerMachineInstallDir,
vmDriverInstallDir) != null;
} catch (DockerException e) {
Activator.log(e);
return false;
}
}));
} catch (DockerException e) {
Activator.log(e);
return Collections.emptyMap();
}
}
};
}
/**
* Opens a new {@link DockerConnection} using the settings of this
* {@link NewDockerConnectionPage}.
*
* @return
* @throws DockerCertificateException
*/
protected DockerConnection getDockerConnection() {
if (model.getBindingMode() == UNIX_SOCKET) {
return new DockerConnection.Builder()
.name(model.getConnectionName())
.unixSocketConnection(new UnixSocketConnectionSettings(
model.getUnixSocketPath()));
} else {
return new DockerConnection.Builder()
.name(model.getConnectionName())
.tcpConnection(new TCPConnectionSettings(model.getTcpHost(),
model.getTcpCertPath()));
}
}
private static abstract class ConnectionNameValidator
extends MultiValidator {
private final IObservableValue<String> connectionNameModelObservable;
ConnectionNameValidator(
final IObservableValue<String> connectionNameModelObservable) {
this.connectionNameModelObservable = connectionNameModelObservable;
}
public IObservableValue<String> getConnectionNameModelObservable() {
return this.connectionNameModelObservable;
}
@Override
public IObservableList<IObservable> getTargets() {
IObservableList<IObservable> targets = new WritableList<>();
targets.add(connectionNameModelObservable);
return targets;
}
@Override
protected IStatus validate() {
final String connectionName = this.connectionNameModelObservable
.getValue();
if (connectionName == null || connectionName.isEmpty()) {
return ValidationStatus.error(WizardMessages.getString(
"DockerConnectionPage.validation.missingConnectionName.msg")); //$NON-NLS-1$
} else if (DockerConnectionManager.getInstance()
.findConnection(connectionName) != null) {
return ValidationStatus.error(WizardMessages.getString(
"DockerConnectionPage.validation.duplicateConnectionName.msg")); //$NON-NLS-1$
}
return ValidationStatus.ok();
}
}
private static class UnixSocketValidator extends ConnectionNameValidator {
private final IObservableValue<Boolean> unixSocketBindingModeModelObservable;
private final IObservableValue<String> unixSocketPathModelObservable;
public UnixSocketValidator(
final IObservableValue<String> connectionNameModelObservable,
final IObservableValue<Boolean> unixSocketBindingModeModelObservable,
final IObservableValue<String> unixSocketPathModelObservable) {
super(connectionNameModelObservable);
this.unixSocketBindingModeModelObservable = unixSocketBindingModeModelObservable;
this.unixSocketPathModelObservable = unixSocketPathModelObservable;
}
@Override
public IObservableList<IObservable> getTargets() {
IObservableList<IObservable> targets = new WritableList<>();
targets.add(getConnectionNameModelObservable());
targets.add(unixSocketPathModelObservable);
return targets;
}
@Override
protected IStatus validate() {
final IStatus connectionNameStatus = super.validate();
final Boolean unixSocketBindingMode = this.unixSocketBindingModeModelObservable
.getValue();
final String unixSocketPath = this.unixSocketPathModelObservable
.getValue();
if (!connectionNameStatus.isOK()) {
return connectionNameStatus;
}
if (unixSocketBindingMode) {
if (unixSocketPath == null || unixSocketPath.isEmpty()) {
return ValidationStatus.error(WizardMessages.getString(
"DockerConnectionPage.validation.missingUnixSocket.msg")); //$NON-NLS-1$
}
try {
// validate the Unix socket format
final URI unixSocketURI = new URI(unixSocketPath);
if (unixSocketURI.getScheme() != null
&& !unixSocketURI.getScheme().equals("unix")) { //$NON-NLS-1$
return ValidationStatus.error(WizardMessages.getString(
"DockerConnectionPage.validation.invalidUnixSocketScheme.msg")); //$NON-NLS-1$
}
if (unixSocketURI.getPath() == null) {
return ValidationStatus.error(WizardMessages.getString(
"DockerConnectionPage.validation.invalidUnixSocketPath.msg")); //$NON-NLS-1$
}
final File unixSocket = new File(unixSocketURI.getPath());
if (!unixSocket.exists()) {
return ValidationStatus.error(WizardMessages.getString(
"DockerConnectionPage.validation.invalidUnixSocketPath.msg")); //$NON-NLS-1$
} else if (!unixSocket.canRead()
|| !unixSocket.canWrite()) {
return ValidationStatus.error(WizardMessages.getString(
"DockerConnectionPage.validation.unreadableUnixSocket.msg")); //$NON-NLS-1$
}
// check for other, existing connections with the same
// settings
final boolean duplicateConnection = DockerConnectionManager
.getInstance().getAllConnections().stream()
.filter(c -> c.getSettings()
.getType() == BindingType.UNIX_SOCKET_CONNECTION)
.map(c -> (UnixSocketConnectionSettings) c
.getSettings())
.anyMatch(s -> s.getPath().equals(unixSocketPath));
if (duplicateConnection) {
return ValidationStatus.error(WizardMessages.getString(
"DockerConnectionPage.validation.duplicateUnixSocketPath.msg")); //$NON-NLS-1$
}
} catch (URISyntaxException e) {
return ValidationStatus.error(WizardMessages.getString(
"DockerConnectionPage.validation.invalidUnixSocketPath.msg")); //$NON-NLS-1$
}
}
return ValidationStatus.ok();
}
}
private static class TcpHostValidator extends ConnectionNameValidator {
private final IObservableValue<Boolean> tcpConnectionBindingModeModelObservable;
private final IObservableValue<String> tcpHostModelObservable;
public TcpHostValidator(
final IObservableValue<String> connectionNameModelObservable,
final IObservableValue<Boolean> tcpConnectionBindingModeModelObservable,
final IObservableValue<String> tcpHostModelObservable) {
super(connectionNameModelObservable);
this.tcpConnectionBindingModeModelObservable = tcpConnectionBindingModeModelObservable;
this.tcpHostModelObservable = tcpHostModelObservable;
}
@Override
public IObservableList<IObservable> getTargets() {
IObservableList<IObservable> targets = new WritableList<>();
targets.add(getConnectionNameModelObservable());
targets.add(tcpHostModelObservable);
return targets;
}
@Override
protected IStatus validate() {
final Boolean tcpConnectionBindingMode = this.tcpConnectionBindingModeModelObservable
.getValue();
final String tcpHost = this.tcpHostModelObservable.getValue();
final IStatus connectionNameStatus = super.validate();
if (!connectionNameStatus.isOK()) {
return connectionNameStatus;
}
if (tcpConnectionBindingMode) {
if (tcpHost == null || tcpHost.isEmpty()) {
return ValidationStatus.error(WizardMessages.getString(
"DockerConnectionPage.validation.missingTcpConnectionURI.msg")); //$NON-NLS-1$
}
try {
final URI uri = new URI(tcpHost);
final String scheme = uri.getScheme() != null
? uri.getScheme().toLowerCase() : null;
final String host = uri.getHost();
final int port = uri.getPort();
if (scheme != null
&& !(scheme.equals("tcp") || scheme.equals("http") //$NON-NLS-1$ //$NON-NLS-2$
|| scheme.equals("https"))) { //$NON-NLS-1$
return ValidationStatus.error(WizardMessages.getString(
"DockerConnectionPage.validation.invalidTcpConnectionScheme.msg")); //$NON-NLS-1$
} else if (host == null) {
return ValidationStatus.error(WizardMessages.getString(
"DockerConnectionPage.validation.invalidTcpConnectionHost.msg")); //$NON-NLS-1$
} else if (port == -1) {
return ValidationStatus.error(WizardMessages.getString(
"DockerConnectionPage.validation.invalidTcpConnectionPort.msg")); //$NON-NLS-1$
}
// check for other, existing connections with the same
// settings
final boolean duplicateConnection = DockerConnectionManager
.getInstance().getAllConnections().stream()
.filter(c -> c.getSettings()
.getType() == BindingType.TCP_CONNECTION)
.map(c -> (TCPConnectionSettings) c.getSettings())
.anyMatch(s -> s.getHost().equals(tcpHost));
if (duplicateConnection) {
return ValidationStatus.error(WizardMessages.getString(
"DockerConnectionPage.validation.duplicateTcpConnectionHost.msg")); //$NON-NLS-1$
}
} catch (URISyntaxException e) {
// URI is not valid
return ValidationStatus.error(WizardMessages.getString(
"DockerConnectionPage.validation.invalidTcpConnectionURI.msg")); //$NON-NLS-1$
}
}
return ValidationStatus.ok();
}
}
private static class TcpCertificatesValidator extends MultiValidator {
private final IObservableValue<Boolean> tcpConnectionBindingModeModelObservable;
private final IObservableValue<Boolean> tcpTlsVerifyModelObservable;
private final IObservableValue<String> tcpCertPathModelObservable;
public TcpCertificatesValidator(
final IObservableValue<Boolean> tcpConnectionBindingModeModelObservable,
final IObservableValue<Boolean> tcpTlsVerifyModelObservable,
final IObservableValue<String> tcpCertPathModelObservable) {
this.tcpConnectionBindingModeModelObservable = tcpConnectionBindingModeModelObservable;
this.tcpTlsVerifyModelObservable = tcpTlsVerifyModelObservable;
this.tcpCertPathModelObservable = tcpCertPathModelObservable;
}
@Override
public IObservableList<IObservable> getTargets() {
IObservableList<IObservable> targets = new WritableList<>();
targets.add(tcpCertPathModelObservable);
return targets;
}
@Override
protected IStatus validate() {
final Boolean tcpConnectionBindingMode = this.tcpConnectionBindingModeModelObservable
.getValue();
final Boolean tcpTlsVerify = this.tcpTlsVerifyModelObservable
.getValue();
final String tcpCertPath = this.tcpCertPathModelObservable
.getValue();
if (tcpConnectionBindingMode && tcpTlsVerify) {
if (tcpCertPath == null || tcpCertPath.isEmpty()) {
return ValidationStatus.error(WizardMessages.getString(
"DockerConnectionPage.validation.missingTcpCertPath.msg")); //$NON-NLS-1$
}
final File tcpCert = new File(tcpCertPath);
if (!tcpCert.exists()) {
return ValidationStatus.error(WizardMessages.getString(
"DockerConnectionPage.validation.invalidTcpCertPath.msg")); //$NON-NLS-1$
} else if (!tcpCert.canRead() || !tcpCert.canRead()) {
return ValidationStatus.error(WizardMessages.getString(
"DockerConnectionPage.validation.unreadableTcpCertPath.msg")); //$NON-NLS-1$
}
}
return ValidationStatus.ok();
}
}
private class ConnectionSelectionContentProvider
implements IStructuredContentProvider {
@Override
public void inputChanged(Viewer viewer, Object oldInput,
Object newInput) {
}
@Override
public void dispose() {
}
@Override
public Object[] getElements(Object inputElement) {
return (String[]) (inputElement);
}
}
private class ConnectionSelectionLabelProvider implements ILabelProvider {
private Image CONNECTION_IMAGE = SWTImagesFactory.DESC_REPOSITORY_MIDDLE
.createImage();
@Override
public void removeListener(ILabelProviderListener listener) {
}
@Override
public boolean isLabelProperty(Object element, String property) {
return false;
}
@Override
public void dispose() {
CONNECTION_IMAGE.dispose();
}
@Override
public void addListener(ILabelProviderListener listener) {
}
@Override
public String getText(Object element) {
return element.toString();
}
@Override
public Image getImage(Object element) {
return CONNECTION_IMAGE;
}
}
}