Devfile local features implenentation with schema validation and automated model build.
parent
1fe397f874
commit
3a14bacda1
|
|
@ -157,6 +157,10 @@
|
|||
<groupId>org.eclipse.che.core</groupId>
|
||||
<artifactId>che-core-api-core</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.eclipse.che.core</groupId>
|
||||
<artifactId>che-core-api-devfile</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.eclipse.che.core</groupId>
|
||||
<artifactId>che-core-api-factory</artifactId>
|
||||
|
|
@ -305,6 +309,10 @@
|
|||
<groupId>org.eclipse.che.multiuser</groupId>
|
||||
<artifactId>che-multiuser-machine-authentication</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.eclipse.che.multiuser</groupId>
|
||||
<artifactId>che-multiuser-permission-devfile</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.eclipse.che.multiuser</groupId>
|
||||
<artifactId>che-multiuser-permission-factory</artifactId>
|
||||
|
|
|
|||
|
|
@ -31,6 +31,8 @@ import org.eclipse.che.api.core.notification.RemoteSubscriptionStorage;
|
|||
import org.eclipse.che.api.core.rest.CheJsonProvider;
|
||||
import org.eclipse.che.api.core.rest.MessageBodyAdapter;
|
||||
import org.eclipse.che.api.core.rest.MessageBodyAdapterInterceptor;
|
||||
import org.eclipse.che.api.devfile.server.DevfileSchemaValidator;
|
||||
import org.eclipse.che.api.devfile.server.DevfileService;
|
||||
import org.eclipse.che.api.factory.server.FactoryAcceptValidator;
|
||||
import org.eclipse.che.api.factory.server.FactoryCreateValidator;
|
||||
import org.eclipse.che.api.factory.server.FactoryEditValidator;
|
||||
|
|
@ -154,6 +156,9 @@ public class WsMasterModule extends AbstractModule {
|
|||
bind(org.eclipse.che.api.user.server.PreferencesService.class);
|
||||
bind(org.eclipse.che.security.oauth.OAuthAuthenticationService.class);
|
||||
|
||||
bind(DevfileSchemaValidator.class);
|
||||
bind(DevfileService.class);
|
||||
|
||||
MapBinder<String, String> stacks =
|
||||
MapBinder.newMapBinder(
|
||||
binder(), String.class, String.class, Names.named(StackLoader.CHE_PREDEFINED_STACKS));
|
||||
|
|
@ -371,6 +376,7 @@ public class WsMasterModule extends AbstractModule {
|
|||
bind(org.eclipse.che.multiuser.permission.logger.LoggerServicePermissionsFilter.class);
|
||||
|
||||
bind(org.eclipse.che.multiuser.permission.factory.FactoryPermissionsFilter.class);
|
||||
bind(org.eclipse.che.multiuser.permission.devfile.DevfilePermissionsFilter.class);
|
||||
bind(
|
||||
org.eclipse.che.multiuser.permission.installer.InstallerRegistryServicePermissionsFilter
|
||||
.class);
|
||||
|
|
|
|||
|
|
@ -21,6 +21,12 @@ import java.util.Map;
|
|||
*/
|
||||
public interface Command {
|
||||
|
||||
/**
|
||||
* {@link Command} attribute which indicates the working directory where the given command must be
|
||||
* run
|
||||
*/
|
||||
String WORKING_DIRECTORY_ATTRIBUTE = "workingDir";
|
||||
|
||||
/**
|
||||
* Returns command name (i.e. 'start tomcat') The name should be unique per user in one workspace,
|
||||
* which means that user may create only one command with the same name in the same workspace
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ package org.eclipse.che.workspace.infrastructure.kubernetes.wsplugins;
|
|||
|
||||
import static java.lang.String.format;
|
||||
import static java.util.Collections.emptyList;
|
||||
import static org.eclipse.che.api.core.model.workspace.config.Command.WORKING_DIRECTORY_ATTRIBUTE;
|
||||
import static org.eclipse.che.workspace.infrastructure.kubernetes.server.secure.SecureServerExposerFactoryProvider.SECURE_EXPOSER_IMPL_PROPERTY;
|
||||
|
||||
import com.google.common.annotations.Beta;
|
||||
|
|
@ -212,7 +213,7 @@ public class KubernetesPluginsToolingApplier implements ChePluginsApplier {
|
|||
command.getName(),
|
||||
command.getCommand().stream().collect(Collectors.joining(" ")),
|
||||
"custom");
|
||||
cmd.getAttributes().put("workDir", command.getWorkingDir());
|
||||
cmd.getAttributes().put(WORKING_DIRECTORY_ATTRIBUTE, command.getWorkingDir());
|
||||
cmd.getAttributes().put("machineName", machineName);
|
||||
return cmd;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@ import static java.util.Collections.emptyList;
|
|||
import static java.util.Collections.emptyMap;
|
||||
import static java.util.Collections.singletonList;
|
||||
import static java.util.Collections.singletonMap;
|
||||
import static org.eclipse.che.api.core.model.workspace.config.Command.WORKING_DIRECTORY_ATTRIBUTE;
|
||||
import static org.eclipse.che.api.core.model.workspace.config.MachineConfig.MEMORY_LIMIT_ATTRIBUTE;
|
||||
import static org.eclipse.che.commons.lang.NameGenerator.generate;
|
||||
import static org.eclipse.che.workspace.infrastructure.kubernetes.Constants.CHE_ORIGINAL_NAME_LABEL;
|
||||
|
|
@ -136,7 +137,8 @@ public class KubernetesPluginsToolingApplierTest {
|
|||
envCommand.getCommandLine(),
|
||||
pluginCommand.getCommand().stream().collect(Collectors.joining(" ")));
|
||||
assertEquals(envCommand.getType(), "custom");
|
||||
assertEquals(envCommand.getAttributes().get("workDir"), pluginCommand.getWorkingDir());
|
||||
assertEquals(
|
||||
envCommand.getAttributes().get(WORKING_DIRECTORY_ATTRIBUTE), pluginCommand.getWorkingDir());
|
||||
assertEquals(envCommand.getAttributes().get("machineName"), POD_NAME + "/plugin-container");
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,122 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!--
|
||||
|
||||
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
|
||||
|
||||
-->
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<parent>
|
||||
<artifactId>che-multiuser-permission</artifactId>
|
||||
<groupId>org.eclipse.che.multiuser</groupId>
|
||||
<version>6.16.0-SNAPSHOT</version>
|
||||
</parent>
|
||||
<artifactId>che-multiuser-permission-devfile</artifactId>
|
||||
<name>Che Multiuser :: Devfile Permissions</name>
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>javax.inject</groupId>
|
||||
<artifactId>javax.inject</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>javax.ws.rs</groupId>
|
||||
<artifactId>javax.ws.rs-api</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.eclipse.che.core</groupId>
|
||||
<artifactId>che-core-api-core</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.eclipse.che.core</groupId>
|
||||
<artifactId>che-core-api-devfile</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.eclipse.che.core</groupId>
|
||||
<artifactId>che-core-api-workspace</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.eclipse.che.core</groupId>
|
||||
<artifactId>che-core-commons-test</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.eclipse.che.multiuser</groupId>
|
||||
<artifactId>che-multiuser-api-permission</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.eclipse.che.multiuser</groupId>
|
||||
<artifactId>che-multiuser-permission-workspace</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.everrest</groupId>
|
||||
<artifactId>everrest-core</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>ch.qos.logback</groupId>
|
||||
<artifactId>logback-classic</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.jayway.restassured</groupId>
|
||||
<artifactId>rest-assured</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.eclipse.che.core</groupId>
|
||||
<artifactId>che-core-api-dto</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.eclipse.che.core</groupId>
|
||||
<artifactId>che-core-api-factory-shared</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.everrest</groupId>
|
||||
<artifactId>everrest-assured</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.mockito</groupId>
|
||||
<artifactId>mockito-core</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.mockito</groupId>
|
||||
<artifactId>mockito-testng</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.testng</groupId>
|
||||
<artifactId>testng</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-dependency-plugin</artifactId>
|
||||
<executions>
|
||||
<!-- compiler inlines constants, so it is impossible to find reference on dependency -->
|
||||
<execution>
|
||||
<id>analyze</id>
|
||||
<configuration>
|
||||
<ignoredDependencies>
|
||||
<ignoreDependency>org.eclipse.che.multiuser:che-multiuser-api-permission</ignoreDependency>
|
||||
<ignoreDependency>org.eclipse.che.core:che-core-api-devfile</ignoreDependency>
|
||||
</ignoredDependencies>
|
||||
</configuration>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
</project>
|
||||
|
|
@ -0,0 +1,73 @@
|
|||
/*
|
||||
* 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.multiuser.permission.devfile;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javax.ws.rs.Path;
|
||||
import org.eclipse.che.api.core.ForbiddenException;
|
||||
import org.eclipse.che.api.core.NotFoundException;
|
||||
import org.eclipse.che.api.core.ServerException;
|
||||
import org.eclipse.che.api.devfile.server.DevfileService;
|
||||
import org.eclipse.che.api.workspace.server.WorkspaceManager;
|
||||
import org.eclipse.che.api.workspace.server.model.impl.WorkspaceImpl;
|
||||
import org.eclipse.che.commons.env.EnvironmentContext;
|
||||
import org.eclipse.che.commons.subject.Subject;
|
||||
import org.eclipse.che.everrest.CheMethodInvokerFilter;
|
||||
import org.eclipse.che.multiuser.permission.workspace.server.WorkspaceDomain;
|
||||
import org.everrest.core.Filter;
|
||||
import org.everrest.core.resource.GenericResourceMethod;
|
||||
|
||||
/** Restricts access to methods of {@link DevfileService} by user's permissions. */
|
||||
@Filter
|
||||
@Path("/devfile{path:(/.*)?}")
|
||||
public class DevfilePermissionsFilter extends CheMethodInvokerFilter {
|
||||
|
||||
private final WorkspaceManager workspaceManager;
|
||||
|
||||
@Inject
|
||||
public DevfilePermissionsFilter(WorkspaceManager workspaceManager) {
|
||||
this.workspaceManager = workspaceManager;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void filter(GenericResourceMethod genericResourceMethod, Object[] arguments)
|
||||
throws ForbiddenException, NotFoundException, ServerException {
|
||||
final String methodName = genericResourceMethod.getMethod().getName();
|
||||
switch (methodName) {
|
||||
// public methods
|
||||
case "getSchema":
|
||||
case "createFromYaml":
|
||||
return;
|
||||
case "createFromWorkspace":
|
||||
{
|
||||
// check user has reading rights
|
||||
checkPermissionsWithCompositeKey((String) arguments[0]);
|
||||
return;
|
||||
}
|
||||
default:
|
||||
throw new ForbiddenException("The user does not have permission to perform this operation");
|
||||
}
|
||||
}
|
||||
|
||||
private void checkPermissionsWithCompositeKey(String key)
|
||||
throws ForbiddenException, NotFoundException, ServerException {
|
||||
final Subject currentSubject = EnvironmentContext.getCurrent().getSubject();
|
||||
if (!key.contains(":") && !key.contains("/")) {
|
||||
// key is id
|
||||
currentSubject.checkPermission(WorkspaceDomain.DOMAIN_ID, key, WorkspaceDomain.READ);
|
||||
} else {
|
||||
final WorkspaceImpl workspace = workspaceManager.getWorkspace(key);
|
||||
currentSubject.checkPermission(
|
||||
WorkspaceDomain.DOMAIN_ID, workspace.getId(), WorkspaceDomain.READ);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,116 @@
|
|||
/*
|
||||
* 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.multiuser.permissions.devfile;
|
||||
|
||||
import static com.jayway.restassured.RestAssured.given;
|
||||
import static org.eclipse.che.multiuser.permission.workspace.server.WorkspaceDomain.DOMAIN_ID;
|
||||
import static org.eclipse.che.multiuser.permission.workspace.server.WorkspaceDomain.READ;
|
||||
import static org.everrest.assured.JettyHttpServer.ADMIN_USER_NAME;
|
||||
import static org.everrest.assured.JettyHttpServer.ADMIN_USER_PASSWORD;
|
||||
import static org.everrest.assured.JettyHttpServer.SECURE_PATH;
|
||||
import static org.mockito.ArgumentMatchers.anyString;
|
||||
import static org.mockito.ArgumentMatchers.eq;
|
||||
import static org.mockito.Mockito.doThrow;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
import static org.testng.Assert.assertEquals;
|
||||
|
||||
import com.jayway.restassured.response.Response;
|
||||
import org.eclipse.che.api.core.ForbiddenException;
|
||||
import org.eclipse.che.api.devfile.server.DevfileService;
|
||||
import org.eclipse.che.api.workspace.server.WorkspaceManager;
|
||||
import org.eclipse.che.api.workspace.server.model.impl.WorkspaceImpl;
|
||||
import org.eclipse.che.commons.env.EnvironmentContext;
|
||||
import org.eclipse.che.commons.subject.Subject;
|
||||
import org.eclipse.che.multiuser.permission.devfile.DevfilePermissionsFilter;
|
||||
import org.everrest.assured.EverrestJetty;
|
||||
import org.everrest.core.Filter;
|
||||
import org.everrest.core.GenericContainerRequest;
|
||||
import org.everrest.core.RequestFilter;
|
||||
import org.mockito.InjectMocks;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.testng.MockitoTestNGListener;
|
||||
import org.testng.annotations.Listeners;
|
||||
import org.testng.annotations.Test;
|
||||
|
||||
@Listeners(value = {EverrestJetty.class, MockitoTestNGListener.class})
|
||||
public class DevfilePermissionsFilterTest {
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
private static final EnvironmentFilter FILTER = new EnvironmentFilter();
|
||||
|
||||
@Mock private static Subject subject;
|
||||
@Mock private WorkspaceManager workspaceManager;
|
||||
@Mock private DevfileService service;
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
@InjectMocks
|
||||
private DevfilePermissionsFilter permissionsFilter;
|
||||
|
||||
@Test
|
||||
public void shouldCheckPermissionsOnExportingWorkspaceById() throws Exception {
|
||||
final String wsId = "workspace123";
|
||||
final Response response =
|
||||
given()
|
||||
.auth()
|
||||
.basic(ADMIN_USER_NAME, ADMIN_USER_PASSWORD)
|
||||
.when()
|
||||
.get(SECURE_PATH + "/devfile/" + wsId);
|
||||
|
||||
assertEquals(response.getStatusCode(), 204);
|
||||
verify(subject).checkPermission(DOMAIN_ID, wsId, READ);
|
||||
verify(service).createFromWorkspace((eq(wsId)));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldCheckPermissionsOnExportingWorkspaceByKey() throws Exception {
|
||||
final String key = "namespace/ws_name";
|
||||
final String wsId = "workspace123";
|
||||
WorkspaceImpl workspace = new WorkspaceImpl();
|
||||
workspace.setId(wsId);
|
||||
when(workspaceManager.getWorkspace(eq(key))).thenReturn(workspace);
|
||||
final Response response =
|
||||
given()
|
||||
.auth()
|
||||
.basic(ADMIN_USER_NAME, ADMIN_USER_PASSWORD)
|
||||
.when()
|
||||
.get(SECURE_PATH + "/devfile/" + key);
|
||||
|
||||
assertEquals(response.getStatusCode(), 204);
|
||||
verify(subject).checkPermission(DOMAIN_ID, wsId, READ);
|
||||
verify(service).createFromWorkspace((eq(key)));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldReturnForbiddenWhenUserDoesHavePermissionsToExportWorkspaceToDevfile()
|
||||
throws Exception {
|
||||
doThrow(new ForbiddenException("User in not authorized"))
|
||||
.when(subject)
|
||||
.checkPermission(anyString(), anyString(), anyString());
|
||||
|
||||
final Response response =
|
||||
given()
|
||||
.auth()
|
||||
.basic(ADMIN_USER_NAME, ADMIN_USER_PASSWORD)
|
||||
.when()
|
||||
.get(SECURE_PATH + "/devfile/workspace123");
|
||||
|
||||
assertEquals(response.getStatusCode(), 403);
|
||||
}
|
||||
|
||||
@Filter
|
||||
public static class EnvironmentFilter implements RequestFilter {
|
||||
public void doFilter(GenericContainerRequest request) {
|
||||
EnvironmentContext.getCurrent().setSubject(subject);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,26 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!--
|
||||
|
||||
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
|
||||
|
||||
-->
|
||||
<configuration>
|
||||
|
||||
<appender name="stdout" class="ch.qos.logback.core.ConsoleAppender">
|
||||
<encoder>
|
||||
<pattern>%-41(%date[%.15thread]) %-45([%-5level] [%.30logger{30} %L]) - %msg%n</pattern>
|
||||
</encoder>
|
||||
</appender>
|
||||
<root level="ERROR">
|
||||
<appender-ref ref="stdout"/>
|
||||
</root>
|
||||
|
||||
</configuration>
|
||||
|
|
@ -25,6 +25,7 @@
|
|||
<name>Che Multiuser :: Permissions Parent</name>
|
||||
<modules>
|
||||
<module>che-multiuser-permission-user</module>
|
||||
<module>che-multiuser-permission-devfile</module>
|
||||
<module>che-multiuser-permission-workspace</module>
|
||||
<module>che-multiuser-permission-workspace-activity</module>
|
||||
<module>che-multiuser-permission-factory</module>
|
||||
|
|
|
|||
10
pom.xml
10
pom.xml
|
|
@ -325,6 +325,11 @@
|
|||
<version>${che.version}</version>
|
||||
<classifier>sources</classifier>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.eclipse.che.core</groupId>
|
||||
<artifactId>che-core-api-devfile</artifactId>
|
||||
<version>${che.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.eclipse.che.core</groupId>
|
||||
<artifactId>che-core-api-dto</artifactId>
|
||||
|
|
@ -1062,6 +1067,11 @@
|
|||
<version>${che.version}</version>
|
||||
<classifier>sources</classifier>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.eclipse.che.multiuser</groupId>
|
||||
<artifactId>che-multiuser-permission-devfile</artifactId>
|
||||
<version>${che.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.eclipse.che.multiuser</groupId>
|
||||
<artifactId>che-multiuser-permission-factory</artifactId>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,181 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!--
|
||||
|
||||
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
|
||||
|
||||
-->
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<parent>
|
||||
<artifactId>che-master-parent</artifactId>
|
||||
<groupId>org.eclipse.che.core</groupId>
|
||||
<version>6.16.0-SNAPSHOT</version>
|
||||
</parent>
|
||||
<artifactId>che-core-api-devfile</artifactId>
|
||||
<packaging>jar</packaging>
|
||||
<name>Che Core :: API :: Devfile</name>
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>com.fasterxml.jackson.core</groupId>
|
||||
<artifactId>jackson-annotations</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.fasterxml.jackson.core</groupId>
|
||||
<artifactId>jackson-core</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.fasterxml.jackson.core</groupId>
|
||||
<artifactId>jackson-databind</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.fasterxml.jackson.dataformat</groupId>
|
||||
<artifactId>jackson-dataformat-yaml</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.github.java-json-tools</groupId>
|
||||
<artifactId>jackson-coreutils</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.github.java-json-tools</groupId>
|
||||
<artifactId>json-schema-core</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.github.java-json-tools</groupId>
|
||||
<artifactId>json-schema-validator</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.google.guava</groupId>
|
||||
<artifactId>guava</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.swagger</groupId>
|
||||
<artifactId>swagger-annotations</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>javax.inject</groupId>
|
||||
<artifactId>javax.inject</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.eclipse.che.core</groupId>
|
||||
<artifactId>che-core-api-core</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.eclipse.che.core</groupId>
|
||||
<artifactId>che-core-api-model</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.eclipse.che.core</groupId>
|
||||
<artifactId>che-core-api-workspace</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.eclipse.che.core</groupId>
|
||||
<artifactId>che-core-api-workspace-shared</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.eclipse.che.core</groupId>
|
||||
<artifactId>che-core-commons-lang</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>javax.ws.rs</groupId>
|
||||
<artifactId>javax.ws.rs-api</artifactId>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.jayway.restassured</groupId>
|
||||
<artifactId>rest-assured</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.eclipse.che.core</groupId>
|
||||
<artifactId>che-core-api-account</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.eclipse.che.core</groupId>
|
||||
<artifactId>che-core-api-dto</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.eclipse.che.core</groupId>
|
||||
<artifactId>che-core-commons-json</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.everrest</groupId>
|
||||
<artifactId>everrest-assured</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.everrest</groupId>
|
||||
<artifactId>everrest-core</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.mockito</groupId>
|
||||
<artifactId>mockito-core</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.mockito</groupId>
|
||||
<artifactId>mockito-testng</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.testng</groupId>
|
||||
<artifactId>testng</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.codehaus.mojo</groupId>
|
||||
<artifactId>build-helper-maven-plugin</artifactId>
|
||||
<version>1.4</version>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>test</id>
|
||||
<phase>generate-sources</phase>
|
||||
<goals>
|
||||
<goal>add-source</goal>
|
||||
</goals>
|
||||
<configuration>
|
||||
<sources>
|
||||
<source>${basedir}/target/java-gen</source>
|
||||
</sources>
|
||||
</configuration>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.jsonschema2pojo</groupId>
|
||||
<artifactId>jsonschema2pojo-maven-plugin</artifactId>
|
||||
<version>0.5.1</version>
|
||||
<executions>
|
||||
<execution>
|
||||
<goals>
|
||||
<goal>generate</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
</executions>
|
||||
<configuration>
|
||||
<sourceDirectory>${basedir}/src/main/resources/schema</sourceDirectory>
|
||||
<targetPackage>org.eclipse.che.api.devfile.model</targetPackage>
|
||||
<includeAdditionalProperties>false</includeAdditionalProperties>
|
||||
<includeHashcodeAndEquals>false</includeHashcodeAndEquals>
|
||||
<includeToString>false</includeToString>
|
||||
<initializeCollections>true</initializeCollections>
|
||||
<generateBuilders>true</generateBuilders>
|
||||
</configuration>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
</project>
|
||||
|
|
@ -0,0 +1,29 @@
|
|||
/*
|
||||
* 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.devfile.server;
|
||||
|
||||
public class Constants {
|
||||
|
||||
public static final String SCHEMA_LOCATION = "schema/devfile.json";
|
||||
|
||||
public static final String CURRENT_SPEC_VERSION = "0.0.1";
|
||||
|
||||
/**
|
||||
* Workspace attribute which contains comma-separated list of mappings of tool id to its name
|
||||
* Example value:
|
||||
*
|
||||
* <pre>
|
||||
* eclipse/maven-jdk8:1.0.0=mvn-stack,eclipse/theia:0.0.3=theia-ide,eclipse/theia-jdtls:0.0.3=jdt.ls
|
||||
* </pre>
|
||||
*/
|
||||
public static final String ALIASES_WORKSPACE_ATTRIBUTE_NAME = "toolsAliases";
|
||||
}
|
||||
|
|
@ -0,0 +1,221 @@
|
|||
/*
|
||||
* 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.devfile.server;
|
||||
|
||||
import static com.google.common.base.MoreObjects.firstNonNull;
|
||||
import static com.google.common.base.Strings.isNullOrEmpty;
|
||||
import static java.lang.String.format;
|
||||
import static org.eclipse.che.api.core.model.workspace.config.Command.WORKING_DIRECTORY_ATTRIBUTE;
|
||||
import static org.eclipse.che.api.devfile.server.Constants.ALIASES_WORKSPACE_ATTRIBUTE_NAME;
|
||||
import static org.eclipse.che.api.devfile.server.Constants.CURRENT_SPEC_VERSION;
|
||||
import static org.eclipse.che.api.workspace.shared.Constants.WORKSPACE_TOOLING_EDITOR_ATTRIBUTE;
|
||||
import static org.eclipse.che.api.workspace.shared.Constants.WORKSPACE_TOOLING_PLUGINS_ATTRIBUTE;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.StringJoiner;
|
||||
import java.util.stream.Collectors;
|
||||
import org.eclipse.che.api.devfile.model.Action;
|
||||
import org.eclipse.che.api.devfile.model.Command;
|
||||
import org.eclipse.che.api.devfile.model.Devfile;
|
||||
import org.eclipse.che.api.devfile.model.Project;
|
||||
import org.eclipse.che.api.devfile.model.Source;
|
||||
import org.eclipse.che.api.devfile.model.Tool;
|
||||
import org.eclipse.che.api.workspace.server.model.impl.CommandImpl;
|
||||
import org.eclipse.che.api.workspace.server.model.impl.ProjectConfigImpl;
|
||||
import org.eclipse.che.api.workspace.server.model.impl.SourceStorageImpl;
|
||||
import org.eclipse.che.api.workspace.server.model.impl.WorkspaceConfigImpl;
|
||||
|
||||
/** Helps to convert devfile into workspace config and back. */
|
||||
public class DevfileConverter {
|
||||
|
||||
public Devfile workspaceToDevFile(WorkspaceConfigImpl wsConfig) throws WorkspaceExportException {
|
||||
|
||||
if (!isNullOrEmpty(wsConfig.getDefaultEnv()) || !wsConfig.getEnvironments().isEmpty()) {
|
||||
throw new WorkspaceExportException(
|
||||
format(
|
||||
"Workspace %s cannot be converted to devfile since it is contains environments (which have no equivalent in devfile model)",
|
||||
wsConfig.getName()));
|
||||
}
|
||||
|
||||
Devfile devFile =
|
||||
new Devfile().withSpecVersion(CURRENT_SPEC_VERSION).withName(wsConfig.getName());
|
||||
|
||||
// Manage projects
|
||||
List<Project> projects = new ArrayList<>();
|
||||
wsConfig
|
||||
.getProjects()
|
||||
.forEach(projectConfig -> projects.add(projectConfigToDevProject(projectConfig)));
|
||||
devFile.setProjects(projects);
|
||||
|
||||
// Manage commands
|
||||
Map<String, String> toolsIdToName = parseTools(wsConfig);
|
||||
List<Command> commands = new ArrayList<>();
|
||||
wsConfig
|
||||
.getCommands()
|
||||
.forEach(command -> commands.add(commandImplToDevCommand(command, toolsIdToName)));
|
||||
devFile.setCommands(commands);
|
||||
|
||||
// Manage tools
|
||||
List<Tool> tools = new ArrayList<>();
|
||||
for (Map.Entry<String, String> entry : wsConfig.getAttributes().entrySet()) {
|
||||
if (entry.getKey().equals(WORKSPACE_TOOLING_EDITOR_ATTRIBUTE)) {
|
||||
String editorId = entry.getValue();
|
||||
Tool editorTool =
|
||||
new Tool()
|
||||
.withType("cheEditor")
|
||||
.withId(editorId)
|
||||
.withName(toolsIdToName.getOrDefault(editorId, editorId));
|
||||
tools.add(editorTool);
|
||||
} else if (entry.getKey().equals(WORKSPACE_TOOLING_PLUGINS_ATTRIBUTE)) {
|
||||
for (String pluginId : entry.getValue().split(",")) {
|
||||
Tool pluginTool =
|
||||
new Tool()
|
||||
.withId(pluginId)
|
||||
.withType("chePlugin")
|
||||
.withName(toolsIdToName.getOrDefault(pluginId, pluginId));
|
||||
tools.add(pluginTool);
|
||||
}
|
||||
}
|
||||
}
|
||||
devFile.setTools(tools);
|
||||
return devFile;
|
||||
}
|
||||
|
||||
public WorkspaceConfigImpl devFileToWorkspaceConfig(Devfile devFile)
|
||||
throws DevfileFormatException {
|
||||
validateCurrentVersion(devFile);
|
||||
WorkspaceConfigImpl config = new WorkspaceConfigImpl();
|
||||
|
||||
config.setName(devFile.getName());
|
||||
|
||||
// Manage projects
|
||||
List<ProjectConfigImpl> projects = new ArrayList<>();
|
||||
devFile.getProjects().forEach(project -> projects.add(devProjectToProjectConfig(project)));
|
||||
config.setProjects(projects);
|
||||
|
||||
// Manage tools
|
||||
Map<String, String> attributes = new HashMap<>();
|
||||
StringJoiner pluginsStringJoiner = new StringJoiner(",");
|
||||
StringJoiner toolIdToNameMappingStringJoiner = new StringJoiner(",");
|
||||
for (Tool tool : devFile.getTools()) {
|
||||
switch (tool.getType()) {
|
||||
case "cheEditor":
|
||||
attributes.put(WORKSPACE_TOOLING_EDITOR_ATTRIBUTE, tool.getId());
|
||||
break;
|
||||
case "chePlugin":
|
||||
pluginsStringJoiner.add(tool.getId());
|
||||
break;
|
||||
default:
|
||||
throw new DevfileFormatException(
|
||||
format("Unsupported tool %s type provided: %s", tool.getName(), tool.getType()));
|
||||
}
|
||||
toolIdToNameMappingStringJoiner.add(tool.getId() + "=" + tool.getName());
|
||||
}
|
||||
if (pluginsStringJoiner.length() > 0) {
|
||||
attributes.put(WORKSPACE_TOOLING_PLUGINS_ATTRIBUTE, pluginsStringJoiner.toString());
|
||||
}
|
||||
if (toolIdToNameMappingStringJoiner.length() > 0) {
|
||||
attributes.put(ALIASES_WORKSPACE_ATTRIBUTE_NAME, toolIdToNameMappingStringJoiner.toString());
|
||||
}
|
||||
config.setAttributes(attributes);
|
||||
|
||||
// Manage commands
|
||||
List<CommandImpl> commands = new ArrayList<>();
|
||||
devFile
|
||||
.getCommands()
|
||||
.forEach(command -> commands.addAll(devCommandToCommandImpls(devFile, command)));
|
||||
config.setCommands(commands);
|
||||
return config;
|
||||
}
|
||||
|
||||
private List<CommandImpl> devCommandToCommandImpls(Devfile devFile, Command devCommand) {
|
||||
List<CommandImpl> commands = new ArrayList<>();
|
||||
for (Action devAction : devCommand.getActions()) {
|
||||
CommandImpl command = new CommandImpl();
|
||||
command.setName(devCommand.getName() + ":" + devAction.getTool());
|
||||
command.setType(devAction.getType());
|
||||
command.setCommandLine(devAction.getCommand());
|
||||
if (devAction.getWorkdir() != null) {
|
||||
command.getAttributes().put(WORKING_DIRECTORY_ATTRIBUTE, devAction.getWorkdir());
|
||||
}
|
||||
Optional<Tool> toolOfCommand =
|
||||
devFile
|
||||
.getTools()
|
||||
.stream()
|
||||
.filter(tool -> tool.getName().equals(devAction.getTool()))
|
||||
.findFirst();
|
||||
if (toolOfCommand.isPresent() && !isNullOrEmpty(toolOfCommand.get().getId())) {
|
||||
command.getAttributes().put("pluginId", toolOfCommand.get().getId());
|
||||
}
|
||||
if (devCommand.getAttributes() != null) {
|
||||
command.getAttributes().putAll(devCommand.getAttributes());
|
||||
}
|
||||
commands.add(command);
|
||||
}
|
||||
return commands;
|
||||
}
|
||||
|
||||
private Command commandImplToDevCommand(CommandImpl command, Map<String, String> toolsIdToName) {
|
||||
Command devCommand = new Command().withName(command.getName());
|
||||
Action action = new Action().withCommand(command.getCommandLine()).withType(command.getType());
|
||||
String workingDir = command.getAttributes().get(WORKING_DIRECTORY_ATTRIBUTE);
|
||||
if (!isNullOrEmpty(workingDir)) {
|
||||
action.setWorkdir(workingDir);
|
||||
}
|
||||
action.setTool(toolsIdToName.getOrDefault(command.getAttributes().get("pluginId"), ""));
|
||||
devCommand.getActions().add(action);
|
||||
devCommand.setAttributes(command.getAttributes());
|
||||
// Remove internal attributes
|
||||
devCommand.getAttributes().remove(WORKING_DIRECTORY_ATTRIBUTE);
|
||||
devCommand.getAttributes().remove("pluginId");
|
||||
return devCommand;
|
||||
}
|
||||
|
||||
private Project projectConfigToDevProject(ProjectConfigImpl projectConfig) {
|
||||
Source source =
|
||||
new Source()
|
||||
.withType(projectConfig.getSource().getType())
|
||||
.withLocation(projectConfig.getSource().getLocation());
|
||||
return new Project().withName(projectConfig.getName()).withSource(source);
|
||||
}
|
||||
|
||||
private ProjectConfigImpl devProjectToProjectConfig(Project devProject) {
|
||||
ProjectConfigImpl projectConfig = new ProjectConfigImpl();
|
||||
projectConfig.setName(devProject.getName());
|
||||
projectConfig.setPath("/" + projectConfig.getName());
|
||||
SourceStorageImpl sourceStorage = new SourceStorageImpl();
|
||||
sourceStorage.setType(devProject.getSource().getType());
|
||||
sourceStorage.setLocation(devProject.getSource().getLocation());
|
||||
projectConfig.setSource(sourceStorage);
|
||||
return projectConfig;
|
||||
}
|
||||
|
||||
private Map<String, String> parseTools(WorkspaceConfigImpl wsConfig) {
|
||||
String aliasesString =
|
||||
firstNonNull(wsConfig.getAttributes().get(ALIASES_WORKSPACE_ATTRIBUTE_NAME), "");
|
||||
return Arrays.stream(aliasesString.split(","))
|
||||
.map(s -> s.split("=", 2))
|
||||
.collect(Collectors.toMap(arr -> arr[0], arr -> arr[1]));
|
||||
}
|
||||
|
||||
private static void validateCurrentVersion(Devfile devFile) throws DevfileFormatException {
|
||||
if (!CURRENT_SPEC_VERSION.equals(devFile.getSpecVersion())) {
|
||||
throw new DevfileFormatException(
|
||||
format("Provided Devfile has unsupported version %s", devFile.getSpecVersion()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,20 @@
|
|||
/*
|
||||
* 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.devfile.server;
|
||||
|
||||
/** Thrown when devfile schema or integrity validation is failed. */
|
||||
public class DevfileFormatException extends Exception {
|
||||
|
||||
public DevfileFormatException(String formatError) {
|
||||
super(formatError);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,46 @@
|
|||
/*
|
||||
* 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.devfile.server;
|
||||
|
||||
import static org.eclipse.che.api.devfile.server.Constants.SCHEMA_LOCATION;
|
||||
import static org.eclipse.che.commons.lang.IoUtil.getResource;
|
||||
import static org.eclipse.che.commons.lang.IoUtil.readAndCloseQuietly;
|
||||
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import com.github.fge.jackson.JsonLoader;
|
||||
import java.io.IOException;
|
||||
import java.lang.ref.SoftReference;
|
||||
import javax.inject.Singleton;
|
||||
|
||||
/** Loads a schema content and stores it in soft reference. */
|
||||
@Singleton
|
||||
public class DevfileSchemaProvider {
|
||||
|
||||
private SoftReference<String> schemaRef = new SoftReference<>(null);
|
||||
|
||||
public String getSchemaContent() throws IOException {
|
||||
String schema = schemaRef.get();
|
||||
if (schema == null) {
|
||||
schema = loadFile();
|
||||
schemaRef = new SoftReference<>(schema);
|
||||
}
|
||||
return schema;
|
||||
}
|
||||
|
||||
public JsonNode getJsoneNode() throws IOException {
|
||||
return JsonLoader.fromString(getSchemaContent());
|
||||
}
|
||||
|
||||
private String loadFile() throws IOException {
|
||||
return readAndCloseQuietly(getResource(SCHEMA_LOCATION));
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,65 @@
|
|||
/*
|
||||
* 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.devfile.server;
|
||||
|
||||
import static java.lang.String.format;
|
||||
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.fasterxml.jackson.dataformat.yaml.YAMLFactory;
|
||||
import com.github.fge.jsonschema.core.exceptions.ProcessingException;
|
||||
import com.github.fge.jsonschema.core.report.LogLevel;
|
||||
import com.github.fge.jsonschema.core.report.ProcessingReport;
|
||||
import com.github.fge.jsonschema.main.JsonSchemaFactory;
|
||||
import com.github.fge.jsonschema.main.JsonValidator;
|
||||
import java.io.IOException;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.StreamSupport;
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Singleton;
|
||||
|
||||
/** Validates YAML devfile content against given JSON schema. */
|
||||
@Singleton
|
||||
public class DevfileSchemaValidator {
|
||||
|
||||
private JsonValidator validator;
|
||||
private ObjectMapper yamlReader;
|
||||
private DevfileSchemaProvider schemaProvider;
|
||||
|
||||
@Inject
|
||||
DevfileSchemaValidator(DevfileSchemaProvider schemaProvider) {
|
||||
this.schemaProvider = schemaProvider;
|
||||
this.validator = JsonSchemaFactory.byDefault().getValidator();
|
||||
this.yamlReader = new ObjectMapper(new YAMLFactory());
|
||||
}
|
||||
|
||||
JsonNode validateBySchema(String yamlContent, boolean verbose) throws DevfileFormatException {
|
||||
ProcessingReport report;
|
||||
JsonNode data;
|
||||
try {
|
||||
data = yamlReader.readTree(yamlContent);
|
||||
report = validator.validate(schemaProvider.getJsoneNode(), data);
|
||||
} catch (IOException | ProcessingException e) {
|
||||
throw new DevfileFormatException("Unable to validate Devfile. Error: " + e.getMessage());
|
||||
}
|
||||
if (!report.isSuccess()) {
|
||||
String error =
|
||||
StreamSupport.stream(report.spliterator(), false)
|
||||
.filter(m -> m.getLogLevel() == LogLevel.ERROR || m.getLogLevel() == LogLevel.FATAL)
|
||||
.map(message -> verbose ? message.asJson().toString() : message.getMessage())
|
||||
.collect(Collectors.joining(", ", "[", "]"));
|
||||
throw new DevfileFormatException(
|
||||
format("Devfile schema validation failed. Errors: %s", error));
|
||||
}
|
||||
return data;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,213 @@
|
|||
/*
|
||||
* 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.devfile.server;
|
||||
|
||||
import static java.util.Collections.emptyMap;
|
||||
import static javax.ws.rs.core.MediaType.APPLICATION_JSON;
|
||||
import static org.eclipse.che.api.workspace.server.DtoConverter.asDto;
|
||||
import static org.eclipse.che.api.workspace.server.WorkspaceKeyValidator.validateKey;
|
||||
|
||||
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.fasterxml.jackson.dataformat.yaml.YAMLFactory;
|
||||
import io.swagger.annotations.Api;
|
||||
import io.swagger.annotations.ApiOperation;
|
||||
import io.swagger.annotations.ApiParam;
|
||||
import io.swagger.annotations.ApiResponse;
|
||||
import io.swagger.annotations.ApiResponses;
|
||||
import io.swagger.annotations.Example;
|
||||
import io.swagger.annotations.ExampleProperty;
|
||||
import java.io.IOException;
|
||||
import javax.inject.Inject;
|
||||
import javax.ws.rs.Consumes;
|
||||
import javax.ws.rs.DefaultValue;
|
||||
import javax.ws.rs.GET;
|
||||
import javax.ws.rs.POST;
|
||||
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.Response;
|
||||
import org.eclipse.che.api.core.BadRequestException;
|
||||
import org.eclipse.che.api.core.ConflictException;
|
||||
import org.eclipse.che.api.core.NotFoundException;
|
||||
import org.eclipse.che.api.core.ServerException;
|
||||
import org.eclipse.che.api.core.ValidationException;
|
||||
import org.eclipse.che.api.core.rest.Service;
|
||||
import org.eclipse.che.api.devfile.model.Devfile;
|
||||
import org.eclipse.che.api.workspace.server.WorkspaceLinksGenerator;
|
||||
import org.eclipse.che.api.workspace.server.WorkspaceManager;
|
||||
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.shared.dto.WorkspaceDto;
|
||||
import org.eclipse.che.commons.env.EnvironmentContext;
|
||||
|
||||
@Api(value = "/devfile", description = "Devfile REST API")
|
||||
@Path("/devfile")
|
||||
public class DevfileService extends Service {
|
||||
|
||||
private WorkspaceLinksGenerator linksGenerator;
|
||||
private DevfileSchemaValidator schemaValidator;
|
||||
private DevfileSchemaProvider schemaCachedProvider;
|
||||
private WorkspaceManager workspaceManager;
|
||||
private ObjectMapper objectMapper;
|
||||
private DevfileConverter devfileConverter;
|
||||
|
||||
@Inject
|
||||
public DevfileService(
|
||||
WorkspaceLinksGenerator linksGenerator,
|
||||
DevfileSchemaValidator schemaValidator,
|
||||
DevfileSchemaProvider schemaCachedProvider,
|
||||
WorkspaceManager workspaceManager) {
|
||||
this.linksGenerator = linksGenerator;
|
||||
this.schemaValidator = schemaValidator;
|
||||
this.schemaCachedProvider = schemaCachedProvider;
|
||||
this.workspaceManager = workspaceManager;
|
||||
this.objectMapper = new ObjectMapper(new YAMLFactory());
|
||||
this.devfileConverter = new DevfileConverter();
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the json schema.
|
||||
*
|
||||
* @return json schema
|
||||
*/
|
||||
@GET
|
||||
@Produces(APPLICATION_JSON)
|
||||
@ApiOperation(value = "Retrieves current version of devfile JSON schema")
|
||||
@ApiResponses({
|
||||
@ApiResponse(code = 200, message = "The schema successfully retrieved"),
|
||||
@ApiResponse(code = 500, message = "Internal server error occurred")
|
||||
})
|
||||
public Response getSchema() throws ServerException {
|
||||
try {
|
||||
return Response.ok(schemaCachedProvider.getSchemaContent()).build();
|
||||
} catch (IOException e) {
|
||||
throw new ServerException(e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates workspace from provided devfile
|
||||
*
|
||||
* @param data devfile content
|
||||
* @param verbose return more explained validation error messages if any
|
||||
* @return created workspace configuration
|
||||
*/
|
||||
@POST
|
||||
@Consumes({"text/yaml", "text/x-yaml", "application/yaml", "application/json"})
|
||||
@Produces(APPLICATION_JSON)
|
||||
@ApiOperation(
|
||||
value = "Create a new workspace based on provided devfile",
|
||||
notes =
|
||||
"This operation can be performed only by authorized user,"
|
||||
+ "this user will be the owner of the created workspace",
|
||||
response = WorkspaceDto.class)
|
||||
@ApiResponses({
|
||||
@ApiResponse(code = 200, message = "The workspace successfully created"),
|
||||
@ApiResponse(
|
||||
code = 400,
|
||||
message =
|
||||
"Provided devfile syntactically incorrect, doesn't match with actual schema or has integrity violations"),
|
||||
@ApiResponse(code = 403, message = "The user does not have access to create a new workspace"),
|
||||
@ApiResponse(code = 500, message = "Internal server error occurred")
|
||||
})
|
||||
public Response createFromYaml(
|
||||
String data,
|
||||
@ApiParam(value = "Provide extended validation messages")
|
||||
@DefaultValue("false")
|
||||
@QueryParam("verbose")
|
||||
boolean verbose)
|
||||
throws ServerException, ConflictException, NotFoundException, ValidationException,
|
||||
BadRequestException {
|
||||
|
||||
Devfile devFile;
|
||||
WorkspaceConfigImpl workspaceConfig;
|
||||
try {
|
||||
JsonNode parsed = schemaValidator.validateBySchema(data, verbose);
|
||||
devFile = objectMapper.treeToValue(parsed, Devfile.class);
|
||||
workspaceConfig = devfileConverter.devFileToWorkspaceConfig(devFile);
|
||||
} catch (IOException e) {
|
||||
throw new ServerException(e.getMessage());
|
||||
} catch (DevfileFormatException e) {
|
||||
throw new BadRequestException(e.getMessage());
|
||||
}
|
||||
|
||||
final String namespace = EnvironmentContext.getCurrent().getSubject().getUserName();
|
||||
WorkspaceImpl workspace =
|
||||
workspaceManager.createWorkspace(findAvailableName(workspaceConfig), namespace, emptyMap());
|
||||
return Response.status(201)
|
||||
.entity(asDto(workspace).withLinks(linksGenerator.genLinks(workspace, getServiceContext())))
|
||||
.build();
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates the devfile based on an existing workspace. Key is workspace id or
|
||||
* namespace/workspace_name
|
||||
*
|
||||
* @see WorkspaceManager#getByKey(String)
|
||||
*/
|
||||
@GET
|
||||
@Path("/{key:.*}")
|
||||
@Produces("text/yml")
|
||||
@ApiOperation(
|
||||
value = "Generates the devfile from given workspace",
|
||||
notes =
|
||||
"This operation can be performed only by authorized user,"
|
||||
+ "this user must be the owner of the exported workspace")
|
||||
@ApiResponses({
|
||||
@ApiResponse(code = 200, message = "The workspace successfully exported"),
|
||||
@ApiResponse(code = 403, message = "The user does not have access to create a new workspace"),
|
||||
@ApiResponse(code = 500, message = "Internal server error occurred")
|
||||
})
|
||||
public Response createFromWorkspace(
|
||||
@ApiParam(
|
||||
value = "Composite key",
|
||||
examples =
|
||||
@Example({
|
||||
@ExampleProperty("workspace12345678"),
|
||||
@ExampleProperty("namespace/workspace_name"),
|
||||
@ExampleProperty("namespace_part_1/namespace_part_2/workspace_name")
|
||||
}))
|
||||
@PathParam("key")
|
||||
String key)
|
||||
throws NotFoundException, ServerException, BadRequestException, ConflictException {
|
||||
validateKey(key);
|
||||
WorkspaceImpl workspace = workspaceManager.getWorkspace(key);
|
||||
try {
|
||||
Devfile workspaceDevFile = devfileConverter.workspaceToDevFile(workspace.getConfig());
|
||||
// Write object as YAML
|
||||
return Response.ok().entity(objectMapper.writeValueAsString(workspaceDevFile)).build();
|
||||
} catch (JsonProcessingException e) {
|
||||
throw new ServerException(e.getMessage(), e);
|
||||
} catch (WorkspaceExportException e) {
|
||||
throw new ConflictException(e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
private WorkspaceConfigImpl findAvailableName(WorkspaceConfigImpl config) throws ServerException {
|
||||
String nameCandidate = config.getName();
|
||||
String namespace = EnvironmentContext.getCurrent().getSubject().getUserName();
|
||||
int counter = 0;
|
||||
while (true) {
|
||||
try {
|
||||
workspaceManager.getWorkspace(nameCandidate, namespace);
|
||||
nameCandidate = config.getName() + "_" + ++counter;
|
||||
} catch (NotFoundException nf) {
|
||||
config.setName(nameCandidate);
|
||||
break;
|
||||
}
|
||||
}
|
||||
return config;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,20 @@
|
|||
/*
|
||||
* 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.devfile.server;
|
||||
|
||||
/** Thrown when workspace can not be exported into devfile by some reason. */
|
||||
public class WorkspaceExportException extends Exception {
|
||||
|
||||
public WorkspaceExportException(String error) {
|
||||
super(error);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,180 @@
|
|||
{
|
||||
"definitions": {
|
||||
"attributes" : {
|
||||
"id": "propertyList",
|
||||
"type": "object",
|
||||
"javaType": "java.util.Map<String, String>"
|
||||
}
|
||||
},
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"type": "object",
|
||||
"title": "The Root Schema of DevFile object",
|
||||
"required": [
|
||||
"specVersion",
|
||||
"name",
|
||||
"projects",
|
||||
"tools",
|
||||
"commands"
|
||||
],
|
||||
"properties": {
|
||||
"specVersion": {
|
||||
"type": "string",
|
||||
"title": "Devfile Schema Specification Version",
|
||||
"examples": [
|
||||
"0.0.1"
|
||||
]
|
||||
},
|
||||
"name": {
|
||||
"type": "string",
|
||||
"title": "Devfile Name",
|
||||
"examples": [
|
||||
"petclinic-dev-environment"
|
||||
]
|
||||
},
|
||||
"projects": {
|
||||
"type": "array",
|
||||
"title": "The Projects Schema",
|
||||
"description" : "Description of the project sources location and type",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"name",
|
||||
"source"
|
||||
],
|
||||
"properties": {
|
||||
"name": {
|
||||
"type": "string",
|
||||
"examples": [
|
||||
"petclinic"
|
||||
]
|
||||
},
|
||||
"source": {
|
||||
"type": "object",
|
||||
"title": "The Project Source Schema",
|
||||
"required": [
|
||||
"type",
|
||||
"location"
|
||||
],
|
||||
"properties": {
|
||||
"type": {
|
||||
"type": "string",
|
||||
"description": "Project-s source type.",
|
||||
"examples": [
|
||||
"git",
|
||||
"github",
|
||||
"zip"
|
||||
]
|
||||
},
|
||||
"location": {
|
||||
"type": "string",
|
||||
"description": "Project-s source location address. Should be URL for git and github located projects, and file:// for zip.",
|
||||
"examples": [
|
||||
"git@github.com:spring-projects/spring-petclinic.git"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"tools": {
|
||||
"type": "array",
|
||||
"title": "The Tools Schema",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"name",
|
||||
"type",
|
||||
"id"
|
||||
],
|
||||
"properties": {
|
||||
"name": {
|
||||
"type": "string",
|
||||
"examples": [
|
||||
"mvn-stack"
|
||||
]
|
||||
},
|
||||
"type": {
|
||||
"description": "Describes type or tool, e.g. whether it is and plugin or editor or other type",
|
||||
"type": "string",
|
||||
"examples": [
|
||||
"chePlugin",
|
||||
"cheEditor"
|
||||
]
|
||||
},
|
||||
"id": {
|
||||
"type": "string",
|
||||
"description": "Describes the tool FQN",
|
||||
"examples": [
|
||||
"eclipse/maven-jdk8:1.0.0"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"commands": {
|
||||
"type": "array",
|
||||
"title": "The Commands Schema",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"name",
|
||||
"actions"
|
||||
],
|
||||
"properties": {
|
||||
"name": {
|
||||
"type": "string",
|
||||
"examples": [
|
||||
"build"
|
||||
]
|
||||
},
|
||||
"attributes": {
|
||||
"$ref": "#/definitions/attributes"
|
||||
},
|
||||
"actions": {
|
||||
"type": "array",
|
||||
"title": "The Command Actions Schema",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"type",
|
||||
"tool",
|
||||
"command"
|
||||
],
|
||||
"properties": {
|
||||
"type": {
|
||||
"description": "Describes action type",
|
||||
"type": "string",
|
||||
"examples": [
|
||||
"exec"
|
||||
]
|
||||
},
|
||||
"tool": {
|
||||
"type": "string",
|
||||
"description": "Describes tool to which given action relates",
|
||||
"examples": [
|
||||
"mvn-stack"
|
||||
]
|
||||
},
|
||||
"command": {
|
||||
"type": "string",
|
||||
"description": "The actual action command-line string",
|
||||
"examples": [
|
||||
"mvn package"
|
||||
]
|
||||
},
|
||||
"workdir": {
|
||||
"type": "string",
|
||||
"description": "Working directory where the schema should be executed",
|
||||
"examples": [
|
||||
"/projects/spring-petclinic"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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.devfile.server;
|
||||
|
||||
import static org.testng.Assert.assertEquals;
|
||||
import static org.testng.Assert.assertTrue;
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.fasterxml.jackson.dataformat.yaml.YAMLFactory;
|
||||
import org.eclipse.che.api.devfile.model.Action;
|
||||
import org.eclipse.che.api.devfile.model.Command;
|
||||
import org.eclipse.che.api.devfile.model.Devfile;
|
||||
import org.eclipse.che.api.devfile.model.Project;
|
||||
import org.eclipse.che.api.devfile.model.Tool;
|
||||
import org.eclipse.che.api.workspace.server.model.impl.EnvironmentImpl;
|
||||
import org.eclipse.che.api.workspace.server.model.impl.WorkspaceConfigImpl;
|
||||
import org.eclipse.che.commons.json.JsonHelper;
|
||||
import org.testng.annotations.Test;
|
||||
import org.testng.reporters.Files;
|
||||
|
||||
public class DevfileConverterTest {
|
||||
|
||||
private ObjectMapper objectMapper = new ObjectMapper(new YAMLFactory());
|
||||
private DevfileConverter devfileConverter = new DevfileConverter();
|
||||
|
||||
@Test
|
||||
public void shouldBuildWorkspaceConfigFromYamlDevFile() throws Exception {
|
||||
|
||||
String yamlContent =
|
||||
Files.readFile(getClass().getClassLoader().getResourceAsStream("devfile.yaml"));
|
||||
|
||||
Devfile devFile = objectMapper.readValue(yamlContent, Devfile.class);
|
||||
|
||||
WorkspaceConfigImpl wsConfigImpl = devfileConverter.devFileToWorkspaceConfig(devFile);
|
||||
|
||||
String jsonContent =
|
||||
Files.readFile(getClass().getClassLoader().getResourceAsStream("workspace_config.json"));
|
||||
|
||||
assertEquals(wsConfigImpl, JsonHelper.fromJson(jsonContent, WorkspaceConfigImpl.class, null));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldBuildYamlDevFileFromWorkspaceConfig() throws Exception {
|
||||
|
||||
String jsonContent =
|
||||
Files.readFile(getClass().getClassLoader().getResourceAsStream("workspace_config.json"));
|
||||
WorkspaceConfigImpl workspaceConfig =
|
||||
JsonHelper.fromJson(jsonContent, WorkspaceConfigImpl.class, null);
|
||||
Devfile devFile = devfileConverter.workspaceToDevFile(workspaceConfig);
|
||||
|
||||
String yamlContent =
|
||||
Files.readFile(getClass().getClassLoader().getResourceAsStream("devfile.yaml"));
|
||||
|
||||
Devfile expectedDevFile = objectMapper.readValue(yamlContent, Devfile.class);
|
||||
|
||||
// Recursively compare
|
||||
assertEquals(devFile.getSpecVersion(), expectedDevFile.getSpecVersion());
|
||||
assertEquals(devFile.getName(), expectedDevFile.getName());
|
||||
assertEquals(devFile.getProjects().size(), expectedDevFile.getProjects().size());
|
||||
for (Project project : devFile.getProjects()) {
|
||||
Project expectedProject =
|
||||
expectedDevFile
|
||||
.getProjects()
|
||||
.stream()
|
||||
.filter(project1 -> project1.getName().equals(project.getName()))
|
||||
.findFirst()
|
||||
.get();
|
||||
assertEquals(project.getSource().getType(), expectedProject.getSource().getType());
|
||||
assertEquals(project.getSource().getLocation(), expectedProject.getSource().getLocation());
|
||||
}
|
||||
|
||||
assertEquals(devFile.getCommands().size(), expectedDevFile.getCommands().size());
|
||||
for (Command command : devFile.getCommands()) {
|
||||
Command expectedCommand =
|
||||
expectedDevFile
|
||||
.getCommands()
|
||||
.stream()
|
||||
.filter(command1 -> command1.getName().equals(command.getName().split(":")[0]))
|
||||
.findFirst()
|
||||
.get();
|
||||
for (Action action : command.getActions()) {
|
||||
Action expectedAction =
|
||||
expectedCommand
|
||||
.getActions()
|
||||
.stream()
|
||||
.filter(action1 -> action1.getTool().equals(action.getTool()))
|
||||
.findFirst()
|
||||
.get();
|
||||
assertEquals(action.getCommand(), expectedAction.getCommand());
|
||||
assertEquals(action.getType(), expectedAction.getType());
|
||||
assertEquals(action.getWorkdir(), expectedAction.getWorkdir());
|
||||
}
|
||||
if (command.getAttributes() != null && expectedCommand.getAttributes() != null) {
|
||||
assertTrue(
|
||||
command
|
||||
.getAttributes()
|
||||
.entrySet()
|
||||
.containsAll(expectedCommand.getAttributes().entrySet()));
|
||||
}
|
||||
}
|
||||
|
||||
assertEquals(devFile.getTools().size(), expectedDevFile.getTools().size());
|
||||
for (Tool tool : devFile.getTools()) {
|
||||
Tool expectedTool =
|
||||
expectedDevFile
|
||||
.getTools()
|
||||
.stream()
|
||||
.filter(tool1 -> tool1.getName().equals(tool.getName()))
|
||||
.findFirst()
|
||||
.get();
|
||||
assertEquals(tool.getId(), expectedTool.getId());
|
||||
assertEquals(tool.getType(), expectedTool.getType());
|
||||
}
|
||||
}
|
||||
|
||||
@Test(
|
||||
expectedExceptions = WorkspaceExportException.class,
|
||||
expectedExceptionsMessageRegExp =
|
||||
"Workspace .* cannot be converted to devfile since it is contains environments \\(which have no equivalent in devfile model\\)")
|
||||
public void shouldThrowExceptionWhenWorkspaceHasEnvironments() throws Exception {
|
||||
String jsonContent =
|
||||
Files.readFile(getClass().getClassLoader().getResourceAsStream("workspace_config.json"));
|
||||
WorkspaceConfigImpl workspaceConfig =
|
||||
JsonHelper.fromJson(jsonContent, WorkspaceConfigImpl.class, null);
|
||||
workspaceConfig.getEnvironments().put("env1", new EnvironmentImpl());
|
||||
|
||||
devfileConverter.workspaceToDevFile(workspaceConfig);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,45 @@
|
|||
/*
|
||||
* 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.devfile.server;
|
||||
|
||||
import org.testng.annotations.BeforeClass;
|
||||
import org.testng.annotations.Test;
|
||||
import org.testng.reporters.Files;
|
||||
|
||||
public class DevfileSchemaValidatorTest {
|
||||
|
||||
private DevfileSchemaValidator schemaValidator;
|
||||
|
||||
@BeforeClass
|
||||
public void setUp() throws Exception {
|
||||
schemaValidator = new DevfileSchemaValidator(new DevfileSchemaProvider());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldValidateCorrectYamlBySchema() throws Exception {
|
||||
String devFileYamlContent =
|
||||
Files.readFile(getClass().getClassLoader().getResourceAsStream("devfile.yaml"));
|
||||
// when
|
||||
schemaValidator.validateBySchema(devFileYamlContent, false);
|
||||
}
|
||||
|
||||
@Test(
|
||||
expectedExceptions = DevfileFormatException.class,
|
||||
expectedExceptionsMessageRegExp =
|
||||
"Devfile schema validation failed. Errors: \\[object has missing required properties \\(\\[\"name\"\\]\\)\\]$")
|
||||
public void shouldValidateIncorrectYamlBySchema() throws Exception {
|
||||
String devFileYamlContent =
|
||||
Files.readFile(getClass().getClassLoader().getResourceAsStream("devfile_bad.yaml"));
|
||||
// when
|
||||
schemaValidator.validateBySchema(devFileYamlContent, false);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,174 @@
|
|||
/*
|
||||
* 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.devfile.server;
|
||||
|
||||
import static com.jayway.restassured.RestAssured.given;
|
||||
import static org.everrest.assured.JettyHttpServer.ADMIN_USER_NAME;
|
||||
import static org.everrest.assured.JettyHttpServer.ADMIN_USER_PASSWORD;
|
||||
import static org.everrest.assured.JettyHttpServer.SECURE_PATH;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.ArgumentMatchers.anyMap;
|
||||
import static org.mockito.ArgumentMatchers.anyString;
|
||||
import static org.mockito.ArgumentMatchers.eq;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.spy;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
import static org.testng.Assert.assertEquals;
|
||||
import static org.testng.Assert.assertNotNull;
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.fasterxml.jackson.dataformat.yaml.YAMLFactory;
|
||||
import com.jayway.restassured.http.ContentType;
|
||||
import com.jayway.restassured.response.Response;
|
||||
import java.io.IOException;
|
||||
import org.eclipse.che.account.spi.AccountImpl;
|
||||
import org.eclipse.che.api.core.NotFoundException;
|
||||
import org.eclipse.che.api.core.model.workspace.WorkspaceConfig;
|
||||
import org.eclipse.che.api.core.model.workspace.WorkspaceStatus;
|
||||
import org.eclipse.che.api.devfile.model.Devfile;
|
||||
import org.eclipse.che.api.workspace.server.WorkspaceLinksGenerator;
|
||||
import org.eclipse.che.api.workspace.server.WorkspaceManager;
|
||||
import org.eclipse.che.api.workspace.server.model.impl.WorkspaceConfigImpl;
|
||||
import org.eclipse.che.api.workspace.server.model.impl.WorkspaceImpl;
|
||||
import org.eclipse.che.commons.env.EnvironmentContext;
|
||||
import org.eclipse.che.commons.json.JsonHelper;
|
||||
import org.eclipse.che.commons.json.JsonParseException;
|
||||
import org.eclipse.che.commons.subject.Subject;
|
||||
import org.eclipse.che.commons.subject.SubjectImpl;
|
||||
import org.everrest.assured.EverrestJetty;
|
||||
import org.everrest.core.Filter;
|
||||
import org.everrest.core.GenericContainerRequest;
|
||||
import org.everrest.core.RequestFilter;
|
||||
import org.mockito.ArgumentCaptor;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.testng.MockitoTestNGListener;
|
||||
import org.testng.annotations.BeforeMethod;
|
||||
import org.testng.annotations.Listeners;
|
||||
import org.testng.annotations.Test;
|
||||
import org.testng.reporters.Files;
|
||||
|
||||
@Listeners({EverrestJetty.class, MockitoTestNGListener.class})
|
||||
public class DevfileServiceTest {
|
||||
|
||||
@Mock private WorkspaceLinksGenerator linksGenerator;
|
||||
|
||||
@Mock private WorkspaceManager workspaceManager;
|
||||
@Mock private EnvironmentContext environmentContext;
|
||||
private DevfileSchemaProvider schemaProvider = new DevfileSchemaProvider();
|
||||
private DevfileSchemaValidator validator;
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
private static final EnvironmentFilter FILTER = new EnvironmentFilter();
|
||||
|
||||
private static final Subject SUBJECT = new SubjectImpl("user", "user123", "token", false);
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
private DevfileService devFileService;
|
||||
|
||||
@BeforeMethod
|
||||
public void initService() throws IOException {
|
||||
this.validator = spy(new DevfileSchemaValidator(schemaProvider));
|
||||
this.devFileService =
|
||||
new DevfileService(linksGenerator, validator, schemaProvider, workspaceManager);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldRetrieveSchema() throws Exception {
|
||||
final Response response =
|
||||
given()
|
||||
.auth()
|
||||
.basic(ADMIN_USER_NAME, ADMIN_USER_PASSWORD)
|
||||
.when()
|
||||
.get(SECURE_PATH + "/devfile");
|
||||
|
||||
assertEquals(response.getStatusCode(), 200);
|
||||
assertEquals(response.getBody().asString(), schemaProvider.getSchemaContent());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldAcceptDevFileAndFindAvailableName() throws Exception {
|
||||
ArgumentCaptor<WorkspaceConfigImpl> captor = ArgumentCaptor.forClass(WorkspaceConfigImpl.class);
|
||||
EnvironmentContext.setCurrent(environmentContext);
|
||||
WorkspaceImpl ws = mock(WorkspaceImpl.class);
|
||||
when(workspaceManager.createWorkspace(any(), eq(SUBJECT.getUserName()), anyMap()))
|
||||
.thenReturn(createWorkspace(WorkspaceStatus.STOPPED));
|
||||
String yamlContent =
|
||||
Files.readFile(getClass().getClassLoader().getResourceAsStream("devfile.yaml"));
|
||||
when(workspaceManager.getWorkspace(anyString(), anyString()))
|
||||
.thenAnswer(
|
||||
invocation -> {
|
||||
String wsname = invocation.getArgument(0);
|
||||
if (wsname.equals("petclinic-dev-environment")
|
||||
|| wsname.equals("petclinic-dev-environment_1")) {
|
||||
return ws;
|
||||
}
|
||||
throw new NotFoundException("ws not found");
|
||||
});
|
||||
|
||||
final Response response =
|
||||
given()
|
||||
.auth()
|
||||
.basic(ADMIN_USER_NAME, ADMIN_USER_PASSWORD)
|
||||
.contentType(ContentType.JSON)
|
||||
.body(yamlContent)
|
||||
.when()
|
||||
.post(SECURE_PATH + "/devfile");
|
||||
|
||||
assertEquals(response.getStatusCode(), 201);
|
||||
verify(validator).validateBySchema(eq(yamlContent), eq(false));
|
||||
verify(workspaceManager).createWorkspace(captor.capture(), eq(SUBJECT.getUserName()), anyMap());
|
||||
assertEquals("petclinic-dev-environment_2", captor.getValue().getName());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldCreateDevFileFromWorkspace() throws Exception {
|
||||
ObjectMapper objectMapper = new ObjectMapper(new YAMLFactory());
|
||||
when(workspaceManager.getWorkspace(anyString()))
|
||||
.thenReturn(createWorkspace(WorkspaceStatus.STOPPED));
|
||||
|
||||
final Response response =
|
||||
given()
|
||||
.auth()
|
||||
.basic(ADMIN_USER_NAME, ADMIN_USER_PASSWORD)
|
||||
.when()
|
||||
.get(SECURE_PATH + "/devfile/ws123456");
|
||||
|
||||
assertEquals(response.getStatusCode(), 200);
|
||||
Devfile devFile = objectMapper.readValue(response.getBody().asString(), Devfile.class);
|
||||
assertNotNull(devFile);
|
||||
}
|
||||
|
||||
private WorkspaceImpl createWorkspace(WorkspaceStatus status)
|
||||
throws IOException, JsonParseException {
|
||||
return WorkspaceImpl.builder()
|
||||
.setConfig(createConfig())
|
||||
.generateId()
|
||||
.setAccount(new AccountImpl("anyId", SUBJECT.getUserName(), "test"))
|
||||
.setStatus(status)
|
||||
.build();
|
||||
}
|
||||
|
||||
private WorkspaceConfig createConfig() throws IOException, JsonParseException {
|
||||
String jsonContent =
|
||||
Files.readFile(getClass().getClassLoader().getResourceAsStream("workspace_config.json"));
|
||||
return JsonHelper.fromJson(jsonContent, WorkspaceConfigImpl.class, null);
|
||||
}
|
||||
|
||||
@Filter
|
||||
public static class EnvironmentFilter implements RequestFilter {
|
||||
@Override
|
||||
public void doFilter(GenericContainerRequest request) {
|
||||
EnvironmentContext.getCurrent().setSubject(SUBJECT);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,50 @@
|
|||
#
|
||||
# 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
|
||||
#
|
||||
|
||||
---
|
||||
specVersion: 0.0.1
|
||||
name: petclinic-dev-environment
|
||||
projects:
|
||||
- name: petclinic
|
||||
source:
|
||||
type: git
|
||||
location: 'git@github.com:spring-projects/spring-petclinic.git'
|
||||
tools:
|
||||
- name: mvn-stack
|
||||
type: chePlugin
|
||||
id: eclipse/maven-jdk8:1.0.0
|
||||
- name: theia-ide
|
||||
type: cheEditor
|
||||
id: eclipse/theia:0.0.3
|
||||
- name: jdt.ls
|
||||
type: chePlugin
|
||||
id: eclipse/theia-jdtls:0.0.3
|
||||
commands:
|
||||
- name: build
|
||||
actions:
|
||||
- type: exec
|
||||
tool: mvn-stack
|
||||
command: mvn package
|
||||
workdir: /projects/spring-petclinic
|
||||
- name: run
|
||||
attributes:
|
||||
runType: sequential
|
||||
actions:
|
||||
- type: exec
|
||||
tool: mvn-stack
|
||||
command: mvn spring-boot:run
|
||||
workdir: /projects/spring-petclinic
|
||||
- name: other
|
||||
actions:
|
||||
- type: exec
|
||||
tool: jdt.ls
|
||||
command: run.sh
|
||||
|
|
@ -0,0 +1,30 @@
|
|||
#
|
||||
# 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
|
||||
#
|
||||
|
||||
---
|
||||
specVersion: 0.0.1
|
||||
name: petclinic-dev-environment
|
||||
projects:
|
||||
- name: petclinic
|
||||
source:
|
||||
type: git
|
||||
location: 'git@github.com:spring-projects/spring-petclinic.git'
|
||||
tools:
|
||||
- type: chePlugin
|
||||
id: eclipse/maven-jdk8:1.0.0
|
||||
commands:
|
||||
- name: build
|
||||
actions:
|
||||
- type: exec
|
||||
tool: mvn-stack
|
||||
command: mvn package
|
||||
workdir: /projects/spring-petclinic
|
||||
|
|
@ -0,0 +1,35 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!--
|
||||
|
||||
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
|
||||
|
||||
-->
|
||||
<configuration>
|
||||
|
||||
<appender name="stdout" class="ch.qos.logback.core.ConsoleAppender">
|
||||
<encoder>
|
||||
<pattern>%-41(%date[%.15thread]) %-45([%-5level] [%.30logger{30} %L]) - %msg%n</pattern>
|
||||
</encoder>
|
||||
</appender>
|
||||
|
||||
<appender name="file" class="ch.qos.logback.core.FileAppender">
|
||||
<File>target/log/codenvy-factory-commons.log</File>
|
||||
<encoder>
|
||||
<pattern>%-41(%date[%.15thread]) %-45([%-5level] [%.30logger{30} %L]) - %msg%n</pattern>
|
||||
</encoder>
|
||||
</appender>
|
||||
|
||||
<root level="INFO">
|
||||
<appender-ref ref="stdout"/>
|
||||
<appender-ref ref="file"/>
|
||||
</root>
|
||||
|
||||
</configuration>
|
||||
|
|
@ -0,0 +1,51 @@
|
|||
{
|
||||
"projects": [
|
||||
{
|
||||
"source": {
|
||||
"location": "git@github.com:spring-projects/spring-petclinic.git",
|
||||
"type": "git",
|
||||
"parameters": {}
|
||||
},
|
||||
"mixins": [],
|
||||
"name": "petclinic",
|
||||
"path": "/petclinic",
|
||||
"attributes": {}
|
||||
}
|
||||
],
|
||||
"commands": [
|
||||
{
|
||||
"commandLine": "mvn package",
|
||||
"name": "build:mvn-stack",
|
||||
"type": "exec",
|
||||
"attributes": {
|
||||
"pluginId": "eclipse/maven-jdk8:1.0.0",
|
||||
"workingDir": "/projects/spring-petclinic"
|
||||
}
|
||||
},
|
||||
{
|
||||
"commandLine": "mvn spring-boot:run",
|
||||
"name": "run:mvn-stack",
|
||||
"type": "exec",
|
||||
"attributes": {
|
||||
"pluginId": "eclipse/maven-jdk8:1.0.0",
|
||||
"runType": "sequential",
|
||||
"workingDir": "/projects/spring-petclinic"
|
||||
}
|
||||
},
|
||||
{
|
||||
"commandLine": "run.sh",
|
||||
"name": "other:jdt.ls",
|
||||
"type": "exec",
|
||||
"attributes": {
|
||||
"pluginId": "eclipse/theia-jdtls:0.0.3"
|
||||
}
|
||||
}
|
||||
],
|
||||
"environments": {},
|
||||
"name": "petclinic-dev-environment",
|
||||
"attributes": {
|
||||
"toolsAliases": "eclipse/maven-jdk8:1.0.0=mvn-stack,eclipse/theia:0.0.3=theia-ide,eclipse/theia-jdtls:0.0.3=jdt.ls",
|
||||
"editor": "eclipse/theia:0.0.3",
|
||||
"plugins": "eclipse/maven-jdk8:1.0.0,eclipse/theia-jdtls:0.0.3"
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,49 @@
|
|||
/*
|
||||
* 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 java.lang.String.format;
|
||||
|
||||
import org.eclipse.che.api.core.BadRequestException;
|
||||
|
||||
/** Helper class to validate workspace composite keys. */
|
||||
public class WorkspaceKeyValidator {
|
||||
|
||||
/**
|
||||
* Checks that key consists either from workspaceId or username:workspace_name string.
|
||||
*
|
||||
* @param key key string to validate
|
||||
* @throws BadRequestException if validation is failed
|
||||
*/
|
||||
public static void validateKey(String key) throws BadRequestException {
|
||||
String[] parts = key.split(":", -1); // -1 is to prevent skipping trailing part
|
||||
switch (parts.length) {
|
||||
case 1:
|
||||
{
|
||||
return; // consider it's id
|
||||
}
|
||||
case 2:
|
||||
{
|
||||
if (parts[1].isEmpty()) {
|
||||
throw new BadRequestException(
|
||||
"Wrong composite key format - workspace name required to be set.");
|
||||
}
|
||||
break;
|
||||
}
|
||||
default:
|
||||
{
|
||||
throw new BadRequestException(
|
||||
format("Wrong composite key %s. Format should be 'username:workspace_name'. ", key));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -17,6 +17,7 @@ import static java.util.Collections.emptyMap;
|
|||
import static java.util.stream.Collectors.toList;
|
||||
import static javax.ws.rs.core.MediaType.APPLICATION_JSON;
|
||||
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_PLUGIN_REGISTRY_URL_PROPERTY;
|
||||
|
||||
|
|
@ -733,33 +734,6 @@ public class WorkspaceService extends Service {
|
|||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Validate composite key.
|
||||
*
|
||||
*/
|
||||
private void validateKey(String key) throws BadRequestException {
|
||||
String[] parts = key.split(":", -1); // -1 is to prevent skipping trailing part
|
||||
switch (parts.length) {
|
||||
case 1:
|
||||
{
|
||||
return; // consider it's id
|
||||
}
|
||||
case 2:
|
||||
{
|
||||
if (parts[1].isEmpty()) {
|
||||
throw new BadRequestException(
|
||||
"Wrong composite key format - workspace name required to be set.");
|
||||
}
|
||||
break;
|
||||
}
|
||||
default:
|
||||
{
|
||||
throw new BadRequestException(
|
||||
format("Wrong composite key %s. Format should be 'username:workspace_name'. ", key));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void relativizeRecipeLinks(WorkspaceConfigDto config) {
|
||||
if (config != null) {
|
||||
Map<String, EnvironmentDto> environments = config.getEnvironments();
|
||||
|
|
|
|||
|
|
@ -28,6 +28,7 @@
|
|||
<module>che-core-api-installer</module>
|
||||
<module>che-core-api-auth-shared</module>
|
||||
<module>che-core-api-auth</module>
|
||||
<module>che-core-api-devfile</module>
|
||||
<module>che-core-api-project-templates-shared</module>
|
||||
<module>che-core-api-project-templates</module>
|
||||
<module>che-core-api-workspace-shared</module>
|
||||
|
|
|
|||
Loading…
Reference in New Issue