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
parent
9976bb050f
commit
7312af9fdb
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 =
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
||||
|
|
|
|||
|
|
@ -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());
|
||||
|
|
|
|||
|
|
@ -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());
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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());
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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());
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -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(),
|
||||
|
|
|
|||
|
|
@ -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));
|
||||
}
|
||||
});
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue