Gh18838 factory devfile2 (#18868)

Signed-off-by: Michal Vala <mvala@redhat.com>
7.26.x
Michal Vala 2021-02-03 10:23:13 +01:00 committed by GitHub
parent 07929cb591
commit ef05a7c9eb
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
32 changed files with 826 additions and 253 deletions

View File

@ -41,6 +41,7 @@ import org.eclipse.che.api.devfile.server.model.impl.UserDevfileImpl;
import org.eclipse.che.api.devfile.shared.dto.UserDevfileDto;
import org.eclipse.che.api.workspace.server.devfile.DevfileEntityProvider;
import org.eclipse.che.api.workspace.server.devfile.DevfileParser;
import org.eclipse.che.api.workspace.server.devfile.DevfileVersionDetector;
import org.eclipse.che.api.workspace.server.devfile.schema.DevfileSchemaProvider;
import org.eclipse.che.api.workspace.server.devfile.validator.DevfileIntegrityValidator;
import org.eclipse.che.api.workspace.server.devfile.validator.DevfileSchemaValidator;
@ -70,7 +71,7 @@ public class UserDevfilePermissionsFilterTest {
private DevfileEntityProvider devfileEntityProvider =
new DevfileEntityProvider(
new DevfileParser(
new DevfileSchemaValidator(new DevfileSchemaProvider()),
new DevfileSchemaValidator(new DevfileSchemaProvider(), new DevfileVersionDetector()),
new DevfileIntegrityValidator(Collections.emptyMap())));
@SuppressWarnings("unused")

View File

@ -61,6 +61,7 @@ import org.eclipse.che.api.devfile.server.spi.UserDevfileDao;
import org.eclipse.che.api.devfile.shared.dto.UserDevfileDto;
import org.eclipse.che.api.workspace.server.devfile.DevfileEntityProvider;
import org.eclipse.che.api.workspace.server.devfile.DevfileParser;
import org.eclipse.che.api.workspace.server.devfile.DevfileVersionDetector;
import org.eclipse.che.api.workspace.server.devfile.schema.DevfileSchemaProvider;
import org.eclipse.che.api.workspace.server.devfile.validator.DevfileIntegrityValidator;
import org.eclipse.che.api.workspace.server.devfile.validator.DevfileSchemaValidator;
@ -98,7 +99,7 @@ public class DevfileServiceTest {
private DevfileParser devfileParser =
new DevfileParser(
new DevfileSchemaValidator(new DevfileSchemaProvider()),
new DevfileSchemaValidator(new DevfileSchemaProvider(), new DevfileVersionDetector()),
new DevfileIntegrityValidator(Collections.emptyMap()));
DevfileEntityProvider devfileEntityProvider = new DevfileEntityProvider(devfileParser);
UserDevfileEntityProvider userDevfileEntityProvider =
@ -514,7 +515,7 @@ public class DevfileServiceTest {
newDto(DevfileDto.class)
.withApiVersion(null)
.withMetadata(newDto(MetadataDto.class).withName("name"))),
"Devfile schema validation failed. Error: The object must have a property whose name is \"apiVersion\"."
"Devfile schema validation failed. Error: Neither of `apiVersion` or `schemaVersion` found. This is not a valid devfile."
}
};
}

View File

@ -15,8 +15,6 @@ import static org.eclipse.che.api.factory.shared.Constants.CURRENT_VERSION;
import static org.eclipse.che.api.factory.shared.Constants.URL_PARAMETER_NAME;
import static org.eclipse.che.dto.server.DtoFactory.newDto;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import javax.inject.Inject;
import javax.inject.Singleton;
@ -27,6 +25,8 @@ import org.eclipse.che.api.factory.server.scm.GitCredentialManager;
import org.eclipse.che.api.factory.server.scm.PersonalAccessTokenManager;
import org.eclipse.che.api.factory.server.urlfactory.URLFactoryBuilder;
import org.eclipse.che.api.factory.shared.dto.FactoryDto;
import org.eclipse.che.api.factory.shared.dto.FactoryMetaDto;
import org.eclipse.che.api.factory.shared.dto.FactoryVisitor;
import org.eclipse.che.api.workspace.server.devfile.FileContentProvider;
import org.eclipse.che.api.workspace.server.devfile.URLFetcher;
import org.eclipse.che.api.workspace.shared.dto.devfile.ProjectDto;
@ -80,7 +80,7 @@ public class BitbucketServerAuthorizingFactoryParametersResolver
* @throws BadRequestException when data are invalid
*/
@Override
public FactoryDto createFactory(@NotNull final Map<String, String> factoryParameters)
public FactoryMetaDto createFactory(@NotNull final Map<String, String> factoryParameters)
throws BadRequestException {
// no need to check null value of url parameter as accept() method has performed the check
@ -92,41 +92,50 @@ public class BitbucketServerAuthorizingFactoryParametersResolver
bitbucketUrl, urlFetcher, gitCredentialManager, personalAccessTokenManager);
// create factory from the following location if location exists, else create default factory
FactoryDto factory =
urlFactoryBuilder
.createFactoryFromDevfile(
bitbucketUrl, fileContentProvider, extractOverrideParams(factoryParameters))
.orElseGet(() -> newDto(FactoryDto.class).withV(CURRENT_VERSION).withSource("repo"));
return urlFactoryBuilder
.createFactoryFromDevfile(
bitbucketUrl, fileContentProvider, extractOverrideParams(factoryParameters))
.orElseGet(() -> newDto(FactoryDto.class).withV(CURRENT_VERSION).withSource("repo"))
.acceptVisitor(new BitbucketFactoryVisitor(bitbucketUrl));
}
if (factory.getDevfile() == null) {
// initialize default devfile
factory.setDevfile(urlFactoryBuilder.buildDefaultDevfile(bitbucketUrl.getRepository()));
/**
* Visitor that puts the default devfile or updates devfile projects into the Bitbucket Factory,
* if needed.
*/
private class BitbucketFactoryVisitor implements FactoryVisitor {
private final BitbucketUrl bitbucketUrl;
private BitbucketFactoryVisitor(BitbucketUrl bitbucketUrl) {
this.bitbucketUrl = bitbucketUrl;
}
List<ProjectDto> projects = factory.getDevfile().getProjects();
// if no projects set, set the default one from Bitbucket url
if (projects.isEmpty()) {
factory
.getDevfile()
.setProjects(
Collections.singletonList(
newDto(ProjectDto.class)
.withSource(
newDto(SourceDto.class)
.withLocation(bitbucketUrl.repositoryLocation())
.withType("git")
.withBranch(bitbucketUrl.getBranch()))
.withName(bitbucketUrl.getRepository())));
} else {
// update existing project with same repository, set current branch if needed
projects.forEach(
@Override
public FactoryDto visit(FactoryDto factory) {
if (factory.getDevfile() == null) {
// initialize default devfile
factory.setDevfile(urlFactoryBuilder.buildDefaultDevfile(bitbucketUrl.getRepository()));
}
updateProjects(
factory.getDevfile(),
() ->
newDto(ProjectDto.class)
.withSource(
newDto(SourceDto.class)
.withLocation(bitbucketUrl.repositoryLocation())
.withType("git")
.withBranch(bitbucketUrl.getBranch()))
.withName(bitbucketUrl.getRepository()),
project -> {
final String location = project.getSource().getLocation();
if (location.equals(bitbucketUrl.repositoryLocation())) {
project.getSource().setBranch(bitbucketUrl.getBranch());
}
});
return factory;
}
return factory;
}
}

View File

@ -108,7 +108,8 @@ public class BitbucketServerAuthorizingFactoryParametersResolverTest {
.thenReturn(Optional.empty());
Map<String, String> params = ImmutableMap.of(URL_PARAMETER_NAME, bitbucketUrl);
// when
FactoryDto factory = bitbucketServerFactoryParametersResolver.createFactory(params);
FactoryDto factory =
(FactoryDto) bitbucketServerFactoryParametersResolver.createFactory(params);
// then
verify(urlFactoryBuilder).buildDefaultDevfile(eq("repo"));
assertEquals(factory, computedFactory);
@ -129,7 +130,8 @@ public class BitbucketServerAuthorizingFactoryParametersResolverTest {
Map<String, String> params = ImmutableMap.of(URL_PARAMETER_NAME, bitbucketUrl);
// when
FactoryDto factory = bitbucketServerFactoryParametersResolver.createFactory(params);
FactoryDto factory =
(FactoryDto) bitbucketServerFactoryParametersResolver.createFactory(params);
// then
assertNotNull(factory.getDevfile());
SourceDto source = factory.getDevfile().getProjects().get(0).getSource();

View File

@ -15,18 +15,17 @@ import static org.eclipse.che.api.factory.shared.Constants.CURRENT_VERSION;
import static org.eclipse.che.api.factory.shared.Constants.URL_PARAMETER_NAME;
import static org.eclipse.che.dto.server.DtoFactory.newDto;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import javax.inject.Inject;
import javax.inject.Singleton;
import javax.validation.constraints.NotNull;
import org.eclipse.che.api.core.BadRequestException;
import org.eclipse.che.api.core.ServerException;
import org.eclipse.che.api.factory.server.DefaultFactoryParameterResolver;
import org.eclipse.che.api.factory.server.urlfactory.ProjectConfigDtoMerger;
import org.eclipse.che.api.factory.server.urlfactory.URLFactoryBuilder;
import org.eclipse.che.api.factory.shared.dto.FactoryDto;
import org.eclipse.che.api.factory.shared.dto.FactoryMetaDto;
import org.eclipse.che.api.factory.shared.dto.FactoryVisitor;
import org.eclipse.che.api.workspace.server.devfile.URLFetcher;
import org.eclipse.che.api.workspace.shared.dto.ProjectConfigDto;
import org.eclipse.che.api.workspace.shared.dto.devfile.ProjectDto;
@ -82,49 +81,57 @@ public class GithubFactoryParametersResolver extends DefaultFactoryParameterReso
* @throws BadRequestException when data are invalid
*/
@Override
public FactoryDto createFactory(@NotNull final Map<String, String> factoryParameters)
throws BadRequestException, ServerException {
public FactoryMetaDto createFactory(@NotNull final Map<String, String> factoryParameters)
throws BadRequestException {
// no need to check null value of url parameter as accept() method has performed the check
final GithubUrl githubUrl = githubUrlParser.parse(factoryParameters.get(URL_PARAMETER_NAME));
// create factory from the following location if location exists, else create default factory
FactoryDto factory =
urlFactoryBuilder
.createFactoryFromDevfile(
githubUrl,
new GithubFileContentProvider(githubUrl, urlFetcher),
extractOverrideParams(factoryParameters))
.orElseGet(() -> newDto(FactoryDto.class).withV(CURRENT_VERSION).withSource("repo"));
return urlFactoryBuilder
.createFactoryFromDevfile(
githubUrl,
new GithubFileContentProvider(githubUrl, urlFetcher),
extractOverrideParams(factoryParameters))
.orElseGet(() -> newDto(FactoryDto.class).withV(CURRENT_VERSION).withSource("repo"))
.acceptVisitor(new GithubFactoryVisitor(githubUrl));
}
if (factory.getWorkspace() != null) {
return projectConfigDtoMerger.merge(
factory,
() -> {
// Compute project configuration
return newDto(ProjectConfigDto.class)
.withSource(githubSourceStorageBuilder.buildWorkspaceConfigSource(githubUrl))
.withName(githubUrl.getRepository())
.withPath("/".concat(githubUrl.getRepository()));
});
} else if (factory.getDevfile() == null) {
// initialize default devfile
factory.setDevfile(urlFactoryBuilder.buildDefaultDevfile(githubUrl.getRepository()));
/**
* Visitor that puts the default devfile or updates devfile projects into the Github Factory, if
* needed.
*/
private class GithubFactoryVisitor implements FactoryVisitor {
private final GithubUrl githubUrl;
private GithubFactoryVisitor(GithubUrl githubUrl) {
this.githubUrl = githubUrl;
}
List<ProjectDto> projects = factory.getDevfile().getProjects();
// if no projects set, set the default one from GitHub url
if (projects.isEmpty()) {
factory
.getDevfile()
.setProjects(
Collections.singletonList(
newDto(ProjectDto.class)
.withSource(githubSourceStorageBuilder.buildDevfileSource(githubUrl))
.withName(githubUrl.getRepository())));
} else {
// update existing project with same repository, set current branch if needed
projects.forEach(
@Override
public FactoryDto visit(FactoryDto factory) {
if (factory.getWorkspace() != null) {
return projectConfigDtoMerger.merge(
factory,
() -> {
// Compute project configuration
return newDto(ProjectConfigDto.class)
.withSource(githubSourceStorageBuilder.buildWorkspaceConfigSource(githubUrl))
.withName(githubUrl.getRepository())
.withPath("/".concat(githubUrl.getRepository()));
});
} else if (factory.getDevfile() == null) {
// initialize default devfile
factory.setDevfile(urlFactoryBuilder.buildDefaultDevfile(githubUrl.getRepository()));
}
updateProjects(
factory.getDevfile(),
() ->
newDto(ProjectDto.class)
.withSource(githubSourceStorageBuilder.buildDevfileSource(githubUrl))
.withName(githubUrl.getRepository()),
project -> {
final String location = project.getSource().getLocation();
if (location.equals(githubUrl.repositoryLocation())
@ -132,7 +139,8 @@ public class GithubFactoryParametersResolver extends DefaultFactoryParameterReso
project.getSource().setBranch(githubUrl.getBranch());
}
});
return factory;
}
return factory;
}
}

View File

@ -141,7 +141,7 @@ public class GithubFactoryParametersResolverTest {
.thenReturn(Optional.empty());
Map<String, String> params = ImmutableMap.of(URL_PARAMETER_NAME, githubUrl);
// when
FactoryDto factory = githubFactoryParametersResolver.createFactory(params);
FactoryDto factory = (FactoryDto) githubFactoryParametersResolver.createFactory(params);
// then
verify(urlFactoryBuilder).buildDefaultDevfile(eq("che"));
assertEquals(factory, computedFactory);
@ -165,7 +165,7 @@ public class GithubFactoryParametersResolverTest {
Map<String, String> params = ImmutableMap.of(URL_PARAMETER_NAME, githubUrl);
// when
FactoryDto factory = githubFactoryParametersResolver.createFactory(params);
FactoryDto factory = (FactoryDto) githubFactoryParametersResolver.createFactory(params);
// then
assertNotNull(factory.getDevfile());
assertNull(factory.getWorkspace());
@ -191,7 +191,7 @@ public class GithubFactoryParametersResolverTest {
Map<String, String> params = ImmutableMap.of(URL_PARAMETER_NAME, githubUrl);
// when
FactoryDto factory = githubFactoryParametersResolver.createFactory(params);
FactoryDto factory = (FactoryDto) githubFactoryParametersResolver.createFactory(params);
// then
assertNotNull(factory.getDevfile());
SourceDto source = factory.getDevfile().getProjects().get(0).getSource();
@ -218,7 +218,7 @@ public class GithubFactoryParametersResolverTest {
Map<String, String> params = ImmutableMap.of(URL_PARAMETER_NAME, githubUrl);
// when
FactoryDto factory = githubFactoryParametersResolver.createFactory(params);
FactoryDto factory = (FactoryDto) githubFactoryParametersResolver.createFactory(params);
// then
assertNotNull(factory.getDevfile());
SourceDto source = factory.getDevfile().getProjects().get(0).getSource();

View File

@ -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
*/
package org.eclipse.che.api.factory.shared.dto;
import static org.eclipse.che.api.core.factory.FactoryParameter.Obligation.MANDATORY;
import java.util.List;
import java.util.Map;
import org.eclipse.che.api.core.factory.FactoryParameter;
import org.eclipse.che.api.core.rest.shared.dto.Hyperlinks;
import org.eclipse.che.api.core.rest.shared.dto.Link;
import org.eclipse.che.dto.shared.DTO;
/**
* Factory DTO for Devfile v2. As che-server don't know the structure of Devfile v2, we're using
* just generic {@code Map<String, Object>} here.
*/
@DTO
public interface FactoryDevfileV2Dto extends FactoryMetaDto, Hyperlinks {
@Override
default FactoryMetaDto acceptVisitor(FactoryVisitor visitor) {
return visitor.visit(this);
}
@Override
FactoryDevfileV2Dto withV(String v);
@FactoryParameter(obligation = MANDATORY)
Map<String, Object> getDevfile();
void setDevfile(Map<String, Object> devfile);
FactoryDevfileV2Dto withDevfile(Map<String, Object> devfile);
@Override
FactoryDevfileV2Dto withSource(String source);
@Override
FactoryDevfileV2Dto withLinks(List<Link> links);
}

View File

@ -11,7 +11,6 @@
*/
package org.eclipse.che.api.factory.shared.dto;
import static org.eclipse.che.api.core.factory.FactoryParameter.Obligation.MANDATORY;
import static org.eclipse.che.api.core.factory.FactoryParameter.Obligation.OPTIONAL;
import java.util.List;
@ -24,28 +23,50 @@ import org.eclipse.che.api.workspace.shared.dto.devfile.DevfileDto;
import org.eclipse.che.dto.shared.DTO;
/**
* Factory of version 4.0
* Factory of version 4.0.
*
* <p>This 'implementation' of {@link FactoryMetaDto} is used for Devfile v1.
*
* @author Max Shaposhnik
*/
@DTO
public interface FactoryDto extends Factory, Hyperlinks {
public interface FactoryDto extends FactoryMetaDto, Factory, Hyperlinks {
@Override
@FactoryParameter(obligation = MANDATORY)
String getV();
void setV(String v);
FactoryDto withV(String v);
default FactoryMetaDto acceptVisitor(FactoryVisitor visitor) {
return visitor.visit(this);
}
@FactoryParameter(obligation = OPTIONAL)
DevfileDto getDevfile();
void setDevfile(DevfileDto workspace);
void setDevfile(DevfileDto devfileDto);
FactoryDto withDevfile(DevfileDto devfileDto);
FactoryDto withV(String v);
@Override
FactoryDto withName(String name);
@Override
FactoryDto withPolicies(PoliciesDto policies);
@Override
FactoryDto withIde(IdeDto ide);
@Override
FactoryDto withId(String id);
@Override
FactoryDto withSource(String source);
@Override
FactoryDto withCreator(AuthorDto creator);
@Override
FactoryDto withLinks(List<Link> links);
/** because factory DTO may have devfile, in that case, workspace may be optional */
@Override
@FactoryParameter(obligation = OPTIONAL)
@ -54,59 +75,4 @@ public interface FactoryDto extends Factory, Hyperlinks {
void setWorkspace(WorkspaceConfigDto workspace);
FactoryDto withWorkspace(WorkspaceConfigDto workspace);
@Override
@FactoryParameter(obligation = OPTIONAL, trackedOnly = true)
PoliciesDto getPolicies();
void setPolicies(PoliciesDto policies);
FactoryDto withPolicies(PoliciesDto policies);
@Override
@FactoryParameter(obligation = OPTIONAL)
AuthorDto getCreator();
void setCreator(AuthorDto creator);
FactoryDto withCreator(AuthorDto creator);
@Override
@FactoryParameter(obligation = OPTIONAL)
IdeDto getIde();
void setIde(IdeDto ide);
FactoryDto withIde(IdeDto ide);
@Override
@FactoryParameter(obligation = OPTIONAL, setByServer = true)
String getId();
void setId(String id);
FactoryDto withId(String id);
/**
* Indicates filename in repository from which the factory was created (for example, .devfile) or
* just contains 'repo' value if factory was created from bare GitHub repository. For custom raw
* URL's (pastebin, gist etc) value is {@code null}
*/
@FactoryParameter(obligation = OPTIONAL, setByServer = true)
String getSource();
void setSource(String source);
FactoryDto withSource(String source);
@Override
@FactoryParameter(obligation = OPTIONAL)
String getName();
void setName(String name);
FactoryDto withName(String name);
@Override
FactoryDto withLinks(List<Link> links);
}

View File

@ -0,0 +1,89 @@
/*
* Copyright (c) 2012-2018 Red Hat, Inc.
* This program and the accompanying materials are made
* available under the terms of the Eclipse Public License 2.0
* which is available at https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*
* Contributors:
* Red Hat, Inc. - initial API and implementation
*/
package org.eclipse.che.api.factory.shared.dto;
import static org.eclipse.che.api.core.factory.FactoryParameter.Obligation.MANDATORY;
import static org.eclipse.che.api.core.factory.FactoryParameter.Obligation.OPTIONAL;
import java.util.List;
import org.eclipse.che.api.core.factory.FactoryParameter;
import org.eclipse.che.api.core.rest.shared.dto.Hyperlinks;
import org.eclipse.che.api.core.rest.shared.dto.Link;
/** Ancestor for Factory DTOs that does not know about devfile version it will hold. */
public interface FactoryMetaDto extends Hyperlinks {
/**
* Gives an option to update the factory based on devfile version. See {@link FactoryVisitor}.
*
* @param visitor visitor that should update the factory
* @return updated factory
*/
FactoryMetaDto acceptVisitor(FactoryVisitor visitor);
@FactoryParameter(obligation = MANDATORY)
String getV();
void setV(String v);
FactoryMetaDto withV(String v);
/**
* Indicates filename in repository from which the factory was created (for example, .devfile) or
* just contains 'repo' value if factory was created from bare GitHub repository. For custom raw
* URL's (pastebin, gist etc) value is {@code null}
*/
@FactoryParameter(obligation = OPTIONAL, setByServer = true)
String getSource();
void setSource(String source);
FactoryMetaDto withSource(String source);
@FactoryParameter(obligation = OPTIONAL)
String getName();
void setName(String name);
FactoryMetaDto withName(String name);
@FactoryParameter(obligation = OPTIONAL, setByServer = true)
String getId();
void setId(String id);
FactoryMetaDto withId(String id);
@FactoryParameter(obligation = OPTIONAL)
AuthorDto getCreator();
void setCreator(AuthorDto creator);
FactoryMetaDto withCreator(AuthorDto creator);
@Override
FactoryMetaDto withLinks(List<Link> links);
@FactoryParameter(obligation = OPTIONAL, trackedOnly = true)
PoliciesDto getPolicies();
void setPolicies(PoliciesDto policies);
FactoryMetaDto withPolicies(PoliciesDto policies);
@FactoryParameter(obligation = OPTIONAL)
IdeDto getIde();
void setIde(IdeDto ide);
FactoryMetaDto withIde(IdeDto ide);
}

View File

@ -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.factory.shared.dto;
/**
* Visitor that allows us to do necessary updates to the factory, like include default devfile, set
* project url, set branch of the project etc.
*/
public interface FactoryVisitor {
/**
* Visit factory with devfile v1.
*
* <p>Implementation should update given factory with needed changes and give it back.
*
* @param factoryDto factory to visit
* @return updated factory
*/
FactoryDto visit(FactoryDto factoryDto);
/**
* Visit factory with devfile v2.
*
* <p>Implementation should update given factory and give it back.
*
* <p>Che-server does not know devfile v2 structure so most likely we don't want to do anything
* with it. The default implementation is here for that reason.
*
* @param factoryDto factory to visit
* @return update factory
*/
default FactoryDevfileV2Dto visit(FactoryDevfileV2Dto factoryDto) {
// most likely nothing to do with Devfile v2 factory as we don't know or touch the structure
return factoryDto;
}
}

View File

@ -26,6 +26,10 @@
<findbugs.failonerror>false</findbugs.failonerror>
</properties>
<dependencies>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
</dependency>
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>

View File

@ -19,8 +19,12 @@ import java.net.MalformedURLException;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.function.Consumer;
import java.util.function.Supplier;
import javax.inject.Inject;
import javax.inject.Singleton;
import javax.validation.constraints.NotNull;
@ -28,9 +32,11 @@ import org.eclipse.che.api.core.BadRequestException;
import org.eclipse.che.api.core.ServerException;
import org.eclipse.che.api.factory.server.urlfactory.DefaultFactoryUrl;
import org.eclipse.che.api.factory.server.urlfactory.URLFactoryBuilder;
import org.eclipse.che.api.factory.shared.dto.FactoryDto;
import org.eclipse.che.api.factory.shared.dto.FactoryMetaDto;
import org.eclipse.che.api.workspace.server.devfile.URLFetcher;
import org.eclipse.che.api.workspace.server.devfile.URLFileContentProvider;
import org.eclipse.che.api.workspace.shared.dto.devfile.DevfileDto;
import org.eclipse.che.api.workspace.shared.dto.devfile.ProjectDto;
/**
* Default {@link FactoryParametersResolver} implementation. Tries to resolve factory based on
@ -65,7 +71,7 @@ public class DefaultFactoryParameterResolver implements FactoryParametersResolve
* @param factoryParameters map containing factory data parameters provided through URL
*/
@Override
public FactoryDto createFactory(@NotNull final Map<String, String> factoryParameters)
public FactoryMetaDto createFactory(@NotNull final Map<String, String> factoryParameters)
throws BadRequestException, ServerException {
// This should never be null, because our contract in #accept prohibits that
String devfileLocation = factoryParameters.get(URL_PARAMETER_NAME);
@ -100,4 +106,25 @@ public class DefaultFactoryParameterResolver implements FactoryParametersResolve
.filter(e -> e.getKey().startsWith(OVERRIDE_PREFIX))
.collect(toMap(e -> e.getKey().substring(OVERRIDE_PREFIX.length()), Entry::getValue));
}
/**
* If devfile has no projects, put there one provided by given `projectSupplier`. Otherwise update
* all projects with given `projectModifier`.
*
* @param devfile of the projects to update
* @param projectSupplier provides default project
* @param projectModifier updates existing projects
*/
protected void updateProjects(
DevfileDto devfile,
Supplier<ProjectDto> projectSupplier,
Consumer<ProjectDto> projectModifier) {
List<ProjectDto> projects = devfile.getProjects();
if (projects.isEmpty()) {
devfile.setProjects(Collections.singletonList(projectSupplier.get()));
} else {
// update existing project with same repository, set current branch if needed
projects.forEach(projectModifier);
}
}
}

View File

@ -12,7 +12,7 @@
package org.eclipse.che.api.factory.server;
import org.eclipse.che.api.core.BadRequestException;
import org.eclipse.che.api.factory.shared.dto.FactoryDto;
import org.eclipse.che.api.factory.shared.dto.FactoryMetaDto;
/** Interface for validations of factory urls on accept stage. */
public interface FactoryAcceptValidator {
@ -24,5 +24,5 @@ public interface FactoryAcceptValidator {
* @param factory factory object to validate
* @throws BadRequestException in case if factory is not valid
*/
void validateOnAccept(FactoryDto factory) throws BadRequestException;
void validateOnAccept(FactoryMetaDto factory) throws BadRequestException;
}

View File

@ -25,7 +25,7 @@ import javax.ws.rs.HttpMethod;
import javax.ws.rs.core.UriBuilder;
import org.eclipse.che.api.core.rest.ServiceContext;
import org.eclipse.che.api.core.rest.shared.dto.Link;
import org.eclipse.che.api.factory.shared.dto.FactoryDto;
import org.eclipse.che.api.factory.shared.dto.FactoryMetaDto;
/**
* Helper class for creation links.
@ -43,7 +43,7 @@ public class FactoryLinksHelper {
* @return list of factory links
*/
public static List<Link> createLinks(
FactoryDto factory, ServiceContext serviceContext, String userName) {
FactoryMetaDto factory, ServiceContext serviceContext, String userName) {
final List<Link> links = new LinkedList<>();
final UriBuilder uriBuilder = serviceContext.getServiceUriBuilder();
final String factoryId = factory.getId();

View File

@ -15,7 +15,7 @@ import java.util.Map;
import javax.validation.constraints.NotNull;
import org.eclipse.che.api.core.BadRequestException;
import org.eclipse.che.api.core.ServerException;
import org.eclipse.che.api.factory.shared.dto.FactoryDto;
import org.eclipse.che.api.factory.shared.dto.FactoryMetaDto;
/**
* Defines a resolver that will produce factories for some parameters
@ -39,6 +39,6 @@ public interface FactoryParametersResolver {
* @param factoryParameters map containing factory data parameters provided through URL
* @throws BadRequestException when data are invalid
*/
FactoryDto createFactory(@NotNull Map<String, String> factoryParameters)
FactoryMetaDto createFactory(@NotNull Map<String, String> factoryParameters)
throws BadRequestException, ServerException;
}

View File

@ -32,7 +32,7 @@ import org.eclipse.che.api.core.ApiException;
import org.eclipse.che.api.core.BadRequestException;
import org.eclipse.che.api.core.ServerException;
import org.eclipse.che.api.core.rest.Service;
import org.eclipse.che.api.factory.shared.dto.FactoryDto;
import org.eclipse.che.api.factory.shared.dto.FactoryMetaDto;
import org.eclipse.che.api.user.server.UserManager;
/**
@ -78,7 +78,7 @@ public class FactoryService extends Service {
@ApiResponse(code = 400, message = "Missed required parameters, failed to validate factory"),
@ApiResponse(code = 500, message = "Internal server error")
})
public FactoryDto resolveFactory(
public FactoryMetaDto resolveFactory(
@ApiParam(value = "Parameters provided to create factories") Map<String, String> parameters,
@ApiParam(
value = "Whether or not to validate values like it is done when accepting a Factory",
@ -93,7 +93,7 @@ public class FactoryService extends Service {
requiredNotNull(parameters, "Factory build parameters");
// search matching resolver and create factory from matching resolver
FactoryDto resolvedFactory =
FactoryMetaDto resolvedFactory =
factoryParametersResolverHolder
.getFactoryParametersResolver(parameters)
.createFactory(parameters);
@ -103,11 +103,14 @@ public class FactoryService extends Service {
if (validate) {
acceptValidator.validateOnAccept(resolvedFactory);
}
return injectLinks(resolvedFactory);
resolvedFactory = injectLinks(resolvedFactory);
return resolvedFactory;
}
/** Injects factory links. If factory is named then accept named link will be injected. */
private FactoryDto injectLinks(FactoryDto factory) {
private FactoryMetaDto injectLinks(FactoryMetaDto factory) {
String username = null;
if (factory.getCreator() != null && factory.getCreator().getUserId() != null) {
try {

View File

@ -14,7 +14,7 @@ package org.eclipse.che.api.factory.server.impl;
import javax.inject.Singleton;
import org.eclipse.che.api.core.BadRequestException;
import org.eclipse.che.api.factory.server.FactoryAcceptValidator;
import org.eclipse.che.api.factory.shared.dto.FactoryDto;
import org.eclipse.che.api.factory.shared.dto.FactoryMetaDto;
/** Factory accept stage validator. */
@Singleton
@ -22,7 +22,7 @@ public class FactoryAcceptValidatorImpl extends FactoryBaseValidator
implements FactoryAcceptValidator {
@Override
public void validateOnAccept(FactoryDto factory) throws BadRequestException {
public void validateOnAccept(FactoryMetaDto factory) throws BadRequestException {
validateCurrentTimeBetweenSinceUntil(factory);
validateProjectActions(factory);
}

View File

@ -24,6 +24,7 @@ import java.util.regex.Pattern;
import org.eclipse.che.api.core.BadRequestException;
import org.eclipse.che.api.factory.server.FactoryConstants;
import org.eclipse.che.api.factory.shared.dto.FactoryDto;
import org.eclipse.che.api.factory.shared.dto.FactoryMetaDto;
import org.eclipse.che.api.factory.shared.dto.IdeActionDto;
import org.eclipse.che.api.factory.shared.dto.IdeDto;
import org.eclipse.che.api.factory.shared.dto.OnAppLoadedDto;
@ -94,7 +95,7 @@ public abstract class FactoryBaseValidator {
* @throws BadRequestException if since date greater than current date<br>
* @throws BadRequestException if until date less than current date<br>
*/
protected void validateCurrentTimeBetweenSinceUntil(FactoryDto factory)
protected void validateCurrentTimeBetweenSinceUntil(FactoryMetaDto factory)
throws BadRequestException {
final PoliciesDto policies = factory.getPolicies();
if (policies == null) {
@ -149,7 +150,7 @@ public abstract class FactoryBaseValidator {
* @param factory factory to validate
* @throws BadRequestException when factory actions is invalid
*/
protected void validateProjectActions(FactoryDto factory) throws BadRequestException {
protected void validateProjectActions(FactoryMetaDto factory) throws BadRequestException {
final IdeDto ide = factory.getIde();
if (ide == null) {
return;

View File

@ -19,6 +19,7 @@ import static org.eclipse.che.api.workspace.shared.Constants.WORKSPACE_TOOLING_E
import static org.eclipse.che.api.workspace.shared.Constants.WORKSPACE_TOOLING_PLUGINS_ATTRIBUTE;
import static org.eclipse.che.dto.server.DtoFactory.newDto;
import com.fasterxml.jackson.databind.JsonNode;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
@ -28,9 +29,12 @@ import javax.inject.Named;
import javax.inject.Singleton;
import org.eclipse.che.api.core.BadRequestException;
import org.eclipse.che.api.factory.server.urlfactory.RemoteFactoryUrl.DevfileLocation;
import org.eclipse.che.api.factory.shared.dto.FactoryDevfileV2Dto;
import org.eclipse.che.api.factory.shared.dto.FactoryDto;
import org.eclipse.che.api.factory.shared.dto.FactoryMetaDto;
import org.eclipse.che.api.workspace.server.DtoConverter;
import org.eclipse.che.api.workspace.server.devfile.DevfileParser;
import org.eclipse.che.api.workspace.server.devfile.DevfileVersionDetector;
import org.eclipse.che.api.workspace.server.devfile.FileContentProvider;
import org.eclipse.che.api.workspace.server.devfile.exception.DevfileException;
import org.eclipse.che.api.workspace.server.devfile.exception.OverrideParameterException;
@ -57,15 +61,18 @@ public class URLFactoryBuilder {
private final String defaultChePlugins;
private final DevfileParser devfileParser;
private final DevfileVersionDetector devfileVersionDetector;
@Inject
public URLFactoryBuilder(
@Named("che.factory.default_editor") String defaultCheEditor,
@Named("che.factory.default_plugins") String defaultChePlugins,
DevfileParser devfileParser) {
DevfileParser devfileParser,
DevfileVersionDetector devfileVersionDetector) {
this.defaultCheEditor = defaultCheEditor;
this.defaultChePlugins = defaultChePlugins;
this.devfileParser = devfileParser;
this.devfileVersionDetector = devfileVersionDetector;
}
/**
@ -83,7 +90,7 @@ public class URLFactoryBuilder {
* @param overrideProperties map of overridden properties to apply in devfile
* @return a factory or null if devfile is not found
*/
public Optional<FactoryDto> createFactoryFromDevfile(
public Optional<FactoryMetaDto> createFactoryFromDevfile(
RemoteFactoryUrl remoteFactoryUrl,
FileContentProvider fileContentProvider,
Map<String, String> overrideProperties)
@ -109,17 +116,11 @@ public class URLFactoryBuilder {
if (isNullOrEmpty(devfileYamlContent)) {
return Optional.empty();
}
try {
DevfileImpl devfile = devfileParser.parseYaml(devfileYamlContent, overrideProperties);
devfileParser.resolveReference(devfile, fileContentProvider);
devfile = ensureToUseGenerateName(devfile);
FactoryDto factoryDto =
newDto(FactoryDto.class)
.withV(CURRENT_VERSION)
.withDevfile(DtoConverter.asDto(devfile))
.withSource(location.filename().isPresent() ? location.filename().get() : null);
return Optional.of(factoryDto);
try {
JsonNode parsedDevfile = devfileParser.parseYamlRaw(devfileYamlContent);
return Optional.of(
createFactory(parsedDevfile, overrideProperties, fileContentProvider, location));
} catch (DevfileException | OverrideParameterException e) {
throw new BadRequestException(
"Error occurred during creation a workspace from devfile located at `"
@ -131,6 +132,41 @@ public class URLFactoryBuilder {
return Optional.empty();
}
/**
* Converts given devfile json into factory based on the devfile version.
*
* @param overrideProperties map of overridden properties to apply in devfile
* @param fileContentProvider service-specific devfile related file content provider
* @param location devfile's location
* @return new factory created from the given devfile
* @throws OverrideParameterException when any issue when overriding parameters occur
* @throws DevfileException when devfile is not valid or we can't work with it
*/
private FactoryMetaDto createFactory(
JsonNode devfileJson,
Map<String, String> overrideProperties,
FileContentProvider fileContentProvider,
DevfileLocation location)
throws OverrideParameterException, DevfileException {
if (devfileVersionDetector.devfileMajorVersion(devfileJson) == 1) {
DevfileImpl devfile = devfileParser.parseJsonNode(devfileJson, overrideProperties);
devfileParser.resolveReference(devfile, fileContentProvider);
devfile = ensureToUseGenerateName(devfile);
return newDto(FactoryDto.class)
.withV(CURRENT_VERSION)
.withDevfile(DtoConverter.asDto(devfile))
.withSource(location.filename().isPresent() ? location.filename().get() : null);
} else {
return newDto(FactoryDevfileV2Dto.class)
.withV(CURRENT_VERSION)
.withDevfile(devfileParser.convertYamlToMap(devfileJson))
.withSource(location.filename().isPresent() ? location.filename().get() : null);
}
}
/**
* Creates devfile with only `generateName` and no `name`. We take `generateName` with precedence.
* See doc of {@link URLFactoryBuilder#createFactoryFromDevfile(RemoteFactoryUrl,

View File

@ -32,6 +32,7 @@ import org.eclipse.che.api.core.BadRequestException;
import org.eclipse.che.api.factory.server.urlfactory.RemoteFactoryUrl;
import org.eclipse.che.api.factory.server.urlfactory.URLFactoryBuilder;
import org.eclipse.che.api.workspace.server.devfile.DevfileParser;
import org.eclipse.che.api.workspace.server.devfile.DevfileVersionDetector;
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.schema.DevfileSchemaProvider;
@ -66,7 +67,8 @@ public class DefaultFactoryParameterResolverTest {
// given
// we need to set up an "almost real" devfile converter which is a little bit involved
DevfileSchemaValidator validator = new DevfileSchemaValidator(new DevfileSchemaProvider());
DevfileSchemaValidator validator =
new DevfileSchemaValidator(new DevfileSchemaProvider(), new DevfileVersionDetector());
Map<String, ComponentIntegrityValidator> validators = new HashMap<>();
validators.put(EDITOR_COMPONENT_TYPE, new NoopComponentIntegrityValidator());
@ -78,7 +80,8 @@ public class DefaultFactoryParameterResolverTest {
DevfileParser devfileParser = new DevfileParser(validator, integrityValidator);
URLFactoryBuilder factoryBuilder = new URLFactoryBuilder("editor", "plugin", devfileParser);
URLFactoryBuilder factoryBuilder =
new URLFactoryBuilder("editor", "plugin", devfileParser, new DevfileVersionDetector());
DefaultFactoryParameterResolver res =
new DefaultFactoryParameterResolver(factoryBuilder, urlFetcher);

View File

@ -18,6 +18,7 @@ import static org.eclipse.che.api.workspace.server.devfile.Constants.KUBERNETES_
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 static org.eclipse.che.dto.server.DtoFactory.newDto;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyMap;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.Mockito.mock;
@ -25,16 +26,23 @@ import static org.mockito.Mockito.when;
import static org.testng.Assert.assertEquals;
import static org.testng.Assert.assertNotNull;
import static org.testng.Assert.assertNull;
import static org.testng.Assert.assertTrue;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.node.JsonNodeFactory;
import com.fasterxml.jackson.databind.node.ObjectNode;
import java.io.IOException;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.Optional;
import org.eclipse.che.api.core.BadRequestException;
import org.eclipse.che.api.core.ServerException;
import org.eclipse.che.api.factory.server.urlfactory.RemoteFactoryUrl.DevfileLocation;
import org.eclipse.che.api.factory.shared.dto.FactoryDevfileV2Dto;
import org.eclipse.che.api.factory.shared.dto.FactoryDto;
import org.eclipse.che.api.factory.shared.dto.FactoryMetaDto;
import org.eclipse.che.api.workspace.server.devfile.DevfileParser;
import org.eclipse.che.api.workspace.server.devfile.DevfileVersionDetector;
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.exception.DevfileException;
@ -68,12 +76,15 @@ public class URLFactoryBuilderTest {
@Mock private DevfileParser devfileParser;
@Mock private DevfileVersionDetector devfileVersionDetector;
/** Tested instance. */
private URLFactoryBuilder urlFactoryBuilder;
@BeforeClass
public void setUp() {
this.urlFactoryBuilder = new URLFactoryBuilder(defaultEditor, defaultPlugin, devfileParser);
this.urlFactoryBuilder =
new URLFactoryBuilder(defaultEditor, defaultPlugin, devfileParser, devfileVersionDetector);
}
@Test
@ -103,9 +114,12 @@ public class URLFactoryBuilderTest {
workspaceConfigImpl.setDefaultEnv("name");
when(urlFetcher.fetchSafely(anyString())).thenReturn("random_content");
when(devfileParser.parseYaml(anyString(), anyMap())).thenReturn(devfile);
when(devfileParser.parseYamlRaw(anyString()))
.thenReturn(new ObjectNode(JsonNodeFactory.instance));
when(devfileParser.parseJsonNode(any(JsonNode.class), anyMap())).thenReturn(devfile);
when(devfileVersionDetector.devfileMajorVersion(any(JsonNode.class))).thenReturn(1);
FactoryDto factory =
FactoryMetaDto factory =
urlFactoryBuilder
.createFactoryFromDevfile(
new DefaultFactoryUrl().withDevfileFileLocation(myLocation),
@ -115,6 +129,67 @@ public class URLFactoryBuilderTest {
assertNotNull(factory);
assertNull(factory.getSource());
assertTrue(factory instanceof FactoryDto);
}
@Test
public void testDevfileV2() throws BadRequestException, DevfileException {
String myLocation = "http://foo-location/";
Map<String, Object> devfileAsMap = Map.of("hello", "there", "how", "are", "you", "?");
JsonNode devfile = new ObjectNode(JsonNodeFactory.instance);
when(devfileParser.parseYamlRaw(anyString())).thenReturn(devfile);
when(devfileParser.convertYamlToMap(devfile)).thenReturn(devfileAsMap);
when(devfileVersionDetector.devfileMajorVersion(devfile)).thenReturn(2);
FactoryMetaDto factory =
urlFactoryBuilder
.createFactoryFromDevfile(
new DefaultFactoryUrl().withDevfileFileLocation(myLocation),
s -> myLocation + ".list",
emptyMap())
.get();
assertNotNull(factory);
assertNull(factory.getSource());
assertTrue(factory instanceof FactoryDevfileV2Dto);
assertEquals(((FactoryDevfileV2Dto) factory).getDevfile(), devfileAsMap);
}
@Test
public void testDevfileV2WithFilename() throws BadRequestException, DevfileException {
String myLocation = "http://foo-location/";
Map<String, Object> devfileAsMap = Map.of("hello", "there", "how", "are", "you", "?");
JsonNode devfile = new ObjectNode(JsonNodeFactory.instance);
when(devfileParser.parseYamlRaw(anyString())).thenReturn(devfile);
when(devfileParser.convertYamlToMap(devfile)).thenReturn(devfileAsMap);
when(devfileVersionDetector.devfileMajorVersion(devfile)).thenReturn(2);
RemoteFactoryUrl githubLikeRemoteUrl =
() ->
Collections.singletonList(
new DevfileLocation() {
@Override
public Optional<String> filename() {
return Optional.of("devfile.yaml");
}
@Override
public String location() {
return myLocation;
}
});
FactoryMetaDto factory =
urlFactoryBuilder
.createFactoryFromDevfile(githubLikeRemoteUrl, s -> myLocation + ".list", emptyMap())
.get();
assertNotNull(factory);
assertEquals(factory.getSource(), "devfile.yaml");
assertTrue(factory instanceof FactoryDevfileV2Dto);
assertEquals(((FactoryDevfileV2Dto) factory).getDevfile(), devfileAsMap);
}
@DataProvider
@ -145,8 +220,7 @@ public class URLFactoryBuilderTest {
@Test(dataProvider = "devfiles")
public void checkThatDtoHasCorrectNames(DevfileImpl devfile, String expectedGenerateName)
throws BadRequestException, ServerException, DevfileException, IOException,
OverrideParameterException {
throws BadRequestException, DevfileException, IOException, OverrideParameterException {
DefaultFactoryUrl defaultFactoryUrl = mock(DefaultFactoryUrl.class);
FileContentProvider fileContentProvider = mock(FileContentProvider.class);
when(defaultFactoryUrl.devfileFileLocations())
@ -163,12 +237,16 @@ public class URLFactoryBuilderTest {
return "http://foo.bar/anything";
}
}));
when(devfileParser.parseYaml(anyString(), anyMap())).thenReturn(devfile);
when(fileContentProvider.fetchContent(anyString())).thenReturn("anything");
when(devfileParser.parseYamlRaw("anything"))
.thenReturn(new ObjectNode(JsonNodeFactory.instance));
when(devfileParser.parseJsonNode(any(JsonNode.class), anyMap())).thenReturn(devfile);
when(devfileVersionDetector.devfileMajorVersion(any(JsonNode.class))).thenReturn(1);
FactoryDto factory =
urlFactoryBuilder
.createFactoryFromDevfile(defaultFactoryUrl, fileContentProvider, emptyMap())
.get();
(FactoryDto)
urlFactoryBuilder
.createFactoryFromDevfile(defaultFactoryUrl, fileContentProvider, emptyMap())
.get();
assertNull(factory.getDevfile().getMetadata().getName());
assertEquals(factory.getDevfile().getMetadata().getGenerateName(), expectedGenerateName);

View File

@ -11,7 +11,6 @@
*/
package org.eclipse.che.api.workspace.server.devfile;
import java.util.Collections;
import java.util.List;
public class Constants {
@ -24,8 +23,7 @@ public class Constants {
public static final String CURRENT_API_VERSION = "1.0.0";
public static final List<String> SUPPORTED_VERSIONS =
Collections.singletonList(CURRENT_API_VERSION);
public static final List<String> SUPPORTED_VERSIONS = List.of(CURRENT_API_VERSION, "2.0.0");
public static final String EDITOR_COMPONENT_TYPE = "cheEditor";

View File

@ -19,6 +19,7 @@ import static org.eclipse.che.api.workspace.server.devfile.Constants.KUBERNETES_
import static org.eclipse.che.api.workspace.server.devfile.Constants.OPENSHIFT_COMPONENT_TYPE;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.dataformat.yaml.YAMLFactory;
@ -47,8 +48,8 @@ import org.eclipse.che.api.workspace.server.model.impl.devfile.DevfileImpl;
@Singleton
public class DevfileParser {
private ObjectMapper yamlMapper;
private ObjectMapper jsonMapper;
private final ObjectMapper yamlMapper;
private final ObjectMapper jsonMapper;
private final DevfileSchemaValidator schemaValidator;
private final DevfileIntegrityValidator integrityValidator;
private final OverridePropertiesApplier overridePropertiesApplier;
@ -87,7 +88,7 @@ public class DevfileParser {
*/
public DevfileImpl parseYaml(String devfileContent) throws DevfileFormatException {
try {
return parse(devfileContent, yamlMapper, emptyMap());
return parse(parseYamlRaw(devfileContent), yamlMapper, emptyMap());
} catch (OverrideParameterException e) {
// should never happen as we send empty overrides map
throw new RuntimeException(e.getMessage());
@ -95,28 +96,56 @@ public class DevfileParser {
}
/**
* Creates {@link DevfileImpl} from given devfile content in YAML and provides possibility to
* override its values using key-value map, where key is an json-pointer-like string and value is
* desired property value. NOTE: unlike json pointers, objects in arrays should be pointed by
* their names, not by index. Examples:
* Tries to parse given `yaml` into {@link JsonNode} and validates it with devfile schema.
*
* @param yaml to parse
* @return parsed yaml
* @throws DevfileFormatException if given yaml is empty or is not valid devfile
*/
public JsonNode parseYamlRaw(String yaml) throws DevfileFormatException {
try {
JsonNode devfileJson = yamlMapper.readTree(yaml);
if (devfileJson == null) {
throw new DevfileFormatException("Unable to parse Devfile - provided source is empty");
}
return devfileJson;
} catch (JsonProcessingException jpe) {
throw new DevfileFormatException("Can't parse devfile yaml.", jpe);
}
}
/**
* converts given devfile in {@link JsonNode} into {@link Map}.
*
* @param devfileJson json with devfile content
* @return devfile in simple Map structure
*/
public Map<String, Object> convertYamlToMap(JsonNode devfileJson) {
return yamlMapper.convertValue(devfileJson, new TypeReference<>() {});
}
/**
* Parse given devfile in {@link JsonNode} format into our {@link DevfileImpl} and provides
* possibility to override its values using key-value map, where key is an json-pointer-like
* string and value is desired property value. NOTE: unlike json pointers, objects in arrays
* should be pointed by their names, not by index. Examples:
*
* <ul>
* <li>metadata.generateName : python-dev-
* <li>projects.foo.source.type : git // foo is an project name
* </ul>
*
* Performs schema and integrity validation of input data.
* <p>Performs schema and integrity validation of input data.
*
* @param devfileContent raw content of devfile
* @param overrideProperties map of overridden values
* @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
* @throws OverrideParameterException when override properties is incorrect
* @param devfile devfile parsed in Json
* @param overrideProperties properties to override
* @return devfile created from given {@link JsonNode}
* @throws OverrideParameterException when any error when overriding parameters
* @throws DevfileFormatException when given devfile is not valid devfile
*/
public DevfileImpl parseYaml(String devfileContent, Map<String, String> overrideProperties)
throws DevfileFormatException, OverrideParameterException {
return parse(devfileContent, yamlMapper, overrideProperties);
public DevfileImpl parseJsonNode(JsonNode devfile, Map<String, String> overrideProperties)
throws OverrideParameterException, DevfileFormatException {
return parse(devfile, jsonMapper, overrideProperties);
}
/**
@ -137,31 +166,6 @@ public class DevfileParser {
}
}
/**
* Creates {@link DevfileImpl} from given devfile content in JSON and provides possibility to
* override its values using key-value map, where key is an json-pointer-like string and value is
* desired property value. NOTE: unlike json pointers, objects in arrays should be pointed by
* their names, not by index. Examples:
*
* <ul>
* <li>metadata.generateName : python-dev-
* <li>projects.foo.source.type : git // foo is an project name
* </ul>
*
* Performs schema and integrity validation of input data.
*
* @param devfileContent raw content of devfile
* @param overrideProperties map of overridden values
* @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
* @throws OverrideParameterException when override properties is incorrect
*/
public DevfileImpl parseJson(String devfileContent, Map<String, String> overrideProperties)
throws DevfileFormatException, OverrideParameterException {
return parse(devfileContent, jsonMapper, overrideProperties);
}
/**
* Resolve devfile component references into their reference content.
*
@ -196,19 +200,26 @@ public class DevfileParser {
private DevfileImpl parse(
String content, ObjectMapper mapper, Map<String, String> overrideProperties)
throws DevfileFormatException, OverrideParameterException {
try {
return parse(mapper.readTree(content), mapper, overrideProperties);
} catch (JsonProcessingException e) {
throw new DevfileFormatException(e.getMessage());
}
}
private DevfileImpl parse(
JsonNode parsed, ObjectMapper mapper, Map<String, String> overrideProperties)
throws DevfileFormatException, OverrideParameterException {
if (parsed == null) {
throw new DevfileFormatException("Unable to parse Devfile - provided source is empty");
}
DevfileImpl devfile;
try {
JsonNode parsed = mapper.readTree(content);
if (parsed == null) {
throw new DevfileFormatException("Unable to parse Devfile - provided source is empty");
}
parsed = overridePropertiesApplier.applyPropertiesOverride(parsed, overrideProperties);
schemaValidator.validate(parsed);
devfile = mapper.treeToValue(parsed, DevfileImpl.class);
} catch (JsonProcessingException e) {
throw new DevfileFormatException(e.getMessage());
} catch (IOException e) {
throw new DevfileFormatException("Unable to parse Devfile. Error: " + e.getMessage());
}
integrityValidator.validateDevfile(devfile);
return devfile;

View File

@ -0,0 +1,75 @@
/*
* Copyright (c) 2012-2018 Red Hat, Inc.
* This program and the accompanying materials are made
* available under the terms of the Eclipse Public License 2.0
* which is available at https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*
* Contributors:
* Red Hat, Inc. - initial API and implementation
*/
package org.eclipse.che.api.workspace.server.devfile;
import com.fasterxml.jackson.databind.JsonNode;
import javax.inject.Singleton;
import org.eclipse.che.api.workspace.server.devfile.exception.DevfileException;
/** Class that helps determine devfile versions. */
@Singleton
public class DevfileVersionDetector {
private final String DEVFILE_V1_VERSION_FIELD = "apiVersion";
private final String DEVFILE_V2_VERSION_FIELD = "schemaVersion";
/**
* Gives exact version of the devfile.
*
* @param devfile to inspect
* @return exact version of the devfile
* @throws DevfileException when can't find the field with version
*/
public String devfileVersion(JsonNode devfile) throws DevfileException {
final String version;
if (devfile.has(DEVFILE_V1_VERSION_FIELD)) {
version = devfile.get(DEVFILE_V1_VERSION_FIELD).asText();
} else if (devfile.has(DEVFILE_V2_VERSION_FIELD)) {
version = devfile.get(DEVFILE_V2_VERSION_FIELD).asText();
} else {
throw new DevfileException(
"Neither of `apiVersion` or `schemaVersion` found. This is not a valid devfile.");
}
return version;
}
/**
* Gives major version of the devfile.
*
* <pre>
* 1 -> 1
* 1.0.0 -> 1
* 1.99 -> 1
* 2.0.0 -> 2
* 2.1 -> 2
* a.a -> DevfileException
* a -> DevfileException
* </pre>
*
* @param devfile to inspect
* @return major version of the devfile
* @throws DevfileException when can't find the field with version
*/
public int devfileMajorVersion(JsonNode devfile) throws DevfileException {
String version = devfileVersion(devfile);
int dot = version.indexOf(".");
final String majorVersion = dot > 0 ? version.substring(0, dot) : version;
try {
return Integer.parseInt(majorVersion);
} catch (NumberFormatException nfe) {
throw new DevfileException(
"Unable to parse devfile version. This is not a valid devfile.", nfe);
}
}
}

View File

@ -25,6 +25,8 @@ import java.util.Map;
import javax.inject.Inject;
import javax.inject.Singleton;
import javax.json.JsonReader;
import org.eclipse.che.api.workspace.server.devfile.DevfileVersionDetector;
import org.eclipse.che.api.workspace.server.devfile.exception.DevfileException;
import org.eclipse.che.api.workspace.server.devfile.exception.DevfileFormatException;
import org.eclipse.che.api.workspace.server.devfile.schema.DevfileSchemaProvider;
import org.leadpony.justify.api.JsonSchema;
@ -36,15 +38,19 @@ import org.leadpony.justify.api.ProblemHandler;
@Singleton
public class DevfileSchemaValidator {
private final JsonValidationService service = JsonValidationService.newInstance();
private ObjectMapper jsonMapper;
private Map<String, JsonSchema> schemasByVersion;
private ErrorMessageComposer errorMessageComposer;
private final JsonValidationService service;
private final ObjectMapper jsonMapper;
private final Map<String, JsonSchema> schemasByVersion;
private final ErrorMessageComposer errorMessageComposer;
private final DevfileVersionDetector devfileVersionDetector;
@Inject
public DevfileSchemaValidator(DevfileSchemaProvider schemaProvider) {
public DevfileSchemaValidator(
DevfileSchemaProvider schemaProvider, DevfileVersionDetector devfileVersionDetector) {
this.service = JsonValidationService.newInstance();
this.jsonMapper = new ObjectMapper();
this.errorMessageComposer = new ErrorMessageComposer();
this.devfileVersionDetector = devfileVersionDetector;
try {
this.schemasByVersion = new HashMap<>();
for (String version : SUPPORTED_VERSIONS) {
@ -59,19 +65,15 @@ public class DevfileSchemaValidator {
try {
List<Problem> validationErrors = new ArrayList<>();
ProblemHandler handler = ProblemHandler.collectingTo(validationErrors);
if (!contentNode.hasNonNull("apiVersion")) {
throw new DevfileFormatException(
"Devfile schema validation failed. Error: The object must have a property whose name is \"apiVersion\".");
}
String apiVersion = contentNode.get("apiVersion").asText();
String devfileVersion = devfileVersionDetector.devfileVersion(contentNode);
if (!schemasByVersion.containsKey(apiVersion)) {
if (!schemasByVersion.containsKey(devfileVersion)) {
throw new DevfileFormatException(
String.format(
"Version '%s' of the devfile is not supported. Supported versions are '%s'.",
apiVersion, SUPPORTED_VERSIONS));
devfileVersion, SUPPORTED_VERSIONS));
}
JsonSchema schema = schemasByVersion.get(apiVersion);
JsonSchema schema = schemasByVersion.get(devfileVersion);
try (JsonReader reader =
service.createReader(
new StringReader(jsonMapper.writeValueAsString(contentNode)), schema, handler)) {
@ -79,9 +81,11 @@ public class DevfileSchemaValidator {
}
if (!validationErrors.isEmpty()) {
String error = errorMessageComposer.extractMessages(validationErrors, new StringBuilder());
throw new DevfileFormatException(
format("Devfile schema validation failed. Error: %s", error));
throw new DevfileFormatException(error);
}
} catch (DevfileException dfe) {
throw new DevfileFormatException(
format("Devfile schema validation failed. Error: %s", dfe.getMessage()));
} catch (IOException e) {
throw new DevfileFormatException("Unable to validate Devfile. Error: " + e.getMessage());
}

View File

@ -0,0 +1,8 @@
{
"description": "This is a temporary dummy structure for devfile 2.0.0.",
"type": "object",
"title": "Devfile schema - Version 2.0.0",
"required": [
"schemaVersion"
]
}

View File

@ -71,6 +71,7 @@ import org.eclipse.che.api.core.rest.CheJsonProvider;
import org.eclipse.che.api.core.rest.shared.dto.ServiceError;
import org.eclipse.che.api.workspace.server.devfile.DevfileEntityProvider;
import org.eclipse.che.api.workspace.server.devfile.DevfileParser;
import org.eclipse.che.api.workspace.server.devfile.DevfileVersionDetector;
import org.eclipse.che.api.workspace.server.devfile.URLFetcher;
import org.eclipse.che.api.workspace.server.devfile.schema.DevfileSchemaProvider;
import org.eclipse.che.api.workspace.server.devfile.validator.DevfileIntegrityValidator;
@ -152,7 +153,7 @@ public class WorkspaceServiceTest {
private DevfileEntityProvider devfileEntityProvider =
new DevfileEntityProvider(
new DevfileParser(
new DevfileSchemaValidator(new DevfileSchemaProvider()),
new DevfileSchemaValidator(new DevfileSchemaProvider(), new DevfileVersionDetector()),
new DevfileIntegrityValidator(Collections.emptyMap())));
@Mock private WorkspaceManager wsManager;

View File

@ -0,0 +1,100 @@
/*
* Copyright (c) 2012-2018 Red Hat, Inc.
* This program and the accompanying materials are made
* available under the terms of the Eclipse Public License 2.0
* which is available at https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*
* Contributors:
* Red Hat, Inc. - initial API and implementation
*/
package org.eclipse.che.api.workspace.server.devfile;
import static org.testng.Assert.*;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.node.JsonNodeFactory;
import com.fasterxml.jackson.databind.node.ObjectNode;
import org.eclipse.che.api.workspace.server.devfile.exception.DevfileException;
import org.testng.annotations.Test;
public class DevfileVersionTest {
private final DevfileVersionDetector devfileVersionDetector = new DevfileVersionDetector();
@Test(expectedExceptions = DevfileException.class)
public void shouldThrowExceptionWhenEmptyDevfile() throws DevfileException {
JsonNode devfile = new ObjectNode(JsonNodeFactory.instance);
devfileVersionDetector.devfileVersion(devfile);
}
@Test
public void shouldReturnApiVersion() throws DevfileException {
ObjectNode devfile = new ObjectNode(JsonNodeFactory.instance);
devfile.put("apiVersion", "1.1.1");
assertEquals(devfileVersionDetector.devfileVersion(devfile), "1.1.1");
}
@Test
public void shouldReturnSchemaVersion() throws DevfileException {
ObjectNode devfile = new ObjectNode(JsonNodeFactory.instance);
devfile.put("schemaVersion", "1.1.1");
assertEquals(devfileVersionDetector.devfileVersion(devfile), "1.1.1");
}
@Test
public void shouldReturnApiVersionWhenBothDefined() throws DevfileException {
ObjectNode devfile = new ObjectNode(JsonNodeFactory.instance);
devfile.put("apiVersion", "1");
devfile.put("schemaVersion", "2");
assertEquals(devfileVersionDetector.devfileVersion(devfile), "1");
}
@Test
public void shouldReturnMainVersionFromSchemaVersion() throws DevfileException {
ObjectNode devfile = new ObjectNode(JsonNodeFactory.instance);
devfile.put("schemaVersion", "10.1.1");
assertEquals(devfileVersionDetector.devfileMajorVersion(devfile), 10);
}
@Test
public void shouldReturnMainVersionFromApiVersion() throws DevfileException {
ObjectNode devfile = new ObjectNode(JsonNodeFactory.instance);
devfile.put("apiVersion", "11.1.1");
assertEquals(devfileVersionDetector.devfileMajorVersion(devfile), 11);
}
@Test(expectedExceptions = DevfileException.class)
public void shouldThrowExceptionWhenVersionNotDefined() throws DevfileException {
ObjectNode devfile = new ObjectNode(JsonNodeFactory.instance);
devfileVersionDetector.devfileMajorVersion(devfile);
}
@Test
public void shouldReturnMajorVersionWhenIsNumberString() throws DevfileException {
ObjectNode devfile = new ObjectNode(JsonNodeFactory.instance);
devfile.put("apiVersion", "2");
assertEquals(devfileVersionDetector.devfileMajorVersion(devfile), 2);
}
@Test
public void shouldReturnMajorVersionWhenIsNumber() throws DevfileException {
ObjectNode devfile = new ObjectNode(JsonNodeFactory.instance);
devfile.put("apiVersion", 2);
assertEquals(devfileVersionDetector.devfileMajorVersion(devfile), 2);
}
@Test(expectedExceptions = DevfileException.class)
public void shouldThrowExceptionWhenVersionIsNotNumber() throws DevfileException {
ObjectNode devfile = new ObjectNode(JsonNodeFactory.instance);
devfile.put("apiVersion", "a");
devfileVersionDetector.devfileMajorVersion(devfile);
}
@Test(expectedExceptions = DevfileException.class)
public void shouldThrowExceptionWhenVersionIsNotSemverNumber() throws DevfileException {
ObjectNode devfile = new ObjectNode(JsonNodeFactory.instance);
devfile.put("apiVersion", "a.a");
devfileVersionDetector.devfileMajorVersion(devfile);
}
}

View File

@ -19,6 +19,7 @@ import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.dataformat.yaml.YAMLFactory;
import java.io.IOException;
import org.eclipse.che.api.workspace.server.devfile.Constants;
import org.eclipse.che.api.workspace.server.devfile.DevfileVersionDetector;
import org.eclipse.che.api.workspace.server.devfile.exception.DevfileFormatException;
import org.eclipse.che.api.workspace.server.devfile.schema.DevfileSchemaProvider;
import org.testng.annotations.BeforeClass;
@ -34,7 +35,8 @@ public class DevfileSchemaValidatorTest {
@BeforeClass
public void setUp() {
yamlMapper = new ObjectMapper(new YAMLFactory());
schemaValidator = new DevfileSchemaValidator(new DevfileSchemaProvider());
schemaValidator =
new DevfileSchemaValidator(new DevfileSchemaProvider(), new DevfileVersionDetector());
}
@Test(dataProvider = "validDevfiles")
@ -76,6 +78,7 @@ public class DevfileSchemaValidatorTest {
{"devfile/devfile_name_and_generatename.yaml"},
{"devfile/devfile_with_sparse_checkout_dir.yaml"},
{"devfile/devfile_name_and_generatename.yaml"},
{"devfile/devfile_v2_just_schemaVersion.yaml"},
{"command/devfile_command_with_preview_url.yaml"},
{"command/devfile_command_with_preview_url_only_port.yaml"},
};
@ -105,7 +108,7 @@ public class DevfileSchemaValidatorTest {
} catch (DevfileFormatException e) {
assertEquals(
e.getMessage(),
"Version '111.111' of the devfile is not supported. "
"Devfile schema validation failed. Error: Version '111.111' of the devfile is not supported. "
+ "Supported versions are '"
+ Constants.SUPPORTED_VERSIONS
+ "'.");
@ -136,7 +139,7 @@ public class DevfileSchemaValidatorTest {
},
{
"devfile/devfile_missing_api_version.yaml",
"The object must have a property whose name is \"apiVersion\"."
"Neither of `apiVersion` or `schemaVersion` found. This is not a valid devfile."
},
{
"devfile/devfile_with_undeclared_field.yaml",
@ -253,6 +256,14 @@ public class DevfileSchemaValidatorTest {
"command/devfile_command_with_preview_url_only_path.yaml",
"(/commands/0/previewUrl):The object must have a property whose name is \"port\"."
},
{
"devfile/devfile_v2_invalid_schemaVersion.yaml",
"Version 'a.b.c' of the devfile is not supported. Supported versions are '[1.0.0, 2.0.0]'."
},
{
"devfile/devfile_v2_unsupported_schemaVersion.yaml",
"Version '22.33.44' of the devfile is not supported. Supported versions are '[1.0.0, 2.0.0]'."
}
};
}

View File

@ -0,0 +1,14 @@
#
# 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
#
---
schemaVersion: a.b.c

View File

@ -0,0 +1,14 @@
#
# 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
#
---
schemaVersion: 2.0.0

View File

@ -0,0 +1,14 @@
#
# 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
#
---
schemaVersion: 22.33.44