This repository has been archived by the owner on May 14, 2022. It is now read-only.
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
[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
1 parent
c28d0ad
commit 33bf7bb
Showing
4 changed files
with
380 additions
and
5 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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)) { | ||
This comment has been minimized.
Sorry, something went wrong.
This comment has been minimized.
Sorry, something went wrong.
oleg-nenashev
Author
Member
|
||
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; | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
85 changes: 85 additions & 0 deletions
85
src/test/java/org/jenkinsci/modules/windows_slave_installer/MockLaunchConfiguration.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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.
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 adotnet4.7
choco package but refused to install thedotnet3.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/