Devfile validation via message entity provider

7.20.x
Max Shaposhnik 2019-10-08 14:10:15 +03:00 committed by GitHub
parent 6e853c517b
commit fc531ea015
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 604 additions and 80 deletions

View File

@ -42,6 +42,7 @@ import org.eclipse.che.api.user.server.jpa.JpaPreferenceDao;
import org.eclipse.che.api.user.server.jpa.JpaUserDao;
import org.eclipse.che.api.user.server.spi.PreferenceDao;
import org.eclipse.che.api.user.server.spi.UserDao;
import org.eclipse.che.api.workspace.server.WorkspaceEntityProvider;
import org.eclipse.che.api.workspace.server.WorkspaceLockService;
import org.eclipse.che.api.workspace.server.WorkspaceStatusCache;
import org.eclipse.che.api.workspace.server.devfile.DevfileModule;
@ -148,6 +149,7 @@ public class WsMasterModule extends AbstractModule {
install(new DevfileModule());
bind(WorkspaceEntityProvider.class);
bind(org.eclipse.che.api.workspace.server.TemporaryWorkspaceRemover.class);
bind(org.eclipse.che.api.workspace.server.WorkspaceService.class);
install(new FactoryModuleBuilder().build(ServersCheckerFactory.class));

View File

@ -25,5 +25,6 @@ public class CoreRestModule extends AbstractModule {
bind(RuntimeExceptionMapper.class);
bind(ApiInfo.class).toProvider(ApiInfoProvider.class);
Multibinder.newSetBinder(binder(), Class.class, Names.named("che.json.ignored_classes"));
bind(WebApplicationExceptionMapper.class);
}
}

View File

@ -0,0 +1,89 @@
/*
* Copyright (c) 2012-2018 Red Hat, Inc.
* This program and the accompanying materials are made
* available under the terms of the Eclipse Public License 2.0
* which is available at https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*
* Contributors:
* Red Hat, Inc. - initial API and implementation
*/
package org.eclipse.che.api.core.rest;
import static org.eclipse.che.dto.server.DtoFactory.newDto;
import javax.inject.Singleton;
import javax.ws.rs.BadRequestException;
import javax.ws.rs.ForbiddenException;
import javax.ws.rs.NotAcceptableException;
import javax.ws.rs.NotAllowedException;
import javax.ws.rs.NotAuthorizedException;
import javax.ws.rs.NotFoundException;
import javax.ws.rs.NotSupportedException;
import javax.ws.rs.WebApplicationException;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.Response.Status;
import javax.ws.rs.ext.ExceptionMapper;
import javax.ws.rs.ext.Provider;
import org.eclipse.che.api.core.rest.shared.dto.ServiceError;
import org.eclipse.che.dto.server.DtoFactory;
/**
* Mapper for the {@link WebApplicationException} exceptions.
*
* @author Max Shaposhnyk
*/
@Provider
@Singleton
public class WebApplicationExceptionMapper implements ExceptionMapper<WebApplicationException> {
@Override
public Response toResponse(WebApplicationException exception) {
ServiceError error = newDto(ServiceError.class).withMessage(exception.getMessage());
if (exception instanceof BadRequestException) {
return Response.status(Response.Status.BAD_REQUEST)
.entity(DtoFactory.getInstance().toJson(error))
.type(MediaType.APPLICATION_JSON)
.build();
} else if (exception instanceof ForbiddenException) {
return Response.status(Response.Status.FORBIDDEN)
.entity(DtoFactory.getInstance().toJson(error))
.type(MediaType.APPLICATION_JSON)
.build();
} else if (exception instanceof NotFoundException) {
return Response.status(Response.Status.NOT_FOUND)
.entity(DtoFactory.getInstance().toJson(error))
.type(MediaType.APPLICATION_JSON)
.build();
} else if (exception instanceof NotAuthorizedException) {
return Response.status(Response.Status.UNAUTHORIZED)
.entity(DtoFactory.getInstance().toJson(error))
.type(MediaType.APPLICATION_JSON)
.build();
} else if (exception instanceof NotAcceptableException) {
return Response.status(Status.NOT_ACCEPTABLE)
.entity(DtoFactory.getInstance().toJson(error))
.type(MediaType.APPLICATION_JSON)
.build();
} else if (exception instanceof NotAllowedException) {
return Response.status(Status.METHOD_NOT_ALLOWED)
.entity(DtoFactory.getInstance().toJson(error))
.type(MediaType.APPLICATION_JSON)
.build();
} else if (exception instanceof NotSupportedException) {
return Response.status(Status.UNSUPPORTED_MEDIA_TYPE)
.entity(DtoFactory.getInstance().toJson(error))
.type(MediaType.APPLICATION_JSON)
.build();
} else {
return Response.serverError()
.entity(DtoFactory.getInstance().toJson(error))
.type(MediaType.APPLICATION_JSON)
.build();
}
}
}

View File

@ -158,7 +158,7 @@ public class WorkspacePermissionsFilterTest {
.post(SECURE_PATH + "/workspace/devfile?namespace=userok");
assertEquals(response.getStatusCode(), 204);
verify(workspaceService).create(anyString(), any(), any(), eq("userok"), any());
verify(workspaceService).create(any(DevfileDto.class), any(), any(), eq("userok"), any());
verify(permissionsFilter).checkAccountPermissions("userok", AccountOperation.CREATE_WORKSPACE);
verifyZeroInteractions(subject);
}

View File

@ -0,0 +1,138 @@
/*
* Copyright (c) 2012-2018 Red Hat, Inc.
* This program and the accompanying materials are made
* available under the terms of the Eclipse Public License 2.0
* which is available at https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*
* Contributors:
* Red Hat, Inc. - initial API and implementation
*/
package org.eclipse.che.api.workspace.server;
import static javax.ws.rs.core.MediaType.APPLICATION_JSON;
import static org.eclipse.che.api.workspace.server.DtoConverter.asDto;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.JsonDeserializer;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.module.SimpleModule;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.Writer;
import java.lang.annotation.Annotation;
import java.lang.reflect.Type;
import java.nio.charset.StandardCharsets;
import javax.inject.Inject;
import javax.inject.Singleton;
import javax.ws.rs.BadRequestException;
import javax.ws.rs.Consumes;
import javax.ws.rs.Produces;
import javax.ws.rs.WebApplicationException;
import javax.ws.rs.core.HttpHeaders;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.MultivaluedMap;
import javax.ws.rs.ext.MessageBodyReader;
import javax.ws.rs.ext.MessageBodyWriter;
import javax.ws.rs.ext.Provider;
import org.eclipse.che.api.workspace.server.devfile.DevfileManager;
import org.eclipse.che.api.workspace.server.devfile.exception.DevfileFormatException;
import org.eclipse.che.api.workspace.server.dto.DtoServerImpls.WorkspaceDtoImpl;
import org.eclipse.che.api.workspace.shared.dto.WorkspaceDto;
import org.eclipse.che.api.workspace.shared.dto.devfile.DevfileDto;
import org.eclipse.che.dto.server.DtoFactory;
/**
* Entity provider for {@link WorkspaceDto}. Performs schema validation of devfile part of the
* workspace before actual {@link DevfileDto} creation.
*
* @author Max Shaposhnyk
*/
@Singleton
@Provider
@Produces({APPLICATION_JSON})
@Consumes({APPLICATION_JSON})
public class WorkspaceEntityProvider
implements MessageBodyReader<WorkspaceDto>, MessageBodyWriter<WorkspaceDto> {
private DevfileManager devfileManager;
private ObjectMapper mapper = new ObjectMapper();
@Inject
public WorkspaceEntityProvider(DevfileManager devfileManager) {
this.devfileManager = devfileManager;
SimpleModule module = new SimpleModule();
module.addDeserializer(DevfileDto.class, new DevfileDtoDeserializer());
mapper.registerModule(module);
}
@Override
public boolean isReadable(
Class<?> type, Type genericType, Annotation[] annotations, MediaType mediaType) {
return type == WorkspaceDto.class;
}
@Override
public WorkspaceDto readFrom(
Class<WorkspaceDto> type,
Type genericType,
Annotation[] annotations,
MediaType mediaType,
MultivaluedMap<String, String> httpHeaders,
InputStream entityStream)
throws IOException, WebApplicationException {
return mapper
.readerFor(WorkspaceDtoImpl.class)
.without(DeserializationFeature.WRAP_EXCEPTIONS)
.readValue(entityStream);
}
@Override
public boolean isWriteable(
Class<?> type, Type genericType, Annotation[] annotations, MediaType mediaType) {
return WorkspaceDto.class.isAssignableFrom(type);
}
@Override
public long getSize(
WorkspaceDto workspaceDto,
Class<?> type,
Type genericType,
Annotation[] annotations,
MediaType mediaType) {
return -1;
}
@Override
public void writeTo(
WorkspaceDto workspaceDto,
Class<?> type,
Type genericType,
Annotation[] annotations,
MediaType mediaType,
MultivaluedMap<String, Object> httpHeaders,
OutputStream entityStream)
throws IOException, WebApplicationException {
httpHeaders.putSingle(HttpHeaders.CACHE_CONTROL, "public, no-cache, no-store, no-transform");
try (Writer w = new OutputStreamWriter(entityStream, StandardCharsets.UTF_8)) {
w.write(DtoFactory.getInstance().toJson(workspaceDto));
w.flush();
}
}
class DevfileDtoDeserializer extends JsonDeserializer<DevfileDto> {
@Override
public DevfileDto deserialize(JsonParser p, DeserializationContext ctxt) throws IOException {
try {
return asDto(devfileManager.parseJson(p.readValueAsTree().toString()));
} catch (DevfileFormatException e) {
throw new BadRequestException(e.getMessage());
}
}
}
}

View File

@ -17,14 +17,12 @@ import static java.util.Collections.emptyMap;
import static java.util.stream.Collectors.toList;
import static javax.ws.rs.core.HttpHeaders.CONTENT_TYPE;
import static javax.ws.rs.core.MediaType.APPLICATION_JSON;
import static javax.ws.rs.core.MediaType.APPLICATION_JSON_TYPE;
import static org.eclipse.che.api.workspace.server.DtoConverter.asDto;
import static org.eclipse.che.api.workspace.server.WorkspaceKeyValidator.validateKey;
import static org.eclipse.che.api.workspace.shared.Constants.CHE_WORKSPACE_AUTO_START;
import static org.eclipse.che.api.workspace.shared.Constants.CHE_WORKSPACE_DEVFILE_REGISTRY_URL_PROPERTY;
import static org.eclipse.che.api.workspace.shared.Constants.CHE_WORKSPACE_PLUGIN_REGISTRY_URL_PROPERTY;
import com.google.common.annotations.Beta;
import com.google.common.base.Joiner;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableMap.Builder;
@ -66,16 +64,13 @@ import org.eclipse.che.api.core.ValidationException;
import org.eclipse.che.api.core.model.workspace.Workspace;
import org.eclipse.che.api.core.model.workspace.config.ServerConfig;
import org.eclipse.che.api.core.rest.Service;
import org.eclipse.che.api.workspace.server.devfile.DevfileManager;
import org.eclipse.che.api.workspace.server.devfile.FileContentProvider;
import org.eclipse.che.api.workspace.server.devfile.URLFetcher;
import org.eclipse.che.api.workspace.server.devfile.URLFileContentProvider;
import org.eclipse.che.api.workspace.server.devfile.exception.DevfileException;
import org.eclipse.che.api.workspace.server.model.impl.CommandImpl;
import org.eclipse.che.api.workspace.server.model.impl.EnvironmentImpl;
import org.eclipse.che.api.workspace.server.model.impl.ProjectConfigImpl;
import org.eclipse.che.api.workspace.server.model.impl.WorkspaceImpl;
import org.eclipse.che.api.workspace.server.model.impl.devfile.DevfileImpl;
import org.eclipse.che.api.workspace.server.token.MachineAccessForbidden;
import org.eclipse.che.api.workspace.server.token.MachineTokenException;
import org.eclipse.che.api.workspace.server.token.MachineTokenProvider;
@ -89,6 +84,7 @@ import org.eclipse.che.api.workspace.shared.dto.RuntimeDto;
import org.eclipse.che.api.workspace.shared.dto.ServerDto;
import org.eclipse.che.api.workspace.shared.dto.WorkspaceConfigDto;
import org.eclipse.che.api.workspace.shared.dto.WorkspaceDto;
import org.eclipse.che.api.workspace.shared.dto.devfile.DevfileDto;
import org.eclipse.che.commons.annotation.Nullable;
import org.eclipse.che.commons.env.EnvironmentContext;
@ -110,7 +106,6 @@ public class WorkspaceService extends Service {
private final String apiEndpoint;
private final boolean cheWorkspaceAutoStart;
private final FileContentProvider devfileContentProvider;
private final DevfileManager devfileManager;
@Inject
public WorkspaceService(
@ -121,8 +116,7 @@ public class WorkspaceService extends Service {
WorkspaceLinksGenerator linksGenerator,
@Named(CHE_WORKSPACE_PLUGIN_REGISTRY_URL_PROPERTY) @Nullable String pluginRegistryUrl,
@Named(CHE_WORKSPACE_DEVFILE_REGISTRY_URL_PROPERTY) @Nullable String devfileRegistryUrl,
URLFetcher urlFetcher,
DevfileManager devfileManager) {
URLFetcher urlFetcher) {
this.apiEndpoint = apiEndpoint;
this.cheWorkspaceAutoStart = cheWorkspaceAutoStart;
this.workspaceManager = workspaceManager;
@ -131,7 +125,6 @@ public class WorkspaceService extends Service {
this.pluginRegistryUrl = pluginRegistryUrl;
this.devfileRegistryUrl = devfileRegistryUrl;
this.devfileContentProvider = new URLFileContentProvider(null, urlFetcher);
this.devfileManager = devfileManager;
}
@POST
@ -196,16 +189,12 @@ public class WorkspaceService extends Service {
return Response.status(201).entity(asDtoWithLinksAndToken(workspace)).build();
}
@Beta
@Path("/devfile")
@POST
@Consumes({APPLICATION_JSON, "text/yaml", "text/x-yaml"})
@Produces(APPLICATION_JSON)
@ApiOperation(
value = "Creates a new workspace based on the Devfile.",
notes =
"This method is in beta phase. It's strongly recommended to use `POST /devfile` instead"
+ " to get a workspace from Devfile. Workspaces created with this method are not stable yet.",
consumes = "application/json, text/yaml, text/x-yaml",
produces = APPLICATION_JSON,
nickname = "createFromDevfile",
@ -222,7 +211,8 @@ public class WorkspaceService extends Service {
@ApiResponse(code = 500, message = "Internal server error occurred")
})
public Response create(
@ApiParam(value = "The devfile of the workspace to create", required = true) String devfile,
@ApiParam(value = "The devfile of the workspace to create", required = true)
DevfileDto devfile,
@ApiParam(
value =
"Workspace attribute defined in 'attrName:attrValue' format. "
@ -242,29 +232,15 @@ public class WorkspaceService extends Service {
throws ConflictException, BadRequestException, ForbiddenException, NotFoundException,
ServerException {
requiredNotNull(devfile, "Devfile");
DevfileImpl devfileModel;
try {
if (APPLICATION_JSON_TYPE.isCompatible(contentType)) {
devfileModel = devfileManager.parseJson(devfile);
} else {
devfileModel = devfileManager.parseYaml(devfile);
}
} catch (DevfileException e) {
throw new BadRequestException(e.getMessage());
}
final Map<String, String> attributes = parseAttrs(attrsList);
if (namespace == null) {
namespace = EnvironmentContext.getCurrent().getSubject().getUserName();
}
WorkspaceImpl workspace;
try {
workspace =
workspaceManager.createWorkspace(
devfileModel,
devfile,
namespace,
attributes,
// create a new cache for each request so that we don't have to care about lifetime

View File

@ -0,0 +1,140 @@
/*
* Copyright (c) 2012-2018 Red Hat, Inc.
* This program and the accompanying materials are made
* available under the terms of the Eclipse Public License 2.0
* which is available at https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*
* Contributors:
* Red Hat, Inc. - initial API and implementation
*/
package org.eclipse.che.api.workspace.server.devfile;
import static com.google.common.base.Strings.isNullOrEmpty;
import static javax.ws.rs.core.MediaType.APPLICATION_JSON;
import static org.eclipse.che.api.workspace.server.DtoConverter.asDto;
import com.google.common.io.CharStreams;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.Writer;
import java.lang.annotation.Annotation;
import java.lang.reflect.Type;
import java.nio.charset.StandardCharsets;
import javax.inject.Inject;
import javax.inject.Singleton;
import javax.ws.rs.BadRequestException;
import javax.ws.rs.Consumes;
import javax.ws.rs.NotSupportedException;
import javax.ws.rs.Produces;
import javax.ws.rs.WebApplicationException;
import javax.ws.rs.core.HttpHeaders;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.MultivaluedMap;
import javax.ws.rs.ext.MessageBodyReader;
import javax.ws.rs.ext.MessageBodyWriter;
import javax.ws.rs.ext.Provider;
import org.eclipse.che.api.workspace.server.devfile.exception.DevfileFormatException;
import org.eclipse.che.api.workspace.shared.dto.devfile.DevfileDto;
import org.eclipse.che.dto.server.DtoFactory;
/**
* Parses {@link DevfileDto} either from Json or yaml content, and performs schema validation before
* the actual DTO created.
*
* @author Max Shaposhnyk
*/
@Singleton
@Provider
@Produces({APPLICATION_JSON})
@Consumes({APPLICATION_JSON, "text/yaml", "text/x-yaml"})
public class DevfileEntityProvider
implements MessageBodyReader<DevfileDto>, MessageBodyWriter<DevfileDto> {
private DevfileManager devfileManager;
@Inject
public DevfileEntityProvider(DevfileManager devfileManager) {
this.devfileManager = devfileManager;
}
@Override
public boolean isReadable(
Class<?> type, Type genericType, Annotation[] annotations, MediaType mediaType) {
return type == DevfileDto.class;
}
@Override
public DevfileDto readFrom(
Class<DevfileDto> type,
Type genericType,
Annotation[] annotations,
MediaType mediaType,
MultivaluedMap<String, String> httpHeaders,
InputStream entityStream)
throws IOException, WebApplicationException {
try {
if (mediaType.isCompatible(MediaType.APPLICATION_JSON_TYPE)) {
return asDto(
devfileManager.parseJson(
CharStreams.toString(
new InputStreamReader(entityStream, getCharsetOrUtf8(mediaType)))));
} else if (mediaType.isCompatible(MediaType.valueOf("text/yaml"))
|| mediaType.isCompatible(MediaType.valueOf("text/x-yaml"))) {
return asDto(
devfileManager.parseYaml(
CharStreams.toString(
new InputStreamReader(entityStream, getCharsetOrUtf8(mediaType)))));
}
} catch (DevfileFormatException e) {
throw new BadRequestException(e.getMessage());
}
throw new NotSupportedException("Unknown media type " + mediaType.toString());
}
@Override
public boolean isWriteable(
Class<?> type, Type genericType, Annotation[] annotations, MediaType mediaType) {
return DevfileDto.class.isAssignableFrom(type);
}
@Override
public long getSize(
DevfileDto devfileDto,
Class<?> type,
Type genericType,
Annotation[] annotations,
MediaType mediaType) {
return -1;
}
@Override
public void writeTo(
DevfileDto devfileDto,
Class<?> type,
Type genericType,
Annotation[] annotations,
MediaType mediaType,
MultivaluedMap<String, Object> httpHeaders,
OutputStream entityStream)
throws IOException, WebApplicationException {
httpHeaders.putSingle(HttpHeaders.CACHE_CONTROL, "public, no-cache, no-store, no-transform");
try (Writer w = new OutputStreamWriter(entityStream, StandardCharsets.UTF_8)) {
w.write(DtoFactory.getInstance().toJson(devfileDto));
w.flush();
}
}
private String getCharsetOrUtf8(MediaType mediaType) {
String charset = mediaType == null ? null : mediaType.getParameters().get("charset");
if (isNullOrEmpty(charset)) {
charset = "UTF-8";
}
return charset;
}
}

View File

@ -25,6 +25,7 @@ public class DevfileModule extends AbstractModule {
@Override
protected void configure() {
bind(DevfileService.class);
bind(DevfileEntityProvider.class);
DevfileBindings.onWorkspaceApplierBinder(
binder(),

View File

@ -0,0 +1,59 @@
/*
* Copyright (c) 2012-2018 Red Hat, Inc.
* This program and the accompanying materials are made
* available under the terms of the Eclipse Public License 2.0
* which is available at https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*
* Contributors:
* Red Hat, Inc. - initial API and implementation
*/
package org.eclipse.che.api.workspace.server;
import static org.eclipse.che.dto.server.DtoFactory.newDto;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import java.io.ByteArrayInputStream;
import java.nio.charset.StandardCharsets;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.MultivaluedHashMap;
import org.eclipse.che.api.workspace.server.devfile.DevfileManager;
import org.eclipse.che.api.workspace.server.model.impl.devfile.DevfileImpl;
import org.eclipse.che.api.workspace.shared.dto.WorkspaceDto;
import org.eclipse.che.api.workspace.shared.dto.devfile.DevfileDto;
import org.eclipse.che.dto.server.DtoFactory;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.testng.MockitoTestNGListener;
import org.testng.annotations.Listeners;
import org.testng.annotations.Test;
@Listeners(MockitoTestNGListener.class)
public class WorkspaceEntityProviderTest {
@Mock private DevfileManager devfileManager;
@InjectMocks private WorkspaceEntityProvider workspaceEntityProvider;
@Test
public void shouldBuildDtoFromValidJson() throws Exception {
when(devfileManager.parseJson(anyString())).thenReturn(new DevfileImpl());
WorkspaceDto actual = newDto(WorkspaceDto.class).withDevfile(newDto(DevfileDto.class));
workspaceEntityProvider.readFrom(
WorkspaceDto.class,
WorkspaceDto.class,
null,
MediaType.APPLICATION_JSON_TYPE,
new MultivaluedHashMap<>(),
new ByteArrayInputStream(
DtoFactory.getInstance().toJson(actual).getBytes(StandardCharsets.UTF_8)));
verify(devfileManager).parseJson(anyString());
}
}

View File

@ -51,6 +51,7 @@ import java.util.Map;
import org.eclipse.che.account.shared.model.Account;
import org.eclipse.che.account.spi.AccountImpl;
import org.eclipse.che.api.core.Page;
import org.eclipse.che.api.core.ValidationException;
import org.eclipse.che.api.core.model.workspace.WorkspaceConfig;
import org.eclipse.che.api.core.model.workspace.WorkspaceStatus;
import org.eclipse.che.api.core.model.workspace.config.ProjectConfig;
@ -62,9 +63,7 @@ import org.eclipse.che.api.core.model.workspace.runtime.ServerStatus;
import org.eclipse.che.api.core.rest.ApiExceptionMapper;
import org.eclipse.che.api.core.rest.CheJsonProvider;
import org.eclipse.che.api.core.rest.shared.dto.ServiceError;
import org.eclipse.che.api.workspace.server.devfile.DevfileManager;
import org.eclipse.che.api.workspace.server.devfile.URLFetcher;
import org.eclipse.che.api.workspace.server.devfile.exception.DevfileFormatException;
import org.eclipse.che.api.workspace.server.model.impl.CommandImpl;
import org.eclipse.che.api.workspace.server.model.impl.EnvironmentImpl;
import org.eclipse.che.api.workspace.server.model.impl.MachineConfigImpl;
@ -74,7 +73,6 @@ import org.eclipse.che.api.workspace.server.model.impl.RuntimeImpl;
import org.eclipse.che.api.workspace.server.model.impl.ServerImpl;
import org.eclipse.che.api.workspace.server.model.impl.WorkspaceConfigImpl;
import org.eclipse.che.api.workspace.server.model.impl.WorkspaceImpl;
import org.eclipse.che.api.workspace.server.model.impl.devfile.DevfileImpl;
import org.eclipse.che.api.workspace.server.token.MachineTokenProvider;
import org.eclipse.che.api.workspace.shared.Constants;
import org.eclipse.che.api.workspace.shared.dto.CommandDto;
@ -134,7 +132,6 @@ public class WorkspaceServiceTest {
@Mock private WorkspaceManager wsManager;
@Mock private MachineTokenProvider machineTokenProvider;
@Mock private WorkspaceLinksGenerator linksGenerator;
@Mock private DevfileManager devfileManager;
@Mock private URLFetcher urlFetcher;
private WorkspaceService service;
@ -150,8 +147,7 @@ public class WorkspaceServiceTest {
linksGenerator,
CHE_WORKSPACE_PLUGIN_REGISTRY_ULR,
CHE_WORKSPACE_DEVFILE_REGISTRY_ULR,
urlFetcher,
devfileManager);
urlFetcher);
}
@Test
@ -193,8 +189,6 @@ public class WorkspaceServiceTest {
final DevfileDto devfileDto = createDevfileDto();
final WorkspaceImpl workspace = createWorkspace(devfileDto);
when(devfileManager.parseJson(any())).thenReturn(new DevfileImpl());
when(wsManager.createWorkspace(any(Devfile.class), anyString(), any(), any()))
.thenReturn(workspace);
@ -226,52 +220,13 @@ public class WorkspaceServiceTest {
any());
}
@Test
public void shouldAcceptYamlDevfileWhenCreatingWorkspace() throws Exception {
final DevfileDto devfileDto = createDevfileDto();
final WorkspaceImpl workspace = createWorkspace(devfileDto);
when(devfileManager.parseYaml(any())).thenReturn(new DevfileImpl());
when(wsManager.createWorkspace(any(Devfile.class), anyString(), any(), any()))
.thenReturn(workspace);
final Response response =
given()
.auth()
.basic(ADMIN_USER_NAME, ADMIN_USER_PASSWORD)
.contentType("text/yaml")
.when()
.post(
SECURE_PATH
+ "/workspace/devfile"
+ "?namespace=test"
+ "&attribute=factoryId:factory123"
+ "&attribute=custom:custom:value");
assertEquals(response.getStatusCode(), 201);
assertEquals(
new WorkspaceImpl(unwrapDto(response, WorkspaceDto.class), TEST_ACCOUNT), workspace);
verify(wsManager)
.createWorkspace(
any(Devfile.class),
eq("test"),
eq(
ImmutableMap.of(
"factoryId", "factory123",
"custom", "custom:value")),
any());
}
@Test
public void shouldReturnBadRequestOnInvalidDevfile() throws Exception {
final DevfileDto devfileDto = createDevfileDto();
final WorkspaceImpl workspace = createWorkspace(devfileDto);
when(devfileManager.parseJson(any())).thenThrow(new DevfileFormatException("boom"));
when(wsManager.createWorkspace(any(Devfile.class), anyString(), any(), any()))
.thenReturn(workspace);
.thenThrow(new ValidationException("boom"));
final Response response =
given()
@ -290,8 +245,6 @@ public class WorkspaceServiceTest {
assertEquals(response.getStatusCode(), 400);
String error = unwrapError(response);
assertEquals(error, "boom");
verify(wsManager, never()).createWorkspace(any(Devfile.class), any(), any(), any());
}
@Test

View File

@ -0,0 +1,81 @@
/*
* Copyright (c) 2012-2018 Red Hat, Inc.
* This program and the accompanying materials are made
* available under the terms of the Eclipse Public License 2.0
* which is available at https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*
* Contributors:
* Red Hat, Inc. - initial API and implementation
*/
package org.eclipse.che.api.workspace.server.devfile;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import javax.ws.rs.NotSupportedException;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.MultivaluedHashMap;
import org.eclipse.che.api.workspace.server.model.impl.devfile.DevfileImpl;
import org.eclipse.che.api.workspace.shared.dto.devfile.DevfileDto;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.testng.MockitoTestNGListener;
import org.testng.annotations.Listeners;
import org.testng.annotations.Test;
@Listeners(MockitoTestNGListener.class)
public class DevfileEntityProviderTest {
@Mock private DevfileManager devfileManager;
@InjectMocks private DevfileEntityProvider devfileEntityProvider;
@Test
public void shouldBuildDtoFromValidYaml() throws Exception {
when(devfileManager.parseYaml(anyString())).thenReturn(new DevfileImpl());
devfileEntityProvider.readFrom(
DevfileDto.class,
DevfileDto.class,
null,
MediaType.valueOf("text/x-yaml"),
new MultivaluedHashMap<>(),
getClass().getClassLoader().getResourceAsStream("devfile/devfile.yaml"));
verify(devfileManager).parseYaml(anyString());
}
@Test
public void shouldBuildDtoFromValidJson() throws Exception {
when(devfileManager.parseJson(anyString())).thenReturn(new DevfileImpl());
devfileEntityProvider.readFrom(
DevfileDto.class,
DevfileDto.class,
null,
MediaType.APPLICATION_JSON_TYPE,
new MultivaluedHashMap<>(),
getClass().getClassLoader().getResourceAsStream("devfile/devfile.json"));
verify(devfileManager).parseJson(anyString());
}
@Test(
expectedExceptions = NotSupportedException.class,
expectedExceptionsMessageRegExp = "Unknown media type text/plain")
public void shouldThrowErrorOnInvalidMediaType() throws Exception {
devfileEntityProvider.readFrom(
DevfileDto.class,
DevfileDto.class,
null,
MediaType.TEXT_PLAIN_TYPE,
new MultivaluedHashMap<>(),
getClass().getClassLoader().getResourceAsStream("devfile/devfile.json"));
}
}

View File

@ -0,0 +1,84 @@
{
"apiVersion": "1.0.0",
"metadata": {
"name": "petclinic-dev-environment",
"generateName": "petclinic-"
},
"projects": [
{
"name": "petclinic",
"source": {
"type": "git",
"location": "git@github.com:spring-projects/spring-petclinic.git"
}
}
],
"components": [
{
"alias": "mvn-stack",
"type": "chePlugin",
"id": "eclipse/chemaven-jdk8/1.0.0"
},
{
"type": "cheEditor",
"id": "eclipse/che-theia/0.0.3"
},
{
"alias": "jdt.ls",
"type": "chePlugin",
"id": "org.eclipse.chetheia-jdtls:0.0.3",
"preferences": {
"java.home": "/home/user/jdk11",
"java.jdt.ls.vmargs": "-noverify -Xmx1G -XX:+UseG1GC -XX:+UseStringDeduplication",
"java.jtg.memory": 12345,
"java.boolean": true
}
},
{
"type": "openshift",
"reference": "petclinic.yaml",
"selector": {
"app.kubernetes.io/name": "mysql",
"app.kubernetes.io/component": "database",
"app.kubernetes.io/part-of": "petclinic"
}
}
],
"commands": [
{
"name": "build",
"actions": [
{
"type": "exec",
"component": "mvn-stack",
"command": "mvn package",
"workdir": "/projects/spring-petclinic"
}
]
},
{
"name": "run",
"attributes": {
"runType": "sequential"
},
"actions": [
{
"type": "exec",
"component": "mvn-stack",
"command": "mvn spring-boot:run",
"workdir": "/projects/spring-petclinic"
}
]
},
{
"name": "other",
"actions": [
{
"type": "exec",
"component": "jdt.ls",
"command": "run.sh"
}
]
}
]
}