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