Skip to content
This repository has been archived by the owner on May 14, 2022. It is now read-only.

Commit

Permalink
[JENKINS-39237] - Enable the automatic agent download during Windows …
Browse files Browse the repository at this point in the history
…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 33bf7bb
Show file tree
Hide file tree
Showing 4 changed files with 380 additions and 5 deletions.
Expand Up @@ -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() {
}

Expand Down Expand Up @@ -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)) {

This comment has been minimized.

Copy link
@ilatypov

ilatypov May 5, 2020

Thank you for the great work, Oleg Nenashev. Can this be updated to allow latest DotNet Framework versions (e.g., by adding && !DotNet.isInstalled(4,0))? An Azure-provided Windows 2016 Datacenter could get a dotnet4.7 choco package but refused to install the dotnet3.5 one because of incorrect Microsoft fixes applies earlier,

https://ardamis.com/2015/01/10/microsoft-net-framework-3-5-fails-to-install-on-windows-8-1-enterprise-with-update-x64/

This comment has been minimized.

Copy link
@oleg-nenashev

oleg-nenashev May 5, 2020

Author Member

@ilatypov Agreed. I hope to cleanup all the version checks once we conclude the Windows support policy discussion: https://groups.google.com/forum/#!msg/jenkinsci-dev/oK8pBCzPPpo/1Ue1DI4TAQAJ

This comment has been minimized.

Copy link
@ilatypov

ilatypov May 6, 2020

I worked this around by manually installing the service. For others,

  • I downloaded the latest WinSW.NET4.exe from https://github.com/winsw/winsw/releases
  • renamed it to jenkins_agent.exe
  • copied and adjusted the jenkins_agent.xml and jenkins_agent.exe.config files
  • ran jenkins_agent install followed by net start jenkins_agent.
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);
Expand All @@ -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);
Expand Down Expand Up @@ -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;
}
}
}
Expand Up @@ -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).
Expand Down
@@ -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;
}
}

0 comments on commit 33bf7bb

Please sign in to comment.