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 sonia.scm.web.HgVndMediaType;
import javax.inject.Inject; import javax.inject.Inject;
import javax.inject.Provider;
import javax.ws.rs.Consumes; import javax.ws.rs.Consumes;
import javax.ws.rs.GET; import javax.ws.rs.GET;
import javax.ws.rs.PUT; import javax.ws.rs.PUT;
@@ -23,16 +24,25 @@ import javax.ws.rs.core.Response;
public class HgConfigResource { public class HgConfigResource {
static final String HG_CONFIG_PATH_V2 = "v2/config/hg"; static final String HG_CONFIG_PATH_V2 = "v2/config/hg";
private final HgConfigDtoToHgConfigMapper dtoToConfigMapper; private final HgConfigDtoToHgConfigMapper dtoToConfigMapper;
private final HgConfigToHgConfigDtoMapper configToDtoMapper; private final HgConfigToHgConfigDtoMapper configToDtoMapper;
private final HgRepositoryHandler repositoryHandler; private final HgRepositoryHandler repositoryHandler;
private final Provider<HgConfigPackageResource> packagesResource;
private final Provider<HgConfigAutoConfigurationResource> autoconfigResource;
private final Provider<HgConfigInstallationsResource> installationsResource;
@Inject @Inject
public HgConfigResource(HgConfigDtoToHgConfigMapper dtoToConfigMapper, HgConfigToHgConfigDtoMapper configToDtoMapper, public HgConfigResource(HgConfigDtoToHgConfigMapper dtoToConfigMapper, HgConfigToHgConfigDtoMapper configToDtoMapper,
HgRepositoryHandler repositoryHandler) { HgRepositoryHandler repositoryHandler, Provider<HgConfigPackageResource> packagesResource,
Provider<HgConfigAutoConfigurationResource> autoconfigResource,
Provider<HgConfigInstallationsResource> installationsResource) {
this.dtoToConfigMapper = dtoToConfigMapper; this.dtoToConfigMapper = dtoToConfigMapper;
this.configToDtoMapper = configToDtoMapper; this.configToDtoMapper = configToDtoMapper;
this.repositoryHandler = repositoryHandler; this.repositoryHandler = repositoryHandler;
this.packagesResource = packagesResource;
this.autoconfigResource = autoconfigResource;
this.installationsResource = installationsResource;
} }
/** /**
@@ -40,7 +50,7 @@ public class HgConfigResource {
*/ */
@GET @GET
@Path("") @Path("")
@Produces(HgVndMediaType.HG_CONFIG) @Produces(HgVndMediaType.CONFIG)
@TypeHint(HgConfigDto.class) @TypeHint(HgConfigDto.class)
@StatusCodes({ @StatusCodes({
@ResponseCode(code = 200, condition = "success"), @ResponseCode(code = 200, condition = "success"),
@@ -69,7 +79,7 @@ public class HgConfigResource {
*/ */
@PUT @PUT
@Path("") @Path("")
@Consumes(HgVndMediaType.HG_CONFIG) @Consumes(HgVndMediaType.CONFIG)
@StatusCodes({ @StatusCodes({
@ResponseCode(code = 204, condition = "update success"), @ResponseCode(code = 204, condition = "update success"),
@ResponseCode(code = 401, condition = "not authenticated / invalid credentials"), @ResponseCode(code = 401, condition = "not authenticated / invalid credentials"),
@@ -89,10 +99,18 @@ public class HgConfigResource {
return Response.noContent().build(); return Response.noContent().build();
} }
@Path("packages")
public HgConfigPackageResource getPackagesResource() {
return packagesResource.get();
}
// TODO @Path("auto-configuration")
// * `packages` public HgConfigAutoConfigurationResource getAutoConfigurationResource() {
// * `packages/{pkgId}` return autoconfigResource.get();
// * `installations/hg` }
// * `installations/python
@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 com.google.inject.servlet.ServletModule;
import org.mapstruct.factory.Mappers; import org.mapstruct.factory.Mappers;
import sonia.scm.api.v2.resources.HgConfigDtoToHgConfigMapper; 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.api.v2.resources.HgConfigToHgConfigDtoMapper;
import sonia.scm.installer.HgPackageReader; import sonia.scm.installer.HgPackageReader;
import sonia.scm.plugin.Extension; import sonia.scm.plugin.Extension;
@@ -74,6 +77,9 @@ public class HgServletModule extends ServletModule
bind(HgConfigDtoToHgConfigMapper.class).to(Mappers.getMapper(HgConfigDtoToHgConfigMapper.class).getClass()); bind(HgConfigDtoToHgConfigMapper.class).to(Mappers.getMapper(HgConfigDtoToHgConfigMapper.class).getClass());
bind(HgConfigToHgConfigDtoMapper.class).to(Mappers.getMapper(HgConfigToHgConfigDtoMapper.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 // bind servlets
serve(MAPPING_HOOK).with(HgHookCallbackServlet.class); serve(MAPPING_HOOK).with(HgHookCallbackServlet.class);

View File

@@ -2,7 +2,11 @@ package sonia.scm.web;
public class HgVndMediaType { 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() { private HgVndMediaType() {
} }

View File

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