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