]> git.argeo.org Git - gpl/argeo-slc.git/blob - runtime/org.argeo.slc.support.simple/src/main/java/org/argeo/slc/jsch/RemoteExec.java
9569be35b31a3ab06d9fca5c0e854a6a5cab57de
[gpl/argeo-slc.git] / runtime / org.argeo.slc.support.simple / src / main / java / 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.io.IOUtils;
20 import org.apache.commons.logging.Log;
21 import org.apache.commons.logging.LogFactory;
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 Log log = LogFactory.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 /**
54 * If set, stdout is written to it as a list of lines. Cleared before each
55 * run.
56 */
57 private List<String> stdOutLines = null;
58 private Boolean logEvenIfStdOutLines = false;
59 private Boolean quiet = false;
60
61 public RemoteExec() {
62 }
63
64 public RemoteExec(SshTarget sshTarget, String cmd) {
65 setSshTarget(sshTarget);
66 setCommand(cmd);
67 }
68
69 public void run(Session session) {
70 List<String> commandsToUse = new ArrayList<String>(commands);
71 String commandToUse = command;
72 // convert system calls
73 if (systemCall != null) {
74 if (command != null)
75 throw new SlcException("Cannot specify command AND systemCall");
76 commandToUse = convertSystemCall(systemCall);
77 }
78
79 if (systemCalls.size() != 0) {
80 if (commandsToUse.size() != 0)
81 throw new SlcException(
82 "Cannot specify commands AND systemCalls");
83 for (SystemCall systemCall : systemCalls)
84 commandsToUse.add(convertSystemCall(systemCall));
85 }
86
87 if (script != null) {
88 // TODO: simply pass the script as a string command
89 if (commandsToUse.size() != 0)
90 throw new SlcException("Cannot specify commands and script");
91 BufferedReader reader = null;
92 try {
93 reader = new BufferedReader(new InputStreamReader(script
94 .getInputStream()));
95 String line = null;
96 while ((line = reader.readLine()) != null) {
97 if (!StringUtils.hasText(line))
98 continue;
99 commandsToUse.add(line);
100 }
101 } catch (IOException e) {
102 throw new SlcException("Cannot read script " + script, e);
103 } finally {
104 IOUtils.closeQuietly(reader);
105 }
106 }
107
108 if (forceShell) {
109 // for the time being do not interpret both \n and ;
110 // priority to \n
111 // until we know how to parse ; within ""
112 if (commandToUse.indexOf('\n') >= 0) {
113 StringTokenizer st = new StringTokenizer(commandToUse, "\n");
114 while (st.hasMoreTokens()) {
115 String cmd = st.nextToken();
116 commandsToUse.add(cmd);
117 }
118 } else if (commandToUse.indexOf(';') >= 0) {
119 StringTokenizer st = new StringTokenizer(commandToUse, ";");
120 while (st.hasMoreTokens()) {
121 String cmd = st.nextToken();
122 commandsToUse.add(cmd);
123 }
124 } else {
125 commandsToUse.add(commandToUse);
126 }
127 commandToUse = null;
128 }
129
130 // run as user
131 if (user != null) {
132 if (commandsToUse.size() > 0) {
133 commandsToUse.add(0, "su - " + user);
134 commandsToUse.add("exit");
135 } else {
136 if (command.indexOf('\"') >= 0)
137 throw new SlcException(
138 "Don't know how to su a command with \", use shell instead.");
139 commandToUse = "su - " + user + " -c \"" + command + "\"";
140 }
141 }
142
143 // execute command(s)
144 if (commandToUse != null) {
145 if (commandsToUse.size() != 0)
146 throw new SlcException(
147 "Specify either a single command or a list of commands.");
148 remoteExec(session, commandToUse);
149 } else {
150 if (commandsToUse.size() == 0)
151 throw new SlcException(
152 "Neither a single command or a list of commands has been specified.");
153
154 remoteExec(session, commandsToUse, script != null ? "script "
155 + script.getFilename() : commandsToUse.size() + " commands");
156 }
157 }
158
159 protected String convertSystemCall(SystemCall systemCall) {
160 // TODO: prepend environment variables
161 // TODO: deal with exec dir
162 return systemCall.asCommand();
163 }
164
165 protected void remoteExec(Session session, final List<String> commands,
166 String description) {
167 try {
168 final ChannelShell channel = (ChannelShell) session
169 .openChannel("shell");
170 channel.setInputStream(null);
171 channel.setXForwarding(xForwarding);
172 channel.setAgentForwarding(agentForwarding);
173 channel.setEnv(new Hashtable<String, String>(env));
174
175 /*
176 * // Choose the pty-type "vt102".
177 * ((ChannelShell)channel).setPtyType("vt102");
178 */
179 // Writer thread
180 final BufferedWriter writer = new BufferedWriter(
181 new OutputStreamWriter(channel.getOutputStream()));
182
183 if (log.isDebugEnabled())
184 log.debug("Run " + description + " on " + getSshTarget()
185 + "...");
186 channel.connect();
187
188 // write commands to shell
189 Thread writerThread = new Thread("Shell writer " + getSshTarget()) {
190 @Override
191 public void run() {
192 try {
193 for (String line : commands) {
194 if (!StringUtils.hasText(line))
195 continue;
196 writer.write(line);
197 writer.newLine();
198 }
199 writer.append("exit");
200 writer.newLine();
201 writer.flush();
202 // channel.disconnect();
203 } catch (IOException e) {
204 throw new SlcException("Cannot write to shell on "
205 + getSshTarget(), e);
206 } finally {
207 IOUtils.closeQuietly(writer);
208 }
209 }
210 };
211 writerThread.start();
212
213 readStdOut(channel);
214 checkExitStatus(channel);
215 channel.disconnect();
216
217 } catch (Exception e) {
218 throw new SlcException("Cannot use SSH shell on " + getSshTarget(),
219 e);
220 }
221
222 }
223
224 protected void remoteExec(Session session, String command) {
225 try {
226 final ChannelExec channel = (ChannelExec) session
227 .openChannel("exec");
228 channel.setCommand(command);
229
230 channel.setInputStream(null);
231 channel.setXForwarding(xForwarding);
232 channel.setAgentForwarding(agentForwarding);
233 channel.setEnv(new Hashtable<String, String>(env));
234 channel.setErrStream(null);
235
236 // Standard Error
237 readStdErr(channel);
238
239 if (log.isDebugEnabled())
240 log.debug("Run '" + command + "' on " + getSshTarget() + "...");
241 channel.connect();
242
243 if (stdIn != null) {
244 Thread stdInThread = new Thread("Stdin " + getSshTarget()) {
245 @Override
246 public void run() {
247 OutputStream out = null;
248 try {
249 out = channel.getOutputStream();
250 IOUtils.copy(stdIn.getInputStream(), out);
251 } catch (IOException e) {
252 throw new SlcException("Cannot write stdin on "
253 + getSshTarget(), e);
254 } finally {
255 IOUtils.closeQuietly(out);
256 }
257 }
258 };
259 stdInThread.start();
260 }
261 readStdOut(channel);
262 checkExitStatus(channel);
263 channel.disconnect();
264 } catch (Exception e) {
265 throw new SlcException("Cannot execute remotely '" + command
266 + "' on " + getSshTarget(), e);
267 }
268 }
269
270 protected void readStdErr(final ChannelExec channel) {
271 new Thread("stderr " + getSshTarget()) {
272 public void run() {
273 BufferedReader stdErr = null;
274 try {
275 InputStream in = channel.getErrStream();
276 stdErr = new BufferedReader(new InputStreamReader(in));
277 String line = null;
278 while ((line = stdErr.readLine()) != null) {
279 if (!line.trim().equals(""))
280 log.error(line);
281 }
282 } catch (IOException e) {
283 if (log.isDebugEnabled())
284 log.error("Cannot read stderr from " + getSshTarget(),
285 e);
286 } finally {
287 IOUtils.closeQuietly(stdErr);
288 }
289 }
290 }.start();
291 }
292
293 protected void readStdOut(Channel channel) {
294 if (stdOut != null) {
295 OutputStream localStdOut = createOutputStream(stdOut);
296 try {
297 IOUtils.copy(channel.getInputStream(), localStdOut);
298 } catch (IOException e) {
299 throw new SlcException("Cannot redirect stdout", e);
300 } finally {
301 IOUtils.closeQuietly(localStdOut);
302 }
303 } else {
304 BufferedReader stdOut = null;
305 try {
306 InputStream in = channel.getInputStream();
307 stdOut = new BufferedReader(new InputStreamReader(in));
308 String line = null;
309 while ((line = stdOut.readLine()) != null) {
310 if (!line.trim().equals("")) {
311
312 if (stdOutLines != null) {
313 stdOutLines.add(line);
314 if (logEvenIfStdOutLines && !quiet)
315 log.info(line);
316 } else {
317 if (!quiet)
318 log.info(line);
319 }
320 }
321 }
322 } catch (IOException e) {
323 if (log.isDebugEnabled())
324 log.error("Cannot read stdout from " + getSshTarget(), e);
325 } finally {
326 IOUtils.closeQuietly(stdOut);
327 }
328 }
329 }
330
331 protected void checkExitStatus(Channel channel) {
332 if (channel.isClosed()) {
333 int exitStatus = channel.getExitStatus();
334 if (exitStatus == 0) {
335 if (log.isTraceEnabled())
336 log.trace("Remote execution exit status: " + exitStatus);
337 } else {
338 String msg = "Remote execution failed with " + " exit status: "
339 + exitStatus;
340 if (failOnBadExitStatus)
341 throw new SlcException(msg);
342 else
343 log.error(msg);
344 }
345 }
346
347 }
348
349 protected OutputStream createOutputStream(Resource target) {
350 FileOutputStream out = null;
351 try {
352
353 final File file;
354 if (executionResources != null)
355 file = new File(executionResources.getAsOsPath(target, true));
356 else
357 file = target.getFile();
358 out = new FileOutputStream(file, false);
359 } catch (IOException e) {
360 log.error("Cannot get file for " + target, e);
361 IOUtils.closeQuietly(out);
362 }
363 return out;
364 }
365
366 public void setCommand(String command) {
367 this.command = command;
368 }
369
370 public void setCommands(List<String> commands) {
371 this.commands = commands;
372 }
373
374 public void setFailOnBadExitStatus(Boolean failOnBadExitStatus) {
375 this.failOnBadExitStatus = failOnBadExitStatus;
376 }
377
378 public void setSystemCall(SystemCall systemCall) {
379 this.systemCall = systemCall;
380 }
381
382 public void setSystemCalls(List<SystemCall> systemCalls) {
383 this.systemCalls = systemCalls;
384 }
385
386 public void setScript(Resource script) {
387 this.script = script;
388 }
389
390 public void setxForwarding(Boolean xForwarding) {
391 this.xForwarding = xForwarding;
392 }
393
394 public void setAgentForwarding(Boolean agentForwarding) {
395 this.agentForwarding = agentForwarding;
396 }
397
398 public void setEnv(Map<String, String> env) {
399 this.env = env;
400 }
401
402 public void setForceShell(Boolean forceShell) {
403 this.forceShell = forceShell;
404 }
405
406 public List<String> getCommands() {
407 return commands;
408 }
409
410 public void setStdOutLines(List<String> stdOutLines) {
411 this.stdOutLines = stdOutLines;
412 }
413
414 public void setLogEvenIfStdOutLines(Boolean logEvenIfStdOutLines) {
415 this.logEvenIfStdOutLines = logEvenIfStdOutLines;
416 }
417
418 public void setQuiet(Boolean quiet) {
419 this.quiet = quiet;
420 }
421
422 public void setStdIn(Resource stdIn) {
423 this.stdIn = stdIn;
424 }
425
426 public void setStdOut(Resource stdOut) {
427 this.stdOut = stdOut;
428 }
429
430 public void setExecutionResources(ExecutionResources executionResources) {
431 this.executionResources = executionResources;
432 }
433
434 public void setUser(String user) {
435 this.user = user;
436 }
437
438 }