Skip to content

Commit 4e0c1b4

Browse files
authored
Merge pull request #1149 from Vlatombe/disable-echo-windows
2 parents 6103d89 + dcf688f commit 4e0c1b4

File tree

11 files changed

+328
-21
lines changed

11 files changed

+328
-21
lines changed

src/main/java/org/csanchez/jenkins/plugins/kubernetes/pipeline/ContainerExecDecorator.java

Lines changed: 73 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@
4545

4646
import hudson.AbortException;
4747
import io.fabric8.kubernetes.api.model.Container;
48+
import org.apache.commons.io.output.NullOutputStream;
4849
import org.apache.commons.io.output.TeeOutputStream;
4950
import org.csanchez.jenkins.plugins.kubernetes.ContainerTemplate;
5051
import org.csanchez.jenkins.plugins.kubernetes.KubernetesSlave;
@@ -344,6 +345,7 @@ private Proc doLaunch(boolean quiet, String[] cmdEnvs, OutputStream outputForCal
344345

345346
// Only output to stdout at the beginning for diagnostics.
346347
ByteArrayOutputStream stdout = new ByteArrayOutputStream();
348+
// Wrap stdout so that we can toggle it off.
347349
ToggleOutputStream toggleStdout = new ToggleOutputStream(stdout);
348350

349351
// Do not send this command to the output when in quiet mode
@@ -355,14 +357,27 @@ private Proc doLaunch(boolean quiet, String[] cmdEnvs, OutputStream outputForCal
355357
stream = new TeeOutputStream(toggleStdout, printStream);
356358
}
357359

360+
ByteArrayOutputStream dryRunCaller = null;;
361+
ToggleOutputStream toggleDryRunCaller = null;
362+
ToggleOutputStream toggleOutputForCaller = null;
358363
// Send to proc caller as well if they sent one
359364
if (outputForCaller != null && !outputForCaller.equals(printStream)) {
360-
stream = new TeeOutputStream(outputForCaller, stream);
365+
if (launcher.isUnix()) {
366+
stream = new TeeOutputStream(outputForCaller, stream);
367+
} else {
368+
// Prepare to capture output for later.
369+
dryRunCaller = new ByteArrayOutputStream();
370+
toggleDryRunCaller = new ToggleOutputStream(dryRunCaller);
371+
// Initially disable the output for the caller, to prevent it from getting unwanted output such as prompt
372+
toggleOutputForCaller = new ToggleOutputStream(outputForCaller, true);
373+
stream = new TeeOutputStream(toggleOutputForCaller, stream);
374+
stream = new TeeOutputStream(toggleDryRunCaller, stream);
375+
}
361376
}
362377
ByteArrayOutputStream error = new ByteArrayOutputStream();
363378

364-
String sh = shell != null ? shell : launcher.isUnix() ? "sh" : "cmd";
365-
String msg = "Executing " + sh + " script inside container " + containerName + " of pod " + getPodName();
379+
String[] sh = shell != null ? new String[]{shell} : launcher.isUnix() ? new String[] {"sh"} : new String[] {"cmd", "/Q"};
380+
String msg = "Executing " + String.join(" ", sh) + " script inside container " + containerName + " of pod " + getPodName();
366381
LOGGER.log(Level.FINEST, msg);
367382
printStream.println(msg);
368383

@@ -453,6 +468,10 @@ public void onClose(int i, String s) {
453468
toggleStdout.disable();
454469
OutputStream stdin = watch.getInput();
455470
PrintStream in = new PrintStream(stdin, true, StandardCharsets.UTF_8.name());
471+
if (!launcher.isUnix()) {
472+
in.print("@echo off");
473+
in.print(newLine(true));
474+
}
456475
if (pwd != null) {
457476
// We need to get into the project workspace.
458477
// The workspace is not known in advance, so we have to execute a cd command.
@@ -485,7 +504,31 @@ public void onClose(int i, String s) {
485504
LOGGER.log(Level.FINEST, "Launching with env vars: {0}", envVars.toString());
486505

487506
setupEnvironmentVariable(envVars, in, !launcher.isUnix());
488-
507+
if (!launcher.isUnix() && toggleOutputForCaller!= null) {
508+
// Windows welcome message should not be sent to the caller as it is a side-effect of calling the wrapping cmd.exe
509+
// Microsoft Windows [Version 10.0.17763.2686]
510+
// (c) 2018 Microsoft Corporation. All rights reserved.
511+
//
512+
// C:\>
513+
stream.flush();
514+
long beginning = System.currentTimeMillis();
515+
// watch for the prompt character
516+
while(!dryRunCaller.toString(StandardCharsets.UTF_8.name()).contains(">")) {
517+
Thread.sleep(100);
518+
}
519+
LOGGER.log(Level.FINEST, "Windows prompt printed after " + (System.currentTimeMillis() - beginning) + " ms");
520+
}
521+
// We don't need to capture output anymore
522+
if (toggleDryRunCaller != null) {
523+
toggleDryRunCaller.disable();
524+
}
525+
// Clear any captured bytes
526+
if (dryRunCaller != null) {
527+
dryRunCaller.reset();
528+
}
529+
if (toggleOutputForCaller != null) {
530+
toggleOutputForCaller.enable();
531+
}
489532
doExec(in, !launcher.isUnix(), printStream, masks, commands);
490533

491534
LOGGER.log(Level.INFO, "Created process inside pod: [" + getPodName() + "], container: ["
@@ -554,7 +597,12 @@ public void close() throws IOException {
554597
private static class ToggleOutputStream extends FilterOutputStream {
555598
private boolean disabled;
556599
public ToggleOutputStream(OutputStream out) {
600+
this(out, false);
601+
}
602+
603+
public ToggleOutputStream(OutputStream out, boolean disabled) {
557604
super(out);
605+
this.disabled = disabled;
558606
}
559607

560608
public void disable() {
@@ -571,6 +619,20 @@ public void write(int b) throws IOException {
571619
out.write(b);
572620
}
573621
}
622+
623+
@Override
624+
public void write(byte[] b) throws IOException {
625+
if (!disabled) {
626+
out.write(b);
627+
}
628+
}
629+
630+
@Override
631+
public void write(byte[] b, int off, int len) throws IOException {
632+
if (!disabled) {
633+
out.write(b, off, len);
634+
}
635+
}
574636
}
575637

576638
/**
@@ -625,17 +687,19 @@ private static void doExec(PrintStream in, boolean windows, PrintStream out, boo
625687
// Mask sensitive output
626688
MaskOutputStream maskedOutput = new MaskOutputStream(teeOutput, masks);
627689
// Tee everything together
628-
PrintStream tee = null;
690+
PrintStream tee;
629691
try {
630692
String encoding = StandardCharsets.UTF_8.name();
631693
tee = new PrintStream(new TeeOutputStream(in, maskedOutput), false, encoding);
632694
// To output things that shouldn't be considered for masking
633695
PrintStream unmasked = new PrintStream(teeOutput, false, encoding);
634696
unmasked.print("Executing command: ");
635697
for (String statement : statements) {
636-
tee.append("\"")
637-
.append(statement)
638-
.append("\" ");
698+
if (windows) {
699+
tee.append(statement).append(" ");
700+
} else {
701+
tee.append("\"").append(statement).append("\" ");
702+
}
639703
}
640704
tee.print(newLine(windows));
641705
LOGGER.log(Level.FINEST, loggingOutput.toString(encoding) + "[" + TimeUnit.NANOSECONDS.toMicros(System.nanoTime() - start) + " μs." + "]");
@@ -644,7 +708,7 @@ private static void doExec(PrintStream in, boolean windows, PrintStream out, boo
644708
tee.print(newLine(windows));
645709
tee.flush();
646710
} catch (UnsupportedEncodingException e) {
647-
e.printStackTrace();
711+
LOGGER.log(Level.SEVERE, "Failed to execute command because of unsupported encoding", e);
648712
}
649713
}
650714

src/test/java/org/csanchez/jenkins/plugins/kubernetes/KubernetesTestUtil.java

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
import static java.util.logging.Level.*;
2727
import static org.junit.Assume.*;
2828

29+
import edu.umd.cs.findbugs.annotations.CheckForNull;
2930
import java.io.IOException;
3031
import java.util.Collections;
3132
import java.util.HashMap;
@@ -81,6 +82,8 @@ public class KubernetesTestUtil {
8182
public static final String CONTAINER_ENV_VAR_FROM_SECRET_VALUE = "container-pa55w0rd";
8283
public static final String POD_ENV_VAR_FROM_SECRET_VALUE = "pod-pa55w0rd";
8384

85+
public static final String WINDOWS_1809_BUILD = "10.0.17763";
86+
8487
public static KubernetesCloud setupCloud(Object test, TestName name) throws KubernetesAuthException, IOException {
8588
KubernetesCloud cloud = new KubernetesCloud("kubernetes");
8689
// unique labels per test
@@ -150,17 +153,21 @@ public static void assumeKubernetes() throws Exception {
150153
* This means that we can run tests involving Windows agent pods.
151154
* Note that running the <em>controller</em> on Windows is untested.
152155
*/
153-
public static void assumeWindows() {
154-
assumeTrue("Cluster seems to contain no Windows nodes", isWindows());
156+
public static void assumeWindows(String buildNumber) {
157+
assumeTrue("Cluster seems to contain no Windows nodes with build " + buildNumber, isWindows(buildNumber));
155158
}
156159

157-
public static boolean isWindows() {
160+
public static boolean isWindows(@CheckForNull String buildNumber) {
158161
try (KubernetesClient client = new DefaultKubernetesClient(new ConfigBuilder(Config.autoConfigure(null)).build())) {
159162
for (Node n : client.nodes().list().getItems()) {
160-
String os = n.getMetadata().getLabels().get("kubernetes.io/os");
161-
LOGGER.info(() -> "Found node " + n.getMetadata().getName() + " running OS " + os);
163+
Map<String, String> labels = n.getMetadata().getLabels();
164+
String os = labels.get("kubernetes.io/os");
165+
String windowsBuild = labels.get("node.kubernetes.io/windows-build");
166+
LOGGER.info(() -> "Found node " + n.getMetadata().getName() + " running OS " + os + " with Windows build " + windowsBuild);
162167
if ("windows".equals(os)) {
163-
return true;
168+
if (buildNumber == null || buildNumber.equals(windowsBuild)) {
169+
return true;
170+
}
164171
}
165172
}
166173
}

0 commit comments

Comments
 (0)