2 * Copyright (C) 2010 Mathieu Baudier <mbaudier@argeo.org>
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.
17 package org
.argeo
.slc
.jsch
;
19 import java
.io
.BufferedReader
;
20 import java
.io
.BufferedWriter
;
22 import java
.io
.FileOutputStream
;
23 import java
.io
.IOException
;
24 import java
.io
.InputStream
;
25 import java
.io
.InputStreamReader
;
26 import java
.io
.OutputStream
;
27 import java
.io
.OutputStreamWriter
;
28 import java
.util
.ArrayList
;
29 import java
.util
.HashMap
;
30 import java
.util
.Hashtable
;
31 import java
.util
.List
;
33 import java
.util
.StringTokenizer
;
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
;
70 * If set, stdout is written to it as a list of lines. Cleared before each
73 private List
<String
> stdOutLines
= null;
74 private Boolean logEvenIfStdOutLines
= false;
75 private Boolean quiet
= false;
80 public RemoteExec(SshTarget sshTarget
, String cmd
) {
81 setSshTarget(sshTarget
);
85 public void run(Session session
) {
86 List
<String
> commandsToUse
= new ArrayList
<String
>(commands
);
87 String commandToUse
= command
;
88 // convert system calls
89 if (systemCall
!= null) {
91 throw new SlcException("Cannot specify command AND systemCall");
92 commandToUse
= convertSystemCall(systemCall
);
95 if (systemCalls
.size() != 0) {
96 if (commandsToUse
.size() != 0)
97 throw new SlcException(
98 "Cannot specify commands AND systemCalls");
99 for (SystemCall systemCall
: systemCalls
)
100 commandsToUse
.add(convertSystemCall(systemCall
));
103 if (script
!= null) {
104 // TODO: simply pass the script as a string command
105 if (commandsToUse
.size() != 0)
106 throw new SlcException("Cannot specify commands and script");
107 BufferedReader reader
= null;
109 reader
= new BufferedReader(new InputStreamReader(script
112 while ((line
= reader
.readLine()) != null) {
113 if (!StringUtils
.hasText(line
))
115 commandsToUse
.add(line
);
117 } catch (IOException e
) {
118 throw new SlcException("Cannot read script " + script
, e
);
120 IOUtils
.closeQuietly(reader
);
125 // for the time being do not interpret both \n and ;
127 // until we know how to parse ; within ""
128 if (commandToUse
.indexOf('\n') >= 0) {
129 StringTokenizer st
= new StringTokenizer(commandToUse
, "\n");
130 while (st
.hasMoreTokens()) {
131 String cmd
= st
.nextToken();
132 commandsToUse
.add(cmd
);
134 } else if (commandToUse
.indexOf(';') >= 0) {
135 StringTokenizer st
= new StringTokenizer(commandToUse
, ";");
136 while (st
.hasMoreTokens()) {
137 String cmd
= st
.nextToken();
138 commandsToUse
.add(cmd
);
141 commandsToUse
.add(commandToUse
);
148 if (commandsToUse
.size() > 0) {
149 commandsToUse
.add(0, "su - " + user
);
150 commandsToUse
.add("exit");
152 if (command
.indexOf('\"') >= 0)
153 throw new SlcException(
154 "Don't know how to su a command with \", use shell instead.");
155 commandToUse
= "su - " + user
+ " -c \"" + command
+ "\"";
159 // execute command(s)
160 if (commandToUse
!= null) {
161 if (commandsToUse
.size() != 0)
162 throw new SlcException(
163 "Specify either a single command or a list of commands.");
164 remoteExec(session
, commandToUse
);
166 if (commandsToUse
.size() == 0)
167 throw new SlcException(
168 "Neither a single command or a list of commands has been specified.");
170 remoteExec(session
, commandsToUse
, script
!= null ?
"script "
171 + script
.getFilename() : commandsToUse
.size() + " commands");
175 protected String
convertSystemCall(SystemCall systemCall
) {
176 // TODO: prepend environment variables
177 // TODO: deal with exec dir
178 return systemCall
.asCommand();
181 protected void remoteExec(Session session
, final List
<String
> commands
,
182 String description
) {
184 final ChannelShell channel
= (ChannelShell
) session
185 .openChannel("shell");
186 channel
.setInputStream(null);
187 channel
.setXForwarding(xForwarding
);
188 channel
.setAgentForwarding(agentForwarding
);
189 channel
.setEnv(new Hashtable
<String
, String
>(env
));
192 * // Choose the pty-type "vt102".
193 * ((ChannelShell)channel).setPtyType("vt102");
196 final BufferedWriter writer
= new BufferedWriter(
197 new OutputStreamWriter(channel
.getOutputStream()));
199 if (log
.isDebugEnabled())
200 log
.debug("Run " + description
+ " on " + getSshTarget()
204 // write commands to shell
205 Thread writerThread
= new Thread("Shell writer " + getSshTarget()) {
209 for (String line
: commands
) {
210 if (!StringUtils
.hasText(line
))
215 writer
.append("exit");
218 // channel.disconnect();
219 } catch (IOException e
) {
220 throw new SlcException("Cannot write to shell on "
221 + getSshTarget(), e
);
223 IOUtils
.closeQuietly(writer
);
227 writerThread
.start();
230 checkExitStatus(channel
);
231 channel
.disconnect();
233 } catch (Exception e
) {
234 throw new SlcException("Cannot use SSH shell on " + getSshTarget(),
240 protected void remoteExec(Session session
, String command
) {
242 final ChannelExec channel
= (ChannelExec
) session
243 .openChannel("exec");
244 channel
.setCommand(command
);
246 channel
.setInputStream(null);
247 channel
.setXForwarding(xForwarding
);
248 channel
.setAgentForwarding(agentForwarding
);
249 channel
.setEnv(new Hashtable
<String
, String
>(env
));
250 channel
.setErrStream(null);
255 if (log
.isDebugEnabled())
256 log
.debug("Run '" + command
+ "' on " + getSshTarget() + "...");
260 Thread stdInThread
= new Thread("Stdin " + getSshTarget()) {
263 OutputStream out
= null;
265 out
= channel
.getOutputStream();
266 IOUtils
.copy(stdIn
.getInputStream(), out
);
267 } catch (IOException e
) {
268 throw new SlcException("Cannot write stdin on "
269 + getSshTarget(), e
);
271 IOUtils
.closeQuietly(out
);
278 checkExitStatus(channel
);
279 channel
.disconnect();
280 } catch (Exception e
) {
281 throw new SlcException("Cannot execute remotely '" + command
282 + "' on " + getSshTarget(), e
);
286 protected void readStdErr(final ChannelExec channel
) {
287 new Thread("stderr " + getSshTarget()) {
289 BufferedReader stdErr
= null;
291 InputStream in
= channel
.getErrStream();
292 stdErr
= new BufferedReader(new InputStreamReader(in
));
294 while ((line
= stdErr
.readLine()) != null) {
295 if (!line
.trim().equals(""))
298 } catch (IOException e
) {
299 if (log
.isDebugEnabled())
300 log
.error("Cannot read stderr from " + getSshTarget(),
303 IOUtils
.closeQuietly(stdErr
);
309 protected void readStdOut(Channel channel
) {
310 if (stdOut
!= null) {
311 OutputStream localStdOut
= createOutputStream(stdOut
);
313 IOUtils
.copy(channel
.getInputStream(), localStdOut
);
314 } catch (IOException e
) {
315 throw new SlcException("Cannot redirect stdout", e
);
317 IOUtils
.closeQuietly(localStdOut
);
320 BufferedReader stdOut
= null;
322 InputStream in
= channel
.getInputStream();
323 stdOut
= new BufferedReader(new InputStreamReader(in
));
325 while ((line
= stdOut
.readLine()) != null) {
326 if (!line
.trim().equals("")) {
328 if (stdOutLines
!= null) {
329 stdOutLines
.add(line
);
330 if (logEvenIfStdOutLines
&& !quiet
)
338 } catch (IOException e
) {
339 if (log
.isDebugEnabled())
340 log
.error("Cannot read stdout from " + getSshTarget(), e
);
342 IOUtils
.closeQuietly(stdOut
);
347 protected void checkExitStatus(Channel channel
) {
348 if (channel
.isClosed()) {
349 int exitStatus
= channel
.getExitStatus();
350 if (exitStatus
== 0) {
351 if (log
.isTraceEnabled())
352 log
.trace("Remote execution exit status: " + exitStatus
);
354 String msg
= "Remote execution failed with " + " exit status: "
356 if (failOnBadExitStatus
)
357 throw new SlcException(msg
);
365 protected OutputStream
createOutputStream(Resource target
) {
366 FileOutputStream out
= null;
370 if (executionResources
!= null)
371 file
= new File(executionResources
.getAsOsPath(target
, true));
373 file
= target
.getFile();
374 out
= new FileOutputStream(file
, false);
375 } catch (IOException e
) {
376 log
.error("Cannot get file for " + target
, e
);
377 IOUtils
.closeQuietly(out
);
382 public void setCommand(String command
) {
383 this.command
= command
;
386 public void setCommands(List
<String
> commands
) {
387 this.commands
= commands
;
390 public void setFailOnBadExitStatus(Boolean failOnBadExitStatus
) {
391 this.failOnBadExitStatus
= failOnBadExitStatus
;
394 public void setSystemCall(SystemCall systemCall
) {
395 this.systemCall
= systemCall
;
398 public void setSystemCalls(List
<SystemCall
> systemCalls
) {
399 this.systemCalls
= systemCalls
;
402 public void setScript(Resource script
) {
403 this.script
= script
;
406 public void setxForwarding(Boolean xForwarding
) {
407 this.xForwarding
= xForwarding
;
410 public void setAgentForwarding(Boolean agentForwarding
) {
411 this.agentForwarding
= agentForwarding
;
414 public void setEnv(Map
<String
, String
> env
) {
418 public void setForceShell(Boolean forceShell
) {
419 this.forceShell
= forceShell
;
422 public List
<String
> getCommands() {
426 public void setStdOutLines(List
<String
> stdOutLines
) {
427 this.stdOutLines
= stdOutLines
;
430 public void setLogEvenIfStdOutLines(Boolean logEvenIfStdOutLines
) {
431 this.logEvenIfStdOutLines
= logEvenIfStdOutLines
;
434 public void setQuiet(Boolean quiet
) {
438 public void setStdIn(Resource stdIn
) {
442 public void setStdOut(Resource stdOut
) {
443 this.stdOut
= stdOut
;
446 public void setExecutionResources(ExecutionResources executionResources
) {
447 this.executionResources
= executionResources
;
450 public void setUser(String user
) {