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
.exec
.ExecuteStreamHandler
;
20 import org
.apache
.commons
.io
.IOUtils
;
21 import org
.apache
.commons
.logging
.Log
;
22 import org
.apache
.commons
.logging
.LogFactory
;
23 import org
.argeo
.slc
.SlcException
;
24 import org
.argeo
.slc
.core
.execution
.ExecutionResources
;
25 import org
.argeo
.slc
.core
.execution
.tasks
.SystemCall
;
26 import org
.springframework
.core
.io
.Resource
;
27 import org
.springframework
.util
.StringUtils
;
29 import com
.jcraft
.jsch
.Channel
;
30 import com
.jcraft
.jsch
.ChannelExec
;
31 import com
.jcraft
.jsch
.ChannelShell
;
32 import com
.jcraft
.jsch
.Session
;
34 public class RemoteExec
extends AbstractJschTask
{
35 private final static Log log
= LogFactory
.getLog(RemoteExec
.class);
37 private Boolean failOnBadExitStatus
= true;
39 private List
<String
> commands
= new ArrayList
<String
>();
40 private String command
;
41 private SystemCall systemCall
;
42 private List
<SystemCall
> systemCalls
= new ArrayList
<SystemCall
>();
43 private Resource script
;
44 private Boolean xForwarding
= false;
45 private Boolean agentForwarding
= false;
46 private Boolean forceShell
= false;
47 private Map
<String
, String
> env
= new HashMap
<String
, String
>();
48 private Resource stdIn
= null;
49 private Resource stdOut
= null;
50 private ExecutionResources executionResources
;
54 private ExecuteStreamHandler streamHandler
= null;
56 private Integer lastExitStatus
= null;
58 * If set, stdout is written to it as a list of lines. Cleared before each
61 private List
<String
> stdOutLines
= null;
62 private Boolean logEvenIfStdOutLines
= false;
63 private Boolean quiet
= false;
68 public RemoteExec(SshTarget sshTarget
, String cmd
) {
69 setSshTarget(sshTarget
);
73 public void run(Session session
) {
74 List
<String
> commandsToUse
= new ArrayList
<String
>(commands
);
75 String commandToUse
= command
;
76 // convert system calls
77 if (systemCall
!= null) {
79 throw new SlcException("Cannot specify command AND systemCall");
80 commandToUse
= convertSystemCall(systemCall
);
83 if (systemCalls
.size() != 0) {
84 if (commandsToUse
.size() != 0)
85 throw new SlcException(
86 "Cannot specify commands AND systemCalls");
87 for (SystemCall systemCall
: systemCalls
)
88 commandsToUse
.add(convertSystemCall(systemCall
));
92 // TODO: simply pass the script as a string command
93 if (commandsToUse
.size() != 0)
94 throw new SlcException("Cannot specify commands and script");
95 BufferedReader reader
= null;
97 reader
= new BufferedReader(new InputStreamReader(
98 script
.getInputStream()));
100 while ((line
= reader
.readLine()) != null) {
101 if (!StringUtils
.hasText(line
))
103 commandsToUse
.add(line
);
105 } catch (IOException e
) {
106 throw new SlcException("Cannot read script " + script
, e
);
108 IOUtils
.closeQuietly(reader
);
113 // for the time being do not interpret both \n and ;
115 // until we know how to parse ; within ""
116 if (commandToUse
.indexOf('\n') >= 0) {
117 StringTokenizer st
= new StringTokenizer(commandToUse
, "\n");
118 while (st
.hasMoreTokens()) {
119 String cmd
= st
.nextToken();
120 commandsToUse
.add(cmd
);
122 } else if (commandToUse
.indexOf(';') >= 0) {
123 StringTokenizer st
= new StringTokenizer(commandToUse
, ";");
124 while (st
.hasMoreTokens()) {
125 String cmd
= st
.nextToken();
126 commandsToUse
.add(cmd
);
129 commandsToUse
.add(commandToUse
);
136 if (commandsToUse
.size() > 0) {
137 commandsToUse
.add(0, "su - " + user
);
138 commandsToUse
.add("exit");
140 if (command
.indexOf('\"') >= 0)
141 throw new SlcException(
142 "Don't know how to su a command with \", use shell instead.");
143 commandToUse
= "su - " + user
+ " -c \"" + command
+ "\"";
147 // execute command(s)
148 if (commandToUse
!= null) {
149 if (commandsToUse
.size() != 0)
150 throw new SlcException(
151 "Specify either a single command or a list of commands.");
152 remoteExec(session
, commandToUse
);
154 if (commandsToUse
.size() == 0)
155 throw new SlcException(
156 "Neither a single command or a list of commands has been specified.");
158 remoteExec(session
, commandsToUse
, script
!= null ?
"script "
159 + script
.getFilename() : commandsToUse
.size() + " commands");
163 protected String
convertSystemCall(SystemCall systemCall
) {
164 // TODO: prepend environment variables
165 // TODO: deal with exec dir
166 return systemCall
.asCommand();
169 protected void remoteExec(Session session
, final List
<String
> commands
,
170 String description
) {
172 final ChannelShell channel
= (ChannelShell
) session
173 .openChannel("shell");
174 channel
.setInputStream(null);
175 channel
.setXForwarding(xForwarding
);
176 channel
.setAgentForwarding(agentForwarding
);
177 channel
.setEnv(new Hashtable
<String
, String
>(env
));
180 * // Choose the pty-type "vt102".
181 * ((ChannelShell)channel).setPtyType("vt102");
184 final BufferedWriter writer
= new BufferedWriter(
185 new OutputStreamWriter(channel
.getOutputStream()));
187 if (log
.isDebugEnabled())
188 log
.debug("Run " + description
+ " on " + getSshTarget()
192 // write commands to shell
193 Thread writerThread
= new Thread("Shell writer " + getSshTarget()) {
197 for (String line
: commands
) {
198 if (!StringUtils
.hasText(line
))
203 writer
.append("exit");
206 // channel.disconnect();
207 } catch (IOException e
) {
208 throw new SlcException("Cannot write to shell on "
209 + getSshTarget(), e
);
211 IOUtils
.closeQuietly(writer
);
215 writerThread
.start();
218 checkExitStatus(channel
);
219 channel
.disconnect();
221 } catch (Exception e
) {
222 throw new SlcException("Cannot use SSH shell on " + getSshTarget(),
228 protected void remoteExec(Session session
, String command
) {
230 final ChannelExec channel
= (ChannelExec
) session
231 .openChannel("exec");
232 channel
.setCommand(command
);
234 channel
.setInputStream(null);
235 channel
.setXForwarding(xForwarding
);
236 channel
.setAgentForwarding(agentForwarding
);
237 channel
.setEnv(new Hashtable
<String
, String
>(env
));
238 channel
.setErrStream(null);
243 if (log
.isTraceEnabled())
244 log
.trace("Run '" + command
+ "' on " + getSshTarget() + "...");
250 if (streamHandler
!= null) {
251 streamHandler
.start();
252 while (!channel
.isClosed()) {
255 } catch (Exception e
) {
261 checkExitStatus(channel
);
262 channel
.disconnect();
263 } catch (Exception e
) {
264 throw new SlcException("Cannot execute remotely '" + command
265 + "' on " + getSshTarget(), e
);
269 protected void readStdOut(Channel channel
) {
271 if (stdOut
!= null) {
272 OutputStream localStdOut
= createOutputStream(stdOut
);
274 IOUtils
.copy(channel
.getInputStream(), localStdOut
);
276 IOUtils
.closeQuietly(localStdOut
);
278 } else if (streamHandler
!= null) {
279 if (channel
.getInputStream() != null)
280 streamHandler
.setProcessOutputStream(channel
283 BufferedReader stdOut
= null;
285 InputStream in
= channel
.getInputStream();
286 stdOut
= new BufferedReader(new InputStreamReader(in
));
288 while ((line
= stdOut
.readLine()) != null) {
289 if (!line
.trim().equals("")) {
291 if (stdOutLines
!= null) {
292 stdOutLines
.add(line
);
293 if (logEvenIfStdOutLines
&& !quiet
)
302 IOUtils
.closeQuietly(stdOut
);
305 } catch (IOException e
) {
306 throw new SlcException("Cannot redirect stdout from "
307 + getSshTarget(), e
);
311 protected void readStdErr(final ChannelExec channel
) {
312 if (streamHandler
!= null) {
314 streamHandler
.setProcessOutputStream(channel
.getErrStream());
315 } catch (IOException e
) {
316 throw new SlcException("Cannot read stderr from "
317 + getSshTarget(), e
);
320 new Thread("stderr " + getSshTarget()) {
322 BufferedReader stdErr
= null;
324 InputStream in
= channel
.getErrStream();
325 stdErr
= new BufferedReader(new InputStreamReader(in
));
327 while ((line
= stdErr
.readLine()) != null) {
328 if (!line
.trim().equals(""))
331 } catch (IOException e
) {
332 if (log
.isDebugEnabled())
333 log
.error("Cannot read stderr from "
334 + getSshTarget(), e
);
336 IOUtils
.closeQuietly(stdErr
);
343 protected void readStdIn(final ChannelExec channel
) {
345 Thread stdInThread
= new Thread("Stdin " + getSshTarget()) {
348 OutputStream out
= null;
350 out
= channel
.getOutputStream();
351 IOUtils
.copy(stdIn
.getInputStream(), out
);
352 } catch (IOException e
) {
353 throw new SlcException("Cannot write stdin on "
354 + getSshTarget(), e
);
356 IOUtils
.closeQuietly(out
);
361 } else if (streamHandler
!= null) {
363 streamHandler
.setProcessInputStream(channel
.getOutputStream());
364 } catch (IOException e
) {
365 throw new SlcException("Cannot write stdin on "
366 + getSshTarget(), e
);
371 protected void checkExitStatus(Channel channel
) {
372 if (channel
.isClosed()) {
373 lastExitStatus
= channel
.getExitStatus();
374 if (lastExitStatus
== 0) {
375 if (log
.isTraceEnabled())
376 log
.trace("Remote execution exit status: " + lastExitStatus
);
378 String msg
= "Remote execution failed with " + " exit status: "
380 if (failOnBadExitStatus
)
381 throw new SlcException(msg
);
389 protected OutputStream
createOutputStream(Resource target
) {
390 FileOutputStream out
= null;
394 if (executionResources
!= null)
395 file
= new File(executionResources
.getAsOsPath(target
, true));
397 file
= target
.getFile();
398 out
= new FileOutputStream(file
, false);
399 } catch (IOException e
) {
400 log
.error("Cannot get file for " + target
, e
);
401 IOUtils
.closeQuietly(out
);
406 public Integer
getLastExitStatus() {
407 return lastExitStatus
;
410 public void setStreamHandler(ExecuteStreamHandler executeStreamHandler
) {
411 this.streamHandler
= executeStreamHandler
;
414 public void setCommand(String command
) {
415 this.command
= command
;
418 public void setCommands(List
<String
> commands
) {
419 this.commands
= commands
;
422 public void setFailOnBadExitStatus(Boolean failOnBadExitStatus
) {
423 this.failOnBadExitStatus
= failOnBadExitStatus
;
426 public void setSystemCall(SystemCall systemCall
) {
427 this.systemCall
= systemCall
;
430 public void setSystemCalls(List
<SystemCall
> systemCalls
) {
431 this.systemCalls
= systemCalls
;
434 public void setScript(Resource script
) {
435 this.script
= script
;
438 public void setxForwarding(Boolean xForwarding
) {
439 this.xForwarding
= xForwarding
;
442 public void setAgentForwarding(Boolean agentForwarding
) {
443 this.agentForwarding
= agentForwarding
;
446 public void setEnv(Map
<String
, String
> env
) {
450 public void setForceShell(Boolean forceShell
) {
451 this.forceShell
= forceShell
;
454 public List
<String
> getCommands() {
458 public void setStdOutLines(List
<String
> stdOutLines
) {
459 this.stdOutLines
= stdOutLines
;
462 public void setLogEvenIfStdOutLines(Boolean logEvenIfStdOutLines
) {
463 this.logEvenIfStdOutLines
= logEvenIfStdOutLines
;
466 public void setQuiet(Boolean quiet
) {
470 public void setStdIn(Resource stdIn
) {
474 public void setStdOut(Resource stdOut
) {
475 this.stdOut
= stdOut
;
478 public void setExecutionResources(ExecutionResources executionResources
) {
479 this.executionResources
= executionResources
;
482 public void setUser(String user
) {