/*******************************************************************************
* Copyright (c) 2015-2017 Red Hat Inc..
* 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 Incorporated - initial API and implementation
*******************************************************************************/
package org.jboss.tools.openshift.core.server;
import java.io.UnsupportedEncodingException;
import java.net.MalformedURLException;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.stream.Stream;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.NullProgressMonitor;
import org.eclipse.core.runtime.preferences.IEclipsePreferences;
import org.eclipse.jdt.core.JavaCore;
import org.eclipse.osgi.util.NLS;
import org.eclipse.wst.server.core.IModule;
import org.eclipse.wst.server.core.IServer;
import org.eclipse.wst.server.core.IServerAttributes;
import org.eclipse.wst.server.core.IServerType;
import org.eclipse.wst.server.core.IServerWorkingCopy;
import org.eclipse.wst.server.core.ServerCore;
import org.eclipse.wst.server.core.internal.Server;
import org.eclipse.wst.server.core.internal.ServerWorkingCopy;
import org.eclipse.wst.server.core.model.ModuleDelegate;
import org.eclipse.wst.server.core.util.ProjectModule;
import org.jboss.ide.eclipse.as.core.server.IDeployableServer;
import org.jboss.ide.eclipse.as.core.util.IJBossToolingConstants;
import org.jboss.tools.foundation.core.plugin.log.StatusFactory;
import org.jboss.tools.openshift.common.core.connection.ConnectionURL;
import org.jboss.tools.openshift.common.core.connection.ConnectionsRegistrySingleton;
import org.jboss.tools.openshift.common.core.connection.IConnection;
import org.jboss.tools.openshift.common.core.server.ServerUtils;
import org.jboss.tools.openshift.common.core.utils.ProjectUtils;
import org.jboss.tools.openshift.common.core.utils.StringUtils;
import org.jboss.tools.openshift.common.core.utils.UrlUtils;
import org.jboss.tools.openshift.common.core.utils.VariablesHelper;
import org.jboss.tools.openshift.core.connection.Connection;
import org.jboss.tools.openshift.core.util.OpenShiftResourceUniqueId;
import org.jboss.tools.openshift.internal.core.OpenShiftCoreActivator;
import org.jboss.tools.openshift.internal.core.WatchManager;
import org.jboss.tools.openshift.internal.core.preferences.OCBinary;
import org.jboss.tools.openshift.internal.core.util.ResourceUtils;
import org.osgi.service.prefs.BackingStoreException;
import com.openshift.restclient.NotFoundException;
import com.openshift.restclient.ResourceKind;
import com.openshift.restclient.model.IDeploymentConfig;
import com.openshift.restclient.model.IPod;
import com.openshift.restclient.model.IReplicationController;
import com.openshift.restclient.model.IResource;
import com.openshift.restclient.model.IService;
/**
* @author Andre Dietisheim
*/
public class OpenShiftServerUtils {
private static final String LIVERELOAD_PORT_KEY = "port";//Key to the port # of the host the LiveReload server need to proxy
public static final String SERVER_PROJECT_QUALIFIER = "org.jboss.tools.openshift.core"; //$NON-NLS-1$
public static final String ATTR_SERVICE = "org.jboss.tools.openshift.Service"; //$NON-NLS-1$
public static final String ATTR_DEPLOYPROJECT = "org.jboss.tools.openshift.DeployProject"; //$NON-NLS-1$
public static final String ATTR_SOURCE_PATH = "org.jboss.tools.openshift.SourcePath"; //$NON-NLS-1$
public static final String ATTR_POD_PATH = "org.jboss.tools.openshift.PodPath"; //$NON-NLS-1$
public static final String ATTR_ROUTE = "org.jboss.tools.openshift.Route"; //$NON-NLS-1$
public static final String ATTR_IGNORE_CONTEXT_ROOT = "org.jboss.tools.openshift.IgnoreContextRoot";//$NON-NLS-1$
public static final String ATTR_OVERRIDE_PROJECT_SETTINGS = "org.jboss.tools.openshift.project.Override";//$NON-NLS-1$
public static final String ATTR_CONNECTIONURL = "org.jboss.tools.openshift.Connection";//$NON-NLS-1$
/** the OpensHift Server Type as defined in the plugin.xml. */
public static final String OPENSHIFT_SERVER_TYPE = "org.jboss.tools.openshift.openshift.server.type";//$NON-NLS-1$
public static final String SERVER_START_ON_CREATION = "org.jboss.tools.openshift.SERVER_START_ON_CREATION";
private static final Collection<String> SERVER_ADAPTER_ALLOWED_RESOURCE_TYPES = Collections.unmodifiableCollection(
Arrays.asList(
ResourceKind.ROUTE,
ResourceKind.SERVICE,
ResourceKind.REPLICATION_CONTROLLER,
ResourceKind.DEPLOYMENT_CONFIG,
ResourceKind.POD));
/**
* Checks if the resource is allowed for OpenShift server adapter.
*
* @param resource the OpenShift resource
* @return true if allowed
*/
public static boolean isAllowedForServerAdapter(IResource resource) {
return SERVER_ADAPTER_ALLOWED_RESOURCE_TYPES.contains(resource.getKind());
}
/**
* Returns the first openshift 3 server in the current workspace
* that matches the given OpenShift resource (service,
* deployment config, replication controller) name.
*
* @see #ATTR_SERVICE
*/
public static IServer findServerForResource(String serviceName) {
return findServerForResource(serviceName, ServerCore.getServers());
}
/**
* Returns the first openshift 3 server within the given list of servers
* that matches the given OpenShift resource (service,
* deployment config, replication controller) name.
*
* @see #ATTR_SERVICE
*/
public static IServer findServerForResource(String serviceName, IServer[] servers) {
if (StringUtils.isEmpty(serviceName)
|| servers == null
|| servers.length == 0) {
return null;
}
final IServerType serverType = getServerType();
if (serverType == null) {
return null;
}
return Stream.of(servers)
.filter(server -> serverType.equals(server.getServerType())
&& server.getAttribute(ATTR_SERVICE, "").equals(serviceName))
.findFirst().orElse(null);
}
public static IServerType getServerType() {
return ServerCore.findServerType(OpenShiftServer.SERVER_TYPE_ID);
}
public static String getServerName(IResource resource, IConnection connection) {
if (resource == null) {
return null;
}
String baseName = new StringBuilder(resource.getName())
.append(" (")
.append(resource.getKind())
.append(") at OpenShift 3 (")
.append(UrlUtils.cutPort(UrlUtils.cutScheme(connection.getHost())))
.append(")")
.toString();
return ServerUtils.getServerName(baseName);
}
public static void updateServer(String serverName, String host, String connectionUrl, IResource resource, String sourcePath, String podPath, IProject deployProject, String routeURL, IServerWorkingCopy server) {
String deployProjectName = ProjectUtils.getName(deployProject);
updateServer(serverName, host, connectionUrl, deployProjectName, OpenShiftResourceUniqueId.get(resource), sourcePath, podPath, routeURL, server);
}
/**
* Fills the given settings into the given server adapter working copy.
* <b>IMPORTANT:</b> If the server adapter name is matching an existing server adapter, then
* we're updating this existing server adapter. If the name is a new one, then we're
* creating a new server adapter.
*
* @param server
* the server adapter working copy to configure
* @param serverName
* the name for the server adapter
* @param host
* the host for the server adapter
* @param deployProjectName
* the deploy project for the server adapter
* @param deployFolder
* the deploy folder for the server adapter
* @param remote
* the remote for the server adapter
* @param applicationName
* the application name for the server adapter
* @deprecated no callers
*/
public static void updateServer(String serverName, String host, String connectionUrl, String deployProjectName, String serviceId, String sourcePath, String podPath, IServerWorkingCopy server) {
updateServer(serverName, host, connectionUrl, deployProjectName, serviceId, sourcePath, podPath, null, server);
}
public static void updateServer(String serverName, String host, String connectionUrl, String deployProjectName, String serviceId, String sourcePath, String podPath, String routeURL, IServerWorkingCopy server) {
updateServer(server);
server.setName(serverName);
server.setHost(host);
server.setAttribute(ATTR_CONNECTIONURL, connectionUrl);
server.setAttribute(ATTR_DEPLOYPROJECT, deployProjectName);
server.setAttribute(ATTR_SOURCE_PATH, sourcePath);
server.setAttribute(ATTR_POD_PATH, podPath);
server.setAttribute(ATTR_SERVICE, serviceId);
server.setAttribute(ATTR_ROUTE, routeURL);
}
/**
* Sets the given value for the given attribute in the given server and saves it.
*
* @param attribute
* @param value
* @param server
* @throws CoreException
*/
public static void updateServer(String attribute, String value, IServerWorkingCopy server) throws CoreException {
if (!StringUtils.isEmpty(attribute)) {
throw new CoreException(OpenShiftCoreActivator.statusFactory().errorStatus(
"Could not update server project, setting name missing."));
}
if (!StringUtils.isEmpty(value)) {
throw new CoreException(OpenShiftCoreActivator.statusFactory().errorStatus(
NLS.bind("Could not update server project, value for setting {0} is missing.", attribute)));
}
server.setAttribute(attribute, value);
server.save(true, new NullProgressMonitor());
}
private static void updateServer(IServerWorkingCopy server) {
server.setAttribute(IDeployableServer.SERVER_MODE, OpenShiftServer.OPENSHIFT3_MODE_ID);
((ServerWorkingCopy) server).setAutoPublishSetting(Server.AUTO_PUBLISH_RESOURCE);
server.setAttribute(IJBossToolingConstants.IGNORE_LAUNCH_COMMANDS, String.valueOf(Boolean.TRUE));
int webPort = 80;//TODO should we determine the webPort from the route?
server.setAttribute(IJBossToolingConstants.WEB_PORT, webPort);
server.setAttribute(LIVERELOAD_PORT_KEY, webPort);//So that we can open via LiveReload
server.setAttribute(IJBossToolingConstants.WEB_PORT_DETECT, Boolean.FALSE.toString());
server.setAttribute(IDeployableServer.DEPLOY_DIRECTORY_TYPE, IDeployableServer.DEPLOY_CUSTOM);
server.setAttribute(IDeployableServer.ZIP_DEPLOYMENTS_PREF, true);
}
public static void updateServerProject(String connectionUrl, IResource resource, String sourcePath, String podPath, String routeURL, IProject project) {
updateServerProject(connectionUrl, OpenShiftResourceUniqueId.get(resource), sourcePath, podPath, routeURL, project);
}
public static void updateServerProject(String connectionUrl, String serviceId, String sourcePath, String podPath, String routeURL, IProject project) {
IEclipsePreferences node = ServerUtils.getProjectNode(SERVER_PROJECT_QUALIFIER, project);
node.put(ATTR_CONNECTIONURL, connectionUrl);
node.put(ATTR_DEPLOYPROJECT, project.getName());
node.put(ATTR_SOURCE_PATH, sourcePath);
node.put(ATTR_SERVICE, serviceId);
updateProjectNode(ATTR_POD_PATH, podPath, node);
updateProjectNode(ATTR_ROUTE, routeURL, node);
saveProject(node);
}
private static void updateProjectNode(String attribute, String value, IEclipsePreferences node) {
if (value != null) {
node.put(attribute, value);
} else {
node.remove(attribute);
}
}
public static void updateServerProject(String attribute, String value, IProject project) throws CoreException {
if (!StringUtils.isEmpty(attribute)) {
throw new CoreException(OpenShiftCoreActivator.statusFactory().errorStatus(
"Could not update server project, setting name missing."));
}
if (!StringUtils.isEmpty(value)) {
throw new CoreException(OpenShiftCoreActivator.statusFactory().errorStatus(
NLS.bind("Could not update server project, value for setting {0} is missing.", attribute)));
}
IEclipsePreferences node = ServerUtils.getProjectNode(SERVER_PROJECT_QUALIFIER, project);
node.put(attribute, value);
saveProject(node);
}
private static void saveProject(IEclipsePreferences node) {
try {
node.flush();
} catch (BackingStoreException e) {
// TODO: throw, dont swallow
OpenShiftCoreActivator.pluginLog().logError(e);
}
}
public static IModule findProjectModule(IProject p) {
IModule[] all = org.eclipse.wst.server.core.ServerUtil.getModules(p);
for( int i = 0; i < all.length; i++ ) {
ModuleDelegate md = (ModuleDelegate)all[i].loadAdapter(ModuleDelegate.class, new NullProgressMonitor());
if( md instanceof ProjectModule
&& !(md instanceof org.eclipse.jst.j2ee.internal.deployables.BinaryFileModuleDelegate)) {
return all[i];
}
}
return null;
}
public static IProject getDeployProject(IServerAttributes server) {
// TODO: implement override project settings with server settings
return ProjectUtils.getProject(getDeployProjectName(server));
}
public static String getDeployProjectName(IServerAttributes server) {
if (server == null) {
return null;
}
return server.getAttribute(ATTR_DEPLOYPROJECT, (String) null);
}
public static boolean isIgnoresContextRoot(IServerAttributes server) {
return server.getAttribute(ATTR_IGNORE_CONTEXT_ROOT, true);
}
/**
* Returns true if the given server is an OpenShift one, false otherwise.
*
* @param server
* the server adapter to check
* @return true or false
*/
public static boolean isOpenShiftRuntime(IServerAttributes server) {
return OPENSHIFT_SERVER_TYPE.equals(server.getServerType().getId());
}
public static IServerWorkingCopy create(String name) throws CoreException {
final IServerWorkingCopy serverWorkingCopy =
(IServerWorkingCopy) getServerType().createServer(name, null, null);
return serverWorkingCopy;
}
/**
* Returns the connection for the given server
* @param server
* @return
*/
public static Connection getConnection(IServerAttributes server) {
if (server == null) {
return null;
}
try {
String url = getConnectionURL(server);
ConnectionURL connectionUrl = ConnectionURL.forURL(url);
if (connectionUrl != null) {
return ConnectionsRegistrySingleton.getInstance().getByUrl(connectionUrl, Connection.class);
}
} catch (UnsupportedEncodingException | MalformedURLException e) {
OpenShiftCoreActivator.pluginLog()
.logError(NLS.bind("Could not get connection url for user {0}", server.getName()), e);
}
return null;
}
public static String getConnectionURL(IServerAttributes server) {
return getAttribute(ATTR_CONNECTIONURL, server);
}
public static IResource getResource(IServerAttributes attributes) {
return getResource(attributes, getConnection(attributes));
}
/**
* Returns the OpenShift resource (service, replication controller) for the given server.
* It gets the resource name and type from
* server settings and requests the resource from the OpenShit server. It
* should thus never be called from the UI thread.
*
* @param server the server (attributes) to get the resource name from
* @param connection the connection (to the OpenShift server) to retrieve the resource from
* @return the OpenShift resource
*/
public static IResource getResource(IServerAttributes server, Connection connection) {
// TODO: implement override project settings with server settings
String uniqueId = getAttribute(ATTR_SERVICE, server);
if (StringUtils.isEmpty(uniqueId)) {
return null;
}
if( connection == null ) {
return null;
}
String projectName = OpenShiftResourceUniqueId.getProjectName(uniqueId);
String kind = OpenShiftResourceUniqueId.getKind(uniqueId);
List<IResource> resources = connection.getResources(kind, projectName);
IResource resource = OpenShiftResourceUniqueId.getByUniqueId(uniqueId, resources);
if (resource != null) {
WatchManager.getInstance().startWatch(resource.getProject(), connection);
}
return resource;
}
public static String getRouteURL(IServerAttributes server) {
return getAttribute(ATTR_ROUTE, server);
}
public static String getPodPath(IServerAttributes server) {
// TODO: implement override project settings with server settings
return getAttribute(OpenShiftServerUtils.ATTR_POD_PATH, server);
}
/**
* Creates an {@link RSync}
* @param server the {@link IServer} on which the {@code rsync} operation will be performed
* @return the {@link RSync} to be used to execute the command.
* @throws CoreException
*/
public static RSync createRSync(final IServer server) throws CoreException {
assertServerNotNull(server);
final String location = OCBinary.getInstance().getLocation();
if( location == null ) {
throw new CoreException(OpenShiftCoreActivator.statusFactory().errorStatus(
"Binary for oc-tools could not be found. Please open the OpenShift 3 Preference Page and set the location of the oc binary."));
}
final IResource resource = getResource(server);
if (resource == null) {
throw new CoreException(OpenShiftCoreActivator.statusFactory().errorStatus(
NLS.bind("Server {0} could not determine the service to publish to.", server.getName())));
}
String podPath = getPodPath(server);
if (StringUtils.isEmpty(podPath)) {
podPath = loadPodPath(resource, server);
if (StringUtils.isEmpty(podPath)) {
throw new CoreException(OpenShiftCoreActivator.statusFactory().errorStatus(
NLS.bind("Server {0} could not determine the destination directory to publish to.", server.getName())));
}
}
return new RSync(resource, podPath, server);
}
public static String loadPodPath(IResource resource, IServer server) throws CoreException {
return new PodDeploymentPathProvider().load(resource, getConnection(server));
}
public static String getSourcePath(IServerAttributes server) {
// TODO: implement override project settings with server settings
String rawSourcePath = getAttribute(ATTR_SOURCE_PATH, server);
if (org.apache.commons.lang.StringUtils.isBlank(rawSourcePath)) {
return rawSourcePath;
}
return VariablesHelper.replaceVariables(rawSourcePath);
}
public static boolean isOverridesProject(IServerAttributes server) {
return server.getAttribute(ATTR_OVERRIDE_PROJECT_SETTINGS, false);
}
/**
* Returns the attribute value for the given name and project. The given
* default value is return if the value doesnt exist or cannot be retrieved.
*
* @param project
* @param name
* @param defaultValue
* @return
*/
private static String getProjectAttribute(String name, String defaultValue, IProject project) {
return ServerUtils.getProjectAttribute(name, defaultValue, SERVER_PROJECT_QUALIFIER, project);
}
/**
* Returns the replication controller for the given server (attributes). The
* match is done by the service that the given (openshift server) is bound
* to. This method does remote calls to the OpenShift server and thus should
* never be called from the UI thread.
*
* @param server
* @return the replication controller for the given server
*
* @see #getResource(IServerAttributes)
* @see ResourceUtils#getPodsFor(IService, Collection)
*/
public static IReplicationController getReplicationController(IServerAttributes server) throws CoreException {
assertServerNotNull(server);
Connection connection = getConnection(server);
if (connection == null) {
throw new CoreException(OpenShiftCoreActivator.statusFactory().errorStatus(
NLS.bind("Could not find the connection for server {0}"
+ "Your server adapter might refer to an inexistant connection."
, server.getName())));
}
IResource resource = getResource(server, connection);
if (resource == null) {
throw new CoreException(OpenShiftCoreActivator.statusFactory().errorStatus(
NLS.bind("Could not find the resource for server {0}"
+ "Your server adapter might refer to an inexistant resource.",
server.getName())));
}
if (resource instanceof IDeploymentConfig) {
return (IDeploymentConfig) resource;
} else if (resource instanceof IService) {
List<IPod> pods = connection.getResources(ResourceKind.POD, resource.getProject().getName());
List<IPod> resourcePods = ResourceUtils.getPodsFor(resource, pods);
if (resourcePods == null
|| resourcePods.isEmpty()) {
throw new CoreException(OpenShiftCoreActivator.statusFactory().errorStatus(
NLS.bind("Could not find pods for resource {0} in connection {1}. "
+ "OpenShift might be still building the pods for resource {0}.",
resource.getName(), connection.getHost())));
}
String dcName = ResourceUtils.getDeploymentConfigNameFor(resourcePods);
if (dcName == null) {
throw new CoreException(OpenShiftCoreActivator.statusFactory().errorStatus(
NLS.bind("Could not find deployment config for {0}. "
+ "Your build might be still running and pods not created yet or "
+ "there might be no labels on your pods pointing to the wanted deployment config.",
server.getName())));
}
try {
return connection.getResource(ResourceKind.DEPLOYMENT_CONFIG, resource.getNamespace(), dcName);
} catch (NotFoundException e) {
return null;
}
} else if (resource instanceof IReplicationController) {
String deploymentConfigName = ResourceUtils.getDeploymentConfigName((IReplicationController) resource);
if (deploymentConfigName != null) {
try {
return connection.getResource(ResourceKind.DEPLOYMENT_CONFIG, resource.getNamespace(), deploymentConfigName);
} catch (NotFoundException e) {
return (IReplicationController) resource;
}
} else {
return (IReplicationController) resource;
}
} else {
return null;
}
}
/**
* Returns the value for the given key and server. Will first query the
* server and if no value was found the deploy project is queried.
*
* @param key
* @param server
* @return
*/
protected static String getAttribute(String key, IServerAttributes server) {
if (server == null) {
return null;
}
String attribute = server.getAttribute(key, (String) null);
if (attribute == null) {
attribute = getProjectAttribute(key, null, getDeployProject(server));
}
return attribute;
}
/**
* Return {@code true} if the given server has a deploy project that is a
* java project.
*
* @param server
* @return
*
* @see #getDeployProject(IServerAttributes)
*/
public static boolean isJavaProject(IServerAttributes server) {
IProject p = getDeployProject(server);
try {
return ProjectUtils.isAccessible(p) && p.hasNature(JavaCore.NATURE_ID);
} catch (CoreException e) {
OpenShiftCoreActivator.pluginLog().logError(e);
}
return false;
}
private static void assertServerNotNull(IServerAttributes server) throws CoreException {
if (server == null) {
throw new CoreException(OpenShiftCoreActivator.statusFactory().errorStatus(
"Could not determine the server to use."));
}
}
public static CoreException toCoreException(String msg, Exception e) {
return new CoreException(StatusFactory.errorStatus(OpenShiftCoreActivator.PLUGIN_ID, msg, e));
}
public static CoreException toCoreException(String msg) {
return toCoreException(msg, null);
}
}