Improve plugin center error feedback and cache invalidation (#2147)

The plugin center cache was not invalidated when the proxy configuration was changed in the global settings. This caused stale and inconsistent state to be displayed to the user while there was no feedback that something was wrong.
This commit is contained in:
Konstantin Schaper
2022-11-04 11:49:08 +01:00
committed by GitHub
parent ec83de3600
commit 7b933c6821
22 changed files with 307 additions and 63 deletions

View File

@@ -95,10 +95,10 @@ public class AvailablePluginResource {
@Produces(VndMediaType.PLUGIN_COLLECTION)
public Response getAvailablePlugins() {
PluginPermissions.read().check();
List<InstalledPlugin> installed = pluginManager.getInstalled();
List<AvailablePlugin> available = pluginManager.getAvailable().stream().filter(a -> notInstalled(a, installed)).collect(Collectors.toList());
PluginManager.PluginResult plugins = pluginManager.getPlugins();
List<AvailablePlugin> available = plugins.getAvailablePlugins().stream().filter(a -> notInstalled(a, plugins.getInstalledPlugins())).collect(Collectors.toList());
return Response.ok(collectionMapper.mapAvailable(available)).build();
return Response.ok(collectionMapper.mapAvailable(available, plugins.getPluginCenterStatus())).build();
}
private boolean notInstalled(AvailablePlugin a, List<InstalledPlugin> installed) {

View File

@@ -95,9 +95,8 @@ public class InstalledPluginResource {
)
public Response getInstalledPlugins() {
PluginPermissions.read().check();
List<InstalledPlugin> plugins = pluginManager.getInstalled();
List<AvailablePlugin> available = pluginManager.getAvailable();
return Response.ok(collectionMapper.mapInstalled(plugins, available)).build();
PluginManager.PluginResult plugins = pluginManager.getPlugins();
return Response.ok(collectionMapper.mapInstalled(plugins)).build();
}
/**

View File

@@ -0,0 +1,44 @@
/*
* MIT License
*
* Copyright (c) 2020-present Cloudogu GmbH and Contributors
*
* 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 sonia.scm.api.v2.resources;
import de.otto.edison.hal.Embedded;
import de.otto.edison.hal.HalRepresentation;
import de.otto.edison.hal.Links;
import lombok.Getter;
import lombok.NoArgsConstructor;
import sonia.scm.plugin.PluginCenterStatus;
@Getter
@NoArgsConstructor
public class PluginCollectionDto extends HalRepresentation {
private PluginCenterStatus pluginCenterStatus;
public PluginCollectionDto(Links links, Embedded embedded, PluginCenterStatus pluginCenterStatus) {
super(links, embedded);
this.pluginCenterStatus = pluginCenterStatus;
}
}

View File

@@ -26,10 +26,9 @@ package sonia.scm.api.v2.resources;
import com.google.inject.Inject;
import de.otto.edison.hal.Embedded;
import de.otto.edison.hal.HalRepresentation;
import de.otto.edison.hal.Links;
import sonia.scm.plugin.AvailablePlugin;
import sonia.scm.plugin.InstalledPlugin;
import sonia.scm.plugin.PluginCenterStatus;
import sonia.scm.plugin.PluginManager;
import sonia.scm.plugin.PluginPermissions;
@@ -53,17 +52,18 @@ public class PluginDtoCollectionMapper {
this.manager = manager;
}
public HalRepresentation mapInstalled(List<InstalledPlugin> plugins, List<AvailablePlugin> availablePlugins) {
public PluginCollectionDto mapInstalled(PluginManager.PluginResult plugins) {
List<PluginDto> dtos = plugins
.getInstalledPlugins()
.stream()
.map(i -> mapper.mapInstalled(i, availablePlugins))
.map(i -> mapper.mapInstalled(i, plugins.getAvailablePlugins()))
.collect(toList());
return new HalRepresentation(createInstalledPluginsLinks(), embedDtos(dtos));
return new PluginCollectionDto(createInstalledPluginsLinks(), embedDtos(dtos), plugins.getPluginCenterStatus());
}
public HalRepresentation mapAvailable(List<AvailablePlugin> plugins) {
public PluginCollectionDto mapAvailable(List<AvailablePlugin> plugins, PluginCenterStatus pluginCenterStatus) {
List<PluginDto> dtos = plugins.stream().map(mapper::mapAvailable).collect(toList());
return new HalRepresentation(createAvailablePluginsLinks(plugins), embedDtos(dtos));
return new PluginCollectionDto(createAvailablePluginsLinks(plugins), embedDtos(dtos), pluginCenterStatus);
}
private Links createInstalledPluginsLinks() {

View File

@@ -107,6 +107,17 @@ public class DefaultPluginManager implements PluginManager {
updateMayUninstallFlag();
}
@Override
public PluginResult getPlugins() {
PluginPermissions.read().check();
PluginCenterResult pluginCenterResult = center.getPluginResult();
return new PluginResult(
getInstalled(),
filterNotInstalledOrMoreUpToDate(pluginCenterResult.getPlugins()),
pluginCenterResult.getStatus()
);
}
@Override
public Optional<AvailablePlugin> getAvailable(String name) {
PluginPermissions.read().check();
@@ -144,7 +155,11 @@ public class DefaultPluginManager implements PluginManager {
@Override
public List<AvailablePlugin> getAvailable() {
PluginPermissions.read().check();
return center.getAvailablePlugins()
return filterNotInstalledOrMoreUpToDate(center.getAvailablePlugins());
}
private List<AvailablePlugin> filterNotInstalledOrMoreUpToDate(Set<AvailablePlugin> availablePlugins) {
return availablePlugins
.stream()
.filter(this::isNotInstalledOrMoreUpToDate)
.map(p -> getPending(p.getDescriptor().getInformation().getName()).orElse(p))

View File

@@ -32,6 +32,7 @@ import sonia.scm.SCMContextProvider;
import sonia.scm.cache.Cache;
import sonia.scm.cache.CacheManager;
import sonia.scm.config.ScmConfiguration;
import sonia.scm.config.ScmConfigurationChangedEvent;
import sonia.scm.util.HttpUtil;
import sonia.scm.util.SystemUtil;
@@ -65,6 +66,12 @@ public class PluginCenter {
pluginCenterResultCache.clear();
}
@Subscribe
public void handle(ScmConfigurationChangedEvent event) {
LOG.debug("clear plugin center cache, because of {}", event);
pluginCenterResultCache.clear();
}
synchronized Set<AvailablePlugin> getAvailablePlugins() {
String url = buildPluginUrl(configuration.getPluginUrl());
return getPluginCenterResult(url).getPlugins();
@@ -75,6 +82,11 @@ public class PluginCenter {
return getPluginCenterResult(url).getPluginSets();
}
synchronized PluginCenterResult getPluginResult() {
String url = buildPluginUrl(configuration.getPluginUrl());
return getPluginCenterResult(url);
}
private PluginCenterResult getPluginCenterResult(String url) {
PluginCenterResult pluginCenterResult = pluginCenterResultCache.get(url);
if (pluginCenterResult == null) {

View File

@@ -25,6 +25,7 @@
package sonia.scm.plugin;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Strings;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import sonia.scm.event.ScmEventBus;
@@ -32,7 +33,6 @@ import sonia.scm.net.ahc.AdvancedHttpClient;
import sonia.scm.net.ahc.AdvancedHttpRequest;
import javax.inject.Inject;
import java.util.Collections;
import static sonia.scm.plugin.Tracing.SPAN_KIND;
@@ -65,6 +65,10 @@ class PluginCenterLoader {
PluginCenterResult load(String url) {
try {
if (Strings.isNullOrEmpty(url)) {
LOG.info("plugin center is deactivated, returning empty list");
return new PluginCenterResult(PluginCenterStatus.DEACTIVATED);
}
LOG.info("fetch plugins from {}", url);
AdvancedHttpRequest request = client.get(url).spanKind(SPAN_KIND);
if (authenticator.isAuthenticated()) {
@@ -75,7 +79,7 @@ class PluginCenterLoader {
} catch (Exception ex) {
LOG.error("failed to load plugins from plugin center, returning empty list", ex);
eventBus.post(new PluginCenterErrorEvent());
return new PluginCenterResult(Collections.emptySet(), Collections.emptySet());
return new PluginCenterResult(PluginCenterStatus.ERROR);
}
}
}

View File

@@ -27,6 +27,7 @@ package sonia.scm.plugin;
import lombok.AllArgsConstructor;
import lombok.Getter;
import java.util.Collections;
import java.util.Set;
@AllArgsConstructor
@@ -34,4 +35,18 @@ import java.util.Set;
class PluginCenterResult {
private Set<AvailablePlugin> plugins;
private Set<PluginSet> pluginSets;
private PluginCenterStatus status;
public PluginCenterResult() {
this(Collections.emptySet(), Collections.emptySet(), PluginCenterStatus.OK);
}
public PluginCenterResult(PluginCenterStatus status) {
this(Collections.emptySet(), Collections.emptySet(), status);
}
public PluginCenterResult(Set<AvailablePlugin> plugins, Set<PluginSet> pluginSets) {
this(plugins, pluginSets, PluginCenterStatus.OK);
}
}