package hudson.plugins.ec2.win; import hudson.model.Descriptor; import hudson.model.TaskListener; import hudson.plugins.ec2.EC2Computer; import hudson.plugins.ec2.EC2ComputerLauncher; import hudson.plugins.ec2.win.winrm.WindowsProcess; import hudson.remoting.Channel; import hudson.remoting.Channel.Listener; import hudson.slaves.ComputerLauncher; import java.io.IOException; import java.io.OutputStream; import java.io.PrintStream; import java.nio.charset.StandardCharsets; import java.util.concurrent.TimeUnit; import jenkins.model.Jenkins; import org.apache.commons.io.IOUtils; import com.amazonaws.AmazonClientException; import com.amazonaws.services.ec2.model.Instance; public class EC2WindowsLauncher extends EC2ComputerLauncher { private static final String SLAVE_JAR = "slave.jar"; final long sleepBetweenAttemps = TimeUnit.SECONDS.toMillis(10); @Override protected void launch(EC2Computer computer, TaskListener listener, Instance inst) throws IOException, AmazonClientException, InterruptedException { final PrintStream logger = listener.getLogger(); final WinConnection connection = connectToWinRM(computer, logger); try { String initScript = computer.getNode().initScript; String tmpDir = (computer.getNode().tmpDir != null && !computer.getNode().tmpDir.equals("") ? computer.getNode().tmpDir : "C:\\Windows\\Temp\\"); logger.println("Creating tmp directory if it does not exist"); connection.execute("if not exist " + tmpDir + " mkdir " + tmpDir); if (initScript != null && initScript.trim().length() > 0 && !connection.exists(tmpDir + ".jenkins-init")) { logger.println("Executing init script"); OutputStream init = connection.putFile(tmpDir + "init.bat"); init.write(initScript.getBytes("utf-8")); WindowsProcess initProcess = connection.execute("cmd /c " + tmpDir + "init.bat"); IOUtils.copy(initProcess.getStdout(), logger); int exitStatus = initProcess.waitFor(); if (exitStatus != 0) { logger.println("init script failed: exit code=" + exitStatus); return; } OutputStream initGuard = connection.putFile(tmpDir + ".jenkins-init"); initGuard.write("init ran".getBytes(StandardCharsets.UTF_8)); logger.println("init script ran successfully"); } OutputStream slaveJar = connection.putFile(tmpDir + SLAVE_JAR); slaveJar.write(Jenkins.getInstance().getJnlpJars(SLAVE_JAR).readFully()); logger.println("slave.jar sent remotely. Bootstrapping it"); final String jvmopts = computer.getNode().jvmopts; final WindowsProcess process = connection.execute("java " + (jvmopts != null ? jvmopts : "") + " -jar " + tmpDir + SLAVE_JAR, 86400); computer.setChannel(process.getStdout(), process.getStdin(), logger, new Listener() { @Override public void onClosed(Channel channel, IOException cause) { process.destroy(); connection.close(); } }); } catch (Throwable ioe) { logger.println("Ouch:"); ioe.printStackTrace(logger); } finally { connection.close(); } } private WinConnection connectToWinRM(EC2Computer computer, PrintStream logger) throws AmazonClientException, InterruptedException { final long minTimeout = 3000; long timeout = computer.getNode().getLaunchTimeoutInMillis(); // timeout is less than 0 when jenkins is booting up. if (timeout < minTimeout) { timeout = minTimeout; } final long startTime = System.currentTimeMillis(); logger.println(computer.getNode().getDisplayName() + " booted at " + computer.getNode().getCreatedTime()); boolean alreadyBooted = (startTime - computer.getNode().getCreatedTime()) > TimeUnit.MINUTES.toMillis(3); while (true) { try { long waitTime = System.currentTimeMillis() - startTime; if (waitTime > timeout) { throw new AmazonClientException("Timed out after " + (waitTime / 1000) + " seconds of waiting for winrm to be connected"); } Instance instance = computer.updateInstanceDescription(); String ip, host; if (computer.getNode().usePrivateDnsName) { host = instance.getPrivateDnsName(); ip = instance.getPrivateIpAddress(); // SmbFile doesn't // quite work with // hostnames } else { host = instance.getPublicDnsName(); if (host == null || host.equals("")) { host = instance.getPrivateDnsName(); ip = instance.getPrivateIpAddress(); // SmbFile doesn't // quite work with // hostnames } else { host = instance.getPublicDnsName(); ip = instance.getPublicIpAddress(); // SmbFile doesn't // quite work with // hostnames } } if ("0.0.0.0".equals(host)) { logger.println("Invalid host 0.0.0.0, your host is most likely waiting for an ip address."); throw new IOException("goto sleep"); } logger.println("Connecting to " + host + "(" + ip + ") with WinRM as " + computer.getNode().remoteAdmin); WinConnection connection = new WinConnection(ip, computer.getNode().remoteAdmin, computer.getNode().getAdminPassword().getPlainText()); connection.setUseHTTPS(computer.getNode().isUseHTTPS()); if (!connection.ping()) { logger.println("Waiting for WinRM to come up. Sleeping 10s."); Thread.sleep(sleepBetweenAttemps); continue; } if (!alreadyBooted || computer.getNode().stopOnTerminate) { logger.println("WinRM service responded. Waiting for WinRM service to stabilize on " + computer.getNode().getDisplayName()); Thread.sleep(computer.getNode().getBootDelay()); alreadyBooted = true; logger.println("WinRM should now be ok on " + computer.getNode().getDisplayName()); if (!connection.ping()) { logger.println("WinRM not yet up. Sleeping 10s."); Thread.sleep(sleepBetweenAttemps); continue; } } logger.println("Connected with WinRM."); return connection; // successfully connected } catch (IOException e) { logger.println("Waiting for WinRM to come up. Sleeping 10s."); Thread.sleep(sleepBetweenAttemps); } } } @Override public Descriptor<ComputerLauncher> getDescriptor() { throw new UnsupportedOperationException(); } }