sshexec 蚂蚁任务:环境变量

Posted

技术标签:

【中文标题】sshexec 蚂蚁任务:环境变量【英文标题】:sshexec ant task: environment variables 【发布时间】:2009-07-02 13:37:01 【问题描述】:

我正在使用 SSHExec ant 任务连接到远程主机,并且我依赖于在远程主机上设置的环境变量才能成功执行某些命令。

<sshexec host="somehost"
    username="$username"
    password="$password"
    command="set"/>

使用环境中的任务。输出的变量与我使用 SSH 客户端登录时得到的变量不同。

如何制作环境。会话可用的远程主机的变量?

【问题讨论】:

【参考方案1】:

实际上,您可以对它不启动 shell 的事实采取一些措施。使用以下内容:

<ssshexec command="/bin/bash -l yourScript.sh" .../>

使用 /bin/bash -l 将启动一个登录 shell,然后在该 shell 中执行您的脚本。就好像您有一个可以正确启动登录 shell 的 sshexec 版本一样。它必须是一个脚本。如果你想运行单个可执行命令,你可以这样做:

<sshexec command="/bin/bash -l -c 'echo $CATALINA_HOME'" .../>

【讨论】:

【参考方案2】:

我发现当前的 SSHExeceec 任务实现是使用 JSCh 的 ChannelExec(远程执行命令)而不是 ChannelShell(远程 shell)作为连接通道。

这意味着显然根据 JSCh 的当前实现,ChannelExec 不会加载环境。变量。

我仍然不确定这是对协议还是 API 的限制。

结论是,目前还没有解决问题的办法,除非你实现自己的 Ant 任务。

一份工作草案:

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();
            
        
    

【讨论】:

【参考方案3】:

另一个简单的解决方法是在运行命令之前获取用户的.bash_profile

<sshexec host="somehost"
    username="$username"
    password="$password"
    command="source ~/.bash_profile &amp;&amp; set"/>

【讨论】:

当我使用 command="source ~/.bash_profile && set"/> 时,出现类似 [sshexec] Connecting to somehost:22 [sshexec] sh: source [sshexec] 的错误: 未找到 当我运行像 command="/home/aykut/myscript" 这样的 shell 脚本时,ant 冻结了。是什么原因造成的?【参考方案4】:

很棒的帖子 chubbsondubs。我需要设置 ORACLE SID,然后执行一个没有正确出口的 PLSQL 脚本。因此,回声出口通过管道传输。

        <sshexec host="$db.ipaddr"

            verbose="true"
            trust="true"
            username="$scp.oracle.userid"
            password="$scp.oracle.password"
            command="echo exit | /bin/bash -l -c 'export ORACLE_SID=$db.name ; sqlplus $db.dbo.userid/$db.dbo.password @./INSTALL_REVPORT/CreateDatabase/gengrant.sql'"

        />

【讨论】:

以上是关于sshexec 蚂蚁任务:环境变量的主要内容,如果未能解决你的问题,请参考以下文章

linux定时任务内环境变量引起错误解决方法

如何为 AWS ECS 任务定义提供环境变量?

如何通过 terraform 在 ecs 任务定义中指定环境变量?

nodejs中如何访问ecs任务定义环境变量?

如何从 Ant 'exec' 任务中导出环境变量?

Bamboo - 在任务/脚本之间传递环境变量