Skip to content

Commit

Permalink
[FIXED JENKINS-10965] Enable exec command to be run in a pseudo TTY
Browse files Browse the repository at this point in the history
  • Loading branch information
bap2000 committed Sep 11, 2011
1 parent 25f7a62 commit 5f0509d
Show file tree
Hide file tree
Showing 18 changed files with 119 additions and 18 deletions.
2 changes: 1 addition & 1 deletion pom.xml
Expand Up @@ -69,7 +69,7 @@
<dependency>
<groupId>org.jenkins-ci.plugins</groupId>
<artifactId>publish-over</artifactId>
<version>0.13-SNAPSHOT</version>
<version>0.12</version>
</dependency>
<dependency>
<groupId>junit</groupId>
Expand Down
Expand Up @@ -141,6 +141,7 @@ private void exec(final BapSshTransfer transfer) {
ChannelExec exec = null;
try {
exec = openExecChannel();
exec.setPty(transfer.isUsePty());
exec.setInputStream(null);
exec.setOutputStream(buildInfo.getListener().getLogger(), true);
exec.setErrStream(buildInfo.getListener().getLogger(), true);
Expand Down
19 changes: 14 additions & 5 deletions src/main/java/jenkins/plugins/publish_over_ssh/BapSshTransfer.java
Expand Up @@ -41,18 +41,21 @@ public class BapSshTransfer extends BPTransfer implements Describable<BapSshTran

private String execCommand;
private int execTimeout;
private boolean usePty;

BapSshTransfer(final String sourceFiles, final String remoteDirectory, final String removePrefix,
final boolean remoteDirectorySDF, final boolean flatten, final String execCommand, final int execTimeout) {
this(sourceFiles, null, remoteDirectory, removePrefix, remoteDirectorySDF, flatten, execCommand, execTimeout);
this(sourceFiles, null, remoteDirectory, removePrefix, remoteDirectorySDF, flatten, execCommand, execTimeout, false);
}

@DataBoundConstructor
public BapSshTransfer(final String sourceFiles, final String excludes, final String remoteDirectory, final String removePrefix,
final boolean remoteDirectorySDF, final boolean flatten, final String execCommand, final int execTimeout) {
final boolean remoteDirectorySDF, final boolean flatten, final String execCommand, final int execTimeout,
final boolean usePty) {
super(sourceFiles, excludes, remoteDirectory, removePrefix, remoteDirectorySDF, flatten);
this.execCommand = execCommand;
this.execTimeout = execTimeout;
this.usePty = usePty;
}

public String getExecCommand() { return execCommand; }
Expand All @@ -63,24 +66,30 @@ public boolean hasExecCommand() {
return Util.fixEmptyAndTrim(getExecCommand()) != null;
}

public boolean isUsePty() {
return usePty;
}

public BapSshTransferDescriptor getDescriptor() {
return Hudson.getInstance().getDescriptorByType(BapSshTransferDescriptor.class);
}

protected HashCodeBuilder addToHashCode(final HashCodeBuilder builder) {
return super.addToHashCode(builder).append(execCommand).append(execTimeout);
return super.addToHashCode(builder).append(execCommand).append(execTimeout).append(usePty);
}

protected EqualsBuilder addToEquals(final EqualsBuilder builder, final BapSshTransfer that) {
return super.addToEquals(builder, that)
.append(execCommand, that.execCommand)
.append(execTimeout, that.execTimeout);
.append(execTimeout, that.execTimeout)
.append(usePty, that.usePty);
}

protected ToStringBuilder addToToString(final ToStringBuilder builder) {
return super.addToToString(builder)
.append("execCommand", execCommand)
.append("execTimeout", execTimeout);
.append("execTimeout", execTimeout)
.append("pseudoTty", usePty);
}

public boolean equals(final Object that) {
Expand Down
Expand Up @@ -44,11 +44,12 @@ public class SshOverrideTransferDefaults implements SshTransferOptions, Describa
private final boolean remoteDirectorySDF;
private final boolean flatten;
private final boolean cleanRemote;
private final boolean usePty;

@DataBoundConstructor
public SshOverrideTransferDefaults(final String sourceFiles, final String excludes, final String removePrefix,
final String remoteDirectory, final boolean flatten, final boolean remoteDirectorySDF,
final boolean cleanRemote, final String execCommand, final int execTimeout) {
final boolean cleanRemote, final String execCommand, final int execTimeout, final boolean usePty) {
this.cleanRemote = cleanRemote;
this.excludes = excludes;
this.execCommand = execCommand;
Expand All @@ -58,6 +59,7 @@ public SshOverrideTransferDefaults(final String sourceFiles, final String exclud
this.remoteDirectorySDF = remoteDirectorySDF;
this.removePrefix = removePrefix;
this.sourceFiles = sourceFiles;
this.usePty = usePty;
}

public String getExecCommand() {
Expand Down Expand Up @@ -100,7 +102,11 @@ public SshOverrideTransferDefaultsDescriptor getDescriptor() {
return Hudson.getInstance().getDescriptorByType(SshOverrideTransferDefaultsDescriptor.class);
}

@Extension
public boolean isUsePty() {
return usePty;
}

@Extension
public static class SshOverrideTransferDefaultsDescriptor extends Descriptor<SshOverrideTransferDefaults> {

@Override
Expand Down
Expand Up @@ -31,12 +31,15 @@ public final class SshPluginDefaultsHandler implements InvocationHandler {

private static final String GET_EXEC_COMMAND = "getExecCommand";
private static final String GET_EXEC_TIMEOUT = "getExecTimeout";
private static final String IS_USE_PTY = "isUsePty";

public Object invoke(final Object proxy, final Method method, final Object[] args) throws Throwable {
if (method.getName().equals(GET_EXEC_COMMAND))
return getExecCommand();
else if (method.getName().equals(GET_EXEC_TIMEOUT))
return getExecTimeout();
else if (method.getName().equals(IS_USE_PTY))
return isPseudoTty();
return method.invoke(SshPluginDefaults.GLOBAL_DEFAULTS, args);
}

Expand All @@ -48,4 +51,8 @@ public int getExecTimeout() {
return SshTransferOptions.DEFAULT_EXEC_TIMEOUT;
}

public boolean isPseudoTty() {
return SshTransferOptions.DEFAULT_USE_PTY;
}

}
Expand Up @@ -29,9 +29,12 @@
public interface SshTransferOptions extends TransferOptions {

int DEFAULT_EXEC_TIMEOUT = 120000;
boolean DEFAULT_USE_PTY = false;

String getExecCommand();

int getExecTimeout();

boolean isUsePty();

}
Expand Up @@ -68,6 +68,10 @@
<f:textbox default="${defaults.transfer.execTimeout}" clazz="ssh-exec-control"/>
</f:entry>

<f:entry title="${%usePty}" field="usePty">
<f:checkbox default="${defaults.transfer.usePty}"/>
</f:entry>

</f:advanced>

</j:jelly>
Expand Up @@ -26,3 +26,4 @@ execCommand=Exec command
transfers.envVars=All of the transfer fields (except for Exec timeout) support substitution of \
<a href="{0}/env-vars.html" target="_blank">Jenkins environment variables</a>
execTimeout=Exec timeout (ms)
usePty=Exec in pty
Expand Up @@ -26,3 +26,4 @@ execCommand=E*e* c*m*a*d
transfers.envVars=A*l o* t*e t*a*s*e* f*e*d* (e*c*p* f*r E*e* t*m*o*t) s*p*o*t s*b*t*t*t*o* o* \
<a href="{0}/env-vars.html" target="_blank">J*n*i*s e*v*r*n*e*t v*r*a*l*s</a>
execTimeout=E*e* t*m*o*t (m*)
usePty=C*e*t* * *s*u*o*t*y
@@ -0,0 +1,36 @@
<!--
~ The MIT License
~
~ Copyright (C) 2010-2011 by Anthony Robinson
~
~ Permission is hereby granted, free of charge, to any person obtaining a copy
~ of this software and associated documentation files (the "Software"), to deal
~ in the Software without restriction, including without limitation the rights
~ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
~ copies of the Software, and to permit persons to whom the Software is
~ furnished to do so, subject to the following conditions:
~
~ The above copyright notice and this permission notice shall be included in
~ all copies or substantial portions of the Software.
~
~ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
~ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
~ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
~ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
~ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
~ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
~ THE SOFTWARE.
-->

<div>
<p>Exec the command in a pseudo tty</p>
<p>This will enable the execution of sudo commands that require a tty (and possibly help in other scenarios too.)<br />
From the sudoers(5) man page:
<pre><tt>requiretty If set, sudo will only run when the user is logged in
to a real tty. When this flag is set, sudo can only be
run from a login session and not via other means such
as cron(8) or cgi-bin scripts. This flag is off by
default.
</tt></pre>
</p>
</div>
Expand Up @@ -54,5 +54,10 @@
<f:entry title="${%execTimeout}" field="execTimeout">
<f:textbox default="${defaults.transfer.execTimeout}"/>
</f:entry>

<f:entry title="${%usePty}" field="usePty">
<f:checkbox default="${defaults.transfer.usePty}"/>
</f:entry>


</j:jelly>
Expand Up @@ -24,3 +24,4 @@

execCommand=Exec command
execTimeout=Exec timeout (ms)
usePty=Exec in pty
Expand Up @@ -24,3 +24,4 @@

execCommand=E*e* c*m*a*d
execTimeout=E*e* t*m*o*t (m*)
usePty=C*e*t* * *s*u*o*t*y
5 changes: 5 additions & 0 deletions src/main/webapp/js/pos.js
Expand Up @@ -55,6 +55,11 @@ function bap_set_exec_control_visibility(container, show_or_hide_row) {
var row = sshControl.ancestors()[1];
show_or_hide_row(row);
});
// hack for pty as can't get class onto a checkbox
$(container).getElementsBySelector('input[name="_.usePty"]').each(function(sshControl) {
var row = sshControl.ancestors()[1];
show_or_hide_row(row);
});
}

function bap_show_exec_controls(container) {
Expand Down
Expand Up @@ -48,6 +48,7 @@
import static org.easymock.EasyMock.expect;
import static org.easymock.EasyMock.expectLastCall;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;

Expand Down Expand Up @@ -244,6 +245,7 @@ private void assertDisconnect() throws Exception {
bapSshClient.endTransfers(new BapSshTransfer("", "", "", false, false, command, execCommandTimeout));
} });
exec.assertMethodsCalled();
assertFalse(exec.isUsePty());
}

@Test public void testEndTransfersThrowsExceptionIfCommandTimesOut() throws Exception {
Expand Down Expand Up @@ -284,12 +286,25 @@ private void assertDisconnect() throws Exception {
mockControl.verify();
}

@Test public void testCanExecInPty() throws Exception {
final String command = "n/a";
final int timeout = 20000;
final int pollsBeforeClosed = 1;
final TestExec exec = new TestExec(command, timeout, 0, pollsBeforeClosed);
expect(mockSession.openChannel("exec")).andReturn(exec);
expect(mockSession.getTimeout()).andReturn(timeout);
mockControl.replay();
bapSshClient.endTransfers(new BapSshTransfer("", "", "", "", false, false, command, timeout, true));
assertTrue(exec.isUsePty());
}

public static class TestExec extends ChannelExec {
private final String expectedCommand;
private final int expectedTimeout;
private final int exitStatus;
private int pollsBeforeClosed;
private boolean setCommandCalled, connectCalled;
private boolean usePty;
public TestExec(final String expectedCommand, final int expectedConnectTimeout, final int exitStatus, final int pollsBeforeClosed) {
this.expectedCommand = expectedCommand;
this.expectedTimeout = expectedConnectTimeout;
Expand Down Expand Up @@ -323,6 +338,12 @@ public int getExitStatus() {
public void assertMethodsCalled() {
if (!setCommandCalled || !connectCalled) fail();
}
public void setPty(final boolean usePty) {
this.usePty = usePty;
}
public boolean isUsePty() {
return usePty;
}
}

}
Expand Up @@ -287,8 +287,8 @@ private BapSshClient assertCreateClientWithDefaultKey(final boolean disableExec)
@Test public void testDontConnectSftpIfNoSourceFilesInAnyTransfers() throws Exception {
final BapSshCommonConfiguration defaultKeyInfo = new BapSshCommonConfiguration(TEST_PASSPHRASE, null, null, false);
hostConfig = createWithDefaultKeyInfo(mockJSch, defaultKeyInfo);
final BapSshTransfer transfer1 = new BapSshTransfer("", "", "", "", false, false, "ls -la", 10000);
final BapSshTransfer transfer2 = new BapSshTransfer("", "", "", "", false, false, "pwd", 10000);
final BapSshTransfer transfer1 = new BapSshTransfer("", "", "", "", false, false, "ls -la", 10000, false);
final BapSshTransfer transfer2 = new BapSshTransfer("", "", "", "", false, false, "pwd", 10000, false);
final ArrayList<BapSshTransfer> transfers = new ArrayList<BapSshTransfer>();
transfers.addAll(Arrays.asList(transfer1, transfer2));
final BapSshPublisher publisher = new BapSshPublisher(hostConfig.getName(), false, transfers, false, false, null, null);
Expand Down
Expand Up @@ -79,7 +79,7 @@ public JSch createJSch() {
new JenkinsTestHelper().setGlobalConfig(commonConfig, testHostConfig);
final String dirToIgnore = "target";
final int execTimeout = 10000;
final BapSshTransfer transfer = new BapSshTransfer("**/*", null, "sub-home", dirToIgnore, false, false, "", execTimeout);
final BapSshTransfer transfer = new BapSshTransfer("**/*", null, "sub-home", dirToIgnore, false, false, "", execTimeout, false);
final BapSshPublisher publisher = new BapSshPublisher(testHostConfig.getName(), false,
new ArrayList<BapSshTransfer>(Collections.singletonList(transfer)), false, false, null, null);
final BapSshPublisherPlugin plugin = new BapSshPublisherPlugin(
Expand Down
Expand Up @@ -64,7 +64,7 @@ public void testLoadR0x1Minimal() throws Exception {

final int expectedExecTimeout = 120000;
final List<BapSshTransfer> transfers = Collections.singletonList(
new BapSshTransfer("**/*", null, "", "", false, false, "", expectedExecTimeout));
new BapSshTransfer("**/*", null, "", "", false, false, "", expectedExecTimeout, false));
final BapSshPublisher publisher = new BapSshPublisher("default", false, new ArrayList<BapSshTransfer>(transfers),
false, false, null, null);
final ArrayList<BapSshPublisher> publishers = new ArrayList<BapSshPublisher>();
Expand All @@ -88,7 +88,7 @@ public void testLoadR0x12() throws Exception {
assertPublisherPluginConfiguration(DEFAULT_EXEC_TIMEOUT);

final List<BapSshTransfer> builderTransfers = Collections.singletonList(
new BapSshTransfer("builderC/", null, "", "", false, false, "", DEFAULT_EXEC_TIMEOUT));
new BapSshTransfer("builderC/", null, "", "", false, false, "", DEFAULT_EXEC_TIMEOUT, false));
final List<BapSshPublisher> builderPublishers = Collections.singletonList(
new BapSshPublisher(configName('c'), false, new ArrayList<BapSshTransfer>(builderTransfers),
false, false, null, null));
Expand All @@ -97,7 +97,7 @@ public void testLoadR0x12() throws Exception {
assertEquals(expectedBuilderPlugin, getConfiguredBuilderPlugin());

final List<BapSshTransfer> preTransfers = Collections.singletonList(
new BapSshTransfer("beforeA/", null, "", "", false, false, "", DEFAULT_EXEC_TIMEOUT));
new BapSshTransfer("beforeA/", null, "", "", false, false, "", DEFAULT_EXEC_TIMEOUT, false));
final List<BapSshPublisher> prePublishers = Collections.singletonList(
new BapSshPublisher(configName('a'), false, new ArrayList<BapSshTransfer>(preTransfers),
false, false, null, null));
Expand All @@ -106,7 +106,7 @@ public void testLoadR0x12() throws Exception {
assertEquals(expectedPreBuildPlugin, getConfiguredBuildWrapper(BapSshPreBuildWrapper.class));

final List<BapSshTransfer> postTransfers = Collections.singletonList(
new BapSshTransfer("afterD/", null, "", "", false, false, "", DEFAULT_EXEC_TIMEOUT));
new BapSshTransfer("afterD/", null, "", "", false, false, "", DEFAULT_EXEC_TIMEOUT, false));
final List<BapSshPublisher> postPublishers = Collections.singletonList(
new BapSshPublisher(configName('d'), false, new ArrayList<BapSshTransfer>(postTransfers),
false, false, null, null));
Expand Down Expand Up @@ -136,16 +136,16 @@ private void assertGlobalConfig() {

private void assertPublisherPluginConfiguration(final int transfer11Timeout) {
final int transfer12Timeout = 15000;
final BapSshTransfer transfer11 = new BapSshTransfer("", null, "", "", false, false, "date", transfer11Timeout);
final BapSshTransfer transfer11 = new BapSshTransfer("", null, "", "", false, false, "date", transfer11Timeout, false);
final BapSshTransfer transfer12 = new BapSshTransfer("target/*.jar", null, "'builds/'yyyy_MM_dd/'build-${BUILD_NUMBER}'", "target",
true, true, "ls -la /tmp", transfer12Timeout);
true, true, "ls -la /tmp", transfer12Timeout, false);
final ArrayList<BapSshTransfer> transfers1 = new ArrayList<BapSshTransfer>();
transfers1.add(transfer11);
transfers1.add(transfer12);
final BapSshPublisher publisher1 = new BapSshPublisher(configName('a'), true, transfers1, false, false, null, null);
final int transfer21Timeout = 10000;
final BapSshTransfer transfer21 = new BapSshTransfer("out\\dist\\**\\*", null, "", "out\\dist", false, false, "",
transfer21Timeout);
transfer21Timeout, false);
final ArrayList<BapSshTransfer> transfers2 = new ArrayList<BapSshTransfer>();
transfers2.add(transfer21);
final BapSshPublisher publisher2 = new BapSshPublisher(configName('c'), false, transfers2, false, false, null, null);
Expand Down

0 comments on commit 5f0509d

Please sign in to comment.