Hi,

This appeared unexpectedly in my inbox - I can look into apply it as a
patch and committing it if people think it's a good idea.

Thanks,
Kev


---------- Forwarded message ----------
From: Lucas Noleto <[email protected]>
Date: Tue, Jul 7, 2009 at 11:04 PM
Subject: ant SSHExec optional task support to shell mode
To: [email protected]


Hi,
I'm sending this email to u because I've seen that you're an active
comitter for the Ant project.

While using the SSHExec task I noticed that it's using the ExecCommand
mode, which doesn't seem to load and make available the environment
variables on the remote host.
Because I needed to execute the commands with the env. variables
available I decided to add a Shell mode for the task, howeverr I'm not
aware of the project's coding standards nor good practices, so I'm
sending to you a functional version of that I've coded if you ever
feel the project needs it incorporated.
Thanks!
- lucas "luky".

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 &quot;wait forever&quot;.
     *
     * @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();
            }
        }
    }
}

---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to