File D:\maven-qcr-plugin-testsunitaires\qcr4java\jenkins-master\target\site\qc\core\src\main\java\hudson\Launcher.java.html

1 /*
2 * The MIT License
3 *
4 * Copyright (c) 2004-2009, Sun Microsystems, Inc., Kohsuke Kawaguchi, Stephen Connolly, CloudBees, Inc.
5 *
6 * Permission is hereby granted, free of charge, to any person obtaining a copy
7 * of this software and associated documentation files (the "Software"), to deal
8 * in the Software without restriction, including without limitation the rights
9 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 * copies of the Software, and to permit persons to whom the Software is
11 * furnished to do so, subject to the following conditions:
12 *
13 * The above copyright notice and this permission notice shall be included in
14 * all copies or substantial portions of the Software.
15 *
16 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22 * THE SOFTWARE.
23 */
24 package hudson;
25
26 import hudson.Proc.LocalProc;
27 import hudson.model.Computer;
28 import hudson.util.QuotedStringTokenizer;
29 import jenkins.model.Jenkins;
30 import hudson.model.TaskListener;
31 import hudson.model.Node;
32 import hudson.remoting.Callable;
33 import hudson.remoting.Channel;
34 import hudson.remoting.Pipe;
35 import hudson.remoting.RemoteInputStream;
36 import hudson.remoting.RemoteOutputStream;
37 import hudson.remoting.VirtualChannel;
38 import hudson.util.StreamCopyThread;
39 import hudson.util.ArgumentListBuilder;
40 import hudson.util.ProcessTree;
41 import org.apache.commons.io.input.NullInputStream;
42
43 import java.io.BufferedOutputStream;
44 import java.io.File;
45 import java.io.IOException;
46 import java.io.InputStream;
47 import java.io.InterruptedIOException;
48 import java.io.OutputStream;
49 import java.io.Serializable;
50 import java.util.Arrays;
51 import java.util.Map;
52 import java.util.List;
53 import java.util.ArrayList;
54 import java.util.logging.Level;
55 import java.util.logging.Logger;
56
57 import static org.apache.commons.io.output.NullOutputStream.NULL_OUTPUT_STREAM;
58
59 /**
60 * Starts a process.
61 *
62 * <p>
63 * This hides the difference between running programs locally vs remotely.
64 *
65 *
66 * <h2>'env' parameter</h2>
67 * <p>
68 * To allow important environment variables to be copied over to the remote machine,
69 * the 'env' parameter shouldn't contain default inherited environment variables
70 * (which often contains machine-specific information, like PATH, TIMEZONE, etc.)
71 *
72 * <p>
73 * {@link Launcher} is responsible for inheriting environment variables.
74 *
75 *
76 * @author Kohsuke Kawaguchi
77 * @see FilePath#createLauncher(TaskListener)
78 */
79 public abstract class Launcher {
80
81 protected final TaskListener listener;
82
83 protected final VirtualChannel channel;
84
85 public Launcher(TaskListener listener, VirtualChannel channel) {
86 this.listener = listener;
87 this.channel = channel;
88 }
89
90 /**
91 * Constructor for a decorator.
92 */
93 protected Launcher(Launcher launcher) {
94 this(launcher.listener, launcher.channel);
95 }
96
97 /**
98 * Gets the channel that can be used to run a program remotely.
99 *
100 * @return
101 * null if the target node is not configured to support this.
102 * this is a transitional measure.
103 * Note that a launcher for the master is always non-null.
104 */
105 public VirtualChannel getChannel() {
106 return channel;
107 }
108
109 /**
110 * Gets the {@link TaskListener} that this launcher uses to
111 * report the commands that it's executing.
112 */
113 public TaskListener getListener() {
114 return listener;
115 }
116
117 /**
118 * If this {@link Launcher} is encapsulating an execution on a specific {@link Computer},
119 * return it.
120 *
121 * <p>
122 * Because of the way internal Hudson abstractions are set up (that is, {@link Launcher} only
123 * needs a {@link VirtualChannel} to do its job and isn't really required that the channel
124 * comes from an existing {@link Computer}), this method may not always the right {@link Computer} instance.
125 *
126 * @return
127 * null if this launcher is not created from a {@link Computer} object.
128 * @deprecated since 2008-11-16.
129 * See the javadoc for why this is inherently unreliable. If you are trying to
130 * figure out the current {@link Computer} from within a build, use
131 * {@link Computer#currentComputer()}
132 */
133 public Computer getComputer() {
134 for( Computer c : Jenkins.getInstance().getComputers() )
135 if(c.getChannel()==channel)
136 return c;
137 return null;
138 }
139
140 /**
141 * Builder pattern for configuring a process to launch.
142 * @since 1.311
143 */
144 public final class ProcStarter {
145 protected List<String> commands;
146 protected boolean[] masks;
147 protected FilePath pwd;
148 protected OutputStream stdout = NULL_OUTPUT_STREAM, stderr;
149 protected InputStream stdin = NULL_INPUT_STREAM;
150 protected String[] envs;
151 /**
152 * True to reverse the I/O direction.
153 *
154 * For example, if {@link #reverseStdout}==true, then we expose
155 * {@link InputStream} from {@link Proc} and expect the client to read from it,
156 * whereas normally we take {@link OutputStream} via {@link #stdout(OutputStream)}
157 * and feed stdout into that output.
158 *
159 * @since 1.399
160 */
161 protected boolean reverseStdin, reverseStdout, reverseStderr;
162
163 /**
164 * Passes a white-space separated single-string command (like "cat abc def") and parse them
165 * as a command argument. This method also handles quotes.
166 */
167 public ProcStarter cmdAsSingleString(String s) {
168 return cmds(QuotedStringTokenizer.tokenize(s));
169 }
170
171 public ProcStarter cmds(String... args) {
172 return cmds(Arrays.asList(args));
173 }
174
175 public ProcStarter cmds(File program, String... args) {
176 commands = new ArrayList<String>(args.length+1);
177 commands.add(program.getPath());
178 commands.addAll(Arrays.asList(args));
179 return this;
180 }
181
182 public ProcStarter cmds(List<String> args) {
183 commands = new ArrayList<String>(args);
184 return this;
185 }
186
187 public ProcStarter cmds(ArgumentListBuilder args) {
188 commands = args.toList();
189 masks = args.toMaskArray();
190 return this;
191 }
192
193 public List<String> cmds() {
194 return commands;
195 }
196
197 /**
198 * Hide parts of the command line from being printed to the log.
199 * @param masks true for each position in {@link #cmds(String[])} which should be masked, false to print
200 * @return this
201 * @see ArgumentListBuilder#add(String, boolean)
202 * @see #maskedPrintCommandLine(List, boolean[], FilePath)
203 */
204 public ProcStarter masks(boolean... masks) {
205 this.masks = masks;
206 return this;
207 }
208
209 public boolean[] masks() {
210 return masks;
211 }
212
213 public ProcStarter pwd(FilePath workDir) {
214 this.pwd = workDir;
215 return this;
216 }
217
218 public ProcStarter pwd(File workDir) {
219 return pwd(new FilePath(workDir));
220 }
221
222 public ProcStarter pwd(String workDir) {
223 return pwd(new File(workDir));
224 }
225
226 public FilePath pwd() {
227 return pwd;
228 }
229
230 public ProcStarter stdout(OutputStream out) {
231 this.stdout = out;
232 return this;
233 }
234
235 /**
236 * Sends the stdout to the given {@link TaskListener}.
237 */
238 public ProcStarter stdout(TaskListener out) {
239 return stdout(out.getLogger());
240 }
241
242 public OutputStream stdout() {
243 return stdout;
244 }
245
246 /**
247 * Controls where the stderr of the process goes.
248 * By default, it's bundled into stdout.
249 */
250 public ProcStarter stderr(OutputStream err) {
251 this.stderr = err;
252 return this;
253 }
254
255 public OutputStream stderr() {
256 return stderr;
257 }
258
259 /**
260 * Controls where the stdin of the process comes from.
261 * By default, <tt>/dev/null</tt>.
262 */
263 public ProcStarter stdin(InputStream in) {
264 this.stdin = in;
265 return this;
266 }
267
268 public InputStream stdin() {
269 return stdin;
270 }
271
272 /**
273 * Sets the environment variable overrides.
274 *
275 * <p>
276 * In adition to what the current process
277 * is inherited (if this is going to be launched from a slave agent, that
278 * becomes the "current" process), these variables will be also set.
279 */
280 public ProcStarter envs(Map<String, String> overrides) {
281 this.envs = Util.mapToEnv(overrides);
282 return this;
283 }
284
285 /**
286 * @param overrides
287 * List of "VAR=VALUE". See {@link #envs(Map)} for the semantics.
288 */
289 public ProcStarter envs(String... overrides) {
290 if (overrides != null) {
291 for (String override : overrides) {
292 if (override.indexOf('=') == -1) {
293 throw new IllegalArgumentException(override);
294 }
295 }
296 }
297 this.envs = overrides;
298 return this;
299 }
300
301 public String[] envs() {
302 return envs.clone();
303 }
304
305 /**
306 * Indicates that the caller will pump {@code stdout} from the child process
307 * via {@link Proc#getStdout()} (whereas by default you call {@link #stdout(OutputStream)}
308 * and let Jenkins pump stdout into your {@link OutputStream} of choosing.
309 *
310 * <p>
311 * When this method is called, {@link Proc#getStdout()} will read the combined output
312 * of {@code stdout/stderr} from the child process, unless {@link #readStderr()} is called
313 * separately, which lets the caller read those two streams separately.
314 *
315 * @since 1.399
316 */
317 public ProcStarter readStdout() {
318 reverseStdout = true;
319 stdout = stderr = null;
320 return this;
321 }
322
323 /**
324 * In addition to the effect of {@link #readStdout()}, indicate that the caller will pump {@code stderr}
325 * from the child process separately from {@code stdout}. The stderr will be readable from
326 * {@link Proc#getStderr()} while {@link Proc#getStdout()} reads from stdout.
327 *
328 * @since 1.399
329 */
330 public ProcStarter readStderr() {
331 reverseStdout = true;
332 reverseStderr = true;
333 return this;
334 }
335
336 /**
337 * Indicates that the caller will directly write to the child process {@link #stdin()} via {@link Proc#getStdin()}.
338 * (Whereas by default you call {@link #stdin(InputStream)}
339 * and let Jenkins pump your {@link InputStream} of choosing to stdin.)
340 * @since 1.399
341 */
342 public ProcStarter writeStdin() {
343 reverseStdin = true;
344 stdin = null;
345 return this;
346 }
347
348
349 /**
350 * Starts the new process as configured.
351 */
352 public Proc start() throws IOException {
353 return launch(this);
354 }
355
356 /**
357 * Starts the process and waits for its completion.
358 */
359 public int join() throws IOException, InterruptedException {
360 return start().join();
361 }
362
363 /**
364 * Copies a {@link ProcStarter}.
365 */
366 public ProcStarter copy() {
367 ProcStarter rhs = new ProcStarter().cmds(commands).pwd(pwd).masks(masks).stdin(stdin).stdout(stdout).stderr(stderr).envs(envs);
368 rhs.reverseStdin = this.reverseStdin;
369 rhs.reverseStderr = this.reverseStderr;
370 rhs.reverseStdout = this.reverseStdout;
371 return rhs;
372 }
373 }
374
375 /**
376 * Launches a process by using a {@linkplain ProcStarter builder-pattern} to configure
377 * the parameters.
378 */
379 public final ProcStarter launch() {
380 return new ProcStarter();
381 }
382
383 /**
384 * @deprecated as of 1.311
385 * Use {@link #launch()} and its associated builder pattern
386 */
387 public final Proc launch(String cmd, Map<String,String> env, OutputStream out, FilePath workDir) throws IOException {
388 return launch(cmd,Util.mapToEnv(env),out,workDir);
389 }
390
391 /**
392 * @deprecated as of 1.311
393 * Use {@link #launch()} and its associated builder pattern
394 */
395 public final Proc launch(String[] cmd, Map<String, String> env, OutputStream out, FilePath workDir) throws IOException {
396 return launch(cmd, Util.mapToEnv(env), out, workDir);
397 }
398
399 /**
400 * @deprecated as of 1.311
401 * Use {@link #launch()} and its associated builder pattern
402 */
403 public final Proc launch(String[] cmd, Map<String, String> env, InputStream in, OutputStream out) throws IOException {
404 return launch(cmd, Util.mapToEnv(env), in, out);
405 }
406
407 /**
408 * Launch a command with optional censoring of arguments from the listener (Note: <strong>The censored portions will
409 * remain visible through /proc, pargs, process explorer, etc. i.e. people logged in on the same machine</strong>
410 * This version of the launch command just ensures that it is not visible from a build log which is exposed via the
411 * web)
412 *
413 * @param cmd The command and all it's arguments.
414 * @param mask Which of the command and arguments should be masked from the listener
415 * @param env Environment variable overrides.
416 * @param out stdout and stderr of the process will be sent to this stream. the stream won't be closed.
417 * @param workDir null if the working directory could be anything.
418 * @return The process of the command.
419 * @throws IOException When there are IO problems.
420 *
421 * @deprecated as of 1.311
422 * Use {@link #launch()} and its associated builder pattern
423 */
424 public final Proc launch(String[] cmd, boolean[] mask, Map<String, String> env, OutputStream out, FilePath workDir) throws IOException {
425 return launch(cmd, mask, Util.mapToEnv(env), out, workDir);
426 }
427
428 /**
429 * Launch a command with optional censoring of arguments from the listener (Note: <strong>The censored portions will
430 * remain visible through /proc, pargs, process explorer, etc. i.e. people logged in on the same machine</strong>
431 * This version of the launch command just ensures that it is not visible from a build log which is exposed via the
432 * web)
433 *
434 * @param cmd The command and all it's arguments.
435 * @param mask Which of the command and arguments should be masked from the listener
436 * @param env Environment variable overrides.
437 * @param in null if there's no input.
438 * @param out stdout and stderr of the process will be sent to this stream. the stream won't be closed.
439 * @return The process of the command.
440 * @throws IOException When there are IO problems.
441 *
442 * @deprecated as of 1.311
443 * Use {@link #launch()} and its associated builder pattern
444 */
445 public final Proc launch(String[] cmd, boolean[] mask, Map<String, String> env, InputStream in, OutputStream out) throws IOException {
446 return launch(cmd, mask, Util.mapToEnv(env), in, out);
447 }
448
449 /**
450 * @deprecated as of 1.311
451 * Use {@link #launch()} and its associated builder pattern
452 */
453 public final Proc launch(String cmd,String[] env,OutputStream out, FilePath workDir) throws IOException {
454 return launch(Util.tokenize(cmd),env,out,workDir);
455 }
456
457 /**
458 * @deprecated as of 1.311
459 * Use {@link #launch()} and its associated builder pattern
460 */
461 public final Proc launch(String[] cmd, String[] env, OutputStream out, FilePath workDir) throws IOException {
462 return launch(cmd, env, null, out, workDir);
463 }
464
465 /**
466 * @deprecated as of 1.311
467 * Use {@link #launch()} and its associated builder pattern
468 */
469 public final Proc launch(String[] cmd, String[] env, InputStream in, OutputStream out) throws IOException {
470 return launch(cmd, env, in, out, null);
471 }
472
473 /**
474 * Launch a command with optional censoring of arguments from the listener (Note: <strong>The censored portions will
475 * remain visible through /proc, pargs, process explorer, etc. i.e. people logged in on the same machine</strong>
476 * This version of the launch command just ensures that it is not visible from a build log which is exposed via the
477 * web)
478 *
479 * @param cmd The command and all it's arguments.
480 * @param mask Which of the command and arguments should be masked from the listener
481 * @param env Environment variable overrides.
482 * @param out stdout and stderr of the process will be sent to this stream. the stream won't be closed.
483 * @param workDir null if the working directory could be anything.
484 * @return The process of the command.
485 * @throws IOException When there are IO problems.
486 *
487 * @deprecated as of 1.311
488 * Use {@link #launch()} and its associated builder pattern
489 */
490 public final Proc launch(String[] cmd, boolean[] mask, String[] env, OutputStream out, FilePath workDir) throws IOException {
491 return launch(cmd, mask, env, null, out, workDir);
492 }
493
494 /**
495 * Launch a command with optional censoring of arguments from the listener (Note: <strong>The censored portions will
496 * remain visible through /proc, pargs, process explorer, etc. i.e. people logged in on the same machine</strong>
497 * This version of the launch command just ensures that it is not visible from a build log which is exposed via the
498 * web)
499 *
500 * @param cmd The command and all it's arguments.
501 * @param mask Which of the command and arguments should be masked from the listener
502 * @param env Environment variable overrides.
503 * @param in null if there's no input.
504 * @param out stdout and stderr of the process will be sent to this stream. the stream won't be closed.
505 * @return The process of the command.
506 * @throws IOException When there are IO problems.
507 *
508 * @deprecated as of 1.311
509 * Use {@link #launch()} and its associated builder pattern
510 */
511 public final Proc launch(String[] cmd, boolean[] mask, String[] env, InputStream in, OutputStream out) throws IOException {
512 return launch(cmd, mask, env, in, out, null);
513 }
514
515 /**
516 * @param env
517 * Environment variable overrides.
518 * @param in
519 * null if there's no input.
520 * @param workDir
521 * null if the working directory could be anything.
522 * @param out
523 * stdout and stderr of the process will be sent to this stream.
524 * the stream won't be closed.
525 *
526 * @deprecated as of 1.311
527 * Use {@link #launch()} and its associated builder pattern
528 */
529 public Proc launch(String[] cmd, String[] env, InputStream in, OutputStream out, FilePath workDir) throws IOException {
530 return launch(launch().cmds(cmd).envs(env).stdin(in).stdout(out).pwd(workDir));
531 }
532
533 /**
534 * Launch a command with optional censoring of arguments from the listener (Note: <strong>The censored portions will
535 * remain visible through /proc, pargs, process explorer, etc. i.e. people logged in on the same machine</strong>
536 * This version of the launch command just ensures that it is not visible from a build log which is exposed via the
537 * web)
538 *
539 * @param cmd The command and all it's arguments.
540 * @param mask Which of the command and arguments should be masked from the listener
541 * @param env Environment variable overrides.
542 * @param in null if there's no input.
543 * @param out stdout and stderr of the process will be sent to this stream. the stream won't be closed.
544 * @param workDir null if the working directory could be anything.
545 * @return The process of the command.
546 * @throws IOException When there are IO problems.
547 *
548 * @deprecated as of 1.311
549 * Use {@link #launch()} and its associated builder pattern
550 */
551 public Proc launch(String[] cmd, boolean[] mask, String[] env, InputStream in, OutputStream out, FilePath workDir) throws IOException {
552 return launch(launch().cmds(cmd).masks(mask).envs(env).stdin(in).stdout(out).pwd(workDir));
553 }
554
555 /**
556 * Primarily invoked from {@link ProcStarter#start()} to start a process with a specific launcher.
557 */
558 public abstract Proc launch(ProcStarter starter) throws IOException;
559
560 /**
561 * Launches a specified process and connects its input/output to a {@link Channel}, then
562 * return it.
563 *
564 * <p>
565 * When the returned channel is terminated, the process will be killed.
566 *
567 * @param out
568 * Where the stderr from the launched process will be sent.
569 * @param workDir
570 * The working directory of the new process, or null to inherit
571 * from the current process
572 * @param envVars
573 * Environment variable overrides. In addition to what the current process
574 * is inherited (if this is going to be launched from a slave agent, that
575 * becomes the "current" process), these variables will be also set.
576 */
577 public abstract Channel launchChannel(String[] cmd, OutputStream out, FilePath workDir, Map<String,String> envVars) throws IOException, InterruptedException;
578
579 /**
580 * Returns true if this {@link Launcher} is going to launch on Unix.
581 */
582 public boolean isUnix() {
583 return File.pathSeparatorChar==':';
584 }
585
586 /**
587 * Calls {@link ProcessTree#killAll(Map)} to kill processes.
588 */
589 public abstract void kill(Map<String,String> modelEnvVars) throws IOException, InterruptedException;
590
591 /**
592 * Prints out the command line to the listener so that users know what we are doing.
593 */
594 protected final void printCommandLine(String[] cmd, FilePath workDir) {
595 StringBuilder buf = new StringBuilder();
596 if (workDir != null) {
597 buf.append('[');
598 if(showFullPath)
599 buf.append(workDir.getRemote());
600 else
601 buf.append(workDir.getRemote().replaceFirst("^.+[/\\\\]", ""));
602 buf.append("] ");
603 }
604 buf.append('$');
605 for (String c : cmd) {
606 buf.append(' ');
607 if(c.indexOf(' ')>=0) {
608 if(c.indexOf('"')>=0)
609 buf.append('\'').append(c).append('\'');
610 else
611 buf.append('"').append(c).append('"');
612 } else
613 buf.append(c);
614 }
615 listener.getLogger().println(buf.toString());
616 }
617
618 /**
619 * Prints out the command line to the listener with some portions masked to prevent sensitive information from being
620 * recorded on the listener.
621 *
622 * @param cmd The commands
623 * @param mask An array of booleans which control whether a cmd element should be masked (<code>true</code>) or
624 * remain unmasked (<code>false</code>).
625 * @param workDir The work dir.
626 */
627 protected final void maskedPrintCommandLine(List<String> cmd, boolean[] mask, FilePath workDir) {
628 if(mask==null) {
629 printCommandLine(cmd.toArray(new String[cmd.size()]),workDir);
630 return;
631 }
632
633 assert mask.length == cmd.size();
634 final String[] masked = new String[cmd.size()];
635 for (int i = 0; i < cmd.size(); i++) {
636 if (mask[i]) {
637 masked[i] = "********";
638 } else {
639 masked[i] = cmd.get(i);
640 }
641 }
642 printCommandLine(masked, workDir);
643 }
644 protected final void maskedPrintCommandLine(String[] cmd, boolean[] mask, FilePath workDir) {
645 maskedPrintCommandLine(Arrays.asList(cmd),mask,workDir);
646 }
647
648 /**
649 * Returns a decorated {@link Launcher} for the given node.
650 */
651 public final Launcher decorateFor(Node node) {
652 Launcher l = this;
653 for (LauncherDecorator d : LauncherDecorator.all())
654 l = d.decorate(l,node);
655 return l;
656 }
657
658 /**
659 * Returns a decorated {@link Launcher} that puts the given set of arguments as a prefix to any commands
660 * that it invokes.
661 *
662 * @since 1.299
663 */
664 public final Launcher decorateByPrefix(final String... prefix) {
665 final Launcher outer = this;
666 return new Launcher(outer) {
667 @Override
668 public Proc launch(ProcStarter starter) throws IOException {
669 starter.commands.addAll(0,Arrays.asList(prefix));
670 if (starter.masks != null) {
671 starter.masks = prefix(starter.masks);
672 }
673 return outer.launch(starter);
674 }
675
676 @Override
677 public Channel launchChannel(String[] cmd, OutputStream out, FilePath workDir, Map<String, String> envVars) throws IOException, InterruptedException {
678 return outer.launchChannel(prefix(cmd),out,workDir,envVars);
679 }
680
681 @Override
682 public void kill(Map<String, String> modelEnvVars) throws IOException, InterruptedException {
683 outer.kill(modelEnvVars);
684 }
685
686 private String[] prefix(String[] args) {
687 String[] newArgs = new String[args.length+prefix.length];
688 System.arraycopy(prefix,0,newArgs,0,prefix.length);
689 System.arraycopy(args,0,newArgs,prefix.length,args.length);
690 return newArgs;
691 }
692
693 private boolean[] prefix(boolean[] args) {
694 boolean[] newArgs = new boolean[args.length+prefix.length];
695 System.arraycopy(args,0,newArgs,prefix.length,args.length);
696 return newArgs;
697 }
698 };
699 }
700
701 /**
702 * Returns a decorated {@link Launcher} that automatically adds the specified environment
703 * variables.
704 *
705 * Those that are specified in {@link ProcStarter#envs(String...)} will take precedence over
706 * what's specified here.
707 *
708 * @since 1.489
709 */
710 public final Launcher decorateByEnv(EnvVars _env) {
711 final EnvVars env = new EnvVars(_env);
712 final Launcher outer = this;
713 return new Launcher(outer) {
714 @Override
715 public Proc launch(ProcStarter starter) throws IOException {
716 EnvVars e = new EnvVars(env);
717 if (starter.envs!=null) {
718 for (String env : starter.envs) {
719 e.addLine(env);
720 }
721 }
722 starter.envs = Util.mapToEnv(e);
723 return outer.launch(starter);
724 }
725
726 @Override
727 public Channel launchChannel(String[] cmd, OutputStream out, FilePath workDir, Map<String, String> envVars) throws IOException, InterruptedException {
728 EnvVars e = new EnvVars(env);
729 e.putAll(envVars);
730 return outer.launchChannel(cmd,out,workDir,e);
731 }
732
733 @Override
734 public void kill(Map<String, String> modelEnvVars) throws IOException, InterruptedException {
735 outer.kill(modelEnvVars);
736 }
737 };
738 }
739
740 /**
741 * {@link Launcher} that launches process locally.
742 */
743 public static class LocalLauncher extends Launcher {
744 public LocalLauncher(TaskListener listener) {
745 this(listener, Jenkins.MasterComputer.localChannel);
746 }
747
748 public LocalLauncher(TaskListener listener, VirtualChannel channel) {
749 super(listener, channel);
750 }
751
752 @Override
753 public Proc launch(ProcStarter ps) throws IOException {
754 maskedPrintCommandLine(ps.commands, ps.masks, ps.pwd);
755
756 EnvVars jobEnv = inherit(ps.envs);
757
758 // replace variables in command line
759 String[] jobCmd = new String[ps.commands.size()];
760 for ( int idx = 0 ; idx < jobCmd.length; idx++ )
761 jobCmd[idx] = jobEnv.expand(ps.commands.get(idx));
762
763 return new LocalProc(jobCmd, Util.mapToEnv(jobEnv),
764 ps.reverseStdin ?LocalProc.SELFPUMP_INPUT:ps.stdin,
765 ps.reverseStdout?LocalProc.SELFPUMP_OUTPUT:ps.stdout,
766 ps.reverseStderr?LocalProc.SELFPUMP_OUTPUT:ps.stderr,
767 toFile(ps.pwd));
768 }
769
770 private File toFile(FilePath f) {
771 return f==null ? null : new File(f.getRemote());
772 }
773
774 public Channel launchChannel(String[] cmd, OutputStream out, FilePath workDir, Map<String,String> envVars) throws IOException {
775 printCommandLine(cmd, workDir);
776
777 ProcessBuilder pb = new ProcessBuilder(cmd);
778 pb.directory(toFile(workDir));
779 if (envVars!=null) pb.environment().putAll(envVars);
780
781 return launchChannel(out, pb);
782 }
783
784 @Override
785 public void kill(Map<String, String> modelEnvVars) throws InterruptedException {
786 ProcessTree.get().killAll(modelEnvVars);
787 }
788
789 /**
790 * @param out
791 * Where the stderr from the launched process will be sent.
792 */
793 public Channel launchChannel(OutputStream out, ProcessBuilder pb) throws IOException {
794 final EnvVars cookie = EnvVars.createCookie();
795 pb.environment().putAll(cookie);
796
797 final Process proc = pb.start();
798
799 final Thread t2 = new StreamCopyThread(pb.command()+": stderr copier", proc.getErrorStream(), out);
800 t2.start();
801
802 return new Channel("locally launched channel on "+ pb.command(),
803 Computer.threadPoolForRemoting, proc.getInputStream(), proc.getOutputStream(), out) {
804
805 /**
806 * Kill the process when the channel is severed.
807 */
808 @Override
809 protected synchronized void terminate(IOException e) {
810 super.terminate(e);
811 ProcessTree pt = ProcessTree.get();
812 try {
813 pt.killAll(proc,cookie);
814 } catch (InterruptedException x) {
815 LOGGER.log(Level.INFO, "Interrupted", x);
816 }
817 }
818
819 @Override
820 public synchronized void close() throws IOException {
821 super.close();
822 // wait for all the output from the process to be picked up
823 try {
824 t2.join();
825 } catch (InterruptedException e) {
826 // process the interrupt later
827 Thread.currentThread().interrupt();
828 }
829 }
830 };
831 }
832 }
833
834 /**
835 * Launches processes remotely by using the given channel.
836 */
837 public static class RemoteLauncher extends Launcher {
838 private final boolean isUnix;
839
840 public RemoteLauncher(TaskListener listener, VirtualChannel channel, boolean isUnix) {
841 super(listener, channel);
842 this.isUnix = isUnix;
843 }
844
845 public Proc launch(ProcStarter ps) throws IOException {
846 final OutputStream out = ps.stdout == null ? null : new RemoteOutputStream(new CloseProofOutputStream(ps.stdout));
847 final OutputStream err = ps.stderr==null ? null : new RemoteOutputStream(new CloseProofOutputStream(ps.stderr));
848 final InputStream in = (ps.stdin==null || ps.stdin==NULL_INPUT_STREAM) ? null : new RemoteInputStream(ps.stdin,false);
849 final String workDir = ps.pwd==null ? null : ps.pwd.getRemote();
850
851 try {
852 return new ProcImpl(getChannel().call(new RemoteLaunchCallable(ps.commands, ps.masks, ps.envs, in, ps.reverseStdin, out, ps.reverseStdout, err, ps.reverseStderr, workDir, listener)));
853 } catch (InterruptedException e) {
854 throw (IOException)new InterruptedIOException().initCause(e);
855 }
856 }
857
858 public Channel launchChannel(String[] cmd, OutputStream err, FilePath _workDir, Map<String,String> envOverrides) throws IOException, InterruptedException {
859 printCommandLine(cmd, _workDir);
860
861 Pipe out = Pipe.createRemoteToLocal();
862 final String workDir = _workDir==null ? null : _workDir.getRemote();
863
864 OutputStream os = getChannel().call(new RemoteChannelLaunchCallable(cmd, out, err, workDir, envOverrides));
865
866 return new Channel("remotely launched channel on "+channel,
867 Computer.threadPoolForRemoting, out.getIn(), new BufferedOutputStream(os));
868 }
869
870 @Override
871 public boolean isUnix() {
872 return isUnix;
873 }
874
875 @Override
876 public void kill(final Map<String,String> modelEnvVars) throws IOException, InterruptedException {
877 getChannel().call(new KillTask(modelEnvVars));
878 }
879
880 private static final class KillTask implements Callable<Void,RuntimeException> {
881 private final Map<String, String> modelEnvVars;
882
883 public KillTask(Map<String, String> modelEnvVars) {
884 this.modelEnvVars = modelEnvVars;
885 }
886
887 public Void call() throws RuntimeException {
888 try {
889 ProcessTree.get().killAll(modelEnvVars);
890 } catch (InterruptedException e) {
891 // we are asked to terminate early by the caller, so no need to do anything
892 }
893 return null;
894 }
895
896 private static final long serialVersionUID = 1L;
897 }
898
899 public static final class ProcImpl extends Proc {
900 private final RemoteProcess process;
901 private final IOTriplet io;
902
903 public ProcImpl(RemoteProcess process) {
904 this.process = process;
905 this.io = process.getIOtriplet();
906 }
907
908 @Override
909 public void kill() throws IOException, InterruptedException {
910 process.kill();
911 }
912
913 @Override
914 public int join() throws IOException, InterruptedException {
915 return process.join();
916 }
917
918 @Override
919 public boolean isAlive() throws IOException, InterruptedException {
920 return process.isAlive();
921 }
922
923 @Override
924 public InputStream getStdout() {
925 return io.stdout;
926 }
927
928 @Override
929 public InputStream getStderr() {
930 return io.stderr;
931 }
932
933 @Override
934 public OutputStream getStdin() {
935 return io.stdin;
936 }
937 }
938 }
939
940 public static class IOTriplet implements Serializable {
941 InputStream stdout,stderr;
942 OutputStream stdin;
943 private static final long serialVersionUID = 1L;
944 }
945 /**
946 * Remoting interface of a remote process
947 */
948 public interface RemoteProcess {
949 int join() throws InterruptedException, IOException;
950 void kill() throws IOException, InterruptedException;
951 boolean isAlive() throws IOException, InterruptedException;
952 IOTriplet getIOtriplet();
953 }
954
955 private static class RemoteLaunchCallable implements Callable<RemoteProcess,IOException> {
956 private final List<String> cmd;
957 private final boolean[] masks;
958 private final String[] env;
959 private final InputStream in;
960 private final OutputStream out;
961 private final OutputStream err;
962 private final String workDir;
963 private final TaskListener listener;
964 private final boolean reverseStdin, reverseStdout, reverseStderr;
965
966 RemoteLaunchCallable(List<String> cmd, boolean[] masks, String[] env, InputStream in, boolean reverseStdin, OutputStream out, boolean reverseStdout, OutputStream err, boolean reverseStderr, String workDir, TaskListener listener) {
967 this.cmd = new ArrayList<String>(cmd);
968 this.masks = masks;
969 this.env = env;
970 this.in = in;
971 this.out = out;
972 this.err = err;
973 this.workDir = workDir;
974 this.listener = listener;
975 this.reverseStdin = reverseStdin;
976 this.reverseStdout = reverseStdout;
977 this.reverseStderr = reverseStderr;
978 }
979
980 public RemoteProcess call() throws IOException {
981 Launcher.ProcStarter ps = new LocalLauncher(listener).launch();
982 ps.cmds(cmd).masks(masks).envs(env).stdin(in).stdout(out).stderr(err);
983 if(workDir!=null) ps.pwd(workDir);
984 if (reverseStdin) ps.writeStdin();
985 if (reverseStdout) ps.readStdout();
986 if (reverseStderr) ps.readStderr();
987
988 final Proc p = ps.start();
989
990 return Channel.current().export(RemoteProcess.class,new RemoteProcess() {
991 public int join() throws InterruptedException, IOException {
992 try {
993 return p.join();
994 } finally {
995 // make sure I/O is delivered to the remote before we return
996 try {
997 Channel.current().syncIO();
998 } catch (Throwable _) {
999 // this includes a failure to sync, slave.jar too old, etc
1000 }
1001 }
1002 }
1003
1004 public void kill() throws IOException, InterruptedException {
1005 p.kill();
1006 }
1007
1008 public boolean isAlive() throws IOException, InterruptedException {
1009 return p.isAlive();
1010 }
1011
1012 public IOTriplet getIOtriplet() {
1013 IOTriplet r = new IOTriplet();
1014 if (reverseStdout) r.stdout = new RemoteInputStream(p.getStdout());
1015 if (reverseStderr) r.stderr = new RemoteInputStream(p.getStderr());
1016 if (reverseStdin) r.stdin = new RemoteOutputStream(p.getStdin());
1017 return r;
1018 }
1019 });
1020 }
1021
1022 private static final long serialVersionUID = 1L;
1023 }
1024
1025 private static class RemoteChannelLaunchCallable implements Callable<OutputStream,IOException> {
1026 private final String[] cmd;
1027 private final Pipe out;
1028 private final String workDir;
1029 private final OutputStream err;
1030 private final Map<String,String> envOverrides;
1031
1032 public RemoteChannelLaunchCallable(String[] cmd, Pipe out, OutputStream err, String workDir, Map<String,String> envOverrides) {
1033 this.cmd = cmd;
1034 this.out = out;
1035 this.err = new RemoteOutputStream(err);
1036 this.workDir = workDir;
1037 this.envOverrides = envOverrides;
1038 }
1039
1040 public OutputStream call() throws IOException {
1041 Process p = Runtime.getRuntime().exec(cmd,
1042 Util.mapToEnv(inherit(envOverrides)),
1043 workDir == null ? null : new File(workDir));
1044
1045 List<String> cmdLines = Arrays.asList(cmd);
1046 new StreamCopyThread("stdin copier for remote agent on "+cmdLines,
1047 p.getInputStream(), out.getOut()).start();
1048 new StreamCopyThread("stderr copier for remote agent on "+cmdLines,
1049 p.getErrorStream(), err).start();
1050
1051 // TODO: don't we need to join?
1052
1053 return new RemoteOutputStream(p.getOutputStream());
1054 }
1055
1056 private static final long serialVersionUID = 1L;
1057 }
1058
1059 /**
1060 * Expands the list of environment variables by inheriting current env variables.
1061 */
1062 private static EnvVars inherit(String[] env) {
1063 // convert String[] to Map first
1064 EnvVars m = new EnvVars();
1065 if(env!=null) {
1066 for (String e : env) {
1067 int index = e.indexOf('=');
1068 m.put(e.substring(0,index), e.substring(index+1));
1069 }
1070 }
1071 // then do the inheritance
1072 return inherit(m);
1073 }
1074
1075 /**
1076 * Expands the list of environment variables by inheriting current env variables.
1077 */
1078 private static EnvVars inherit(Map<String,String> overrides) {
1079 EnvVars m = new EnvVars(EnvVars.masterEnvVars);
1080 for (Map.Entry<String,String> o : overrides.entrySet())
1081 m.override(o.getKey(),m.expand(o.getValue()));
1082 return m;
1083 }
1084
1085 /**
1086 * Debug option to display full current path instead of just the last token.
1087 */
1088 public static boolean showFullPath = false;
1089
1090 private static final NullInputStream NULL_INPUT_STREAM = new NullInputStream(0);
1091
1092 private static final Logger LOGGER = Logger.getLogger(Launcher.class.getName());
1093 }
1094