[java-identity-provider] 02/02: IDP-1683 Substantial rewrite of Plugin Installer
Rod Widdowson
rdw at steadingsoftware.com
Sat Oct 10 12:55:45 UTC 2020
This is an automated email from the git hooks/post-receive script.
rdw pushed a commit to branch main
in repository java-identity-provider.
View the commit online:
http://git.shibboleth.net/view/?p=java-identity-provider.git;a=commit;h=747befa40847abd385b2940fda06502c51e495c0
commit 747befa40847abd385b2940fda06502c51e495c0
Author: Rod Widdowson <rdw at steadingsoftware.com>
AuthorDate: Sat Oct 10 13:51:32 2020 +0100
IDP-1683 Substantial rewrite of Plugin Installer
https://issues.shibboleth.net/jira/browse/IDP-1683
Add transactional support for update and install.
Recast the lifetimes of the ClasspathLoaders to allow
Module enabling to work with an active ClasspathLoader.
Do the Module enable/disable thing (transactionally).
Move the Module probing to inside the transactional scope
and after the uninstall part of an upgrade, also give the
Service loader a ClasspathLoader which contains the plugins.
---
.../idp/installer/plugin/impl/PluginInstaller.java | 309 ++++++++++++++-------
.../plugin/impl/PluginInstallerSupport.java | 34 +++
.../plugin/impl/RollbackPluginInstall.java | 15 +-
.../idp/installer/plugin/impl/RollbackTester.java | 2 +-
.../src/test/resources/credentials/truststore.asc | 137 +++++++++
5 files changed, 380 insertions(+), 117 deletions(-)
diff --git a/idp-installer/src/main/java/net/shibboleth/idp/installer/plugin/impl/PluginInstaller.java b/idp-installer/src/main/java/net/shibboleth/idp/installer/plugin/impl/PluginInstaller.java
index cc6de0ffe..651d118a2 100644
--- a/idp-installer/src/main/java/net/shibboleth/idp/installer/plugin/impl/PluginInstaller.java
+++ b/idp-installer/src/main/java/net/shibboleth/idp/installer/plugin/impl/PluginInstaller.java
@@ -122,8 +122,8 @@ public final class PluginInstaller extends AbstractInitializableComponent implem
/** What to use to download things. */
private HttpClient httpClient;
- /** Files that were copied - to handle rollback. */
- @Nonnull private List<Path> copiedFiles = new ArrayList<>();
+ /** Dumping space for renamed files. */
+ @Nonnull private Path renamePath;
/** What was installed - this is setup by {@link #loadCopiedFiles()}. */
@Nullable private List<String> installedContents;
@@ -134,6 +134,12 @@ public final class PluginInstaller extends AbstractInitializableComponent implem
/** The Module Context. */
@NonnullAfterInit private ModuleContext moduleContext;
+ /** The "plugins" classpath loader. AutoClosed. */
+ private URLClassLoader installedPluginLoader;
+
+ /** The "plugin under construction" classpath loader. AutoClosed. */
+ private URLClassLoader installingPluginLoader;
+
/** The securiotyParams for the module context. */
private HttpClientSecurityParameters securityParams;
@@ -222,18 +228,21 @@ public final class PluginInstaller extends AbstractInitializableComponent implem
unpack(base, fileName);
setupPluginId();
checkSignature(base, fileName);
- getDescription();
+ setupDescriptionFromDistribution();
LOG.info("Installing Plugin {} version {}.{}.{}", pluginId,
description.getMajorVersion(),description.getMinorVersion(), description.getPatchVersion());
- checkRequiredModules();
-
final Path myWebApp = idpHome.resolve("dist").resolve("edit-webapp-" + pluginId);
- uninstallOld(myWebApp);
- installWebapp(myWebApp);
- InstallerSupport.setReadOnly(myWebApp, true);
- saveCopiedFiles();
+ try (final RollbackPluginInstall rollBack = new RollbackPluginInstall(moduleContext)) {
+ uninstallOld(rollBack, myWebApp);
+
+ checkRequiredModules();
+ installNew(rollBack, myWebApp);
+
+ saveCopiedFiles(rollBack.getFilesCopied());
+ rollBack.completed();
+ }
final BuildWar builder = new BuildWar(idpHome);
try {
@@ -266,65 +275,61 @@ public final class PluginInstaller extends AbstractInitializableComponent implem
throw new BuildException(e);
}
}
- for (final String content: getInstalledContents()) {
- final Path p = Path.of(content);
- if (!Files.exists(p)) {
- continue;
+ if (getVersionFromContents() == null) {
+ LOG.warn("Installed contents for {} not found", pluginId);
+ } else {
+ for (final String content: getInstalledContents()) {
+ final Path p = Path.of(content);
+ if (!Files.exists(p)) {
+ continue;
+ }
+ try {
+ InstallerSupport.setReadOnly(p, false);
+ Files.deleteIfExists(p);
+ } catch (final IOException e) {
+ LOG.warn("Could not delete {}, deferring the delete", content, e);
+ p.toFile().deleteOnExit();
+ }
}
+
+ final BuildWar builder = new BuildWar(idpHome);
try {
- InstallerSupport.setReadOnly(p, false);
- Files.deleteIfExists(p);
- } catch (final IOException e) {
- LOG.warn("Could not delete {}, deferring the delete", content, e);
- p.toFile().deleteOnExit();
+ builder.initialize();
+ } catch (final ComponentInitializationException e) {
+ throw new BuildException(e);
}
+ builder.execute();
+ LOG.info("Removed resources for {} from the war", pluginId);
}
-
- final BuildWar builder = new BuildWar(idpHome);
- try {
- builder.initialize();
- } catch (final ComponentInitializationException e) {
- throw new BuildException(e);
- }
- builder.execute();
- LOG.info("Removed resources for {} from the war", pluginId);
}
/** Get hold of the {@link IdPPlugin} for this plugin.
* @throws BuildException if badness is happens.
*/
- private void getDescription() throws BuildException {
- final List<URL> urls = new ArrayList<>();
- final Path libDir = distribution.resolve("edit-webapp").resolve("WEB-INF").resolve("lib");
-
- try (final DirectoryStream<Path> libDirPaths = Files.newDirectoryStream(libDir)){
- for (final Path jar : libDirPaths) {
- urls.add(jar.toUri().toURL());
- }
- try (final URLClassLoader loader = new URLClassLoader(urls.toArray(URL[]::new))){
-
- final ServiceLoader<IdPPlugin> plugins = ServiceLoader.load(IdPPlugin.class, loader);
- final Optional<IdPPlugin> first = plugins.findFirst();
- if (first.isEmpty()) {
- LOG.error("No Plugin services found in plugin distribution");
- throw new BuildException("No Plugin services found in plugin distribution");
- }
- for (final IdPPlugin plugin:plugins) {
- LOG.debug("Found Service announcing itself as {}", plugin.getPluginId() );
- if (pluginId.equals(plugin.getPluginId())) {
- description = plugin;
- return;
- }
- LOG.trace("Did not match {}", pluginId);
- }
- LOG.error("Looking in plugin distibution for a plugin called {}, but found a plugin called {}.",
- pluginId, first.get().getPluginId());
+ private void setupDescriptionFromDistribution() throws BuildException {
+ final ServiceLoader<IdPPlugin> plugins;
+ try {
+ plugins = ServiceLoader.load(IdPPlugin.class, getDistributionLoader());
+ } catch (final IOException e) {
+ LOG.error("Error loading descritpion");
+ throw new BuildException(e);
+ }
+ final Optional<IdPPlugin> first = plugins.findFirst();
+ if (first.isEmpty()) {
+ LOG.error("No Plugin services found in plugin distribution");
+ throw new BuildException("No Plugin services found in plugin distribution");
+ }
+ for (final IdPPlugin plugin:plugins) {
+ LOG.debug("Found Service announcing itself as {}", plugin.getPluginId() );
+ if (pluginId.equals(plugin.getPluginId())) {
+ description = plugin;
+ return;
}
- throw new BuildException("Could not locate PluginDescription");
- } catch (final IOException e) {
- LOG.error("Could not get description of {} from {}", pluginId, libDir, e);
- throw new BuildException(e);
- }
+ LOG.trace("Did not match {}", pluginId);
+ }
+ LOG.error("Looking in plugin distibution for a plugin called {}, but found a plugin called {}.",
+ pluginId, first.get().getPluginId());
+ throw new BuildException("Could not locate PluginDescription");
}
/** What files were installed to webapp for this plugin?
@@ -350,25 +355,25 @@ public final class PluginInstaller extends AbstractInitializableComponent implem
* @throws BuildException if any required modules are missing or disabled
*/
private void checkRequiredModules() throws BuildException {
-
- // TODO: maybe this belongs in the CLI rather than the utility class?
- // OTOH if the update process is recoverable then a failed update
- // should rollback the uninstall of the original...
-
final Set<String> requiredModules = new HashSet<>(description.getRequiredModules());
- final Iterator<IdPModule> modules = ServiceLoader.load(IdPModule.class).iterator();
- while (modules.hasNext() && !requiredModules.isEmpty()) {
- try {
- final IdPModule module = modules.next();
- if (requiredModules.contains(module.getId())) {
- if (module.isEnabled(moduleContext)) {
- requiredModules.remove(module.getId());
+ try (final URLClassLoader loader = getInstalledPluginLoader()) {
+ final Iterator<IdPModule> modules = ServiceLoader.load(IdPModule.class).iterator();
+ while (modules.hasNext() && !requiredModules.isEmpty()) {
+ try {
+ final IdPModule module = modules.next();
+ if (requiredModules.contains(module.getId())) {
+ if (module.isEnabled(moduleContext)) {
+ requiredModules.remove(module.getId());
+ }
}
+ } catch (final ServiceConfigurationError e) {
+ LOG.error("Unable to instantiate IdPModule", e);
}
- } catch (final ServiceConfigurationError e) {
- LOG.error("Unable to instantiate IdPModule", e);
}
+ } catch (final IOException e) {
+ LOG.error("Error looking for required modules");
+ throw new BuildException(e);
}
if (!requiredModules.isEmpty()) {
@@ -379,28 +384,77 @@ public final class PluginInstaller extends AbstractInitializableComponent implem
/** Copy the webapp folder from the distribution to the per plugin
* location inside dist.
+ * @param rollBack Roll Back Context
* @param myWebApp Where to put it.
* @throws BuildException if badness is detected.
*/
- private void installWebapp(final Path myWebApp) throws BuildException {
+ private void installNew(final RollbackPluginInstall rollBack, final Path myWebApp) throws BuildException {
final Path from = distribution.resolve("edit-webapp");
if (PluginInstallerSupport.detectDuplicates(from, myWebApp)) {
throw new BuildException("Install would overwrite filess");
}
- PluginInstallerSupport.copyWithLogging(from, myWebApp, copiedFiles);
+ PluginInstallerSupport.copyWithLogging(from, myWebApp, rollBack.getFilesCopied());
+ InstallerSupport.setReadOnly(myWebApp, true);
+
+ String moduleId = null;
+ try {
+ for (final IdPModule module: description.getDisableOnRemoval()) {
+ moduleId = module.getId();
+ if (!module.isEnabled(moduleContext)) {
+ module.enable(moduleContext);
+ rollBack.getModulesEnabled().add(module);
+ }
+ }
+ } catch (final ModuleException e) {
+ LOG.error("Error enabling {}", moduleId);
+ throw new BuildException(e);
+ }
}
/** Uninstall the old version of the plugin.
- * @param myWebApp where to delete */
- private void uninstallOld(final Path myWebApp) {
- InstallerSupport.setReadOnly(myWebApp, false);
- PluginInstallerSupport.deleteTree(myWebApp);
+ * @param rollback Rollback Context
+ * @param myWebApp where to delete
+ * @throws BuildException on IO or module errors */
+ private void uninstallOld(final RollbackPluginInstall rollback, final Path myWebApp) throws BuildException {
+ final IdPPlugin oldPlugin = getInstalledPlugin(pluginId);
+ if (oldPlugin == null) {
+ LOG.debug("{} not installed. No modules disabled", pluginId);
+ } else {
+ String moduleId = null;
+ try {
+ for (final IdPModule module: oldPlugin.getDisableOnRemoval()) {
+ moduleId = module.getId();
+ if (module.isEnabled(moduleContext)) {
+ module.disable(moduleContext, true);
+ rollback.getModulesDisabled().add(module);
+ }
+ }
+ } catch (final ModuleException e) {
+ LOG.error("Error disabling {}", moduleId);
+ throw new BuildException(e);
+ }
+ }
+
+ if (getVersionFromContents() == null) {
+ LOG.debug("{} not installed. files renamed", pluginId);
+ } else {
+ InstallerSupport.setReadOnly(myWebApp, false);
+ try {
+ PluginInstallerSupport.renameToTree(myWebApp, renamePath,
+ getInstalledContents(),
+ rollback.getFilesRenamedAway());
+ } catch (final IOException e) {
+ LOG.error("Error uninstalling plugin");
+ throw new BuildException(e);
+ }
+ }
}
/** Stream the copy list to a property file and empty it.
+ * @param copiedFiles The copied files
* @throws BuildException If we hit an IO exception
*/
- private void saveCopiedFiles() throws BuildException {
+ private void saveCopiedFiles(final List<Path> copiedFiles) throws BuildException {
try {
final Path parent = idpHome.resolve("dist").resolve("plugin-contents");
Files.createDirectories(parent);
@@ -416,7 +470,6 @@ public final class PluginInstaller extends AbstractInitializableComponent implem
try (final BufferedOutputStream out = new BufferedOutputStream(new FileOutputStream(outFile))) {
props.store(out, "Files Copied " + Instant.now());
}
- copiedFiles = new ArrayList<>();
} catch (final IOException e) {
LOG.error("Error saving list of copied files.", e);
throw new BuildException(e);
@@ -435,7 +488,7 @@ public final class PluginInstaller extends AbstractInitializableComponent implem
final Properties props = new Properties();
final File inFile = parent.resolve(pluginId).toFile();
if (!inFile.exists()) {
- LOG.error("Contents file for plugin {} ({}) does not exist", pluginId, inFile.getAbsolutePath());
+ LOG.debug("Contents file for plugin {} ({}) does not exist", pluginId, inFile.getAbsolutePath());
installedContents = Collections.emptyList();
return;
}
@@ -597,7 +650,7 @@ public final class PluginInstaller extends AbstractInitializableComponent implem
* @throws BuildException if badness is detected.
*/
private void setupPluginId() throws BuildException {
- final File propertyFile = distribution.resolve("bootstrap").resolve("id.property").toFile();
+ final File propertyFile = distribution.resolve("bootstrap").resolve("plugin.properties").toFile();
if (!propertyFile.exists()) {
LOG.error("Could not locate identity of plugin. "
+ "Identity file 'bootstrap/id.property' not present in plugin distribution.");
@@ -606,7 +659,7 @@ public final class PluginInstaller extends AbstractInitializableComponent implem
try (final InputStream inStream = new BufferedInputStream(new FileInputStream(propertyFile))) {
final Properties idProperties = new Properties();
idProperties.load(inStream);
- final String id = StringSupport.trimOrNull(idProperties.getProperty("pluginid"));
+ final String id = StringSupport.trimOrNull(idProperties.getProperty("plugin.id"));
if (id == null) {
LOG.error("Identity property file 'bootstrap/id.property' did not contain 'pluginid' property");
throw new BuildException("No property in ID file");
@@ -681,6 +734,53 @@ public final class PluginInstaller extends AbstractInitializableComponent implem
moduleContext = new ModuleContext(idpHome);
moduleContext.setHttpClientSecurityParameters(securityParams);
moduleContext.setHttpClient(httpClient);
+ renamePath = idpHome.resolve("dist").resolve("plugin-rollback");
+ }
+
+ /** Generate a {@link URLClassLoader} which looks at the
+ * installed WEB-INF/lib in addition to the dist webapp and bin/lib directories.
+ * @return an appropriate loader
+ * @throws IOException if a directory traversal fails.
+ */
+ private synchronized URLClassLoader getInstalledPluginLoader() throws IOException {
+ if (installedPluginLoader != null) {
+ return installedPluginLoader;
+ }
+ final List<URL> urls = new ArrayList<>();
+ try (final DirectoryStream<Path> webAppList =
+ Files.newDirectoryStream(idpHome.resolve("dist"), "edit-webapp-*")) {
+ for (final Path webApp : webAppList) {
+ try (final DirectoryStream<Path> webInfLibs =
+ Files.newDirectoryStream(webApp.resolve("WEB-INF").resolve("lib"))) {
+ for (final Path jar : webInfLibs) {
+ urls.add(jar.toUri().toURL());
+ }
+ }
+ }
+ }
+ installedPluginLoader = new URLClassLoader(urls.toArray(URL[]::new));
+ return installedPluginLoader;
+ }
+
+
+ /** Generate a {@link URLClassLoader} which looks at the
+ * installing WEB-INF.
+ * @return an appropriate loader
+ * @throws IOException if a directory traversal fails.
+ */
+ private synchronized URLClassLoader getDistributionLoader() throws IOException {
+ if (installingPluginLoader!= null) {
+ return installingPluginLoader;
+ }
+ final List<URL> urls = new ArrayList<>();
+ final Path libDir = distribution.resolve("edit-webapp").resolve("WEB-INF").resolve("lib");
+ try (final DirectoryStream<Path> libDirPaths = Files.newDirectoryStream(libDir)){
+ for (final Path jar : libDirPaths) {
+ urls.add(jar.toUri().toURL());
+ }
+ installingPluginLoader = new URLClassLoader(urls.toArray(URL[]::new));
+ return installingPluginLoader;
+ }
}
/**
@@ -689,20 +789,7 @@ public final class PluginInstaller extends AbstractInitializableComponent implem
*/
public List<IdPPlugin> getInstalledPlugins() {
try {
- final List<URL> urls = new ArrayList<>();
-
- try (final DirectoryStream<Path> webAppList =
- Files.newDirectoryStream(idpHome.resolve("dist"), "edit-webapp-*")) {
- for (final Path webApp : webAppList) {
- try (final DirectoryStream<Path> webInfLibs =
- Files.newDirectoryStream(webApp.resolve("WEB-INF").resolve("lib"))) {
- for (final Path jar : webInfLibs) {
- urls.add(jar.toUri().toURL());
- }
- }
- }
- }
- try (final URLClassLoader loader = new URLClassLoader(urls.toArray(URL[]::new))){
+ try (final URLClassLoader loader = getInstalledPluginLoader()){
try (final Stream<Provider<IdPPlugin>> loaderStream =
ServiceLoader.load(IdPPlugin.class, loader).stream()) {
return loaderStream.map(ServiceLoader.Provider::get).collect(Collectors.toList());
@@ -728,21 +815,27 @@ public final class PluginInstaller extends AbstractInitializableComponent implem
return null;
}
+ /** close, ignoring errors.
+ * @param what what to close
+ */
+ private void closeSilently(final AutoCloseable what) {
+ if (what == null) {
+ return;
+ }
+ try {
+ what.close();
+ } catch (final Exception e) {
+ LOG.error("Autoclose of {} failed", what, e);
+ }
+ }
+
/** {@inheritDoc} */
public void close() {
+ closeSilently(installedPluginLoader);
+ closeSilently(installingPluginLoader);
PluginInstallerSupport.deleteTree(downloadDirectory);
PluginInstallerSupport.deleteTree(unpackDirectory);
- for (final Path p : copiedFiles) {
- try {
- if (Files.exists(p)) {
- InstallerSupport.setReadOnly(p, false);
- Files.delete(p);
- }
- } catch (final IOException e) {
- p.toFile().deleteOnExit();
- LOG.warn("Failed to delete {}", p, e);
- }
- }
+ PluginInstallerSupport.deleteTree(renamePath);
}
}
diff --git a/idp-installer/src/main/java/net/shibboleth/idp/installer/plugin/impl/PluginInstallerSupport.java b/idp-installer/src/main/java/net/shibboleth/idp/installer/plugin/impl/PluginInstallerSupport.java
index 2096e4c45..c9b00eb5b 100644
--- a/idp-installer/src/main/java/net/shibboleth/idp/installer/plugin/impl/PluginInstallerSupport.java
+++ b/idp-installer/src/main/java/net/shibboleth/idp/installer/plugin/impl/PluginInstallerSupport.java
@@ -41,7 +41,9 @@ import org.apache.tools.ant.BuildException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
+import net.shibboleth.idp.installer.InstallerSupport;
import net.shibboleth.utilities.java.support.annotation.constraint.Live;
+import net.shibboleth.utilities.java.support.collection.Pair;
/**
* Support for copying files during plugin manipulation.
@@ -73,6 +75,7 @@ public final class PluginInstallerSupport {
return;
}
LOG.debug("Deleting directory {}", directory);
+ InstallerSupport.setReadOnly(directory, false);
try {
Files.walkFileTree(directory, new DeletingVisitor());
} catch (final IOException e) {
@@ -125,6 +128,37 @@ public final class PluginInstallerSupport {
pathsCopied.addAll(visitor.getCopiedList());
}
+ /** Rename Files into the provided tree.
+ * @param fromBase The root directory of the from files
+ * @param toBase The root directory to rename to
+ * @param fromFiles The list of files (inside fromBase) to rename
+ * @param renames All the work as it is done
+ * @throws IOException If any of the file operations fail
+ */
+
+ public static void renameToTree(@Nonnull final Path fromBase,
+ @Nonnull final Path toBase,
+ @Nonnull final List<String> fromFiles,
+ @Nonnull @Live final List<Pair<Path, Path>> renames) throws IOException {
+
+ if (!Files.exists(toBase)) {
+ Files.createDirectories(toBase);
+ }
+ for (final String file : fromFiles) {
+ final Path path = Path.of(file);
+ if (!Files.exists(path)) {
+ LOG.info("File {} was not renamed away because it does not exist", file);
+ continue;
+ }
+ final Path relName = fromBase.relativize(path);
+ LOG.trace("Relative name {}", relName);
+ final Path to = toBase.resolve(relName);
+ Files.createDirectories(to.getParent());
+ Files.move(path,to);
+ renames.add(new Pair<>(path, to));
+ }
+ }
+
/**
* A @{link {@link FileVisitor} which detects (and logs) whether a copy would overwrite.
*/
diff --git a/idp-installer/src/main/java/net/shibboleth/idp/installer/plugin/impl/RollbackPluginInstall.java b/idp-installer/src/main/java/net/shibboleth/idp/installer/plugin/impl/RollbackPluginInstall.java
index 88fa9f144..a00acd4c8 100644
--- a/idp-installer/src/main/java/net/shibboleth/idp/installer/plugin/impl/RollbackPluginInstall.java
+++ b/idp-installer/src/main/java/net/shibboleth/idp/installer/plugin/impl/RollbackPluginInstall.java
@@ -19,7 +19,6 @@ package net.shibboleth.idp.installer.plugin.impl;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
-import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.InputStream;
@@ -57,7 +56,7 @@ public class RollbackPluginInstall implements AutoCloseable {
@Nonnull private List<IdPModule> modulesDisabled = new ArrayList<>();
/** The files copied in as the {@link IdPPlugin} was installed. */
- @Nonnull private List<String> filesCopied = new ArrayList<>();
+ @Nonnull private List<Path> filesCopied = new ArrayList<>();
/** The files renamed away during the installation. */
@Nonnull private List<Pair<Path, Path>> filesRenamedAway = new ArrayList<>();
@@ -90,7 +89,7 @@ public class RollbackPluginInstall implements AutoCloseable {
/** What was copied?
* @return Returns the files copied as part of the install
*/
- @Live @Nonnull public List<String> getFilesCopied() {
+ @Live @Nonnull public List<Path> getFilesCopied() {
return filesCopied;
}
@@ -147,13 +146,13 @@ public class RollbackPluginInstall implements AutoCloseable {
return false;
}
for (int i = filesCopied.size()-1; i >=0; i--) {
- final String file = filesCopied.get(i);
+ final Path path = filesCopied.get(i);
try {
- log.trace("Deleting {}", file);
- Files.delete(Path.of(file));
+ log.trace("Deleting {}", path);
+ Files.delete(path);
} catch (final Throwable t) {
- log.error("Could not delete {}, continuing ", file, t);
- new File(file).deleteOnExit();
+ log.error("Could not delete {}, continuing ", path, t);
+ path.toFile().deleteOnExit();
}
}
return true;
diff --git a/idp-installer/src/test/java/net/shibboleth/idp/installer/plugin/impl/RollbackTester.java b/idp-installer/src/test/java/net/shibboleth/idp/installer/plugin/impl/RollbackTester.java
index 7bc6bbc82..10b0d93ad 100644
--- a/idp-installer/src/test/java/net/shibboleth/idp/installer/plugin/impl/RollbackTester.java
+++ b/idp-installer/src/test/java/net/shibboleth/idp/installer/plugin/impl/RollbackTester.java
@@ -80,7 +80,7 @@ public class RollbackTester {
assertFalse(disabled2.isEnabled(null));
try (final RollbackPluginInstall rp = new RollbackPluginInstall(new ModuleContext(parent))) {
- rp.getFilesCopied().add(copied.toString());
+ rp.getFilesCopied().add(copied);
rp.getFilesRenamedAway().add(renamed);
rp.getModulesDisabled().add(disabled1);
rp.getModulesDisabled().add(disabled2);
diff --git a/idp-installer/src/test/resources/credentials/truststore.asc b/idp-installer/src/test/resources/credentials/truststore.asc
index 0ccc32281..f65286857 100644
--- a/idp-installer/src/test/resources/credentials/truststore.asc
+++ b/idp-installer/src/test/resources/credentials/truststore.asc
@@ -187,3 +187,140 @@ p4tHovBdwCerszJryXL9jPma9jdfbGmttL+36358si1RNrQG6IhaJpa5a89/BJQH
3aNjQSVwGC4Owz97QpUq9StZb1qhvIw=
=1e4x
-----END PGP PUBLIC KEY BLOCK-----
+-----BEGIN PGP PUBLIC KEY BLOCK-----
+Version: GnuPG v1.4.11 (Darwin)
+
+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
+=ShRP
+-----END PGP PUBLIC KEY BLOCK-----
+
+pub 1024D/47905D15 2004-06-10 Scott Cantor (Internet2) <cantor.2 at osu.edu>
+ Key fingerprint = 272F 5F06 84CE A946 E796 7774 A57B 56F3 4790 5D15
+ sig 3 47905D15 2004-06-10 Scott Cantor (Internet2) <cantor.2 at osu.edu>
+ sig 3 47905D15 2004-06-10 Scott Cantor (Internet2) <cantor.2 at osu.edu>
+ sig 3 EA2882BB 2006-12-08 Ian A. Young <ian at iay.org.uk>
+ sig A260F52E 2007-04-25 Chad La Joie (Georgetown University / Internet 2) <lajoie at georgetown.edu>
+ sig 146B2514 2008-05-17 Chad La Joie <chad.lajoie at switch.ch>
+ sub 1024g/14E203EB 2004-06-10
+ sig 47905D15 2004-06-10 Scott Cantor (Internet2) <cantor.2 at osu.edu>
+
+-----BEGIN PGP PUBLIC KEY BLOCK-----
+Version: GnuPG v1.4.5 (GNU/Linux)
+
+mQGiBEDIr5QRBACv65rtbmSMA2zb4g+5LgcUMznK0fkU/+zL0ApdIKjJWfZ/E0GC
+tXKfn4oKIXNCCHuwbqHx9+xUmdYHJ1KZMmaQY+sm1VzrIexixwTs4M4p/w/mK8ry
+un1WYfBnAleXz0geqE9/EYvum270R1e+Ms36flQGVRdaS0cUzUHCHG3zkwCg8/9B
+SjFPZNsJrv2C0FtEm4nt4PMD/AnE7N5pwUCaGwL/zJbfWOOK7O+jA1LZ9Q/jM6kq
+oPC+hXuQn6duflr31rICmazMBxIGQUIA6GeHM9FnlhJQ3hBNuEMwLqJmYJpBLA1i
+5LUe9NpnvXPsDc+lTHRh9uecHYCRYhuO1bIJ9drRpLALaiuAlGeLDJcLKPLekj2x
+3OCVBACurQQIBDSz3r9gXiU3oWVIsHNmeEM7pOD2fv8rfnDRyCeljef3kCUrUQjW
+Zuu1bBLf5r/N98JdmcrcxS6r3kIGcn68ok+ZHx7poIlxUchXPtWvFLFpEA8KZCOg
+Iz24CnqvbYUYckQHGP8cs4yMBAgZT6jZQQLJVeH3iKgDBAnRRLQrU2NvdHQgQ2Fu
+dG9yIChJbnRlcm5ldDIpIDxjYW50b3IuMkBvc3UuZWR1PohXBBMRAgAXBQJAyK+U
+BQsHCgMEAxUDAgMWAgECF4AACgkQpXtW80eQXRWLfgCZAXU659pjCO9DQ/6CYL19
+lqEqShYAoMlk3kjCmfai14uV5kE40RwQepXZiFcEExECABcFAkDIr5QFCwcKAwQD
+FQMCAxYCAQIXgAAKCRCle1bzR5BdFYt+AKCXvVk4Lmz9JaRBkAI5EszEIJE4mgCg
+yyOCRkBi7uyn7Un5GJWglYMII0+IRgQTEQIABgUCRXn6YQAKCRDvQPwp6iiCuzQx
+AKCfIAINZmu9xCNwkk/CEEzd+xmYwQCfQcjlajsk6muOASv/SLcUZrrEt72IRgQQ
+EQIABgUCRi/L2AAKCRA/Xp6HomD1LoeLAKCNxma/y2VQdGJ6A1IfTMoU0XykIACg
++cbPJ3wf4ZLTL+hByHHiPtySCpCIRgQQEQIABgUCSC4lHQAKCRAj3qZaFGslFHrH
+AJ9U+XqePUTjjs0CXUJhYenyP2tVHwCfUa53/zRRZxQbQvXSKFojiQ6ZUs25AQ0E
+QMivlRAEAJWXpGwp79Yefu3qoZM86JYxBc9IuEzB5sCEr9H3FSMkmqp40ayTBxcN
+8lUa4ZVGuycnlPmBcWRKOvXUgJsjkSmkBHLvNvQOReVCtThpd7/Rzk+TCo4fBujL
+12DpBwG/yl3dBkotD/6RXiVtDXFejm2ok8FOWk+sylevCTeNYXGvAAQLA/9HZwfL
+HB6sfcAeRc3FjzQ0S9Fzi63i92tePL3IQvNmF+VXQgx0q4fXeKBZCzlYBt9x5kjp
+ksSWPKKUao0OggiC0wT72JHSrK2BcWDWEmfrsjPep9eEqayJm06ae//MaKnZ/CCb
+E1loar6uE1KD6uqt2MARjEby/egO2akyKRlLYohGBBgRAgAGBQJAyK+VAAoJEKV7
+VvNHkF0V7YMAoPO+egNv4FH2swYCqzPX/vD4BEKNAJ9fuG71ljRK4lCGrZu+Ei5W
+AhqwAA==
+=Hsu3
+-----END PGP PUBLIC KEY BLOCK-----
+
--
To stop receiving notification emails like this one, please contact
the administrator of this repository.
More information about the commits
mailing list