I've found out that the current SSHExeec task implementation is using JSCh's ChannelExec (remote execution of commands) instead of a ChannelShell (remote shell) as connection channel.
That means that apparentely as per JSCh's current implementation a ChannelExec doesn't load env. variables.
I'm still not sure wether this is a limitation on the protocol or on the API.
The conclusion is that as for now there's no solution for the problem, unless you implement your own Ant task.
A working draft of how it would be:
package org.apache.tools.ant.taskdefs.optional.ssh;
import java.io.BufferedReader;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.PipedInputStream;
import java.io.PipedOutputStream;
import java.io.StringReader;
import org.apache.tools.ant.BuildException;
import org.apache.tools.ant.Project;
import org.apache.tools.ant.types.Resource;
import org.apache.tools.ant.types.resources.FileResource;
import org.apache.tools.ant.util.FileUtils;
import org.apache.tools.ant.util.KeepAliveOutputStream;
import org.apache.tools.ant.util.TeeOutputStream;
import com.jcraft.jsch.Channel;
import com.jcraft.jsch.ChannelExec;
import com.jcraft.jsch.JSchException;
import com.jcraft.jsch.Session;
/**
* Executes a command on a remote machine via ssh.
* @since Ant 1.6 (created February 2, 2003)
*/
public class SSHExecShellSupport extends SSHBase {
private static final String COMMAND_SEPARATOR = System.getProperty("line.separator");
private static final int BUFFER_SIZE = 8192;
private static final int RETRY_INTERVAL = 500;
/** the command to execute via ssh */
private String command = null;
/** units are milliseconds, default is 0=infinite */
private long maxwait = 0;
/** for waiting for the command to finish */
private Thread thread = null;
private String outputProperty = null; // like <exec>
private File outputFile = null; // like <exec>
private boolean append = false; // like <exec>
private Resource commandResource = null;
private boolean isShellMode;
private long maxTimeWithoutAnyData = 1000*10;
private static final String TIMEOUT_MESSAGE =
"Timeout period exceeded, connection dropped.";
public long getMaxTimeWithoutAnyData() {
return maxTimeWithoutAnyData;
}
public void setMaxTimeWithoutAnyData(long maxTimeWithoutAnyData) {
this.maxTimeWithoutAnyData = maxTimeWithoutAnyData;
}
public boolean isShellMode() {
return isShellMode;
}
public void setShellMode(boolean isShellMode) {
this.isShellMode = isShellMode;
}
/**
* Constructor for SSHExecTask.
*/
public SSHExecShellSupport() {
super();
}
/**
* Sets the command to execute on the remote host.
*
* @param command The new command value
*/
public void setCommand(String command) {
this.command = command;
}
/**
* Sets a commandResource from a file
* @param f the value to use.
* @since Ant 1.7.1
*/
public void setCommandResource(String f) {
this.commandResource = new FileResource(new File(f));
}
/**
* The connection can be dropped after a specified number of
* milliseconds. This is sometimes useful when a connection may be
* flaky. Default is 0, which means "wait forever".
*
* @param timeout The new timeout value in seconds
*/
public void setTimeout(long timeout) {
maxwait = timeout;
}
/**
* If used, stores the output of the command to the given file.
*
* @param output The file to write to.
*/
public void setOutput(File output) {
outputFile = output;
}
/**
* Determines if the output is appended to the file given in
* <code>setOutput</code>. Default is false, that is, overwrite
* the file.
*
* @param append True to append to an existing file, false to overwrite.
*/
public void setAppend(boolean append) {
this.append = append;
}
/**
* If set, the output of the command will be stored in the given property.
*
* @param property The name of the property in which the command output
* will be stored.
*/
public void setOutputproperty(String property) {
outputProperty = property;
}
/**
* Execute the command on the remote host.
*
* @exception BuildException Most likely a network error or bad parameter.
*/
public void execute() throws BuildException {
if (getHost() == null) {
throw new BuildException("Host is required.");
}
if (getUserInfo().getName() == null) {
throw new BuildException("Username is required.");
}
if (getUserInfo().getKeyfile() == null
&& getUserInfo().getPassword() == null) {
throw new BuildException("Password or Keyfile is required.");
}
if (command == null && commandResource == null) {
throw new BuildException("Command or commandResource is required.");
}
if(isShellMode){
shellMode();
} else {
commandMode();
}
}
private void shellMode() {
final Object lock = new Object();
Session session = null;
try {
session = openSession();
final Channel channel=session.openChannel("shell");
final PipedOutputStream pipedOS = new PipedOutputStream();
PipedInputStream pipedIS = new PipedInputStream(pipedOS);
final Thread commandProducerThread = new Thread("CommandsProducerThread"){
public void run() {
BufferedReader br = null;
try {
br = new BufferedReader(new InputStreamReader(commandResource.getInputStream()));
String singleCmd;
synchronized (lock) {
lock.wait(); // waits for the reception of the very first data (before commands are issued)
while ((singleCmd = br.readLine()) != null) {
singleCmd += COMMAND_SEPARATOR;
log("cmd : " + singleCmd, Project.MSG_INFO);
pipedOS.write(singleCmd.getBytes());
lock.notify();
try {
lock.wait();
} catch (InterruptedException e) {
log(e, Project.MSG_VERBOSE);
break;
}
}
log("Finished producing commands", Project.MSG_VERBOSE);
}
} catch (IOException e) {
log(e, Project.MSG_VERBOSE);
} catch (InterruptedException e) {
log(e, Project.MSG_VERBOSE);
} finally {
FileUtils.close(br);
}
}
};
ByteArrayOutputStream out = new ByteArrayOutputStream();
final TeeOutputStream tee = new TeeOutputStream(out, new KeepAliveOutputStream(System.out));
channel.setOutputStream(tee);
channel.setExtOutputStream(tee);
channel.setInputStream(pipedIS);
channel.connect();
// waits for it to finish receiving data response and then ask for another the producer to issue one more command
thread = new Thread("DataReceiverThread") {
public void run() {
long lastTimeConsumedData = System.currentTimeMillis(); // initializes the watch
try {
InputStream in = channel.getInputStream();
byte[] tmp = new byte[1024];
while (true) {
if(thread == null){ // works with maxTimeout (for the whole task to complete)
break;
}
while (in.available() > 0) {
int i = in.read(tmp, 0, 1024);
lastTimeConsumedData = System.currentTimeMillis();
if (i < 0){
break;
}
tee.write(tmp, 0, i);
}
if (channel.isClosed()) {
log("exit-status: " + channel.getExitStatus(), Project.MSG_INFO);
log("channel.isEOF(): " + channel.isEOF(), Project.MSG_VERBOSE);
log("channel.isConnected(): " + channel.isConnected(), Project.MSG_VERBOSE);
throw new BuildException("Connection lost."); // NOTE: it also can happen that if one of the command are "exit" the channel will be closed!
}
synchronized(lock){
long elapsedTimeWithoutData = (System.currentTimeMillis() - lastTimeConsumedData);
if (elapsedTimeWithoutData > maxTimeWithoutAnyData) {
log(elapsedTimeWithoutData / 1000 + " secs elapsed without any data reception. Notifying command producer.", Project.MSG_VERBOSE);
lock.notify(); // command producer is waiting for this
try {
lock.wait(500); // wait til we have new commands.
Thread.yield();
log("Continuing consumer loop. commandProducerThread.isAlive()?" + commandProducerThread.isAlive(), Project.MSG_VERBOSE);
if(!commandProducerThread.isAlive()){
log("No more commands to be issued and it's been too long without data reception. Exiting consumer.", Project.MSG_VERBOSE);
break;
}
} catch (InterruptedException e) {
log(e, Project.MSG_VERBOSE);
break;
}
lastTimeConsumedData = System.currentTimeMillis(); // resets watch
}
}
}
} catch (IOException e) {
throw new BuildException(e);
}
}
};
thread.start();
commandProducerThread.start();
thread.join(maxwait);
if (thread.isAlive()) {
// ran out of time
thread = null;
if (getFailonerror()) {
throw new BuildException(TIMEOUT_MESSAGE);
} else {
log(TIMEOUT_MESSAGE, Project.MSG_ERR);
}
} else {
//success
if (outputFile != null) {
writeToFile(out.toString(), append, outputFile);
}
// this is the wrong test if the remote OS is OpenVMS,
// but there doesn't seem to be a way to detect it.
log("Exit status (not reliable): " + channel.getExitStatus(), Project.MSG_INFO);
// int ec = channel.getExitStatus(); FIXME
// if (ec != 0) {
// String msg = "Remote command failed with exit status " + ec;
// if (getFailonerror()) {
// throw new BuildException(msg);
// } else {
// log(msg, Project.MSG_ERR);
// }
// }
}
} catch (Exception e){
throw new BuildException(e);
} finally {
if (session != null && session.isConnected()) {
session.disconnect();
}
}
}
private void commandMode() {
Session session = null;
try {
session = openSession();
/* called once */
if (command != null) {
log("cmd : " + command, Project.MSG_INFO);
ByteArrayOutputStream out = executeCommand(session, command);
if (outputProperty != null) {
//#bugzilla 43437
getProject().setNewProperty(outputProperty, command + " : " + out);
}
} else { // read command resource and execute for each command
try {
BufferedReader br = new BufferedReader(
new InputStreamReader(commandResource.getInputStream()));
String cmd;
String output = "";
while ((cmd = br.readLine()) != null) {
log("cmd : " + cmd, Project.MSG_INFO);
ByteArrayOutputStream out = executeCommand(session, cmd);
output += cmd + " : " + out + "\n";
}
if (outputProperty != null) {
//#bugzilla 43437
getProject().setNewProperty(outputProperty, output);
}
FileUtils.close(br);
} catch (IOException e) {
throw new BuildException(e);
}
}
} catch (JSchException e) {
throw new BuildException(e);
} finally {
if (session != null && session.isConnected()) {
session.disconnect();
}
}
}
private ByteArrayOutputStream executeCommand(Session session, String cmd)
throws BuildException {
ByteArrayOutputStream out = new ByteArrayOutputStream();
TeeOutputStream tee = new TeeOutputStream(out, new KeepAliveOutputStream(System.out));
try {
final ChannelExec channel;
session.setTimeout((int) maxwait);
/* execute the command */
channel = (ChannelExec) session.openChannel("exec");
channel.setCommand(cmd);
channel.setOutputStream(tee);
channel.setExtOutputStream(tee);
channel.connect();
// wait for it to finish
thread =
new Thread() {
public void run() {
while (!channel.isClosed()) {
if (thread == null) {
return;
}
try {
sleep(RETRY_INTERVAL);
} catch (Exception e) {
// ignored
}
}
}
};
thread.start();
thread.join(maxwait);
if (thread.isAlive()) {
// ran out of time
thread = null;
if (getFailonerror()) {
throw new BuildException(TIMEOUT_MESSAGE);
} else {
log(TIMEOUT_MESSAGE, Project.MSG_ERR);
}
} else {
//success
if (outputFile != null) {
writeToFile(out.toString(), append, outputFile);
}
// this is the wrong test if the remote OS is OpenVMS,
// but there doesn't seem to be a way to detect it.
int ec = channel.getExitStatus();
if (ec != 0) {
String msg = "Remote command failed with exit status " + ec;
if (getFailonerror()) {
throw new BuildException(msg);
} else {
log(msg, Project.MSG_ERR);
}
}
}
} catch (BuildException e) {
throw e;
} catch (JSchException e) {
if (e.getMessage().indexOf("session is down") >= 0) {
if (getFailonerror()) {
throw new BuildException(TIMEOUT_MESSAGE, e);
} else {
log(TIMEOUT_MESSAGE, Project.MSG_ERR);
}
} else {
if (getFailonerror()) {
throw new BuildException(e);
} else {
log("Caught exception: " + e.getMessage(),
Project.MSG_ERR);
}
}
} catch (Exception e) {
if (getFailonerror()) {
throw new BuildException(e);
} else {
log("Caught exception: " + e.getMessage(), Project.MSG_ERR);
}
}
return out;
}
/**
* Writes a string to a file. If destination file exists, it may be
* overwritten depending on the "append" value.
*
* @param from string to write
* @param to file to write to
* @param append if true, append to existing file, else overwrite
* @exception Exception most likely an IOException
*/
private void writeToFile(String from, boolean append, File to)
throws IOException {
FileWriter out = null;
try {
out = new FileWriter(to.getAbsolutePath(), append);
StringReader in = new StringReader(from);
char[] buffer = new char[BUFFER_SIZE];
int bytesRead;
while (true) {
bytesRead = in.read(buffer);
if (bytesRead == -1) {
break;
}
out.write(buffer, 0, bytesRead);
}
out.flush();
} finally {
if (out != null) {
out.close();
}
}
}
}