diff --git a/multiuser/permission/che-multiuser-permission-devfile/src/test/java/org/eclipse/che/multiuser/permission/devfile/server/filters/UserDevfilePermissionsFilterTest.java b/multiuser/permission/che-multiuser-permission-devfile/src/test/java/org/eclipse/che/multiuser/permission/devfile/server/filters/UserDevfilePermissionsFilterTest.java index 8d62a00502e..eca43a0817a 100644 --- a/multiuser/permission/che-multiuser-permission-devfile/src/test/java/org/eclipse/che/multiuser/permission/devfile/server/filters/UserDevfilePermissionsFilterTest.java +++ b/multiuser/permission/che-multiuser-permission-devfile/src/test/java/org/eclipse/che/multiuser/permission/devfile/server/filters/UserDevfilePermissionsFilterTest.java @@ -41,6 +41,7 @@ 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") diff --git a/wsmaster/che-core-api-devfile/src/test/java/org/eclipse/che/api/devfile/server/DevfileServiceTest.java b/wsmaster/che-core-api-devfile/src/test/java/org/eclipse/che/api/devfile/server/DevfileServiceTest.java index a7536ec5640..24b6dbd92a9 100644 --- a/wsmaster/che-core-api-devfile/src/test/java/org/eclipse/che/api/devfile/server/DevfileServiceTest.java +++ b/wsmaster/che-core-api-devfile/src/test/java/org/eclipse/che/api/devfile/server/DevfileServiceTest.java @@ -61,6 +61,7 @@ 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 Object[][] invalidUserDevfiles() { 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." } }; } diff --git a/wsmaster/che-core-api-factory-bitbucket-server/src/main/java/org/eclipse/che/api/factory/server/bitbucket/BitbucketServerAuthorizingFactoryParametersResolver.java b/wsmaster/che-core-api-factory-bitbucket-server/src/main/java/org/eclipse/che/api/factory/server/bitbucket/BitbucketServerAuthorizingFactoryParametersResolver.java index 094fe3d1294..f653330d3f4 100644 --- a/wsmaster/che-core-api-factory-bitbucket-server/src/main/java/org/eclipse/che/api/factory/server/bitbucket/BitbucketServerAuthorizingFactoryParametersResolver.java +++ b/wsmaster/che-core-api-factory-bitbucket-server/src/main/java/org/eclipse/che/api/factory/server/bitbucket/BitbucketServerAuthorizingFactoryParametersResolver.java @@ -15,8 +15,6 @@ 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.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 boolean accept(@NotNull final Map factoryParameters) { * @throws BadRequestException when data are invalid */ @Override - public FactoryDto createFactory(@NotNull final Map factoryParameters) + public FactoryMetaDto createFactory(@NotNull final Map factoryParameters) throws BadRequestException { // no need to check null value of url parameter as accept() method has performed the check @@ -92,41 +92,50 @@ public FactoryDto createFactory(@NotNull final Map factoryParame 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")); - - if (factory.getDevfile() == null) { - // initialize default devfile - factory.setDevfile(urlFactoryBuilder.buildDefaultDevfile(bitbucketUrl.getRepository())); + return urlFactoryBuilder + .createFactoryFromDevfile( + bitbucketUrl, fileContentProvider, extractOverrideParams(factoryParameters)) + .orElseGet(() -> newDto(FactoryDto.class).withV(CURRENT_VERSION).withSource("repo")) + .acceptVisitor(new BitbucketFactoryVisitor(bitbucketUrl)); + } + + /** + * 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 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; } } diff --git a/wsmaster/che-core-api-factory-bitbucket-server/src/test/java/org/eclipse/che/api/factory/server/bitbucket/BitbucketServerAuthorizingFactoryParametersResolverTest.java b/wsmaster/che-core-api-factory-bitbucket-server/src/test/java/org/eclipse/che/api/factory/server/bitbucket/BitbucketServerAuthorizingFactoryParametersResolverTest.java index bbceab66632..96dfb853961 100644 --- a/wsmaster/che-core-api-factory-bitbucket-server/src/test/java/org/eclipse/che/api/factory/server/bitbucket/BitbucketServerAuthorizingFactoryParametersResolverTest.java +++ b/wsmaster/che-core-api-factory-bitbucket-server/src/test/java/org/eclipse/che/api/factory/server/bitbucket/BitbucketServerAuthorizingFactoryParametersResolverTest.java @@ -108,7 +108,8 @@ public void shouldGenerateDevfileForFactoryWithNoDevfileOrJson() throws Exceptio .thenReturn(Optional.empty()); Map 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 void shouldSetDefaultProjectIntoDevfileIfNotSpecified() throws Exception Map 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(); diff --git a/wsmaster/che-core-api-factory-github/src/main/java/org/eclipse/che/api/factory/server/github/GithubFactoryParametersResolver.java b/wsmaster/che-core-api-factory-github/src/main/java/org/eclipse/che/api/factory/server/github/GithubFactoryParametersResolver.java index 1a5604db471..8296b1d6489 100644 --- a/wsmaster/che-core-api-factory-github/src/main/java/org/eclipse/che/api/factory/server/github/GithubFactoryParametersResolver.java +++ b/wsmaster/che-core-api-factory-github/src/main/java/org/eclipse/che/api/factory/server/github/GithubFactoryParametersResolver.java @@ -15,18 +15,17 @@ 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 boolean accept(@NotNull final Map factoryParameters) { * @throws BadRequestException when data are invalid */ @Override - public FactoryDto createFactory(@NotNull final Map factoryParameters) - throws BadRequestException, ServerException { + public FactoryMetaDto createFactory(@NotNull final Map 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")); - - 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())); + return urlFactoryBuilder + .createFactoryFromDevfile( + githubUrl, + new GithubFileContentProvider(githubUrl, urlFetcher), + extractOverrideParams(factoryParameters)) + .orElseGet(() -> newDto(FactoryDto.class).withV(CURRENT_VERSION).withSource("repo")) + .acceptVisitor(new GithubFactoryVisitor(githubUrl)); + } + + /** + * 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 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 FactoryDto createFactory(@NotNull final Map factoryParame project.getSource().setBranch(githubUrl.getBranch()); } }); + + return factory; } - return factory; } } diff --git a/wsmaster/che-core-api-factory-github/src/test/java/org/eclipse/che/api/factory/server/github/GithubFactoryParametersResolverTest.java b/wsmaster/che-core-api-factory-github/src/test/java/org/eclipse/che/api/factory/server/github/GithubFactoryParametersResolverTest.java index e6a1f73cc38..8c88f9921af 100644 --- a/wsmaster/che-core-api-factory-github/src/test/java/org/eclipse/che/api/factory/server/github/GithubFactoryParametersResolverTest.java +++ b/wsmaster/che-core-api-factory-github/src/test/java/org/eclipse/che/api/factory/server/github/GithubFactoryParametersResolverTest.java @@ -141,7 +141,7 @@ public void shouldGenerateDevfileForFactoryWithNoDevfile() throws Exception { .thenReturn(Optional.empty()); Map 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 void shouldReturnFactoryFromRepositoryWithDevfile() throws Exception { Map 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 void shouldSetDefaultProjectIntoDevfileIfNotSpecified() throws Exception Map 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 void shouldSetBranchIntoDevfileIfNotMatchesCurrent() throws Exception { Map 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(); diff --git a/wsmaster/che-core-api-factory-shared/src/main/java/org/eclipse/che/api/factory/shared/dto/FactoryDevfileV2Dto.java b/wsmaster/che-core-api-factory-shared/src/main/java/org/eclipse/che/api/factory/shared/dto/FactoryDevfileV2Dto.java new file mode 100644 index 00000000000..27a3ed97d5f --- /dev/null +++ b/wsmaster/che-core-api-factory-shared/src/main/java/org/eclipse/che/api/factory/shared/dto/FactoryDevfileV2Dto.java @@ -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} 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 getDevfile(); + + void setDevfile(Map devfile); + + FactoryDevfileV2Dto withDevfile(Map devfile); + + @Override + FactoryDevfileV2Dto withSource(String source); + + @Override + FactoryDevfileV2Dto withLinks(List links); +} diff --git a/wsmaster/che-core-api-factory-shared/src/main/java/org/eclipse/che/api/factory/shared/dto/FactoryDto.java b/wsmaster/che-core-api-factory-shared/src/main/java/org/eclipse/che/api/factory/shared/dto/FactoryDto.java index aed85fc036f..478a2df3bb6 100644 --- a/wsmaster/che-core-api-factory-shared/src/main/java/org/eclipse/che/api/factory/shared/dto/FactoryDto.java +++ b/wsmaster/che-core-api-factory-shared/src/main/java/org/eclipse/che/api/factory/shared/dto/FactoryDto.java @@ -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,89 +23,56 @@ import org.eclipse.che.dto.shared.DTO; /** - * Factory of version 4.0 + * Factory of version 4.0. + * + *

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); - /** because factory DTO may have devfile, in that case, workspace may be optional */ - @Override - @FactoryParameter(obligation = OPTIONAL) - WorkspaceConfigDto getWorkspace(); - - void setWorkspace(WorkspaceConfigDto workspace); - - FactoryDto withWorkspace(WorkspaceConfigDto workspace); + FactoryDto withV(String v); @Override - @FactoryParameter(obligation = OPTIONAL, trackedOnly = true) - PoliciesDto getPolicies(); - - void setPolicies(PoliciesDto policies); - - FactoryDto withPolicies(PoliciesDto policies); + FactoryDto withName(String name); @Override - @FactoryParameter(obligation = OPTIONAL) - AuthorDto getCreator(); - - void setCreator(AuthorDto creator); - - FactoryDto withCreator(AuthorDto creator); + FactoryDto withPolicies(PoliciesDto policies); @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(); + @Override + FactoryDto withSource(String source); - void setSource(String source); + @Override + FactoryDto withCreator(AuthorDto creator); - FactoryDto withSource(String source); + @Override + FactoryDto withLinks(List links); + /** because factory DTO may have devfile, in that case, workspace may be optional */ @Override @FactoryParameter(obligation = OPTIONAL) - String getName(); - - void setName(String name); + WorkspaceConfigDto getWorkspace(); - FactoryDto withName(String name); + void setWorkspace(WorkspaceConfigDto workspace); - @Override - FactoryDto withLinks(List links); + FactoryDto withWorkspace(WorkspaceConfigDto workspace); } diff --git a/wsmaster/che-core-api-factory-shared/src/main/java/org/eclipse/che/api/factory/shared/dto/FactoryMetaDto.java b/wsmaster/che-core-api-factory-shared/src/main/java/org/eclipse/che/api/factory/shared/dto/FactoryMetaDto.java new file mode 100644 index 00000000000..dd0f062e0f9 --- /dev/null +++ b/wsmaster/che-core-api-factory-shared/src/main/java/org/eclipse/che/api/factory/shared/dto/FactoryMetaDto.java @@ -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 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); +} diff --git a/wsmaster/che-core-api-factory-shared/src/main/java/org/eclipse/che/api/factory/shared/dto/FactoryVisitor.java b/wsmaster/che-core-api-factory-shared/src/main/java/org/eclipse/che/api/factory/shared/dto/FactoryVisitor.java new file mode 100644 index 00000000000..871ab7cfc00 --- /dev/null +++ b/wsmaster/che-core-api-factory-shared/src/main/java/org/eclipse/che/api/factory/shared/dto/FactoryVisitor.java @@ -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. + * + *

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. + * + *

Implementation should update given factory and give it back. + * + *

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; + } +} diff --git a/wsmaster/che-core-api-factory/pom.xml b/wsmaster/che-core-api-factory/pom.xml index a490cb50d8e..b73e77bc690 100644 --- a/wsmaster/che-core-api-factory/pom.xml +++ b/wsmaster/che-core-api-factory/pom.xml @@ -26,6 +26,10 @@ false + + com.fasterxml.jackson.core + jackson-databind + com.google.guava guava diff --git a/wsmaster/che-core-api-factory/src/main/java/org/eclipse/che/api/factory/server/DefaultFactoryParameterResolver.java b/wsmaster/che-core-api-factory/src/main/java/org/eclipse/che/api/factory/server/DefaultFactoryParameterResolver.java index 76c4e0bc93f..68300b6430c 100644 --- a/wsmaster/che-core-api-factory/src/main/java/org/eclipse/che/api/factory/server/DefaultFactoryParameterResolver.java +++ b/wsmaster/che-core-api-factory/src/main/java/org/eclipse/che/api/factory/server/DefaultFactoryParameterResolver.java @@ -19,8 +19,12 @@ 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.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 boolean accept(Map factoryParameters) { * @param factoryParameters map containing factory data parameters provided through URL */ @Override - public FactoryDto createFactory(@NotNull final Map factoryParameters) + public FactoryMetaDto createFactory(@NotNull final Map 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 @@ protected Map extractOverrideParams(Map factoryP .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 projectSupplier, + Consumer projectModifier) { + List 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); + } + } } diff --git a/wsmaster/che-core-api-factory/src/main/java/org/eclipse/che/api/factory/server/FactoryAcceptValidator.java b/wsmaster/che-core-api-factory/src/main/java/org/eclipse/che/api/factory/server/FactoryAcceptValidator.java index e690db4bcb6..ea3e97f1686 100644 --- a/wsmaster/che-core-api-factory/src/main/java/org/eclipse/che/api/factory/server/FactoryAcceptValidator.java +++ b/wsmaster/che-core-api-factory/src/main/java/org/eclipse/che/api/factory/server/FactoryAcceptValidator.java @@ -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; } diff --git a/wsmaster/che-core-api-factory/src/main/java/org/eclipse/che/api/factory/server/FactoryLinksHelper.java b/wsmaster/che-core-api-factory/src/main/java/org/eclipse/che/api/factory/server/FactoryLinksHelper.java index bfdda6c5e66..600a6a915d0 100644 --- a/wsmaster/che-core-api-factory/src/main/java/org/eclipse/che/api/factory/server/FactoryLinksHelper.java +++ b/wsmaster/che-core-api-factory/src/main/java/org/eclipse/che/api/factory/server/FactoryLinksHelper.java @@ -25,7 +25,7 @@ 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 @@ private FactoryLinksHelper() {} * @return list of factory links */ public static List createLinks( - FactoryDto factory, ServiceContext serviceContext, String userName) { + FactoryMetaDto factory, ServiceContext serviceContext, String userName) { final List links = new LinkedList<>(); final UriBuilder uriBuilder = serviceContext.getServiceUriBuilder(); final String factoryId = factory.getId(); diff --git a/wsmaster/che-core-api-factory/src/main/java/org/eclipse/che/api/factory/server/FactoryParametersResolver.java b/wsmaster/che-core-api-factory/src/main/java/org/eclipse/che/api/factory/server/FactoryParametersResolver.java index 191b97df038..68cc80989d5 100644 --- a/wsmaster/che-core-api-factory/src/main/java/org/eclipse/che/api/factory/server/FactoryParametersResolver.java +++ b/wsmaster/che-core-api-factory/src/main/java/org/eclipse/che/api/factory/server/FactoryParametersResolver.java @@ -15,7 +15,7 @@ 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 factoryParameters) + FactoryMetaDto createFactory(@NotNull Map factoryParameters) throws BadRequestException, ServerException; } diff --git a/wsmaster/che-core-api-factory/src/main/java/org/eclipse/che/api/factory/server/FactoryService.java b/wsmaster/che-core-api-factory/src/main/java/org/eclipse/che/api/factory/server/FactoryService.java index b8593ba6731..d1e964fea86 100644 --- a/wsmaster/che-core-api-factory/src/main/java/org/eclipse/che/api/factory/server/FactoryService.java +++ b/wsmaster/che-core-api-factory/src/main/java/org/eclipse/che/api/factory/server/FactoryService.java @@ -32,7 +32,7 @@ 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 FactoryService( @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 parameters, @ApiParam( value = "Whether or not to validate values like it is done when accepting a Factory", @@ -93,7 +93,7 @@ public FactoryDto resolveFactory( 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 FactoryDto resolveFactory( 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 { diff --git a/wsmaster/che-core-api-factory/src/main/java/org/eclipse/che/api/factory/server/impl/FactoryAcceptValidatorImpl.java b/wsmaster/che-core-api-factory/src/main/java/org/eclipse/che/api/factory/server/impl/FactoryAcceptValidatorImpl.java index f297a1dc88b..5eee52ede9b 100644 --- a/wsmaster/che-core-api-factory/src/main/java/org/eclipse/che/api/factory/server/impl/FactoryAcceptValidatorImpl.java +++ b/wsmaster/che-core-api-factory/src/main/java/org/eclipse/che/api/factory/server/impl/FactoryAcceptValidatorImpl.java @@ -14,7 +14,7 @@ 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); } diff --git a/wsmaster/che-core-api-factory/src/main/java/org/eclipse/che/api/factory/server/impl/FactoryBaseValidator.java b/wsmaster/che-core-api-factory/src/main/java/org/eclipse/che/api/factory/server/impl/FactoryBaseValidator.java index a63d1c0e08a..40646195dbf 100644 --- a/wsmaster/che-core-api-factory/src/main/java/org/eclipse/che/api/factory/server/impl/FactoryBaseValidator.java +++ b/wsmaster/che-core-api-factory/src/main/java/org/eclipse/che/api/factory/server/impl/FactoryBaseValidator.java @@ -24,6 +24,7 @@ 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 @@ protected void validateProjects(FactoryDto factory) throws BadRequestException { * @throws BadRequestException if since date greater than current date
* @throws BadRequestException if until date less than current date
*/ - protected void validateCurrentTimeBetweenSinceUntil(FactoryDto factory) + protected void validateCurrentTimeBetweenSinceUntil(FactoryMetaDto factory) throws BadRequestException { final PoliciesDto policies = factory.getPolicies(); if (policies == null) { @@ -149,7 +150,7 @@ protected void validateCurrentTimeAfterSinceUntil(FactoryDto factory) throws Bad * @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; diff --git a/wsmaster/che-core-api-factory/src/main/java/org/eclipse/che/api/factory/server/urlfactory/URLFactoryBuilder.java b/wsmaster/che-core-api-factory/src/main/java/org/eclipse/che/api/factory/server/urlfactory/URLFactoryBuilder.java index 28281b49780..46524b0e505 100644 --- a/wsmaster/che-core-api-factory/src/main/java/org/eclipse/che/api/factory/server/urlfactory/URLFactoryBuilder.java +++ b/wsmaster/che-core-api-factory/src/main/java/org/eclipse/che/api/factory/server/urlfactory/URLFactoryBuilder.java @@ -19,6 +19,7 @@ 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.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 URLFactoryBuilder( * @param overrideProperties map of overridden properties to apply in devfile * @return a factory or null if devfile is not found */ - public Optional createFactoryFromDevfile( + public Optional createFactoryFromDevfile( RemoteFactoryUrl remoteFactoryUrl, FileContentProvider fileContentProvider, Map overrideProperties) @@ -109,17 +116,11 @@ public Optional createFactoryFromDevfile( 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); + 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 Optional createFactoryFromDevfile( 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 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, diff --git a/wsmaster/che-core-api-factory/src/test/java/org/eclipse/che/api/factory/server/DefaultFactoryParameterResolverTest.java b/wsmaster/che-core-api-factory/src/test/java/org/eclipse/che/api/factory/server/DefaultFactoryParameterResolverTest.java index 35d3bd4bfd0..86d26685a9a 100644 --- a/wsmaster/che-core-api-factory/src/test/java/org/eclipse/che/api/factory/server/DefaultFactoryParameterResolverTest.java +++ b/wsmaster/che-core-api-factory/src/test/java/org/eclipse/che/api/factory/server/DefaultFactoryParameterResolverTest.java @@ -32,6 +32,7 @@ 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 void shouldResolveRelativeFiles() throws Exception { // 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 validators = new HashMap<>(); validators.put(EDITOR_COMPONENT_TYPE, new NoopComponentIntegrityValidator()); @@ -78,7 +80,8 @@ public void shouldResolveRelativeFiles() throws Exception { 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); diff --git a/wsmaster/che-core-api-factory/src/test/java/org/eclipse/che/api/factory/server/urlfactory/URLFactoryBuilderTest.java b/wsmaster/che-core-api-factory/src/test/java/org/eclipse/che/api/factory/server/urlfactory/URLFactoryBuilderTest.java index 5e449765660..13b70db273d 100644 --- a/wsmaster/che-core-api-factory/src/test/java/org/eclipse/che/api/factory/server/urlfactory/URLFactoryBuilderTest.java +++ b/wsmaster/che-core-api-factory/src/test/java/org/eclipse/che/api/factory/server/urlfactory/URLFactoryBuilderTest.java @@ -18,6 +18,7 @@ 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.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 void checkWithCustomDevfileAndRecipe() throws Exception { 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 void checkWithCustomDevfileAndRecipe() throws Exception { assertNotNull(factory); assertNull(factory.getSource()); + assertTrue(factory instanceof FactoryDto); + } + + @Test + public void testDevfileV2() throws BadRequestException, DevfileException { + String myLocation = "http://foo-location/"; + Map 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 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 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 Object[][] devfiles() { @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 String location() { 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); diff --git a/wsmaster/che-core-api-workspace/src/main/java/org/eclipse/che/api/workspace/server/devfile/Constants.java b/wsmaster/che-core-api-workspace/src/main/java/org/eclipse/che/api/workspace/server/devfile/Constants.java index 5860fe71a6f..563607d9cdd 100644 --- a/wsmaster/che-core-api-workspace/src/main/java/org/eclipse/che/api/workspace/server/devfile/Constants.java +++ b/wsmaster/che-core-api-workspace/src/main/java/org/eclipse/che/api/workspace/server/devfile/Constants.java @@ -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 @@ private Constants() {} public static final String CURRENT_API_VERSION = "1.0.0"; - public static final List SUPPORTED_VERSIONS = - Collections.singletonList(CURRENT_API_VERSION); + public static final List SUPPORTED_VERSIONS = List.of(CURRENT_API_VERSION, "2.0.0"); public static final String EDITOR_COMPONENT_TYPE = "cheEditor"; diff --git a/wsmaster/che-core-api-workspace/src/main/java/org/eclipse/che/api/workspace/server/devfile/DevfileParser.java b/wsmaster/che-core-api-workspace/src/main/java/org/eclipse/che/api/workspace/server/devfile/DevfileParser.java index dc731cb8306..6d4d3eedaad 100644 --- a/wsmaster/che-core-api-workspace/src/main/java/org/eclipse/che/api/workspace/server/devfile/DevfileParser.java +++ b/wsmaster/che-core-api-workspace/src/main/java/org/eclipse/che/api/workspace/server/devfile/DevfileParser.java @@ -19,6 +19,7 @@ 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 @@ @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 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 DevfileImpl parseYaml(String devfileContent) throws DevfileFormatExceptio } /** - * 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 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: * *

    *
  • metadata.generateName : python-dev- *
  • projects.foo.source.type : git // foo is an project name *
* - * Performs schema and integrity validation of input data. + *

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 overrideProperties) - throws DevfileFormatException, OverrideParameterException { - return parse(devfileContent, yamlMapper, overrideProperties); + public DevfileImpl parseJsonNode(JsonNode devfile, Map overrideProperties) + throws OverrideParameterException, DevfileFormatException { + return parse(devfile, jsonMapper, overrideProperties); } /** @@ -137,31 +166,6 @@ public DevfileImpl parseJson(String devfileContent) throws DevfileFormatExceptio } } - /** - * 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: - * - *

    - *
  • metadata.generateName : python-dev- - *
  • projects.foo.source.type : git // foo is an project name - *
- * - * 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 overrideProperties) - throws DevfileFormatException, OverrideParameterException { - return parse(devfileContent, jsonMapper, overrideProperties); - } - /** * Resolve devfile component references into their reference content. * @@ -196,19 +200,26 @@ public void resolveReference(DevfileImpl devfile, FileContentProvider fileConten private DevfileImpl parse( String content, ObjectMapper mapper, Map 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 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; diff --git a/wsmaster/che-core-api-workspace/src/main/java/org/eclipse/che/api/workspace/server/devfile/DevfileVersionDetector.java b/wsmaster/che-core-api-workspace/src/main/java/org/eclipse/che/api/workspace/server/devfile/DevfileVersionDetector.java new file mode 100644 index 00000000000..c45da169bb6 --- /dev/null +++ b/wsmaster/che-core-api-workspace/src/main/java/org/eclipse/che/api/workspace/server/devfile/DevfileVersionDetector.java @@ -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. + * + *
+   *   1 -> 1
+   *   1.0.0 -> 1
+   *   1.99 -> 1
+   *   2.0.0 -> 2
+   *   2.1 -> 2
+   *   a.a -> DevfileException
+   *   a -> DevfileException
+   * 
+ * + * @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); + } + } +} diff --git a/wsmaster/che-core-api-workspace/src/main/java/org/eclipse/che/api/workspace/server/devfile/validator/DevfileSchemaValidator.java b/wsmaster/che-core-api-workspace/src/main/java/org/eclipse/che/api/workspace/server/devfile/validator/DevfileSchemaValidator.java index 28a08a45a58..4e36aba51e4 100644 --- a/wsmaster/che-core-api-workspace/src/main/java/org/eclipse/che/api/workspace/server/devfile/validator/DevfileSchemaValidator.java +++ b/wsmaster/che-core-api-workspace/src/main/java/org/eclipse/che/api/workspace/server/devfile/validator/DevfileSchemaValidator.java @@ -25,6 +25,8 @@ 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 @@ @Singleton public class DevfileSchemaValidator { - private final JsonValidationService service = JsonValidationService.newInstance(); - private ObjectMapper jsonMapper; - private Map schemasByVersion; - private ErrorMessageComposer errorMessageComposer; + private final JsonValidationService service; + private final ObjectMapper jsonMapper; + private final Map 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 void validate(JsonNode contentNode) throws DevfileFormatException { try { List 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 void validate(JsonNode contentNode) throws DevfileFormatException { } 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()); } diff --git a/wsmaster/che-core-api-workspace/src/main/resources/schema/2.0.0/devfile.json b/wsmaster/che-core-api-workspace/src/main/resources/schema/2.0.0/devfile.json new file mode 100644 index 00000000000..701386a029a --- /dev/null +++ b/wsmaster/che-core-api-workspace/src/main/resources/schema/2.0.0/devfile.json @@ -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" + ] +} diff --git a/wsmaster/che-core-api-workspace/src/test/java/org/eclipse/che/api/workspace/server/WorkspaceServiceTest.java b/wsmaster/che-core-api-workspace/src/test/java/org/eclipse/che/api/workspace/server/WorkspaceServiceTest.java index d9ee1d1b07c..838180be000 100644 --- a/wsmaster/che-core-api-workspace/src/test/java/org/eclipse/che/api/workspace/server/WorkspaceServiceTest.java +++ b/wsmaster/che-core-api-workspace/src/test/java/org/eclipse/che/api/workspace/server/WorkspaceServiceTest.java @@ -71,6 +71,7 @@ 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; diff --git a/wsmaster/che-core-api-workspace/src/test/java/org/eclipse/che/api/workspace/server/devfile/DevfileVersionTest.java b/wsmaster/che-core-api-workspace/src/test/java/org/eclipse/che/api/workspace/server/devfile/DevfileVersionTest.java new file mode 100644 index 00000000000..671523a30d5 --- /dev/null +++ b/wsmaster/che-core-api-workspace/src/test/java/org/eclipse/che/api/workspace/server/devfile/DevfileVersionTest.java @@ -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); + } +} diff --git a/wsmaster/che-core-api-workspace/src/test/java/org/eclipse/che/api/workspace/server/devfile/validator/DevfileSchemaValidatorTest.java b/wsmaster/che-core-api-workspace/src/test/java/org/eclipse/che/api/workspace/server/devfile/validator/DevfileSchemaValidatorTest.java index 4f28cf9e6a2..06685a29b80 100644 --- a/wsmaster/che-core-api-workspace/src/test/java/org/eclipse/che/api/workspace/server/devfile/validator/DevfileSchemaValidatorTest.java +++ b/wsmaster/che-core-api-workspace/src/test/java/org/eclipse/che/api/workspace/server/devfile/validator/DevfileSchemaValidatorTest.java @@ -19,6 +19,7 @@ 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 Object[][] validDevfiles() { {"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 void shouldThrowExceptionWhenDevfileHasUnsupportedApiVersion() throws Exc } 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 Object[][] invalidDevfiles() { }, { "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 Object[][] invalidDevfiles() { "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]'." + } }; } diff --git a/wsmaster/che-core-api-workspace/src/test/resources/devfile/schema_test/devfile/devfile_v2_invalid_schemaVersion.yaml b/wsmaster/che-core-api-workspace/src/test/resources/devfile/schema_test/devfile/devfile_v2_invalid_schemaVersion.yaml new file mode 100644 index 00000000000..58e853805cf --- /dev/null +++ b/wsmaster/che-core-api-workspace/src/test/resources/devfile/schema_test/devfile/devfile_v2_invalid_schemaVersion.yaml @@ -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 diff --git a/wsmaster/che-core-api-workspace/src/test/resources/devfile/schema_test/devfile/devfile_v2_just_schemaVersion.yaml b/wsmaster/che-core-api-workspace/src/test/resources/devfile/schema_test/devfile/devfile_v2_just_schemaVersion.yaml new file mode 100644 index 00000000000..9a2fc9457fe --- /dev/null +++ b/wsmaster/che-core-api-workspace/src/test/resources/devfile/schema_test/devfile/devfile_v2_just_schemaVersion.yaml @@ -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 diff --git a/wsmaster/che-core-api-workspace/src/test/resources/devfile/schema_test/devfile/devfile_v2_unsupported_schemaVersion.yaml b/wsmaster/che-core-api-workspace/src/test/resources/devfile/schema_test/devfile/devfile_v2_unsupported_schemaVersion.yaml new file mode 100644 index 00000000000..67944dfab1e --- /dev/null +++ b/wsmaster/che-core-api-workspace/src/test/resources/devfile/schema_test/devfile/devfile_v2_unsupported_schemaVersion.yaml @@ -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