/*******************************************************************************
*
* Copyright (c) 2004-2009 Oracle Corporation.
*
* 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:
*
* Kohsuke Kawaguchi, Stephen Connolly
*
*
*******************************************************************************/
package hudson.slaves;
import hudson.EnvVars;
import hudson.Util;
import hudson.Extension;
import hudson.model.Descriptor;
import hudson.model.Hudson;
import hudson.model.TaskListener;
import hudson.remoting.Channel;
import hudson.util.StreamCopyThread;
import hudson.util.FormValidation;
import hudson.util.ProcessTree;
import java.io.IOException;
import java.util.Date;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.kohsuke.stapler.DataBoundConstructor;
import org.kohsuke.stapler.QueryParameter;
/**
* {@link ComputerLauncher} through a remote login mechanism like ssh/rsh.
*
* @author Stephen Connolly
* @author Kohsuke Kawaguchi
*/
public class CommandLauncher extends ComputerLauncher {
/**
* Command line to launch the agent, like "ssh myslave java -jar
* /path/to/hudson-remoting.jar"
*/
private final String agentCommand;
/**
* Optional environment variables to add to the current environment. Can be
* null.
*/
private final EnvVars env;
@DataBoundConstructor
public CommandLauncher(String command) {
this(command, null);
}
public CommandLauncher(String command, EnvVars env) {
this.agentCommand = command;
this.env = env;
}
public String getCommand() {
return agentCommand;
}
/**
* Gets the formatted current time stamp.
*/
private static String getTimestamp() {
return String.format("[%1$tD %1$tT]", new Date());
}
@Override
public void launch(SlaveComputer computer, final TaskListener listener) {
EnvVars _cookie = null;
Process _proc = null;
try {
listener.getLogger().println(hudson.model.Messages.Slave_Launching(getTimestamp()));
if (getCommand().trim().length() == 0) {
listener.getLogger().println(Messages.CommandLauncher_NoLaunchCommand());
return;
}
listener.getLogger().println("$ " + getCommand());
ProcessBuilder pb = new ProcessBuilder(Util.tokenize(getCommand()));
final EnvVars cookie = _cookie = EnvVars.createCookie();
pb.environment().putAll(cookie);
{// system defined variables
String rootUrl = Hudson.getInstance().getRootUrl();
if (rootUrl != null) {
pb.environment().put("HUDSON_URL", rootUrl);
pb.environment().put("SLAVEJAR_URL", rootUrl + "/jnlpJars/slave.jar");
}
}
if (env != null) {
pb.environment().putAll(env);
}
final Process proc = _proc = pb.start();
// capture error information from stderr. this will terminate itself
// when the process is killed.
new StreamCopyThread("stderr copier for remote agent on " + computer.getDisplayName(),
proc.getErrorStream(), listener.getLogger()).start();
computer.setChannel(proc.getInputStream(), proc.getOutputStream(), listener.getLogger(), new Channel.Listener() {
@Override
public void onClosed(Channel channel, IOException cause) {
try {
int exitCode = proc.exitValue();
if (exitCode != 0) {
listener.error("Process terminated with exit code " + exitCode);
}
} catch (IllegalThreadStateException e) {
// hasn't terminated yet
}
try {
ProcessTree.get().killAll(proc, cookie);
} catch (InterruptedException e) {
LOGGER.log(Level.INFO, "interrupted", e);
}
}
});
LOGGER.info("slave agent launched for " + computer.getDisplayName());
} catch (InterruptedException e) {
e.printStackTrace(listener.error(Messages.ComputerLauncher_abortedLaunch()));
} catch (RuntimeException e) {
e.printStackTrace(listener.error(Messages.ComputerLauncher_unexpectedError()));
} catch (Error e) {
e.printStackTrace(listener.error(Messages.ComputerLauncher_unexpectedError()));
} catch (IOException e) {
Util.displayIOException(e, listener);
String msg = Util.getWin32ErrorMessage(e);
if (msg == null) {
msg = "";
} else {
msg = " : " + msg;
}
msg = hudson.model.Messages.Slave_UnableToLaunch(computer.getDisplayName(), msg);
LOGGER.log(Level.SEVERE, msg, e);
e.printStackTrace(listener.error(msg));
if (_proc != null) {
try {
ProcessTree.get().killAll(_proc, _cookie);
} catch (InterruptedException x) {
x.printStackTrace(listener.error(Messages.ComputerLauncher_abortedLaunch()));
}
}
}
}
private static final Logger LOGGER = Logger.getLogger(CommandLauncher.class.getName());
@Extension
public static class DescriptorImpl extends Descriptor<ComputerLauncher> {
public String getDisplayName() {
return Messages.CommandLauncher_displayName();
}
public FormValidation doCheckCommand(@QueryParameter String value) {
if (Util.fixEmptyAndTrim(value) == null) {
return FormValidation.error("Command is empty");
} else {
return FormValidation.ok();
}
}
}
}