Skip to content

Test/implement windows ssh #990

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 5 commits into
base: master
Choose a base branch
from
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions src/main/java/hudson/plugins/ec2/AMITypeData.java
Original file line number Diff line number Diff line change
@@ -5,6 +5,9 @@
import java.util.concurrent.TimeUnit;

public abstract class AMITypeData extends AbstractDescribableImpl<AMITypeData> {

public abstract boolean isWindowsSSH();

public abstract boolean isWindows();

public abstract boolean isUnix();
6 changes: 3 additions & 3 deletions src/main/java/hudson/plugins/ec2/EC2OndemandSlave.java
Original file line number Diff line number Diff line change
@@ -7,12 +7,12 @@
import hudson.plugins.ec2.ssh.EC2UnixLauncher;
import hudson.plugins.ec2.win.EC2WindowsLauncher;
import hudson.plugins.ec2.ssh.EC2MacLauncher;
import hudson.plugins.ec2.ssh.EC2WindowsSSHLauncher;
import hudson.slaves.NodeProperty;

import java.io.IOException;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.CountDownLatch;
import java.util.logging.Level;
import java.util.logging.Logger;

@@ -73,8 +73,8 @@
@DataBoundConstructor
public EC2OndemandSlave(String name, String instanceId, String templateDescription, String remoteFS, int numExecutors, String labelString, Mode mode, String initScript, String tmpDir, List<? extends NodeProperty<?>> nodeProperties, String remoteAdmin, String javaPath, String jvmopts, boolean stopOnTerminate, String idleTerminationMinutes, String publicDNS, String privateDNS, List<EC2Tag> tags, String cloudName, int launchTimeout, AMITypeData amiType, ConnectionStrategy connectionStrategy, int maxTotalUses, Tenancy tenancy, Boolean metadataEndpointEnabled, Boolean metadataTokensRequired, Integer metadataHopsLimit, Boolean metadataSupported)
throws FormException, IOException {
super(name, instanceId, templateDescription, remoteFS, numExecutors, mode, labelString, (amiType.isWindows() ? new EC2WindowsLauncher() : (amiType.isMac() ? new EC2MacLauncher():
new EC2UnixLauncher())), new EC2RetentionStrategy(idleTerminationMinutes), initScript, tmpDir, nodeProperties, remoteAdmin, javaPath, jvmopts, stopOnTerminate, idleTerminationMinutes, tags, cloudName, launchTimeout, amiType, connectionStrategy, maxTotalUses, tenancy, metadataEndpointEnabled, metadataTokensRequired, metadataHopsLimit, metadataSupported);
super(name, instanceId, templateDescription, remoteFS, numExecutors, mode, labelString, (amiType.isWindows() ? new EC2WindowsLauncher() : (amiType.isMac() ? new EC2MacLauncher(): (amiType.isWindowsSSH() ? new EC2WindowsSSHLauncher() :

Check warning on line 76 in src/main/java/hudson/plugins/ec2/EC2OndemandSlave.java

ci.jenkins.io / Code Coverage

Partially covered line

Line 76 is only partially covered, 3 branches are missing
new EC2UnixLauncher()))), new EC2RetentionStrategy(idleTerminationMinutes), initScript, tmpDir, nodeProperties, remoteAdmin, javaPath, jvmopts, stopOnTerminate, idleTerminationMinutes, tags, cloudName, launchTimeout, amiType, connectionStrategy, maxTotalUses, tenancy, metadataEndpointEnabled, metadataTokensRequired, metadataHopsLimit, metadataSupported);
this.publicDNS = publicDNS;
this.privateDNS = privateDNS;
}
5 changes: 5 additions & 0 deletions src/main/java/hudson/plugins/ec2/MacData.java
Original file line number Diff line number Diff line change
@@ -29,6 +29,11 @@
return this;
}

@Override
public boolean isWindowsSSH() {
return false;

Check warning on line 34 in src/main/java/hudson/plugins/ec2/MacData.java

ci.jenkins.io / Code Coverage

Not covered line

Line 34 is not covered by tests
}

@Override
public boolean isWindows() {
return false;
119 changes: 119 additions & 0 deletions src/main/java/hudson/plugins/ec2/SSHData.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
package hudson.plugins.ec2;

import jenkins.model.Jenkins;
import org.apache.commons.lang.StringUtils;

public abstract class SSHData extends AMITypeData {
protected final String rootCommandPrefix;
protected final String slaveCommandPrefix;
protected final String slaveCommandSuffix;
protected final String sshPort;
protected final String bootDelay;

protected SSHData(String rootCommandPrefix, String slaveCommandPrefix, String slaveCommandSuffix, String sshPort, String bootDelay) {
this.rootCommandPrefix = rootCommandPrefix;
this.slaveCommandPrefix = slaveCommandPrefix;
this.slaveCommandSuffix = slaveCommandSuffix;
this.sshPort = sshPort;
this.bootDelay = bootDelay;

this.readResolve();
}

protected Object readResolve() {
Jenkins j = Jenkins.getInstanceOrNull();
if (j != null) {
j.checkPermission(Jenkins.ADMINISTER);
}
return this;
}

@Override
public boolean isWindowsSSH() {
return false;
}

@Override
public boolean isWindows() {
return false;
}

@Override
public boolean isUnix() {
return false;

Check warning on line 43 in src/main/java/hudson/plugins/ec2/SSHData.java

ci.jenkins.io / Code Coverage

Not covered line

Line 43 is not covered by tests
}

@Override
public boolean isMac() {
return false;
}

public String getRootCommandPrefix() {
return rootCommandPrefix;
}

public String getSlaveCommandPrefix() {
return slaveCommandPrefix;
}

public String getSlaveCommandSuffix() {
return slaveCommandSuffix;
}

public String getSshPort() {
return sshPort == null || sshPort.isEmpty() ? "22" : sshPort;

Check warning on line 64 in src/main/java/hudson/plugins/ec2/SSHData.java

ci.jenkins.io / Code Coverage

Partially covered line

Line 64 is only partially covered, one branch is missing
}

public String getBootDelay() {
return bootDelay;
}

@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((rootCommandPrefix == null) ? 0 : rootCommandPrefix.hashCode());
result = prime * result + ((slaveCommandPrefix == null) ? 0 : slaveCommandPrefix.hashCode());
result = prime * result + ((slaveCommandSuffix == null) ? 0 : slaveCommandSuffix.hashCode());
result = prime * result + ((sshPort == null) ? 0 : sshPort.hashCode());
result = prime * result + ((bootDelay == null) ? 0 : bootDelay.hashCode());
return result;

Check warning on line 80 in src/main/java/hudson/plugins/ec2/SSHData.java

ci.jenkins.io / Code Coverage

Not covered lines

Lines 73-80 are not covered by tests
}

@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)

Check warning on line 87 in src/main/java/hudson/plugins/ec2/SSHData.java

ci.jenkins.io / Code Coverage

Partially covered line

Line 87 is only partially covered, one branch is missing
return false;

Check warning on line 88 in src/main/java/hudson/plugins/ec2/SSHData.java

ci.jenkins.io / Code Coverage

Not covered line

Line 88 is not covered by tests
if (this.getClass() != obj.getClass())

Check warning on line 89 in src/main/java/hudson/plugins/ec2/SSHData.java

ci.jenkins.io / Code Coverage

Partially covered line

Line 89 is only partially covered, one branch is missing
return false;

Check warning on line 90 in src/main/java/hudson/plugins/ec2/SSHData.java

ci.jenkins.io / Code Coverage

Not covered line

Line 90 is not covered by tests
final SSHData other = (SSHData) obj;
if (StringUtils.isEmpty(rootCommandPrefix)) {

Check warning on line 92 in src/main/java/hudson/plugins/ec2/SSHData.java

ci.jenkins.io / Code Coverage

Partially covered line

Line 92 is only partially covered, one branch is missing
if (!StringUtils.isEmpty(other.rootCommandPrefix))
return false;

Check warning on line 94 in src/main/java/hudson/plugins/ec2/SSHData.java

ci.jenkins.io / Code Coverage

Not covered lines

Lines 93-94 are not covered by tests
} else if (!rootCommandPrefix.equals(other.rootCommandPrefix))

Check warning on line 95 in src/main/java/hudson/plugins/ec2/SSHData.java

ci.jenkins.io / Code Coverage

Partially covered line

Line 95 is only partially covered, one branch is missing
return false;

Check warning on line 96 in src/main/java/hudson/plugins/ec2/SSHData.java

ci.jenkins.io / Code Coverage

Not covered line

Line 96 is not covered by tests
if (StringUtils.isEmpty(slaveCommandPrefix)) {

Check warning on line 97 in src/main/java/hudson/plugins/ec2/SSHData.java

ci.jenkins.io / Code Coverage

Partially covered line

Line 97 is only partially covered, one branch is missing
if (!StringUtils.isEmpty(other.slaveCommandPrefix))

Check warning on line 98 in src/main/java/hudson/plugins/ec2/SSHData.java

ci.jenkins.io / Code Coverage

Partially covered line

Line 98 is only partially covered, one branch is missing
return false;
} else if (!slaveCommandPrefix.equals(other.slaveCommandPrefix))
return false;

Check warning on line 101 in src/main/java/hudson/plugins/ec2/SSHData.java

ci.jenkins.io / Code Coverage

Not covered lines

Lines 99-101 are not covered by tests
if (StringUtils.isEmpty(slaveCommandSuffix)) {

Check warning on line 102 in src/main/java/hudson/plugins/ec2/SSHData.java

ci.jenkins.io / Code Coverage

Partially covered line

Line 102 is only partially covered, one branch is missing
if (!StringUtils.isEmpty(other.slaveCommandSuffix))

Check warning on line 103 in src/main/java/hudson/plugins/ec2/SSHData.java

ci.jenkins.io / Code Coverage

Partially covered line

Line 103 is only partially covered, one branch is missing
return false;
} else if (!slaveCommandSuffix.equals(other.slaveCommandSuffix))
return false;

Check warning on line 106 in src/main/java/hudson/plugins/ec2/SSHData.java

ci.jenkins.io / Code Coverage

Not covered lines

Lines 104-106 are not covered by tests
if (StringUtils.isEmpty(sshPort)) {

Check warning on line 107 in src/main/java/hudson/plugins/ec2/SSHData.java

ci.jenkins.io / Code Coverage

Partially covered line

Line 107 is only partially covered, one branch is missing
if (!StringUtils.isEmpty(other.sshPort))
return false;

Check warning on line 109 in src/main/java/hudson/plugins/ec2/SSHData.java

ci.jenkins.io / Code Coverage

Not covered lines

Lines 108-109 are not covered by tests
} else if (!sshPort.equals(other.sshPort))

Check warning on line 110 in src/main/java/hudson/plugins/ec2/SSHData.java

ci.jenkins.io / Code Coverage

Partially covered line

Line 110 is only partially covered, one branch is missing
return false;

Check warning on line 111 in src/main/java/hudson/plugins/ec2/SSHData.java

ci.jenkins.io / Code Coverage

Not covered line

Line 111 is not covered by tests
if (bootDelay == null) {

Check warning on line 112 in src/main/java/hudson/plugins/ec2/SSHData.java

ci.jenkins.io / Code Coverage

Partially covered line

Line 112 is only partially covered, one branch is missing
if (other.bootDelay != null)
return false;

Check warning on line 114 in src/main/java/hudson/plugins/ec2/SSHData.java

ci.jenkins.io / Code Coverage

Not covered lines

Lines 113-114 are not covered by tests
} else if (!bootDelay.equals(other.bootDelay))

Check warning on line 115 in src/main/java/hudson/plugins/ec2/SSHData.java

ci.jenkins.io / Code Coverage

Partially covered line

Line 115 is only partially covered, one branch is missing
return false;

Check warning on line 116 in src/main/java/hudson/plugins/ec2/SSHData.java

ci.jenkins.io / Code Coverage

Not covered line

Line 116 is not covered by tests
return true;
}
}
106 changes: 4 additions & 102 deletions src/main/java/hudson/plugins/ec2/UnixData.java
Original file line number Diff line number Diff line change
@@ -1,126 +1,28 @@
package hudson.plugins.ec2;

import edu.umd.cs.findbugs.annotations.NonNull;
import hudson.Extension;
import hudson.model.Descriptor;
import jenkins.model.Jenkins;
import org.apache.commons.lang.StringUtils;
import org.kohsuke.stapler.DataBoundConstructor;

public class UnixData extends AMITypeData {
private final String rootCommandPrefix;
private final String slaveCommandPrefix;
private final String slaveCommandSuffix;
private final String sshPort;
private final String bootDelay;
public class UnixData extends SSHData {

@DataBoundConstructor
public UnixData(String rootCommandPrefix, String slaveCommandPrefix, String slaveCommandSuffix, String sshPort, String bootDelay) {
this.rootCommandPrefix = rootCommandPrefix;
this.slaveCommandPrefix = slaveCommandPrefix;
this.slaveCommandSuffix = slaveCommandSuffix;
this.sshPort = sshPort;
this.bootDelay = bootDelay;

this.readResolve();
}

protected Object readResolve() {
Jenkins j = Jenkins.getInstanceOrNull();
if (j != null) {
j.checkPermission(Jenkins.ADMINISTER);
}
return this;
}

@Override
public boolean isWindows() {
return false;
super(rootCommandPrefix, slaveCommandPrefix, slaveCommandSuffix, sshPort, bootDelay);
}

@Override
public boolean isUnix() {
return true;
}

@Override
public boolean isMac() {
return false;
}

@Extension
public static class DescriptorImpl extends Descriptor<AMITypeData> {
@Override
@NonNull
public String getDisplayName() {
return "unix";
}
}

public String getRootCommandPrefix() {
return rootCommandPrefix;
}

public String getSlaveCommandPrefix() {
return slaveCommandPrefix;
}

public String getSlaveCommandSuffix() {
return slaveCommandSuffix;
}

public String getSshPort() {
return sshPort == null || sshPort.isEmpty() ? "22" : sshPort;
}

public String getBootDelay() {
return bootDelay;
}

@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((rootCommandPrefix == null) ? 0 : rootCommandPrefix.hashCode());
result = prime * result + ((slaveCommandPrefix == null) ? 0 : slaveCommandPrefix.hashCode());
result = prime * result + ((slaveCommandSuffix == null) ? 0 : slaveCommandSuffix.hashCode());
result = prime * result + ((sshPort == null) ? 0 : sshPort.hashCode());
result = prime * result + ((bootDelay == null) ? 0 : bootDelay.hashCode());
return result;
}

@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (this.getClass() != obj.getClass())
return false;
final UnixData other = (UnixData) obj;
if (StringUtils.isEmpty(rootCommandPrefix)) {
if (!StringUtils.isEmpty(other.rootCommandPrefix))
return false;
} else if (!rootCommandPrefix.equals(other.rootCommandPrefix))
return false;
if (StringUtils.isEmpty(slaveCommandPrefix)) {
if (!StringUtils.isEmpty(other.slaveCommandPrefix))
return false;
} else if (!slaveCommandPrefix.equals(other.slaveCommandPrefix))
return false;
if (StringUtils.isEmpty(slaveCommandSuffix)) {
if (!StringUtils.isEmpty(other.slaveCommandSuffix))
return false;
} else if (!slaveCommandSuffix.equals(other.slaveCommandSuffix))
return false;
if (StringUtils.isEmpty(sshPort)) {
if (!StringUtils.isEmpty(other.sshPort))
return false;
} else if (!sshPort.equals(other.sshPort))
return false;
if (bootDelay == null) {
if (other.bootDelay != null)
return false;
} else if (!bootDelay.equals(other.bootDelay))
return false;
return true;
}
}
5 changes: 5 additions & 0 deletions src/main/java/hudson/plugins/ec2/WindowsData.java
Original file line number Diff line number Diff line change
@@ -40,6 +40,11 @@
this(password, useHTTPS, bootDelay, false);
}

@Override
public boolean isWindowsSSH() {
return false;

Check warning on line 45 in src/main/java/hudson/plugins/ec2/WindowsData.java

ci.jenkins.io / Code Coverage

Not covered line

Line 45 is not covered by tests
}

@Override
public boolean isWindows() {
return true;
30 changes: 30 additions & 0 deletions src/main/java/hudson/plugins/ec2/WindowsSSHData.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package hudson.plugins.ec2;

import edu.umd.cs.findbugs.annotations.NonNull;
import hudson.Extension;
import hudson.model.Descriptor;
import org.kohsuke.stapler.DataBoundConstructor;


public class WindowsSSHData extends SSHData {

@DataBoundConstructor
public WindowsSSHData(String rootCommandPrefix, String slaveCommandPrefix, String slaveCommandSuffix, String sshPort, String bootDelay) {
super(rootCommandPrefix, slaveCommandPrefix, slaveCommandSuffix, sshPort, bootDelay);
}

@Override
public boolean isWindowsSSH() {
return true;

Check warning on line 18 in src/main/java/hudson/plugins/ec2/WindowsSSHData.java

ci.jenkins.io / Code Coverage

Not covered lines

Lines 13-18 are not covered by tests
}


@Extension
public static class DescriptorImpl extends Descriptor<AMITypeData> {
@Override
@NonNull
public String getDisplayName() {
return "windows-ssh";
}
}
}
465 changes: 35 additions & 430 deletions src/main/java/hudson/plugins/ec2/ssh/EC2MacLauncher.java

Large diffs are not rendered by default.

536 changes: 536 additions & 0 deletions src/main/java/hudson/plugins/ec2/ssh/EC2SSHLauncher.java

Large diffs are not rendered by default.

496 changes: 7 additions & 489 deletions src/main/java/hudson/plugins/ec2/ssh/EC2UnixLauncher.java

Large diffs are not rendered by default.

67 changes: 67 additions & 0 deletions src/main/java/hudson/plugins/ec2/ssh/EC2WindowsSSHLauncher.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
package hudson.plugins.ec2.ssh;

import com.amazonaws.AmazonClientException;
import com.trilead.ssh2.Connection;
import com.trilead.ssh2.SCPClient;
import com.trilead.ssh2.Session;
import hudson.model.TaskListener;
import hudson.plugins.ec2.EC2Computer;
import org.apache.commons.io.IOUtils;

import java.io.IOException;
import java.io.PrintStream;

public class EC2WindowsSSHLauncher extends EC2SSHLauncher {

@Override
protected void runAmiTypeSpecificLaunchScript(EC2Computer computer, String javaPath, Connection conn, PrintStream logger, TaskListener listener) throws IOException, AmazonClientException, InterruptedException {
executeRemote(computer, conn, javaPath + " -fullversion", "choco install temurin17 -y;", logger, listener);
}

@Override
protected boolean runInitScript(EC2Computer computer, TaskListener listener, String initScript, Connection conn, PrintStream logger, SCPClient scp, String tmpDir) throws IOException, InterruptedException {
if (initScript != null && !initScript.trim().isEmpty()
&& conn.exec("test -e " + tmpDir + " init.bat", logger) != 0) {
logInfo(computer, listener, "Executing init script");
scp.put(initScript.getBytes("UTF-8"), "init.bat", tmpDir, "0700");
Session sess = conn.openSession();
sess.requestDumbPTY(); // so that the remote side bundles stdout
// and stderr
sess.execCommand(buildUpCommand(computer, tmpDir + "/init.bat"));

sess.getStdin().close(); // nothing to write here
sess.getStderr().close(); // we are not supposed to get anything
// from stderr
IOUtils.copy(sess.getStdout(), logger);

int exitStatus = waitCompletion(sess);
if (exitStatus != 0) {
logWarning(computer, listener, "init script failed: exit code=" + exitStatus);
return true;
}
sess.close();

logInfo(computer, listener, "Creating " + tmpDir + ".jenkins-init");

// Needs a tty to run sudo.
sess = conn.openSession();
sess.requestDumbPTY(); // so that the remote side bundles stdout
// and stderr
sess.execCommand(buildUpCommand(computer, "Set-Content -Path " + tmpDir + ".jenkins-init -Value $null"));

sess.getStdin().close(); // nothing to write here
sess.getStderr().close(); // we are not supposed to get anything
// from stderr
IOUtils.copy(sess.getStdout(), logger);

exitStatus = waitCompletion(sess);
if (exitStatus != 0) {
logWarning(computer, listener, "init script failed: exit code=" + exitStatus);
return true;
}
sess.close();
}
return false;

Check warning on line 64 in src/main/java/hudson/plugins/ec2/ssh/EC2WindowsSSHLauncher.java

ci.jenkins.io / Code Coverage

Not covered lines

Lines 14-64 are not covered by tests
}

}
42 changes: 42 additions & 0 deletions src/main/resources/hudson/plugins/ec2/WindowsSSHData/config.jelly
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
<!--
The MIT License
Copyright (c) 2010, InfraDNA, Inc.
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.
-->
<?jelly escape-by-default='true'?>
<j:jelly xmlns:j="jelly:core" xmlns:st="jelly:stapler" xmlns:d="jelly:define" xmlns:l="/lib/layout"
xmlns:t="/lib/hudson" xmlns:f="/lib/form">
<f:entry title="${%Root command prefix}" field="rootCommandPrefix">
<f:textbox/>
</f:entry>
<f:entry title="${%Agent command prefix}" field="slaveCommandPrefix">
<f:textbox/>
</f:entry>
<f:entry title="${%Agent command suffix}" field="slaveCommandSuffix">
<f:textbox/>
</f:entry>
<f:entry title="${%Remote ssh port}" field="sshPort">
<f:textbox />
</f:entry>
<f:entry title="${%Boot Delay}" field="bootDelay">
<f:textbox />
</f:entry>
</j:jelly>
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
<div>
Indicate here the time in seconds to wait for the machine to be ready once the plugin detects SSH is available.
Unfortunately, on Windows during the boot, the SSH service might be started, and then several minutes after will be
restarted. If this restart happens during the slave provisioning, Windows will prevent subsequent SSH connections and
the slave will not be correctly provisioned.
</div>