| /******************************************************************************* |
| * 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.internal.docker.ui.launch.IRunDockerImageLaunchConfigurationConstants.ALLOCATE_PSEUDO_CONSOLE; |
| import static org.eclipse.linuxtools.internal.docker.ui.launch.IRunDockerImageLaunchConfigurationConstants.AUTO_REMOVE; |
| import static org.eclipse.linuxtools.internal.docker.ui.launch.IRunDockerImageLaunchConfigurationConstants.COMMAND; |
| import static org.eclipse.linuxtools.internal.docker.ui.launch.IRunDockerImageLaunchConfigurationConstants.CONTAINER_NAME; |
| import static org.eclipse.linuxtools.internal.docker.ui.launch.IRunDockerImageLaunchConfigurationConstants.ENTRYPOINT; |
| import static org.eclipse.linuxtools.internal.docker.ui.launch.IRunDockerImageLaunchConfigurationConstants.INTERACTIVE; |
| import static org.eclipse.linuxtools.internal.docker.ui.launch.IRunDockerImageLaunchConfigurationConstants.LINKS; |
| import static org.eclipse.linuxtools.internal.docker.ui.launch.IRunDockerImageLaunchConfigurationConstants.PRIVILEGED; |
| import static org.eclipse.linuxtools.internal.docker.ui.launch.IRunDockerImageLaunchConfigurationConstants.PUBLISHED_PORTS; |
| import static org.eclipse.linuxtools.internal.docker.ui.launch.IRunDockerImageLaunchConfigurationConstants.PUBLISH_ALL_PORTS; |
| import static org.eclipse.linuxtools.internal.docker.ui.launch.IRunDockerImageLaunchConfigurationConstants.READONLY; |
| |
| import java.lang.reflect.InvocationTargetException; |
| import java.util.ArrayList; |
| import java.util.Collections; |
| import java.util.HashSet; |
| import java.util.Iterator; |
| import java.util.List; |
| |
| import org.eclipse.core.databinding.DataBindingContext; |
| import org.eclipse.core.databinding.beans.BeanProperties; |
| import org.eclipse.core.databinding.observable.IObservable; |
| import org.eclipse.core.databinding.observable.ObservableTracker; |
| 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.observable.value.WritableValue; |
| import org.eclipse.core.databinding.validation.MultiValidator; |
| import org.eclipse.core.databinding.validation.ValidationStatus; |
| import org.eclipse.core.runtime.CoreException; |
| import org.eclipse.core.runtime.IStatus; |
| import org.eclipse.debug.core.ILaunchConfiguration; |
| import org.eclipse.jface.databinding.swt.WidgetProperties; |
| import org.eclipse.jface.databinding.viewers.ObservableListContentProvider; |
| import org.eclipse.jface.databinding.viewers.ViewerSupport; |
| import org.eclipse.jface.databinding.viewers.ViewersObservables; |
| import org.eclipse.jface.databinding.wizard.WizardPageSupport; |
| import org.eclipse.jface.dialogs.IDialogConstants; |
| import org.eclipse.jface.dialogs.IMessageProvider; |
| import org.eclipse.jface.dialogs.MessageDialog; |
| import org.eclipse.jface.fieldassist.ComboContentAdapter; |
| import org.eclipse.jface.fieldassist.ContentProposal; |
| import org.eclipse.jface.fieldassist.ContentProposalAdapter; |
| import org.eclipse.jface.fieldassist.ControlDecoration; |
| import org.eclipse.jface.fieldassist.IContentProposal; |
| import org.eclipse.jface.fieldassist.IContentProposalProvider; |
| import org.eclipse.jface.layout.GridDataFactory; |
| import org.eclipse.jface.layout.GridLayoutFactory; |
| import org.eclipse.jface.viewers.CheckboxTableViewer; |
| import org.eclipse.jface.viewers.ComboViewer; |
| import org.eclipse.jface.viewers.ISelectionChangedListener; |
| import org.eclipse.jface.viewers.IStructuredSelection; |
| import org.eclipse.jface.viewers.TableViewer; |
| import org.eclipse.jface.viewers.TableViewerColumn; |
| import org.eclipse.jface.wizard.WizardPage; |
| import org.eclipse.linuxtools.docker.core.AbstractRegistry; |
| import org.eclipse.linuxtools.docker.core.DockerException; |
| import org.eclipse.linuxtools.docker.core.IDockerConnection; |
| import org.eclipse.linuxtools.docker.core.IDockerContainer; |
| import org.eclipse.linuxtools.docker.core.IDockerImage; |
| import org.eclipse.linuxtools.docker.core.IDockerImageInfo; |
| import org.eclipse.linuxtools.docker.ui.Activator; |
| import org.eclipse.linuxtools.docker.ui.wizards.ImageSearch; |
| import org.eclipse.linuxtools.internal.docker.core.DefaultImagePullProgressHandler; |
| import org.eclipse.linuxtools.internal.docker.core.RegistryInfo; |
| import org.eclipse.linuxtools.internal.docker.ui.SWTImagesFactory; |
| import org.eclipse.linuxtools.internal.docker.ui.commands.CommandUtils; |
| import org.eclipse.linuxtools.internal.docker.ui.jobs.FindImageInfoRunnable; |
| import org.eclipse.linuxtools.internal.docker.ui.views.DVMessages; |
| import org.eclipse.linuxtools.internal.docker.ui.wizards.ImageRunSelectionModel.ContainerLinkModel; |
| import org.eclipse.linuxtools.internal.docker.ui.wizards.ImageRunSelectionModel.ExposedPortModel; |
| import org.eclipse.swt.SWT; |
| import org.eclipse.swt.custom.ScrolledComposite; |
| import org.eclipse.swt.events.SelectionListener; |
| import org.eclipse.swt.graphics.Point; |
| import org.eclipse.swt.widgets.Button; |
| import org.eclipse.swt.widgets.Combo; |
| import org.eclipse.swt.widgets.Composite; |
| import org.eclipse.swt.widgets.Control; |
| import org.eclipse.swt.widgets.Display; |
| import org.eclipse.swt.widgets.Label; |
| import org.eclipse.swt.widgets.Link; |
| import org.eclipse.swt.widgets.Table; |
| import org.eclipse.swt.widgets.TableColumn; |
| import org.eclipse.swt.widgets.Text; |
| import org.eclipse.ui.PlatformUI; |
| |
| /** |
| * A {@link WizardPage} to let the user select the {@link IDockerImage} to run |
| * and select the most common arguments (container name, port settings, etc.) |
| * |
| * @author xcoulon |
| * |
| */ |
| public class ImageRunSelectionPage extends WizardPage { |
| |
| private static final int INDENT = 30; |
| |
| private static final String ERROR_PULLING_IMAGE = "ImagePullError.msg"; //$NON-NLS-1$ |
| |
| private final DataBindingContext dbc = new DataBindingContext(); |
| private final ImageRunSelectionModel model; |
| |
| private final ILaunchConfiguration lastLaunchConfiguration; |
| |
| private Link pullImageLink; |
| |
| private WritableValue<String> writeValue; |
| private static final int COLUMNS = 3; |
| |
| /** |
| * Default constructor. |
| * |
| * @param selectedImage |
| * the {@link IDockerImage} to run |
| * @param lastLaunchConfiguration |
| * the last {@link ILaunchConfiguration} used to run this |
| * {@link IDockerImage} or <code>null</code> if none exists. |
| * |
| */ |
| public ImageRunSelectionPage(final IDockerImage selectedImage, |
| final ILaunchConfiguration lastLaunchConfiguration) { |
| super("ImageSelectionPage", //$NON-NLS-1$ |
| WizardMessages.getString("ImageSelectionPage.title"), //$NON-NLS-1$ |
| SWTImagesFactory.DESC_BANNER_REPOSITORY); |
| setMessage(WizardMessages.getString("ImageSelectionPage.runImage")); //$NON-NLS-1$ |
| setPageComplete(true); |
| this.model = new ImageRunSelectionModel(selectedImage); |
| this.lastLaunchConfiguration = lastLaunchConfiguration; |
| } |
| |
| /** |
| * Default constructor. |
| * |
| * @param selectedConnection |
| * the {@link IDockerConnection} to run |
| * |
| */ |
| public ImageRunSelectionPage(final IDockerConnection selectedConnection) { |
| super("ImageSelectionPage", //$NON-NLS-1$ |
| WizardMessages.getString("ImageSelectionPage.exposedPortTitle"), //$NON-NLS-1$ |
| SWTImagesFactory.DESC_BANNER_REPOSITORY); |
| setMessage(WizardMessages |
| .getString("ImageRunSelectionPage.exposedPortMsg")); //$NON-NLS-1$ |
| setPageComplete(false); |
| this.model = new ImageRunSelectionModel(selectedConnection); |
| this.lastLaunchConfiguration = null; |
| } |
| |
| /** |
| * @return the {@link ImageRunSelectionModel} model associated |
| */ |
| public ImageRunSelectionModel getModel() { |
| return model; |
| } |
| |
| @Override |
| public void dispose() { |
| dbc.dispose(); |
| super.dispose(); |
| } |
| |
| @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); |
| GridDataFactory.fillDefaults().align(SWT.FILL, SWT.FILL).span(1, 1) |
| .grab(true, false).applyTo(container); |
| GridLayoutFactory.fillDefaults().numColumns(COLUMNS).margins(6, 6) |
| .applyTo(container); |
| setDefaultValues(); |
| createImageSettingsSection(container); |
| createSectionSeparator(container, true); |
| createPortSettingsSection(container); |
| // addSectionSeparator(container, false); |
| createLinkSettingsSection(container); |
| // addSectionSeparator(container, false); |
| createRunOptionsSection(container); |
| // Observe model changes to propagate to the UI via listeners. |
| final IObservableValue imageSelectionObservable = BeanProperties |
| .value(ImageRunSelectionModel.class, |
| ImageRunSelectionModel.SELECTED_IMAGE_NAME) |
| .observe(model); |
| imageSelectionObservable |
| .addValueChangeListener(onImageSelectionChange()); |
| writeValue = new WritableValue<>("", String.class); //$NON-NLS-1$ |
| // setup validation support |
| WizardPageSupport.create(this, dbc); |
| |
| // set validation |
| final ImageSelectionValidator imageSelectionValidator = new ImageSelectionValidator( |
| imageSelectionObservable); |
| imageSelectionObservable |
| .addValueChangeListener(onImageSelectionChange()); |
| dbc.addValidationStatusProvider(imageSelectionValidator); |
| imageSelectionObservable |
| .addValueChangeListener(onImageSelectionChange()); |
| final IObservableValue containerNameObservable = BeanProperties |
| .value(ImageRunSelectionModel.class, |
| ImageRunSelectionModel.CONTAINER_NAME) |
| .observe(model); |
| final ContainerNameValidator containerNameValidator = new ContainerNameValidator( |
| model.getSelectedConnection(), containerNameObservable); |
| dbc.addValidationStatusProvider(containerNameValidator); |
| // // force displaying the error message upon startup |
| final Object containerstatus = containerNameValidator |
| .getValidationStatus().getValue(); |
| // attach the Databinding context status to this wizard page. |
| WizardPageSupport.create(this, this.dbc); |
| setStatusMessage(containerstatus); |
| |
| scrollTop.setContent(container); |
| Point point = container.computeSize(SWT.DEFAULT, SWT.DEFAULT); |
| scrollTop.setSize(point); |
| scrollTop.setMinSize(point); |
| setControl(scrollTop); |
| } |
| |
| private void setStatusMessage(final Object containerstatus) { |
| if (containerstatus instanceof ValidationStatus) { |
| final ValidationStatus validationStatus = (ValidationStatus) containerstatus; |
| if (validationStatus.getSeverity() == IStatus.ERROR) { |
| setMessage(validationStatus.getMessage(), |
| IMessageProvider.ERROR); |
| } else if (validationStatus.getSeverity() == IStatus.WARNING) { |
| setMessage(validationStatus.getMessage(), |
| IMessageProvider.WARNING); |
| } |
| } else if (containerstatus instanceof IStatus) { |
| final IStatus status = (IStatus) containerstatus; |
| if (status.getSeverity() == IStatus.ERROR) { |
| setMessage(status.getMessage(), IMessageProvider.ERROR); |
| } else |
| if (status != null && status.getSeverity() == IStatus.WARNING) { |
| setMessage(status.getMessage(), IMessageProvider.WARNING); |
| } |
| |
| } |
| } |
| |
| private void createSectionSeparator(final Composite container, |
| final boolean separator) { |
| final int SECTION_INDENT = 10; |
| GridDataFactory.fillDefaults().align(SWT.FILL, SWT.CENTER) |
| .span(COLUMNS, 1).grab(true, false) |
| .indent(SWT.DEFAULT, SECTION_INDENT) |
| .applyTo(new Label(container, separator |
| ? (SWT.SEPARATOR | SWT.HORIZONTAL) : SWT.NONE)); |
| } |
| |
| /** |
| * Creates the {@link Composite} container that will display widgets to |
| * select an {@link IDockerImage}, name it and specify the command to run. |
| * |
| * @param container |
| * the parent {@link Composite} |
| */ |
| private void createImageSettingsSection(final Composite container) { |
| // Image selection name |
| final Label imageSelectionLabel = new Label(container, SWT.NONE); |
| imageSelectionLabel.setText( |
| WizardMessages.getString("ImageRunSelectionPage.imageName")); //$NON-NLS-1$ |
| GridDataFactory.fillDefaults().align(SWT.FILL, SWT.CENTER) |
| .grab(false, false).applyTo(imageSelectionLabel); |
| final Combo imageSelectionCombo = new Combo(container, SWT.BORDER); |
| final ComboViewer imageSelectionComboViewer = new ComboViewer( |
| imageSelectionCombo); |
| imageSelectionCombo.setToolTipText(WizardMessages |
| .getString("ImageRunSelectionPage.selectTooltip")); //$NON-NLS-1$ |
| GridDataFactory.fillDefaults().align(SWT.FILL, SWT.CENTER) |
| .grab(true, false).span(1, 1).applyTo(imageSelectionCombo); |
| new ControlDecoration(imageSelectionCombo, SWT.TOP | SWT.LEFT); |
| new ContentProposalAdapter(imageSelectionCombo, |
| new ComboContentAdapter() { |
| @Override |
| public void insertControlContents(Control control, |
| String text, int cursorPosition) { |
| final Combo combo = (Combo) control; |
| final Point selection = combo.getSelection(); |
| combo.setText(text); |
| selection.x = text.length(); |
| selection.y = selection.x; |
| combo.setSelection(selection); |
| } |
| }, getImageNameContentProposalProvider(imageSelectionCombo), |
| null, null); |
| // image search |
| final Button searchImageButton = new Button(container, SWT.NONE); |
| searchImageButton.setText( |
| WizardMessages.getString("ImageRunSelectionPage.search")); //$NON-NLS-1$ |
| GridDataFactory.fillDefaults().align(SWT.FILL, SWT.CENTER) |
| .grab(false, false).span(1, 1).applyTo(searchImageButton); |
| searchImageButton.addSelectionListener(onSearchImage()); |
| // link to pull image |
| final Label fillerLabel = new Label(container, SWT.NONE); |
| GridDataFactory.fillDefaults().align(SWT.FILL, SWT.CENTER) |
| .grab(false, false).span(1, 1).applyTo(fillerLabel); |
| pullImageLink = new Link(container, SWT.NONE); |
| pullImageLink.setText( |
| WizardMessages.getString("ImageRunSelectionPage.pullImage")); //$NON-NLS-1$ |
| GridDataFactory.fillDefaults().align(SWT.FILL, SWT.CENTER) |
| .grab(true, false).span(COLUMNS - 1, 1).applyTo(pullImageLink); |
| pullImageLink.addSelectionListener(onPullImage()); |
| dbc.bindValue(WidgetProperties.enabled().observe(pullImageLink), |
| BeanProperties |
| .value(ImageRunSelectionModel.class, |
| ImageRunSelectionModel.SELECTED_IMAGE_NEEDS_PULLING) |
| .observe(model)); |
| // bind combo with model (for values and selection) |
| imageSelectionComboViewer |
| .setContentProvider(new ObservableListContentProvider()); |
| dbc.bindList(WidgetProperties.items().observe(imageSelectionCombo), |
| BeanProperties |
| .list(ImageRunSelectionModel.class, |
| ImageRunSelectionModel.IMAGE_NAMES) |
| .observe(model)); |
| dbc.bindValue(WidgetProperties.selection().observe(imageSelectionCombo), |
| BeanProperties |
| .value(ImageRunSelectionModel.class, |
| ImageRunSelectionModel.SELECTED_IMAGE_NAME) |
| .observe(model)); |
| // Container name (optional) |
| final Label containerNameLabel = new Label(container, SWT.NONE); |
| GridDataFactory.fillDefaults().align(SWT.FILL, SWT.CENTER) |
| .grab(false, false).applyTo(imageSelectionLabel); |
| containerNameLabel.setText(WizardMessages |
| .getString("ImageRunSelectionPage.containerName")); //$NON-NLS-1$ |
| final Text containerNameText = new Text(container, SWT.BORDER); |
| containerNameText.setToolTipText(WizardMessages |
| .getString("ImageRunSelectionPage.containerTooltip")); //$NON-NLS-1$ |
| GridDataFactory.fillDefaults().align(SWT.FILL, SWT.CENTER) |
| .grab(true, false).span(1, 1).applyTo(containerNameText); |
| GridDataFactory.fillDefaults().align(SWT.FILL, SWT.CENTER) |
| .grab(false, false).span(1, 1) |
| .applyTo(new Label(container, SWT.NONE)); |
| dbc.bindValue( |
| WidgetProperties.text(SWT.Modify).observe(containerNameText), |
| BeanProperties |
| .value(ImageRunSelectionModel.class, |
| ImageRunSelectionModel.CONTAINER_NAME) |
| .observe(model)); |
| |
| // EntryPoint (optional) |
| final Label entrypointLabel = new Label(container, SWT.NONE); |
| GridDataFactory.fillDefaults().align(SWT.FILL, SWT.CENTER) |
| .grab(false, false).applyTo(imageSelectionLabel); |
| entrypointLabel.setText( |
| WizardMessages.getString("ImageRunSelectionPage.entrypoint")); //$NON-NLS-1$ |
| // TODO: include SWT.SEARCH | SWT.ICON_SEARCH to support value reset |
| final Text entrypointText = new Text(container, SWT.BORDER); |
| |
| GridDataFactory.fillDefaults().align(SWT.FILL, SWT.CENTER) |
| .grab(true, false).span(1, 1).applyTo(entrypointText); |
| GridDataFactory.fillDefaults().align(SWT.FILL, SWT.CENTER) |
| .grab(false, false).span(1, 1) |
| .applyTo(new Label(container, SWT.NONE)); |
| dbc.bindValue(WidgetProperties.text(SWT.Modify).observe(entrypointText), |
| BeanProperties |
| .value(ImageRunSelectionModel.class, |
| ImageRunSelectionModel.ENTRYPOINT) |
| .observe(model)); |
| |
| // Command (optional) |
| final Label commandLabel = new Label(container, SWT.NONE); |
| GridDataFactory.fillDefaults().align(SWT.FILL, SWT.CENTER) |
| .grab(false, false).applyTo(imageSelectionLabel); |
| commandLabel.setText( |
| WizardMessages.getString("ImageRunSelectionPage.command")); //$NON-NLS-1$ |
| final Text commandText = new Text(container, SWT.BORDER); |
| GridDataFactory.fillDefaults().align(SWT.FILL, SWT.CENTER) |
| .grab(true, false).span(1, 1).applyTo(commandText); |
| GridDataFactory.fillDefaults().align(SWT.FILL, SWT.CENTER) |
| .grab(false, false).span(1, 1) |
| .applyTo(new Label(container, SWT.NONE)); |
| dbc.bindValue(WidgetProperties.text(SWT.Modify).observe(commandText), |
| BeanProperties.value(ImageRunSelectionModel.class, |
| ImageRunSelectionModel.COMMAND).observe(model)); |
| } |
| |
| private void createPortSettingsSection(final Composite container) { |
| final Button publishAllPortsButton = new Button(container, SWT.CHECK); |
| publishAllPortsButton.setText(WizardMessages |
| .getString("ImageRunSelectionPage.publishAllPorts")); //$NON-NLS-1$ |
| GridDataFactory.fillDefaults().align(SWT.FILL, SWT.CENTER) |
| .span(COLUMNS, 1).grab(true, false) |
| .applyTo(publishAllPortsButton); |
| dbc.bindValue( |
| WidgetProperties.selection().observe(publishAllPortsButton), |
| BeanProperties |
| .value(ImageRunSelectionModel.class, |
| ImageRunSelectionModel.PUBLISH_ALL_PORTS) |
| .observe(model)); |
| // specify ports |
| final Label portSettingsLabel = new Label(container, SWT.NONE); |
| portSettingsLabel.setText( |
| WizardMessages.getString("ImageRunSelectionPage.portSettings")); //$NON-NLS-1$ |
| GridDataFactory.fillDefaults().align(SWT.FILL, SWT.CENTER) |
| .grab(true, false).span(COLUMNS, 1).indent(INDENT, 0) |
| .applyTo(portSettingsLabel); |
| final CheckboxTableViewer exposedPortsTableViewer = createPortSettingsTable( |
| container); |
| GridDataFactory.fillDefaults().align(SWT.FILL, SWT.TOP) |
| .grab(true, false).span(COLUMNS - 1, 1).indent(INDENT, 0) |
| .hint(200, 70).applyTo(exposedPortsTableViewer.getTable()); |
| // buttons |
| final Composite buttonsContainers = new Composite(container, SWT.NONE); |
| GridDataFactory.fillDefaults().align(SWT.FILL, SWT.TOP) |
| .grab(false, false).applyTo(buttonsContainers); |
| GridLayoutFactory.fillDefaults().numColumns(1).margins(0, 0) |
| .spacing(SWT.DEFAULT, 0).applyTo(buttonsContainers); |
| |
| final Button addButton = new Button(buttonsContainers, SWT.NONE); |
| GridDataFactory.fillDefaults().align(SWT.FILL, SWT.TOP) |
| .grab(true, false).applyTo(addButton); |
| addButton |
| .setText(WizardMessages |
| .getString("ImageRunSelectionPage.addButton")); //$NON-NLS-1$ |
| addButton.addSelectionListener(onAddPort(exposedPortsTableViewer)); |
| final Button editButton = new Button(buttonsContainers, SWT.NONE); |
| GridDataFactory.fillDefaults().align(SWT.FILL, SWT.TOP) |
| .grab(true, false).applyTo(editButton); |
| editButton.setText( |
| WizardMessages.getString("ImageRunSelectionPage.editButton")); //$NON-NLS-1$ |
| editButton.setEnabled(false); |
| editButton.addSelectionListener(onEditPort(exposedPortsTableViewer)); |
| final Button removeButton = new Button(buttonsContainers, SWT.NONE); |
| GridDataFactory.fillDefaults().align(SWT.FILL, SWT.TOP) |
| .grab(true, false).applyTo(removeButton); |
| removeButton.setText( |
| WizardMessages.getString("ImageRunSelectionPage.remove")); //$NON-NLS-1$ |
| removeButton |
| .addSelectionListener(onRemovePorts(exposedPortsTableViewer)); |
| BeanProperties |
| .value(ImageRunSelectionModel.class, |
| ImageRunSelectionModel.PUBLISH_ALL_PORTS) |
| .observe(model) |
| .addValueChangeListener(onPublishAllPortsChange( |
| exposedPortsTableViewer.getTable(), addButton, |
| removeButton)); |
| ViewerSupport.bind(exposedPortsTableViewer, model.getExposedPorts(), |
| BeanProperties.values(ExposedPortModel.class, |
| ExposedPortModel.CONTAINER_PORT, |
| ExposedPortModel.PORT_TYPE, |
| ExposedPortModel.HOST_ADDRESS, |
| ExposedPortModel.HOST_PORT)); |
| dbc.bindSet( |
| ViewersObservables.observeCheckedElements( |
| exposedPortsTableViewer, ExposedPortModel.class), |
| BeanProperties.set(ImageRunSelectionModel.SELECTED_PORTS) |
| .observe(model)); |
| checkAllElements(exposedPortsTableViewer); |
| |
| // disable the edit and removeButton if the table is empty |
| exposedPortsTableViewer.addSelectionChangedListener( |
| onSelectionChanged(editButton, removeButton)); |
| |
| togglePortMappingControls(exposedPortsTableViewer.getTable(), addButton, |
| removeButton); |
| } |
| |
| private void checkAllElements( |
| final CheckboxTableViewer exposedPortsTableViewer) { |
| exposedPortsTableViewer.setAllChecked(true); |
| model.setSelectedPorts( |
| new HashSet<>(model.getExposedPorts())); |
| } |
| |
| private ISelectionChangedListener onSelectionChanged( |
| final Button... targetButtons) { |
| return e -> { |
| if (e.getSelection().isEmpty()) { |
| setControlsEnabled(targetButtons, false); |
| } else { |
| setControlsEnabled(targetButtons, true); |
| } |
| }; |
| } |
| |
| private CheckboxTableViewer createPortSettingsTable( |
| final Composite container) { |
| final Table table = new Table(container, SWT.BORDER | SWT.FULL_SELECTION |
| | SWT.V_SCROLL | SWT.H_SCROLL | SWT.CHECK); |
| final CheckboxTableViewer tableViewer = new CheckboxTableViewer(table); |
| table.setHeaderVisible(true); |
| table.setLinesVisible(true); |
| createTableViewerColum(tableViewer, |
| WizardMessages |
| .getString("ImageRunSelectionPage.containerPortColumn"), //$NON-NLS-1$ |
| 100); |
| createTableViewerColum(tableViewer, |
| WizardMessages.getString("ImageRunSelectionPage.typeColumn"), //$NON-NLS-1$ |
| 50); |
| createTableViewerColum(tableViewer, |
| WizardMessages |
| .getString("ImageRunSelectionPage.hostAddressColumn"), //$NON-NLS-1$ |
| 100); |
| createTableViewerColum(tableViewer, |
| WizardMessages |
| .getString("ImageRunSelectionPage.hostPortColumn"), //$NON-NLS-1$ |
| 100); |
| tableViewer.setContentProvider(new ObservableListContentProvider()); |
| return tableViewer; |
| } |
| |
| private TableViewerColumn createTableViewerColum( |
| final TableViewer tableViewer, final String title, |
| final int width) { |
| final TableViewerColumn viewerColumn = new TableViewerColumn( |
| tableViewer, SWT.NONE); |
| final TableColumn column = viewerColumn.getColumn(); |
| if (title != null) { |
| column.setText(title); |
| } |
| column.setWidth(width); |
| return viewerColumn; |
| } |
| |
| private void createLinkSettingsSection(final Composite container) { |
| final Label linksLabel = new Label(container, SWT.NONE); |
| linksLabel.setText( |
| WizardMessages.getString("ImageRunSelectionPage.links")); |
| GridDataFactory.fillDefaults().align(SWT.FILL, SWT.CENTER) |
| .grab(false, false).span(COLUMNS, 1).applyTo(linksLabel); |
| final TableViewer linksTableViewer = createLinksTable(container); |
| GridDataFactory.fillDefaults().align(SWT.FILL, SWT.TOP).grab(true, true) |
| .span(COLUMNS - 1, 1).hint(200, 50).indent(INDENT, SWT.DEFAULT) |
| .applyTo(linksTableViewer.getTable()); |
| // buttons |
| final Composite buttonsContainers = new Composite(container, SWT.NONE); |
| GridDataFactory.fillDefaults().align(SWT.FILL, SWT.TOP) |
| .grab(false, false).applyTo(buttonsContainers); |
| GridLayoutFactory.fillDefaults().numColumns(1).margins(0, 0) |
| .spacing(SWT.DEFAULT, 0).applyTo(buttonsContainers); |
| |
| final Button addButton = new Button(buttonsContainers, SWT.NONE); |
| GridDataFactory.fillDefaults().align(SWT.FILL, SWT.TOP) |
| .grab(true, false).applyTo(addButton); |
| addButton.setText( |
| WizardMessages.getString("ImageRunSelectionPage.addButton")); //$NON-NLS-1$ |
| addButton.addSelectionListener(onAddLink()); |
| final Button editButton = new Button(buttonsContainers, SWT.NONE); |
| GridDataFactory.fillDefaults().align(SWT.FILL, SWT.TOP) |
| .grab(true, false).applyTo(editButton); |
| editButton.setText( |
| WizardMessages.getString("ImageRunSelectionPage.editButton")); //$NON-NLS-1$ |
| editButton.setEnabled(false); |
| editButton.addSelectionListener(onEditLink(linksTableViewer)); |
| final Button removeButton = new Button(buttonsContainers, SWT.NONE); |
| GridDataFactory.fillDefaults().align(SWT.FILL, SWT.TOP) |
| .grab(true, false).applyTo(removeButton); |
| removeButton.setText( |
| WizardMessages.getString("ImageRunSelectionPage.remove")); //$NON-NLS-1$ |
| removeButton.addSelectionListener(onRemoveLinks(linksTableViewer)); |
| removeButton.setEnabled(false); |
| ViewerSupport.bind(linksTableViewer, model.getLinks(), |
| BeanProperties.values(ContainerLinkModel.class, |
| ContainerLinkModel.CONTAINER_NAME, |
| ContainerLinkModel.CONTAINER_ALIAS)); |
| // disable the edit and removeButton if the table is empty |
| linksTableViewer.addSelectionChangedListener( |
| onSelectionChanged(editButton, removeButton)); |
| |
| } |
| |
| private TableViewer createLinksTable(final Composite container) { |
| final Table table = new Table(container, |
| SWT.BORDER | SWT.MULTI | SWT.V_SCROLL | SWT.H_SCROLL); |
| final TableViewer tableViewer = new TableViewer(table); |
| table.setHeaderVisible(true); |
| table.setLinesVisible(true); |
| createTableViewerColum(tableViewer, |
| WizardMessages |
| .getString("ImageRunSelectionPage.containerNameColumn"), //$NON-NLS-1$ |
| 200); |
| createTableViewerColum(tableViewer, |
| WizardMessages.getString("ImageRunSelectionPage.aliasColumn"), //$NON-NLS-1$ |
| 150); |
| return tableViewer; |
| } |
| |
| private SelectionListener onAddLink() { |
| return SelectionListener.widgetSelectedAdapter(e -> { |
| final ContainerLinkDialog dialog = new ContainerLinkDialog( |
| getShell(), model.getSelectedConnection()); |
| dialog.create(); |
| if (dialog.open() == IDialogConstants.OK_ID) { |
| model.addLink(dialog.getContainerName(), |
| dialog.getContainerAlias()); |
| } |
| }); |
| } |
| |
| private SelectionListener onEditLink(final TableViewer linksTableViewer) { |
| return SelectionListener.widgetSelectedAdapter(e -> { |
| final IStructuredSelection selection = linksTableViewer |
| .getStructuredSelection(); |
| |
| final ContainerLinkModel selectedContainerLink = (ContainerLinkModel) selection |
| .getFirstElement(); |
| final ContainerLinkDialog dialog = new ContainerLinkDialog( |
| getShell(), model.getSelectedConnection(), |
| selectedContainerLink); |
| dialog.create(); |
| if (dialog.open() == IDialogConstants.OK_ID) { |
| selectedContainerLink |
| .setContainerName(dialog.getContainerName()); |
| selectedContainerLink |
| .setContainerAlias(dialog.getContainerAlias()); |
| linksTableViewer.refresh(); |
| } |
| }); |
| } |
| |
| private SelectionListener onRemoveLinks( |
| final TableViewer linksTableViewer) { |
| return SelectionListener.widgetSelectedAdapter(e -> { |
| final IStructuredSelection selection = linksTableViewer |
| .getStructuredSelection(); |
| for (@SuppressWarnings("unchecked") |
| Iterator<ContainerLinkModel> iterator = selection |
| .iterator(); iterator.hasNext();) { |
| model.removeLink(iterator.next()); |
| } |
| }); |
| } |
| |
| private void createRunOptionsSection(final Composite container) { |
| // interactive/show in console mode |
| final Button interactiveButton = new Button(container, SWT.CHECK); |
| interactiveButton.setText( |
| WizardMessages.getString("ImageRunSelectionPage.openStdin")); //$NON-NLS-1$ |
| GridDataFactory.fillDefaults().align(SWT.FILL, SWT.CENTER) |
| .span(COLUMNS, 1).grab(true, false).applyTo(interactiveButton); |
| dbc.bindValue(WidgetProperties.selection().observe(interactiveButton), |
| BeanProperties |
| .value(ImageRunSelectionModel.class, |
| ImageRunSelectionModel.INTERACTIVE_MODE) |
| .observe(model)); |
| // allocate pseudo-TTY |
| final Button allocatePseudoTTY = new Button(container, SWT.CHECK); |
| allocatePseudoTTY |
| .setText(WizardMessages.getString("ImageRunSelectionPage.tty")); //$NON-NLS-1$ |
| GridDataFactory.fillDefaults().align(SWT.FILL, SWT.CENTER) |
| .span(COLUMNS, 1).grab(true, false).applyTo(allocatePseudoTTY); |
| dbc.bindValue(WidgetProperties.selection().observe(allocatePseudoTTY), |
| BeanProperties |
| .value(ImageRunSelectionModel.class, |
| ImageRunSelectionModel.ALLOCATE_PSEUDO_TTY) |
| .observe(model)); |
| |
| // remove when exits |
| final Button removeWhenExitsButton = new Button(container, SWT.CHECK); |
| removeWhenExitsButton.setText( |
| WizardMessages.getString("ImageRunSelectionPage.autoRemove")); //$NON-NLS-1$ |
| GridDataFactory.fillDefaults().align(SWT.FILL, SWT.CENTER) |
| .span(COLUMNS, 1).grab(true, false) |
| .applyTo(removeWhenExitsButton); |
| dbc.bindValue( |
| WidgetProperties.selection().observe(removeWhenExitsButton), |
| BeanProperties |
| .value(ImageRunSelectionModel.class, |
| ImageRunSelectionModel.REMOVE_WHEN_EXITS) |
| .observe(model)); |
| |
| // privileged |
| final Button privilegedButton = new Button(container, SWT.CHECK); |
| privilegedButton.setText( |
| WizardMessages.getString("ImageRunSelectionPage.privileged")); //$NON-NLS-1$ |
| GridDataFactory.fillDefaults().align(SWT.FILL, SWT.CENTER) |
| .span(COLUMNS, 1).grab(true, false).applyTo(privilegedButton); |
| dbc.bindValue(WidgetProperties.selection().observe(privilegedButton), |
| BeanProperties |
| .value(ImageRunSelectionModel.class, |
| ImageRunSelectionModel.PRIVILEGED) |
| .observe(model)); |
| |
| // seccomp:unconfined |
| final Button unconfinedButton = new Button(container, SWT.CHECK); |
| unconfinedButton.setText( |
| WizardMessages.getString("ImageRunSelectionPage.unconfined")); //$NON-NLS-1$ |
| GridDataFactory.fillDefaults().align(SWT.FILL, SWT.CENTER) |
| .span(COLUMNS, 1).grab(true, false).applyTo(unconfinedButton); |
| dbc.bindValue(WidgetProperties.selection().observe(unconfinedButton), |
| BeanProperties |
| .value(ImageRunSelectionModel.class, |
| ImageRunSelectionModel.UNCONFINED) |
| .observe(model)); |
| |
| // readonly with tmpfs /run and /tmp |
| final Button basicSecurityButton = new Button(container, SWT.CHECK); |
| basicSecurityButton.setText(WizardMessages |
| .getString("ImageRunSelectionPage.basicSecurity")); //$NON-NLS-1$ |
| GridDataFactory.fillDefaults().align(SWT.FILL, SWT.CENTER) |
| .span(COLUMNS, 1).grab(true, false) |
| .applyTo(basicSecurityButton); |
| dbc.bindValue(WidgetProperties.selection().observe(basicSecurityButton), |
| BeanProperties |
| .value(ImageRunSelectionModel.class, |
| ImageRunSelectionModel.BASIC_SECURITY) |
| .observe(model)); |
| } |
| |
| /** |
| * Creates an {@link IContentProposalProvider} to propose |
| * {@link IDockerImage} names based on the current text. |
| * |
| * @param items |
| * @return |
| */ |
| private IContentProposalProvider getImageNameContentProposalProvider( |
| final Combo imageSelectionCombo) { |
| return (contents, position) -> { |
| final List<IContentProposal> proposals = new ArrayList<>(); |
| for (String imageName : imageSelectionCombo.getItems()) { |
| if (imageName.contains(contents)) { |
| proposals.add(new ContentProposal(imageName, imageName, |
| imageName, position)); |
| } |
| } |
| return proposals.toArray(new IContentProposal[0]); |
| }; |
| } |
| |
| private IValueChangeListener onImageSelectionChange() { |
| return event -> { |
| final IDockerImage selectedImage = model.getSelectedImage(); |
| // skip if the selected image does not exist in the local Docker |
| // host |
| if (selectedImage == null) { |
| model.setExposedPorts( |
| Collections.<ExposedPortModel> emptyList()); |
| return; |
| } |
| final IDockerImageInfo selectedImageInfo = getImageInfo( |
| selectedImage); |
| applyImageInfo(selectedImageInfo); |
| }; |
| } |
| |
| private SelectionListener onPullImage() { |
| return SelectionListener |
| .widgetDefaultSelectedAdapter(e -> pullSelectedImage()); |
| } |
| |
| private IValueChangeListener onPublishAllPortsChange( |
| final Control... controls) { |
| return event -> togglePortMappingControls(controls); |
| } |
| |
| private SelectionListener onSearchImage() { |
| return SelectionListener.widgetSelectedAdapter(e -> { |
| final ImageSearch imageSearchWizard = new ImageSearch( |
| ImageRunSelectionPage.this.model.getSelectedConnection(), |
| ImageRunSelectionPage.this.model.getSelectedImageName(), |
| new RegistryInfo(AbstractRegistry.DOCKERHUB_REGISTRY, |
| true)); |
| final boolean completed = CommandUtils.openWizard(imageSearchWizard, |
| getShell()); |
| if (completed) { |
| model.setSelectedImageName( |
| imageSearchWizard.getSelectedImage()); |
| } |
| }); |
| } |
| |
| private SelectionListener onAddPort( |
| final CheckboxTableViewer exposedPortsTableViewer) { |
| return SelectionListener.widgetSelectedAdapter(e -> { |
| final ContainerPortDialog dialog = new ContainerPortDialog( |
| getShell()); |
| dialog.create(); |
| if (dialog.open() == IDialogConstants.OK_ID) { |
| final ExposedPortModel port = dialog.getPort(); |
| port.setSelected(true); |
| model.addAvailablePort(port); |
| model.getSelectedPorts().add(port); |
| exposedPortsTableViewer.setChecked(port, true); |
| } |
| }); |
| } |
| |
| private SelectionListener onEditPort( |
| final CheckboxTableViewer exposedPortsTableViewer) { |
| return SelectionListener.widgetSelectedAdapter(e -> { |
| final IStructuredSelection selection = exposedPortsTableViewer |
| .getStructuredSelection(); |
| final ExposedPortModel selectedContainerPort = (ExposedPortModel) selection |
| .getFirstElement(); |
| final ContainerPortDialog dialog = new ContainerPortDialog( |
| getShell(), selectedContainerPort); |
| dialog.create(); |
| if (dialog.open() == IDialogConstants.OK_ID) { |
| final ExposedPortModel configuredPort = dialog.getPort(); |
| selectedContainerPort |
| .setContainerPort(configuredPort.getContainerPort()); |
| selectedContainerPort |
| .setHostAddress(configuredPort.getHostAddress()); |
| selectedContainerPort.setHostPort(configuredPort.getHostPort()); |
| exposedPortsTableViewer.refresh(); |
| } |
| }); |
| } |
| |
| private SelectionListener onRemovePorts( |
| final TableViewer portsTableViewer) { |
| return SelectionListener.widgetSelectedAdapter(e -> { |
| final IStructuredSelection selection = portsTableViewer |
| .getStructuredSelection(); |
| for (@SuppressWarnings("unchecked") |
| Iterator<ExposedPortModel> iterator = selection.iterator(); iterator |
| .hasNext();) { |
| final ExposedPortModel port = iterator.next(); |
| model.removeAvailablePort(port); |
| model.getSelectedPorts().remove(port); |
| } |
| }); |
| } |
| |
| /** |
| * Sets the default values from the optional given {@link IDockerImage} and |
| * {@link ILaunchConfiguration} elements |
| */ |
| private void setDefaultValues() { |
| final IDockerImage selectedImage = model.getSelectedImage(); |
| if (selectedImage == null) { |
| return; |
| } |
| final IDockerImageInfo selectedImageInfo = getImageInfo(selectedImage); |
| |
| // skip if a previous launch configuration was provided |
| if (this.lastLaunchConfiguration != null) { |
| try { |
| this.model.setContainerName(lastLaunchConfiguration |
| .getAttribute(CONTAINER_NAME, "")); |
| this.model.setEntrypoint( |
| lastLaunchConfiguration.getAttribute(ENTRYPOINT, "")); |
| this.model.setCommand( |
| lastLaunchConfiguration.getAttribute(COMMAND, "")); |
| this.model.setPublishAllPorts(lastLaunchConfiguration |
| .getAttribute(PUBLISH_ALL_PORTS, false)); |
| final List<String> exposedPortInfos = lastLaunchConfiguration |
| .getAttribute(PUBLISHED_PORTS, |
| Collections.<String> emptyList()); |
| // FIXME: handle the case where ports where added (and selected) |
| // by the user. |
| if (selectedImageInfo != null) { |
| final List<ExposedPortModel> exposedPorts = ExposedPortModel |
| .fromStrings( |
| selectedImageInfo.config().exposedPorts()); |
| model.setExposedPorts(exposedPorts); |
| final List<ExposedPortModel> selectedExposedPorts = ExposedPortModel |
| .fromStrings(exposedPortInfos); |
| this.model.setSelectedPorts( |
| new HashSet<>(selectedExposedPorts)); |
| } |
| |
| // links |
| this.model.setLinks(lastLaunchConfiguration.getAttribute(LINKS, |
| Collections.<String> emptyList())); |
| // other options |
| this.model.setRemoveWhenExits(lastLaunchConfiguration |
| .getAttribute(AUTO_REMOVE, false)); |
| this.model.setInteractiveMode(lastLaunchConfiguration |
| .getAttribute(INTERACTIVE, false)); |
| this.model.setAllocatePseudoTTY(lastLaunchConfiguration |
| .getAttribute(ALLOCATE_PSEUDO_CONSOLE, false)); |
| this.model.setPrivileged(lastLaunchConfiguration |
| .getAttribute(PRIVILEGED, false)); |
| this.model.setBasicSecurity( |
| lastLaunchConfiguration.getAttribute(READONLY, false)); |
| } catch (CoreException e) { |
| Activator.log(e); |
| } |
| } else { |
| applyImageInfo(selectedImageInfo); |
| } |
| } |
| |
| /** |
| * @param selectedImage |
| * @return the corresponding {@link IDockerImageInfo} or <code>null</code> |
| * if something went wrong. |
| */ |
| private IDockerImageInfo getImageInfo(final IDockerImage selectedImage) { |
| try { |
| final FindImageInfoRunnable findImageInfoRunnable = new FindImageInfoRunnable( |
| selectedImage); |
| getContainer().run(true, true, findImageInfoRunnable); |
| final IDockerImageInfo selectedImageInfo = findImageInfoRunnable |
| .getResult(); |
| return selectedImageInfo; |
| } catch (InvocationTargetException | InterruptedException e) { |
| Activator.log(e); |
| } |
| return null; |
| } |
| |
| private void applyImageInfo(final IDockerImageInfo selectedImageInfo) { |
| if (selectedImageInfo != null && selectedImageInfo.config() != null) { |
| final List<ExposedPortModel> exposedPorts = ExposedPortModel |
| .fromStrings(selectedImageInfo.config().exposedPorts()); |
| model.setExposedPorts(exposedPorts); |
| model.setEntrypoint(selectedImageInfo.config().entrypoint()); |
| model.setCommand(selectedImageInfo.config().cmd()); |
| } |
| } |
| |
| private void togglePortMappingControls(final Control... controls) { |
| for (Control control : controls) { |
| if (model.isPublishAllPorts()) { |
| control.setEnabled(false); |
| } else { |
| control.setEnabled(true); |
| } |
| } |
| } |
| |
| private void pullSelectedImage() { |
| try { |
| getContainer().run(true, true, monitor -> { |
| final IDockerConnection connection = model |
| .getSelectedConnection(); |
| final String imageName = model.getSelectedImageName(); |
| monitor.beginTask( |
| WizardMessages.getFormattedString( |
| "ImageRunSelectionPage.pullingTask", imageName), //$NON-NLS-1$ |
| 1); |
| try { |
| connection.pullImage(imageName, |
| new DefaultImagePullProgressHandler(connection, |
| imageName)); |
| } catch (final DockerException e) { |
| Display.getDefault().syncExec(() -> MessageDialog.openError( |
| PlatformUI.getWorkbench().getActiveWorkbenchWindow() |
| .getShell(), |
| DVMessages.getFormattedString(ERROR_PULLING_IMAGE, |
| imageName), |
| e.getMessage())); |
| } finally { |
| monitor.done(); |
| // refresh the widgets |
| model.refreshImageNames(); |
| if (model.getImageNames().contains(imageName)) { |
| model.setSelectedImageName(imageName); |
| model.setSelectedImageNeedsPulling(false); |
| // Force revalidation by changing writeValue which |
| // is made to be a dependency of the ImageCombo |
| // MultiValidator. |
| Display.getDefault().syncExec(() -> { |
| writeValue.setValue( |
| Long.toString(System.currentTimeMillis())); |
| }); |
| } |
| } |
| }); |
| } catch (InvocationTargetException | InterruptedException e) { |
| Activator.log(e); |
| } |
| } |
| |
| private static void setControlsEnabled(final Control[] controls, |
| final boolean enabled) { |
| for (Control control : controls) { |
| control.setEnabled(enabled); |
| } |
| } |
| |
| private class ImageSelectionValidator extends MultiValidator { |
| |
| private final IObservableValue<String> imageSelectionObservable; |
| |
| ImageSelectionValidator( |
| final IObservableValue<String> imageSelectionObservable) { |
| this.imageSelectionObservable = imageSelectionObservable; |
| } |
| |
| @Override |
| protected IStatus validate() { |
| ObservableTracker.getterCalled(writeValue); |
| |
| final String selectedImageName = imageSelectionObservable |
| .getValue(); |
| if (selectedImageName.isEmpty()) { |
| model.setSelectedImageNeedsPulling(false); |
| return ValidationStatus.error(WizardMessages |
| .getString("ImageRunSelectionPage.specifyImageMsg")); //$NON-NLS-1$ |
| } |
| if (model.getSelectedImage() != null) { |
| model.setSelectedImageNeedsPulling(false); |
| return ValidationStatus.ok(); |
| } |
| model.setSelectedImageNeedsPulling(true); |
| return ValidationStatus.warning(WizardMessages.getFormattedString( |
| "ImageRunSelectionPage.imageNotFoundMessage", //$NON-NLS-1$ |
| selectedImageName)); |
| } |
| |
| @Override |
| public IObservableList<IObservable> getTargets() { |
| IObservableList<IObservable> targets = new WritableList<>(); |
| targets.add(imageSelectionObservable); |
| return targets; |
| } |
| |
| } |
| |
| private class ContainerNameValidator extends MultiValidator { |
| |
| private final IDockerConnection connection; |
| |
| private final IObservableValue<String> containerNameObservable; |
| |
| ContainerNameValidator(final IDockerConnection connection, |
| final IObservableValue<String> containerNameObservable) { |
| this.connection = connection; |
| this.containerNameObservable = containerNameObservable; |
| } |
| |
| @Override |
| protected IStatus validate() { |
| final String containerName = containerNameObservable.getValue(); |
| |
| for (IDockerContainer container : connection.getContainers()) { |
| if (container.name().equals(containerName)) { |
| return ValidationStatus.error(WizardMessages.getString( |
| "ImageRunSelectionPage.containerWithSameName")); //$NON-NLS-1$ |
| } |
| } |
| return ValidationStatus.ok(); |
| } |
| |
| @Override |
| public IObservableList<IObservable> getTargets() { |
| IObservableList<IObservable> targets = new WritableList<>(); |
| targets.add(containerNameObservable); |
| return targets; |
| } |
| } |
| |
| } |