Implements Hg Config Sub Resources

This commit is contained in:
Johannes Schnatterer
2018-08-02 18:36:28 +02:00
parent 7107d14bce
commit b65a8c6b8d
12 changed files with 402 additions and 12 deletions

View File

@@ -0,0 +1,68 @@
package sonia.scm.api.v2.resources;
import com.google.inject.Inject;
import com.webcohesion.enunciate.metadata.rs.ResponseCode;
import com.webcohesion.enunciate.metadata.rs.StatusCodes;
import com.webcohesion.enunciate.metadata.rs.TypeHint;
import sonia.scm.config.ConfigurationPermissions;
import sonia.scm.repository.HgConfig;
import sonia.scm.repository.HgRepositoryHandler;
import javax.ws.rs.PUT;
import javax.ws.rs.Path;
import javax.ws.rs.core.Response;
public class HgConfigAutoConfigurationResource {
private final HgRepositoryHandler repositoryHandler;
@Inject
public HgConfigAutoConfigurationResource(HgRepositoryHandler repositoryHandler) {
this.repositoryHandler = repositoryHandler;
}
/**
* Sets the default hg config and installs the hg binary.
*/
@PUT
@Path("")
@StatusCodes({
@ResponseCode(code = 204, condition = "update success"),
@ResponseCode(code = 401, condition = "not authenticated / invalid credentials"),
@ResponseCode(code = 403, condition = "not authorized, the current user does not have the \"configuration:write:hg\" privilege"),
@ResponseCode(code = 500, condition = "internal server error")
})
@TypeHint(TypeHint.NO_CONTENT.class)
public Response autoConfiguration() {
return autoConfiguration(null);
}
/**
* Modifies the hg config and installs the hg binary.
*
* @param configDto new configuration object
*/
@PUT
@Path("")
@StatusCodes({
@ResponseCode(code = 204, condition = "update success"),
@ResponseCode(code = 401, condition = "not authenticated / invalid credentials"),
@ResponseCode(code = 403, condition = "not authorized, the current user does not have the \"configuration:write:hg\" privilege"),
@ResponseCode(code = 500, condition = "internal server error")
})
@TypeHint(TypeHint.NO_CONTENT.class)
public Response autoConfiguration(HgConfigDto configDto) {
HgConfig config = repositoryHandler.getConfig();
if (config == null) {
config = new HgConfig();
repositoryHandler.setConfig(config);
}
ConfigurationPermissions.write(config).check();
repositoryHandler.doAutoConfiguration(config);
return Response.noContent().build();
}
}

View File

@@ -0,0 +1,22 @@
package sonia.scm.api.v2.resources;
import de.otto.edison.hal.HalRepresentation;
import de.otto.edison.hal.Links;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import java.util.List;
@NoArgsConstructor
@Getter
@Setter
public class HgConfigInstallationsDto extends HalRepresentation {
public HgConfigInstallationsDto(Links links, List<String> paths) {
super(links);
this.paths = paths;
}
private List<String> paths;
}

View File

@@ -0,0 +1,65 @@
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 de.otto.edison.hal.HalRepresentation;
import sonia.scm.config.ConfigurationPermissions;
import sonia.scm.installer.HgInstallerFactory;
import sonia.scm.repository.HgConfig;
import sonia.scm.web.HgVndMediaType;
import javax.inject.Inject;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
public class HgConfigInstallationsResource {
private final HgConfigInstallationsToDtoMapper hgConfigInstallationsToDtoMapper;
@Inject
public HgConfigInstallationsResource(HgConfigInstallationsToDtoMapper hgConfigInstallationsToDtoMapper) {
this.hgConfigInstallationsToDtoMapper = hgConfigInstallationsToDtoMapper;
}
/**
* Returns the hg installations.
*/
@GET
@Path("hg")
@Produces(HgVndMediaType.INSTALLATIONS)
@TypeHint(HalRepresentation.class)
@StatusCodes({
@ResponseCode(code = 200, condition = "success"),
@ResponseCode(code = 401, condition = "not authenticated / invalid credentials"),
@ResponseCode(code = 403, condition = "not authorized, the current user does not have the \"configuration:read:hg\" privilege"),
@ResponseCode(code = 500, condition = "internal server error")
})
public HalRepresentation getHgInstallations() {
ConfigurationPermissions.read(HgConfig.PERMISSION).check();
return hgConfigInstallationsToDtoMapper.map(HgInstallerFactory.createInstaller().getHgInstallations());
}
/**
* Returns the python installations.
*/
@GET
@Path("python")
@Produces(HgVndMediaType.INSTALLATIONS)
@TypeHint(HalRepresentation.class)
@StatusCodes({
@ResponseCode(code = 200, condition = "success"),
@ResponseCode(code = 401, condition = "not authenticated / invalid credentials"),
@ResponseCode(code = 403, condition = "not authorized, the current user does not have the \"configuration:read:hg\" privilege"),
@ResponseCode(code = 500, condition = "internal server error")
})
public HalRepresentation getPythonInstallations() {
ConfigurationPermissions.read(HgConfig.PERMISSION).check();
return hgConfigInstallationsToDtoMapper.map(HgInstallerFactory.createInstaller().getPythonInstallations());
}
}

View File

@@ -0,0 +1,21 @@
package sonia.scm.api.v2.resources;
import de.otto.edison.hal.HalRepresentation;
import javax.inject.Inject;
import java.util.List;
import static de.otto.edison.hal.Links.linkingTo;
public class HgConfigInstallationsToDtoMapper {
@Inject private UriInfoStore uriInfoStore;
public HalRepresentation map(List<String> installations) {
return new HgConfigInstallationsDto(linkingTo().self(createSelfLink()).build(), installations);
}
private String createSelfLink() {
LinkBuilder linkBuilder = new LinkBuilder(uriInfoStore.get(), HgConfigInstallationsResource.class);
return linkBuilder.method("get").parameters().href();
}
}

View File

@@ -0,0 +1,22 @@
package sonia.scm.api.v2.resources;
import sonia.scm.installer.HgPackage;
import javax.inject.Inject;
public class HgConfigPackageCollectionToDtoMapper extends CollectionToDtoMapper<HgPackage, HgConfigPackageDto> {
private UriInfoStore uriInfoStore;
@Inject
public HgConfigPackageCollectionToDtoMapper(HgConfigPackageToDtoMapper mapper, UriInfoStore uriInfoStore) {
super("packages", mapper);
this.uriInfoStore = uriInfoStore;
}
@Override
protected String createSelfLink() {
LinkBuilder linkBuilder = new LinkBuilder(uriInfoStore.get(), HgConfigResource.class);
return linkBuilder.method("get").parameters().href();
}
}

View File

@@ -0,0 +1,28 @@
package sonia.scm.api.v2.resources;
import de.otto.edison.hal.HalRepresentation;
import de.otto.edison.hal.Links;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import sonia.scm.repository.HgConfig;
@NoArgsConstructor
@Getter
@Setter
public class HgConfigPackageDto extends HalRepresentation {
private String arch;
private HgConfig hgConfigTemplate;
private String hgVersion;
private String id;
private String platform;
private String pythonVersion;
private long size;
private String url;
@Override
protected HalRepresentation add(Links links) {
return super.add(links);
}
}

View File

@@ -0,0 +1,96 @@
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 de.otto.edison.hal.HalRepresentation;
import sonia.scm.SCMContext;
import sonia.scm.config.ConfigurationPermissions;
import sonia.scm.installer.HgInstallerFactory;
import sonia.scm.installer.HgPackage;
import sonia.scm.installer.HgPackageReader;
import sonia.scm.net.ahc.AdvancedHttpClient;
import sonia.scm.repository.HgConfig;
import sonia.scm.repository.HgRepositoryHandler;
import sonia.scm.web.HgVndMediaType;
import javax.inject.Inject;
import javax.ws.rs.GET;
import javax.ws.rs.PUT;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.core.Response;
public class HgConfigPackageResource {
private final HgPackageReader pkgReader;
private final AdvancedHttpClient client;
private final HgRepositoryHandler handler;
private final HgConfigPackageCollectionToDtoMapper configPackageCollectionToDtoMapper;
@Inject
public HgConfigPackageResource(HgPackageReader pkgReader, AdvancedHttpClient client, HgRepositoryHandler handler,
HgConfigPackageCollectionToDtoMapper configPackageCollectionToDtoMapper) {
this.pkgReader = pkgReader;
this.client = client;
this.handler = handler;
this.configPackageCollectionToDtoMapper = configPackageCollectionToDtoMapper;
}
/**
* Returns all mercurial packages.
*/
@GET
@Path("")
@Produces(HgVndMediaType.PACKAGES)
@StatusCodes({
@ResponseCode(code = 204, condition = "update success"),
@ResponseCode(code = 401, condition = "not authenticated / invalid credentials"),
@ResponseCode(code = 403, condition = "not authorized, the current user does not have the \"configuration:read:hg\" privilege"),
@ResponseCode(code = 500, condition = "internal server error")
})
@TypeHint(HalRepresentation.class)
public HalRepresentation getPackages() {
ConfigurationPermissions.read(HgConfig.PERMISSION).check();
return configPackageCollectionToDtoMapper.map(pkgReader.getPackages().getPackages());
}
/**
* Installs a mercurial package
*
* @param id Identifier of the package to install
*/
@PUT
@Path("{pkgId}")
@StatusCodes({
@ResponseCode(code = 204, condition = "update success"),
@ResponseCode(code = 401, condition = "not authenticated / invalid credentials"),
@ResponseCode(code = 403, condition = "not authorized, the current user does not have the \"configuration:write:hg\" privilege"),
@ResponseCode(code = 404, condition = "no package found for id"),
@ResponseCode(code = 500, condition = "internal server error")
})
@TypeHint(TypeHint.NO_CONTENT.class)
public Response installPackage(@PathParam("pkgId") String id) {
Response response;
ConfigurationPermissions.write(HgConfig.PERMISSION).check();
HgPackage pkg = pkgReader.getPackage(id);
if (pkg != null) {
if (HgInstallerFactory.createInstaller().installPackage(client, handler,
SCMContext.getContext().getBaseDirectory(), pkg)) {
response = Response.noContent().build();
} else {
response = Response.status(Response.Status.INTERNAL_SERVER_ERROR).build();
}
} else {
response = Response.status(Response.Status.NOT_FOUND).build();
}
return response;
}
}

View File

@@ -0,0 +1,28 @@
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 sonia.scm.installer.HgPackage;
import javax.inject.Inject;
import static de.otto.edison.hal.Links.linkingTo;
@Mapper
public abstract class HgConfigPackageToDtoMapper extends BaseMapper<HgPackage, HgConfigPackageDto> {
@Inject
private UriInfoStore uriInfoStore;
@AfterMapping
void appendLinks(@MappingTarget HgConfigPackageDto target) {
Links.Builder linksBuilder = linkingTo().self(self());
target.add(linksBuilder.build());
}
private String self() {
LinkBuilder linkBuilder = new LinkBuilder(uriInfoStore.get(), HgConfigPackageResource.class);
return linkBuilder.method("get").parameters().href();
}
}

View File

@@ -9,6 +9,7 @@ import sonia.scm.repository.HgRepositoryHandler;
import sonia.scm.web.HgVndMediaType;
import javax.inject.Inject;
import javax.inject.Provider;
import javax.ws.rs.Consumes;
import javax.ws.rs.GET;
import javax.ws.rs.PUT;
@@ -23,16 +24,25 @@ import javax.ws.rs.core.Response;
public class HgConfigResource {
static final String HG_CONFIG_PATH_V2 = "v2/config/hg";
private final HgConfigDtoToHgConfigMapper dtoToConfigMapper;
private final HgConfigToHgConfigDtoMapper configToDtoMapper;
private final HgRepositoryHandler repositoryHandler;
private final Provider<HgConfigPackageResource> packagesResource;
private final Provider<HgConfigAutoConfigurationResource> autoconfigResource;
private final Provider<HgConfigInstallationsResource> installationsResource;
@Inject
public HgConfigResource(HgConfigDtoToHgConfigMapper dtoToConfigMapper, HgConfigToHgConfigDtoMapper configToDtoMapper,
HgRepositoryHandler repositoryHandler) {
HgRepositoryHandler repositoryHandler, Provider<HgConfigPackageResource> packagesResource,
Provider<HgConfigAutoConfigurationResource> autoconfigResource,
Provider<HgConfigInstallationsResource> installationsResource) {
this.dtoToConfigMapper = dtoToConfigMapper;
this.configToDtoMapper = configToDtoMapper;
this.repositoryHandler = repositoryHandler;
this.packagesResource = packagesResource;
this.autoconfigResource = autoconfigResource;
this.installationsResource = installationsResource;
}
/**
@@ -40,7 +50,7 @@ public class HgConfigResource {
*/
@GET
@Path("")
@Produces(HgVndMediaType.HG_CONFIG)
@Produces(HgVndMediaType.CONFIG)
@TypeHint(HgConfigDto.class)
@StatusCodes({
@ResponseCode(code = 200, condition = "success"),
@@ -69,7 +79,7 @@ public class HgConfigResource {
*/
@PUT
@Path("")
@Consumes(HgVndMediaType.HG_CONFIG)
@Consumes(HgVndMediaType.CONFIG)
@StatusCodes({
@ResponseCode(code = 204, condition = "update success"),
@ResponseCode(code = 401, condition = "not authenticated / invalid credentials"),
@@ -89,10 +99,18 @@ public class HgConfigResource {
return Response.noContent().build();
}
@Path("packages")
public HgConfigPackageResource getPackagesResource() {
return packagesResource.get();
}
// TODO
// * `packages`
// * `packages/{pkgId}`
// * `installations/hg`
// * `installations/python
@Path("auto-configuration")
public HgConfigAutoConfigurationResource getAutoConfigurationResource() {
return autoconfigResource.get();
}
@Path("installations")
public HgConfigInstallationsResource getInstallationsResource() {
return installationsResource.get();
}
}

View File

@@ -38,6 +38,9 @@ package sonia.scm.web;
import com.google.inject.servlet.ServletModule;
import org.mapstruct.factory.Mappers;
import sonia.scm.api.v2.resources.HgConfigDtoToHgConfigMapper;
import sonia.scm.api.v2.resources.HgConfigInstallationsToDtoMapper;
import sonia.scm.api.v2.resources.HgConfigPackageCollectionToDtoMapper;
import sonia.scm.api.v2.resources.HgConfigPackageToDtoMapper;
import sonia.scm.api.v2.resources.HgConfigToHgConfigDtoMapper;
import sonia.scm.installer.HgPackageReader;
import sonia.scm.plugin.Extension;
@@ -74,6 +77,9 @@ public class HgServletModule extends ServletModule
bind(HgConfigDtoToHgConfigMapper.class).to(Mappers.getMapper(HgConfigDtoToHgConfigMapper.class).getClass());
bind(HgConfigToHgConfigDtoMapper.class).to(Mappers.getMapper(HgConfigToHgConfigDtoMapper.class).getClass());
bind(HgConfigPackageToDtoMapper.class).to(Mappers.getMapper(HgConfigPackageToDtoMapper.class).getClass());
bind(HgConfigPackageCollectionToDtoMapper.class);
bind(HgConfigInstallationsToDtoMapper.class);
// bind servlets
serve(MAPPING_HOOK).with(HgHookCallbackServlet.class);

View File

@@ -2,7 +2,11 @@ package sonia.scm.web;
public class HgVndMediaType {
public static final String HG_CONFIG = VndMediaType.PREFIX + "hgConfig" + VndMediaType.SUFFIX;
private static final String PREFIX = VndMediaType.PREFIX + "hgConfig";
public static final String CONFIG = PREFIX + VndMediaType.SUFFIX;
public static final String PACKAGES = PREFIX + "-packages" + VndMediaType.SUFFIX;
public static final String INSTALLATIONS = PREFIX + "-installation" + VndMediaType.SUFFIX;
private HgVndMediaType() {
}

View File

@@ -21,6 +21,7 @@ import sonia.scm.repository.HgConfig;
import sonia.scm.repository.HgRepositoryHandler;
import sonia.scm.web.HgVndMediaType;
import javax.inject.Provider;
import javax.servlet.http.HttpServletResponse;
import java.io.File;
import java.io.IOException;
@@ -61,11 +62,22 @@ public class HgConfigResourceTest {
@Mock
private HgRepositoryHandler repositoryHandler;
@Mock
private Provider<HgConfigPackageResource> packagesResource;
@Mock
private Provider<HgConfigAutoConfigurationResource> autoconfigResource;
@Mock
private Provider<HgConfigInstallationsResource> installationsResource;
@Before
public void prepareEnvironment() {
HgConfig gitConfig = createConfiguration();
when(repositoryHandler.getConfig()).thenReturn(gitConfig);
HgConfigResource gitConfigResource = new HgConfigResource(dtoToConfigMapper, configToDtoMapper, repositoryHandler);
HgConfigResource gitConfigResource =
new HgConfigResource(dtoToConfigMapper, configToDtoMapper, repositoryHandler, packagesResource,
autoconfigResource, installationsResource);
dispatcher.getRegistry().addSingletonResource(gitConfigResource);
when(uriInfoStore.get().getBaseUri()).thenReturn(baseUri);
}
@@ -88,7 +100,7 @@ public class HgConfigResourceTest {
@Test
@SubjectAware(username = "readWrite")
public void shouldGetHgConfigEvenWhenItsEmpty() throws URISyntaxException, IOException {
public void shouldGetHgConfigEvenWhenItsEmpty() throws URISyntaxException {
when(repositoryHandler.getConfig()).thenReturn(null);
MockHttpResponse response = get();
@@ -139,7 +151,7 @@ public class HgConfigResourceTest {
private MockHttpResponse put() throws URISyntaxException {
MockHttpRequest request = MockHttpRequest.put("/" + HgConfigResource.HG_CONFIG_PATH_V2)
.contentType(HgVndMediaType.HG_CONFIG)
.contentType(HgVndMediaType.CONFIG)
.content("{\"disabled\":true}".getBytes());
MockHttpResponse response = new MockHttpResponse();