/*******************************************************************************
* 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 com.openshift.internal.restclient.capability.resources;
import java.io.IOException;
import java.util.Arrays;
import java.util.List;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.openshift.restclient.IClient;
import com.openshift.restclient.OpenShiftContext;
import com.openshift.restclient.OpenShiftException;
import com.openshift.restclient.capability.IBinaryCapability;
import com.openshift.restclient.capability.resources.LocationNotFoundException;
/**
* Capability that wraps the OpenShift binary
*
* @author Jeff Cantrill
*
*/
public abstract class AbstractOpenShiftBinaryCapability implements IBinaryCapability {
private static final Logger LOG = LoggerFactory.getLogger(AbstractOpenShiftBinaryCapability.class);
private Process process;
private IClient client;
protected AbstractOpenShiftBinaryCapability(IClient client) {
this.client = client;
}
/**
* Cleanup required when stopping the process
*/
protected abstract void cleanup();
/**
* Validate arguments before starting process
* @return true if start should continue; false otherwise;
*/
protected abstract boolean validate();
/**
* Callback for building args to be sent to the {@code oc} command.
* @return the String representation of all the arguments to use when running the {@code oc} command.
*/
protected abstract String buildArgs(final List<OpenShiftBinaryOption> options);
protected IClient getClient() {
return client;
}
protected AbstractOpenShiftBinaryCapability() {
addShutdownHook();
}
protected Process getProcess() {
return process;
}
private void addShutdownHook() {
Runnable runnable = new Runnable() {
@Override
public void run() {
stop();
}
};
Runtime.getRuntime().addShutdownHook(new Thread(runnable));
}
protected String getUserFlag() {
final StringBuilder argBuilder = new StringBuilder();
argBuilder.append("--user=").append(client.getAuthorizationContext().getUserName()).append(" ");
return argBuilder.toString();
}
/**
* @return
*/
protected String getServerFlag() {
final StringBuilder argBuilder = new StringBuilder();
argBuilder.append("--server=").append(client.getBaseURL()).append(" ");
return argBuilder.toString();
}
/**
* Adds the authentication token
* @return the command-line argument to use the current token
*/
protected String getTokenFlag() {
return new StringBuilder("--token=")
.append(client.getAuthorizationContext().getToken())
.append(" ").toString();
}
/**
* @return the command-line flag to use insecure connection (skip TLS verification)
*/
protected String getSkipTlsVerifyFlag() {
return "--insecure-skip-tls-verify=true ";
}
/**
* @return the command-line flag to exclude some files/directories that do
* not need to be synchronized between the remote pod and the local
* deployment directory.
*/
protected String getGitFolderExclusionFlag() {
// no support for multiple exclusion, so excluding '.git' only for now
// see https://github.com/openshift/origin/issues/8223
return "--exclude='.git' ";
}
/**
* @return the command-line flag to avoid transferring permissions.
*/
protected String getNoPermsFlags() {
return "--no-perms=true ";
}
/**
* @return the command-line flag to delete extraneous file from destination directories.
*/
protected String getDeleteFlags() {
return "--delete ";
}
/**
* Starts the {@link Process} to run the {@code oc} command.
* @param options the command line options
*/
public final void start(final OpenShiftBinaryOption... options) {
String location = getOpenShiftBinaryLocation();
if(!validate()) {
return;
}
startProcess(location, options);
}
private void startProcess(final String location, final OpenShiftBinaryOption... options) {
String cmdLine = new StringBuilder(location).append(' ').append(buildArgs(Arrays.asList(options))).toString();
String[] args = StringUtils.split(cmdLine, " ");
ProcessBuilder builder = new ProcessBuilder(args);
builder.environment().remove("KUBECONFIG");
LOG.debug("OpenShift binary args: {}", builder.command());
try {
process = builder.start();
checkProcessIsAlive();
} catch (IOException e) {
LOG.error("Could not start process for {}.", new Object[]{ getName(), e });
throw new OpenShiftException(e, "Does your OpenShift binary location exist? Error starting process: %s",
e.getMessage());
}
}
private void checkProcessIsAlive() throws IOException {
try {
// TODO: replace fixed wait with wait for process to be running
Thread.sleep(1000);
if(!process.isAlive() && process.exitValue() != 0) {
throw new OpenShiftException("OpenShiftBinaryCapability process exited: %s",
IOUtils.toString(process.getErrorStream()));
}
} catch (InterruptedException e) {
if(!process.isAlive() && process.exitValue() != 0) {
throw new OpenShiftException("OpenShiftBinaryCapability process exited: %s",
IOUtils.toString(process.getErrorStream()));
}
}
}
/**
* Stops the {@link Process} running the {@code oc} command.
*/
public final synchronized void stop() {
if(process == null) return;
cleanup();
if(!process.isAlive()) {
final int exitValue = process.exitValue();
LOG.debug("OpenShiftBinaryCapability process exit code {}", exitValue);
if(exitValue != 0) {
try {
LOG.debug("OpenShiftBinaryCapability process error stream", IOUtils.toString(process.getErrorStream()));
} catch (IOException e) {
LOG.debug("IOException trying to debug the process error stream", e);
}
}
process = null;
return;
}
process.destroyForcibly();
}
protected String getOpenShiftBinaryLocation() {
//Check the ThreadLocal for oc binary
String location = OpenShiftContext.get().get(OPENSHIFT_BINARY_LOCATION);
if (StringUtils.isBlank(location)) {
//Fall back to System property
location = System.getProperty(OPENSHIFT_BINARY_LOCATION);
}
if(StringUtils.isBlank(location)) {
throw new LocationNotFoundException(
String.format("The OpenShift 'oc' binary location was not specified. Set the property %s",
OPENSHIFT_BINARY_LOCATION));
}
location = addQuotesIfRequired(location);
return location;
}
private String addQuotesIfRequired(String location) {
if (!StringUtils.isEmpty(location)
&& location.contains(" ")) {
location = "\"" + location + "\"";
}
return location;
}
}