1 package org
.argeo
.slc
.jsch
;
3 import java
.io
.BufferedReader
;
4 import java
.io
.BufferedWriter
;
6 import java
.io
.FileOutputStream
;
7 import java
.io
.IOException
;
8 import java
.io
.InputStream
;
9 import java
.io
.InputStreamReader
;
10 import java
.io
.OutputStream
;
11 import java
.io
.OutputStreamWriter
;
12 import java
.util
.ArrayList
;
13 import java
.util
.HashMap
;
14 import java
.util
.Hashtable
;
15 import java
.util
.List
;
17 import java
.util
.StringTokenizer
;
19 import org
.apache
.commons
.io
.IOUtils
;
20 import org
.apache
.commons
.logging
.Log
;
21 import org
.apache
.commons
.logging
.LogFactory
;
22 import org
.argeo
.slc
.SlcException
;
23 import org
.argeo
.slc
.core
.execution
.ExecutionResources
;
24 import org
.argeo
.slc
.core
.execution
.tasks
.SystemCall
;
25 import org
.springframework
.core
.io
.Resource
;
26 import org
.springframework
.util
.StringUtils
;
28 import com
.jcraft
.jsch
.Channel
;
29 import com
.jcraft
.jsch
.ChannelExec
;
30 import com
.jcraft
.jsch
.ChannelShell
;
31 import com
.jcraft
.jsch
.Session
;
33 public class RemoteExec
extends AbstractJschTask
{
34 private final static Log log
= LogFactory
.getLog(RemoteExec
.class);
36 private Boolean failOnBadExitStatus
= true;
38 private List
<String
> commands
= new ArrayList
<String
>();
39 private String command
;
40 private SystemCall systemCall
;
41 private List
<SystemCall
> systemCalls
= new ArrayList
<SystemCall
>();
42 private Resource script
;
43 private Boolean xForwarding
= false;
44 private Boolean agentForwarding
= false;
45 private Boolean forceShell
= false;
46 private Map
<String
, String
> env
= new HashMap
<String
, String
>();
47 private Resource stdIn
= null;
48 private Resource stdOut
= null;
49 private ExecutionResources executionResources
;
54 * If set, stdout is written to it as a list of lines. Cleared before each
57 private List
<String
> stdOutLines
= null;
58 private Boolean logEvenIfStdOutLines
= false;
59 private Boolean quiet
= false;
61 public void run(Session session
) {
62 List
<String
> commandsToUse
= new ArrayList
<String
>(commands
);
63 String commandToUse
= command
;
64 // convert system calls
65 if (systemCall
!= null) {
67 throw new SlcException("Cannot specify command AND systemCall");
68 commandToUse
= convertSystemCall(systemCall
);
71 if (systemCalls
.size() != 0) {
72 if (commandsToUse
.size() != 0)
73 throw new SlcException(
74 "Cannot specify commands AND systemCalls");
75 for (SystemCall systemCall
: systemCalls
)
76 commandsToUse
.add(convertSystemCall(systemCall
));
80 // TODO: simply pass the script as a string command
81 if (commandsToUse
.size() != 0)
82 throw new SlcException("Cannot specify commands and script");
83 BufferedReader reader
= null;
85 reader
= new BufferedReader(new InputStreamReader(script
88 while ((line
= reader
.readLine()) != null) {
89 if (!StringUtils
.hasText(line
))
91 commandsToUse
.add(line
);
93 } catch (IOException e
) {
94 throw new SlcException("Cannot read script " + script
, e
);
96 IOUtils
.closeQuietly(reader
);
101 // for the time being do not interpret both \n and ;
103 // until we know how to parse ; within ""
104 if (commandToUse
.indexOf('\n') >= 0) {
105 StringTokenizer st
= new StringTokenizer(commandToUse
, "\n");
106 while (st
.hasMoreTokens()) {
107 String cmd
= st
.nextToken();
108 commandsToUse
.add(cmd
);
110 } else if (commandToUse
.indexOf(';') >= 0) {
111 StringTokenizer st
= new StringTokenizer(commandToUse
, ";");
112 while (st
.hasMoreTokens()) {
113 String cmd
= st
.nextToken();
114 commandsToUse
.add(cmd
);
117 commandsToUse
.add(commandToUse
);
124 if (commandsToUse
.size() > 0) {
125 commandsToUse
.add(0, "su - " + user
);
126 commandsToUse
.add("exit");
128 if (command
.indexOf('\"') >= 0)
129 throw new SlcException(
130 "Don't know how to su a command with \", use shell instead.");
131 commandToUse
= "su - " + user
+ " -c \"" + command
+ "\"";
135 // execute command(s)
136 if (commandToUse
!= null) {
137 if (commandsToUse
.size() != 0)
138 throw new SlcException(
139 "Specify either a single command or a list of commands.");
140 remoteExec(session
, commandToUse
);
142 if (commandsToUse
.size() == 0)
143 throw new SlcException(
144 "Neither a single command or a list of commands has been specified.");
146 remoteExec(session
, commandsToUse
, script
!= null ?
"script "
147 + script
.getFilename() : commandsToUse
.size() + " commands");
151 protected String
convertSystemCall(SystemCall systemCall
) {
152 // TODO: prepend environment variables
153 // TODO: deal with exec dir
154 return systemCall
.asCommand();
157 protected void remoteExec(Session session
, final List
<String
> commands
,
158 String description
) {
160 final ChannelShell channel
= (ChannelShell
) session
161 .openChannel("shell");
162 channel
.setInputStream(null);
163 channel
.setXForwarding(xForwarding
);
164 channel
.setAgentForwarding(agentForwarding
);
165 channel
.setEnv(new Hashtable
<String
, String
>(env
));
168 * // Choose the pty-type "vt102".
169 * ((ChannelShell)channel).setPtyType("vt102");
172 final BufferedWriter writer
= new BufferedWriter(
173 new OutputStreamWriter(channel
.getOutputStream()));
175 if (log
.isDebugEnabled())
176 log
.debug("Run " + description
+ " on " + getSshTarget()
180 // write commands to shell
181 Thread writerThread
= new Thread("Shell writer " + getSshTarget()) {
185 for (String line
: commands
) {
186 if (!StringUtils
.hasText(line
))
191 writer
.append("exit");
194 // channel.disconnect();
195 } catch (IOException e
) {
196 throw new SlcException("Cannot write to shell on "
197 + getSshTarget(), e
);
199 IOUtils
.closeQuietly(writer
);
203 writerThread
.start();
206 checkExitStatus(channel
);
207 channel
.disconnect();
209 } catch (Exception e
) {
210 throw new SlcException("Cannot use SSH shell on " + getSshTarget(),
216 protected void remoteExec(Session session
, String command
) {
218 final ChannelExec channel
= (ChannelExec
) session
219 .openChannel("exec");
220 channel
.setCommand(command
);
222 channel
.setInputStream(null);
223 channel
.setXForwarding(xForwarding
);
224 channel
.setAgentForwarding(agentForwarding
);
225 channel
.setEnv(new Hashtable
<String
, String
>(env
));
226 channel
.setErrStream(null);
231 if (log
.isDebugEnabled())
232 log
.debug("Run '" + command
+ "' on " + getSshTarget() + "...");
236 Thread stdInThread
= new Thread("Stdin " + getSshTarget()) {
239 OutputStream out
= null;
241 out
= channel
.getOutputStream();
242 IOUtils
.copy(stdIn
.getInputStream(), out
);
243 } catch (IOException e
) {
244 throw new SlcException("Cannot write stdin on "
245 + getSshTarget(), e
);
247 IOUtils
.closeQuietly(out
);
254 checkExitStatus(channel
);
255 channel
.disconnect();
256 } catch (Exception e
) {
257 throw new SlcException("Cannot execute remotely '" + command
258 + "' on " + getSshTarget(), e
);
262 protected void readStdErr(final ChannelExec channel
) {
263 new Thread("stderr " + getSshTarget()) {
265 BufferedReader stdErr
= null;
267 InputStream in
= channel
.getErrStream();
268 stdErr
= new BufferedReader(new InputStreamReader(in
));
270 while ((line
= stdErr
.readLine()) != null) {
271 if (!line
.trim().equals(""))
274 } catch (IOException e
) {
275 if (log
.isDebugEnabled())
276 log
.error("Cannot read stderr from " + getSshTarget(),
279 IOUtils
.closeQuietly(stdErr
);
285 protected void readStdOut(Channel channel
) {
286 if (stdOut
!= null) {
287 OutputStream localStdOut
= createOutputStream(stdOut
);
289 IOUtils
.copy(channel
.getInputStream(), localStdOut
);
290 } catch (IOException e
) {
291 throw new SlcException("Cannot redirect stdout", e
);
293 IOUtils
.closeQuietly(localStdOut
);
296 BufferedReader stdOut
= null;
298 InputStream in
= channel
.getInputStream();
299 stdOut
= new BufferedReader(new InputStreamReader(in
));
301 while ((line
= stdOut
.readLine()) != null) {
302 if (!line
.trim().equals("")) {
304 if (stdOutLines
!= null) {
305 stdOutLines
.add(line
);
306 if (logEvenIfStdOutLines
&& !quiet
)
314 } catch (IOException e
) {
315 if (log
.isDebugEnabled())
316 log
.error("Cannot read stdout from " + getSshTarget(), e
);
318 IOUtils
.closeQuietly(stdOut
);
323 protected void checkExitStatus(Channel channel
) {
324 if (channel
.isClosed()) {
325 int exitStatus
= channel
.getExitStatus();
326 if (exitStatus
== 0) {
327 if (log
.isTraceEnabled())
328 log
.trace("Remote execution exit status: " + exitStatus
);
330 String msg
= "Remote execution failed with " + " exit status: "
332 if (failOnBadExitStatus
)
333 throw new SlcException(msg
);
341 protected OutputStream
createOutputStream(Resource target
) {
342 FileOutputStream out
= null;
346 if (executionResources
!= null)
347 file
= new File(executionResources
.getAsOsPath(target
, true));
349 file
= target
.getFile();
350 out
= new FileOutputStream(file
, false);
351 } catch (IOException e
) {
352 log
.error("Cannot get file for " + target
, e
);
353 IOUtils
.closeQuietly(out
);
358 public void setCommand(String command
) {
359 this.command
= command
;
362 public void setCommands(List
<String
> commands
) {
363 this.commands
= commands
;
366 public void setFailOnBadExitStatus(Boolean failOnBadExitStatus
) {
367 this.failOnBadExitStatus
= failOnBadExitStatus
;
370 public void setSystemCall(SystemCall systemCall
) {
371 this.systemCall
= systemCall
;
374 public void setSystemCalls(List
<SystemCall
> systemCalls
) {
375 this.systemCalls
= systemCalls
;
378 public void setScript(Resource script
) {
379 this.script
= script
;
382 public void setxForwarding(Boolean xForwarding
) {
383 this.xForwarding
= xForwarding
;
386 public void setAgentForwarding(Boolean agentForwarding
) {
387 this.agentForwarding
= agentForwarding
;
390 public void setEnv(Map
<String
, String
> env
) {
394 public void setForceShell(Boolean forceShell
) {
395 this.forceShell
= forceShell
;
398 public List
<String
> getCommands() {
402 public void setStdOutLines(List
<String
> stdOutLines
) {
403 this.stdOutLines
= stdOutLines
;
406 public void setLogEvenIfStdOutLines(Boolean logEvenIfStdOutLines
) {
407 this.logEvenIfStdOutLines
= logEvenIfStdOutLines
;
410 public void setQuiet(Boolean quiet
) {
414 public void setStdIn(Resource stdIn
) {
418 public void setStdOut(Resource stdOut
) {
419 this.stdOut
= stdOut
;
422 public void setExecutionResources(ExecutionResources executionResources
) {
423 this.executionResources
= executionResources
;
426 public void setUser(String user
) {