From 3145b751c690ffadce2242c99ea5d3714502dec4 Mon Sep 17 00:00:00 2001 From: Rene Pfeuffer Date: Thu, 26 Sep 2019 17:50:54 +0200 Subject: [PATCH] Add cancel method to remove install and uninstall files --- .../java/sonia/scm/plugin/PluginManager.java | 2 + .../scm/plugin/DefaultPluginManager.java | 38 ++++++++++--------- .../plugin/PendingPluginUninstallation.java | 31 +++++++++++++++ .../scm/plugin/DefaultPluginManagerTest.java | 24 ++++++++++++ 4 files changed, 78 insertions(+), 17 deletions(-) create mode 100644 scm-webapp/src/main/java/sonia/scm/plugin/PendingPluginUninstallation.java diff --git a/scm-core/src/main/java/sonia/scm/plugin/PluginManager.java b/scm-core/src/main/java/sonia/scm/plugin/PluginManager.java index 3d0ea94536..1e787d24c3 100644 --- a/scm-core/src/main/java/sonia/scm/plugin/PluginManager.java +++ b/scm-core/src/main/java/sonia/scm/plugin/PluginManager.java @@ -93,4 +93,6 @@ public interface PluginManager { * Install all pending plugins and restart the scm context. */ void executePendingAndRestart(); + + void cancelInstallations(); } diff --git a/scm-webapp/src/main/java/sonia/scm/plugin/DefaultPluginManager.java b/scm-webapp/src/main/java/sonia/scm/plugin/DefaultPluginManager.java index 1c636ffb17..c03cfdab84 100644 --- a/scm-webapp/src/main/java/sonia/scm/plugin/DefaultPluginManager.java +++ b/scm-webapp/src/main/java/sonia/scm/plugin/DefaultPluginManager.java @@ -44,8 +44,8 @@ import sonia.scm.lifecycle.RestartEvent; import sonia.scm.version.Version; import javax.inject.Inject; -import java.io.IOException; import java.nio.file.Files; +import java.nio.file.Path; import java.util.ArrayList; import java.util.Collection; import java.util.List; @@ -72,7 +72,8 @@ public class DefaultPluginManager implements PluginManager { private final PluginLoader loader; private final PluginCenter center; private final PluginInstaller installer; - private final Collection pendingQueue = new ArrayList<>(); + private final Collection pendingInstallQueue = new ArrayList<>(); + private final Collection pendingUninstallQueue = new ArrayList<>(); private final PluginDependencyTracker dependencyTracker = new PluginDependencyTracker(); @Inject @@ -106,7 +107,7 @@ public class DefaultPluginManager implements PluginManager { } private Optional getPending(String name) { - return pendingQueue + return pendingInstallQueue .stream() .map(PendingPluginInstallation::getPlugin) .filter(filterByName(name)) @@ -179,7 +180,7 @@ public class DefaultPluginManager implements PluginManager { if (restartAfterInstallation) { restart("plugin installation"); } else { - pendingQueue.addAll(pendingInstallations); + pendingInstallQueue.addAll(pendingInstallations); updateMayUninstallFlag(); } } @@ -192,15 +193,8 @@ public class DefaultPluginManager implements PluginManager { .orElseThrow(() -> NotFoundException.notFound(entity(InstalledPlugin.class, name))); doThrow().violation("plugin is a core plugin and cannot be uninstalled").when(installed.isCore()); - dependencyTracker.removeInstalled(installed.getDescriptor()); - try { - createMarkerFile(installed, InstalledPlugin.UNINSTALL_MARKER_FILENAME); - installed.setMarkedForUninstall(true); - } catch (RuntimeException e) { - dependencyTracker.addInstalled(installed.getDescriptor()); - throw e; - } + markForUninstall(installed); if (restartAfterInstallation) { restart("plugin installation"); @@ -220,18 +214,22 @@ public class DefaultPluginManager implements PluginManager { && dependencyTracker.mayUninstall(p.getDescriptor().getInformation().getName()); } - private void createMarkerFile(InstalledPlugin plugin, String markerFile) { + private void markForUninstall(InstalledPlugin plugin) { + dependencyTracker.removeInstalled(plugin.getDescriptor()); try { - Files.createFile(plugin.getDirectory().resolve(markerFile)); - } catch (IOException e) { - throw new PluginException("could not mark plugin " + plugin.getId() + " in path " + plugin.getDirectory() + "as " + markerFile, e); + Path file = Files.createFile(plugin.getDirectory().resolve(InstalledPlugin.UNINSTALL_MARKER_FILENAME)); + pendingUninstallQueue.add(new PendingPluginUninstallation(plugin, file)); + plugin.setMarkedForUninstall(true); + } catch (Exception e) { + dependencyTracker.addInstalled(plugin.getDescriptor()); + throw new PluginException("could not mark plugin " + plugin.getId() + " in path " + plugin.getDirectory() + "as " + InstalledPlugin.UNINSTALL_MARKER_FILENAME, e); } } @Override public void executePendingAndRestart() { PluginPermissions.manage().check(); - if (!pendingQueue.isEmpty() || getInstalled().stream().anyMatch(InstalledPlugin::isMarkedForUninstall)) { + if (!pendingInstallQueue.isEmpty() || getInstalled().stream().anyMatch(InstalledPlugin::isMarkedForUninstall)) { restart("execute pending plugin changes"); } } @@ -274,4 +272,10 @@ public class DefaultPluginManager implements PluginManager { private boolean isUpdatable(String name) { return getAvailable(name).isPresent() && !getPending(name).isPresent(); } + + @Override + public void cancelInstallations() { + pendingUninstallQueue.forEach(PendingPluginUninstallation::cancel); + pendingInstallQueue.forEach(PendingPluginInstallation::cancel); + } } diff --git a/scm-webapp/src/main/java/sonia/scm/plugin/PendingPluginUninstallation.java b/scm-webapp/src/main/java/sonia/scm/plugin/PendingPluginUninstallation.java new file mode 100644 index 0000000000..e5481fb292 --- /dev/null +++ b/scm-webapp/src/main/java/sonia/scm/plugin/PendingPluginUninstallation.java @@ -0,0 +1,31 @@ +package sonia.scm.plugin; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; + +class PendingPluginUninstallation { + + private static final Logger LOG = LoggerFactory.getLogger(PendingPluginUninstallation.class); + + private final InstalledPlugin plugin; + private final Path uninstallFile; + + PendingPluginUninstallation(InstalledPlugin plugin, Path uninstallFile) { + this.plugin = plugin; + this.uninstallFile = uninstallFile; + } + + void cancel() { + String name = plugin.getDescriptor().getInformation().getName(); + LOG.info("cancel uninstallation of plugin {}", name); + try { + Files.delete(uninstallFile); + } catch (IOException ex) { + throw new PluginFailedToCancelInstallationException("failed to cancel uninstallation of plugin " + name, ex); + } + } +} diff --git a/scm-webapp/src/test/java/sonia/scm/plugin/DefaultPluginManagerTest.java b/scm-webapp/src/test/java/sonia/scm/plugin/DefaultPluginManagerTest.java index ad196b8ca3..c761034ac1 100644 --- a/scm-webapp/src/test/java/sonia/scm/plugin/DefaultPluginManagerTest.java +++ b/scm-webapp/src/test/java/sonia/scm/plugin/DefaultPluginManagerTest.java @@ -20,6 +20,8 @@ import sonia.scm.ScmConstraintViolationException; import sonia.scm.event.ScmEventBus; import sonia.scm.lifecycle.RestartEvent; +import java.io.IOException; +import java.nio.file.Files; import java.nio.file.Path; import java.util.List; import java.util.Optional; @@ -445,6 +447,28 @@ class DefaultPluginManagerTest { verify(eventBus).post(any(RestartEvent.class)); } + + @Test + void shouldUndoPendingInstallations(@TempDirectory.TempDir Path temp) throws IOException { + InstalledPlugin mailPlugin = createInstalled("scm-ssh-plugin"); + Path mailPluginPath = temp.resolve("scm-mail-plugin"); + Files.createDirectories(mailPluginPath); + when(mailPlugin.getDirectory()).thenReturn(mailPluginPath); + when(loader.getInstalledPlugins()).thenReturn(singletonList(mailPlugin)); + + AvailablePlugin git = createAvailable("scm-git-plugin"); + when(center.getAvailable()).thenReturn(ImmutableSet.of(git)); + PendingPluginInstallation gitPendingPluginInformation = mock(PendingPluginInstallation.class); + when(installer.install(git)).thenReturn(gitPendingPluginInformation); + + manager.install("scm-git-plugin", false); + manager.uninstall("scm-ssh-plugin", false); + + manager.cancelInstallations(); + + assertThat(mailPluginPath.resolve("uninstall")).doesNotExist(); + verify(gitPendingPluginInformation).cancel(); + } } @Nested