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
.argeo
.api
.cms
.CmsLog
;
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 CmsLog log
= CmsLog
.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
;
53 private ExecuteStreamHandler streamHandler
= null;
55 private Integer lastExitStatus
= null;
57 * If set, stdout is written to it as a list of lines. Cleared before each
60 private List
<String
> stdOutLines
= null;
61 private Boolean logEvenIfStdOutLines
= false;
62 private Boolean quiet
= false;
67 public RemoteExec(SshTarget sshTarget
, String cmd
) {
68 setSshTarget(sshTarget
);
72 public void run(Session session
) {
73 List
<String
> commandsToUse
= new ArrayList
<String
>(commands
);
74 String commandToUse
= command
;
75 // convert system calls
76 if (systemCall
!= null) {
78 throw new SlcException("Cannot specify command AND systemCall");
79 commandToUse
= convertSystemCall(systemCall
);
82 if (systemCalls
.size() != 0) {
83 if (commandsToUse
.size() != 0)
84 throw new SlcException(
85 "Cannot specify commands AND systemCalls");
86 for (SystemCall systemCall
: systemCalls
)
87 commandsToUse
.add(convertSystemCall(systemCall
));
91 // TODO: simply pass the script as a string command
92 if (commandsToUse
.size() != 0)
93 throw new SlcException("Cannot specify commands and script");
94 BufferedReader reader
= null;
96 reader
= new BufferedReader(new InputStreamReader(
97 script
.getInputStream()));
99 while ((line
= reader
.readLine()) != null) {
100 if (!StringUtils
.hasText(line
))
102 commandsToUse
.add(line
);
104 } catch (IOException e
) {
105 throw new SlcException("Cannot read script " + script
, e
);
107 IOUtils
.closeQuietly(reader
);
112 // for the time being do not interpret both \n and ;
114 // until we know how to parse ; within ""
115 if (commandToUse
.indexOf('\n') >= 0) {
116 StringTokenizer st
= new StringTokenizer(commandToUse
, "\n");
117 while (st
.hasMoreTokens()) {
118 String cmd
= st
.nextToken();
119 commandsToUse
.add(cmd
);
121 } else if (commandToUse
.indexOf(';') >= 0) {
122 StringTokenizer st
= new StringTokenizer(commandToUse
, ";");
123 while (st
.hasMoreTokens()) {
124 String cmd
= st
.nextToken();
125 commandsToUse
.add(cmd
);
128 commandsToUse
.add(commandToUse
);
135 if (commandsToUse
.size() > 0) {
136 commandsToUse
.add(0, "su - " + user
);
137 commandsToUse
.add("exit");
139 if (command
.indexOf('\"') >= 0)
140 throw new SlcException(
141 "Don't know how to su a command with \", use shell instead.");
142 commandToUse
= "su - " + user
+ " -c \"" + command
+ "\"";
146 // execute command(s)
147 if (commandToUse
!= null) {
148 if (commandsToUse
.size() != 0)
149 throw new SlcException(
150 "Specify either a single command or a list of commands.");
151 remoteExec(session
, commandToUse
);
153 if (commandsToUse
.size() == 0)
154 throw new SlcException(
155 "Neither a single command or a list of commands has been specified.");
157 remoteExec(session
, commandsToUse
, script
!= null ?
"script "
158 + script
.getFilename() : commandsToUse
.size() + " commands");
162 protected String
convertSystemCall(SystemCall systemCall
) {
163 // TODO: prepend environment variables
164 // TODO: deal with exec dir
165 return systemCall
.asCommand();
168 protected void remoteExec(Session session
, final List
<String
> commands
,
169 String description
) {
171 final ChannelShell channel
= (ChannelShell
) session
172 .openChannel("shell");
173 channel
.setInputStream(null);
174 channel
.setXForwarding(xForwarding
);
175 channel
.setAgentForwarding(agentForwarding
);
176 channel
.setEnv(new Hashtable
<String
, String
>(env
));
179 * // Choose the pty-type "vt102".
180 * ((ChannelShell)channel).setPtyType("vt102");
183 final BufferedWriter writer
= new BufferedWriter(
184 new OutputStreamWriter(channel
.getOutputStream()));
186 if (log
.isDebugEnabled())
187 log
.debug("Run " + description
+ " on " + getSshTarget()
191 // write commands to shell
192 Thread writerThread
= new Thread("Shell writer " + getSshTarget()) {
196 for (String line
: commands
) {
197 if (!StringUtils
.hasText(line
))
202 writer
.append("exit");
205 // channel.disconnect();
206 } catch (IOException e
) {
207 throw new SlcException("Cannot write to shell on "
208 + getSshTarget(), e
);
210 IOUtils
.closeQuietly(writer
);
214 writerThread
.start();
217 checkExitStatus(channel
);
218 channel
.disconnect();
220 } catch (Exception e
) {
221 throw new SlcException("Cannot use SSH shell on " + getSshTarget(),
227 protected void remoteExec(Session session
, String command
) {
229 final ChannelExec channel
= (ChannelExec
) session
230 .openChannel("exec");
231 channel
.setCommand(command
);
233 channel
.setInputStream(null);
234 channel
.setXForwarding(xForwarding
);
235 channel
.setAgentForwarding(agentForwarding
);
236 channel
.setEnv(new Hashtable
<String
, String
>(env
));
237 channel
.setErrStream(null);
242 if (log
.isTraceEnabled())
243 log
.trace("Run '" + command
+ "' on " + getSshTarget() + "...");
249 if (streamHandler
!= null) {
250 streamHandler
.start();
251 while (!channel
.isClosed()) {
254 } catch (Exception e
) {
260 checkExitStatus(channel
);
261 channel
.disconnect();
262 } catch (Exception e
) {
263 throw new SlcException("Cannot execute remotely '" + command
264 + "' on " + getSshTarget(), e
);
268 protected void readStdOut(Channel channel
) {
270 if (stdOut
!= null) {
271 OutputStream localStdOut
= createOutputStream(stdOut
);
273 IOUtils
.copy(channel
.getInputStream(), localStdOut
);
275 IOUtils
.closeQuietly(localStdOut
);
277 } else if (streamHandler
!= null) {
278 if (channel
.getInputStream() != null)
279 streamHandler
.setProcessOutputStream(channel
282 BufferedReader stdOut
= null;
284 InputStream in
= channel
.getInputStream();
285 stdOut
= new BufferedReader(new InputStreamReader(in
));
287 while ((line
= stdOut
.readLine()) != null) {
288 if (!line
.trim().equals("")) {
290 if (stdOutLines
!= null) {
291 stdOutLines
.add(line
);
292 if (logEvenIfStdOutLines
&& !quiet
)
301 IOUtils
.closeQuietly(stdOut
);
304 } catch (IOException e
) {
305 throw new SlcException("Cannot redirect stdout from "
306 + getSshTarget(), e
);
310 protected void readStdErr(final ChannelExec channel
) {
311 if (streamHandler
!= null) {
313 streamHandler
.setProcessOutputStream(channel
.getErrStream());
314 } catch (IOException e
) {
315 throw new SlcException("Cannot read stderr from "
316 + getSshTarget(), e
);
319 new Thread("stderr " + getSshTarget()) {
321 BufferedReader stdErr
= null;
323 InputStream in
= channel
.getErrStream();
324 stdErr
= new BufferedReader(new InputStreamReader(in
));
326 while ((line
= stdErr
.readLine()) != null) {
327 if (!line
.trim().equals(""))
330 } catch (IOException e
) {
331 if (log
.isDebugEnabled())
332 log
.error("Cannot read stderr from "
333 + getSshTarget(), e
);
335 IOUtils
.closeQuietly(stdErr
);
342 protected void readStdIn(final ChannelExec channel
) {
344 Thread stdInThread
= new Thread("Stdin " + getSshTarget()) {
347 OutputStream out
= null;
349 out
= channel
.getOutputStream();
350 IOUtils
.copy(stdIn
.getInputStream(), out
);
351 } catch (IOException e
) {
352 throw new SlcException("Cannot write stdin on "
353 + getSshTarget(), e
);
355 IOUtils
.closeQuietly(out
);
360 } else if (streamHandler
!= null) {
362 streamHandler
.setProcessInputStream(channel
.getOutputStream());
363 } catch (IOException e
) {
364 throw new SlcException("Cannot write stdin on "
365 + getSshTarget(), e
);
370 protected void checkExitStatus(Channel channel
) {
371 if (channel
.isClosed()) {
372 lastExitStatus
= channel
.getExitStatus();
373 if (lastExitStatus
== 0) {
374 if (log
.isTraceEnabled())
375 log
.trace("Remote execution exit status: " + lastExitStatus
);
377 String msg
= "Remote execution failed with " + " exit status: "
379 if (failOnBadExitStatus
)
380 throw new SlcException(msg
);
388 protected OutputStream
createOutputStream(Resource target
) {
389 FileOutputStream out
= null;
393 if (executionResources
!= null)
394 file
= new File(executionResources
.getAsOsPath(target
, true));
396 file
= target
.getFile();
397 out
= new FileOutputStream(file
, false);
398 } catch (IOException e
) {
399 log
.error("Cannot get file for " + target
, e
);
400 IOUtils
.closeQuietly(out
);
405 public Integer
getLastExitStatus() {
406 return lastExitStatus
;
409 public void setStreamHandler(ExecuteStreamHandler executeStreamHandler
) {
410 this.streamHandler
= executeStreamHandler
;
413 public void setCommand(String command
) {
414 this.command
= command
;
417 public void setCommands(List
<String
> commands
) {
418 this.commands
= commands
;
421 public void setFailOnBadExitStatus(Boolean failOnBadExitStatus
) {
422 this.failOnBadExitStatus
= failOnBadExitStatus
;
425 public void setSystemCall(SystemCall systemCall
) {
426 this.systemCall
= systemCall
;
429 public void setSystemCalls(List
<SystemCall
> systemCalls
) {
430 this.systemCalls
= systemCalls
;
433 public void setScript(Resource script
) {
434 this.script
= script
;
437 public void setxForwarding(Boolean xForwarding
) {
438 this.xForwarding
= xForwarding
;
441 public void setAgentForwarding(Boolean agentForwarding
) {
442 this.agentForwarding
= agentForwarding
;
445 public void setEnv(Map
<String
, String
> env
) {
449 public void setForceShell(Boolean forceShell
) {
450 this.forceShell
= forceShell
;
453 public List
<String
> getCommands() {
457 public void setStdOutLines(List
<String
> stdOutLines
) {
458 this.stdOutLines
= stdOutLines
;
461 public void setLogEvenIfStdOutLines(Boolean logEvenIfStdOutLines
) {
462 this.logEvenIfStdOutLines
= logEvenIfStdOutLines
;
465 public void setQuiet(Boolean quiet
) {
469 public void setStdIn(Resource stdIn
) {
473 public void setStdOut(Resource stdOut
) {
474 this.stdOut
= stdOut
;
477 public void setExecutionResources(ExecutionResources executionResources
) {
478 this.executionResources
= executionResources
;
481 public void setUser(String user
) {