/******************************************************************************* * Copyright (c) 2015 Red Hat, Inc. * Distributed under license by Red Hat, Inc. All rights reserved. * This program is 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, Inc. - initial API and implementation ******************************************************************************/ package org.jboss.tools.openshift.internal.ui.wizard.deployimage; import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.Comparator; import java.util.LinkedHashSet; import java.util.List; import java.util.Map; import java.util.Set; import java.util.stream.Collectors; import org.apache.commons.lang.ObjectUtils; import org.apache.commons.lang.StringUtils; 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.ICommonAttributes; import org.jboss.tools.openshift.core.connection.Connection; import org.jboss.tools.openshift.internal.core.IDockerImageMetadata; 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.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 com.openshift.restclient.ResourceKind; import com.openshift.restclient.images.DockerImageURI; import com.openshift.restclient.model.IPort; import com.openshift.restclient.model.IProject; import com.openshift.restclient.model.IServicePort; /** * The Wizard model to support deploying an image to OpenShift * @author jeff.cantrill * @author Andre Dietisheim * */ public class DeployImageWizardModel extends ResourceLabelsPageModel implements IDeployImageParameters, IDockerConnectionManagerListener2, PropertyChangeListener { private static final int DEFAULT_REPLICA_COUNT = 1; private Connection connection; private IProject project; private List<IProject> projects = new ArrayList<>(); private String resourceName; private String imageName; private EnvironmentVariablesPageModel envModel = new EnvironmentVariablesPageModel(); private List<String> volumes = Collections.emptyList(); private String selectedVolume; private List<IPort> portSpecs = Collections.emptyList(); private int replicas; private boolean addRoute = true; private String routeHostname; List<IServicePort> servicePorts = new ArrayList<>(); IServicePort selectedServicePort = null; private IDockerConnection dockerConnection; private ArrayList<IServicePort> imagePorts; private boolean originatedFromDockerExplorer; private boolean isStartedWithActiveConnection = false; private IDockerImageMetadata imageMeta; private boolean pushImageToRegistry = false; private String targetRegistryLocation; private String targetRegistryUsername; private String targetRegistryPassword; private IServicePort routingPort; private static final DockerImage2OpenshiftResourceConverter dockerImage2OpenshiftResourceConverter = new DockerImage2OpenshiftResourceConverter(); private final List<String> imageNames = new ArrayList<>(); private List<IDockerConnection> dockerConnections = Arrays.asList(DockerConnectionManager.getInstance().getConnections()); private Comparator<IProject> projectsComparator; protected boolean resourcesLoaded = false; public DeployImageWizardModel() { envModel.addPropertyChangeListener(PROPERTY_ENVIRONMENT_VARIABLES, this); envModel.addPropertyChangeListener(PROPERTY_SELECTED_ENVIRONMENT_VARIABLE, this); DockerConnectionManager.getInstance().addConnectionManagerListener(this); } @Override public void dispose() { super.dispose(); DockerConnectionManager.getInstance().removeConnectionManagerListener(this); ((EnvironmentVariablesPageModel) envModel).dispose(); this.connection = null; this.project = null; this.dockerConnection = null; this.projects.clear(); this.volumes.clear(); this.portSpecs.clear(); if(imagePorts != null) { imagePorts.clear(); this.imagePorts = null; } } @Override public void propertyChange(PropertyChangeEvent evt) { if(evt != null) { firePropertyChange(evt.getPropertyName(), evt.getOldValue(), evt.getNewValue()); } } @Override public void setOriginatedFromDockerExplorer(boolean orig) { this.originatedFromDockerExplorer = orig; } @Override public boolean originatedFromDockerExplorer() { return originatedFromDockerExplorer; } @Override public boolean isStartedWithActiveConnection() { return isStartedWithActiveConnection; } public void setStartedWithActiveConnection(boolean active) { isStartedWithActiveConnection = active; } @Override public List<IDockerConnection> getDockerConnections() { return dockerConnections; } @Override public IDockerConnection getDockerConnection() { if (dockerConnection == null) { List<IDockerConnection> all = getDockerConnections(); if (all.size() == 1) { setDockerConnection(all.get(0)); } } return dockerConnection; } @Override public Collection<Connection> getConnections() { return ConnectionsRegistrySingleton.getInstance().getAll(Connection.class); } @Override public Connection getConnection() { if (connection == null) { Collection<Connection> connections = getConnections(); if (connections.size() == 1) { setConnection(connections.iterator().next()); } } return connection; } private void initImageRegistry(Connection connection) { if(connection == null) { return; } setTargetRegistryLocation( (String) connection.getExtendedProperties().get(ICommonAttributes.IMAGE_REGISTRY_URL_KEY)); setTargetRegistryUsername(connection.getUsername()); setTargetRegistryPassword(connection.getToken()); } @Override public void setConnection(final Connection connection) { Connection oldConnection = this.connection; firePropertyChange(PROPERTY_CONNECTION, this.connection, this.connection = connection); initImageRegistry(connection); this.resourcesLoaded = ObjectUtils.equals(oldConnection, connection); } @Override public void loadResources() { if (resourcesLoaded) { return; } List<IProject> projects = connection.getResources(ResourceKind.PROJECT); setProjects(projects); setProjectOrDefault(project); this.resourcesLoaded = true; } protected void setProjects(List<IProject> projects) { if(projects == null) { projects = Collections.emptyList(); } firePropertyChange(PROPERTY_PROJECTS, this.projects, this.projects = projects); } @Override public List<IProject> getProjects() { return projects; } @Override public void addProject(IProject project) { if (project == null) { return; } projects.add(project); } @Override public IProject getProject() { return this.project; } @Override public void setProject(IProject project) { firePropertyChange(PROPERTY_PROJECT, this.project, this.project = project); } protected void setProjectOrDefault(IProject project) { if (projects == null) { return; } if (project != null && projects.contains(project)) { setProject(project); } else if (!projects.isEmpty()) { project = getDefaultProject(); setProject(project); } } private IProject getDefaultProject() { IProject project; if (projectsComparator != null) { Collections.sort(projects, projectsComparator); project = projects.get(0); } else { project = projects.get(0); } return project; } @Override public void setProjectsComparator(Comparator<IProject> comparator) { this.projectsComparator = comparator; } @Override public String getResourceName() { return this.resourceName; } @Override public void setResourceName(String resourceName) { firePropertyChange(PROPERTY_RESOURCE_NAME, this.resourceName, this.resourceName = resourceName); } @Override public String getImageName() { return this.imageName; } @Override public void setImageName(final String imageName) { if(StringUtils.isBlank(imageName)) { return; } if(this.imageName != null && !this.imageName.equals(imageName) && this.imageMeta != null) { //Clean container info loaded for old image name. this.imageMeta = null; setEnvironmentVariables(new ArrayList<>()); setPortSpecs(new ArrayList<>()); setVolumes(new ArrayList<>()); } firePropertyChange(PROPERTY_IMAGE_NAME, this.imageName, this.imageName = imageName); final DockerImageURI uri = new DockerImageURI(imageName); setResourceName(dockerImage2OpenshiftResourceConverter.convert(uri)); } @Override public void setImageName(String imageName, boolean forceUpdate) { if(forceUpdate && this.imageName != null && this.imageName.equals(imageName)) { firePropertyChange(PROPERTY_IMAGE_NAME, this.imageName, this.imageName = null); } 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(); if (this.imageMeta == null) { return false; } final List<EnvironmentVariable> envVars = this.imageMeta.env().stream().filter(env -> env.indexOf('=') != -1) .map(env -> env.split("=")) .map(splittedEnv -> new EnvironmentVariable(splittedEnv[0], splittedEnv.length > 1 ? splittedEnv[1] : StringUtils.EMPTY)) .collect(Collectors.toList()); setEnvironmentVariables(envVars); final List<IPort> portSpecs = this.imageMeta.exposedPorts().stream().map(spec -> new PortSpecAdapter(spec)) .collect(Collectors.toList()); setPortSpecs(portSpecs); if(this.imageMeta.volumes() != null && !this.imageMeta.volumes().isEmpty()) { setVolumes(new ArrayList<>(this.imageMeta.volumes())); } else { setVolumes(new ArrayList<>()); } setReplicas(DEFAULT_REPLICA_COUNT); return true; } @Override public List<EnvironmentVariable> getEnvironmentVariables() { return envModel.getEnvironmentVariables(); } @Override public boolean isEnvironmentVariableModified(EnvironmentVariable envVar) { return envModel.isEnvironmentVariableModified(envVar); } @Override public void setEnvironmentVariables(List<EnvironmentVariable> envVars) { envModel.setEnvironmentVariables(envVars); } @Override public EnvironmentVariable getEnvironmentVariable(String key) { return envModel.getEnvironmentVariable(key); } @Override public void setVolumes(List<String> volumes) { firePropertyChange(PROPERTY_VOLUMES, this.volumes, this.volumes = volumes); } @Override public List<String> getVolumes() { return volumes; } private void setPortSpecs(List<IPort> portSpecs) { firePropertyChange(PROPERTY_PORT_SPECS, this.portSpecs, this.portSpecs = portSpecs); setServicePortsFromPorts(portSpecs); } private void setServicePortsFromPorts(List<IPort> portSpecs) { this.imagePorts = new ArrayList<>(portSpecs.size()); List<IServicePort> servicePorts = new ArrayList<>(portSpecs.size()); for (IPort port : portSpecs) { servicePorts.add(new ServicePortAdapter(port)); imagePorts.add(new ServicePortAdapter(port)); } setServicePorts(servicePorts); } private void setServicePorts(List<IServicePort> servicePorts) { if (servicePorts.size() > 0) { ServicePortAdapter adapterPort = (ServicePortAdapter) servicePorts.get(0); adapterPort.setRoutePort(true); setRoutingPort(adapterPort); } else { setRoutingPort(null); } firePropertyChange(IServiceAndRoutingPageModel.PROPERTY_SERVICE_PORTS, this.servicePorts, this.servicePorts = servicePorts); } @Override public List<IPort> getPortSpecs() { return portSpecs; } @Override public int getReplicas() { return replicas; } @Override public void setReplicas(int replicas) { firePropertyChange(PROPERTY_REPLICAS, this.replicas, this.replicas = replicas); } @Override public boolean hasConnection() { return this.connection != null; } @Override public Object getContext() { return null; } @Override public boolean isAddRoute() { return addRoute; } @Override public void setAddRoute(boolean addRoute) { firePropertyChange(PROPERTY_ADD_ROUTE, this.addRoute, this.addRoute = addRoute); } @Override public String getRouteHostname() { return routeHostname; } @Override public void setRouteHostname(String routeHostname) { firePropertyChange(PROPERTY_ROUTE_HOSTNAME, this.routeHostname, this.routeHostname = routeHostname); } @Override public List<IServicePort> getServicePorts() { return servicePorts; } @Override public void setSelectedEnvironmentVariable(EnvironmentVariable envVar) { envModel.setSelectedEnvironmentVariable(envVar); } @Override public EnvironmentVariable getSelectedEnvironmentVariable() { return envModel.getSelectedEnvironmentVariable(); } @Override public void removeEnvironmentVariable(EnvironmentVariable envVar) { envModel.removeEnvironmentVariable(envVar); } @Override public void updateEnvironmentVariable(EnvironmentVariable envVar, String key, String value) { envModel.updateEnvironmentVariable(envVar, key, value); } @Override public void resetEnvironmentVariable(EnvironmentVariable envVar) { envModel.resetEnvironmentVariable(envVar); } @Override public void addEnvironmentVariable(String key, String value) { envModel.addEnvironmentVariable(key, value); } @Override public void addServicePort(IServicePort port) { if(this.servicePorts.contains(port)) { return; } List<IServicePort> old = new ArrayList<>(this.servicePorts); this.servicePorts.add(port); firePropertyChange(PROPERTY_SERVICE_PORTS, old, Collections.unmodifiableList(servicePorts)); if (port instanceof ServicePortAdapter && ((ServicePortAdapter)port).isRoutePort()) { setRoutingPort(port); } } @Override public void updateServicePort(IServicePort source, IServicePort target){ final int pos = this.servicePorts.indexOf(source); if(pos > -1) { List<IServicePort> old = new ArrayList<>(this.servicePorts); this.servicePorts.set(pos, target); /** * databinding would not replace old object with a new one if only a * boolean property is modified. I could not understand why it is * so, but I found that when target port (String) and route port * (Boolean) are changed in Edit dialog together, everything works. * * @see https://github.com/jbosstools/jbosstools-openshift/pull/1365 */ String p = target.getTargetPort(); target.setTargetPort("dummy"); fireIndexedPropertyChange(PROPERTY_SERVICE_PORTS, pos, old, Collections.unmodifiableList(servicePorts)); if (((ServicePortAdapter)target).isRoutePort()) { setRoutingPort(target); } else if (((ServicePortAdapter)source).isRoutePort() && !((ServicePortAdapter)target).isRoutePort()) { setRoutingPort(null); } target.setTargetPort(p); } } @Override public void setSelectedVolume(String volume) { firePropertyChange(PROPERTY_SELECTED_VOLUME, this.selectedVolume, this.selectedVolume = volume); } @Override public String getSelectedVolume() { return selectedVolume; } @Override public void updateVolume(String volume, String value) { Set<String> old = new LinkedHashSet<>(volumes); this.volumes.remove(volume); this.volumes.add(value); firePropertyChange(PROPERTY_VOLUMES, old, Collections.unmodifiableList(volumes)); } @Override public void setSelectedServicePort(IServicePort servicePort) { firePropertyChange(PROPERTY_SELECTED_SERVICE_PORT, this.selectedServicePort, this.selectedServicePort = servicePort); } @Override public IServicePort getSelectedServicePort() { return selectedServicePort; } @Override public void removeServicePort(IServicePort port) { int index = servicePorts.indexOf(port); if(index > -1) { List<IServicePort> old = new ArrayList<>(servicePorts); this.servicePorts.remove(port); fireIndexedPropertyChange(PROPERTY_SERVICE_PORTS, index, old, Collections.unmodifiableList(servicePorts)); } } @Override public void setDockerConnection(IDockerConnection dockerConnection) { firePropertyChange(PROPERTY_DOCKER_CONNECTION, this.dockerConnection, this.dockerConnection = dockerConnection); this.imageNames.clear(); if(dockerConnection == null) { return; } final List<IDockerImage> 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())); } } protected IDockerImageMetadata lookupImageMetadata() { if (StringUtils.isBlank(this.imageName)) { return null; } final DockerImageURI imageURI = new DockerImageURI(this.imageName); final String repo = imageURI.getUriWithoutTag(); final String tag = StringUtils.defaultIfBlank(imageURI.getTag(), "latest"); if (dockerConnection != null && DockerImageUtils.hasImage(dockerConnection, repo, tag)) { final IDockerImageInfo info = dockerConnection.getImageInfo(this.imageName); return new DockerConfigMetaData(info); } else if (this.project != null) { return OpenShiftProjectUtils.lookupImageMetadata(project, imageURI); } return null; } @Override public List<String> getImageNames() { return this.imageNames; } @Override public void resetServicePorts() { List<IServicePort> ports = imagePorts.stream().map(sp -> new ServicePortAdapter(sp)).collect(Collectors.toList()); setServicePorts(ports); } @Override public void setRoutingPort(IServicePort port) { if (routingPort != null) { ((ServicePortAdapter)routingPort).setRoutePort(false); } firePropertyChange(PROPERTY_ROUTING_PORT, routingPort, this.routingPort = port); } @Override public IServicePort getRoutingPort() { return routingPort; } @Override public Map<String, String> getImageEnvVars() { return envModel.getImageEnvVars(); } @Override public void changeEvent(int event) { } @Override public void changeEvent(IDockerConnection connection, int event) { if ((event == ADD_EVENT) || (event == REMOVE_EVENT)) { firePropertyChange(PROPERTY_DOCKER_CONNECTIONS, dockerConnections, dockerConnections = Arrays.asList(DockerConnectionManager.getInstance().getConnections())); } } }