Merged in feature/install_plugins (pull request #299)

Feature/install plugins
This commit is contained in:
Rene Pfeuffer
2019-08-22 08:51:18 +00:00
78 changed files with 2675 additions and 1970 deletions

View File

@@ -3,24 +3,22 @@ package sonia.scm.api.v2.resources;
import com.webcohesion.enunciate.metadata.rs.ResponseCode;
import com.webcohesion.enunciate.metadata.rs.StatusCodes;
import com.webcohesion.enunciate.metadata.rs.TypeHint;
import sonia.scm.plugin.Plugin;
import sonia.scm.plugin.PluginInformation;
import sonia.scm.plugin.AvailablePlugin;
import sonia.scm.plugin.InstalledPluginDescriptor;
import sonia.scm.plugin.PluginManager;
import sonia.scm.plugin.PluginPermissions;
import sonia.scm.plugin.PluginState;
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;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.QueryParam;
import javax.ws.rs.core.Response;
import java.util.Collection;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;
import static sonia.scm.ContextEntry.ContextBuilder.entity;
import static sonia.scm.NotFoundException.notFound;
@@ -53,11 +51,8 @@ public class AvailablePluginResource {
@Produces(VndMediaType.PLUGIN_COLLECTION)
public Response getAvailablePlugins() {
PluginPermissions.read().check();
Collection<PluginInformation> plugins = pluginManager.getAvailable()
.stream()
.filter(plugin -> plugin.getState().equals(PluginState.AVAILABLE))
.collect(Collectors.toList());
return Response.ok(collectionMapper.map(plugins)).build();
List<AvailablePlugin> available = pluginManager.getAvailable();
return Response.ok(collectionMapper.mapAvailable(available)).build();
}
/**
@@ -66,7 +61,7 @@ public class AvailablePluginResource {
* @return available plugin.
*/
@GET
@Path("/{name}/{version}")
@Path("/{name}")
@StatusCodes({
@ResponseCode(code = 200, condition = "success"),
@ResponseCode(code = 404, condition = "not found"),
@@ -74,35 +69,42 @@ public class AvailablePluginResource {
})
@TypeHint(PluginDto.class)
@Produces(VndMediaType.PLUGIN)
public Response getAvailablePlugin(@PathParam("name") String name, @PathParam("version") String version) {
public Response getAvailablePlugin(@PathParam("name") String name) {
PluginPermissions.read().check();
Optional<PluginInformation> plugin = pluginManager.getAvailable()
.stream()
.filter(p -> p.getId().equals(name + ":" + version))
.findFirst();
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(Plugin.class, name));
throw notFound(entity(InstalledPluginDescriptor.class, name));
}
}
/**
* Triggers plugin installation.
* @param name plugin artefact name
* @param version plugin version
* @param name plugin name
* @return HTTP Status.
*/
@POST
@Path("/{name}/{version}/install")
@Consumes(VndMediaType.PLUGIN)
@Path("/{name}/install")
@StatusCodes({
@ResponseCode(code = 200, condition = "success"),
@ResponseCode(code = 500, condition = "internal server error")
})
public Response installPlugin(@PathParam("name") String name, @PathParam("version") String version) {
public Response installPlugin(@PathParam("name") String name, @QueryParam("restart") boolean restartAfterInstallation) {
PluginPermissions.manage().check();
pluginManager.install(name + ":" + version);
pluginManager.install(name, restartAfterInstallation);
return Response.ok().build();
}
@POST
@Path("/install-pending")
@StatusCodes({
@ResponseCode(code = 200, condition = "success"),
@ResponseCode(code = 500, condition = "internal server error")
})
public Response installPending() {
PluginPermissions.manage().check();
pluginManager.installPendingAndRestart();
return Response.ok().build();
}
}

View File

@@ -3,11 +3,10 @@ package sonia.scm.api.v2.resources;
import com.webcohesion.enunciate.metadata.rs.ResponseCode;
import com.webcohesion.enunciate.metadata.rs.StatusCodes;
import com.webcohesion.enunciate.metadata.rs.TypeHint;
import sonia.scm.plugin.Plugin;
import sonia.scm.plugin.PluginLoader;
import sonia.scm.plugin.InstalledPlugin;
import sonia.scm.plugin.InstalledPluginDescriptor;
import sonia.scm.plugin.PluginManager;
import sonia.scm.plugin.PluginPermissions;
import sonia.scm.plugin.PluginWrapper;
import sonia.scm.web.VndMediaType;
import javax.inject.Inject;
@@ -16,7 +15,6 @@ import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.core.Response;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
@@ -25,17 +23,15 @@ import static sonia.scm.NotFoundException.notFound;
public class InstalledPluginResource {
private final PluginLoader pluginLoader;
private final PluginDtoCollectionMapper collectionMapper;
private final PluginDtoMapper mapper;
private final PluginManager pluginManager;
@Inject
public InstalledPluginResource(PluginLoader pluginLoader, PluginDtoCollectionMapper collectionMapper, PluginDtoMapper mapper, PluginManager pluginManager) {
this.pluginLoader = pluginLoader;
public InstalledPluginResource(PluginManager pluginManager, PluginDtoCollectionMapper collectionMapper, PluginDtoMapper mapper) {
this.pluginManager = pluginManager;
this.collectionMapper = collectionMapper;
this.mapper = mapper;
this.pluginManager = pluginManager;
}
/**
@@ -53,8 +49,8 @@ public class InstalledPluginResource {
@Produces(VndMediaType.PLUGIN_COLLECTION)
public Response getInstalledPlugins() {
PluginPermissions.read().check();
List<PluginWrapper> plugins = new ArrayList<>(pluginLoader.getInstalledPlugins());
return Response.ok(collectionMapper.map(plugins)).build();
List<InstalledPlugin> plugins = pluginManager.getInstalled();
return Response.ok(collectionMapper.mapInstalled(plugins)).build();
}
/**
@@ -75,15 +71,11 @@ public class InstalledPluginResource {
@Produces(VndMediaType.PLUGIN)
public Response getInstalledPlugin(@PathParam("name") String name) {
PluginPermissions.read().check();
Optional<PluginDto> pluginDto = pluginLoader.getInstalledPlugins()
.stream()
.filter(plugin -> name.equals(plugin.getPlugin().getInformation().getName()))
.map(mapper::map)
.findFirst();
Optional<InstalledPlugin> pluginDto = pluginManager.getInstalled(name);
if (pluginDto.isPresent()) {
return Response.ok(pluginDto.get()).build();
return Response.ok(mapper.mapInstalled(pluginDto.get())).build();
} else {
throw notFound(entity(Plugin.class, name));
throw notFound(entity(InstalledPluginDescriptor.class, name));
}
}
}

View File

@@ -6,9 +6,12 @@ import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
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;
@@ -18,6 +21,8 @@ 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) {
add(links);

View File

@@ -3,11 +3,12 @@ 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.Link;
import de.otto.edison.hal.Links;
import sonia.scm.plugin.PluginInformation;
import sonia.scm.plugin.PluginWrapper;
import sonia.scm.plugin.AvailablePlugin;
import sonia.scm.plugin.InstalledPlugin;
import sonia.scm.plugin.PluginPermissions;
import java.util.Collection;
import java.util.List;
import static de.otto.edison.hal.Embedded.embeddedBuilder;
@@ -25,14 +26,14 @@ public class PluginDtoCollectionMapper {
this.mapper = mapper;
}
public HalRepresentation map(List<PluginWrapper> plugins) {
List<PluginDto> dtos = plugins.stream().map(mapper::map).collect(toList());
public HalRepresentation mapInstalled(List<InstalledPlugin> plugins) {
List<PluginDto> dtos = plugins.stream().map(mapper::mapInstalled).collect(toList());
return new HalRepresentation(createInstalledPluginsLinks(), embedDtos(dtos));
}
public HalRepresentation map(Collection<PluginInformation> plugins) {
List<PluginDto> dtos = plugins.stream().map(mapper::map).collect(toList());
return new HalRepresentation(createAvailablePluginsLinks(), embedDtos(dtos));
public HalRepresentation mapAvailable(List<AvailablePlugin> plugins) {
List<PluginDto> dtos = plugins.stream().map(mapper::mapAvailable).collect(toList());
return new HalRepresentation(createAvailablePluginsLinks(plugins), embedDtos(dtos));
}
private Links createInstalledPluginsLinks() {
@@ -43,14 +44,23 @@ public class PluginDtoCollectionMapper {
return linksBuilder.build();
}
private Links createAvailablePluginsLinks() {
private Links createAvailablePluginsLinks(List<AvailablePlugin> plugins) {
String baseUrl = resourceLinks.availablePluginCollection().self();
Links.Builder linksBuilder = linkingTo()
.with(Links.linkingTo().self(baseUrl).build());
if (PluginPermissions.manage().isPermitted() && containsPending(plugins)) {
linksBuilder.single(Link.link("installPending", resourceLinks.availablePluginCollection().installPending()));
}
return linksBuilder.build();
}
private boolean containsPending(List<AvailablePlugin> plugins) {
return plugins.stream().anyMatch(AvailablePlugin::isPending);
}
private Embedded embedDtos(List<PluginDto> dtos) {
return embeddedBuilder()
.with("plugins", dtos)

View File

@@ -1,13 +1,13 @@
package sonia.scm.api.v2.resources;
import de.otto.edison.hal.Links;
import org.mapstruct.AfterMapping;
import org.mapstruct.Mapper;
import org.mapstruct.MappingTarget;
import org.mapstruct.ObjectFactory;
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.PluginWrapper;
import sonia.scm.plugin.PluginPermissions;
import javax.inject.Inject;
@@ -20,35 +20,50 @@ public abstract class PluginDtoMapper {
@Inject
private ResourceLinks resourceLinks;
public PluginDto map(PluginWrapper plugin) {
return map(plugin.getPlugin().getInformation());
public abstract void map(PluginInformation plugin, @MappingTarget PluginDto dto);
public PluginDto mapInstalled(InstalledPlugin plugin) {
PluginDto dto = createDtoForInstalled(plugin);
map(dto, plugin);
return dto;
}
public abstract PluginDto map(PluginInformation plugin);
public PluginDto mapAvailable(AvailablePlugin plugin) {
PluginDto dto = createDtoForAvailable(plugin);
map(dto, plugin);
dto.setPending(plugin.isPending());
return dto;
}
@AfterMapping
protected void appendCategory(@MappingTarget PluginDto 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");
}
}
@ObjectFactory
public PluginDto createDto(PluginInformation pluginInformation) {
Links.Builder linksBuilder;
if (pluginInformation.getState() != null && pluginInformation.getState().equals(PluginState.AVAILABLE)) {
linksBuilder = linkingTo()
.self(resourceLinks.availablePlugin()
.self(pluginInformation.getName(), pluginInformation.getVersion()));
private PluginDto createDtoForAvailable(AvailablePlugin plugin) {
PluginInformation information = plugin.getDescriptor().getInformation();
linksBuilder.single(link("install", resourceLinks.availablePlugin().install(pluginInformation.getName(), pluginInformation.getVersion())));
}
else {
linksBuilder = linkingTo()
.self(resourceLinks.installedPlugin()
.self(pluginInformation.getName()));
Links.Builder links = linkingTo()
.self(resourceLinks.availablePlugin()
.self(information.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();
}
}
@@ -714,6 +715,10 @@ class ResourceLinks {
availablePluginCollectionLinkBuilder = new LinkBuilder(pathInfo, PluginRootResource.class, AvailablePluginResource.class);
}
String installPending() {
return availablePluginCollectionLinkBuilder.method("availablePlugins").parameters().method("installPending").parameters().href();
}
String self() {
return availablePluginCollectionLinkBuilder.method("availablePlugins").parameters().method("getAvailablePlugins").parameters().href();
}

View File

@@ -4,7 +4,7 @@ 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.PluginWrapper;
import sonia.scm.plugin.InstalledPlugin;
import java.util.Collection;
import java.util.List;
@@ -24,7 +24,7 @@ public class UIPluginDtoCollectionMapper {
this.mapper = mapper;
}
public HalRepresentation map(Collection<PluginWrapper> plugins) {
public HalRepresentation map(Collection<InstalledPlugin> plugins) {
List<UIPluginDto> dtos = plugins.stream().map(mapper::map).collect(toList());
return new HalRepresentation(createLinks(), embedDtos(dtos));
}

View File

@@ -2,7 +2,7 @@ package sonia.scm.api.v2.resources;
import com.google.common.base.Strings;
import de.otto.edison.hal.Links;
import sonia.scm.plugin.PluginWrapper;
import sonia.scm.plugin.InstalledPlugin;
import sonia.scm.util.HttpUtil;
import javax.inject.Inject;
@@ -25,9 +25,9 @@ public class UIPluginDtoMapper {
this.request = request;
}
public UIPluginDto map(PluginWrapper plugin) {
public UIPluginDto map(InstalledPlugin plugin) {
UIPluginDto dto = new UIPluginDto(
plugin.getPlugin().getInformation().getName(),
plugin.getDescriptor().getInformation().getName(),
getScriptResources(plugin)
);
@@ -40,8 +40,8 @@ public class UIPluginDtoMapper {
return dto;
}
private Set<String> getScriptResources(PluginWrapper wrapper) {
Set<String> scriptResources = wrapper.getPlugin().getResources().getScriptResources();
private Set<String> getScriptResources(InstalledPlugin wrapper) {
Set<String> scriptResources = wrapper.getDescriptor().getResources().getScriptResources();
if (scriptResources != null) {
return scriptResources.stream()
.map(this::addContextPath)

View File

@@ -4,7 +4,7 @@ import com.webcohesion.enunciate.metadata.rs.ResponseCode;
import com.webcohesion.enunciate.metadata.rs.StatusCodes;
import com.webcohesion.enunciate.metadata.rs.TypeHint;
import sonia.scm.plugin.PluginLoader;
import sonia.scm.plugin.PluginWrapper;
import sonia.scm.plugin.InstalledPlugin;
import sonia.scm.security.AllowAnonymousAccess;
import sonia.scm.web.VndMediaType;
@@ -46,7 +46,7 @@ public class UIPluginResource {
@TypeHint(CollectionDto.class)
@Produces(VndMediaType.UI_PLUGIN_COLLECTION)
public Response getInstalledPlugins() {
List<PluginWrapper> plugins = pluginLoader.getInstalledPlugins()
List<InstalledPlugin> plugins = pluginLoader.getInstalledPlugins()
.stream()
.filter(this::filter)
.collect(Collectors.toList());
@@ -85,8 +85,8 @@ public class UIPluginResource {
}
}
private boolean filter(PluginWrapper plugin) {
return plugin.getPlugin().getResources() != null;
private boolean filter(InstalledPlugin plugin) {
return plugin.getDescriptor().getResources() != null;
}
}