/*******************************************************************************
* Copyright (c) 2015-2017 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.core.util;
import java.net.URISyntaxException;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;
import org.apache.commons.lang.StringUtils;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.jgit.transport.URIish;
import org.jboss.tools.openshift.core.OpenShiftAPIAnnotations;
import org.jboss.tools.openshift.egit.core.EGitUtils;
import com.openshift.restclient.IClient;
import com.openshift.restclient.ResourceKind;
import com.openshift.restclient.capability.CapabilityVisitor;
import com.openshift.restclient.capability.resources.IClientCapability;
import com.openshift.restclient.capability.resources.ITags;
import com.openshift.restclient.model.IBuild;
import com.openshift.restclient.model.IBuildConfig;
import com.openshift.restclient.model.IDeploymentConfig;
import com.openshift.restclient.model.IObjectReference;
import com.openshift.restclient.model.IPod;
import com.openshift.restclient.model.IProject;
import com.openshift.restclient.model.IReplicationController;
import com.openshift.restclient.model.IResource;
import com.openshift.restclient.model.IService;
import com.openshift.restclient.model.deploy.IDeploymentImageChangeTrigger;
import com.openshift.restclient.model.image.IImageStreamImport;
import com.openshift.restclient.model.route.IRoute;
public class ResourceUtils {
public static final String DOCKER_IMAGE_KIND = "DockerImage";
public static final String IMAGE_STREAM_IMAGE_KIND = "ImageStreamImage";
public static final String DEPLOYMENT_CONFIG_KEY = "deploymentconfig";
public static IClient getClient(IResource resource) {
return resource.accept(new CapabilityVisitor<IClientCapability, IClient>() {
@Override
public IClient visit(IClientCapability cap) {
return cap.getClient();
}
}, null);
}
/**
* Returns {@code true} if the given route points to the given service and
* the given service is the service that the given route points to.
*
* @param route
* @param service
* @return
*
* @see IRoute#getServiceName()
* @see IService#getName()
*/
public static boolean areRelated(IRoute route, IService service) {
if (service != null
&& !StringUtils.isEmpty(service.getName())
&& route != null) {
return service.getName().equals(route.getServiceName());
}
return false;
}
/**
* Returns {@code true} if the given build config matches the name of the
* given service.
*
* @param config
* @param service
* @return
*/
public static boolean areRelated(final IBuildConfig config, final IService service) {
if (service != null
&& !StringUtils.isEmpty(service.getName())
&& config != null) {
return service.getName().equals(config.getName());
}
return false;
}
public static boolean areRelated(IDeploymentConfig dc, IService s) {
return containsAll(s.getSelector(), dc.getReplicaSelector());
}
public static boolean areRelated(IReplicationController rc, IService s) {
return containsAll(s.getSelector(), rc.getReplicaSelector());
}
public static boolean areRelated(IPod pod, IService s) {
return containsAll(s.getSelector(), pod.getLabels());
}
public static boolean areRelated(IPod pod, IReplicationController rc) {
return containsAll(rc.getReplicaSelector(), pod.getLabels());
}
/**
* Returns <code>true</code> if the given resource contains the given text
* in name or tags.
*
* @param filterText
* @param resource
* @return
*/
public static boolean isMatching(final String filterText, IResource resource) {
if(resource == null || StringUtils.isBlank(filterText)) {
return true;
}
return resource.accept(new CapabilityVisitor<ITags, Boolean>() {
@Override
public Boolean visit(ITags capability) {
return isMatching(filterText, resource.getName(), capability.getTags());
}
}, Boolean.FALSE);
}
public static boolean isMatching(final String filterText, String name, Collection<String> tags) {
if (StringUtils.isBlank(filterText)) {
return true;
}
final Set<String> items = new HashSet<>(Arrays.asList(
filterText.replaceAll(",", " ").toLowerCase().split(" ")));
if (containsAll(name, items)) {
return true;
}
for (String item : items) {
if (!inCollection(item, tags)) {
return false;
}
}
return true;
}
private static boolean containsAll(String text, final Collection<String> items) {
final String _text = text.toLowerCase();
return items.stream()
.allMatch(it -> {
return _text.contains(it);
});
}
private static boolean inCollection(String item, final Collection<String> texts) {
final String _item = item.toLowerCase();
return texts.stream().anyMatch((String txt)->{return txt.toLowerCase().contains(_item);});
}
/**
* Determine if the source map overlaps the target map (i.e. Matching a service to a pod). There
* is match if the target includes all the the keys from the source and those keys have
* matching values
*
* @param source
* @param target
* @return true if there is overlap; false; otherwise
*/
public static boolean containsAll(Map<String, String> source, Map<String, String> target) {
if(source == null
|| target == null) {
return false;
}
if(!target.keySet().isEmpty()
&& source.keySet().isEmpty()) {
return false;
}
if(!target.keySet().containsAll(source.keySet())) {
return false;
}
for (String key : source.keySet()) {
if (!Objects.deepEquals(target.get(key), source.get(key))) {
return false;
}
}
return true;
}
/**
* Find the collection of services whos selectors match the given pod
* @param pod
* @param services
* @return
*/
public static Collection<IService> getServicesFor(IPod pod, Collection<IService> services){
return services.stream()
.filter(s->containsAll(s.getSelector(), pod.getLabels()))
.collect(Collectors.toSet());
}
public static Collection<IService> getServicesFor(IReplicationController rc, Collection<IService> services){
return services.stream()
.filter(service -> areRelated(rc, service))
.collect(Collectors.toSet());
}
/**
* Find the collection of pods that match the selector of the given service
* @param service
* @param pods
* @return
*/
public static List<IPod> getPodsFor(IService service, Collection<IPod> pods) {
final Map<String, String> serviceSelector = service.getSelector();
return getPodsForSelector(serviceSelector, pods);
}
/**
* Find the collection of pods that match the deployment name annotation
* @param replicationController the replication controller to match
* @param pods the list of pods to search for
* @return the matched pods
*/
public static List<IPod> getPodsFor(IReplicationController replicationController, Collection<IPod> pods) {
return pods.stream().filter(pod -> containsAll(replicationController.getReplicaSelector(), pod.getLabels()))
.collect(Collectors.toList());
}
/**
* Find the collection of pods that match the deployment name annotation
* @param replicationController the replication controller to match
* @param pods the list of pods to search for
* @return the matched pods
*/
public static List<IPod> getPodsFor(IDeploymentConfig deploymentConfig, Collection<IPod> pods) {
return pods.stream().filter(pod -> {
String configName = pod.getAnnotation(OpenShiftAPIAnnotations.DEPLOYMENT_CONFIG_NAME);
return deploymentConfig.getName().equals(configName);
}).collect(Collectors.toList());
}
/**
* Find the collection of pods that match the selector of the given resource
* @param resource the OpenShift resource to start from
* @param pods the list of pods to search
* @return the list of linked pods
*/
public static List<IPod> getPodsFor(IResource resource, Collection<IPod> pods) {
if (resource instanceof IService) {
return getPodsFor((IService) resource, pods);
} else if (resource instanceof IDeploymentConfig) {
return getPodsFor((IDeploymentConfig)resource, pods);
} else if (resource instanceof IReplicationController) {
return getPodsFor((IReplicationController) resource, pods);
} else {
return Collections.emptyList();
}
}
/**
* Return the deployment config or replication controller associated with this pod. Uses
* annotations to do the matching.
*
* @param pod the pod to look for
* @return the deployment config or replication controller
*/
public static IReplicationController getDeploymentConfigOrReplicationControllerFor(IPod pod) {
Optional<IResource> rcOrDc = pod.getProject().getResources(ResourceKind.DEPLOYMENT_CONFIG).stream()
.filter(dc -> dc.getName().equals(pod.getAnnotation(OpenShiftAPIAnnotations.DEPLOYMENT_CONFIG_NAME)))
.findFirst();
if (!rcOrDc.isPresent()) {
rcOrDc = Optional.ofNullable(getReplicationControllerFor(pod, pod.getProject().getResources(ResourceKind.REPLICATION_CONTROLLER)));
}
return (IReplicationController) rcOrDc.orElse(null);
}
/**
* Find the collection of pods that match the given selector
* @param selector
* @param pods
* @return
*/
private static List<IPod> getPodsForSelector(Map<String, String> serviceSelector, Collection<IPod> pods) {
return pods.stream()
.filter(p -> containsAll(serviceSelector, p.getLabels()))
.distinct()
.collect(Collectors.toList());
}
/**
* Returns {@code true} if the given pod is a pod running builds. This is
* the case if the pod is annotated with the build name. Returns
* {@code false} if the given pod is a pod running an application or is
* null.
*
* @param pod
* the pod that shall be checked whether it's a build pod
* @return true if pod is annotated with the build name; false otherwise;
*
* @see IPod
* @see OpenShiftAPIAnnotations#BUILD_NAME
*/
public static boolean isBuildPod(IPod pod) {
if (pod == null) {
return false;
}
return pod.isAnnotatedWith(OpenShiftAPIAnnotations.BUILD_NAME);
}
/**
* The image reference for an image change trigger used to correlate a
* deploymentconfig to a buildconfig
*
* @param trigger
* @return
*/
public static String imageRef(IDeploymentImageChangeTrigger trigger) {
if(trigger != null) {
switch(trigger.getKind()) {
case ResourceKind.IMAGE_STREAM_TAG:
case IMAGE_STREAM_IMAGE_KIND:
case DOCKER_IMAGE_KIND:
return trigger.getFrom().getNameAndTag();
}
}
return "";
}
/**
* Returns all the images for the given build configs.
* @param buildConfigs the build configs to extract the image refs from
* @return all the image references within the given build configs
*
* @see #imageRef(IBuildConfig)
*/
public static List<String> getImageRefs(List<IBuildConfig> buildConfigs) {
if (buildConfigs == null) {
return null;
}
return buildConfigs.stream()
.map(bc -> imageRef(bc))
.collect(Collectors.toList());
}
/**
* The image reference for an image change trigger used to correlate a
* buildconfig to a deploymentconfig
*
* @param trigger
* @return
*/
public static String imageRef(IBuildConfig config) {
if (config != null) {
IObjectReference outputRef = config.getBuildOutputReference();
if (outputRef != null) {
String kind = outputRef.getKind();
if (ResourceKind.IMAGE_STREAM_TAG.equals(kind)
|| IMAGE_STREAM_IMAGE_KIND.equals(kind)) {
return outputRef.getName();
}
}
}
return "";
}
/**
* Returns the image referenced by a given build. Returns empty string if none is found.
*
* @param build
* @return the image referenced
*/
public static String imageRef(IBuild build) {
if (build != null) {
switch(build.getOutputKind()) {
case ResourceKind.IMAGE_STREAM_TAG:
case IMAGE_STREAM_IMAGE_KIND:
case DOCKER_IMAGE_KIND:
return build.getOutputTo().getNameAndTag();
}
}
return "";
}
/**
* Find the collection of pods for the given replication controller
* @param replicationController the replication controller to search pods for
* @param pods the list of pods to search
* @return the list of matched pods
*/
public static Collection<IPod> getPodsFor(IReplicationController replicationController) {
List<IPod> pods = replicationController.getProject().getResources(ResourceKind.POD);
Map<String, String> selector = replicationController.getReplicaSelector();
return getPodsForSelector(selector, pods);
}
/**
* Returns the first route that's found and matches the given service.
*
* @param service
* @param routes
* @return
*/
public static IRoute getRouteFor(final IService service, Collection<IRoute> routes) {
List<IRoute> matchingRoutes = getRoutesFor(service, routes);
if (matchingRoutes.isEmpty()) {
return null;
} else {
return matchingRoutes.get(0);
}
}
/**
*
* Returns the routes from the given routes that match the given service.
*
* @param service
* @param routes
* @return
*/
public static List<IRoute> getRoutesFor(final IService service, Collection<IRoute> routes) {
if (routes == null
|| routes.isEmpty()) {
return Collections.emptyList();
}
return routes.stream()
.filter(r -> areRelated(r, service))
.collect(Collectors.toList());
}
/**
* Returns build configs of the given list of build configs
* that match the given service.
*
* @param service
* @param buildConfigs
* @return
*
* @see #areRelated(IBuildConfig, IService)
* @see IBuildConfig
* @see IService
*/
public static List<IBuildConfig> getBuildConfigsFor(IService service, List<IBuildConfig> buildConfigs) {
if (buildConfigs == null
|| buildConfigs.isEmpty()) {
return Collections.emptyList();
}
return buildConfigs.stream()
.filter(bc -> areRelated(bc, service))
.collect(Collectors.toList());
}
/**
* Returns the 1st replication controllers that's found matching the given
* service. The lookup is done by matching the label in the service and
* replication controller pod template. No existing pods are required.
*
* @param service
* @param allReplicationControllers
* @return
*/
public static IReplicationController getReplicationControllerFor(IService service, List<IReplicationController> allReplicationControllers) {
if (allReplicationControllers == null
|| allReplicationControllers.isEmpty()
|| service == null) {
return null;
}
return allReplicationControllers.stream()
.filter(rc -> containsAll(service.getSelector(), rc.getTemplateLabels()))
.findFirst()
.orElse(null);
}
/**
* Returns the 1st replication controllers that's found matching the given
* service. The lookup is done by matching the label in the service and
* replication controller pod template. No existing pods are required.
*
* @param pod
* @param allReplicationControllers
* @return
*/
public static IReplicationController getReplicationControllerFor(IPod pod, List<IReplicationController> allReplicationControllers) {
if (allReplicationControllers == null
|| allReplicationControllers.isEmpty()
|| pod == null) {
return null;
}
return allReplicationControllers.stream()
.filter(rc -> containsAll(rc.getReplicaSelector(), pod.getLabels()))
.findFirst()
.orElse(null);
}
public static IReplicationController getLatestDeploymentConfigVersion(List<IReplicationController> rcs) {
if (rcs == null
|| rcs.isEmpty()) {
return null;
}
return rcs.stream()
.max(new NumericResourceAttributeComparator<IReplicationController>() {
@Override
protected int getResourceAttribute(IReplicationController rc) {
return safeParseInt(rc.getAnnotation(OpenShiftAPIAnnotations.DEPLOYMENT_CONFIG_LATEST_VERSION));
}
})
.orElse(null);
}
public static IDeploymentConfig getLatestResourceVersion(List<IDeploymentConfig> dcs) {
if (dcs == null
|| dcs.isEmpty()) {
return null;
}
return dcs.stream()
.max(new NumericResourceAttributeComparator<IDeploymentConfig>() {
@Override
protected int getResourceAttribute(IDeploymentConfig dc) {
return safeParseInt(dc.getResourceVersion());
}
})
.orElse(null);
}
private abstract static class NumericResourceAttributeComparator<R> implements Comparator<R>{
@Override
public int compare(R r1, R r2) {
if (r1 == null) {
if (r2 == null) {
return 0;
} else {
return 1;
}
} else {
if (r2 == null) {
return -1;
} else {
int attr1 = getResourceAttribute(r1);
int attr2 = getResourceAttribute(r2);
if (attr1 < attr2) {
return -1;
} else if (attr1 == attr2) {
return 0;
} else {
return 1;
}
}
}
}
protected abstract int getResourceAttribute(R r);
protected int safeParseInt(String string) {
try {
return Integer.parseInt(string);
} catch(NumberFormatException e1) {
return -1;
}
}
}
/**
* Returns the first build config out of the given list of build configs
* that matches the given service.
*
* @param service
* the service that the build configs shall match
* @param buildConfigs
* the build configs that shall be introspected
* @return
*
* @see #getBuildConfigsFor(IService, List)
* @see #areRelated(IBuildConfig, IService)
* @see IBuildConfig
* @see IService
*/
public static IBuildConfig getBuildConfigFor(IService service, List<IBuildConfig> buildConfigs) {
List<IBuildConfig> matchinBuildConfigs = getBuildConfigsFor(service, buildConfigs);
if (matchinBuildConfigs.isEmpty()) {
return null;
} else {
return matchinBuildConfigs.get(0);
}
}
/**
* Returns the first build config out of the given list of build configs
* that matches the given deployment config.
*
* @param deploymentConfig
* the deployment config that the build configs shall match
* @param buildConfigs
* the build configs that shall be introspected
* @return
*
* @see #getBuildConfigsForService(IService, List)
* @see #areRelated(IBuildConfig, IService)
* @see IBuildConfig
* @see IDeploymentConfig
*/
private static IBuildConfig getBuildConfigFor(IDeploymentConfig deploymentConfig, List<IBuildConfig> buildConfigs) {
List<IBuildConfig> matchinBuildConfigs = getBuildConfigsFor(deploymentConfig, buildConfigs);
if (matchinBuildConfigs.isEmpty()) {
return null;
} else {
return matchinBuildConfigs.get(0);
}
}
/**
* Returns build configs of the given list of build configs
* that match the given deployment config.
*
* @param serv
* @param buildConfigs
* @return
*
* @see #areRelated(IBuildConfig, IService)
* @see IBuildConfig
* @see IService
*/
public static List<IBuildConfig> getBuildConfigsFor(IDeploymentConfig deploymentConfig, List<IBuildConfig> buildConfigs) {
if (buildConfigs == null
|| buildConfigs.isEmpty()) {
return Collections.emptyList();
}
return buildConfigs.stream()
.filter(bc -> areRelated(bc, deploymentConfig))
.collect(Collectors.toList());
}
/**
* Returns the first build config out of the given list of build configs
* that matches the given OpenShift resource (service, replication controller,...).
*
* @param resource
* the OpenShift resource that the build configs shall match
* @param buildConfigs
* the build configs that shall be introspected
* @return
*
* @see #getBuildConfigsForService(IService, List)
* @see IBuildConfig
*/
public static IBuildConfig getBuildConfigFor(IResource resource, List<IBuildConfig> buildConfigs) {
if (ResourceKind.SERVICE.equals(resource.getKind())) {
return getBuildConfigFor((IService) resource, buildConfigs);
} else if (ResourceKind.DEPLOYMENT_CONFIG.equals(resource.getKind())) {
return getBuildConfigFor((IDeploymentConfig) resource, buildConfigs);
} else {
return null;
}
}
/**
* Returns {@code true} if the given build config matches the name of the
* given service.
*
* @param config
* @param deploymentConfig
* @return
*/
public static boolean areRelated(final IBuildConfig config, final IDeploymentConfig deploymentConfig) {
if (deploymentConfig != null
&& !StringUtils.isEmpty(deploymentConfig.getName())
&& config != null) {
return deploymentConfig.getName().equals(config.getName());
}
return false;
}
/**
* Checks whether the service and deployment config are related.
* @param service the service to match
* @param dc the deployment config to match
* @return true if they are related
*/
public static boolean areRelated(final IService service, final IDeploymentConfig dc) {
if (dc == null) {
return false;
}
String dcName = dc.getName();
return service.getProject().getResources(ResourceKind.POD).stream()
.filter(pod -> dcName.equals(pod.getAnnotation(OpenShiftAPIAnnotations.DEPLOYMENT_CONFIG_NAME)))
.filter(pod -> containsAll(service.getSelector(), pod.getLabels()))
.count() > 0;
}
/**
* Returns git controlled workspace projects that match the uri of the given build config.
*
* @param buildConfig the build config whose source git shall be matched
* @param workspaceProjects all workspace projects that shall be inspected
* @return
*
* @see IBuildConfig#getSourceURI()
* @see org.eclipse.core.resources.IProject
* @see EGitUtils#isSharedWithGit(org.eclipse.core.resources.IProject)
*/
public static org.eclipse.core.resources.IProject getWorkspaceProjectFor(
IBuildConfig buildConfig, List<org.eclipse.core.resources.IProject> workspaceProjects) {
if (workspaceProjects == null
|| workspaceProjects.isEmpty()) {
return null;
}
return workspaceProjects.stream()
// only git shared projects
.filter(project -> EGitUtils.isSharedWithGit(project))
.filter(project -> {
try {
if (buildConfig != null
&& !StringUtils.isEmpty(buildConfig.getSourceURI())) {
return EGitUtils.getAllRemoteURIs(project)
.contains(new URIish(buildConfig.getSourceURI()));
}
} catch (CoreException | URISyntaxException e) {
}
return false;
})
.findFirst().orElseGet(() -> null);
}
public static boolean isSuccessful(IImageStreamImport imageStreamImport) {
return imageStreamImport.getImageStatus().stream()
.filter(s -> s.isSuccess()).findFirst().isPresent();
}
public static String getDeploymentConfigNameFor(List<IPod> pods) {
if (pods == null
|| pods.isEmpty()) {
return null;
}
return pods.stream()
.filter(pod -> pod.getLabels().containsKey(DEPLOYMENT_CONFIG_KEY))
.findFirst()
.map(pod -> pod.getLabels().get(DEPLOYMENT_CONFIG_KEY))
.orElse(null);
}
public static IProject getProject(IResource resource) {
IProject project = null;
if (resource instanceof IProject) {
project = (IProject) resource;
} else if (resource != null) {
project = resource.getProject();
}
return project;
}
public static String getDeploymentConfigName(IReplicationController rc) {
String dcName = rc.getAnnotation(OpenShiftAPIAnnotations.DEPLOYMENT_CONFIG_NAME);
if (StringUtils.isEmpty(dcName)) {
dcName = null;
}
return dcName;
}
public static IDeploymentConfig getDeploymentConfigFor(IReplicationController rc, Collection<IDeploymentConfig> dcs) {
if (rc == null) {
return null;
}
String dcName = getDeploymentConfigName(rc);
if (dcName == null ||
dcs.isEmpty()) {
return null;
}
return dcs.stream()
.filter(dc -> dcName.equals(dc.getName()))
.max(new NumericResourceAttributeComparator<IDeploymentConfig>() {
@Override
protected int getResourceAttribute(IDeploymentConfig dc) {
return safeParseInt(dc.getResourceVersion());
}
})
.orElse(null);
}
/**
* Extracts the last segment of an URI, stripped from .git suffixes
*
* Made public for testing purposes.
*/
public static String getProjectNameForURI(String uri) {
String projectName = null;
if (StringUtils.isNotEmpty(uri)) {
String mangledUri = uri.trim();
mangledUri = stripTrailingSlashes(mangledUri);
mangledUri = stripDotGit(mangledUri);
if (mangledUri != null) {
projectName = getLastPathSegment(mangledUri);
}
}
return projectName;
}
private static String stripDotGit(String uri) {
String strippedUri = uri;
if (strippedUri.endsWith(".git")) {
strippedUri = strippedUri.substring(0, strippedUri.length() - 4);
if (strippedUri.endsWith("/")) {
// '/' before .git is error
return null;
}
}
return strippedUri;
}
private static String getLastPathSegment(String mangledUri) {
int index = mangledUri.lastIndexOf("/");
if (index >= 0) {
return mangledUri.substring(index + 1);
} else {
return mangledUri;
}
}
/**
* Removes (multiple) trailing "/" from the given uri
*
* @param uri
* @return
*/
private static String stripTrailingSlashes(String uri) {
String strippedUri = uri;
while (strippedUri.endsWith("/")) {
// Trailing slashes do not matter.
strippedUri = strippedUri.substring(0, strippedUri.length() - 1);
}
return strippedUri;
}
}