Add devfile validation to /workspace/devfile endpoint (#13472)

/workspace/devfile now validates the devfile against schema and
workspace manager validates the integrity of the devfile before creating
the workspace.

Signed-off-by: Lukas Krejci <lkrejci@redhat.com>
7.20.x
Lukas Krejci 2019-06-05 13:58:31 +02:00 committed by GitHub
parent 9976bb050f
commit 7312af9fdb
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
16 changed files with 293 additions and 60 deletions

View File

@ -35,7 +35,7 @@ import org.eclipse.che.api.core.notification.EventService;
import org.eclipse.che.api.workspace.server.WorkspaceManager;
import org.eclipse.che.api.workspace.server.WorkspaceRuntimes;
import org.eclipse.che.api.workspace.server.WorkspaceValidator;
import org.eclipse.che.api.workspace.server.devfile.convert.DevfileConverter;
import org.eclipse.che.api.workspace.server.devfile.validator.DevfileIntegrityValidator;
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.spi.WorkspaceDao;
@ -84,8 +84,14 @@ public class LimitsCheckingWorkspaceManager extends WorkspaceManager {
EnvironmentRamCalculator environmentRamCalculator,
ResourceManager resourceManager,
ResourcesLocks resourcesLocks,
DevfileConverter devfileConverter) {
super(workspaceDao, runtimes, eventService, accountManager, workspaceValidator);
DevfileIntegrityValidator devfileIntegrityValidator) {
super(
workspaceDao,
runtimes,
eventService,
accountManager,
workspaceValidator,
devfileIntegrityValidator);
this.environmentRamCalculator = environmentRamCalculator;
this.maxRamPerEnvMB = "-1".equals(maxRamPerEnv) ? -1 : Size.parseSizeToMegabytes(maxRamPerEnv);
this.resourceManager = resourceManager;

View File

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

View File

@ -104,7 +104,7 @@ public class URLFactoryBuilder {
return Optional.empty();
}
try {
DevfileImpl devfile = devfileManager.parse(devfileYamlContent);
DevfileImpl devfile = devfileManager.parseYaml(devfileYamlContent);
WorkspaceConfigImpl wsConfig =
devfileManager.createWorkspaceConfig(devfile, fileContentProvider);
FactoryDto factoryDto =

View File

@ -119,7 +119,7 @@ public class URLFactoryBuilderTest {
workspaceConfigImpl.setDefaultEnv("name");
when(urlFetcher.fetchSafely(anyString())).thenReturn("random_content");
when(devfileManager.parse(anyString())).thenReturn(devfile);
when(devfileManager.parseYaml(anyString())).thenReturn(devfile);
when(devfileManager.createWorkspaceConfig(any(DevfileImpl.class), any()))
.thenReturn(workspaceConfigImpl);

View File

@ -42,6 +42,9 @@ 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.devfile.Devfile;
import org.eclipse.che.api.core.notification.EventService;
import org.eclipse.che.api.workspace.server.devfile.FileContentProvider;
import org.eclipse.che.api.workspace.server.devfile.exception.DevfileFormatException;
import org.eclipse.che.api.workspace.server.devfile.validator.DevfileIntegrityValidator;
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;
@ -73,6 +76,7 @@ public class WorkspaceManager {
private final AccountManager accountManager;
private final EventService eventService;
private final WorkspaceValidator validator;
private final DevfileIntegrityValidator devfileIntegrityValidator;
@Inject
public WorkspaceManager(
@ -80,12 +84,14 @@ public class WorkspaceManager {
WorkspaceRuntimes runtimes,
EventService eventService,
AccountManager accountManager,
WorkspaceValidator validator) {
WorkspaceValidator validator,
DevfileIntegrityValidator devfileIntegrityValidator) {
this.workspaceDao = workspaceDao;
this.runtimes = runtimes;
this.accountManager = accountManager;
this.eventService = eventService;
this.validator = validator;
this.devfileIntegrityValidator = devfileIntegrityValidator;
}
/**
@ -120,9 +126,32 @@ public class WorkspaceManager {
return workspace;
}
/**
* Creates a workspace out of a devfile.
*
* <p>The devfile should have been validated using the {@link
* DevfileIntegrityValidator#validateDevfile(Devfile)}. This method does rest of the validation
* and actually creates the workspace.
*
* @param devfile the devfile describing the workspace
* @param namespace workspace name is unique in this namespace
* @param attributes workspace instance attributes
* @param contentProvider the content provider to use for resolving content references in the
* devfile
* @return new workspace instance
* @throws NullPointerException when either {@code config} or {@code namespace} is null
* @throws NotFoundException when account with given id was not found
* @throws ConflictException when any conflict occurs (e.g Workspace with such name already exists
* for {@code owner})
* @throws ServerException when any other error occurs
* @throws ValidationException when incoming configuration or attributes are not valid
*/
@Traced
public WorkspaceImpl createWorkspace(
Devfile devfile, String namespace, Map<String, String> attributes)
Devfile devfile,
String namespace,
Map<String, String> attributes,
FileContentProvider contentProvider)
throws ServerException, NotFoundException, ConflictException, ValidationException {
TracingTags.STACK_ID.set(() -> attributes.getOrDefault("stackId", "no stack"));
@ -130,6 +159,12 @@ public class WorkspaceManager {
requireNonNull(namespace, "Required non-null namespace");
validator.validateAttributes(attributes);
try {
devfileIntegrityValidator.validateContentReferences(devfile, contentProvider);
} catch (DevfileFormatException e) {
throw new ValidationException(e.getMessage(), e);
}
WorkspaceImpl workspace =
doCreateWorkspace(devfile, accountManager.getByName(namespace), attributes, false);
TracingTags.WORKSPACE_ID.set(workspace.getId());

View File

@ -15,7 +15,9 @@ import static com.google.common.base.Strings.isNullOrEmpty;
import static java.lang.String.format;
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;
@ -44,12 +46,14 @@ import javax.ws.rs.Consumes;
import javax.ws.rs.DELETE;
import javax.ws.rs.DefaultValue;
import javax.ws.rs.GET;
import javax.ws.rs.HeaderParam;
import javax.ws.rs.POST;
import javax.ws.rs.PUT;
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.MediaType;
import javax.ws.rs.core.Response;
import org.eclipse.che.api.core.BadRequestException;
import org.eclipse.che.api.core.ConflictException;
@ -62,10 +66,16 @@ 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;
@ -79,7 +89,6 @@ 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;
@ -100,6 +109,8 @@ public class WorkspaceService extends Service {
private final String devfileRegistryUrl;
private final String apiEndpoint;
private final boolean cheWorkspaceAutoStart;
private final FileContentProvider devfileContentProvider;
private final DevfileManager devfileManager;
@Inject
public WorkspaceService(
@ -109,7 +120,9 @@ public class WorkspaceService extends Service {
MachineTokenProvider machineTokenProvider,
WorkspaceLinksGenerator linksGenerator,
@Named(CHE_WORKSPACE_PLUGIN_REGISTRY_URL_PROPERTY) @Nullable String pluginRegistryUrl,
@Named(CHE_WORKSPACE_DEVFILE_REGISTRY_URL_PROPERTY) @Nullable String devfileRegistryUrl) {
@Named(CHE_WORKSPACE_DEVFILE_REGISTRY_URL_PROPERTY) @Nullable String devfileRegistryUrl,
URLFetcher urlFetcher,
DevfileManager devfileManager) {
this.apiEndpoint = apiEndpoint;
this.cheWorkspaceAutoStart = cheWorkspaceAutoStart;
this.workspaceManager = workspaceManager;
@ -117,6 +130,8 @@ public class WorkspaceService extends Service {
this.linksGenerator = linksGenerator;
this.pluginRegistryUrl = pluginRegistryUrl;
this.devfileRegistryUrl = devfileRegistryUrl;
this.devfileContentProvider = new URLFileContentProvider(null, urlFetcher);
this.devfileManager = devfileManager;
}
@POST
@ -188,14 +203,14 @@ public class WorkspaceService extends Service {
@Beta
@Path("/devfile")
@POST
@Consumes(APPLICATION_JSON)
@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,
consumes = "application/json, text/yaml, text/x-yaml",
produces = APPLICATION_JSON,
nickname = "createFromDevfile",
response = WorkspaceConfigDto.class)
@ -211,8 +226,7 @@ public class WorkspaceService extends Service {
@ApiResponse(code = 500, message = "Internal server error occurred")
})
public Response create(
@ApiParam(value = "The configuration to create the new workspace", required = true)
DevfileDto devfile,
@ApiParam(value = "The devfile of the workspace to create", required = true) String devfile,
@ApiParam(
value =
"Workspace attribute defined in 'attrName:attrValue' format. "
@ -231,11 +245,23 @@ public class WorkspaceService extends Service {
@DefaultValue("false")
Boolean startAfterCreate,
@ApiParam("Namespace where workspace should be created") @QueryParam("namespace")
String namespace)
String namespace,
@HeaderParam(CONTENT_TYPE) MediaType contentType)
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) {
@ -244,7 +270,16 @@ public class WorkspaceService extends Service {
WorkspaceImpl workspace;
try {
workspace = workspaceManager.createWorkspace(devfile, namespace, attributes);
workspace =
workspaceManager.createWorkspace(
devfileModel,
namespace,
attributes,
// create a new cache for each request so that we don't have to care about lifetime
// of the cache, etc. The content is cached only for the duration of this call
// (i.e. all the validation and provisioning of the devfile will download each
// referenced file only once per request)
FileContentProvider.cached(devfileContentProvider));
} catch (ValidationException x) {
throw new BadRequestException(x.getMessage());
}

View File

@ -82,7 +82,7 @@ public class DevfileManager {
}
/**
* Creates {@link DevfileImpl} from given devfile content. Performs schema and integrity
* Creates {@link DevfileImpl} from given devfile content in YAML. Performs schema and integrity
* validation of input data.
*
* @param devfileContent raw content of devfile
@ -90,8 +90,26 @@ public class DevfileManager {
* @throws DevfileFormatException when any of schema or integrity validations fail
* @throws DevfileFormatException when any yaml parsing error occurs
*/
public DevfileImpl parse(String devfileContent) throws DevfileFormatException {
JsonNode parsed = schemaValidator.validateBySchema(devfileContent);
public DevfileImpl parseYaml(String devfileContent) throws DevfileFormatException {
return parse(devfileContent, schemaValidator::validateYaml);
}
/**
* Creates {@link DevfileImpl} from given devfile content in JSON. Performs schema and integrity
* validation of input data.
*
* @param devfileContent raw content of devfile
* @return Devfile object created from the source content
* @throws DevfileFormatException when any of schema or integrity validations fail
* @throws DevfileFormatException when any yaml parsing error occurs
*/
public DevfileImpl parseJson(String devfileContent) throws DevfileFormatException {
return parse(devfileContent, schemaValidator::validateJson);
}
private DevfileImpl parse(String content, ValidationFunction validationFunction)
throws DevfileFormatException {
JsonNode parsed = validationFunction.validate(content);
DevfileImpl devfile;
try {
@ -188,4 +206,9 @@ public class DevfileManager {
}
return config;
}
@FunctionalInterface
private interface ValidationFunction {
JsonNode validate(String content) throws DevfileFormatException;
}
}

View File

@ -120,7 +120,7 @@ public class DevfileService extends Service {
WorkspaceImpl workspace;
try {
DevfileImpl devfile = devfileManager.parse(data);
DevfileImpl devfile = devfileManager.parseYaml(data);
workspace = devfileManager.createWorkspace(devfile, urlFileContentProvider);
} catch (DevfileException e) {
throw new BadRequestException(e.getMessage());

View File

@ -17,6 +17,7 @@ import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
import org.eclipse.che.api.workspace.server.devfile.exception.DevfileException;
import org.eclipse.che.commons.annotation.Nullable;
/**
* A simple implementation of the FileContentProvider that merely uses the function resolve relative
@ -27,7 +28,7 @@ public class URLFileContentProvider implements FileContentProvider {
private final URI devfileLocation;
private final URLFetcher urlFetcher;
public URLFileContentProvider(URI devfileLocation, URLFetcher urlFetcher) {
public URLFileContentProvider(@Nullable URI devfileLocation, URLFetcher urlFetcher) {
this.devfileLocation = devfileLocation;
this.urlFetcher = urlFetcher;
}

View File

@ -35,15 +35,15 @@ import org.leadpony.justify.api.ProblemHandler;
public class DevfileSchemaValidator {
private final JsonValidationService service = JsonValidationService.newInstance();
private ObjectMapper yamlReader;
private ObjectMapper jsonWriter;
private ObjectMapper yamlMapper;
private ObjectMapper jsonMapper;
private JsonSchema schema;
private ErrorMessageComposer errorMessageComposer;
@Inject
public DevfileSchemaValidator(DevfileSchemaProvider schemaProvider) {
this.yamlReader = new ObjectMapper(new YAMLFactory());
this.jsonWriter = new ObjectMapper();
this.yamlMapper = new ObjectMapper(new YAMLFactory());
this.jsonMapper = new ObjectMapper();
this.errorMessageComposer = new ErrorMessageComposer();
try {
this.schema = service.readSchema(schemaProvider.getAsReader());
@ -52,23 +52,39 @@ public class DevfileSchemaValidator {
}
}
public JsonNode validateBySchema(String yamlContent) throws DevfileFormatException {
public JsonNode validateYaml(String yamlContent) throws DevfileFormatException {
return validate(yamlContent, yamlMapper);
}
public JsonNode validateJson(String jsonContent) throws DevfileFormatException {
return validate(jsonContent, jsonMapper);
}
private JsonNode validate(String content, ObjectMapper mapper) throws DevfileFormatException {
JsonNode contentNode;
try {
contentNode = yamlReader.readTree(yamlContent);
contentNode = mapper.readTree(content);
validate(contentNode);
return contentNode;
} catch (IOException e) {
throw new DevfileFormatException("Unable to validate Devfile. Error: " + e.getMessage());
}
}
private void validate(JsonNode contentNode) throws DevfileFormatException {
try {
List<Problem> validationErrors = new ArrayList<>();
ProblemHandler handler = ProblemHandler.collectingTo(validationErrors);
try (JsonReader reader =
service.createReader(
new StringReader(jsonWriter.writeValueAsString(contentNode)), schema, handler)) {
new StringReader(jsonMapper.writeValueAsString(contentNode)), schema, handler)) {
reader.read();
}
if (validationErrors.isEmpty()) {
return contentNode;
if (!validationErrors.isEmpty()) {
String error = errorMessageComposer.extractMessages(validationErrors, new StringBuilder());
throw new DevfileFormatException(
format("Devfile schema validation failed. Error: %s", error));
}
String error = errorMessageComposer.extractMessages(validationErrors, new StringBuilder());
throw new DevfileFormatException(
format("Devfile schema validation failed. Error: %s", error));
} catch (IOException e) {
throw new DevfileFormatException("Unable to validate Devfile. Error: " + e.getMessage());
}

View File

@ -61,6 +61,7 @@ import org.eclipse.che.api.core.ConflictException;
import org.eclipse.che.api.core.NotFoundException;
import org.eclipse.che.api.core.Page;
import org.eclipse.che.api.core.ServerException;
import org.eclipse.che.api.core.ValidationException;
import org.eclipse.che.api.core.model.workspace.Runtime;
import org.eclipse.che.api.core.model.workspace.Warning;
import org.eclipse.che.api.core.model.workspace.Workspace;
@ -73,6 +74,8 @@ import org.eclipse.che.api.core.model.workspace.runtime.Machine;
import org.eclipse.che.api.core.model.workspace.runtime.MachineStatus;
import org.eclipse.che.api.core.notification.EventService;
import org.eclipse.che.api.workspace.server.devfile.convert.DevfileConverter;
import org.eclipse.che.api.workspace.server.devfile.exception.DevfileFormatException;
import org.eclipse.che.api.workspace.server.devfile.validator.DevfileIntegrityValidator;
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;
@ -120,6 +123,7 @@ public class WorkspaceManagerTest {
@Mock private EventService eventService;
@Mock private WorkspaceValidator validator;
@Mock private DevfileConverter devfileConverter;
@Mock private DevfileIntegrityValidator devfileIntegrityValidator;
@Captor private ArgumentCaptor<WorkspaceImpl> workspaceCaptor;
@ -128,7 +132,13 @@ public class WorkspaceManagerTest {
@BeforeMethod
public void setUp() throws Exception {
workspaceManager =
new WorkspaceManager(workspaceDao, runtimes, eventService, accountManager, validator);
new WorkspaceManager(
workspaceDao,
runtimes,
eventService,
accountManager,
validator,
devfileIntegrityValidator);
lenient()
.when(accountManager.getByName(NAMESPACE_1))
.thenReturn(new AccountImpl("accountId", NAMESPACE_1, "test"));
@ -579,6 +589,21 @@ public class WorkspaceManagerTest {
verify(workspaceDao, times(1)).remove(anyString());
}
@Test(expectedExceptions = ValidationException.class, expectedExceptionsMessageRegExp = "boom")
public void shouldFailTocreateWorkspaceUsingInconsistentDevfile() throws Exception {
// given
doThrow(new DevfileFormatException("boom"))
.when(devfileIntegrityValidator)
.validateContentReferences(any(), any());
Devfile devfile = mock(Devfile.class);
// when
workspaceManager.createWorkspace(devfile, "ns", emptyMap(), null);
// then exception is thrown
}
private void mockRuntimeStatus(WorkspaceImpl workspace, WorkspaceStatus status) {
lenient().when(runtimes.getStatus(workspace.getId())).thenReturn(status);
}

View File

@ -60,6 +60,10 @@ import org.eclipse.che.api.core.model.workspace.runtime.Server;
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.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.dto.DtoServerImpls.DevfileDtoImpl;
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;
@ -69,6 +73,7 @@ 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;
@ -124,6 +129,8 @@ 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;
@ -137,7 +144,9 @@ public class WorkspaceServiceTest {
machineTokenProvider,
linksGenerator,
CHE_WORKSPACE_PLUGIN_REGISTRY_ULR,
CHE_WORKSPACE_DEVFILE_REGISTRY_ULR);
CHE_WORKSPACE_DEVFILE_REGISTRY_ULR,
urlFetcher,
devfileManager);
}
@Test
@ -178,16 +187,20 @@ public class WorkspaceServiceTest {
@Test
public void shouldCreateWorkspaceFromDevfile() throws Exception {
final DevfileDto devfileDto = createDevfileDto();
final DevfileDtoImpl devfileDto = createDevfileDto();
final WorkspaceImpl workspace = createWorkspace(devfileDto);
when(wsManager.createWorkspace(any(Devfile.class), anyString(), any())).thenReturn(workspace);
when(devfileManager.parseJson(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("application/json")
.body(devfileDto)
.body(devfileDto.toJson())
.when()
.post(
SECURE_PATH
@ -208,7 +221,79 @@ public class WorkspaceServiceTest {
ImmutableMap.of(
"stackId", "stack123",
"factoryId", "factory123",
"custom", "custom:value")));
"custom", "custom:value")),
any());
}
@Test
public void shouldAcceptYamlDevfileWhenCreatingWorkspace() throws Exception {
final DevfileDtoImpl 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=stackId:stack123"
+ "&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(
"stackId", "stack123",
"factoryId", "factory123",
"custom", "custom:value")),
any());
}
@Test
public void shouldReturnBadRequestOnInvalidDevfile() throws Exception {
final DevfileDtoImpl 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);
final Response response =
given()
.auth()
.basic(ADMIN_USER_NAME, ADMIN_USER_PASSWORD)
.contentType("application/json")
.body(devfileDto.toJson())
.when()
.post(
SECURE_PATH
+ "/workspace/devfile"
+ "?namespace=test"
+ "&attribute=stackId:stack123"
+ "&attribute=factoryId:factory123"
+ "&attribute=custom:custom:value");
assertEquals(response.getStatusCode(), 400);
String error = unwrapError(response);
assertEquals(error, "boom");
verify(wsManager, never()).createWorkspace(any(Devfile.class), any(), any(), any());
}
@Test
@ -1266,17 +1351,18 @@ public class WorkspaceServiceTest {
.build();
}
private DevfileDto createDevfileDto() {
return newDto(DevfileDto.class)
.withSpecVersion("0.0.1")
.withName("ws")
.withProjects(
singletonList(
newDto(ProjectDto.class)
.withName("project")
.withSource(
newDto(SourceDto.class)
.withLocation("https://github.com/eclipse/che.git"))));
private DevfileDtoImpl createDevfileDto() {
return (DevfileDtoImpl)
newDto(DevfileDto.class)
.withSpecVersion("0.0.1")
.withName("ws")
.withProjects(
singletonList(
newDto(ProjectDto.class)
.withName("project")
.withSource(
newDto(SourceDto.class)
.withLocation("https://github.com/eclipse/che.git"))));
}
private static WorkspaceImpl createWorkspace(WorkspaceConfig configDto) {

View File

@ -78,18 +78,18 @@ public class DevfileManagerTest {
public void setUp() throws Exception {
devfile = new DevfileImpl();
lenient().when(schemaValidator.validateBySchema(any())).thenReturn(devfileJsonNode);
lenient().when(schemaValidator.validateYaml(any())).thenReturn(devfileJsonNode);
lenient().when(objectMapper.treeToValue(any(), eq(DevfileImpl.class))).thenReturn(devfile);
}
@Test
public void testValidateAndParse() throws Exception {
// when
DevfileImpl parsed = devfileManager.parse(DEVFILE_YAML_CONTENT);
DevfileImpl parsed = devfileManager.parseYaml(DEVFILE_YAML_CONTENT);
// then
assertEquals(parsed, devfile);
verify(schemaValidator).validateBySchema(DEVFILE_YAML_CONTENT);
verify(schemaValidator).validateYaml(DEVFILE_YAML_CONTENT);
verify(objectMapper).treeToValue(devfileJsonNode, DevfileImpl.class);
verify(integrityValidator).validateDevfile(devfile);
}
@ -106,7 +106,7 @@ public class DevfileManagerTest {
devfile.getComponents().add(component);
// when
DevfileImpl parsed = devfileManager.parse(DEVFILE_YAML_CONTENT);
DevfileImpl parsed = devfileManager.parseYaml(DEVFILE_YAML_CONTENT);
// then
assertNotNull(parsed.getCommands().get(0).getAttributes());
@ -119,10 +119,10 @@ public class DevfileManagerTest {
expectedExceptionsMessageRegExp = "non valid")
public void shouldThrowExceptionWhenExceptionOccurredDuringSchemaValidation() throws Exception {
// given
doThrow(new DevfileFormatException("non valid")).when(schemaValidator).validateBySchema(any());
doThrow(new DevfileFormatException("non valid")).when(schemaValidator).validateYaml(any());
// when
devfileManager.parse(DEVFILE_YAML_CONTENT);
devfileManager.parseYaml(DEVFILE_YAML_CONTENT);
}
@Test(
@ -135,7 +135,7 @@ public class DevfileManagerTest {
doThrow(jsonException).when(objectMapper).treeToValue(any(), any());
// when
devfileManager.parse(DEVFILE_YAML_CONTENT);
devfileManager.parseYaml(DEVFILE_YAML_CONTENT);
}
@Test

View File

@ -89,7 +89,7 @@ public class DevfileServiceTest {
Files.readFile(getClass().getClassLoader().getResourceAsStream("devfile/devfile.yaml"));
DevfileImpl devfile = createDevfile(yamlContent);
WorkspaceImpl ws = createWorkspace(WorkspaceStatus.STOPPED);
when(devfileManager.parse(anyString())).thenReturn(devfile);
when(devfileManager.parseYaml(anyString())).thenReturn(devfile);
when(devfileManager.createWorkspace(any(DevfileImpl.class), any())).thenReturn(ws);
final Response response =
given()

View File

@ -35,7 +35,7 @@ public class DevfileSchemaValidatorTest {
@Test(dataProvider = "validDevfiles")
public void shouldNotThrowExceptionOnValidationOfValidDevfile(String resourceFilePath)
throws Exception {
schemaValidator.validateBySchema(getResource(resourceFilePath));
schemaValidator.validateYaml(getResource(resourceFilePath));
}
@DataProvider
@ -69,7 +69,7 @@ public class DevfileSchemaValidatorTest {
public void shouldThrowExceptionOnValidationOfNonValidDevfile(
String resourceFilePath, String expectedMessage) throws Exception {
try {
schemaValidator.validateBySchema(getResource(resourceFilePath));
schemaValidator.validateYaml(getResource(resourceFilePath));
} catch (DevfileFormatException e) {
assertEquals(
e.getMessage(),

View File

@ -36,6 +36,7 @@ import com.google.inject.AbstractModule;
import com.google.inject.Guice;
import com.google.inject.Injector;
import com.google.inject.Stage;
import com.google.inject.multibindings.MapBinder;
import com.google.inject.name.Names;
import java.util.Map;
import java.util.concurrent.Callable;
@ -71,6 +72,7 @@ import org.eclipse.che.api.workspace.server.WorkspaceManager;
import org.eclipse.che.api.workspace.server.WorkspaceRuntimes;
import org.eclipse.che.api.workspace.server.WorkspaceSharedPool;
import org.eclipse.che.api.workspace.server.devfile.convert.DevfileConverter;
import org.eclipse.che.api.workspace.server.devfile.validator.ComponentIntegrityValidator;
import org.eclipse.che.api.workspace.server.hc.probe.ProbeScheduler;
import org.eclipse.che.api.workspace.server.jpa.JpaWorkspaceDao.RemoveWorkspaceBeforeAccountRemovedEventSubscriber;
import org.eclipse.che.api.workspace.server.jpa.WorkspaceJpaModule;
@ -263,6 +265,10 @@ public class CascadeRemovalTest {
bind(AccountManager.class);
bind(WorkspaceSharedPool.class)
.toInstance(new WorkspaceSharedPool("cached", null, null, null));
MapBinder.newMapBinder(binder(), String.class, ComponentIntegrityValidator.class)
.addBinding("kubernetes")
.toInstance(mock(ComponentIntegrityValidator.class));
}
});