]> git.argeo.org Git - gpl/argeo-slc.git/blob - legacy/org.argeo.slc.support/src/org/argeo/slc/jsch/RemoteExec.java
Adapt to changes in Argeo Commons
[gpl/argeo-slc.git] / legacy / org.argeo.slc.support / src / org / argeo / slc / jsch / RemoteExec.java
1 package org.argeo.slc.jsch;
2
3 import java.io.BufferedReader;
4 import java.io.BufferedWriter;
5 import java.io.File;
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;
16 import java.util.Map;
17 import java.util.StringTokenizer;
18
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;
28
29 import com.jcraft.jsch.Channel;
30 import com.jcraft.jsch.ChannelExec;
31 import com.jcraft.jsch.ChannelShell;
32 import com.jcraft.jsch.Session;
33
34 public class RemoteExec extends AbstractJschTask {
35 private final static Log log = LogFactory.getLog(RemoteExec.class);
36
37 private Boolean failOnBadExitStatus = true;
38
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;
51
52 private String user;
53
54 private ExecuteStreamHandler streamHandler = null;
55
56 private Integer lastExitStatus = null;
57 /**
58 * If set, stdout is written to it as a list of lines. Cleared before each
59 * run.
60 */
61 private List<String> stdOutLines = null;
62 private Boolean logEvenIfStdOutLines = false;
63 private Boolean quiet = false;
64
65 public RemoteExec() {
66 }
67
68 public RemoteExec(SshTarget sshTarget, String cmd) {
69 setSshTarget(sshTarget);
70 setCommand(cmd);
71 }
72
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) {
78 if (command != null)
79 throw new SlcException("Cannot specify command AND systemCall");
80 commandToUse = convertSystemCall(systemCall);
81 }
82
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));
89 }
90
91 if (script != null) {
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;
96 try {
97 reader = new BufferedReader(new InputStreamReader(
98 script.getInputStream()));
99 String line = null;
100 while ((line = reader.readLine()) != null) {
101 if (!StringUtils.hasText(line))
102 continue;
103 commandsToUse.add(line);
104 }
105 } catch (IOException e) {
106 throw new SlcException("Cannot read script " + script, e);
107 } finally {
108 IOUtils.closeQuietly(reader);
109 }
110 }
111
112 if (forceShell) {
113 // for the time being do not interpret both \n and ;
114 // priority to \n
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);
121 }
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);
127 }
128 } else {
129 commandsToUse.add(commandToUse);
130 }
131 commandToUse = null;
132 }
133
134 // run as user
135 if (user != null) {
136 if (commandsToUse.size() > 0) {
137 commandsToUse.add(0, "su - " + user);
138 commandsToUse.add("exit");
139 } else {
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 + "\"";
144 }
145 }
146
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);
153 } else {
154 if (commandsToUse.size() == 0)
155 throw new SlcException(
156 "Neither a single command or a list of commands has been specified.");
157
158 remoteExec(session, commandsToUse, script != null ? "script "
159 + script.getFilename() : commandsToUse.size() + " commands");
160 }
161 }
162
163 protected String convertSystemCall(SystemCall systemCall) {
164 // TODO: prepend environment variables
165 // TODO: deal with exec dir
166 return systemCall.asCommand();
167 }
168
169 protected void remoteExec(Session session, final List<String> commands,
170 String description) {
171 try {
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));
178
179 /*
180 * // Choose the pty-type "vt102".
181 * ((ChannelShell)channel).setPtyType("vt102");
182 */
183 // Writer thread
184 final BufferedWriter writer = new BufferedWriter(
185 new OutputStreamWriter(channel.getOutputStream()));
186
187 if (log.isDebugEnabled())
188 log.debug("Run " + description + " on " + getSshTarget()
189 + "...");
190 channel.connect();
191
192 // write commands to shell
193 Thread writerThread = new Thread("Shell writer " + getSshTarget()) {
194 @Override
195 public void run() {
196 try {
197 for (String line : commands) {
198 if (!StringUtils.hasText(line))
199 continue;
200 writer.write(line);
201 writer.newLine();
202 }
203 writer.append("exit");
204 writer.newLine();
205 writer.flush();
206 // channel.disconnect();
207 } catch (IOException e) {
208 throw new SlcException("Cannot write to shell on "
209 + getSshTarget(), e);
210 } finally {
211 IOUtils.closeQuietly(writer);
212 }
213 }
214 };
215 writerThread.start();
216
217 readStdOut(channel);
218 checkExitStatus(channel);
219 channel.disconnect();
220
221 } catch (Exception e) {
222 throw new SlcException("Cannot use SSH shell on " + getSshTarget(),
223 e);
224 }
225
226 }
227
228 protected void remoteExec(Session session, String command) {
229 try {
230 final ChannelExec channel = (ChannelExec) session
231 .openChannel("exec");
232 channel.setCommand(command);
233
234 channel.setInputStream(null);
235 channel.setXForwarding(xForwarding);
236 channel.setAgentForwarding(agentForwarding);
237 channel.setEnv(new Hashtable<String, String>(env));
238 channel.setErrStream(null);
239
240 // Standard Error
241 readStdErr(channel);
242
243 if (log.isTraceEnabled())
244 log.trace("Run '" + command + "' on " + getSshTarget() + "...");
245 channel.connect();
246
247 readStdIn(channel);
248 readStdOut(channel);
249
250 if (streamHandler != null) {
251 streamHandler.start();
252 while (!channel.isClosed()) {
253 try {
254 Thread.sleep(100);
255 } catch (Exception e) {
256 break;
257 }
258 }
259 }
260
261 checkExitStatus(channel);
262 channel.disconnect();
263 } catch (Exception e) {
264 throw new SlcException("Cannot execute remotely '" + command
265 + "' on " + getSshTarget(), e);
266 }
267 }
268
269 protected void readStdOut(Channel channel) {
270 try {
271 if (stdOut != null) {
272 OutputStream localStdOut = createOutputStream(stdOut);
273 try {
274 IOUtils.copy(channel.getInputStream(), localStdOut);
275 } finally {
276 IOUtils.closeQuietly(localStdOut);
277 }
278 } else if (streamHandler != null) {
279 if (channel.getInputStream() != null)
280 streamHandler.setProcessOutputStream(channel
281 .getInputStream());
282 } else {
283 BufferedReader stdOut = null;
284 try {
285 InputStream in = channel.getInputStream();
286 stdOut = new BufferedReader(new InputStreamReader(in));
287 String line = null;
288 while ((line = stdOut.readLine()) != null) {
289 if (!line.trim().equals("")) {
290
291 if (stdOutLines != null) {
292 stdOutLines.add(line);
293 if (logEvenIfStdOutLines && !quiet)
294 log.info(line);
295 } else {
296 if (!quiet)
297 log.info(line);
298 }
299 }
300 }
301 } finally {
302 IOUtils.closeQuietly(stdOut);
303 }
304 }
305 } catch (IOException e) {
306 throw new SlcException("Cannot redirect stdout from "
307 + getSshTarget(), e);
308 }
309 }
310
311 protected void readStdErr(final ChannelExec channel) {
312 if (streamHandler != null) {
313 try {
314 streamHandler.setProcessOutputStream(channel.getErrStream());
315 } catch (IOException e) {
316 throw new SlcException("Cannot read stderr from "
317 + getSshTarget(), e);
318 }
319 } else {
320 new Thread("stderr " + getSshTarget()) {
321 public void run() {
322 BufferedReader stdErr = null;
323 try {
324 InputStream in = channel.getErrStream();
325 stdErr = new BufferedReader(new InputStreamReader(in));
326 String line = null;
327 while ((line = stdErr.readLine()) != null) {
328 if (!line.trim().equals(""))
329 log.error(line);
330 }
331 } catch (IOException e) {
332 if (log.isDebugEnabled())
333 log.error("Cannot read stderr from "
334 + getSshTarget(), e);
335 } finally {
336 IOUtils.closeQuietly(stdErr);
337 }
338 }
339 }.start();
340 }
341 }
342
343 protected void readStdIn(final ChannelExec channel) {
344 if (stdIn != null) {
345 Thread stdInThread = new Thread("Stdin " + getSshTarget()) {
346 @Override
347 public void run() {
348 OutputStream out = null;
349 try {
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);
355 } finally {
356 IOUtils.closeQuietly(out);
357 }
358 }
359 };
360 stdInThread.start();
361 } else if (streamHandler != null) {
362 try {
363 streamHandler.setProcessInputStream(channel.getOutputStream());
364 } catch (IOException e) {
365 throw new SlcException("Cannot write stdin on "
366 + getSshTarget(), e);
367 }
368 }
369 }
370
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);
377 } else {
378 String msg = "Remote execution failed with " + " exit status: "
379 + lastExitStatus;
380 if (failOnBadExitStatus)
381 throw new SlcException(msg);
382 else
383 log.error(msg);
384 }
385 }
386
387 }
388
389 protected OutputStream createOutputStream(Resource target) {
390 FileOutputStream out = null;
391 try {
392
393 final File file;
394 if (executionResources != null)
395 file = new File(executionResources.getAsOsPath(target, true));
396 else
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);
402 }
403 return out;
404 }
405
406 public Integer getLastExitStatus() {
407 return lastExitStatus;
408 }
409
410 public void setStreamHandler(ExecuteStreamHandler executeStreamHandler) {
411 this.streamHandler = executeStreamHandler;
412 }
413
414 public void setCommand(String command) {
415 this.command = command;
416 }
417
418 public void setCommands(List<String> commands) {
419 this.commands = commands;
420 }
421
422 public void setFailOnBadExitStatus(Boolean failOnBadExitStatus) {
423 this.failOnBadExitStatus = failOnBadExitStatus;
424 }
425
426 public void setSystemCall(SystemCall systemCall) {
427 this.systemCall = systemCall;
428 }
429
430 public void setSystemCalls(List<SystemCall> systemCalls) {
431 this.systemCalls = systemCalls;
432 }
433
434 public void setScript(Resource script) {
435 this.script = script;
436 }
437
438 public void setxForwarding(Boolean xForwarding) {
439 this.xForwarding = xForwarding;
440 }
441
442 public void setAgentForwarding(Boolean agentForwarding) {
443 this.agentForwarding = agentForwarding;
444 }
445
446 public void setEnv(Map<String, String> env) {
447 this.env = env;
448 }
449
450 public void setForceShell(Boolean forceShell) {
451 this.forceShell = forceShell;
452 }
453
454 public List<String> getCommands() {
455 return commands;
456 }
457
458 public void setStdOutLines(List<String> stdOutLines) {
459 this.stdOutLines = stdOutLines;
460 }
461
462 public void setLogEvenIfStdOutLines(Boolean logEvenIfStdOutLines) {
463 this.logEvenIfStdOutLines = logEvenIfStdOutLines;
464 }
465
466 public void setQuiet(Boolean quiet) {
467 this.quiet = quiet;
468 }
469
470 public void setStdIn(Resource stdIn) {
471 this.stdIn = stdIn;
472 }
473
474 public void setStdOut(Resource stdOut) {
475 this.stdOut = stdOut;
476 }
477
478 public void setExecutionResources(ExecutionResources executionResources) {
479 this.executionResources = executionResources;
480 }
481
482 public void setUser(String user) {
483 this.user = user;
484 }
485
486 }