Skip to content
Permalink
Browse files

[JENKINS-39237] - Enable the automatic agent download during Windows …

…Service Setup (#6)

* [JENKINS-39237] - Enable the automatic agent download during Windows Service Setup

This change enables download of slave.jar on startup when it is possible.
WindowsSlaveInstaller internal API has been slightly tweaked for testing purposes.

* [JENKINS-39237] - Fix FindBugs

*  [JENKINS-39237] - Disable the download by default

* [JENKINS-39237] - Enable download for HTTPS only
  • Loading branch information...
oleg-nenashev committed Mar 3, 2017
1 parent c28d0ad commit 33bf7bb673a70fb8b5c63ac35ba6229e88ee6aa8
@@ -24,13 +24,27 @@
import java.io.IOException;

import static hudson.util.jna.SHELLEXECUTEINFO.*;
import java.net.URL;
import java.util.Arrays;
import java.util.Collections;
import java.util.Map;
import java.util.TreeMap;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.annotation.CheckForNull;
import javax.annotation.Nonnull;
import org.kohsuke.accmod.Restricted;
import org.kohsuke.accmod.restrictions.NoExternalUse;

/**
* Installs agent as a Windows service.
* The installer uses <a href="https://github.com/kohsuke/winsw">WinSW</a> as a service wrapper.
* @author Kohsuke Kawaguchi
*/
public class WindowsSlaveInstaller extends SlaveInstaller {

private final static Logger LOGGER = Logger.getLogger(WindowsSlaveInstaller.class.getName());

public WindowsSlaveInstaller() {
}

@@ -77,17 +91,23 @@ static int runElevated(File agentExe, String command, TaskListener out, File pwd
}
}


@Override @SuppressFBWarnings("DM_EXIT")
@Override
public void install(LaunchConfiguration params, Prompter prompter) throws InstallationException, IOException, InterruptedException {
if(!DotNet.isInstalled(2,0))
install(params, prompter, false);
}

@SuppressFBWarnings(value = "DM_EXIT", justification = "Legacy design, but as designed")
/*package*/ void install(LaunchConfiguration params, Prompter prompter, boolean mock) throws InstallationException, IOException, InterruptedException {
if(!mock && !DotNet.isInstalled(2,0)) {
throw new InstallationException(Messages.WindowsSlaveInstaller_DotNetRequired());

}

final File dir = params.getStorage().getAbsoluteFile();
if (!dir.exists())
if (!dir.mkdirs()){
throw new InstallationException(Messages.WindowsSlaveInstaller_RootFsCreationFailed(dir));
}
params.getLatestJarURL();

final File agentExe = new File(dir, "jenkins-slave.exe");
FileUtils.copyURLToFile(WindowsSlaveInstaller.class.getResource("jenkins-slave.exe"), agentExe);
@@ -98,14 +118,21 @@ public void install(LaunchConfiguration params, Prompter prompter) throws Instal
// write out the descriptor
String xml = generateSlaveXml(
generateServiceId(dir.getPath()),
System.getProperty("java.home")+"\\bin\\java.exe", null, params.buildRunnerArguments().toStringWithQuote());
System.getProperty("java.home")+"\\bin\\java.exe", null,
params.buildRunnerArguments().toStringWithQuote(),
Arrays.asList(new MacroValueProvider[] {new AgentURLMacroProvider(params)}));
FileUtils.writeStringToFile(new File(dir, "jenkins-slave.xml"),xml,"UTF-8");

// copy slave.jar
File dstAgentJar = new File(dir,"slave.jar").getCanonicalFile();
if(!dstAgentJar.exists()) // perhaps slave.jar is already there?
FileUtils.copyFile(params.getJarFile(), dstAgentJar);

if (mock) {
// If the installation is mocked, do not really try to install it
return;
}

// install as a service
ByteArrayOutputStream baos = new ByteArrayOutputStream();
StreamTaskListener task = new StreamTaskListener(baos);
@@ -133,22 +160,110 @@ public void run() {
}
}
});

// TODO: FindBugs: Move to the outer installation logic?
System.exit(0);
}

public static String generateServiceId(String slaveRoot) throws IOException {
return "jenkinsslave-"+slaveRoot.replace(':','_').replace('\\','_').replace('/','_');
}

/**
* @deprecated Use {@link #generateSlaveXml(java.lang.String, java.lang.String, java.lang.String, java.lang.String, java.util.Map)}
*/
@Deprecated
@Restricted(NoExternalUse.class)
public static String generateSlaveXml(String id, String java, String vmargs, String args) throws IOException {
return generateSlaveXml(id, java, vmargs, args, Collections.<String, String>emptyMap());
}

/**
* Generates WinSW configuration for the agent.
* @param id Service Id
* @param java Path to Java
* @param vmargs JVM args arguments to be passed
* @param args slave.jar arguments to be passed
* @param extraMacroValues Additional macro values to be injected.
* @return Generated WinSW configuration file.
* @throws IOException The file cannot be generated
* @since TODO
*/
@Restricted(NoExternalUse.class)
public static String generateSlaveXml(String id, String java, String vmargs, String args, @Nonnull Map<String, String> extraMacroValues) throws IOException {
// Just a legacy behavior for the obsolete installer
String xml = IOUtils.toString(WindowsSlaveInstaller.class.getResourceAsStream("jenkins-slave.xml"), "UTF-8");
xml = xml.replace("@ID@", id);
xml = xml.replace("@JAVA@", java);
xml = xml.replace("@VMARGS@", StringUtils.defaultString(vmargs));
xml = xml.replace("@ARGS@", args);
xml = xml.replace("\n","\r\n");

for (Map.Entry<String, String> entry : extraMacroValues.entrySet()) {
xml = xml.replace("@" + entry.getKey() + "@", entry.getValue());
}
return xml;
}

/*package*/ static String generateSlaveXml(String id, String java, @CheckForNull String vmargs,
@Nonnull String args, @Nonnull Iterable<MacroValueProvider> providers
) throws IOException {
Map<String, String> macroValues = new TreeMap<>();
for (MacroValueProvider provider : providers) {
//TODO: fail in the case of duplicated entries?
macroValues.putAll(provider.getMacroValues());
}

return generateSlaveXml(id, java, vmargs, args, macroValues);
}

private static final long serialVersionUID = 1L;

/**
* Macro provider implementation for the internal use.
*/
@Restricted(NoExternalUse.class)
/*package*/ static abstract class MacroValueProvider {

@Nonnull
public abstract Map<String, String> getMacroValues();
}

/*package*/ static class AgentURLMacroProvider extends MacroValueProvider {

@Nonnull
private final LaunchConfiguration launchConfiguration;

public AgentURLMacroProvider(@Nonnull LaunchConfiguration launchConfig) {
this.launchConfiguration = launchConfig;
}

@Override
public Map<String, String> getMacroValues() {
Map<String, String> res = new TreeMap<>();
URL remotingURL = null;
try {
remotingURL = launchConfiguration.getLatestJarURL();
} catch (IOException ex) {
LOGGER.log(Level.SEVERE, "Failed to retrieve the latest Remoting JAR URL. Auto-download will be disabled", ex);
}

res.put("AGENT_DOWNLOAD_URL", generateDownloadMacroValue(remotingURL));
return res;
}

@Nonnull
/**package*/ static String generateDownloadMacroValue(@CheckForNull URL remotingURL) {
String macroValue;
if (remotingURL != null) {
macroValue = "<download from=\"" + remotingURL.toString() + "\" to=\"%BASE%\\slave.jar\"/>";
if (!"https".equals(remotingURL.getProtocol())) {
macroValue = "<!-- " + macroValue + " -->";
}
} else {
macroValue = "<!-- <download from=\"TODO:jarFile\" to=\"%BASE%\\slave.jar\"/> -->";
}
return macroValue;
}
}
}
@@ -51,6 +51,14 @@ THE SOFTWARE.

<onfailure action="restart" />

<!--
If uncommented, download the Remoting version provided by the Jenkins master.
Enabling for HTTP implies security risks (e.g. replacement of JAR via DNS poisoning). Use on your own risk.
NOTE: This option may fail to work correctly (e.g. if Jenkins is located behind HTTPS with untrusted certificate).
In such case the old agent version will be used; you can replace slave.jar manually or to specify another download URL.
-->
@AGENT_DOWNLOAD_URL@

<!--
In the case WinSW gets terminated and leaks the process, we want to abort
these runaway JAR processes on startup to prevent "Slave is already connected errors" (JENKINS-28492).
@@ -0,0 +1,85 @@
/**
The MIT License
Copyright (c) 2017 CloudBees, 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.
*/
package org.jenkinsci.modules.windows_slave_installer;

import hudson.remoting.Channel;
import hudson.remoting.Which;
import hudson.util.ArgumentListBuilder;
import org.jenkinsci.modules.slave_installer.LaunchConfiguration;

import java.io.File;
import java.io.IOException;
import java.net.URL;
import javax.annotation.CheckForNull;

//TODO: consider moving the class to slave_installer module
/**
* Mock Launch Configuration for testing purposes.
* @author Oleg Nenashev
*/
/*package*/ class MockLaunchConfiguration extends LaunchConfiguration {

private final URL jarUrl;
private final URL jnlpUrl;
private final File storage;
private final File jarFile;
private final String jnlpMac;

MockLaunchConfiguration(URL jarUrl, URL jnlpUrl, File storage, File jarFile) {
this(jarUrl, jnlpUrl, storage, jarFile, null);
}

MockLaunchConfiguration(URL jarUrl, URL jnlpUrl, File storage, File jarFile, @CheckForNull String jnlpMac) {
this.jarUrl = jarUrl;
this.jnlpUrl = jnlpUrl;
this.storage = storage;
this.jnlpMac = jnlpMac;
this.jarFile = jarFile;
}

@Override
public File getStorage() throws IOException {
return storage;
}

@Override
public File getJarFile() throws IOException {
return jarFile;
}

@Override
public URL getLatestJarURL() throws IOException {
return jarUrl;
}

@Override
public ArgumentListBuilder buildRunnerArguments() {
ArgumentListBuilder args = new ArgumentListBuilder();
args.add("-jnlpUrl").add(jnlpUrl);
if (jnlpMac != null) {
args.add("-secret").add(jnlpMac);
}
return args;
}
}
Oops, something went wrong.

0 comments on commit 33bf7bb

Please sign in to comment.
You can’t perform that action at this time.