From d8991eafd1a49aec704fe618834d1d5e99eb6883 Mon Sep 17 00:00:00 2001 From: Igor Vinokur Date: Fri, 22 Sep 2023 17:09:48 +0300 Subject: [PATCH] Add an SSH Factory parameter resolver for unsupported SCM providers (#567) Return the SSH factory resolver in order to handle SSH urls from unsupported SCM providers. Add a priority value for all factory resolvers to be able to control resolver's priority. --------- Co-authored-by: Aleksandr Shmaraiev --- assembly/assembly-wsmaster-war/pom.xml | 4 + .../che/api/deploy/WsMasterModule.java | 8 + .../che/core/db/TracingDataSourceTest.java | 4 +- pom.xml | 5 + .../AzureDevOpsFactoryParametersResolver.java | 9 +- ...rAuthorizingFactoryParametersResolver.java | 9 +- ...tbucketAuthorizingFileContentProvider.java | 2 +- .../BitbucketFactoryParametersResolver.java | 9 +- wsmaster/che-core-api-factory-git-ssh/pom.xml | 112 ++++++++++++++ .../GitSshAuthorizingFileContentProvider.java | 31 ++++ .../ssh/GitSshFactoryParametersResolver.java | 139 ++++++++++++++++++ .../server/git/ssh/GitSshScmFileResolver.java | 45 ++++++ .../server/git/ssh/GitSshURLParser.java | 66 +++++++++ .../api/factory/server/git/ssh/GitSshUrl.java | 108 ++++++++++++++ .../server/git/ssh/GitSshURLParserTest.java | 50 +++++++ .../src/test/resources/logback-test.xml | 26 ++++ .../GithubFactoryParametersResolver.java | 30 +--- .../GitlabFactoryParametersResolver.java | 9 +- .../server/FactoryParametersResolver.java | 51 ++++++- .../server/FactoryResolverPriority.java | 28 ++++ .../api/factory/server/FactoryService.java | 31 ++-- ...RawDevfileUrlFactoryParameterResolver.java | 47 +----- .../scm/AuthorizingFileContentProvider.java | 6 +- .../factory/server/FactoryServiceTest.java | 135 +++++++++-------- wsmaster/pom.xml | 1 + 25 files changed, 801 insertions(+), 164 deletions(-) create mode 100644 wsmaster/che-core-api-factory-git-ssh/pom.xml create mode 100644 wsmaster/che-core-api-factory-git-ssh/src/main/java/org/eclipse/che/api/factory/server/git/ssh/GitSshAuthorizingFileContentProvider.java create mode 100644 wsmaster/che-core-api-factory-git-ssh/src/main/java/org/eclipse/che/api/factory/server/git/ssh/GitSshFactoryParametersResolver.java create mode 100644 wsmaster/che-core-api-factory-git-ssh/src/main/java/org/eclipse/che/api/factory/server/git/ssh/GitSshScmFileResolver.java create mode 100644 wsmaster/che-core-api-factory-git-ssh/src/main/java/org/eclipse/che/api/factory/server/git/ssh/GitSshURLParser.java create mode 100644 wsmaster/che-core-api-factory-git-ssh/src/main/java/org/eclipse/che/api/factory/server/git/ssh/GitSshUrl.java create mode 100644 wsmaster/che-core-api-factory-git-ssh/src/test/java/org/eclipse/che/api/factory/server/git/ssh/GitSshURLParserTest.java create mode 100644 wsmaster/che-core-api-factory-git-ssh/src/test/resources/logback-test.xml create mode 100644 wsmaster/che-core-api-factory/src/main/java/org/eclipse/che/api/factory/server/FactoryResolverPriority.java diff --git a/assembly/assembly-wsmaster-war/pom.xml b/assembly/assembly-wsmaster-war/pom.xml index 0e0589267e8..4142eb56bf6 100644 --- a/assembly/assembly-wsmaster-war/pom.xml +++ b/assembly/assembly-wsmaster-war/pom.xml @@ -139,6 +139,10 @@ org.eclipse.che.core che-core-api-factory-bitbucket-server + + org.eclipse.che.core + che-core-api-factory-git-ssh + org.eclipse.che.core che-core-api-factory-github diff --git a/assembly/assembly-wsmaster-war/src/main/java/org/eclipse/che/api/deploy/WsMasterModule.java b/assembly/assembly-wsmaster-war/src/main/java/org/eclipse/che/api/deploy/WsMasterModule.java index 274c1ff2f82..93379d7a999 100644 --- a/assembly/assembly-wsmaster-war/src/main/java/org/eclipse/che/api/deploy/WsMasterModule.java +++ b/assembly/assembly-wsmaster-war/src/main/java/org/eclipse/che/api/deploy/WsMasterModule.java @@ -36,6 +36,7 @@ import org.eclipse.che.api.factory.server.FactoryCreateValidator; import org.eclipse.che.api.factory.server.FactoryEditValidator; import org.eclipse.che.api.factory.server.FactoryParametersResolver; +import org.eclipse.che.api.factory.server.RawDevfileUrlFactoryParameterResolver; import org.eclipse.che.api.factory.server.ScmFileResolver; import org.eclipse.che.api.factory.server.ScmService; import org.eclipse.che.api.factory.server.azure.devops.AzureDevOpsFactoryParametersResolver; @@ -44,6 +45,8 @@ import org.eclipse.che.api.factory.server.bitbucket.BitbucketScmFileResolver; import org.eclipse.che.api.factory.server.bitbucket.BitbucketServerAuthorizingFactoryParametersResolver; import org.eclipse.che.api.factory.server.bitbucket.BitbucketServerScmFileResolver; +import org.eclipse.che.api.factory.server.git.ssh.GitSshFactoryParametersResolver; +import org.eclipse.che.api.factory.server.git.ssh.GitSshScmFileResolver; import org.eclipse.che.api.factory.server.github.GithubFactoryParametersResolver; import org.eclipse.che.api.factory.server.github.GithubScmFileResolver; import org.eclipse.che.api.factory.server.gitlab.GitlabFactoryParametersResolver; @@ -175,6 +178,10 @@ protected void configure() { factoryParametersResolverMultibinder .addBinding() .to(AzureDevOpsFactoryParametersResolver.class); + factoryParametersResolverMultibinder + .addBinding() + .to(RawDevfileUrlFactoryParameterResolver.class); + factoryParametersResolverMultibinder.addBinding().to(GitSshFactoryParametersResolver.class); Multibinder scmFileResolverResolverMultibinder = Multibinder.newSetBinder(binder(), ScmFileResolver.class); @@ -183,6 +190,7 @@ protected void configure() { scmFileResolverResolverMultibinder.addBinding().to(GitlabScmFileResolver.class); scmFileResolverResolverMultibinder.addBinding().to(BitbucketServerScmFileResolver.class); scmFileResolverResolverMultibinder.addBinding().to(AzureDevOpsScmFileResolver.class); + scmFileResolverResolverMultibinder.addBinding().to(GitSshScmFileResolver.class); install(new org.eclipse.che.api.factory.server.scm.KubernetesScmModule()); install(new org.eclipse.che.api.factory.server.bitbucket.BitbucketServerModule()); diff --git a/core/che-core-db/src/test/java/org/eclipse/che/core/db/TracingDataSourceTest.java b/core/che-core-db/src/test/java/org/eclipse/che/core/db/TracingDataSourceTest.java index 4d62c462376..b6f7ebc5d30 100644 --- a/core/che-core-db/src/test/java/org/eclipse/che/core/db/TracingDataSourceTest.java +++ b/core/che-core-db/src/test/java/org/eclipse/che/core/db/TracingDataSourceTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2012-2021 Red Hat, Inc. + * Copyright (c) 2012-2023 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/ @@ -77,7 +77,7 @@ public void shouldBeAbleTogetTracingDataSource() throws Exception { } @Test - public void shouldNotWrapDatasourceIfEnvSetToFalseØ() throws Exception { + public void shouldNotWrapDatasourceIfEnvSetToFalse() throws Exception { setEnv(ImmutableMap.of("CHE_DB_TRACING_ENABLED", "false")); DataSource actual = TracingDataSource.wrapWithTracingIfEnabled(dataSource); diff --git a/pom.xml b/pom.xml index 3baad12ffdb..d54aa879c66 100644 --- a/pom.xml +++ b/pom.xml @@ -759,6 +759,11 @@ che-core-api-factory-bitbucket-server ${che.version} + + org.eclipse.che.core + che-core-api-factory-git-ssh + ${che.version} + org.eclipse.che.core che-core-api-factory-github diff --git a/wsmaster/che-core-api-factory-azure-devops/src/main/java/org/eclipse/che/api/factory/server/azure/devops/AzureDevOpsFactoryParametersResolver.java b/wsmaster/che-core-api-factory-azure-devops/src/main/java/org/eclipse/che/api/factory/server/azure/devops/AzureDevOpsFactoryParametersResolver.java index 065beb994c0..e98e35d7e63 100644 --- a/wsmaster/che-core-api-factory-azure-devops/src/main/java/org/eclipse/che/api/factory/server/azure/devops/AzureDevOpsFactoryParametersResolver.java +++ b/wsmaster/che-core-api-factory-azure-devops/src/main/java/org/eclipse/che/api/factory/server/azure/devops/AzureDevOpsFactoryParametersResolver.java @@ -23,7 +23,7 @@ import javax.inject.Inject; import javax.inject.Singleton; import org.eclipse.che.api.core.ApiException; -import org.eclipse.che.api.factory.server.RawDevfileUrlFactoryParameterResolver; +import org.eclipse.che.api.factory.server.FactoryParametersResolver; import org.eclipse.che.api.factory.server.scm.PersonalAccessTokenManager; import org.eclipse.che.api.factory.server.urlfactory.ProjectConfigDtoMerger; import org.eclipse.che.api.factory.server.urlfactory.RemoteFactoryUrl; @@ -45,11 +45,13 @@ * @author Anatolii Bazko */ @Singleton -public class AzureDevOpsFactoryParametersResolver extends RawDevfileUrlFactoryParameterResolver { +public class AzureDevOpsFactoryParametersResolver implements FactoryParametersResolver { /** Parser which will allow to check validity of URLs and create objects. */ private final AzureDevOpsURLParser azureDevOpsURLParser; + private final URLFetcher urlFetcher; + private final URLFactoryBuilder urlFactoryBuilder; private final PersonalAccessTokenManager personalAccessTokenManager; private final ProjectConfigDtoMerger projectConfigDtoMerger; @@ -60,8 +62,9 @@ public AzureDevOpsFactoryParametersResolver( URLFetcher urlFetcher, URLFactoryBuilder urlFactoryBuilder, PersonalAccessTokenManager personalAccessTokenManager) { - super(urlFactoryBuilder, urlFetcher); this.azureDevOpsURLParser = azureDevOpsURLParser; + this.urlFetcher = urlFetcher; + this.urlFactoryBuilder = urlFactoryBuilder; this.personalAccessTokenManager = personalAccessTokenManager; this.projectConfigDtoMerger = projectConfigDtoMerger; } 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 b055d51d707..7aafa4baec8 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 @@ -22,7 +22,7 @@ import javax.inject.Singleton; import org.eclipse.che.api.core.ApiException; import org.eclipse.che.api.core.BadRequestException; -import org.eclipse.che.api.factory.server.RawDevfileUrlFactoryParameterResolver; +import org.eclipse.che.api.factory.server.FactoryParametersResolver; import org.eclipse.che.api.factory.server.scm.PersonalAccessTokenManager; import org.eclipse.che.api.factory.server.urlfactory.RemoteFactoryUrl; import org.eclipse.che.api.factory.server.urlfactory.URLFactoryBuilder; @@ -43,8 +43,10 @@ */ @Singleton public class BitbucketServerAuthorizingFactoryParametersResolver - extends RawDevfileUrlFactoryParameterResolver { + implements FactoryParametersResolver { + private final URLFactoryBuilder urlFactoryBuilder; + private final URLFetcher urlFetcher; /** Parser which will allow to check validity of URLs and create objects. */ private final BitbucketServerURLParser bitbucketURLParser; @@ -56,7 +58,8 @@ public BitbucketServerAuthorizingFactoryParametersResolver( URLFetcher urlFetcher, BitbucketServerURLParser bitbucketURLParser, PersonalAccessTokenManager personalAccessTokenManager) { - super(urlFactoryBuilder, urlFetcher); + this.urlFactoryBuilder = urlFactoryBuilder; + this.urlFetcher = urlFetcher; this.bitbucketURLParser = bitbucketURLParser; this.personalAccessTokenManager = personalAccessTokenManager; } diff --git a/wsmaster/che-core-api-factory-bitbucket/src/main/java/org/eclipse/che/api/factory/server/bitbucket/BitbucketAuthorizingFileContentProvider.java b/wsmaster/che-core-api-factory-bitbucket/src/main/java/org/eclipse/che/api/factory/server/bitbucket/BitbucketAuthorizingFileContentProvider.java index 66e03c8bcdd..bdee138fcc9 100644 --- a/wsmaster/che-core-api-factory-bitbucket/src/main/java/org/eclipse/che/api/factory/server/bitbucket/BitbucketAuthorizingFileContentProvider.java +++ b/wsmaster/che-core-api-factory-bitbucket/src/main/java/org/eclipse/che/api/factory/server/bitbucket/BitbucketAuthorizingFileContentProvider.java @@ -61,7 +61,7 @@ public String fetchContent(String fileURL) throws IOException, DevfileException requestURL.substring(requestURL.indexOf(split[6]) + split[6].length() + 1), token.getToken()); } catch (UnknownScmProviderException e) { - return fetchContentWithoutToken(requestURL, e); + return fetchContentWithoutToken(requestURL); } catch (ScmCommunicationException e) { return toIOException(fileURL, e); } catch (ScmUnauthorizedException diff --git a/wsmaster/che-core-api-factory-bitbucket/src/main/java/org/eclipse/che/api/factory/server/bitbucket/BitbucketFactoryParametersResolver.java b/wsmaster/che-core-api-factory-bitbucket/src/main/java/org/eclipse/che/api/factory/server/bitbucket/BitbucketFactoryParametersResolver.java index 75631a9624a..86b37a7cb01 100644 --- a/wsmaster/che-core-api-factory-bitbucket/src/main/java/org/eclipse/che/api/factory/server/bitbucket/BitbucketFactoryParametersResolver.java +++ b/wsmaster/che-core-api-factory-bitbucket/src/main/java/org/eclipse/che/api/factory/server/bitbucket/BitbucketFactoryParametersResolver.java @@ -22,7 +22,7 @@ import javax.inject.Singleton; import org.eclipse.che.api.core.ApiException; import org.eclipse.che.api.core.BadRequestException; -import org.eclipse.che.api.factory.server.RawDevfileUrlFactoryParameterResolver; +import org.eclipse.che.api.factory.server.FactoryParametersResolver; import org.eclipse.che.api.factory.server.scm.PersonalAccessTokenManager; import org.eclipse.che.api.factory.server.urlfactory.ProjectConfigDtoMerger; import org.eclipse.che.api.factory.server.urlfactory.RemoteFactoryUrl; @@ -38,14 +38,16 @@ /** Provides Factory Parameters resolver for bitbucket repositories. */ @Singleton -public class BitbucketFactoryParametersResolver extends RawDevfileUrlFactoryParameterResolver { +public class BitbucketFactoryParametersResolver implements FactoryParametersResolver { /** Parser which will allow to check validity of URLs and create objects. */ private final BitbucketURLParser bitbucketURLParser; + private final URLFetcher urlFetcher; /** Builder allowing to build objects from bitbucket URL. */ private final BitbucketSourceStorageBuilder bitbucketSourceStorageBuilder; + private final URLFactoryBuilder urlFactoryBuilder; /** ProjectDtoMerger */ private final ProjectConfigDtoMerger projectConfigDtoMerger; @@ -63,9 +65,10 @@ public BitbucketFactoryParametersResolver( ProjectConfigDtoMerger projectConfigDtoMerger, PersonalAccessTokenManager personalAccessTokenManager, BitbucketApiClient bitbucketApiClient) { - super(urlFactoryBuilder, urlFetcher); this.bitbucketURLParser = bitbucketURLParser; + this.urlFetcher = urlFetcher; this.bitbucketSourceStorageBuilder = bitbucketSourceStorageBuilder; + this.urlFactoryBuilder = urlFactoryBuilder; this.projectConfigDtoMerger = projectConfigDtoMerger; this.personalAccessTokenManager = personalAccessTokenManager; this.bitbucketApiClient = bitbucketApiClient; diff --git a/wsmaster/che-core-api-factory-git-ssh/pom.xml b/wsmaster/che-core-api-factory-git-ssh/pom.xml new file mode 100644 index 00000000000..09f41ce2521 --- /dev/null +++ b/wsmaster/che-core-api-factory-git-ssh/pom.xml @@ -0,0 +1,112 @@ + + + + 4.0.0 + + che-master-parent + org.eclipse.che.core + 7.74.1-SNAPSHOT + + che-core-api-factory-git-ssh + jar + Che Core :: API :: Factory Resolver Git Ssh + + true + + + + jakarta.inject + jakarta.inject-api + + + jakarta.validation + jakarta.validation-api + + + org.eclipse.che.core + che-core-api-core + + + org.eclipse.che.core + che-core-api-dto + + + org.eclipse.che.core + che-core-api-factory + + + org.eclipse.che.core + che-core-api-factory-shared + + + org.eclipse.che.core + che-core-api-workspace + + + org.eclipse.che.core + che-core-api-workspace-shared + + + ch.qos.logback + logback-classic + test + + + com.github.tomakehurst + wiremock-jre8-standalone + test + + + jakarta.servlet + jakarta.servlet-api + test + + + jakarta.ws.rs + jakarta.ws.rs-api + test + + + org.eclipse.che.core + che-core-commons-json + test + + + org.hamcrest + hamcrest-core + test + + + org.mockito + mockito-core + test + + + org.mockito + mockito-testng + test + + + org.slf4j + jcl-over-slf4j + test + + + org.testng + testng + test + + + diff --git a/wsmaster/che-core-api-factory-git-ssh/src/main/java/org/eclipse/che/api/factory/server/git/ssh/GitSshAuthorizingFileContentProvider.java b/wsmaster/che-core-api-factory-git-ssh/src/main/java/org/eclipse/che/api/factory/server/git/ssh/GitSshAuthorizingFileContentProvider.java new file mode 100644 index 00000000000..094a2a2575b --- /dev/null +++ b/wsmaster/che-core-api-factory-git-ssh/src/main/java/org/eclipse/che/api/factory/server/git/ssh/GitSshAuthorizingFileContentProvider.java @@ -0,0 +1,31 @@ +/* + * Copyright (c) 2012-2023 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.server.git.ssh; + +import org.eclipse.che.api.factory.server.scm.AuthorizingFileContentProvider; +import org.eclipse.che.api.factory.server.scm.PersonalAccessTokenManager; +import org.eclipse.che.api.workspace.server.devfile.URLFetcher; + +/** + * Git Ssh specific authorizing file content provider. + * + * @author Anatolii Bazko + */ +class GitSshAuthorizingFileContentProvider extends AuthorizingFileContentProvider { + + GitSshAuthorizingFileContentProvider( + GitSshUrl gitSshUrl, + URLFetcher urlFetcher, + PersonalAccessTokenManager personalAccessTokenManager) { + super(gitSshUrl, urlFetcher, personalAccessTokenManager); + } +} diff --git a/wsmaster/che-core-api-factory-git-ssh/src/main/java/org/eclipse/che/api/factory/server/git/ssh/GitSshFactoryParametersResolver.java b/wsmaster/che-core-api-factory-git-ssh/src/main/java/org/eclipse/che/api/factory/server/git/ssh/GitSshFactoryParametersResolver.java new file mode 100644 index 00000000000..685976aef1d --- /dev/null +++ b/wsmaster/che-core-api-factory-git-ssh/src/main/java/org/eclipse/che/api/factory/server/git/ssh/GitSshFactoryParametersResolver.java @@ -0,0 +1,139 @@ +/* + * Copyright (c) 2012-2023 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.server.git.ssh; + +import static org.eclipse.che.api.factory.server.FactoryResolverPriority.LOWEST; +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 jakarta.validation.constraints.NotNull; +import java.util.Map; +import javax.inject.Inject; +import javax.inject.Singleton; +import org.eclipse.che.api.core.ApiException; +import org.eclipse.che.api.factory.server.FactoryParametersResolver; +import org.eclipse.che.api.factory.server.FactoryResolverPriority; +import org.eclipse.che.api.factory.server.scm.PersonalAccessTokenManager; +import org.eclipse.che.api.factory.server.urlfactory.RemoteFactoryUrl; +import org.eclipse.che.api.factory.server.urlfactory.URLFactoryBuilder; +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.factory.shared.dto.FactoryVisitor; +import org.eclipse.che.api.factory.shared.dto.ScmInfoDto; +import org.eclipse.che.api.workspace.server.devfile.URLFetcher; +import org.eclipse.che.api.workspace.shared.dto.devfile.ProjectDto; +import org.eclipse.che.api.workspace.shared.dto.devfile.SourceDto; + +/** + * Provides Factory Parameters resolver for Git Ssh repositories. + * + * @author Anatolii Bazko + */ +@Singleton +public class GitSshFactoryParametersResolver implements FactoryParametersResolver { + + private final GitSshURLParser gitSshURLParser; + + private final URLFetcher urlFetcher; + private final URLFactoryBuilder urlFactoryBuilder; + private final PersonalAccessTokenManager personalAccessTokenManager; + + @Inject + public GitSshFactoryParametersResolver( + GitSshURLParser gitSshURLParser, + URLFetcher urlFetcher, + URLFactoryBuilder urlFactoryBuilder, + PersonalAccessTokenManager personalAccessTokenManager) { + this.gitSshURLParser = gitSshURLParser; + this.urlFetcher = urlFetcher; + this.urlFactoryBuilder = urlFactoryBuilder; + this.personalAccessTokenManager = personalAccessTokenManager; + } + + @Override + public boolean accept(@NotNull final Map factoryParameters) { + return factoryParameters.containsKey(URL_PARAMETER_NAME) + && gitSshURLParser.isValid(factoryParameters.get(URL_PARAMETER_NAME)); + } + + @Override + public FactoryMetaDto createFactory(@NotNull final Map factoryParameters) + throws ApiException { + // no need to check null value of url parameter as accept() method has performed the check + final GitSshUrl gitSshUrl = gitSshURLParser.parse(factoryParameters.get(URL_PARAMETER_NAME)); + + // create factory from the following location if location exists, else create default factory + return urlFactoryBuilder + .createFactoryFromDevfile( + gitSshUrl, + new GitSshAuthorizingFileContentProvider( + gitSshUrl, urlFetcher, personalAccessTokenManager), + extractOverrideParams(factoryParameters), + true) + .orElseGet(() -> newDto(FactoryDto.class).withV(CURRENT_VERSION).withSource("repo")) + .acceptVisitor(new GitSshFactoryVisitor(gitSshUrl)); + } + + /** + * Visitor that puts the default devfile or updates devfile projects into the Git Ssh Factory, if + * needed. + */ + private class GitSshFactoryVisitor implements FactoryVisitor { + + private final GitSshUrl gitSshUrl; + + private GitSshFactoryVisitor(GitSshUrl gitSshUrl) { + this.gitSshUrl = gitSshUrl; + } + + @Override + public FactoryDevfileV2Dto visit(FactoryDevfileV2Dto factoryDto) { + ScmInfoDto scmInfo = + newDto(ScmInfoDto.class) + .withScmProviderName(gitSshUrl.getProviderName()) + .withRepositoryUrl(gitSshUrl.getRepositoryLocation()); + return factoryDto.withScmInfo(scmInfo); + } + + @Override + public FactoryDto visit(FactoryDto factory) { + if (factory.getDevfile() == null) { + factory.setDevfile(urlFactoryBuilder.buildDefaultDevfile(gitSshUrl.getRepository())); + } + + updateProjects( + factory.getDevfile(), + () -> + newDto(ProjectDto.class) + .withSource( + newDto(SourceDto.class) + .withLocation(gitSshUrl.getRepositoryLocation()) + .withType("git")) + .withName(gitSshUrl.getRepository()), + project -> {}); + + return factory; + } + } + + @Override + public RemoteFactoryUrl parseFactoryUrl(String factoryUrl) { + return gitSshURLParser.parse(factoryUrl); + } + + @Override + public FactoryResolverPriority priority() { + return LOWEST; + } +} diff --git a/wsmaster/che-core-api-factory-git-ssh/src/main/java/org/eclipse/che/api/factory/server/git/ssh/GitSshScmFileResolver.java b/wsmaster/che-core-api-factory-git-ssh/src/main/java/org/eclipse/che/api/factory/server/git/ssh/GitSshScmFileResolver.java new file mode 100644 index 00000000000..a28b208cb89 --- /dev/null +++ b/wsmaster/che-core-api-factory-git-ssh/src/main/java/org/eclipse/che/api/factory/server/git/ssh/GitSshScmFileResolver.java @@ -0,0 +1,45 @@ +/* + * Copyright (c) 2012-2023 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.server.git.ssh; + +import jakarta.validation.constraints.NotNull; +import javax.inject.Inject; +import org.eclipse.che.api.factory.server.ScmFileResolver; + +/** + * Git Ssh specific SCM file resolver. + * + * @author Anatolii Bazko + */ +public class GitSshScmFileResolver implements ScmFileResolver { + + private final GitSshURLParser gitSshURLParser; + + @Inject + public GitSshScmFileResolver(GitSshURLParser gitSshURLParser) { + this.gitSshURLParser = gitSshURLParser; + } + + @Override + public boolean accept(@NotNull String repository) { + return gitSshURLParser.isValid(repository); + } + + /** + * There is no way to get a file content from a git repository via ssh protocol. So this method + * always returns an empty string. It allows to start a workspace from an empty devfile. + */ + @Override + public String fileContent(@NotNull String repository, @NotNull String filePath) { + return ""; + } +} diff --git a/wsmaster/che-core-api-factory-git-ssh/src/main/java/org/eclipse/che/api/factory/server/git/ssh/GitSshURLParser.java b/wsmaster/che-core-api-factory-git-ssh/src/main/java/org/eclipse/che/api/factory/server/git/ssh/GitSshURLParser.java new file mode 100644 index 00000000000..9c25e825b72 --- /dev/null +++ b/wsmaster/che-core-api-factory-git-ssh/src/main/java/org/eclipse/che/api/factory/server/git/ssh/GitSshURLParser.java @@ -0,0 +1,66 @@ +/* + * Copyright (c) 2012-2023 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.server.git.ssh; + +import static java.lang.String.format; +import static java.util.regex.Pattern.compile; + +import jakarta.validation.constraints.NotNull; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import javax.inject.Inject; +import javax.inject.Singleton; +import org.eclipse.che.api.factory.server.urlfactory.DevfileFilenamesProvider; + +/** + * Parser of String Git Ssh URLs and provide {@link GitSshUrl} objects. + * + * @author Anatolii Bazko + */ +@Singleton +public class GitSshURLParser { + + private final Pattern gitSshPattern; + + private final DevfileFilenamesProvider devfileFilenamesProvider; + + @Inject + public GitSshURLParser(DevfileFilenamesProvider devfileFilenamesProvider) { + this.devfileFilenamesProvider = devfileFilenamesProvider; + this.gitSshPattern = compile("^git@(?[^:]++):(.*)/(?[^/]++)$"); + } + + public boolean isValid(@NotNull String url) { + return gitSshPattern.matcher(url).matches(); + } + + public GitSshUrl parse(String url) { + Matcher matcher = gitSshPattern.matcher(url); + if (!matcher.matches()) { + throw new IllegalArgumentException( + format("The given url %s is not a valid. It should start with git@", url)); + } + + String hostName = matcher.group("hostName"); + String repoName = matcher.group("repoName"); + if (repoName.endsWith(".git")) { + repoName = repoName.substring(0, repoName.length() - 4); + } + + return new GitSshUrl() + .withDevfileFilenames(devfileFilenamesProvider.getConfiguredDevfileFilenames()) + .withHostName(hostName) + .withRepository(repoName) + .withRepositoryLocation(url) + .withUrl(url); + } +} diff --git a/wsmaster/che-core-api-factory-git-ssh/src/main/java/org/eclipse/che/api/factory/server/git/ssh/GitSshUrl.java b/wsmaster/che-core-api-factory-git-ssh/src/main/java/org/eclipse/che/api/factory/server/git/ssh/GitSshUrl.java new file mode 100644 index 00000000000..ae180c9e07b --- /dev/null +++ b/wsmaster/che-core-api-factory-git-ssh/src/main/java/org/eclipse/che/api/factory/server/git/ssh/GitSshUrl.java @@ -0,0 +1,108 @@ +/* + * Copyright (c) 2012-2023 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.server.git.ssh; + +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; +import java.util.stream.Collectors; +import org.eclipse.che.api.factory.server.urlfactory.DefaultFactoryUrl; + +/** + * Representation of Git Ssh URL, allowing to get details from it. + * + * @author Anatolii Bazko + */ +public class GitSshUrl extends DefaultFactoryUrl { + + private String repository; + private String hostName; + + private String repositoryLocation; + + private final List devfileFilenames = new ArrayList<>(); + + protected GitSshUrl() {} + + @Override + public String getProviderName() { + return "git-ssh"; + } + + @Override + public String getBranch() { + return null; + } + + public GitSshUrl withDevfileFilenames(List devfileFilenames) { + this.devfileFilenames.addAll(devfileFilenames); + return this; + } + + @Override + public void setDevfileFilename(String devfileName) { + this.devfileFilenames.clear(); + this.devfileFilenames.add(devfileName); + } + + @Override + public List devfileFileLocations() { + return devfileFilenames.stream().map(this::createDevfileLocation).collect(Collectors.toList()); + } + + @Override + public String rawFileLocation(String filename) { + return filename; + } + + private DevfileLocation createDevfileLocation(String devfileFilename) { + return new DevfileLocation() { + @Override + public Optional filename() { + return Optional.of(devfileFilename); + } + + @Override + public String location() { + return devfileFilename; + } + }; + } + + @Override + public String getHostName() { + return hostName; + } + + public GitSshUrl withHostName(String hostName) { + this.hostName = hostName; + return this; + } + + public String getRepositoryLocation() { + return repositoryLocation; + } + + public GitSshUrl withRepositoryLocation(String repositoryLocation) { + this.repositoryLocation = repositoryLocation; + return this; + } + + public String getRepository() { + return repository; + } + + public GitSshUrl withRepository(String repository) { + this.repository = repository; + return this; + } +} diff --git a/wsmaster/che-core-api-factory-git-ssh/src/test/java/org/eclipse/che/api/factory/server/git/ssh/GitSshURLParserTest.java b/wsmaster/che-core-api-factory-git-ssh/src/test/java/org/eclipse/che/api/factory/server/git/ssh/GitSshURLParserTest.java new file mode 100644 index 00000000000..715e5f544b0 --- /dev/null +++ b/wsmaster/che-core-api-factory-git-ssh/src/test/java/org/eclipse/che/api/factory/server/git/ssh/GitSshURLParserTest.java @@ -0,0 +1,50 @@ +/* + * Copyright (c) 2012-2023 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.server.git.ssh; + +import static org.mockito.Mockito.mock; +import static org.testng.Assert.assertEquals; + +import org.eclipse.che.api.factory.server.urlfactory.DevfileFilenamesProvider; +import org.mockito.testng.MockitoTestNGListener; +import org.testng.annotations.BeforeMethod; +import org.testng.annotations.DataProvider; +import org.testng.annotations.Listeners; +import org.testng.annotations.Test; + +/** @author Anatalii Bazko */ +@Listeners(MockitoTestNGListener.class) +public class GitSshURLParserTest { + + private GitSshURLParser gitSshURLParser; + + @BeforeMethod + protected void start() { + gitSshURLParser = new GitSshURLParser(mock(DevfileFilenamesProvider.class)); + } + + @Test(dataProvider = "parsing") + public void testParse(String url, String hostName, String repository) { + GitSshUrl gitSshUrl = gitSshURLParser.parse(url); + + assertEquals(gitSshUrl.getHostName(), hostName); + assertEquals(gitSshUrl.getRepository(), repository); + } + + @DataProvider(name = "parsing") + public Object[][] expectedParsing() { + return new Object[][] { + {"git@ssh.dev.azure.com:v3/MyOrg/MyProject/MyRepo", "ssh.dev.azure.com", "MyRepo"}, + {"git@github.com:MyOrg/MyRepo.git", "github.com", "MyRepo"}, + }; + } +} diff --git a/wsmaster/che-core-api-factory-git-ssh/src/test/resources/logback-test.xml b/wsmaster/che-core-api-factory-git-ssh/src/test/resources/logback-test.xml new file mode 100644 index 00000000000..704cbbf50f4 --- /dev/null +++ b/wsmaster/che-core-api-factory-git-ssh/src/test/resources/logback-test.xml @@ -0,0 +1,26 @@ + + + + + + %-41(%date[%.15thread]) %-45([%-5level] [%.30logger{30} %L]) - %msg%n%nopex + + + + + + + + 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 477e8ce5426..4c96fd82446 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 @@ -19,11 +19,10 @@ import jakarta.validation.constraints.NotNull; import java.util.Map; import javax.inject.Inject; -import javax.inject.Named; import javax.inject.Singleton; import org.eclipse.che.api.core.ApiException; import org.eclipse.che.api.core.BadRequestException; -import org.eclipse.che.api.factory.server.RawDevfileUrlFactoryParameterResolver; +import org.eclipse.che.api.factory.server.FactoryParametersResolver; import org.eclipse.che.api.factory.server.scm.PersonalAccessTokenManager; import org.eclipse.che.api.factory.server.urlfactory.ProjectConfigDtoMerger; import org.eclipse.che.api.factory.server.urlfactory.RemoteFactoryUrl; @@ -36,7 +35,6 @@ 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; -import org.eclipse.che.commons.annotation.Nullable; /** * Provides Factory Parameters resolver for github repositories. @@ -44,14 +42,18 @@ * @author Florent Benoit */ @Singleton -public class GithubFactoryParametersResolver extends RawDevfileUrlFactoryParameterResolver { +public class GithubFactoryParametersResolver implements FactoryParametersResolver { /** Parser which will allow to check validity of URLs and create objects. */ private final GithubURLParser githubUrlParser; + private final URLFetcher urlFetcher; + /** Builder allowing to build objects from github URL. */ private final GithubSourceStorageBuilder githubSourceStorageBuilder; + private final URLFactoryBuilder urlFactoryBuilder; + /** ProjectDtoMerger */ private final ProjectConfigDtoMerger projectConfigDtoMerger; @@ -59,32 +61,16 @@ public class GithubFactoryParametersResolver extends RawDevfileUrlFactoryParamet @Inject public GithubFactoryParametersResolver( - GithubURLParser githubUrlParser, - URLFetcher urlFetcher, - GithubSourceStorageBuilder githubSourceStorageBuilder, - URLFactoryBuilder urlFactoryBuilder, - ProjectConfigDtoMerger projectConfigDtoMerger, - PersonalAccessTokenManager personalAccessTokenManager, - @Nullable @Named("che.integration.github.oauth_endpoint") String oauthEndpoint) { - this( - githubUrlParser, - urlFetcher, - githubSourceStorageBuilder, - urlFactoryBuilder, - projectConfigDtoMerger, - personalAccessTokenManager); - } - - GithubFactoryParametersResolver( GithubURLParser githubUrlParser, URLFetcher urlFetcher, GithubSourceStorageBuilder githubSourceStorageBuilder, URLFactoryBuilder urlFactoryBuilder, ProjectConfigDtoMerger projectConfigDtoMerger, PersonalAccessTokenManager personalAccessTokenManager) { - super(urlFactoryBuilder, urlFetcher); this.githubUrlParser = githubUrlParser; + this.urlFetcher = urlFetcher; this.githubSourceStorageBuilder = githubSourceStorageBuilder; + this.urlFactoryBuilder = urlFactoryBuilder; this.projectConfigDtoMerger = projectConfigDtoMerger; this.personalAccessTokenManager = personalAccessTokenManager; } diff --git a/wsmaster/che-core-api-factory-gitlab/src/main/java/org/eclipse/che/api/factory/server/gitlab/GitlabFactoryParametersResolver.java b/wsmaster/che-core-api-factory-gitlab/src/main/java/org/eclipse/che/api/factory/server/gitlab/GitlabFactoryParametersResolver.java index cdbf4a02048..efedf3f1eef 100644 --- a/wsmaster/che-core-api-factory-gitlab/src/main/java/org/eclipse/che/api/factory/server/gitlab/GitlabFactoryParametersResolver.java +++ b/wsmaster/che-core-api-factory-gitlab/src/main/java/org/eclipse/che/api/factory/server/gitlab/GitlabFactoryParametersResolver.java @@ -22,7 +22,7 @@ import javax.inject.Singleton; import org.eclipse.che.api.core.ApiException; import org.eclipse.che.api.core.BadRequestException; -import org.eclipse.che.api.factory.server.RawDevfileUrlFactoryParameterResolver; +import org.eclipse.che.api.factory.server.FactoryParametersResolver; import org.eclipse.che.api.factory.server.scm.PersonalAccessTokenManager; import org.eclipse.che.api.factory.server.urlfactory.RemoteFactoryUrl; import org.eclipse.che.api.factory.server.urlfactory.URLFactoryBuilder; @@ -41,8 +41,10 @@ * @author Max Shaposhnyk */ @Singleton -public class GitlabFactoryParametersResolver extends RawDevfileUrlFactoryParameterResolver { +public class GitlabFactoryParametersResolver implements FactoryParametersResolver { + private final URLFactoryBuilder urlFactoryBuilder; + private final URLFetcher urlFetcher; private final GitlabUrlParser gitlabURLParser; private final PersonalAccessTokenManager personalAccessTokenManager; @@ -52,7 +54,8 @@ public GitlabFactoryParametersResolver( URLFetcher urlFetcher, GitlabUrlParser gitlabURLParser, PersonalAccessTokenManager personalAccessTokenManager) { - super(urlFactoryBuilder, urlFetcher); + this.urlFactoryBuilder = urlFactoryBuilder; + this.urlFetcher = urlFetcher; this.gitlabURLParser = gitlabURLParser; this.personalAccessTokenManager = personalAccessTokenManager; } 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 617b0758519..d29e982e437 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 @@ -11,12 +11,20 @@ */ package org.eclipse.che.api.factory.server; +import static java.util.stream.Collectors.toMap; + import jakarta.validation.constraints.NotNull; +import java.util.Collections; +import java.util.List; import java.util.Map; +import java.util.function.Consumer; +import java.util.function.Supplier; import org.eclipse.che.api.core.ApiException; import org.eclipse.che.api.core.BadRequestException; import org.eclipse.che.api.factory.server.urlfactory.RemoteFactoryUrl; import org.eclipse.che.api.factory.shared.dto.FactoryMetaDto; +import org.eclipse.che.api.workspace.shared.dto.devfile.DevfileDto; +import org.eclipse.che.api.workspace.shared.dto.devfile.ProjectDto; /** * Defines a resolver that will produce factories for some parameters @@ -24,7 +32,6 @@ * @author Florent Benoit */ public interface FactoryParametersResolver { - /** * Resolver acceptance based on the given parameters. * @@ -50,4 +57,46 @@ public interface FactoryParametersResolver { * @throws ApiException when authentication required operations fail */ RemoteFactoryUrl parseFactoryUrl(String factoryUrl) throws ApiException; + + /** + * Returns priority of the resolver. Resolvers with higher priority will be used among matched + * resolvers. + */ + default FactoryResolverPriority priority() { + return FactoryResolverPriority.DEFAULT; + } + + /** + * Finds and returns devfile override parameters in general factory parameters map. + * + * @param factoryParameters map containing factory data parameters provided through URL + * @return filtered devfile values override map + */ + default Map extractOverrideParams(Map factoryParameters) { + String overridePrefix = "override."; + return factoryParameters.entrySet().stream() + .filter(e -> e.getKey().startsWith(overridePrefix)) + .collect(toMap(e -> e.getKey().substring(overridePrefix.length()), Map.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 + */ + default 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/FactoryResolverPriority.java b/wsmaster/che-core-api-factory/src/main/java/org/eclipse/che/api/factory/server/FactoryResolverPriority.java new file mode 100644 index 00000000000..abbc82a57e2 --- /dev/null +++ b/wsmaster/che-core-api-factory/src/main/java/org/eclipse/che/api/factory/server/FactoryResolverPriority.java @@ -0,0 +1,28 @@ +/* + * Copyright (c) 2012-2023 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.server; + +public enum FactoryResolverPriority { + DEFAULT(1), + HIGHEST(2), + LOWEST(0); + + private final int value; + + FactoryResolverPriority(int value) { + this.value = value; + } + + public int getValue() { + return value; + } +} 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 580e42f4af5..6a5b030d3ae 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 @@ -13,6 +13,7 @@ import static jakarta.ws.rs.core.MediaType.APPLICATION_JSON; import static java.util.Collections.singletonMap; +import static java.util.Comparator.comparingInt; import static org.eclipse.che.api.factory.server.ApiExceptionMapper.toApiException; import static org.eclipse.che.api.factory.server.FactoryLinksHelper.createLinks; import static org.eclipse.che.api.factory.shared.Constants.URL_PARAMETER_NAME; @@ -28,6 +29,7 @@ import jakarta.ws.rs.Produces; import jakarta.ws.rs.QueryParam; import java.util.Map; +import java.util.Optional; import java.util.Set; import javax.inject.Inject; import org.eclipse.che.api.core.ApiException; @@ -175,8 +177,6 @@ protected static class FactoryParametersResolverHolder { @SuppressWarnings("unused") private Set specificFactoryParametersResolvers; - @Inject private RawDevfileUrlFactoryParameterResolver defaultFactoryResolver; - /** * Provides a suitable resolver for the given parameters. If there is no at least one resolver * able to process parameters,then {@link BadRequestException} will be thrown @@ -185,20 +185,19 @@ protected static class FactoryParametersResolverHolder { */ public FactoryParametersResolver getFactoryParametersResolver(Map parameters) throws BadRequestException { - // Check if the URL is a raw devfile URL. If so, use the default resolver, - // which resolves factories from a direct URL to a devfile content. - if (defaultFactoryResolver.accept(parameters)) { - return defaultFactoryResolver; - } - for (FactoryParametersResolver factoryParametersResolver : - specificFactoryParametersResolvers) { - try { - if (factoryParametersResolver.accept(parameters)) { - return factoryParametersResolver; - } - } catch (IllegalArgumentException e) { - // ignore and try next resolver - } + Optional resolverOptional = + specificFactoryParametersResolvers.stream() + .filter( + r -> { + try { + return r.accept(parameters); + } catch (IllegalArgumentException e) { + return false; + } + }) + .max(comparingInt(r -> r.priority().getValue())); + if (resolverOptional.isPresent()) { + return resolverOptional.get(); } throw new BadRequestException(FACTORY_NOT_RESOLVABLE); } diff --git a/wsmaster/che-core-api-factory/src/main/java/org/eclipse/che/api/factory/server/RawDevfileUrlFactoryParameterResolver.java b/wsmaster/che-core-api-factory/src/main/java/org/eclipse/che/api/factory/server/RawDevfileUrlFactoryParameterResolver.java index 2c87847d6ab..50e5147cbe8 100644 --- a/wsmaster/che-core-api-factory/src/main/java/org/eclipse/che/api/factory/server/RawDevfileUrlFactoryParameterResolver.java +++ b/wsmaster/che-core-api-factory/src/main/java/org/eclipse/che/api/factory/server/RawDevfileUrlFactoryParameterResolver.java @@ -13,7 +13,7 @@ import static com.google.common.base.Strings.isNullOrEmpty; import static java.lang.String.format; -import static java.util.stream.Collectors.toMap; +import static org.eclipse.che.api.factory.server.FactoryResolverPriority.HIGHEST; import static org.eclipse.che.api.factory.shared.Constants.URL_PARAMETER_NAME; import jakarta.validation.constraints.NotNull; @@ -21,14 +21,8 @@ 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 org.eclipse.che.api.core.ApiException; import org.eclipse.che.api.core.BadRequestException; import org.eclipse.che.api.factory.server.urlfactory.DefaultFactoryUrl; @@ -37,18 +31,13 @@ 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; /** * {@link FactoryParametersResolver} implementation to resolve factory based on url parameter as a * direct URL to a devfile content. Extracts and applies devfile values override parameters. */ -@Singleton public class RawDevfileUrlFactoryParameterResolver implements FactoryParametersResolver { - private static final String OVERRIDE_PREFIX = "override."; - protected final URLFactoryBuilder urlFactoryBuilder; protected final URLFetcher urlFetcher; @@ -109,36 +98,8 @@ public RemoteFactoryUrl parseFactoryUrl(String factoryUrl) throws ApiException { throw new ApiException("Operation is not supported"); } - /** - * Finds and returns devfile override parameters in general factory parameters map. - * - * @param factoryParameters map containing factory data parameters provided through URL - * @return filtered devfile values override map - */ - protected Map extractOverrideParams(Map factoryParameters) { - return factoryParameters.entrySet().stream() - .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); - } + @Override + public FactoryResolverPriority priority() { + return HIGHEST; } } diff --git a/wsmaster/che-core-api-factory/src/main/java/org/eclipse/che/api/factory/server/scm/AuthorizingFileContentProvider.java b/wsmaster/che-core-api-factory/src/main/java/org/eclipse/che/api/factory/server/scm/AuthorizingFileContentProvider.java index 3cae6931813..1bb4fbcfa19 100644 --- a/wsmaster/che-core-api-factory/src/main/java/org/eclipse/che/api/factory/server/scm/AuthorizingFileContentProvider.java +++ b/wsmaster/che-core-api-factory/src/main/java/org/eclipse/che/api/factory/server/scm/AuthorizingFileContentProvider.java @@ -92,7 +92,7 @@ private String fetchContent( return urlFetcher.fetch(requestURL, authorization); } } catch (UnknownScmProviderException e) { - return fetchContentWithoutToken(requestURL, e); + return fetchContentWithoutToken(requestURL); } catch (ScmCommunicationException e) { return toIOException(fileURL, e); } catch (ScmUnauthorizedException @@ -102,7 +102,7 @@ private String fetchContent( } } - protected String fetchContentWithoutToken(String requestURL, UnknownScmProviderException e) + protected String fetchContentWithoutToken(String requestURL) throws DevfileException, IOException { // we don't have any provider matching this SCM provider // so try without secrets being configured @@ -123,7 +123,7 @@ protected String fetchContentWithoutToken(String requestURL, UnknownScmProviderE } } throw new DevfileException( - String.format("%s: %s", e.getMessage(), exception.getMessage()), exception); + "Could not reach devfile at " + "`" + exception.getMessage() + "`", exception); } } diff --git a/wsmaster/che-core-api-factory/src/test/java/org/eclipse/che/api/factory/server/FactoryServiceTest.java b/wsmaster/che-core-api-factory/src/test/java/org/eclipse/che/api/factory/server/FactoryServiceTest.java index 77157764afd..44046b990f8 100644 --- a/wsmaster/che-core-api-factory/src/test/java/org/eclipse/che/api/factory/server/FactoryServiceTest.java +++ b/wsmaster/che-core-api-factory/src/test/java/org/eclipse/che/api/factory/server/FactoryServiceTest.java @@ -15,6 +15,9 @@ import static jakarta.ws.rs.core.Response.Status.BAD_REQUEST; import static java.lang.String.valueOf; import static java.util.Collections.singletonMap; +import static org.eclipse.che.api.factory.server.FactoryResolverPriority.DEFAULT; +import static org.eclipse.che.api.factory.server.FactoryResolverPriority.HIGHEST; +import static org.eclipse.che.api.factory.server.FactoryResolverPriority.LOWEST; import static org.eclipse.che.api.factory.server.FactoryService.VALIDATE_QUERY_PARAMETER; import static org.eclipse.che.api.factory.shared.Constants.CURRENT_VERSION; import static org.eclipse.che.api.factory.shared.Constants.URL_PARAMETER_NAME; @@ -35,40 +38,27 @@ import static org.testng.Assert.assertEquals; import static org.testng.Assert.assertTrue; -import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import io.restassured.http.ContentType; import io.restassured.response.Response; -import java.io.IOException; -import java.util.ArrayList; +import java.lang.reflect.Field; import java.util.HashMap; -import java.util.List; +import java.util.HashSet; import java.util.Map; +import java.util.Set; import org.eclipse.che.api.core.BadRequestException; import org.eclipse.che.api.core.model.user.User; -import org.eclipse.che.api.core.model.workspace.WorkspaceConfig; -import org.eclipse.che.api.core.model.workspace.config.ProjectConfig; import org.eclipse.che.api.core.rest.ApiExceptionMapper; import org.eclipse.che.api.core.rest.shared.dto.ServiceError; import org.eclipse.che.api.factory.server.FactoryService.FactoryParametersResolverHolder; import org.eclipse.che.api.factory.server.builder.FactoryBuilder; import org.eclipse.che.api.factory.server.impl.SourceStorageParametersValidator; -import org.eclipse.che.api.factory.server.model.impl.AuthorImpl; -import org.eclipse.che.api.factory.server.model.impl.FactoryImpl; import org.eclipse.che.api.factory.server.scm.PersonalAccessTokenManager; import org.eclipse.che.api.factory.server.urlfactory.RemoteFactoryUrl; import org.eclipse.che.api.factory.shared.dto.FactoryDto; import org.eclipse.che.api.user.server.PreferenceManager; import org.eclipse.che.api.user.server.UserManager; import org.eclipse.che.api.user.server.model.impl.UserImpl; -import org.eclipse.che.api.workspace.server.model.impl.EnvironmentImpl; -import org.eclipse.che.api.workspace.server.model.impl.MachineConfigImpl; -import org.eclipse.che.api.workspace.server.model.impl.ProjectConfigImpl; -import org.eclipse.che.api.workspace.server.model.impl.RecipeImpl; -import org.eclipse.che.api.workspace.server.model.impl.ServerConfigImpl; -import org.eclipse.che.api.workspace.server.model.impl.SourceStorageImpl; -import org.eclipse.che.api.workspace.server.model.impl.WorkspaceConfigImpl; -import org.eclipse.che.api.workspace.shared.dto.EnvironmentDto; import org.eclipse.che.commons.env.EnvironmentContext; import org.eclipse.che.commons.subject.SubjectImpl; import org.eclipse.che.dto.server.DtoFactory; @@ -111,6 +101,7 @@ public class FactoryServiceTest { @Mock private PersonalAccessTokenManager personalAccessTokenManager; @InjectMocks private FactoryParametersResolverHolder factoryParametersResolverHolder; + private Set specificFactoryParametersResolvers; private FactoryBuilder factoryBuilderSpy; @@ -126,6 +117,13 @@ public class FactoryServiceTest { @BeforeMethod public void setUp() throws Exception { + specificFactoryParametersResolvers = new HashSet<>(); + Field parametersResolvers = + FactoryParametersResolverHolder.class.getDeclaredField( + "specificFactoryParametersResolvers"); + parametersResolvers.setAccessible(true); + parametersResolvers.set(factoryParametersResolverHolder, specificFactoryParametersResolvers); + specificFactoryParametersResolvers.add(rawDevfileUrlFactoryParameterResolver); factoryBuilderSpy = spy(new FactoryBuilder(new SourceStorageParametersValidator())); lenient().doNothing().when(factoryBuilderSpy).checkValid(any(FactoryDto.class)); lenient().doNothing().when(factoryBuilderSpy).checkValid(any(FactoryDto.class), anyBoolean()); @@ -284,62 +282,71 @@ public void shouldReturnDefaultFactoryParameterResolver() throws Exception { .startsWith(RawDevfileUrlFactoryParameterResolver.class.getName())); } - private FactoryImpl createFactory() { - return createNamedFactory(FACTORY_NAME); - } + @Test + public void shouldReturnTopPriorityFactoryParameterResolverOverLowPriority() throws Exception { + // given + Map params = singletonMap(URL_PARAMETER_NAME, "https://host/path/devfile.yaml"); + specificFactoryParametersResolvers.clear(); + FactoryParametersResolver topPriorityResolver = mock(FactoryParametersResolver.class); + FactoryParametersResolver lowPriorityResolver = mock(FactoryParametersResolver.class); + when(topPriorityResolver.accept(eq(params))).thenReturn(true); + when(lowPriorityResolver.accept(eq(params))).thenReturn(true); + when(topPriorityResolver.priority()).thenReturn(HIGHEST); + when(lowPriorityResolver.priority()).thenReturn(LOWEST); + specificFactoryParametersResolvers.add(topPriorityResolver); + specificFactoryParametersResolvers.add(lowPriorityResolver); - private FactoryImpl createNamedFactory(String name) { - return createFactoryWithStorage(name, PROJECT_SOURCE_TYPE, PROJECT_SOURCE_LOCATION); - } + // when + FactoryParametersResolver factoryParametersResolver = + factoryParametersResolverHolder.getFactoryParametersResolver(params); - private FactoryImpl createFactoryWithStorage(String name, String type, String location) { - return FactoryImpl.builder() - .setId(FACTORY_ID) - .setVersion("4.0") - .setWorkspace(createWorkspaceConfig(type, location)) - .setCreator(new AuthorImpl(USER_ID, 12L)) - .setName(name) - .build(); + // then + assertEquals(factoryParametersResolver, topPriorityResolver); } - private static WorkspaceConfig createWorkspaceConfig(String type, String location) { - return WorkspaceConfigImpl.builder() - .setName(WORKSPACE_NAME) - .setEnvironments(singletonMap("env1", new EnvironmentImpl(createEnvDto()))) - .setProjects(createProjects(type, location)) - .build(); - } + @Test + public void shouldReturnTopPriorityFactoryParameterResolverOverDefaultPriority() + throws Exception { + // given + Map params = singletonMap(URL_PARAMETER_NAME, "https://host/path/devfile.yaml"); + specificFactoryParametersResolvers.clear(); + FactoryParametersResolver topPriorityResolver = mock(FactoryParametersResolver.class); + FactoryParametersResolver defaultPriorityResolver = mock(FactoryParametersResolver.class); + when(topPriorityResolver.accept(eq(params))).thenReturn(true); + when(defaultPriorityResolver.accept(eq(params))).thenReturn(true); + when(topPriorityResolver.priority()).thenReturn(HIGHEST); + when(defaultPriorityResolver.priority()).thenReturn(DEFAULT); + specificFactoryParametersResolvers.add(topPriorityResolver); + specificFactoryParametersResolvers.add(defaultPriorityResolver); - private static EnvironmentDto createEnvDto() { - final RecipeImpl environmentRecipe = new RecipeImpl(); - environmentRecipe.setType("type"); - environmentRecipe.setContent("content"); - environmentRecipe.setContentType("compose"); - environmentRecipe.setLocation("location"); - final EnvironmentImpl env = new EnvironmentImpl(); - final MachineConfigImpl extendedMachine = new MachineConfigImpl(); - extendedMachine.setAttributes(singletonMap("att1", "value")); - extendedMachine.setServers( - singletonMap( - "agent", new ServerConfigImpl("5555", "https", "path", singletonMap("key", "value")))); - env.setRecipe(environmentRecipe); - env.setMachines(singletonMap("machine1", extendedMachine)); - return org.eclipse.che.api.workspace.server.DtoConverter.asDto(env); - } + // when + FactoryParametersResolver factoryParametersResolver = + factoryParametersResolverHolder.getFactoryParametersResolver(params); - private static List createProjects(String type, String location) { - final ProjectConfigImpl projectConfig = new ProjectConfigImpl(); - projectConfig.setSource(new SourceStorageImpl(type, location, null)); - return ImmutableList.of(projectConfig); + // then + assertEquals(factoryParametersResolver, topPriorityResolver); } - private static T getFromResponse(Response response, Class clazz) throws Exception { - return DTO.createDtoFromJson(response.getBody().asInputStream(), clazz); - } + @Test + public void shouldReturnDefaultPriorityFactoryParameterResolverOverLowPriority() + throws Exception { + // given + Map params = singletonMap(URL_PARAMETER_NAME, "https://host/path/devfile.yaml"); + specificFactoryParametersResolvers.clear(); + FactoryParametersResolver lowPriorityResolver = mock(FactoryParametersResolver.class); + FactoryParametersResolver defaultPriorityResolver = mock(FactoryParametersResolver.class); + when(lowPriorityResolver.accept(eq(params))).thenReturn(true); + when(defaultPriorityResolver.accept(eq(params))).thenReturn(true); + when(lowPriorityResolver.priority()).thenReturn(LOWEST); + when(defaultPriorityResolver.priority()).thenReturn(DEFAULT); + specificFactoryParametersResolvers.add(lowPriorityResolver); + specificFactoryParametersResolvers.add(defaultPriorityResolver); + + // when + FactoryParametersResolver factoryParametersResolver = + factoryParametersResolverHolder.getFactoryParametersResolver(params); - private static List unwrapDtoList(Response response, Class dtoClass) - throws IOException { - return new ArrayList<>( - DtoFactory.getInstance().createListDtoFromJson(response.body().asInputStream(), dtoClass)); + // then + assertEquals(factoryParametersResolver, defaultPriorityResolver); } } diff --git a/wsmaster/pom.xml b/wsmaster/pom.xml index 01255211c63..6cda4419fa4 100644 --- a/wsmaster/pom.xml +++ b/wsmaster/pom.xml @@ -40,6 +40,7 @@ che-core-api-account che-core-api-user che-core-api-factory-azure-devops + che-core-api-factory-git-ssh che-core-api-factory-shared che-core-api-factory che-core-api-factory-github