handle pending plugin installations

This commit is contained in:
Sebastian Sdorra
2019-08-21 12:49:15 +02:00
parent 25cb0d6a25
commit 9514a94492
17 changed files with 292 additions and 66 deletions

View File

@@ -10,7 +10,6 @@ import sonia.scm.plugin.PluginPermissions;
import sonia.scm.web.VndMediaType;
import javax.inject.Inject;
import javax.ws.rs.Consumes;
import javax.ws.rs.GET;
import javax.ws.rs.POST;
import javax.ws.rs.Path;
@@ -74,7 +73,7 @@ public class AvailablePluginResource {
PluginPermissions.read().check();
Optional<AvailablePlugin> plugin = pluginManager.getAvailable(name);
if (plugin.isPresent()) {
return Response.ok(mapper.map(plugin.get())).build();
return Response.ok(mapper.mapAvailable(plugin.get())).build();
} else {
throw notFound(entity(InstalledPluginDescriptor.class, name));
}

View File

@@ -73,7 +73,7 @@ public class InstalledPluginResource {
PluginPermissions.read().check();
Optional<InstalledPlugin> pluginDto = pluginManager.getInstalled(name);
if (pluginDto.isPresent()) {
return Response.ok(mapper.map(pluginDto.get())).build();
return Response.ok(mapper.mapInstalled(pluginDto.get())).build();
} else {
throw notFound(entity(InstalledPluginDescriptor.class, name));
}

View File

@@ -11,6 +11,7 @@ import java.util.Set;
@Getter
@Setter
@NoArgsConstructor
@SuppressWarnings("squid:S2160") // we do not need equals for dto
public class PluginDto extends HalRepresentation {
private String name;
@@ -20,6 +21,7 @@ public class PluginDto extends HalRepresentation {
private String author;
private String category;
private String avatarUrl;
private boolean pending;
private Set<String> dependencies;
public PluginDto(Links links) {

View File

@@ -5,10 +5,8 @@ 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.PluginInformation;
import sonia.scm.plugin.InstalledPlugin;
import java.util.Collection;
import java.util.List;
import static de.otto.edison.hal.Embedded.embeddedBuilder;
@@ -27,12 +25,12 @@ public class PluginDtoCollectionMapper {
}
public HalRepresentation mapInstalled(List<InstalledPlugin> plugins) {
List<PluginDto> dtos = plugins.stream().map(mapper::map).collect(toList());
List<PluginDto> dtos = plugins.stream().map(mapper::mapInstalled).collect(toList());
return new HalRepresentation(createInstalledPluginsLinks(), embedDtos(dtos));
}
public HalRepresentation mapAvailable(List<AvailablePlugin> plugins) {
List<PluginDto> dtos = plugins.stream().map(mapper::map).collect(toList());
List<PluginDto> dtos = plugins.stream().map(mapper::mapAvailable).collect(toList());
return new HalRepresentation(createAvailablePluginsLinks(), embedDtos(dtos));
}

View File

@@ -3,9 +3,11 @@ package sonia.scm.api.v2.resources;
import de.otto.edison.hal.Links;
import org.mapstruct.Mapper;
import org.mapstruct.MappingTarget;
import sonia.scm.plugin.AvailablePlugin;
import sonia.scm.plugin.InstalledPlugin;
import sonia.scm.plugin.Plugin;
import sonia.scm.plugin.PluginInformation;
import sonia.scm.plugin.PluginState;
import sonia.scm.plugin.PluginPermissions;
import javax.inject.Inject;
@@ -20,34 +22,48 @@ public abstract class PluginDtoMapper {
public abstract void map(PluginInformation plugin, @MappingTarget PluginDto dto);
public PluginDto map(Plugin plugin) {
PluginDto dto = createDto(plugin);
public PluginDto mapInstalled(InstalledPlugin plugin) {
PluginDto dto = createDtoForInstalled(plugin);
map(dto, plugin);
return dto;
}
public PluginDto mapAvailable(AvailablePlugin plugin) {
PluginDto dto = createDtoForAvailable(plugin);
map(dto, plugin);
dto.setPending(plugin.isPending());
return dto;
}
private void map(PluginDto dto, Plugin plugin) {
dto.setDependencies(plugin.getDescriptor().getDependencies());
map(plugin.getDescriptor().getInformation(), dto);
if (dto.getCategory() == null) {
dto.setCategory("Miscellaneous");
}
return dto;
}
private PluginDto createDto(Plugin plugin) {
Links.Builder linksBuilder;
private PluginDto createDtoForAvailable(AvailablePlugin plugin) {
PluginInformation information = plugin.getDescriptor().getInformation();
PluginInformation pluginInformation = plugin.getDescriptor().getInformation();
Links.Builder links = linkingTo()
.self(resourceLinks.availablePlugin()
.self(information.getName()));
if (plugin.getState() != null && plugin.getState().equals(PluginState.AVAILABLE)) {
linksBuilder = linkingTo()
.self(resourceLinks.availablePlugin()
.self(pluginInformation.getName(), pluginInformation.getVersion()));
linksBuilder.single(link("install", resourceLinks.availablePlugin().install(pluginInformation.getName(), pluginInformation.getVersion())));
}
else {
linksBuilder = linkingTo()
.self(resourceLinks.installedPlugin()
.self(pluginInformation.getName()));
if (!plugin.isPending() && PluginPermissions.manage().isPermitted()) {
links.single(link("install", resourceLinks.availablePlugin().install(information.getName())));
}
return new PluginDto(linksBuilder.build());
return new PluginDto(links.build());
}
private PluginDto createDtoForInstalled(InstalledPlugin plugin) {
PluginInformation information = plugin.getDescriptor().getInformation();
Links.Builder links = linkingTo()
.self(resourceLinks.installedPlugin()
.self(information.getName()));
return new PluginDto(links.build());
}
}

View File

@@ -6,6 +6,7 @@ import javax.inject.Inject;
import java.net.URI;
import java.net.URISyntaxException;
@SuppressWarnings("squid:S1192") // string literals should not be duplicated
class ResourceLinks {
private final ScmPathInfoStore scmPathInfoStore;
@@ -694,12 +695,12 @@ class ResourceLinks {
availablePluginLinkBuilder = new LinkBuilder(pathInfo, PluginRootResource.class, AvailablePluginResource.class);
}
String self(String name, String version) {
return availablePluginLinkBuilder.method("availablePlugins").parameters().method("getAvailablePlugin").parameters(name, version).href();
String self(String name) {
return availablePluginLinkBuilder.method("availablePlugins").parameters().method("getAvailablePlugin").parameters(name).href();
}
String install(String name, String version) {
return availablePluginLinkBuilder.method("availablePlugins").parameters().method("installPlugin").parameters(name, version).href();
String install(String name) {
return availablePluginLinkBuilder.method("availablePlugins").parameters().method("installPlugin").parameters(name).href();
}
}

View File

@@ -67,6 +67,7 @@ public class DefaultPluginManager implements PluginManager {
private final PluginLoader loader;
private final PluginCenter center;
private final PluginInstaller installer;
private final List<PendingPluginInstallation> pendingQueue = new ArrayList<>();
@Inject
public DefaultPluginManager(ScmEventBus eventBus, PluginLoader loader, PluginCenter center, PluginInstaller installer) {
@@ -83,6 +84,15 @@ public class DefaultPluginManager implements PluginManager {
.stream()
.filter(filterByName(name))
.filter(this::isNotInstalled)
.map(p -> getPending(name).orElse(p))
.findFirst();
}
private Optional<AvailablePlugin> getPending(String name) {
return pendingQueue
.stream()
.map(PendingPluginInstallation::getPlugin)
.filter(filterByName(name))
.findFirst();
}
@@ -104,7 +114,11 @@ public class DefaultPluginManager implements PluginManager {
@Override
public List<AvailablePlugin> getAvailable() {
PluginPermissions.read().check();
return center.getAvailable().stream().filter(this::isNotInstalled).collect(Collectors.toList());
return center.getAvailable()
.stream()
.filter(this::isNotInstalled)
.map(p -> getPending(p.getDescriptor().getInformation().getName()).orElse(p))
.collect(Collectors.toList());
}
private <T extends Plugin> Predicate<T> filterByName(String name) {
@@ -129,11 +143,28 @@ public class DefaultPluginManager implements PluginManager {
throw ex;
}
}
if (restartAfterInstallation) {
eventBus.post(new RestartEvent(PluginManager.class, "plugin installation"));
if (!pendingInstallations.isEmpty()) {
if (restartAfterInstallation) {
restart("plugin installation");
} else {
pendingQueue.addAll(pendingInstallations);
}
}
}
@Override
public void installPendingAndRestart() {
PluginPermissions.manage().check();
if (!pendingQueue.isEmpty()) {
restart("install pending plugins");
}
}
private void restart(String cause) {
eventBus.post(new RestartEvent(PluginManager.class, cause));
}
private void cancelPending(List<PendingPluginInstallation> pendingInstallations) {
pendingInstallations.forEach(PendingPluginInstallation::cancel);
}
@@ -144,8 +175,12 @@ public class DefaultPluginManager implements PluginManager {
return plugins;
}
private boolean isInstalledOrPending(String name) {
return getInstalled(name).isPresent() || getPending(name).isPresent();
}
private void collectPluginsToInstall(List<AvailablePlugin> plugins, String name) {
if (!getInstalled(name).isPresent()) {
if (!isInstalledOrPending(name)) {
AvailablePlugin plugin = getAvailable(name).orElseThrow(() -> NotFoundException.notFound(entity(AvailablePlugin.class, name)));
Set<String> dependencies = plugin.getDescriptor().getDependencies();
@@ -157,7 +192,7 @@ public class DefaultPluginManager implements PluginManager {
plugins.add(plugin);
} else {
LOG.info("plugin {} is already installed, skipping installation", name);
LOG.info("plugin {} is already installed or installation is pending, skipping installation", name);
}
}
}

View File

@@ -33,7 +33,7 @@ class PluginInstaller {
Files.copy(input, file);
verifyChecksum(plugin, input.hash(), file);
return new PendingPluginInstallation(plugin, file);
return new PendingPluginInstallation(plugin.install(), file);
} catch (IOException ex) {
cleanup(file);
throw new PluginDownloadException("failed to download plugin", ex);