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

Commit

Permalink
Browse files Browse the repository at this point in the history
Merge pull request #10 from oleg-nenashev/feature/JENKINS-42745
[JENKINS-42745] - Windows Agent Installer should generate valid jenkins-slave.xml for old APIs
  • Loading branch information
oleg-nenashev committed Apr 25, 2017
2 parents 981a038 + 89cffce commit a3b83c8
Show file tree
Hide file tree
Showing 2 changed files with 119 additions and 12 deletions.
Expand Up @@ -26,9 +26,14 @@
import static hudson.util.jna.SHELLEXECUTEINFO.*;
import java.net.URL;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;
import java.util.TreeSet;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.annotation.CheckForNull;
Expand All @@ -45,6 +50,13 @@ public class WindowsSlaveInstaller extends SlaveInstaller {

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

/**
* Lists the new required macros, which has been added to the pattern since 1.6.
* All of these macros are expected to have a default value.
*/
private static final Set<String> ADDITIONAL_REQUIRED_MACROS = new TreeSet<>(
Arrays.asList(AgentURLMacroProvider.MACRO_NAME));

public WindowsSlaveInstaller() {
}

Expand Down Expand Up @@ -173,23 +185,27 @@ public static String generateServiceId(String slaveRoot) throws IOException {
* @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.
* This method takes a template from the resources and injects macro values there.
* Macro values can be contributed by {@code extraMacroValues} or by {@link MacroValueProvider}s.
* @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.
* The list of required macros is provided in {@link #ADDITIONAL_REQUIRED_MACROS}.
* If the macro value is not provided, the implementation will look up for the default value in
* available {@link MacroValueProvider}s.
*
* @return Generated WinSW configuration file.
* @throws IOException The file cannot be generated
* @throws IOException The file cannot be generated or if not all macro variables can be resolved
* @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");
Expand All @@ -199,9 +215,36 @@ public static String generateSlaveXml(String id, String java, String vmargs, Str
xml = xml.replace("@ARGS@", args);
xml = xml.replace("\n","\r\n");

for (Map.Entry<String, String> entry : extraMacroValues.entrySet()) {
// Resolve missing macros to retain compatibility with old API
Map <String, String> toResolve = new HashMap<>(extraMacroValues);
Collection<MacroValueProvider> defaultProviders = MacroValueProvider.allDefaultProviders();
for (String macroName : ADDITIONAL_REQUIRED_MACROS) {
if (!extraMacroValues.containsKey(macroName)) {
for (MacroValueProvider provider : defaultProviders) {
String defaultValue = provider.getDefaulValue(macroName);
if (defaultValue != null) {
toResolve.put(macroName, defaultValue);
break;
}
}
}
}

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

if (xml.contains("@")) {
Set<String> unresolvedMacros = new HashSet<>();
for (String macroName : ADDITIONAL_REQUIRED_MACROS) {
if (xml.contains("@" + macroName + "@")) {
unresolvedMacros.add(macroName);
}
}
// If there is any unknown macro, it will be caught by tests.
throw new IOException("Unresolved macros in the XML file: " + String.join(",", unresolvedMacros));
}

return xml;
}

Expand All @@ -221,34 +264,53 @@ public static String generateSlaveXml(String id, String java, String vmargs, Str

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

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

@Nonnull
public abstract Set<String> getMacroNames();

@CheckForNull
public abstract String getDefaulValue(@Nonnull String macroName);

static final Collection<MacroValueProvider> allDefaultProviders() {
return Arrays.<MacroValueProvider>asList(new AgentURLMacroProvider(null));
}
}

/*package*/ static class AgentURLMacroProvider extends MacroValueProvider {

@Nonnull
static final String MACRO_NAME = "AGENT_DOWNLOAD_URL";
static final String DEFAULT_DISABLED_VALUE = "<!-- <download from=\"TODO:jarFile\" to=\"%BASE%\\slave.jar\"/> -->";

private static final Set<String> MACRO_NAMES = new TreeSet<>(Arrays.asList(MACRO_NAME));

@CheckForNull
private final LaunchConfiguration launchConfiguration;

public AgentURLMacroProvider(@Nonnull LaunchConfiguration launchConfig) {
public AgentURLMacroProvider(@CheckForNull 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);
if (launchConfiguration != 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));
res.put(MACRO_NAME, generateDownloadMacroValue(remotingURL));
return res;
}

Expand All @@ -261,9 +323,23 @@ public Map<String, String> getMacroValues() {
macroValue = "<!-- " + macroValue + " -->";
}
} else {
macroValue = "<!-- <download from=\"TODO:jarFile\" to=\"%BASE%\\slave.jar\"/> -->";
macroValue = DEFAULT_DISABLED_VALUE;
}
return macroValue;
}

@Override
public Set<String> getMacroNames() {
return Collections.unmodifiableSet(MACRO_NAMES);
}

@Override
public String getDefaulValue(String macroName) {
if (MACRO_NAMES.contains(macroName)) {
// Fine since we keep one macro
return DEFAULT_DISABLED_VALUE;
}
return null;
}
}
}
Expand Up @@ -29,12 +29,15 @@ of this software and associated documentation files (the "Software"), to deal
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.HashMap;
import java.util.Map;
import javax.annotation.CheckForNull;
import org.apache.commons.io.IOUtils;
import static org.hamcrest.CoreMatchers.*;
import org.jenkinsci.modules.slave_installer.InstallationException;
import org.jenkinsci.modules.slave_installer.LaunchConfiguration;
import org.jenkinsci.modules.slave_installer.Prompter;
import org.jenkinsci.modules.windows_slave_installer.WindowsSlaveInstaller.AgentURLMacroProvider;
import org.junit.Assert;
import static org.junit.Assert.assertThat;
import org.junit.Before;
Expand Down Expand Up @@ -79,6 +82,34 @@ public String promptPassword(String question) throws InterruptedException {
};
}

@Test
@Issue("JENKINS-42745")
public void shouldGenerateValidConfigWithOldAPI() throws Exception {
@SuppressWarnings("deprecation")
String xml = WindowsSlaveInstaller.generateSlaveXml("serviceid", "myjava", "", "");
assertThat("There is unresolved macro", xml, not(containsString("@")));
}

@Test
@Issue("JENKINS-42745")
public void shouldThrowUnresolveMacro() throws Exception {
// Create a nested macro definition, which won't be resolved
Map<String,String> macroValues = new HashMap<>();
macroValues.put(AgentURLMacroProvider.MACRO_NAME, "Depends on @" + AgentURLMacroProvider.MACRO_NAME + "@");

// Try to resolve
try {
String xml = WindowsSlaveInstaller.generateSlaveXml("serviceid", "myjava", "", "", macroValues);
} catch (IOException ex) {
assertThat("Exception message does not mention unresolved macros",
ex.getMessage(), containsString("Unresolved macros in the XML file: "));
assertThat("Exception message does not reference the macro name",
ex.getMessage(), containsString(AgentURLMacroProvider.MACRO_NAME));
return;
}
Assert.fail("Expected the Unresolved macro exception");
}

@Test
@Issue("JENKINS-39237")
public void shouldGenerateConfigWithValidDownloadLink() throws InstallationException, IOException, InterruptedException {
Expand Down

0 comments on commit a3b83c8

Please sign in to comment.