Skip to content

Commit

Permalink
Merge pull request #111 from casz/fix/pwsh
Browse files Browse the repository at this point in the history
Allow setting pwsh as powershell executable
  • Loading branch information
dwnusbaum authored Oct 10, 2019
2 parents 93b86c9 + 32cfda0 commit 0fca597
Show file tree
Hide file tree
Showing 6 changed files with 344 additions and 8 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@
import hudson.FilePath;
import hudson.Plugin;
import hudson.Launcher;
import hudson.util.ListBoxModel;
import hudson.util.ListBoxModel.Option;
import jenkins.model.Jenkins;
import java.util.ArrayList;
import java.util.Arrays;
Expand All @@ -42,18 +44,29 @@
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.nio.charset.Charset;
import org.kohsuke.stapler.DataBoundSetter;

/**
* Runs a Powershell script
*/
public final class PowershellScript extends FileMonitoringTask {
private final String script;
private String powershellBinary = "powershell";
private boolean capturingOutput;

@DataBoundConstructor public PowershellScript(String script) {
this.script = script;
}


public String getPowershellBinary() {
return powershellBinary;
}

@DataBoundSetter
public void setPowershellBinary(String powershellBinary) {
this.powershellBinary = powershellBinary;
}

public String getScript() {
return script;
}
Expand Down Expand Up @@ -82,9 +95,7 @@ public String getScript() {
quote(c.getLogFile(ws)),
quote(c.getResultFile(ws)));
}

// Note: PowerShell core is now named pwsh. Workaround this issue on *nix systems by creating a symlink that maps 'powershell' to 'pwsh'.
String powershellBinary = "powershell";

String powershellArgs;
if (launcher.isUnix()) {
powershellArgs = "-NoProfile -NonInteractive";
Expand All @@ -106,8 +117,8 @@ public String getScript() {
// Copy the helper script from the resources directory into the workspace
c.getPowerShellHelperFile(ws).copyFrom(getClass().getResource("powershellHelper.ps1"));

if (launcher.isUnix()) {
// There is no need to add a BOM with Open PowerShell
if (launcher.isUnix() || "pwsh".equals(powershellBinary)) {
// There is no need to add a BOM with Open PowerShell / PowerShell Core
c.getPowerShellScriptFile(ws).write(scriptWithExit, "UTF-8");
if (!capturingOutput) {
c.getPowerShellWrapperFile(ws).write(scriptWrapper, "UTF-8");
Expand Down Expand Up @@ -171,6 +182,10 @@ public FilePath getPowerShellWrapperFile(FilePath ws) throws IOException, Interr
return Messages.PowershellScript_powershell();
}

public ListBoxModel doFillPowershellBinary() {
return new ListBoxModel(new Option("powershell"), new Option("pwsh"));
}

}

}
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,7 @@
<f:entry field="script" title="Script">
<f:textarea/>
</f:entry>
<f:entry field="powershellBinary" title="Executable">
<f:select />
</f:entry>
</j:jelly>
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package org.jenkinsci.plugins.durabletask;

import org.jenkinsci.test.acceptance.docker.DockerContainer;
import org.jenkinsci.test.acceptance.docker.DockerFixture;

@DockerFixture(id = "pwsh", ports = 22)
public class PowerShellCoreFixture extends DockerContainer {
public static final String PWSH_JAVA_LOCATION = "/usr/lib/jvm/java-8-openjdk-amd64/jre/bin/java";
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,295 @@
package org.jenkinsci.plugins.durabletask;

import com.cloudbees.plugins.credentials.Credentials;
import com.cloudbees.plugins.credentials.CredentialsScope;
import com.cloudbees.plugins.credentials.SystemCredentialsProvider;
import com.cloudbees.plugins.credentials.domains.Domain;
import com.cloudbees.plugins.credentials.impl.UsernamePasswordCredentialsImpl;
import hudson.EnvVars;
import hudson.FilePath;
import hudson.Launcher;
import hudson.model.Slave;
import hudson.plugins.sshslaves.SSHLauncher;
import hudson.slaves.DumbSlave;
import hudson.util.StreamTaskListener;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.util.Collections;
import java.util.Properties;
import java.util.logging.Level;
import org.jenkinsci.test.acceptance.docker.Docker;
import org.jenkinsci.test.acceptance.docker.DockerContainer;
import org.jenkinsci.test.acceptance.docker.DockerRule;
import org.junit.After;
import org.junit.Assume;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Rule;
import org.junit.Test;
import org.jvnet.hudson.test.JenkinsRule;
import org.jvnet.hudson.test.LoggerRule;

import static org.hamcrest.Matchers.containsString;
import static org.jenkinsci.plugins.durabletask.BourneShellScriptTest.assumeDocker;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.assertTrue;

public class PowerShellCoreScriptTest {

@Rule
public JenkinsRule j = new JenkinsRule();
@Rule
public DockerRule<PowerShellCoreFixture> dockerPWSH = new DockerRule<>(PowerShellCoreFixture.class);
@Rule
public LoggerRule logging = new LoggerRule().recordPackage(PowershellScript.class, Level.FINE);

private StreamTaskListener listener;
private FilePath ws;
private Launcher launcher;
private Slave s;
static boolean pwshExists;

@BeforeClass
public static void pwshOrDocker() throws Exception {
checkPwsh();
if (!pwshExists && new Docker().isAvailable()) {
assumeDocker();
} else {
Assume.assumeTrue("This test should only run if pwsh is available", pwshExists);
}
}

private static void checkPwsh() {
Properties properties = System.getProperties();
String pathSeparator = properties.getProperty("path.separator");
String[] paths = System.getenv("PATH").split(pathSeparator);
String cmd = "pwsh";
for (String p : paths) {
// If running on *nix then the binary does not have an extension. Check for both variants to ensure *nix and windows+cygwin are both supported.
File withoutExtension = new File(p, cmd);
File withExtension = new File(p, cmd + ".exe");
if (withoutExtension.exists() || withExtension.exists()) {
pwshExists = true;
break;
}
}
}

@Before
public void setUp() throws Exception {
listener = StreamTaskListener.fromStdout();
if (pwshExists) {
s = j.createOnlineSlave();
} else {
DockerContainer container = dockerPWSH.get();
SystemCredentialsProvider.getInstance().setDomainCredentialsMap(Collections.singletonMap(
Domain
.global(), Collections.<Credentials>singletonList(new UsernamePasswordCredentialsImpl(
CredentialsScope.GLOBAL, "test", null, "test", "test"))));
SSHLauncher sshLauncher = new SSHLauncher(container.ipBound(22), container.port(22), "test");
sshLauncher.setJavaPath(PowerShellCoreFixture.PWSH_JAVA_LOCATION);
s = new DumbSlave("docker", "/home/test", sshLauncher);
j.jenkins.addNode(s);
j.waitOnline(s);
}
ws = s.getWorkspaceRoot().child("ws");
launcher = s.createLauncher(listener);
}

@After
public void tearDown() throws Exception {
if (s != null) {
j.jenkins.removeNode(s);
}
}

@Test
public void explicitExit() throws Exception {
PowershellScript s = new PowershellScript("Write-Output \"Hello, World!\"; exit 1;");
s.setPowershellBinary("pwsh");
Controller c = s.launch(new EnvVars(), ws, launcher, listener);
while (c.exitStatus(ws, launcher, listener) == null) {
Thread.sleep(100);
}
ByteArrayOutputStream baos = new ByteArrayOutputStream();
c.writeLog(ws, baos);
assertEquals(Integer.valueOf(1), c.exitStatus(ws, launcher));
assertThat(baos.toString(), containsString("Hello, World!"));
c.cleanup(ws);
}

@Test public void implicitExit() throws Exception {
PowershellScript s = new PowershellScript("Write-Output \"Success!\";");
s.setPowershellBinary("pwsh");
Controller c = s.launch(new EnvVars(), ws, launcher, listener);
while (c.exitStatus(ws, launcher, listener) == null) {
Thread.sleep(100);
}
ByteArrayOutputStream baos = new ByteArrayOutputStream();
c.writeLog(ws, baos);
assertEquals(Integer.valueOf(0), c.exitStatus(ws, launcher));
assertThat(baos.toString(), containsString("Success!"));
c.cleanup(ws);
}

@Test public void implicitError() throws Exception {
PowershellScript s = new PowershellScript("$ErrorActionPreference = 'Stop'; Write-Error \"Bogus error\"");
s.setPowershellBinary("pwsh");
Controller c = s.launch(new EnvVars(), ws, launcher, listener);
while (c.exitStatus(ws, launcher, listener) == null) {
Thread.sleep(100);
}
ByteArrayOutputStream baos = new ByteArrayOutputStream();
c.writeLog(ws, baos);
assertTrue(c.exitStatus(ws, launcher, listener) != 0);
assertThat(baos.toString(), containsString("Bogus error"));
c.cleanup(ws);
}

@Test public void implicitErrorNegativeTest() throws Exception {
PowershellScript s = new PowershellScript("$ErrorActionPreference = 'SilentlyContinue'; Write-Error \"Bogus error\"");
s.setPowershellBinary("pwsh");
Controller c = s.launch(new EnvVars(), ws, launcher, listener);
while (c.exitStatus(ws, launcher, listener) == null) {
Thread.sleep(100);
}
assertTrue(c.exitStatus(ws, launcher, listener) == 0);
c.cleanup(ws);
}

@Test public void explicitThrow() throws Exception {
PowershellScript s = new PowershellScript("Write-Output \"Hello, World!\"; throw \"explicit error\";");
s.setPowershellBinary("pwsh");
s.captureOutput();
Controller c = s.launch(new EnvVars(), ws, launcher, listener);
while (c.exitStatus(ws, launcher, listener) == null) {
Thread.sleep(100);
}
ByteArrayOutputStream baos = new ByteArrayOutputStream();
c.writeLog(ws, baos);
assertTrue(c.exitStatus(ws, launcher, listener).intValue() != 0);
assertThat(baos.toString(), containsString("explicit error"));
if (launcher.isUnix()) {
assertEquals("Hello, World!\n", new String(c.getOutput(ws, launcher)));
} else {
assertEquals("Hello, World!\r\n", new String(c.getOutput(ws, launcher)));
}
c.cleanup(ws);
}

@Test public void implicitThrow() throws Exception {
PowershellScript s = new PowershellScript("$ErrorActionPreference = 'Stop'; My-BogusCmdlet;");
s.setPowershellBinary("pwsh");
Controller c = s.launch(new EnvVars(), ws, launcher, listener);
while (c.exitStatus(ws, launcher, listener) == null) {
Thread.sleep(100);
}
ByteArrayOutputStream baos = new ByteArrayOutputStream();
c.writeLog(ws, baos);
assertTrue(c.exitStatus(ws, launcher, listener).intValue() != 0);
assertThat(baos.toString(), containsString("My-BogusCmdlet"));
c.cleanup(ws);
}

@Test public void noStdoutPollution() throws Exception {
PowershellScript s = new PowershellScript("$VerbosePreference = \"Continue\"; " +
"$WarningPreference = \"Continue\"; " +
"$DebugPreference = \"Continue\"; " +
"Write-Verbose \"Hello, Verbose!\"; " +
"Write-Warning \"Hello, Warning!\"; " +
"Write-Debug \"Hello, Debug!\"; " +
"Write-Output \"Success\"");
s.setPowershellBinary("pwsh");
s.captureOutput();
Controller c = s.launch(new EnvVars(), ws, launcher, listener);
while (c.exitStatus(ws, launcher, listener) == null) {
Thread.sleep(100);
}
ByteArrayOutputStream baos = new ByteArrayOutputStream();
c.writeLog(ws, baos);
assertTrue(c.exitStatus(ws, launcher, listener).intValue() == 0);
assertThat(baos.toString(), containsString("Hello, Verbose!"));
assertThat(baos.toString(), containsString("Hello, Warning!"));
assertThat(baos.toString(), containsString("Hello, Debug!"));
if (launcher.isUnix()) {
assertEquals("Success\n", new String(c.getOutput(ws, launcher)));
} else {
assertEquals("Success\r\n", new String(c.getOutput(ws, launcher)));
}
c.cleanup(ws);
}

@Test public void specialStreams() throws Exception {
PowershellScript s = new PowershellScript("$VerbosePreference = \"Continue\"; " +
"$WarningPreference = \"Continue\"; " +
"$DebugPreference = \"Continue\"; " +
"Write-Verbose \"Hello, Verbose!\"; " +
"Write-Warning \"Hello, Warning!\"; " +
"Write-Debug \"Hello, Debug!\";");
s.setPowershellBinary("pwsh");
Controller c = s.launch(new EnvVars(), ws, launcher, listener);
while (c.exitStatus(ws, launcher, listener) == null) {
Thread.sleep(100);
}
ByteArrayOutputStream baos = new ByteArrayOutputStream();
c.writeLog(ws, baos);
assertEquals(0, c.exitStatus(ws, launcher, listener).intValue());
assertThat(baos.toString(), containsString("VERBOSE: Hello, Verbose!"));
assertThat(baos.toString(), containsString("WARNING: Hello, Warning!"));
assertThat(baos.toString(), containsString("DEBUG: Hello, Debug!"));
c.cleanup(ws);
}

@Test public void spacesInWorkspace() throws Exception {
final FilePath newWs = new FilePath(ws, "subdirectory with spaces");
PowershellScript s = new PowershellScript("Write-Host 'Running in a workspace with spaces in the path'");
s.setPowershellBinary("pwsh");
Controller c = s.launch(new EnvVars(), newWs, launcher, listener);
while (c.exitStatus(newWs, launcher, listener) == null) {
Thread.sleep(100);
}
assertEquals(0, c.exitStatus(newWs, launcher).intValue());
c.cleanup(ws);
}

@Test public void echoEnvVar() throws Exception {
PowershellScript s = new PowershellScript("echo envvar=$env:MYVAR");
s.setPowershellBinary("pwsh");
Controller c = s.launch(new EnvVars("MYVAR", "power$hell"), ws, launcher, listener);
while (c.exitStatus(ws, launcher, listener) == null) {
Thread.sleep(100);
}
ByteArrayOutputStream baos = new ByteArrayOutputStream();
c.writeLog(ws,baos);
assertEquals(0, c.exitStatus(ws, launcher, listener).intValue());
assertThat(baos.toString(), containsString("envvar=power$hell"));
c.cleanup(ws);
}

@Test public void unicodeChars() throws Exception {
PowershellScript s = new PowershellScript("Write-Output \"Helló, Wõrld ®\";");
s.setPowershellBinary("pwsh");
Controller c = s.launch(new EnvVars(), ws, launcher, listener);
while (c.exitStatus(ws, launcher, listener) == null) {
Thread.sleep(100);
}
ByteArrayOutputStream baos = new ByteArrayOutputStream();
c.writeLog(ws, baos);
assertEquals(Integer.valueOf(0), c.exitStatus(ws, launcher));
String log = baos.toString("UTF-8");
assertTrue(log, log.contains("Helló, Wõrld ®"));
c.cleanup(ws);
}

@Test public void correctExitCode() throws Exception {
PowershellScript s = new PowershellScript("exit 5;");
s.setPowershellBinary("pwsh");
Controller c = s.launch(new EnvVars(), ws, launcher, listener);
while (c.exitStatus(ws, launcher, listener) == null) {
Thread.sleep(100);
}
assertEquals(Integer.valueOf(5), c.exitStatus(ws, launcher));
c.cleanup(ws);
}
}
Loading

0 comments on commit 0fca597

Please sign in to comment.