|
@@ -56,14 +56,13 @@ package org.apache.tools.ant.taskdefs.optional.ssh; |
|
|
|
|
|
|
|
|
import org.apache.tools.ant.BuildException; |
|
|
import org.apache.tools.ant.BuildException; |
|
|
import org.apache.tools.ant.Project; |
|
|
import org.apache.tools.ant.Project; |
|
|
import org.apache.tools.ant.Task; |
|
|
|
|
|
import org.apache.tools.ant.TaskContainer; |
|
|
|
|
|
import org.apache.tools.ant.taskdefs.LogOutputStream; |
|
|
|
|
|
|
|
|
|
|
|
import java.io.BufferedReader; |
|
|
|
|
|
import java.io.InputStreamReader; |
|
|
|
|
|
import java.util.Vector; |
|
|
|
|
|
import java.util.Enumeration; |
|
|
|
|
|
|
|
|
import java.io.ByteArrayOutputStream; |
|
|
|
|
|
import java.io.File; |
|
|
|
|
|
import java.io.FileWriter; |
|
|
|
|
|
import java.io.IOException; |
|
|
|
|
|
import java.io.OutputStream; |
|
|
|
|
|
import java.io.StringReader; |
|
|
|
|
|
|
|
|
import com.jcraft.jsch.Channel; |
|
|
import com.jcraft.jsch.Channel; |
|
|
import com.jcraft.jsch.ChannelExec; |
|
|
import com.jcraft.jsch.ChannelExec; |
|
@@ -71,14 +70,22 @@ import com.jcraft.jsch.Session; |
|
|
|
|
|
|
|
|
/** |
|
|
/** |
|
|
* Executes a command on a remote machine via ssh. |
|
|
* Executes a command on a remote machine via ssh. |
|
|
|
|
|
* |
|
|
* @author Robert Anderson, riznob@hotmail.com |
|
|
* @author Robert Anderson, riznob@hotmail.com |
|
|
|
|
|
* @author Dale Anson, danson@germane-software.com |
|
|
|
|
|
* @version $Revision$ |
|
|
* @created February 2, 2003 |
|
|
* @created February 2, 2003 |
|
|
* @since Ant 1.6 |
|
|
|
|
|
|
|
|
* @since Ant 1.6 |
|
|
*/ |
|
|
*/ |
|
|
public class SSHExec extends SSHBase { |
|
|
public class SSHExec extends SSHBase { |
|
|
|
|
|
|
|
|
private String command = null; |
|
|
|
|
|
private int maxwait = 30000; |
|
|
|
|
|
|
|
|
private String command = null; // the command to execute via ssh |
|
|
|
|
|
private int maxwait = 0; // units are milliseconds, default is 0=infinite |
|
|
|
|
|
private Thread thread = null; // for waiting for the command to finish |
|
|
|
|
|
|
|
|
|
|
|
private String output_property = null; // like <exec> |
|
|
|
|
|
private File output_file = null; // like <exec> |
|
|
|
|
|
private boolean append = false; // like <exec> |
|
|
|
|
|
|
|
|
/** |
|
|
/** |
|
|
* Constructor for SSHExecTask. |
|
|
* Constructor for SSHExecTask. |
|
@@ -97,44 +104,112 @@ public class SSHExec extends SSHBase { |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
/** |
|
|
/** |
|
|
* The connection will be dropped after maxwait seconds. This is |
|
|
|
|
|
* sometimes useful when a connection may be flaky. Default is to |
|
|
|
|
|
* wait forever. |
|
|
|
|
|
|
|
|
* 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(int timeout) { |
|
|
|
|
|
maxwait = timeout * 1000; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
|
|
|
* If used, stores the output of the command to the given file. |
|
|
* |
|
|
* |
|
|
* @param maxwait The new maxwait value |
|
|
* @param maxwait The new maxwait value |
|
|
*/ |
|
|
*/ |
|
|
public void setMaxwait(int maxwait) { |
|
|
|
|
|
this.maxwait = maxwait; |
|
|
|
|
|
|
|
|
public void setOutput(File output) { |
|
|
|
|
|
output_file = output; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
|
|
|
* Should the output be 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) { |
|
|
|
|
|
output_property = property; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
/** |
|
|
/** |
|
|
* Execute the command on the remote host. |
|
|
* Execute the command on the remote host. |
|
|
* @exception BuildException Most likely a network error or bad |
|
|
|
|
|
* parameter. |
|
|
|
|
|
|
|
|
* |
|
|
|
|
|
* @exception BuildException Most likely a network error or bad parameter. |
|
|
*/ |
|
|
*/ |
|
|
public void execute() throws BuildException { |
|
|
public void execute() throws BuildException { |
|
|
if (getHost() == null) { |
|
|
if (getHost() == null) { |
|
|
throw new BuildException("Host is null."); |
|
|
|
|
|
|
|
|
throw new BuildException("Host is required."); |
|
|
} |
|
|
} |
|
|
if (getUserInfo().getName() == null) { |
|
|
if (getUserInfo().getName() == null) { |
|
|
throw new BuildException("Username is null."); |
|
|
|
|
|
|
|
|
throw new BuildException("Username is required."); |
|
|
} |
|
|
} |
|
|
if (getUserInfo().getKeyfile() == null |
|
|
if (getUserInfo().getKeyfile() == null |
|
|
&& getUserInfo().getPassword() == null) { |
|
|
&& getUserInfo().getPassword() == null) { |
|
|
throw new BuildException("Password and Keyfile are null."); |
|
|
|
|
|
|
|
|
throw new BuildException("Password or Keyfile is required."); |
|
|
} |
|
|
} |
|
|
if (command == null) { |
|
|
if (command == null) { |
|
|
throw new BuildException("Command is null."); |
|
|
|
|
|
|
|
|
throw new BuildException("Command is required."); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
ByteArrayOutputStream out = new ByteArrayOutputStream(); |
|
|
|
|
|
Tee tee = new Tee(out, System.out); |
|
|
|
|
|
|
|
|
try { |
|
|
try { |
|
|
|
|
|
// execute the command |
|
|
Session session = openSession(); |
|
|
Session session = openSession(); |
|
|
ChannelExec channel=(ChannelExec) session.openChannel("exec"); |
|
|
|
|
|
|
|
|
session.setTimeout(maxwait); |
|
|
|
|
|
final ChannelExec channel=(ChannelExec) session.openChannel("exec"); |
|
|
channel.setCommand(command); |
|
|
channel.setCommand(command); |
|
|
channel.setInputStream(System.in); |
|
|
|
|
|
channel.setOutputStream(System.out); |
|
|
|
|
|
|
|
|
channel.setOutputStream(tee); |
|
|
channel.connect(); |
|
|
channel.connect(); |
|
|
|
|
|
|
|
|
|
|
|
// wait for it to finish |
|
|
|
|
|
thread = |
|
|
|
|
|
new Thread() { |
|
|
|
|
|
public void run() { |
|
|
|
|
|
while (!channel.isEOF()) { |
|
|
|
|
|
if (thread == null) { |
|
|
|
|
|
return; |
|
|
|
|
|
} |
|
|
|
|
|
try { |
|
|
|
|
|
sleep(500); |
|
|
|
|
|
} catch (Exception e) { |
|
|
|
|
|
// ignored |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
|
|
thread.start(); |
|
|
|
|
|
thread.join(maxwait); |
|
|
|
|
|
|
|
|
|
|
|
if (thread.isAlive()) { |
|
|
|
|
|
// ran out of time |
|
|
|
|
|
thread = null; |
|
|
|
|
|
log("Timeout period exceeded, connection dropped."); |
|
|
|
|
|
} else { |
|
|
|
|
|
// completed successfully |
|
|
|
|
|
if (output_property != null) { |
|
|
|
|
|
getProject().setProperty(output_property, out.toString()); |
|
|
|
|
|
} |
|
|
|
|
|
if (output_file != null) { |
|
|
|
|
|
writeToFile(out.toString(), append, output_file); |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
} catch(Exception e){ |
|
|
} catch(Exception e){ |
|
|
if (getFailonerror()) { |
|
|
if (getFailonerror()) { |
|
|
throw new BuildException(e); |
|
|
throw new BuildException(e); |
|
@@ -143,5 +218,100 @@ public class SSHExec extends SSHBase { |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
|
|
|
* 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[8192]; |
|
|
|
|
|
int bytes_read; |
|
|
|
|
|
while (true) { |
|
|
|
|
|
bytes_read = in.read(buffer); |
|
|
|
|
|
if (bytes_read == -1) { |
|
|
|
|
|
break; |
|
|
|
|
|
} |
|
|
|
|
|
out.write(buffer, 0, bytes_read); |
|
|
|
|
|
} |
|
|
|
|
|
out.flush(); |
|
|
|
|
|
} finally { |
|
|
|
|
|
if (out != null) { |
|
|
|
|
|
out.close(); |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
|
|
|
* Similar to standard unix "tee" utility, sends output to two streams. |
|
|
|
|
|
* |
|
|
|
|
|
* @author Dale Anson, danson@germane-software.com |
|
|
|
|
|
* @version $Revision$ |
|
|
|
|
|
*/ |
|
|
|
|
|
public class Tee extends OutputStream { |
|
|
|
|
|
|
|
|
|
|
|
private OutputStream left = null; |
|
|
|
|
|
private OutputStream right = null; |
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
|
|
|
* Constructor for Tee, sends output to both of the given |
|
|
|
|
|
* streams, which are referred to as the "teed" streams. |
|
|
|
|
|
* |
|
|
|
|
|
* @param left one stream to write to |
|
|
|
|
|
* @param right the other stream to write to |
|
|
|
|
|
*/ |
|
|
|
|
|
public Tee(OutputStream left, OutputStream right) { |
|
|
|
|
|
if (left == null || right == null) { |
|
|
|
|
|
throw new IllegalArgumentException("Both streams are required."); |
|
|
|
|
|
} |
|
|
|
|
|
this.left = left; |
|
|
|
|
|
this.right = right; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
|
|
|
* Writes the specified byte to both of the teed streams. Per java api, |
|
|
|
|
|
* the general contract for write is that one byte is written to the |
|
|
|
|
|
* output stream. The byte to be written is the eight low-order bits of |
|
|
|
|
|
* the argument b. The 24 high-order bits of b are ignored. |
|
|
|
|
|
* |
|
|
|
|
|
* @param b |
|
|
|
|
|
* @exception IOException If an IO error occurs |
|
|
|
|
|
*/ |
|
|
|
|
|
public void write( int b ) throws IOException { |
|
|
|
|
|
left.write( b ); |
|
|
|
|
|
right.write( b ); |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
|
|
|
* Closes both of the teed streams. |
|
|
|
|
|
* |
|
|
|
|
|
* @exception IOException If an IO error occurs |
|
|
|
|
|
*/ |
|
|
|
|
|
public void close() throws IOException { |
|
|
|
|
|
left.close(); |
|
|
|
|
|
right.close(); |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
|
|
|
* Flushes both of the teed streams. |
|
|
|
|
|
* |
|
|
|
|
|
* @exception IOException If an IO error occurs |
|
|
|
|
|
*/ |
|
|
|
|
|
public void flush() throws IOException { |
|
|
|
|
|
left.flush(); |
|
|
|
|
|
right.flush(); |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
} |
|
|
} |
|
|
|
|
|
|