2 * Copyright (C) 2007-2012 Argeo GmbH
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
8 * http://www.apache.org/licenses/LICENSE-2.0
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
16 package org
.argeo
.slc
.jsch
;
18 import java
.io
.BufferedReader
;
19 import java
.io
.BufferedWriter
;
21 import java
.io
.FileOutputStream
;
22 import java
.io
.IOException
;
23 import java
.io
.InputStream
;
24 import java
.io
.InputStreamReader
;
25 import java
.io
.OutputStream
;
26 import java
.io
.OutputStreamWriter
;
27 import java
.util
.ArrayList
;
28 import java
.util
.HashMap
;
29 import java
.util
.Hashtable
;
30 import java
.util
.List
;
32 import java
.util
.StringTokenizer
;
34 import org
.apache
.commons
.exec
.ExecuteStreamHandler
;
35 import org
.apache
.commons
.io
.IOUtils
;
36 import org
.apache
.commons
.logging
.Log
;
37 import org
.apache
.commons
.logging
.LogFactory
;
38 import org
.argeo
.slc
.SlcException
;
39 import org
.argeo
.slc
.core
.execution
.ExecutionResources
;
40 import org
.argeo
.slc
.core
.execution
.tasks
.SystemCall
;
41 import org
.springframework
.core
.io
.Resource
;
42 import org
.springframework
.util
.StringUtils
;
44 import com
.jcraft
.jsch
.Channel
;
45 import com
.jcraft
.jsch
.ChannelExec
;
46 import com
.jcraft
.jsch
.ChannelShell
;
47 import com
.jcraft
.jsch
.Session
;
49 public class RemoteExec
extends AbstractJschTask
{
50 private final static Log log
= LogFactory
.getLog(RemoteExec
.class);
52 private Boolean failOnBadExitStatus
= true;
54 private List
<String
> commands
= new ArrayList
<String
>();
55 private String command
;
56 private SystemCall systemCall
;
57 private List
<SystemCall
> systemCalls
= new ArrayList
<SystemCall
>();
58 private Resource script
;
59 private Boolean xForwarding
= false;
60 private Boolean agentForwarding
= false;
61 private Boolean forceShell
= false;
62 private Map
<String
, String
> env
= new HashMap
<String
, String
>();
63 private Resource stdIn
= null;
64 private Resource stdOut
= null;
65 private ExecutionResources executionResources
;
69 private ExecuteStreamHandler streamHandler
= null;
71 private Integer lastExitStatus
= null;
73 * If set, stdout is written to it as a list of lines. Cleared before each
76 private List
<String
> stdOutLines
= null;
77 private Boolean logEvenIfStdOutLines
= false;
78 private Boolean quiet
= false;
83 public RemoteExec(SshTarget sshTarget
, String cmd
) {
84 setSshTarget(sshTarget
);
88 public void run(Session session
) {
89 List
<String
> commandsToUse
= new ArrayList
<String
>(commands
);
90 String commandToUse
= command
;
91 // convert system calls
92 if (systemCall
!= null) {
94 throw new SlcException("Cannot specify command AND systemCall");
95 commandToUse
= convertSystemCall(systemCall
);
98 if (systemCalls
.size() != 0) {
99 if (commandsToUse
.size() != 0)
100 throw new SlcException(
101 "Cannot specify commands AND systemCalls");
102 for (SystemCall systemCall
: systemCalls
)
103 commandsToUse
.add(convertSystemCall(systemCall
));
106 if (script
!= null) {
107 // TODO: simply pass the script as a string command
108 if (commandsToUse
.size() != 0)
109 throw new SlcException("Cannot specify commands and script");
110 BufferedReader reader
= null;
112 reader
= new BufferedReader(new InputStreamReader(
113 script
.getInputStream()));
115 while ((line
= reader
.readLine()) != null) {
116 if (!StringUtils
.hasText(line
))
118 commandsToUse
.add(line
);
120 } catch (IOException e
) {
121 throw new SlcException("Cannot read script " + script
, e
);
123 IOUtils
.closeQuietly(reader
);
128 // for the time being do not interpret both \n and ;
130 // until we know how to parse ; within ""
131 if (commandToUse
.indexOf('\n') >= 0) {
132 StringTokenizer st
= new StringTokenizer(commandToUse
, "\n");
133 while (st
.hasMoreTokens()) {
134 String cmd
= st
.nextToken();
135 commandsToUse
.add(cmd
);
137 } else if (commandToUse
.indexOf(';') >= 0) {
138 StringTokenizer st
= new StringTokenizer(commandToUse
, ";");
139 while (st
.hasMoreTokens()) {
140 String cmd
= st
.nextToken();
141 commandsToUse
.add(cmd
);
144 commandsToUse
.add(commandToUse
);
151 if (commandsToUse
.size() > 0) {
152 commandsToUse
.add(0, "su - " + user
);
153 commandsToUse
.add("exit");
155 if (command
.indexOf('\"') >= 0)
156 throw new SlcException(
157 "Don't know how to su a command with \", use shell instead.");
158 commandToUse
= "su - " + user
+ " -c \"" + command
+ "\"";
162 // execute command(s)
163 if (commandToUse
!= null) {
164 if (commandsToUse
.size() != 0)
165 throw new SlcException(
166 "Specify either a single command or a list of commands.");
167 remoteExec(session
, commandToUse
);
169 if (commandsToUse
.size() == 0)
170 throw new SlcException(
171 "Neither a single command or a list of commands has been specified.");
173 remoteExec(session
, commandsToUse
, script
!= null ?
"script "
174 + script
.getFilename() : commandsToUse
.size() + " commands");
178 protected String
convertSystemCall(SystemCall systemCall
) {
179 // TODO: prepend environment variables
180 // TODO: deal with exec dir
181 return systemCall
.asCommand();
184 protected void remoteExec(Session session
, final List
<String
> commands
,
185 String description
) {
187 final ChannelShell channel
= (ChannelShell
) session
188 .openChannel("shell");
189 channel
.setInputStream(null);
190 channel
.setXForwarding(xForwarding
);
191 channel
.setAgentForwarding(agentForwarding
);
192 channel
.setEnv(new Hashtable
<String
, String
>(env
));
195 * // Choose the pty-type "vt102".
196 * ((ChannelShell)channel).setPtyType("vt102");
199 final BufferedWriter writer
= new BufferedWriter(
200 new OutputStreamWriter(channel
.getOutputStream()));
202 if (log
.isDebugEnabled())
203 log
.debug("Run " + description
+ " on " + getSshTarget()
207 // write commands to shell
208 Thread writerThread
= new Thread("Shell writer " + getSshTarget()) {
212 for (String line
: commands
) {
213 if (!StringUtils
.hasText(line
))
218 writer
.append("exit");
221 // channel.disconnect();
222 } catch (IOException e
) {
223 throw new SlcException("Cannot write to shell on "
224 + getSshTarget(), e
);
226 IOUtils
.closeQuietly(writer
);
230 writerThread
.start();
233 checkExitStatus(channel
);
234 channel
.disconnect();
236 } catch (Exception e
) {
237 throw new SlcException("Cannot use SSH shell on " + getSshTarget(),
243 protected void remoteExec(Session session
, String command
) {
245 final ChannelExec channel
= (ChannelExec
) session
246 .openChannel("exec");
247 channel
.setCommand(command
);
249 channel
.setInputStream(null);
250 channel
.setXForwarding(xForwarding
);
251 channel
.setAgentForwarding(agentForwarding
);
252 channel
.setEnv(new Hashtable
<String
, String
>(env
));
253 channel
.setErrStream(null);
258 if (log
.isDebugEnabled())
259 log
.debug("Run '" + command
+ "' on " + getSshTarget() + "...");
265 if (streamHandler
!= null){
266 streamHandler
.start();
267 while(!channel
.isClosed()){
270 } catch (Exception e
) {
276 checkExitStatus(channel
);
277 channel
.disconnect();
278 } catch (Exception e
) {
279 throw new SlcException("Cannot execute remotely '" + command
280 + "' on " + getSshTarget(), e
);
284 protected void readStdOut(Channel channel
) {
286 if (stdOut
!= null) {
287 OutputStream localStdOut
= createOutputStream(stdOut
);
289 IOUtils
.copy(channel
.getInputStream(), localStdOut
);
291 IOUtils
.closeQuietly(localStdOut
);
293 } else if (streamHandler
!= null) {
294 if (channel
.getInputStream() != null)
295 streamHandler
.setProcessOutputStream(channel
298 BufferedReader stdOut
= null;
300 InputStream in
= channel
.getInputStream();
301 stdOut
= new BufferedReader(new InputStreamReader(in
));
303 while ((line
= stdOut
.readLine()) != null) {
304 if (!line
.trim().equals("")) {
306 if (stdOutLines
!= null) {
307 stdOutLines
.add(line
);
308 if (logEvenIfStdOutLines
&& !quiet
)
317 IOUtils
.closeQuietly(stdOut
);
320 } catch (IOException e
) {
321 throw new SlcException("Cannot redirect stdout from "
322 + getSshTarget(), e
);
326 protected void readStdErr(final ChannelExec channel
) {
327 if (streamHandler
!= null) {
329 streamHandler
.setProcessOutputStream(channel
.getErrStream());
330 } catch (IOException e
) {
331 throw new SlcException("Cannot read stderr from "
332 + getSshTarget(), e
);
335 new Thread("stderr " + getSshTarget()) {
337 BufferedReader stdErr
= null;
339 InputStream in
= channel
.getErrStream();
340 stdErr
= new BufferedReader(new InputStreamReader(in
));
342 while ((line
= stdErr
.readLine()) != null) {
343 if (!line
.trim().equals(""))
346 } catch (IOException e
) {
347 if (log
.isDebugEnabled())
348 log
.error("Cannot read stderr from "
349 + getSshTarget(), e
);
351 IOUtils
.closeQuietly(stdErr
);
358 protected void readStdIn(final ChannelExec channel
) {
360 Thread stdInThread
= new Thread("Stdin " + getSshTarget()) {
363 OutputStream out
= null;
365 out
= channel
.getOutputStream();
366 IOUtils
.copy(stdIn
.getInputStream(), out
);
367 } catch (IOException e
) {
368 throw new SlcException("Cannot write stdin on "
369 + getSshTarget(), e
);
371 IOUtils
.closeQuietly(out
);
376 } else if (streamHandler
!= null) {
378 streamHandler
.setProcessInputStream(channel
.getOutputStream());
379 } catch (IOException e
) {
380 throw new SlcException("Cannot write stdin on "
381 + getSshTarget(), e
);
386 protected void checkExitStatus(Channel channel
) {
387 if (channel
.isClosed()) {
388 lastExitStatus
= channel
.getExitStatus();
389 if (lastExitStatus
== 0) {
390 if (log
.isTraceEnabled())
391 log
.trace("Remote execution exit status: " + lastExitStatus
);
393 String msg
= "Remote execution failed with " + " exit status: "
395 if (failOnBadExitStatus
)
396 throw new SlcException(msg
);
404 protected OutputStream
createOutputStream(Resource target
) {
405 FileOutputStream out
= null;
409 if (executionResources
!= null)
410 file
= new File(executionResources
.getAsOsPath(target
, true));
412 file
= target
.getFile();
413 out
= new FileOutputStream(file
, false);
414 } catch (IOException e
) {
415 log
.error("Cannot get file for " + target
, e
);
416 IOUtils
.closeQuietly(out
);
421 public Integer
getLastExitStatus() {
422 return lastExitStatus
;
425 public void setStreamHandler(ExecuteStreamHandler executeStreamHandler
) {
426 this.streamHandler
= executeStreamHandler
;
429 public void setCommand(String command
) {
430 this.command
= command
;
433 public void setCommands(List
<String
> commands
) {
434 this.commands
= commands
;
437 public void setFailOnBadExitStatus(Boolean failOnBadExitStatus
) {
438 this.failOnBadExitStatus
= failOnBadExitStatus
;
441 public void setSystemCall(SystemCall systemCall
) {
442 this.systemCall
= systemCall
;
445 public void setSystemCalls(List
<SystemCall
> systemCalls
) {
446 this.systemCalls
= systemCalls
;
449 public void setScript(Resource script
) {
450 this.script
= script
;
453 public void setxForwarding(Boolean xForwarding
) {
454 this.xForwarding
= xForwarding
;
457 public void setAgentForwarding(Boolean agentForwarding
) {
458 this.agentForwarding
= agentForwarding
;
461 public void setEnv(Map
<String
, String
> env
) {
465 public void setForceShell(Boolean forceShell
) {
466 this.forceShell
= forceShell
;
469 public List
<String
> getCommands() {
473 public void setStdOutLines(List
<String
> stdOutLines
) {
474 this.stdOutLines
= stdOutLines
;
477 public void setLogEvenIfStdOutLines(Boolean logEvenIfStdOutLines
) {
478 this.logEvenIfStdOutLines
= logEvenIfStdOutLines
;
481 public void setQuiet(Boolean quiet
) {
485 public void setStdIn(Resource stdIn
) {
489 public void setStdOut(Resource stdOut
) {
490 this.stdOut
= stdOut
;
493 public void setExecutionResources(ExecutionResources executionResources
) {
494 this.executionResources
= executionResources
;
497 public void setUser(String user
) {