diff --git a/plugins/org.jboss.tools.openshift.ui/META-INF/MANIFEST.MF b/plugins/org.jboss.tools.openshift.ui/META-INF/MANIFEST.MF index 0ea3dd666a..484330b007 100644 --- a/plugins/org.jboss.tools.openshift.ui/META-INF/MANIFEST.MF +++ b/plugins/org.jboss.tools.openshift.ui/META-INF/MANIFEST.MF @@ -50,7 +50,8 @@ Require-Bundle: org.jboss.tools.openshift.common.ui;bundle-version="[3.0.0,4.0.0 org.eclipse.ui.editors, org.eclipse.core.expressions;bundle-version="3.5.0", org.eclipse.jst.server.core, - org.eclipse.swt;bundle-version="3.104.1" + org.eclipse.swt;bundle-version="3.104.1", + com.spotify.docker.client;bundle-version="3.4.0" Bundle-RequiredExecutionEnvironment: JavaSE-1.8 Bundle-ActivationPolicy: lazy Export-Package: org.jboss.tools.openshift.internal.ui;x-friends:="org.jboss.tools.openshift.test", diff --git a/plugins/org.jboss.tools.openshift.ui/src/org/jboss/tools/openshift/internal/ui/dockerutils/DockerImageUtils.java b/plugins/org.jboss.tools.openshift.ui/src/org/jboss/tools/openshift/internal/ui/dockerutils/DockerImageUtils.java new file mode 100644 index 0000000000..41ae4a83b7 --- /dev/null +++ b/plugins/org.jboss.tools.openshift.ui/src/org/jboss/tools/openshift/internal/ui/dockerutils/DockerImageUtils.java @@ -0,0 +1,100 @@ +/******************************************************************************* + * Copyright (c) 2016 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.jboss.tools.openshift.internal.ui.dockerutils; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; + +import org.eclipse.linuxtools.docker.core.IDockerConnection; +import org.eclipse.linuxtools.docker.core.IDockerImage; + +/** + * Utility class for {@link IDockerImage}s + */ +public class DockerImageUtils { + + /** + * Checks if an image with the given {@code repo} and {@code tag} exists in + * the given {@code dockerConnection} + *

+ * Workaround until https://bugs.eclipse.org/bugs/show_bug.cgi?id=495243 is + * fixed. + *

+ * + * @param dockerConnection + * the {@link IDockerConnection} + * @param repoName + * the repository/name of the image to look-up + * @param tag + * the image tag + * @return true if match found, false otherwise + */ + public static boolean hasImage(final IDockerConnection dockerConnection, final String repoName, final String tag) { + for (IDockerImage image : dockerConnection.getImages()) { + final Map> repoTags = extractTagsByRepo(image.repoTags()); + for (Entry> entry : repoTags.entrySet()) { + final String repo = entry.getKey(); + final List tags = entry.getValue(); + if (repo != null && repo.equals(repoName) && tags != null && tags.contains(tag)) { + return true; + } + } + } + return false; + } + + /** + * Extracts the org/repo and all the associated tags from the given + * {@code repoTags}, assuming that the given repoTags elements have the + * following format: {@code [org/]repo[:tag]}. Tags are sorted by their + * natural order. + * + * @param repoTags + * the list of repo/tags to analyze + * @return the tags indexed by org/repo + */ + public static Map> extractTagsByRepo(final List repoTags) { + final Map> results = new HashMap<>(); + for (String entry : repoTags) { + final int indexOfColonChar = entry.lastIndexOf(':'); + final String repo = (indexOfColonChar > -1) ? entry.substring(0, indexOfColonChar) : entry; + if (!results.containsKey(repo)) { + results.put(repo, new ArrayList()); + } + if (indexOfColonChar > -1) { + results.get(repo).add(entry.substring(indexOfColonChar + 1)); + } + } + // now sort the tags + for (Entry> entry : results.entrySet()) { + Collections.sort(entry.getValue()); + } + return results; + } + + /** + * Retrieves the {@code 'name:tag'} from {@code '[[registry/]repo/]name:tag'} format of the given {@code imageName} + * @param imageName + * @return the {@code 'name:tag'} + */ + public static String extractImageNameAndTag(final String imageName) { + final int lastIndexOfSlash = imageName.lastIndexOf('/'); + if(lastIndexOfSlash == -1) { + return imageName; + } + return imageName.substring(lastIndexOfSlash + 1); + } +} diff --git a/plugins/org.jboss.tools.openshift.ui/src/org/jboss/tools/openshift/internal/ui/dockerutils/ProgressJob.java b/plugins/org.jboss.tools.openshift.ui/src/org/jboss/tools/openshift/internal/ui/dockerutils/ProgressJob.java new file mode 100644 index 0000000000..de90670be0 --- /dev/null +++ b/plugins/org.jboss.tools.openshift.ui/src/org/jboss/tools/openshift/internal/ui/dockerutils/ProgressJob.java @@ -0,0 +1,74 @@ +package org.jboss.tools.openshift.internal.ui.dockerutils; + +import org.eclipse.core.runtime.IProgressMonitor; +import org.eclipse.core.runtime.IStatus; +import org.eclipse.core.runtime.Status; +import org.eclipse.core.runtime.jobs.Job; + +// FIXME: temporary code duplication until https://bugs.eclipse.org/bugs/show_bug.cgi?id=495251 is done. +public class ProgressJob extends Job { + + private int percentageDone = 0; + private int percentageChange = 0; + + private Object lockObject = new Object(); + + private String jobName; + + public ProgressJob(String name, String jobName) { + super(name); + this.jobName = jobName; + } + + @Override + protected IStatus run(IProgressMonitor monitor) { + monitor.beginTask(jobName, 100); + boolean done = false; + + while (!done) { + if (monitor.isCanceled()) { + return Status.CANCEL_STATUS; + } + // if work percentage has changed...add new amount + int change = getPercentageChange(); + if (change > 0) { + monitor.worked(change); + setPercentageChange(0); + } + // if we are 100% or more done, then we are done + if (percentageDone >= 100) { + done = true; + } + // otherwise, sleep and then loop again + try { + Thread.sleep(500); + } catch (InterruptedException e) { + done = true; + } + } + monitor.done(); + return Status.OK_STATUS; + } + + private int getPercentageChange() { + synchronized (lockObject) { + return percentageChange; + } + } + + private void setPercentageChange(int percentChange) { + synchronized (lockObject) { + this.percentageChange = percentChange; + } + } + + public void setPercentageDone(int percentage) { + synchronized (lockObject) { + if (percentage > percentageDone) { + percentageChange = percentage - percentageDone; + percentageDone = percentage; + } + } + } + +} diff --git a/plugins/org.jboss.tools.openshift.ui/src/org/jboss/tools/openshift/internal/ui/dockerutils/PushImageToRegistryJob.java b/plugins/org.jboss.tools.openshift.ui/src/org/jboss/tools/openshift/internal/ui/dockerutils/PushImageToRegistryJob.java new file mode 100644 index 0000000000..0d5eb6d652 --- /dev/null +++ b/plugins/org.jboss.tools.openshift.ui/src/org/jboss/tools/openshift/internal/ui/dockerutils/PushImageToRegistryJob.java @@ -0,0 +1,106 @@ +/******************************************************************************* + * Copyright (c) 2016 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.jboss.tools.openshift.internal.ui.dockerutils; + +import java.net.MalformedURLException; +import java.net.URL; + +import org.eclipse.core.runtime.IProgressMonitor; +import org.eclipse.core.runtime.IStatus; +import org.eclipse.core.runtime.Status; +import org.eclipse.core.runtime.jobs.Job; +import org.eclipse.linuxtools.docker.core.DockerException; +import org.eclipse.linuxtools.docker.core.IDockerConnection; +import org.eclipse.linuxtools.docker.core.IRegistryAccount; +import org.eclipse.linuxtools.internal.docker.ui.views.ImagePushProgressHandler; +import org.jboss.tools.openshift.internal.common.core.job.AbstractDelegatingMonitorJob; +import org.jboss.tools.openshift.internal.ui.OpenShiftUIActivator; + +import com.spotify.docker.client.DockerCertificateException; + +/** + * {@link Job} to push an image from a Docker daemon into the OpenShift registry + */ +public class PushImageToRegistryJob extends AbstractDelegatingMonitorJob { + + private final IDockerConnection dockerConnection; + + private final IRegistryAccount registryAccount; + + private final String imageName; + + private final String openshiftProject; + + /** + * Constructor + * @param dockerConnection the Docker connection to use + * @param registryAccount the registry account to push the image into + * @param openshiftProject the name of the OpenShift project, because the image has to be into the same namespace + * @param imageName the name of the image + */ + public PushImageToRegistryJob(final IDockerConnection dockerConnection, final IRegistryAccount registryAccount, final String openshiftProject, final String imageName) { + super("Pushing Docker image to OpenShift registry..."); + this.dockerConnection = dockerConnection; + this.registryAccount = registryAccount; + this.imageName = imageName; + this.openshiftProject = openshiftProject; + } + + @Override + protected IStatus doRun(final IProgressMonitor monitor) { + monitor.beginTask("Pushing image to registry", 1); + final String tmpImageName = getPushToRegistryImageName(); + try { + // first, we need to tag the image with the OpenShift target + // project + this.dockerConnection.tagImage(imageName, tmpImageName); + // then we can push that image with the new name + this.dockerConnection.pushImage(tmpImageName, registryAccount, + new ImagePushProgressHandler(this.dockerConnection, tmpImageName)); + } + // FIXME: needs to catch DockerCertificateException until + // https://bugs.eclipse.org/bugs/show_bug.cgi?id=495249 is fixed + catch (DockerException | InterruptedException | DockerCertificateException e) { + return new Status(IStatus.ERROR, OpenShiftUIActivator.PLUGIN_ID, + "Failed to push the selected Docker image into OpenShift registry", e); + } finally { + // we need to untag the image, even if the push operation failed + try { + this.dockerConnection.removeTag(tmpImageName); + } catch (DockerException | InterruptedException e) { + return new Status(IStatus.WARNING, OpenShiftUIActivator.PLUGIN_ID, + "Pushed the selected Docker image into OpenShift registry but failed to untag it afterwards", + e); + } + monitor.done(); + } + // TODO Auto-generated method stub + return Status.OK_STATUS; + } + + /** + * @return the name used to push to the registry. + */ + public String getPushToRegistryImageName() { + try { + final String registryHostname = new URL(this.registryAccount.getServerAddress()).getHost(); + final String tmpImageName = registryHostname + '/' + this.openshiftProject + '/' + DockerImageUtils.extractImageNameAndTag(this.imageName); + return tmpImageName; + } catch (MalformedURLException e) { + OpenShiftUIActivator.getDefault().getLog().log(new Status(IStatus.ERROR, OpenShiftUIActivator.PLUGIN_ID, + "Failed to push the selected Docker image into OpenShift registry", e)); + return null; + } + + } + +} diff --git a/plugins/org.jboss.tools.openshift.ui/src/org/jboss/tools/openshift/internal/ui/dockerutils/package-info.java b/plugins/org.jboss.tools.openshift.ui/src/org/jboss/tools/openshift/internal/ui/dockerutils/package-info.java new file mode 100644 index 0000000000..4076d9adbd --- /dev/null +++ b/plugins/org.jboss.tools.openshift.ui/src/org/jboss/tools/openshift/internal/ui/dockerutils/package-info.java @@ -0,0 +1,15 @@ +/******************************************************************************* + * Copyright (c) 2016 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.jboss.tools.openshift.internal.ui.dockerutils; \ No newline at end of file diff --git a/plugins/org.jboss.tools.openshift.ui/src/org/jboss/tools/openshift/internal/ui/handler/DeployImageHandler.java b/plugins/org.jboss.tools.openshift.ui/src/org/jboss/tools/openshift/internal/ui/handler/DeployImageHandler.java index d816ec6e9e..0acd3c1ed4 100644 --- a/plugins/org.jboss.tools.openshift.ui/src/org/jboss/tools/openshift/internal/ui/handler/DeployImageHandler.java +++ b/plugins/org.jboss.tools.openshift.ui/src/org/jboss/tools/openshift/internal/ui/handler/DeployImageHandler.java @@ -117,7 +117,7 @@ public void done(IJobChangeEvent event) { @Override public void run() { DeployImageWizard wizard = new DeployImageWizard(image, connection, project, connected[0]); - WizardUtils.openWizardDialog(600, 1500, wizard, shell); + WizardUtils.openWizardDialog(500, 500, wizard, shell); } }); } diff --git a/plugins/org.jboss.tools.openshift.ui/src/org/jboss/tools/openshift/internal/ui/job/DeployImageJob.java b/plugins/org.jboss.tools.openshift.ui/src/org/jboss/tools/openshift/internal/ui/job/DeployImageJob.java index 6ae86f73ac..0e6a805414 100644 --- a/plugins/org.jboss.tools.openshift.ui/src/org/jboss/tools/openshift/internal/ui/job/DeployImageJob.java +++ b/plugins/org.jboss.tools.openshift.ui/src/org/jboss/tools/openshift/internal/ui/job/DeployImageJob.java @@ -33,6 +33,7 @@ import org.jboss.tools.openshift.internal.core.Trace; import org.jboss.tools.openshift.internal.core.util.OpenShiftProjectUtils; import org.jboss.tools.openshift.internal.ui.OpenShiftUIActivator; +import org.jboss.tools.openshift.internal.ui.dockerutils.DockerImageUtils; import org.jboss.tools.openshift.internal.ui.wizard.common.EnvironmentVariable; import org.jboss.tools.openshift.internal.ui.wizard.common.IResourceLabelsPageModel.Label; import org.jboss.tools.openshift.internal.ui.wizard.deployimage.IDeployImageParameters; @@ -138,7 +139,13 @@ private Collection createResources(Connection connection, Collection private Map generateResources(final Connection connection, final String name) { final IResourceFactory factory = connection.getResourceFactory(); final IProject project = parameters.getProject(); - DockerImageURI sourceImage = new DockerImageURI(parameters.getImageName()); + String imageName; + if (parameters.isPushImageToRegistry()) { + imageName = project.getNamespace() +"/" + DockerImageUtils.extractImageNameAndTag(parameters.getImageName()); + } else { + imageName = parameters.getImageName(); + } + DockerImageURI sourceImage = new DockerImageURI(imageName); Map resources = new HashMap<>(4); diff --git a/plugins/org.jboss.tools.openshift.ui/src/org/jboss/tools/openshift/internal/ui/wizard/deployimage/DeployImagePage.java b/plugins/org.jboss.tools.openshift.ui/src/org/jboss/tools/openshift/internal/ui/wizard/deployimage/DeployImagePage.java index 24401a7a97..5097ba32b7 100644 --- a/plugins/org.jboss.tools.openshift.ui/src/org/jboss/tools/openshift/internal/ui/wizard/deployimage/DeployImagePage.java +++ b/plugins/org.jboss.tools.openshift.ui/src/org/jboss/tools/openshift/internal/ui/wizard/deployimage/DeployImagePage.java @@ -63,6 +63,7 @@ import org.jboss.tools.common.ui.WizardUtils; import org.jboss.tools.common.ui.databinding.ParametrizableWizardPageSupport; import org.jboss.tools.common.ui.databinding.ValueBindingBuilder; +import org.jboss.tools.openshift.common.core.utils.UrlUtils; import org.jboss.tools.openshift.core.connection.Connection; import org.jboss.tools.openshift.internal.common.ui.connection.ConnectionColumLabelProvider; import org.jboss.tools.openshift.internal.common.ui.databinding.IsNotNull2BooleanConverter; @@ -88,13 +89,16 @@ * @author jeff.cantrill */ public class DeployImagePage extends AbstractOpenShiftWizardPage { + private static final String MISSING_DOCKER_CONNECTION_MSG = "You must select a Docker connection."; static String DEPLOY_IMAGE_PAGE_NAME = "Deployment Config Settings Page"; private static final String PAGE_DESCRIPTION = "This page allows you to choose an image and the name to be used for the deployed resources."; - private IDeployImagePageModel model; + private static final int NUM_COLUMS = 4; + + private final IDeployImagePageModel model; ContentProposalAdapter imageNameProposalAdapter; @@ -175,7 +179,9 @@ protected void onPageWillGetActivated(final Direction progress, final PageChangi @Override protected void doCreateControls(Composite parent, DataBindingContext dbc) { - GridLayoutFactory.fillDefaults().numColumns(3).margins(10, 10).applyTo(parent); + GridLayoutFactory.fillDefaults().numColumns(NUM_COLUMS) + .margins(10, 10) + .applyTo(parent); createOpenShiftConnectionControl(parent, dbc); createProjectControl(parent, dbc); createSeparator(parent); @@ -185,7 +191,18 @@ protected void doCreateControls(Composite parent, DataBindingContext dbc) { createDockerConnectionInfoControl(parent, dbc); } createImageNameControls(parent, dbc); - new ResourceNameControl().doCreateControl(parent, dbc, model); + new ResourceNameControl() { + @Override + protected void layoutText(Text resourceNameText) { + GridDataFactory.fillDefaults() + .align(SWT.FILL, SWT.CENTER) + .grab(true, false) + .span(NUM_COLUMS -1, 1) + .applyTo(resourceNameText); + } + }.doCreateControl(parent, dbc, model); + createSeparator(parent); + createPushToRegistrySettings(parent, dbc); } private void createSeparator(Composite parent) { @@ -193,11 +210,30 @@ private void createSeparator(Composite parent) { .fillDefaults() .align(SWT.FILL, SWT.BEGINNING) .grab(true, false) - .span(3, 1) + .span(NUM_COLUMS, 1) .applyTo(new Label(parent, SWT.SEPARATOR | SWT.HORIZONTAL)); } - private SelectionAdapter onSearch(Text txtImage) { + private SelectionAdapter onBrowseImage() { + return new SelectionAdapter() { + @Override + public void widgetSelected(SelectionEvent e) { + if (model.getDockerConnection() == null) { + MessageDialog.openError(getShell(), "A Docker connection must be selected", MISSING_DOCKER_CONNECTION_MSG); + return; + } + final ListDockerImagesWizard wizard = new ListDockerImagesWizard(model.getDockerConnection(), model.getImageName()); + final OkCancelButtonWizardDialog wizardDialog = new OkCancelButtonWizardDialog(getShell(), wizard); + wizardDialog.setPageSize(500, 400); + if(Window.OK == wizardDialog.open()){ + //this bypasses validation + model.setImageName(wizard.getSelectedImageName()); + } + } + }; + } + + private SelectionAdapter onSearchImage(final Text txtImage) { return new SelectionAdapter() { @Override public void widgetSelected(SelectionEvent e) { @@ -223,7 +259,7 @@ private void createDockerConnectionControl(Composite parent, DataBindingContext GridDataFactory.fillDefaults() .align(SWT.FILL, SWT.CENTER) .grab(true, false) - .span(1, 1) + .span(NUM_COLUMS - 2, 1) .applyTo(connectionViewer.getControl()); connectionViewer.setContentProvider(new ObservableListContentProvider()); @@ -259,7 +295,7 @@ public String getText(Object element) { .applyTo(newDockerConnectionButton); UIUtils.setDefaultButtonWidth(newDockerConnectionButton); newDockerConnectionButton.addSelectionListener(onNewDockerConnectionClicked()); - + dbc.addValidationStatusProvider(validator); } @@ -304,7 +340,7 @@ private void createDockerConnectionInfoControl(Composite parent, DataBindingCont connectionText.setBackground(lblConnection.getBackground()); GridDataFactory.fillDefaults() .align(SWT.FILL, SWT.CENTER) - .span(2, 1) + .span(NUM_COLUMS - 1, 1) .grab(true, false) .applyTo(connectionText); final IObservableValue connnectionTextObservable = WidgetProperties.text(SWT.None).observe(connectionText); @@ -332,7 +368,7 @@ private void createOpenShiftConnectionControl(Composite parent, DataBindingConte connectionText.setBackground(lblConnection.getBackground()); GridDataFactory.fillDefaults() .align(SWT.FILL, SWT.CENTER) - .span(2, 1) + .span(NUM_COLUMS - 1, 1) .grab(true, false) .applyTo(connectionText); final IObservableValue connnectionTextObservable = WidgetProperties.text(SWT.None).observe(connectionText); @@ -362,6 +398,7 @@ private void createProjectControl(Composite parent, DataBindingContext dbc) { .align(SWT.FILL, SWT.CENTER) .grab(true, false) .hint(SWT.DEFAULT, 30) + .span(NUM_COLUMS - 2, 1) .applyTo(cmboProject.getControl()); final OpenShiftExplorerLabelProvider labelProvider = new OpenShiftExplorerLabelProvider(); @@ -391,7 +428,7 @@ private void createProjectControl(Composite parent, DataBindingContext dbc) { .applyTo(newProjectButton); UIUtils.setDefaultButtonWidth(newProjectButton); newProjectButton.addSelectionListener(onNewProjectClicked()); - + dbc.addValidationStatusProvider(validator); cmboProject.getControl().forceFocus(); @@ -456,15 +493,23 @@ public void insertControlContents(Control control, } }, getImageNameContentProposalProvider(imageNameText), null, null); - //browse + + // List local Docker images + Button btnDockerBrowse = new Button(parent, SWT.NONE); + btnDockerBrowse.setText("Browse..."); + btnDockerBrowse.setToolTipText("Look-up an image by browsing the Docker daemon"); + btnDockerBrowse.addSelectionListener(onBrowseImage()); + GridDataFactory.fillDefaults().align(SWT.FILL, SWT.CENTER).applyTo(btnDockerBrowse); + ValueBindingBuilder.bind(WidgetProperties.enabled().observe(btnDockerBrowse)).notUpdatingParticipant() + .to(BeanProperties.value(IDeployImagePageModel.PROPERTY_DOCKER_CONNECTION).observe(model)) + .converting(new IsNotNull2BooleanConverter()).in(dbc); + + // search on Docker registry (Docker Hub) Button btnDockerSearch = new Button(parent, SWT.NONE); btnDockerSearch.setText("Search..."); - btnDockerSearch.setToolTipText("Look-up an image by browsing the docker daemon"); - btnDockerSearch.addSelectionListener(onSearch(imageNameText)); - GridDataFactory.fillDefaults() - .align(SWT.FILL, SWT.CENTER) - .applyTo(btnDockerSearch); - + btnDockerSearch.setToolTipText("Search an image on the Docker registry"); + btnDockerSearch.addSelectionListener(onSearchImage(imageNameText)); + GridDataFactory.fillDefaults().align(SWT.FILL, SWT.CENTER).applyTo(btnDockerSearch); ValueBindingBuilder .bind(WidgetProperties.enabled().observe(btnDockerSearch)) .notUpdatingParticipant() @@ -472,7 +517,110 @@ public void insertControlContents(Control control, .converting(new IsNotNull2BooleanConverter()) .in(dbc); } + + @SuppressWarnings("unchecked") + private void createPushToRegistrySettings(final Composite parent, final DataBindingContext dbc) { + // checkbox + final Button pushImageToRegistryButton = new Button(parent, SWT.CHECK); + pushImageToRegistryButton.setText("Push Image to Registry"); + GridDataFactory.fillDefaults().align(SWT.FILL, SWT.CENTER).span(NUM_COLUMS, 1).applyTo(pushImageToRegistryButton); + final IObservableValue pushImageToRegistryButtonObservable = BeanProperties + .value(IDeployImagePageModel.PROPERTY_PUSH_IMAGE_TO_REGISTRY).observe(model); + ValueBindingBuilder.bind(WidgetProperties.selection().observe(pushImageToRegistryButton)) + .to(pushImageToRegistryButtonObservable).in(dbc); + + // registry location + final Label registryLocationLabel = new Label(parent, SWT.NONE); + registryLocationLabel.setText("Image Registry URL:"); + GridDataFactory.fillDefaults().align(SWT.FILL, SWT.CENTER).grab(true, false).indent(30, 0) + .applyTo(registryLocationLabel); + final Text registryLocationText = new Text(parent, SWT.BORDER); + GridDataFactory.fillDefaults().align(SWT.FILL, SWT.CENTER).grab(true, false).span(NUM_COLUMS - 1, 1) + .applyTo(registryLocationText); + final IObservableValue registryLocationObservable = BeanProperties + .value(IDeployImagePageModel.PROPERTY_TARGET_REGISTRY_LOCATION).observe(model); + ValueBindingBuilder.bind(WidgetProperties.text(SWT.Modify).observe(registryLocationText)) + .to(registryLocationObservable).in(dbc); + ValueBindingBuilder.bind(WidgetProperties.enabled().observe(registryLocationText)) + .to(pushImageToRegistryButtonObservable).in(dbc); + + // username to authenticate on registry + final Label registryUsernameLabel = new Label(parent, SWT.NONE); + registryUsernameLabel.setText("Username:"); + GridDataFactory.fillDefaults().align(SWT.FILL, SWT.CENTER).grab(true, false).indent(30, 0) + .applyTo(registryUsernameLabel); + final Text registryUsernameText = new Text(parent, SWT.BORDER); + GridDataFactory.fillDefaults().align(SWT.FILL, SWT.CENTER).grab(true, false).span(NUM_COLUMS - 1, 1) + .applyTo(registryUsernameText); + final IObservableValue registryUsernameObservable = BeanProperties + .value(IDeployImagePageModel.PROPERTY_TARGET_REGISTRY_USERNAME).observe(model); + ValueBindingBuilder.bind(WidgetProperties.text(SWT.Modify).observe(registryUsernameText)) + .to(registryUsernameObservable).in(dbc); + ValueBindingBuilder.bind(WidgetProperties.enabled().observe(registryUsernameText)) + .to(pushImageToRegistryButtonObservable).in(dbc); + + // password to authenticate on registry + final Label registryPasswordLabel = new Label(parent, SWT.NONE); + registryPasswordLabel.setText("Password:"); + GridDataFactory.fillDefaults().align(SWT.FILL, SWT.CENTER).grab(true, false).indent(30, 0) + .applyTo(registryPasswordLabel); + final Text registryPasswordText = new Text(parent, SWT.BORDER + SWT.PASSWORD); + GridDataFactory.fillDefaults().align(SWT.FILL, SWT.CENTER).grab(true, false).span(NUM_COLUMS - 1, 1) + .applyTo(registryPasswordText); + final IObservableValue registryPasswordObservable = BeanProperties + .value(IDeployImagePageModel.PROPERTY_TARGET_REGISTRY_PASSWORD).observe(model); + ValueBindingBuilder.bind(WidgetProperties.text(SWT.Modify).observe(registryPasswordText)) + .to(registryPasswordObservable).in(dbc); + ValueBindingBuilder.bind(WidgetProperties.enabled().observe(registryPasswordText)) + .to(pushImageToRegistryButtonObservable).in(dbc); + + // validation + final PushImageToRegistryStatusProvider validator = new PushImageToRegistryStatusProvider( + pushImageToRegistryButtonObservable, registryLocationObservable, registryUsernameObservable, + registryPasswordObservable); + dbc.addValidationStatusProvider(validator); + + } + class PushImageToRegistryStatusProvider extends MultiValidator { + + private final IObservableValue pushImageToRegistryObservable; + private final IObservableValue targetRegistryLocationObservable; + private final IObservableValue targetRegistryUsernameObservable; + private final IObservableValue targetRegistryPasswordObservable; + + PushImageToRegistryStatusProvider(final IObservableValue pushImageToRegistryObservable, + final IObservableValue targetRegistryLocationObservable, final IObservableValue targetRegistryUsernameObservable, + final IObservableValue targetRegistryPasswordObservable) { + this.pushImageToRegistryObservable = pushImageToRegistryObservable; + this.targetRegistryLocationObservable = targetRegistryLocationObservable; + this.targetRegistryUsernameObservable = targetRegistryUsernameObservable; + this.targetRegistryPasswordObservable = targetRegistryPasswordObservable; + } + + @Override + protected IStatus validate() { + final boolean pushImageToRegistry = pushImageToRegistryObservable.getValue(); + final String targetRegistryLocation = targetRegistryLocationObservable.getValue(); + final String targetRegistryUsername = targetRegistryUsernameObservable.getValue(); + final String targetRegistryPassword = targetRegistryPasswordObservable.getValue(); + if (pushImageToRegistry) { + if (targetRegistryLocation == null || targetRegistryLocation.isEmpty()) { + return ValidationStatus.error("Please specify location of the Docker registry to push the image"); + } else if(!UrlUtils.hasScheme(targetRegistryLocation)) { + return ValidationStatus.error("Please provide a valid image registry (HTTP/S) URL."); + } + if (targetRegistryUsername == null || targetRegistryUsername.isEmpty()) { + return ValidationStatus.info("The username to authenticate to the target registry is missing. Authentication may fail."); + } + if (targetRegistryPassword == null || targetRegistryPassword.isEmpty()) { + return ValidationStatus.info("The password to authenticate to the target registry is missing. Authentication may fail."); + } + } + return Status.OK_STATUS; + } + } + /** * Creates an {@link IContentProposalProvider} to propose * {@link IDockerImage} names based on the current text. @@ -572,4 +720,6 @@ protected IStatus updateUI(IProgressMonitor monitor) { } }; } + + } diff --git a/plugins/org.jboss.tools.openshift.ui/src/org/jboss/tools/openshift/internal/ui/wizard/deployimage/DeployImageWizard.java b/plugins/org.jboss.tools.openshift.ui/src/org/jboss/tools/openshift/internal/ui/wizard/deployimage/DeployImageWizard.java index 2aa5800e84..d24cb5de04 100644 --- a/plugins/org.jboss.tools.openshift.ui/src/org/jboss/tools/openshift/internal/ui/wizard/deployimage/DeployImageWizard.java +++ b/plugins/org.jboss.tools.openshift.ui/src/org/jboss/tools/openshift/internal/ui/wizard/deployimage/DeployImageWizard.java @@ -11,17 +11,26 @@ import static org.jboss.tools.common.ui.WizardUtils.runInWizard; import java.lang.reflect.InvocationTargetException; +import java.util.List; import org.eclipse.core.runtime.IStatus; import org.eclipse.core.runtime.jobs.IJobChangeEvent; import org.eclipse.core.runtime.jobs.Job; import org.eclipse.core.runtime.jobs.JobChangeAdapter; import org.eclipse.jface.wizard.IWizardPage; +import org.eclipse.linuxtools.docker.core.DockerException; import org.eclipse.linuxtools.docker.core.IDockerConnection; import org.eclipse.linuxtools.docker.core.IDockerImage; +import org.eclipse.linuxtools.docker.core.IDockerImageSearchResult; +import org.eclipse.linuxtools.docker.core.IRegistryAccount; +import org.eclipse.linuxtools.docker.core.IRepositoryTag; import org.eclipse.swt.widgets.Display; +import org.eclipse.swt.widgets.Shell; +import org.eclipse.ui.PlatformUI; import org.jboss.tools.common.ui.JobUtils; +import org.jboss.tools.foundation.core.jobs.DelegatingProgressMonitor; import org.jboss.tools.openshift.common.ui.wizard.AbstractOpenShiftWizard; +import org.jboss.tools.openshift.core.ICommonAttributes; import org.jboss.tools.openshift.core.connection.Connection; import org.jboss.tools.openshift.core.connection.ConnectionsRegistryUtil; import org.jboss.tools.openshift.internal.common.core.UsageStats; @@ -30,6 +39,7 @@ import org.jboss.tools.openshift.internal.common.ui.utils.OpenShiftUIUtils; import org.jboss.tools.openshift.internal.ui.OpenShiftUIActivator; import org.jboss.tools.openshift.internal.ui.dialog.ResourceSummaryDialog; +import org.jboss.tools.openshift.internal.ui.dockerutils.PushImageToRegistryJob; import org.jboss.tools.openshift.internal.ui.job.DeployImageJob; import org.jboss.tools.openshift.internal.ui.job.RefreshResourcesJob; import org.jboss.tools.openshift.internal.ui.wizard.common.ResourceLabelsPage; @@ -65,7 +75,12 @@ public DeployImageWizard(IDockerImage image, Connection connection, IProject pro model.setConnection(connection); } } - + if(connection != null) { + model.setTargetRegistryLocation( + (String) connection.getExtendedProperties().get(ICommonAttributes.IMAGE_REGISTRY_URL_KEY)); + model.setTargetRegistryUsername(connection.getUsername()); + model.setTargetRegistryPassword(connection.getToken()); + } model.setStartedWithActiveConnection(isConnected); setNeedsProgressMonitor(true); @@ -93,7 +108,40 @@ public IWizardPage getStartingPage() { @Override public boolean performFinish() { - final DeployImageJob deployJob = new DeployImageJob( getModel()); + // checks if we need to push the image, first + final Job job = getJobChain(getModel(), PlatformUI.getWorkbench().getActiveWorkbenchWindow().getShell()); + job.addJobChangeListener(new JobChangeAdapter() { + @Override + public void done(IJobChangeEvent event) { + final IStatus status = event.getResult(); + UsageStats.getInstance().newV3Application( getModel().getConnection().getHost(), isFailed(status)); + super.done(event); + } + }); + job.schedule(); + + return true; + } + + /** + * Gets the Job to run as a chain of smaller jobs, depending on the use-case + * @param model the wizard model + * @param shell the current shell + * @return + */ + private Job getJobChain(final IDeployImageParameters model, final Shell shell) { + final DeployImageJob deployJob = getDeployImageJob(getModel(), getShell()); + final boolean pushImageToRegistry = model.isPushImageToRegistry(); + if(pushImageToRegistry) { + final PushImageToRegistryJob pushImageToRegistryJob = getPushImageToRegistryJob(model); + return new JobChainBuilder(pushImageToRegistryJob).runWhenSuccessfullyDone(deployJob) + .runWhenSuccessfullyDone(new RefreshResourcesJob(deployJob, true)).build(); + } + return new JobChainBuilder(deployJob).runWhenSuccessfullyDone(new RefreshResourcesJob(deployJob, true)).build(); + } + + private static DeployImageJob getDeployImageJob(final IDeployImageParameters model, final Shell shell) { + final DeployImageJob deployJob = new DeployImageJob(model); deployJob.addJobChangeListener(new JobChangeAdapter(){ @Override @@ -104,7 +152,7 @@ public void done(IJobChangeEvent event) { @Override public void run() { new ResourceSummaryDialog( - getShell(), + shell, deployJob.getResources(), TITLE, deployJob.getSummaryMessage()).open(); @@ -114,25 +162,62 @@ public void run() { } } }); - boolean success = false; - try { - Job job = new JobChainBuilder(deployJob) - .runWhenSuccessfullyDone(new RefreshResourcesJob(deployJob, true)) - .build(); - IStatus status = runInWizard( - job, - deployJob.getDelegatingProgressMonitor(), - getContainer()); - success = isFailed(status); - } catch (InvocationTargetException | InterruptedException e) { - OpenShiftUIActivator.getDefault().getLogger().logError(e); - success = false; - } finally { - UsageStats.getInstance().newV3Application( getModel().getConnection().getHost(), success); - } - return success; + return deployJob; + } + + private static PushImageToRegistryJob getPushImageToRegistryJob(final IDeployImageParameters model) { + final IDockerConnection dockerConnection = model.getDockerConnection(); + final String imageName = model.getImageName(); + final String deployProjectName = model.getProject().getName(); + final IRegistryAccount registryAccount = new IRegistryAccount() { + + @Override + public String getServerAddress() { + return model.getTargetRegistryLocation(); + } + + @Override + public String getUsername() { + return model.getTargetRegistryUsername(); + } + + @Override + public char[] getPassword() { + return model.getTargetRegistryPassword().toCharArray(); + } + + @Override + public String getEmail() { + return null; + } + + @Override + public List getTags(String arg0) throws DockerException { + return null; + } + + @Override + public boolean isVersion2() { + return false; + } + + @Override + public List getImages(String arg0) throws DockerException { + return null; + } + }; + return new PushImageToRegistryJob(dockerConnection, registryAccount, deployProjectName, imageName); } + /** + * Checks if the given {@code status} + * + * @param status + * the {@link IStatus} to check + * @return true if the given status severity is + * {@link IStatus.OK} or {@link IStatus.WARNING}, false + * otherwise. + */ public static boolean isFailed(IStatus status) { return JobUtils.isOk(status) || JobUtils.isWarning(status); } @@ -140,7 +225,7 @@ public static boolean isFailed(IStatus status) { @Override public void dispose() { super.dispose(); - getModel().dispose(); + //getModel().dispose(); } } diff --git a/plugins/org.jboss.tools.openshift.ui/src/org/jboss/tools/openshift/internal/ui/wizard/deployimage/DeployImageWizardModel.java b/plugins/org.jboss.tools.openshift.ui/src/org/jboss/tools/openshift/internal/ui/wizard/deployimage/DeployImageWizardModel.java index 655ba00a54..dcf7eddfbb 100644 --- a/plugins/org.jboss.tools.openshift.ui/src/org/jboss/tools/openshift/internal/ui/wizard/deployimage/DeployImageWizardModel.java +++ b/plugins/org.jboss.tools.openshift.ui/src/org/jboss/tools/openshift/internal/ui/wizard/deployimage/DeployImageWizardModel.java @@ -20,6 +20,7 @@ import java.util.List; import java.util.Map; import java.util.Set; +import java.util.Map.Entry; import java.util.stream.Collectors; import org.apache.commons.lang.StringUtils; @@ -32,6 +33,7 @@ import org.eclipse.linuxtools.docker.core.DockerConnectionManager; import org.eclipse.linuxtools.docker.core.IDockerConnection; import org.eclipse.linuxtools.docker.core.IDockerConnectionManagerListener2; +import org.eclipse.linuxtools.docker.core.IDockerImage; import org.eclipse.linuxtools.docker.core.IDockerImageInfo; import org.jboss.tools.openshift.common.core.connection.ConnectionsRegistrySingleton; import org.jboss.tools.openshift.core.connection.Connection; @@ -40,9 +42,11 @@ import org.jboss.tools.openshift.internal.core.models.PortSpecAdapter; import org.jboss.tools.openshift.internal.core.util.OpenShiftProjectUtils; import org.jboss.tools.openshift.internal.ui.OpenShiftUIActivator; +import org.jboss.tools.openshift.internal.ui.dockerutils.DockerImageUtils; import org.jboss.tools.openshift.internal.ui.wizard.common.EnvironmentVariable; import org.jboss.tools.openshift.internal.ui.wizard.common.EnvironmentVariablesPageModel; import org.jboss.tools.openshift.internal.ui.wizard.common.ResourceLabelsPageModel; +import org.jboss.tools.openshift.internal.ui.wizard.deployimage.ListDockerImagesWizardModel.DockerImageTag; import com.openshift.restclient.ResourceKind; import com.openshift.restclient.images.DockerImageURI; @@ -86,6 +90,11 @@ public class DeployImageWizardModel private boolean originatedFromDockerExplorer; private boolean isStartedWithActiveConnection = false; private IDockerImageMetadata imageMeta; + private boolean pushImageToRegistry = false; + private String targetRegistryLocation; + private String targetRegistryUsername; + private String targetRegistryPassword; + private final List imageNames = new ArrayList<>(); @@ -277,6 +286,50 @@ public void setImageName(String imageName, boolean forceUpdate) { setImageName(imageName); } + @Override + public boolean isPushImageToRegistry() { + return this.pushImageToRegistry; + } + + @Override + public void setPushImageToRegistry(final boolean pushImageToRegistry) { + firePropertyChange(PROPERTY_PUSH_IMAGE_TO_REGISTRY, this.pushImageToRegistry, + this.pushImageToRegistry = pushImageToRegistry); + } + + @Override + public String getTargetRegistryLocation() { + return this.targetRegistryLocation; + } + + @Override + public void setTargetRegistryLocation(final String targetRegistryLocation) { + firePropertyChange(PROPERTY_TARGET_REGISTRY_LOCATION, this.targetRegistryLocation, + this.targetRegistryLocation = targetRegistryLocation); + } + + @Override + public String getTargetRegistryUsername() { + return this.targetRegistryUsername; + } + + @Override + public void setTargetRegistryUsername(final String targetRegistryUsername) { + firePropertyChange(PROPERTY_TARGET_REGISTRY_USERNAME, this.targetRegistryUsername, + this.targetRegistryUsername = targetRegistryUsername); + } + + @Override + public String getTargetRegistryPassword() { + return this.targetRegistryPassword; + } + + @Override + public void setTargetRegistryPassword(final String targetRegistryPassword) { + firePropertyChange(PROPERTY_TARGET_REGISTRY_PASSWORD, this.targetRegistryPassword, + this.targetRegistryPassword = targetRegistryPassword); + } + @Override public boolean initializeContainerInfo() { this.imageMeta = lookupImageMetadata(); @@ -500,9 +553,12 @@ public void setDockerConnection(IDockerConnection dockerConnection) { if(dockerConnection == null) { return; } - this.imageNames.addAll(dockerConnection.getImages().stream() - .filter(image -> !image.isDangling() && !image.isIntermediateImage()) - .flatMap(image -> image.repoTags().stream()).sorted().collect(Collectors.toList())); + final List images = dockerConnection.getImages(); + if(images != null) { + this.imageNames.addAll(dockerConnection.getImages().stream() + .filter(image -> !image.isDangling() && !image.isIntermediateImage()) + .flatMap(image -> image.repoTags().stream()).sorted().collect(Collectors.toList())); + } } @@ -514,7 +570,8 @@ protected IDockerImageMetadata lookupImageMetadata() { final DockerImageURI imageURI = new DockerImageURI(this.imageName); final String repo = imageURI.getUriWithoutTag(); final String tag = StringUtils.defaultIfBlank(imageURI.getTag(), "latest"); - if (dockerConnection != null && dockerConnection.hasImage(repo, tag)) { + + if (dockerConnection != null && DockerImageUtils.hasImage(dockerConnection, repo, tag)) { final IDockerImageInfo info = dockerConnection.getImageInfo(this.imageName); return new DockerConfigMetaData(info); } else if (this.project != null) { diff --git a/plugins/org.jboss.tools.openshift.ui/src/org/jboss/tools/openshift/internal/ui/wizard/deployimage/IDeployImagePageModel.java b/plugins/org.jboss.tools.openshift.ui/src/org/jboss/tools/openshift/internal/ui/wizard/deployimage/IDeployImagePageModel.java index cb98a9fcd8..4dd6deb585 100644 --- a/plugins/org.jboss.tools.openshift.ui/src/org/jboss/tools/openshift/internal/ui/wizard/deployimage/IDeployImagePageModel.java +++ b/plugins/org.jboss.tools.openshift.ui/src/org/jboss/tools/openshift/internal/ui/wizard/deployimage/IDeployImagePageModel.java @@ -33,6 +33,11 @@ public interface IDeployImagePageModel extends IConnectionAware{ static final String PROPERTY_PROJECT = "project"; static final String PROPERTY_RESOURCE_NAME = "resourceName"; static final String PROPERTY_IMAGE_NAME = "imageName"; + static final String PROPERTY_PUSH_IMAGE_TO_REGISTRY = "pushImageToRegistry"; + static final String PROPERTY_TARGET_REGISTRY_LOCATION = "targetRegistryLocation"; + static final String PROPERTY_TARGET_REGISTRY_USERNAME = "targetRegistryUsername"; + static final String PROPERTY_TARGET_REGISTRY_PASSWORD = "targetRegistryPassword"; + /** * @@ -110,6 +115,47 @@ public interface IDeployImagePageModel extends IConnectionAware{ */ List getImageNames(); + + /** + * @return flag to indicate if the image should be pushed to the Docker registry on OpenShift + */ + boolean isPushImageToRegistry(); + + /** + * @param pushImageToRegistry flag to indicate if the image should be pushed to the Docker registry on OpenShift + */ + void setPushImageToRegistry(boolean pushImageToRegistry); + + /** + * @return the URL to the target registry where the image will be pushed + */ + String getTargetRegistryLocation(); + + /** + * @param targetRegistryLocation the URL to the target registry where the image will be pushed + */ + void setTargetRegistryLocation(String targetRegistryLocation); + + /** + * @return the username to connect to the target registry where the image will be pushed + */ + String getTargetRegistryUsername(); + + /** + * @param targetRegistryUsername the username to connect to the target registry where the image will be pushed + */ + void setTargetRegistryUsername(String targetRegistryUsername); + + /** + * @return the password to connect to the target registry where the image will be pushed + */ + String getTargetRegistryPassword(); + + /** + * @param targetRegistryPassword the password to connect to the target registry where the image will be pushed + */ + void setTargetRegistryPassword(String targetRegistryPassword); + /** * Initializes the container info from the selected Docker Image. * diff --git a/plugins/org.jboss.tools.openshift.ui/src/org/jboss/tools/openshift/internal/ui/wizard/deployimage/ListDockerImagesWizard.java b/plugins/org.jboss.tools.openshift.ui/src/org/jboss/tools/openshift/internal/ui/wizard/deployimage/ListDockerImagesWizard.java new file mode 100644 index 0000000000..4375dad3af --- /dev/null +++ b/plugins/org.jboss.tools.openshift.ui/src/org/jboss/tools/openshift/internal/ui/wizard/deployimage/ListDockerImagesWizard.java @@ -0,0 +1,57 @@ +/******************************************************************************* + * Copyright (c) 2016 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.jboss.tools.openshift.internal.ui.wizard.deployimage; + +import org.eclipse.jface.wizard.Wizard; +import org.eclipse.linuxtools.docker.core.IDockerConnection; + +/** + * {@link Wizard} to list and select a Docker Image. + */ +public class ListDockerImagesWizard extends Wizard { + + private final ListDockerImagesWizardModel model; + + private final ListDockerImagesWizardPage dockerImagesWizardPage; + + /** + * Constructor. + * @param dockerConnection the Docker connection + * @param filterName the name to use to start filtering images + */ + public ListDockerImagesWizard(final IDockerConnection dockerConnection, final String filterName) { + this.model = new ListDockerImagesWizardModel(dockerConnection, filterName); + this.dockerImagesWizardPage = new ListDockerImagesWizardPage(this, this.model); + setNeedsProgressMonitor(true); + } + + @Override + public void addPages() { + addPage(this.dockerImagesWizardPage); + super.addPages(); + } + + @Override + public boolean performFinish() { + return true; + } + + public String getSelectedImageName() { + if(this.model.getSelectedDockerImage() == null) { + return null; + } + return this.model.getSelectedDockerImage().getRepoName() + ':' + this.model.getSelectedDockerImage().getTag(); + } + + + +} diff --git a/plugins/org.jboss.tools.openshift.ui/src/org/jboss/tools/openshift/internal/ui/wizard/deployimage/ListDockerImagesWizardModel.java b/plugins/org.jboss.tools.openshift.ui/src/org/jboss/tools/openshift/internal/ui/wizard/deployimage/ListDockerImagesWizardModel.java new file mode 100644 index 0000000000..431add2c05 --- /dev/null +++ b/plugins/org.jboss.tools.openshift.ui/src/org/jboss/tools/openshift/internal/ui/wizard/deployimage/ListDockerImagesWizardModel.java @@ -0,0 +1,143 @@ +/******************************************************************************* + * Copyright (c) 2016 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.jboss.tools.openshift.internal.ui.wizard.deployimage; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.stream.Collectors; + +import org.eclipse.jface.wizard.WizardPage; +import org.eclipse.linuxtools.docker.core.IDockerConnection; +import org.eclipse.linuxtools.docker.core.IDockerImage; +import org.jboss.tools.common.databinding.ObservablePojo; +import org.jboss.tools.openshift.internal.ui.dockerutils.DockerImageUtils; + +/** + * {@link WizardPage} to list and select a Docker Image. + */ +public class ListDockerImagesWizardModel extends ObservablePojo { + + private final IDockerConnection dockerConnection; + + public static final String FILTER_NAME = "filterName"; + + public static final String DOCKER_IMAGES = "dockerImages"; + + public static final String SELECTED_DOCKER_IMAGE = "selectedDockerImage"; + + private String filterName; + + private List dockerImages; + + private DockerImageTag selectedDockerImage; + + public ListDockerImagesWizardModel(final IDockerConnection dockerConnection, final String imageName) { + this.dockerConnection = dockerConnection; + this.filterName = imageName; + } + + public IDockerConnection getDockerConnection() { + return this.dockerConnection; + } + + public String getFilterName() { + return this.filterName; + } + + public void setImageName(final String filterName) { + firePropertyChange(FILTER_NAME, this.filterName, this.filterName = filterName); + } + + public List getDockerImages() { + return this.dockerImages; + } + + public void setDockerImages(final List dockerImages) { + final List topLevelImages = dockerImages.stream() + .filter(image -> !image.isDangling() && !image.isIntermediateImage()).collect(Collectors.toList()); + final List imageTags = new ArrayList<>(); + for(IDockerImage topLevelImage : topLevelImages) { + final Map> repoTags = DockerImageUtils.extractTagsByRepo(topLevelImage.repoTags()); + for(Entry> entry : repoTags.entrySet()) { + final String repo = entry.getKey(); + final List tags = entry.getValue(); + for(String tag : tags) { + imageTags.add(new DockerImageTag(topLevelImage.id(), repo, tag)); + } + } + } + + Collections.sort(imageTags, new Comparator() { + @Override + public int compare(DockerImageTag image1, DockerImageTag image2) { + return image1.getRepoName().compareTo(image2.getRepoName()); + } + }); + firePropertyChange(DOCKER_IMAGES, this.dockerImages, + this.dockerImages = imageTags); + } + + public DockerImageTag getSelectedDockerImage() { + return this.selectedDockerImage; + } + + public void setSelectedDockerImage(final DockerImageTag selectedDockerImage) { + firePropertyChange(SELECTED_DOCKER_IMAGE, this.selectedDockerImage, + this.selectedDockerImage = selectedDockerImage); + } + + static class DockerImageTag { + + /** the corresponding image id. */ + private final String id; + + /** repository name of the image. */ + private final String repoName; + + /** the tag for this specific image. */ + private final String tag; + + public DockerImageTag(final String id, final String repoName, final String tag) { + this.id = id.startsWith("sha256:") ? id.substring("sha256:".length(), "sha256:".length() + 12) : id.substring(0, 12); + this.repoName = repoName; + this.tag = tag; + } + + /** + * @return the id + */ + public String getId() { + return id; + } + + /** + * @return the repoName + */ + public String getRepoName() { + return repoName; + } + + /** + * @return the tag + */ + public String getTag() { + return tag; + } + + } + +} diff --git a/plugins/org.jboss.tools.openshift.ui/src/org/jboss/tools/openshift/internal/ui/wizard/deployimage/ListDockerImagesWizardPage.java b/plugins/org.jboss.tools.openshift.ui/src/org/jboss/tools/openshift/internal/ui/wizard/deployimage/ListDockerImagesWizardPage.java new file mode 100644 index 0000000000..2a350754b9 --- /dev/null +++ b/plugins/org.jboss.tools.openshift.ui/src/org/jboss/tools/openshift/internal/ui/wizard/deployimage/ListDockerImagesWizardPage.java @@ -0,0 +1,193 @@ +/******************************************************************************* + * Copyright (c) 2016 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.jboss.tools.openshift.internal.ui.wizard.deployimage; + +import java.lang.reflect.InvocationTargetException; + +import org.eclipse.core.databinding.DataBindingContext; +import org.eclipse.core.databinding.beans.BeanProperties; +import org.eclipse.core.runtime.IProgressMonitor; +import org.eclipse.jface.databinding.viewers.ObservableListContentProvider; +import org.eclipse.jface.databinding.viewers.ViewerProperties; +import org.eclipse.jface.layout.GridDataFactory; +import org.eclipse.jface.layout.GridLayoutFactory; +import org.eclipse.jface.operation.IRunnableWithProgress; +import org.eclipse.jface.viewers.CellLabelProvider; +import org.eclipse.jface.viewers.ColumnLabelProvider; +import org.eclipse.jface.viewers.TableViewer; +import org.eclipse.jface.viewers.TableViewerColumn; +import org.eclipse.jface.viewers.Viewer; +import org.eclipse.jface.viewers.ViewerFilter; +import org.eclipse.jface.wizard.IWizard; +import org.eclipse.jface.wizard.WizardPage; +import org.eclipse.swt.SWT; +import org.eclipse.swt.events.ModifyEvent; +import org.eclipse.swt.events.ModifyListener; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Label; +import org.eclipse.swt.widgets.Table; +import org.eclipse.swt.widgets.TableColumn; +import org.eclipse.swt.widgets.Text; +import org.jboss.tools.openshift.internal.common.ui.wizard.AbstractOpenShiftWizardPage; +import org.jboss.tools.openshift.internal.ui.OpenShiftUIActivator; +import org.jboss.tools.openshift.internal.ui.wizard.deployimage.ListDockerImagesWizardModel.DockerImageTag; + +/** + * {@link WizardPage} to list and select a Docker Image. + */ +public class ListDockerImagesWizardPage extends AbstractOpenShiftWizardPage { + + /** the model. */ + private final ListDockerImagesWizardModel model; + + private static String LIST_DOCKER_IMAGES_PAGE_NAME = "List Docker Images Page"; + + private static final String PAGE_DESCRIPTION = "This page allows you to choose a local image and the name to be used for the deployed resources."; + + /** + * Constructor. + * + * @param wizard + * the parent wizard + * @param model + * the model + */ + public ListDockerImagesWizardPage(final IWizard wizard, final ListDockerImagesWizardModel model) { + super("Deploy an Image", PAGE_DESCRIPTION, LIST_DOCKER_IMAGES_PAGE_NAME, wizard); + this.model = model; + } + + @Override + public boolean isPageComplete() { + // can finish if a Docker image was selected + return this.model.getSelectedDockerImage() != null; + } + + @SuppressWarnings("unchecked") + @Override + protected void doCreateControls(final Composite parent, final DataBindingContext dbc) { + GridLayoutFactory.fillDefaults().margins(10, 10).numColumns(2).applyTo(parent); + + // filter image by name + final Label filterByNameLabel = new Label(parent, SWT.SEARCH); + filterByNameLabel.setText("Filter:"); + filterByNameLabel.setToolTipText("Filter images by their name"); + GridDataFactory.fillDefaults().align(SWT.FILL, SWT.CENTER).grab(false, false).applyTo(filterByNameLabel); + final Text filterByNameText = new Text(parent, SWT.BORDER); + GridDataFactory.fillDefaults().align(SWT.FILL, SWT.CENTER).grab(true, false).applyTo(filterByNameText); + + // table with all images + final Table dockerImagesTable = new Table(parent, + SWT.BORDER | SWT.FULL_SELECTION | SWT.V_SCROLL | SWT.H_SCROLL); + final TableViewer dockerImagesTableViewer = new TableViewer(dockerImagesTable); + dockerImagesTable.setHeaderVisible(true); + dockerImagesTable.setLinesVisible(true); + addTableViewerColum(dockerImagesTableViewer, "Name", SWT.NONE, SWT.LEFT, 200, new ColumnLabelProvider() { + + @Override + public String getText(final Object element) { + return ((DockerImageTag) element).getRepoName(); + } + }); + addTableViewerColum(dockerImagesTableViewer, "Tag", SWT.NONE, SWT.LEFT, 100, new ColumnLabelProvider() { + + @Override + public String getText(final Object element) { + return ((DockerImageTag) element).getTag(); + } + }); + addTableViewerColum(dockerImagesTableViewer, "Image ID", SWT.NONE, SWT.LEFT, 150, new ColumnLabelProvider() { + + @Override + public String getText(final Object element) { + return ((DockerImageTag) element).getId(); + } + }); + GridDataFactory.fillDefaults().align(SWT.FILL, SWT.FILL).grab(true, true).span(2, 1).hint(200, 100) + .applyTo(dockerImagesTable); + + // observe the viewer content + dockerImagesTableViewer.setContentProvider(new ObservableListContentProvider()); + // observe the viewer content + dockerImagesTableViewer.setInput(BeanProperties + .list(ListDockerImagesWizardModel.class, ListDockerImagesWizardModel.DOCKER_IMAGES).observe(model)); + + // filter by name + final ViewerFilter imageNameFilter = new ViewerFilter() { + + @Override + public boolean select(Viewer viewer, Object parentElement, Object element) { + return ((DockerImageTag)element).getRepoName().contains(filterByNameText.getText()); + } + }; + dockerImagesTableViewer.addFilter(imageNameFilter); + filterByNameText.addModifyListener(onFilterImages(dockerImagesTableViewer)); + + // bind selection + dbc.bindValue(ViewerProperties.singleSelection().observe(dockerImagesTableViewer), + BeanProperties.value(ListDockerImagesWizardModel.SELECTED_DOCKER_IMAGE).observe(model)); + + // load the Docker images + try { + getContainer().run(true, false, new IRunnableWithProgress() { + + @Override + public void run(IProgressMonitor monitor) throws InvocationTargetException, InterruptedException { + model.setDockerImages(model.getDockerConnection().getImages(true)); + + } + }); + } catch (InvocationTargetException | InterruptedException e) { + OpenShiftUIActivator.getDefault().getLogger().logError(e); + } + } + + private static ModifyListener onFilterImages(final TableViewer dockerImagesTableViewer) { + return new ModifyListener() { + + @Override + public void modifyText(ModifyEvent e) { + dockerImagesTableViewer.refresh(); + } + }; + } + + private static TableViewerColumn addTableViewerColum(final TableViewer tableViewer, final String title, + final int style, final int alignment, final int width, final CellLabelProvider columnLabelProvider) { + final TableViewerColumn viewerColumn = new TableViewerColumn(tableViewer, style); + final TableColumn column = viewerColumn.getColumn(); + if (title != null) { + column.setText(title); + } + column.setAlignment(alignment); + column.setWidth(width); + viewerColumn.setLabelProvider(columnLabelProvider); + return viewerColumn; + } + + static class ImageIDColumnLabelProvider extends ColumnLabelProvider { + + @Override + public String getText(final Object element) { + return ((DockerImageTag) element).getId(); + } + } + + static class ImageNameColumnLabelProvider extends ColumnLabelProvider { + + @Override + public String getText(final Object element) { + return ((DockerImageTag) element).getRepoName(); + } + } + +}