diff --git a/CHANGELOG.md b/CHANGELOG.md index 2d6de96521..9cc4300c56 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Add branch link provider to access branch links in plugins ([#1243](https://github.com/scm-manager/scm-manager/pull/1243)) - Add key value input field component ([#1246](https://github.com/scm-manager/scm-manager/pull/1246)) - Add Jexl parser ([#1251](https://github.com/scm-manager/scm-manager/pull/1251)) +- Update installed optional plugin dependencies upon plugin upgrade ([#1260](https://github.com/scm-manager/scm-manager/pull/1260)) ### Changed - Adding start delay to liveness and readiness probes in helm chart template diff --git a/pom.xml b/pom.xml index d1d9f90491..fa72aebe74 100644 --- a/pom.xml +++ b/pom.xml @@ -560,7 +560,7 @@ sonia.scm.maven smp-maven-plugin - 1.0.0 + 1.1.0 diff --git a/scm-core/src/main/java/sonia/scm/plugin/AvailablePluginDescriptor.java b/scm-core/src/main/java/sonia/scm/plugin/AvailablePluginDescriptor.java index f8dd2d1a71..e251753113 100644 --- a/scm-core/src/main/java/sonia/scm/plugin/AvailablePluginDescriptor.java +++ b/scm-core/src/main/java/sonia/scm/plugin/AvailablePluginDescriptor.java @@ -21,12 +21,14 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ - + package sonia.scm.plugin; import java.util.Optional; import java.util.Set; +import static java.util.Collections.emptySet; + /** * @since 2.0.0 */ @@ -35,13 +37,23 @@ public class AvailablePluginDescriptor implements PluginDescriptor { private final PluginInformation information; private final PluginCondition condition; private final Set dependencies; + private final Set optionalDependencies; private final String url; private final String checksum; + /** + * @deprecated Use {@link #AvailablePluginDescriptor(PluginInformation, PluginCondition, Set, Set, String, String)} instead + */ + @Deprecated public AvailablePluginDescriptor(PluginInformation information, PluginCondition condition, Set dependencies, String url, String checksum) { + this(information, condition, dependencies, emptySet(), url, checksum); + } + + public AvailablePluginDescriptor(PluginInformation information, PluginCondition condition, Set dependencies, Set optionalDependencies, String url, String checksum) { this.information = information; this.condition = condition; this.dependencies = dependencies; + this.optionalDependencies = optionalDependencies; this.url = url; this.checksum = checksum; } @@ -68,4 +80,9 @@ public class AvailablePluginDescriptor implements PluginDescriptor { public Set getDependencies() { return dependencies; } + + @Override + public Set getOptionalDependencies() { + return optionalDependencies; + } } diff --git a/scm-core/src/main/java/sonia/scm/plugin/InstalledPluginDescriptor.java b/scm-core/src/main/java/sonia/scm/plugin/InstalledPluginDescriptor.java index 916c8691d9..faedb2a6f5 100644 --- a/scm-core/src/main/java/sonia/scm/plugin/InstalledPluginDescriptor.java +++ b/scm-core/src/main/java/sonia/scm/plugin/InstalledPluginDescriptor.java @@ -191,6 +191,7 @@ public final class InstalledPluginDescriptor extends ScmModule implements Plugin * * @since 2.0.0 */ + @Override public Set getOptionalDependencies() { if (optionalDependencies == null) { diff --git a/scm-core/src/main/java/sonia/scm/plugin/PluginDescriptor.java b/scm-core/src/main/java/sonia/scm/plugin/PluginDescriptor.java index b93f3b0798..76d2fd2cdb 100644 --- a/scm-core/src/main/java/sonia/scm/plugin/PluginDescriptor.java +++ b/scm-core/src/main/java/sonia/scm/plugin/PluginDescriptor.java @@ -21,11 +21,13 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ - + package sonia.scm.plugin; import java.util.Set; +import static java.util.Collections.emptySet; + public interface PluginDescriptor { PluginInformation getInformation(); @@ -34,4 +36,8 @@ public interface PluginDescriptor { Set getDependencies(); + default Set getOptionalDependencies() { + return emptySet(); + } + } diff --git a/scm-ui/ui-types/src/Plugin.ts b/scm-ui/ui-types/src/Plugin.ts index 66cb8b994f..635f277f9f 100644 --- a/scm-ui/ui-types/src/Plugin.ts +++ b/scm-ui/ui-types/src/Plugin.ts @@ -36,6 +36,7 @@ export type Plugin = { pending: boolean; markedForUninstall?: boolean; dependencies: string[]; + optionalDependencies: string[]; _links: Links; }; diff --git a/scm-ui/ui-webapp/public/locales/de/admin.json b/scm-ui/ui-webapp/public/locales/de/admin.json index db096832eb..620228df0b 100644 --- a/scm-ui/ui-webapp/public/locales/de/admin.json +++ b/scm-ui/ui-webapp/public/locales/de/admin.json @@ -62,6 +62,7 @@ "currentVersion": "Installierte Version", "newVersion": "Neue Version", "dependencyNotification": "Mit diesem Plugin werden folgende Abhängigkeiten mit installiert bzw. aktualisiert, wenn sie noch nicht in der aktuellen Version vorhanden sind!", + "optionalDependencyNotification": "Mit diesem Plugin werden folgende optionale Abhängigkeiten mit aktualisiert, falls sie installiert sind!", "dependencies": "Abhängigkeiten", "installedNotification": "Das Plugin wurde erfolgreich installiert. Um Änderungen an der UI zu sehen, muss die Seite neu geladen werden:", "updatedNotification": "Das Plugin wurde erfolgreich aktualisiert. Um Änderungen an der UI zu sehen, muss die Seite neu geladen werden:", diff --git a/scm-ui/ui-webapp/public/locales/en/admin.json b/scm-ui/ui-webapp/public/locales/en/admin.json index 829aecf3d4..eae79e3a9c 100644 --- a/scm-ui/ui-webapp/public/locales/en/admin.json +++ b/scm-ui/ui-webapp/public/locales/en/admin.json @@ -62,6 +62,7 @@ "currentVersion": "Installed version", "newVersion": "New version", "dependencyNotification": "With this plugin, the following dependencies will be installed/updated if their latest versions are not installed yet!", + "optionalDependencyNotification": "With this plugin, the following optional dependencies will be updated if they are installed!", "dependencies": "Dependencies", "installedNotification": "Successfully installed plugin. You have to reload the page, to see ui changes:", "updatedNotification": "Successfully updated plugin. You have to reload the page, to see ui changes:", diff --git a/scm-ui/ui-webapp/src/admin/plugins/components/PluginModal.tsx b/scm-ui/ui-webapp/src/admin/plugins/components/PluginModal.tsx index 05ddf1f848..8a76a0d7e3 100644 --- a/scm-ui/ui-webapp/src/admin/plugins/components/PluginModal.tsx +++ b/scm-ui/ui-webapp/src/admin/plugins/components/PluginModal.tsx @@ -183,6 +183,27 @@ class PluginModal extends React.Component { return dependencies; } + renderOptionalDependencies() { + const { plugin, t } = this.props; + + let optionalDependencies = null; + if (plugin.optionalDependencies && plugin.optionalDependencies.length > 0) { + optionalDependencies = ( +
+ + {t("plugins.modal.optionalDependencyNotification")} +
    + {plugin.optionalDependencies.map((optionalDependency, index) => { + return
  • {optionalDependency}
  • ; + })} +
+
+
+ ); + } + return optionalDependencies; + } + renderNotifications = () => { const { t, pluginAction } = this.props; const { restart, error, success } = this.state; @@ -275,6 +296,7 @@ class PluginModal extends React.Component { )} {this.renderDependencies()} + {this.renderOptionalDependencies()}
diff --git a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/PluginDto.java b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/PluginDto.java index 4dce1e9e92..282b9860b3 100644 --- a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/PluginDto.java +++ b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/PluginDto.java @@ -53,6 +53,7 @@ public class PluginDto extends HalRepresentation { private Boolean core; private Boolean markedForUninstall; private Set dependencies; + private Set optionalDependencies; public PluginDto(Links links) { add(links); diff --git a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/PluginDtoMapper.java b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/PluginDtoMapper.java index 1d71735278..0156a976ec 100644 --- a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/PluginDtoMapper.java +++ b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/PluginDtoMapper.java @@ -68,6 +68,7 @@ public abstract class PluginDtoMapper { private void map(PluginDto dto, Plugin plugin) { dto.setDependencies(plugin.getDescriptor().getDependencies()); + dto.setOptionalDependencies(plugin.getDescriptor().getOptionalDependencies()); map(plugin.getDescriptor().getInformation(), dto); if (dto.getCategory() == null) { dto.setCategory("Miscellaneous"); 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 ef1f4e6873..69fc876704 100644 --- a/scm-webapp/src/main/java/sonia/scm/plugin/DefaultPluginManager.java +++ b/scm-webapp/src/main/java/sonia/scm/plugin/DefaultPluginManager.java @@ -253,21 +253,40 @@ public class DefaultPluginManager implements PluginManager { private void collectPluginsToInstallOrUpdate(List plugins, String name) { if (!isInstalledOrPending(name) || isUpdatable(name)) { - AvailablePlugin plugin = getAvailable(name).orElseThrow(() -> NotFoundException.notFound(entity(AvailablePlugin.class, name))); - - Set dependencies = plugin.getDescriptor().getDependencies(); - if (dependencies != null) { - for (String dependency : dependencies) { - collectPluginsToInstallOrUpdate(plugins, dependency); - } - } - - plugins.add(plugin); + collectDependentPlugins(plugins, name); } else { LOG.info("plugin {} is already installed or installation is pending, skipping installation", name); } } + private void collectOptionalPluginToInstallOrUpdate(List plugins, String name) { + if (isInstalledOrPending(name) && isUpdatable(name)) { + collectDependentPlugins(plugins, name); + } else { + LOG.info("optional plugin {} is not installed or not updatable", name); + } + } + + private void collectDependentPlugins(List plugins, String name) { + AvailablePlugin plugin = getAvailable(name).orElseThrow(() -> NotFoundException.notFound(entity(AvailablePlugin.class, name))); + + Set dependencies = plugin.getDescriptor().getDependencies(); + if (dependencies != null) { + for (String dependency : dependencies) { + collectPluginsToInstallOrUpdate(plugins, dependency); + } + } + + Set optionalDependencies = plugin.getDescriptor().getOptionalDependencies(); + if (dependencies != null) { + for (String optionalDependency : optionalDependencies) { + collectOptionalPluginToInstallOrUpdate(plugins, optionalDependency); + } + } + + plugins.add(plugin); + } + private boolean isInstalledOrPending(String name) { return getInstalled(name).isPresent() || getPending(name).isPresent(); } diff --git a/scm-webapp/src/main/java/sonia/scm/plugin/PluginCenterDto.java b/scm-webapp/src/main/java/sonia/scm/plugin/PluginCenterDto.java index 223e02ad47..c034c98a56 100644 --- a/scm-webapp/src/main/java/sonia/scm/plugin/PluginCenterDto.java +++ b/scm-webapp/src/main/java/sonia/scm/plugin/PluginCenterDto.java @@ -85,6 +85,9 @@ public final class PluginCenterDto implements Serializable { @XmlElement(name = "dependencies") private Set dependencies; + @XmlElement(name = "optionalDependencies") + private Set optionalDependencies; + @XmlElement(name = "_links") private Map links; } diff --git a/scm-webapp/src/main/java/sonia/scm/plugin/PluginCenterDtoMapper.java b/scm-webapp/src/main/java/sonia/scm/plugin/PluginCenterDtoMapper.java index 27d20d638a..5927592d0c 100644 --- a/scm-webapp/src/main/java/sonia/scm/plugin/PluginCenterDtoMapper.java +++ b/scm-webapp/src/main/java/sonia/scm/plugin/PluginCenterDtoMapper.java @@ -43,7 +43,7 @@ public abstract class PluginCenterDtoMapper { for (PluginCenterDto.Plugin plugin : pluginCenterDto.getEmbedded().getPlugins()) { String url = plugin.getLinks().get("download").getHref(); AvailablePluginDescriptor descriptor = new AvailablePluginDescriptor( - map(plugin), map(plugin.getConditions()), plugin.getDependencies(), url, plugin.getSha256sum() + map(plugin), map(plugin.getConditions()), plugin.getDependencies(), plugin.getOptionalDependencies(), url, plugin.getSha256sum() ); plugins.add(new AvailablePlugin(descriptor)); } diff --git a/scm-webapp/src/test/java/sonia/scm/api/v2/resources/PluginDtoMapperTest.java b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/PluginDtoMapperTest.java index 7c3c3975c0..9a6b51db7a 100644 --- a/scm-webapp/src/test/java/sonia/scm/api/v2/resources/PluginDtoMapperTest.java +++ b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/PluginDtoMapperTest.java @@ -164,6 +164,15 @@ class PluginDtoMapperTest { assertThat(dto.getDependencies()).containsOnly("one", "two"); } + @Test + void shouldAppendOptionalDependencies() { + AvailablePlugin plugin = createAvailable(createPluginInformation()); + when(plugin.getDescriptor().getOptionalDependencies()).thenReturn(ImmutableSet.of("one", "two")); + + PluginDto dto = mapper.mapAvailable(plugin); + assertThat(dto.getOptionalDependencies()).containsOnly("one", "two"); + } + @Test void shouldAppendUninstallLink() { when(subject.isPermitted("plugin:write")).thenReturn(true); 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 b1b1db9efe..b16d010c5d 100644 --- a/scm-webapp/src/test/java/sonia/scm/plugin/DefaultPluginManagerTest.java +++ b/scm-webapp/src/test/java/sonia/scm/plugin/DefaultPluginManagerTest.java @@ -260,6 +260,35 @@ class DefaultPluginManagerTest { verify(installer).install(review); } + @Test + void shouldUpdateAlreadyInstalledOptionalDependenciesWhenNewerVersionIsAvailable() { + AvailablePlugin review = createAvailable("scm-review-plugin"); + when(review.getDescriptor().getOptionalDependencies()).thenReturn(ImmutableSet.of("scm-mail-plugin")); + AvailablePlugin mail = createAvailable("scm-mail-plugin", "1.1.0"); + when(center.getAvailable()).thenReturn(ImmutableSet.of(review, mail)); + + InstalledPlugin installedMail = createInstalled("scm-mail-plugin", "1.0.0"); + when(loader.getInstalledPlugins()).thenReturn(ImmutableList.of(installedMail)); + + manager.install("scm-review-plugin", false); + + verify(installer).install(mail); + verify(installer).install(review); + } + + @Test + void shouldNotUpdateOptionalDependenciesWhenNewerVersionIsAvailableButItIsNotInstalled() { + AvailablePlugin review = createAvailable("scm-review-plugin"); + when(review.getDescriptor().getOptionalDependencies()).thenReturn(ImmutableSet.of("scm-mail-plugin")); + AvailablePlugin mail = createAvailable("scm-mail-plugin", "1.1.0"); + when(center.getAvailable()).thenReturn(ImmutableSet.of(review, mail)); + + manager.install("scm-review-plugin", false); + + verify(installer, never()).install(mail); + verify(installer).install(review); + } + @Test void shouldRollbackOnFailedInstallation() { AvailablePlugin review = createAvailable("scm-review-plugin"); diff --git a/scm-webapp/src/test/java/sonia/scm/plugin/PluginCenterDtoMapperTest.java b/scm-webapp/src/test/java/sonia/scm/plugin/PluginCenterDtoMapperTest.java index 42ecf64da4..96ceccfb54 100644 --- a/scm-webapp/src/test/java/sonia/scm/plugin/PluginCenterDtoMapperTest.java +++ b/scm-webapp/src/test/java/sonia/scm/plugin/PluginCenterDtoMapperTest.java @@ -67,6 +67,7 @@ class PluginCenterDtoMapperTest { "555000444", new Condition(Collections.singletonList("linux"), "amd64","2.0.0"), ImmutableSet.of("scm-review-plugin"), + ImmutableSet.of(), ImmutableMap.of("download", new Link("http://download.hitchhiker.com")) ); @@ -101,6 +102,7 @@ class PluginCenterDtoMapperTest { "12345678aa", new Condition(Collections.singletonList("linux"), "amd64","2.0.0"), ImmutableSet.of("scm-review-plugin"), + ImmutableSet.of(), ImmutableMap.of("download", new Link("http://download.hitchhiker.com/review")) ); @@ -115,6 +117,7 @@ class PluginCenterDtoMapperTest { "555000444", new Condition(Collections.singletonList("linux"), "amd64","2.0.0"), ImmutableSet.of("scm-review-plugin"), + ImmutableSet.of(), ImmutableMap.of("download", new Link("http://download.hitchhiker.com/hitchhiker")) );