From 64e81135d7c240ad8f2ef74b8b84d4b922a71427 Mon Sep 17 00:00:00 2001 From: Wojtek Wajerowicz <115081248+wojciech-wajerowicz-sonarsource@users.noreply.github.com> Date: Thu, 21 Mar 2024 16:58:36 +0100 Subject: [PATCH] SONAR-21819 Add DevOpsPlatformCreator for BitBucket Cloud. --- .../almsettings/DevOpsProjectDescriptor.java | 3 +- .../BitbucketCloudProjectCreator.java | 119 ++++++++++ .../BitbucketCloudProjectCreatorFactory.java | 72 ++++++ .../bitbucketcloud/package-info.java | 23 ++ .../common/project/ImportProjectService.java | 4 +- ...tbucketCloudProjectCreatorFactoryTest.java | 80 +++++++ .../BitbucketCloudProjectCreatorTest.java | 205 ++++++++++++++++++ .../gitlab/GitlabProjectCreatorTest.java | 47 ++-- .../BoundProjectCreateRestRequest.java | 7 +- .../ImportBitbucketCloudRepoActionIT.java | 29 ++- .../ImportBitbucketCloudRepoAction.java | 122 +---------- .../platformlevel/PlatformLevel4.java | 2 + 12 files changed, 563 insertions(+), 150 deletions(-) create mode 100644 server/sonar-webserver-common/src/main/java/org/sonar/server/common/almsettings/bitbucketcloud/BitbucketCloudProjectCreator.java create mode 100644 server/sonar-webserver-common/src/main/java/org/sonar/server/common/almsettings/bitbucketcloud/BitbucketCloudProjectCreatorFactory.java create mode 100644 server/sonar-webserver-common/src/main/java/org/sonar/server/common/almsettings/bitbucketcloud/package-info.java create mode 100644 server/sonar-webserver-common/src/test/java/org/sonar/server/common/almsettings/bitbucketcloud/BitbucketCloudProjectCreatorFactoryTest.java create mode 100644 server/sonar-webserver-common/src/test/java/org/sonar/server/common/almsettings/bitbucketcloud/BitbucketCloudProjectCreatorTest.java diff --git a/server/sonar-webserver-common/src/main/java/org/sonar/server/common/almsettings/DevOpsProjectDescriptor.java b/server/sonar-webserver-common/src/main/java/org/sonar/server/common/almsettings/DevOpsProjectDescriptor.java index 4459ccbbf09..55ff967e7e5 100644 --- a/server/sonar-webserver-common/src/main/java/org/sonar/server/common/almsettings/DevOpsProjectDescriptor.java +++ b/server/sonar-webserver-common/src/main/java/org/sonar/server/common/almsettings/DevOpsProjectDescriptor.java @@ -19,7 +19,8 @@ */ package org.sonar.server.common.almsettings; +import javax.annotation.Nullable; import org.sonar.db.alm.setting.ALM; -public record DevOpsProjectDescriptor(ALM alm, String url, String projectIdentifier) { +public record DevOpsProjectDescriptor(ALM alm, @Nullable String url, String projectIdentifier) { } diff --git a/server/sonar-webserver-common/src/main/java/org/sonar/server/common/almsettings/bitbucketcloud/BitbucketCloudProjectCreator.java b/server/sonar-webserver-common/src/main/java/org/sonar/server/common/almsettings/bitbucketcloud/BitbucketCloudProjectCreator.java new file mode 100644 index 00000000000..e7221d09565 --- /dev/null +++ b/server/sonar-webserver-common/src/main/java/org/sonar/server/common/almsettings/bitbucketcloud/BitbucketCloudProjectCreator.java @@ -0,0 +1,119 @@ +/* + * SonarQube + * Copyright (C) 2009-2024 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.server.common.almsettings.bitbucketcloud; + +import java.util.Optional; +import org.jetbrains.annotations.Nullable; +import org.sonar.alm.client.bitbucket.bitbucketcloud.BitbucketCloudRestClient; +import org.sonar.alm.client.bitbucket.bitbucketcloud.Repository; +import org.sonar.db.DbClient; +import org.sonar.db.DbSession; +import org.sonar.db.alm.pat.AlmPatDto; +import org.sonar.db.alm.setting.AlmSettingDto; +import org.sonar.db.alm.setting.ProjectAlmSettingDto; +import org.sonar.db.project.CreationMethod; +import org.sonar.db.project.ProjectDto; +import org.sonar.server.common.almintegration.ProjectKeyGenerator; +import org.sonar.server.common.almsettings.DevOpsProjectCreator; +import org.sonar.server.common.almsettings.DevOpsProjectDescriptor; +import org.sonar.server.common.project.ProjectCreator; +import org.sonar.server.component.ComponentCreationData; +import org.sonar.server.user.UserSession; + +import static java.lang.String.format; +import static java.util.Objects.requireNonNull; +import static java.util.Optional.ofNullable; + +public class BitbucketCloudProjectCreator implements DevOpsProjectCreator { + + private final DbClient dbClient; + private final AlmSettingDto almSettingDto; + private final DevOpsProjectDescriptor devOpsProjectDescriptor; + private final UserSession userSession; + private final BitbucketCloudRestClient bitbucketCloudRestClient; + private final ProjectCreator projectCreator; + private final ProjectKeyGenerator projectKeyGenerator; + + + public BitbucketCloudProjectCreator(DbClient dbClient, AlmSettingDto almSettingDto, DevOpsProjectDescriptor devOpsProjectDescriptor, UserSession userSession, + BitbucketCloudRestClient bitbucketCloudRestClient, ProjectCreator projectCreator, ProjectKeyGenerator projectKeyGenerator) { + this.dbClient = dbClient; + this.almSettingDto = almSettingDto; + this.devOpsProjectDescriptor = devOpsProjectDescriptor; + this.userSession = userSession; + this.bitbucketCloudRestClient = bitbucketCloudRestClient; + this.projectCreator = projectCreator; + this.projectKeyGenerator = projectKeyGenerator; + } + + @Override + public boolean isScanAllowedUsingPermissionsFromDevopsPlatform() { + throw new UnsupportedOperationException("Not Implemented"); + } + + @Override + public ComponentCreationData createProjectAndBindToDevOpsPlatform(DbSession dbSession, CreationMethod creationMethod, Boolean monorepo, @Nullable String projectKey, + @Nullable String projectName) { + + String pat = findPersonalAccessTokenOrThrow(dbSession, almSettingDto); + String workspace = ofNullable(almSettingDto.getAppId()) + .orElseThrow(() -> new IllegalArgumentException(String.format("workspace for alm setting %s is missing", almSettingDto.getKey()))); + + Repository repo = bitbucketCloudRestClient.getRepo(pat, workspace, devOpsProjectDescriptor.projectIdentifier()); + + ComponentCreationData componentCreationData = projectCreator.createProject( + dbSession, + getProjectKey(workspace, projectKey, repo), + getProjectName(projectName, repo), + repo.getMainBranch().getName(), + creationMethod); + ProjectDto projectDto = Optional.ofNullable(componentCreationData.projectDto()).orElseThrow(); + + createProjectAlmSettingDto(dbSession, repo.getSlug(), projectDto, almSettingDto, monorepo); + return componentCreationData; + } + + private String findPersonalAccessTokenOrThrow(DbSession dbSession, AlmSettingDto almSettingDto) { + String userUuid = requireNonNull(userSession.getUuid(), "User UUID cannot be null."); + Optional almPatDto = dbClient.almPatDao().selectByUserAndAlmSetting(dbSession, userUuid, almSettingDto); + return almPatDto.map(AlmPatDto::getPersonalAccessToken) + .orElseThrow(() -> new IllegalArgumentException(format("personal access token for '%s' is missing", almSettingDto.getKey()))); + } + + private String getProjectKey(String workspace, @Nullable String projectKey, Repository repository) { + return Optional.ofNullable(projectKey).orElseGet(() -> projectKeyGenerator.generateUniqueProjectKey(workspace, repository.getSlug())); + } + + private static String getProjectName(@Nullable String projectName, Repository repository) { + return Optional.ofNullable(projectName).orElse(repository.getName()); + } + + private void createProjectAlmSettingDto(DbSession dbSession, String repoSlug, ProjectDto projectDto, AlmSettingDto almSettingDto, Boolean monorepo) { + ProjectAlmSettingDto projectAlmSettingDto = new ProjectAlmSettingDto() + .setAlmSettingUuid(almSettingDto.getUuid()) + .setAlmRepo(repoSlug) + .setAlmSlug(null) + .setProjectUuid(projectDto.getUuid()) + .setSummaryCommentEnabled(true) + .setMonorepo(monorepo); + dbClient.projectAlmSettingDao().insertOrUpdate(dbSession, projectAlmSettingDto, almSettingDto.getKey(), projectDto.getName(), projectDto.getKey()); + } + +} diff --git a/server/sonar-webserver-common/src/main/java/org/sonar/server/common/almsettings/bitbucketcloud/BitbucketCloudProjectCreatorFactory.java b/server/sonar-webserver-common/src/main/java/org/sonar/server/common/almsettings/bitbucketcloud/BitbucketCloudProjectCreatorFactory.java new file mode 100644 index 00000000000..bc5c9696c76 --- /dev/null +++ b/server/sonar-webserver-common/src/main/java/org/sonar/server/common/almsettings/bitbucketcloud/BitbucketCloudProjectCreatorFactory.java @@ -0,0 +1,72 @@ +/* + * SonarQube + * Copyright (C) 2009-2024 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.server.common.almsettings.bitbucketcloud; + +import java.util.Map; +import java.util.Optional; +import org.sonar.alm.client.bitbucket.bitbucketcloud.BitbucketCloudRestClient; +import org.sonar.db.DbClient; +import org.sonar.db.DbSession; +import org.sonar.db.alm.setting.ALM; +import org.sonar.db.alm.setting.AlmSettingDto; +import org.sonar.server.common.almintegration.ProjectKeyGenerator; +import org.sonar.server.common.almsettings.DevOpsProjectCreator; +import org.sonar.server.common.almsettings.DevOpsProjectCreatorFactory; +import org.sonar.server.common.almsettings.DevOpsProjectDescriptor; +import org.sonar.server.common.project.ProjectCreator; +import org.sonar.server.user.UserSession; + +public class BitbucketCloudProjectCreatorFactory implements DevOpsProjectCreatorFactory { + private final DbClient dbClient; + private final UserSession userSession; + private final BitbucketCloudRestClient bitbucketCloudRestClient; + private final ProjectCreator projectCreator; + private final ProjectKeyGenerator projectKeyGenerator; + + public BitbucketCloudProjectCreatorFactory(DbClient dbClient, UserSession userSession, BitbucketCloudRestClient bitbucketCloudRestClient, ProjectCreator projectCreator, + ProjectKeyGenerator projectKeyGenerator) { + this.dbClient = dbClient; + this.userSession = userSession; + this.bitbucketCloudRestClient = bitbucketCloudRestClient; + this.projectCreator = projectCreator; + this.projectKeyGenerator = projectKeyGenerator; + } + + @Override + public Optional getDevOpsProjectCreator(DbSession dbSession, Map characteristics) { + return Optional.empty(); + } + + @Override + public Optional getDevOpsProjectCreator(AlmSettingDto almSettingDto, DevOpsProjectDescriptor devOpsProjectDescriptor) { + if (almSettingDto.getAlm() != ALM.BITBUCKET_CLOUD) { + return Optional.empty(); + } + return Optional.of( + new BitbucketCloudProjectCreator( + dbClient, + almSettingDto, + devOpsProjectDescriptor, + userSession, + bitbucketCloudRestClient, + projectCreator, + projectKeyGenerator)); + } +} diff --git a/server/sonar-webserver-common/src/main/java/org/sonar/server/common/almsettings/bitbucketcloud/package-info.java b/server/sonar-webserver-common/src/main/java/org/sonar/server/common/almsettings/bitbucketcloud/package-info.java new file mode 100644 index 00000000000..b22f4835f3c --- /dev/null +++ b/server/sonar-webserver-common/src/main/java/org/sonar/server/common/almsettings/bitbucketcloud/package-info.java @@ -0,0 +1,23 @@ +/* + * SonarQube + * Copyright (C) 2009-2024 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +@ParametersAreNonnullByDefault +package org.sonar.server.common.almsettings.bitbucketcloud; + +import javax.annotation.ParametersAreNonnullByDefault; diff --git a/server/sonar-webserver-common/src/main/java/org/sonar/server/common/project/ImportProjectService.java b/server/sonar-webserver-common/src/main/java/org/sonar/server/common/project/ImportProjectService.java index 30150205d20..feaab35332b 100644 --- a/server/sonar-webserver-common/src/main/java/org/sonar/server/common/project/ImportProjectService.java +++ b/server/sonar-webserver-common/src/main/java/org/sonar/server/common/project/ImportProjectService.java @@ -37,7 +37,6 @@ import org.sonar.server.component.ComponentCreationData; import org.sonar.server.user.UserSession; import static java.lang.String.format; -import static java.util.Objects.requireNonNull; import static org.sonar.db.project.CreationMethod.getCreationMethod; import static org.sonar.db.project.CreationMethod.Category.ALM_IMPORT; import static org.sonar.server.common.newcodeperiod.NewCodeDefinitionResolver.checkNewCodeDefinitionParam; @@ -63,8 +62,7 @@ public class ImportProjectService { try (DbSession dbSession = dbClient.openSession(false)) { checkNewCodeDefinitionParam(request.newCodeDefinitionType(), request.newCodeDefinitionValue()); AlmSettingDto almSetting = dbClient.almSettingDao().selectByUuid(dbSession, request.almSettingId()).orElseThrow(() -> new IllegalArgumentException("ALM setting not found")); - String almUrl = requireNonNull(almSetting.getUrl()); - DevOpsProjectDescriptor projectDescriptor = new DevOpsProjectDescriptor(almSetting.getAlm(), almUrl, request.repositoryIdentifier()); + DevOpsProjectDescriptor projectDescriptor = new DevOpsProjectDescriptor(almSetting.getAlm(), almSetting.getUrl(), request.repositoryIdentifier()); DevOpsProjectCreator projectCreator = devOpsProjectCreatorFactory.getDevOpsProjectCreator(almSetting, projectDescriptor) .orElseThrow(() -> new IllegalArgumentException(format("Platform %s not supported", almSetting.getAlm().name()))); diff --git a/server/sonar-webserver-common/src/test/java/org/sonar/server/common/almsettings/bitbucketcloud/BitbucketCloudProjectCreatorFactoryTest.java b/server/sonar-webserver-common/src/test/java/org/sonar/server/common/almsettings/bitbucketcloud/BitbucketCloudProjectCreatorFactoryTest.java new file mode 100644 index 00000000000..34e63761ef1 --- /dev/null +++ b/server/sonar-webserver-common/src/test/java/org/sonar/server/common/almsettings/bitbucketcloud/BitbucketCloudProjectCreatorFactoryTest.java @@ -0,0 +1,80 @@ +/* + * SonarQube + * Copyright (C) 2009-2024 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.server.common.almsettings.bitbucketcloud; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.sonar.alm.client.bitbucket.bitbucketcloud.BitbucketCloudRestClient; +import org.sonar.db.DbClient; +import org.sonar.db.alm.setting.ALM; +import org.sonar.db.alm.setting.AlmSettingDto; +import org.sonar.server.common.almintegration.ProjectKeyGenerator; +import org.sonar.server.common.almsettings.DevOpsProjectCreator; +import org.sonar.server.common.almsettings.DevOpsProjectDescriptor; +import org.sonar.server.common.project.ProjectCreator; +import org.sonar.server.user.UserSession; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +@ExtendWith(MockitoExtension.class) +class BitbucketCloudProjectCreatorFactoryTest { + + @Mock + private DbClient dbClient; + @Mock + private UserSession userSession; + @Mock + private BitbucketCloudRestClient bitbucketCloudRestClient; + @Mock + private ProjectCreator projectCreator; + @Mock + private ProjectKeyGenerator projectKeyGenerator; + + @InjectMocks + private BitbucketCloudProjectCreatorFactory underTest; + + @Test + void getDevOpsProjectCreator_whenAlmIsNotBitbucketCloud_shouldReturnEmpty() { + AlmSettingDto almSettingDto = mock(AlmSettingDto.class); + when(almSettingDto.getAlm()).thenReturn(ALM.GITLAB); + DevOpsProjectDescriptor devOpsProjectDescriptor = new DevOpsProjectDescriptor(ALM.GITLAB, null, "bitbucket_project"); + + assertThat(underTest.getDevOpsProjectCreator(almSettingDto, devOpsProjectDescriptor)).isEmpty(); + } + + @Test + void getDevOpsProjectCreator_whenAlmItBitbucketCloud_shouldReturnProjectCreator() { + AlmSettingDto almSettingDto = mock(AlmSettingDto.class); + when(almSettingDto.getAlm()).thenReturn(ALM.BITBUCKET_CLOUD); + DevOpsProjectDescriptor devOpsProjectDescriptor = new DevOpsProjectDescriptor(ALM.BITBUCKET_CLOUD, null, "bitbucket_project"); + + DevOpsProjectCreator expectedProjectCreator = new BitbucketCloudProjectCreator(dbClient, almSettingDto, devOpsProjectDescriptor, userSession, bitbucketCloudRestClient, + projectCreator, projectKeyGenerator); + DevOpsProjectCreator devOpsProjectCreator = underTest.getDevOpsProjectCreator(almSettingDto, devOpsProjectDescriptor).orElseThrow(); + + assertThat(devOpsProjectCreator).usingRecursiveComparison().isEqualTo(expectedProjectCreator); + } + +} diff --git a/server/sonar-webserver-common/src/test/java/org/sonar/server/common/almsettings/bitbucketcloud/BitbucketCloudProjectCreatorTest.java b/server/sonar-webserver-common/src/test/java/org/sonar/server/common/almsettings/bitbucketcloud/BitbucketCloudProjectCreatorTest.java new file mode 100644 index 00000000000..92d73e8f7ae --- /dev/null +++ b/server/sonar-webserver-common/src/test/java/org/sonar/server/common/almsettings/bitbucketcloud/BitbucketCloudProjectCreatorTest.java @@ -0,0 +1,205 @@ +/* + * SonarQube + * Copyright (C) 2009-2024 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.server.common.almsettings.bitbucketcloud; + +import java.util.Optional; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Answers; +import org.mockito.ArgumentCaptor; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.sonar.alm.client.bitbucket.bitbucketcloud.BitbucketCloudRestClient; +import org.sonar.alm.client.bitbucket.bitbucketcloud.Repository; +import org.sonar.db.DbClient; +import org.sonar.db.DbSession; +import org.sonar.db.alm.pat.AlmPatDto; +import org.sonar.db.alm.setting.ALM; +import org.sonar.db.alm.setting.AlmSettingDto; +import org.sonar.db.alm.setting.ProjectAlmSettingDto; +import org.sonar.db.project.CreationMethod; +import org.sonar.db.project.ProjectDto; +import org.sonar.server.common.almintegration.ProjectKeyGenerator; +import org.sonar.server.common.almsettings.DevOpsProjectDescriptor; +import org.sonar.server.common.project.ProjectCreator; +import org.sonar.server.component.ComponentCreationData; +import org.sonar.server.user.UserSession; + +import static org.assertj.core.api.AssertionsForClassTypes.assertThat; +import static org.assertj.core.api.AssertionsForClassTypes.assertThatExceptionOfType; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.lenient; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +@ExtendWith(MockitoExtension.class) +class BitbucketCloudProjectCreatorTest { + + private static final String USER_LOGIN = "userLogin"; + private static final String USER_UUID = "userUuid"; + private static final String REPOSITORY_SLUG = "projectSlug"; + private static final String REPOSITORY_NAME = "repositoryName"; + private static final String ALM_SETTING_KEY = "bitbucketcloud_config_1"; + private static final String ALM_SETTING_UUID = "almSettingUuid"; + private static final String USER_PAT = "1234"; + private static final String PROJECT_UUID = "projectUuid"; + private static final String WORKSPACE = "workspace"; + private static final String MAIN_BRANCH_NAME = "defaultBranch"; + + @Mock(answer = Answers.RETURNS_DEEP_STUBS) + private DbClient dbClient; + @Mock + private AlmSettingDto almSettingDto; + @Mock + private DevOpsProjectDescriptor devOpsProjectDescriptor; + @Mock + private UserSession userSession; + @Mock + private BitbucketCloudRestClient bitbucketCloudRestClient; + @Mock + private ProjectCreator projectCreator; + @Mock + private ProjectKeyGenerator projectKeyGenerator; + + @InjectMocks + private BitbucketCloudProjectCreator underTest; + + @BeforeEach + void setup() { + lenient().when(userSession.getLogin()).thenReturn(USER_LOGIN); + lenient().when(userSession.getUuid()).thenReturn(USER_UUID); + + lenient().when(almSettingDto.getKey()).thenReturn(ALM_SETTING_KEY); + lenient().when(almSettingDto.getUuid()).thenReturn(ALM_SETTING_UUID); + + lenient().when(devOpsProjectDescriptor.projectIdentifier()).thenReturn(REPOSITORY_SLUG); + lenient().when(devOpsProjectDescriptor.alm()).thenReturn(ALM.BITBUCKET_CLOUD); + } + + @Test + void isScanAllowedUsingPermissionsFromDevopsPlatform_shouldThrowUnsupportedOperationException() { + assertThatExceptionOfType(UnsupportedOperationException.class) + .isThrownBy(() -> underTest.isScanAllowedUsingPermissionsFromDevopsPlatform()) + .withMessage("Not Implemented"); + } + + @Test + void createProjectAndBindToDevOpsPlatform_whenPatIsMissing_shouldThrow() { + assertThatExceptionOfType(IllegalArgumentException.class) + .isThrownBy(() -> underTest.createProjectAndBindToDevOpsPlatform(mock(DbSession.class), CreationMethod.ALM_IMPORT_API, false, null, null)) + .withMessage("personal access token for 'bitbucketcloud_config_1' is missing"); + } + + @Test + void createProjectAndBindToDevOpsPlatform_whenWorkspaceIsNotDefined_shouldThrow() { + mockPatForUser(); + assertThatExceptionOfType(IllegalArgumentException.class) + .isThrownBy(() -> underTest.createProjectAndBindToDevOpsPlatform(mock(DbSession.class), CreationMethod.ALM_IMPORT_API, false, null, null)) + .withMessage("workspace for alm setting bitbucketcloud_config_1 is missing"); + } + + @Test + void createProjectAndBindToDevOpsPlatform_whenRepositoryNotFound_shouldThrow() { + mockPatForUser(); + when(almSettingDto.getAppId()).thenReturn("workspace"); + when(bitbucketCloudRestClient.getRepo(USER_PAT, "workspace", REPOSITORY_SLUG)).thenThrow(new IllegalStateException("Problem fetching repository from Bitbucket Cloud")); + assertThatExceptionOfType(IllegalStateException.class) + .isThrownBy(() -> underTest.createProjectAndBindToDevOpsPlatform(mock(DbSession.class), CreationMethod.ALM_IMPORT_API, false, null, null)) + .withMessage("Problem fetching repository from Bitbucket Cloud"); + } + + @Test + void createProjectAndBindToDevOpsPlatform_whenRepoFoundOnBitbucket_successfullyCreatesProject() { + mockPatForUser(); + + when(almSettingDto.getAppId()).thenReturn(WORKSPACE); + + mockBitbucketCloudRepository(); + mockProjectCreation("projectKey", "projectName"); + + underTest.createProjectAndBindToDevOpsPlatform(mock(DbSession.class), CreationMethod.ALM_IMPORT_API, true, "projectKey", "projectName"); + + ArgumentCaptor projectAlmSettingCaptor = ArgumentCaptor.forClass(ProjectAlmSettingDto.class); + + verify(dbClient.projectAlmSettingDao()).insertOrUpdate(any(), projectAlmSettingCaptor.capture(), eq(ALM_SETTING_KEY), eq("projectName"), eq("projectKey")); + + ProjectAlmSettingDto createdProjectAlmSettingDto = projectAlmSettingCaptor.getValue(); + + assertThat(createdProjectAlmSettingDto.getAlmSettingUuid()).isEqualTo(ALM_SETTING_UUID); + assertThat(createdProjectAlmSettingDto.getAlmRepo()).isEqualTo(REPOSITORY_SLUG); + assertThat(createdProjectAlmSettingDto.getProjectUuid()).isEqualTo(PROJECT_UUID); + assertThat(createdProjectAlmSettingDto.getMonorepo()).isTrue(); + } + + @Test + void createProjectAndBindToDevOpsPlatform_whenNoKeyAndNameSpecified_generatesKeyAndUsersBitbucketRepositoryName() { + mockPatForUser(); + + when(almSettingDto.getAppId()).thenReturn(WORKSPACE); + + mockBitbucketCloudRepository(); + String generatedProjectKey = "generatedProjectKey"; + when(projectKeyGenerator.generateUniqueProjectKey(WORKSPACE, REPOSITORY_SLUG)).thenReturn(generatedProjectKey); + mockProjectCreation(generatedProjectKey, REPOSITORY_NAME); + + underTest.createProjectAndBindToDevOpsPlatform(mock(DbSession.class), CreationMethod.ALM_IMPORT_API, true, null, null); + + ArgumentCaptor projectAlmSettingCaptor = ArgumentCaptor.forClass(ProjectAlmSettingDto.class); + + verify(dbClient.projectAlmSettingDao()).insertOrUpdate(any(), projectAlmSettingCaptor.capture(), eq(ALM_SETTING_KEY), eq(REPOSITORY_NAME), eq(generatedProjectKey)); + + ProjectAlmSettingDto createdProjectAlmSettingDto = projectAlmSettingCaptor.getValue(); + + assertThat(createdProjectAlmSettingDto.getAlmSettingUuid()).isEqualTo(ALM_SETTING_UUID); + assertThat(createdProjectAlmSettingDto.getAlmRepo()).isEqualTo(REPOSITORY_SLUG); + assertThat(createdProjectAlmSettingDto.getProjectUuid()).isEqualTo(PROJECT_UUID); + assertThat(createdProjectAlmSettingDto.getMonorepo()).isTrue(); + } + + private void mockPatForUser() { + AlmPatDto almPatDto = mock(); + when(almPatDto.getPersonalAccessToken()).thenReturn(USER_PAT); + when(dbClient.almPatDao().selectByUserAndAlmSetting(any(), eq(USER_UUID), eq(almSettingDto))).thenReturn(Optional.of(almPatDto)); + } + + private void mockBitbucketCloudRepository() { + Repository repository = mock(Repository.class, Answers.RETURNS_DEEP_STUBS); + when(repository.getMainBranch().getName()).thenReturn(MAIN_BRANCH_NAME); + when(repository.getSlug()).thenReturn(REPOSITORY_SLUG); + when(repository.getName()).thenReturn(REPOSITORY_NAME); + when(bitbucketCloudRestClient.getRepo(USER_PAT, WORKSPACE, REPOSITORY_SLUG)).thenReturn(repository); + } + + private void mockProjectCreation(String projectKey, String projectName) { + ComponentCreationData componentCreationData = mock(); + ProjectDto projectDto = mock(); + when(componentCreationData.projectDto()).thenReturn(projectDto); + when(projectDto.getUuid()).thenReturn(PROJECT_UUID); + when(projectDto.getKey()).thenReturn(projectKey); + when(projectDto.getName()).thenReturn(projectName); + when(projectCreator.createProject(any(), eq(projectKey), eq(projectName), eq(MAIN_BRANCH_NAME), eq(CreationMethod.ALM_IMPORT_API))) + .thenReturn(componentCreationData); + } + +} diff --git a/server/sonar-webserver-common/src/test/java/org/sonar/server/common/almsettings/gitlab/GitlabProjectCreatorTest.java b/server/sonar-webserver-common/src/test/java/org/sonar/server/common/almsettings/gitlab/GitlabProjectCreatorTest.java index 76e2d2da308..7e945bc0589 100644 --- a/server/sonar-webserver-common/src/test/java/org/sonar/server/common/almsettings/gitlab/GitlabProjectCreatorTest.java +++ b/server/sonar-webserver-common/src/test/java/org/sonar/server/common/almsettings/gitlab/GitlabProjectCreatorTest.java @@ -60,6 +60,24 @@ import static org.mockito.Mockito.when; class GitlabProjectCreatorTest { private static final String PROJECT_UUID = "projectUuid"; + + private static final String USER_LOGIN = "userLogin"; + private static final String USER_UUID = "userUuid"; + + private static final String REPOSITORY_PATH_WITH_NAMESPACE = "pathWith/namespace"; + + private static final String GITLAB_PROJECT_NAME = "gitlabProjectName"; + + private static final String REPOSITORY_ID = "1234"; + + private static final String MAIN_BRANCH_NAME = "defaultBranch"; + + private static final String ALM_SETTING_KEY = "gitlab_config_1"; + private static final String ALM_SETTING_UUID = "almSettingUuid"; + + private static final String USER_PAT = "1234"; + + public static final String GITLAB_URL = "http://api.com"; @Mock(answer = Answers.RETURNS_DEEP_STUBS) private DbClient dbClient; @@ -81,26 +99,6 @@ class GitlabProjectCreatorTest { @InjectMocks private GitlabProjectCreator underTest; - private static final String USER_LOGIN = "userLogin"; - private static final String USER_UUID = "userUuid"; - - private static final String GROUP_NAME = "group1"; - private static final String REPOSITORY_PATH_WITH_NAMESPACE = "pathWith/namespace"; - - private static final String GITLAB_PROJECT_NAME = "gitlabProjectName"; - - private static final String REPOSITORY_ID = "1234"; - - private static final String MAIN_BRANCH_NAME = "defaultBranch"; - - private static final String ALM_SETTING_KEY = "gitlab_config_1"; - private static final String ALM_SETTING_UUID = "almSettingUuid"; - - private static final String USER_PAT = "1234"; - - public static final String GITLAB_URL = "http://api.com"; - private static final DevOpsProjectDescriptor DEVOPS_PROJECT_DESCRIPTOR = new DevOpsProjectDescriptor(ALM.GITLAB, GITLAB_URL, REPOSITORY_ID); - @BeforeEach void setup() { lenient().when(userSession.getLogin()).thenReturn(USER_LOGIN); @@ -132,7 +130,7 @@ class GitlabProjectCreatorTest { @Test void createProjectAndBindToDevOpsPlatform_whenRepoNotFound_throws() { mockPatForUser(); - when(gitlabApplicationClient.getProject(DEVOPS_PROJECT_DESCRIPTOR.url(), USER_PAT, Long.valueOf(REPOSITORY_ID))).thenThrow(new GitlabServerException(404, "Not found")); + when(gitlabApplicationClient.getProject(GITLAB_URL, USER_PAT, Long.valueOf(REPOSITORY_ID))).thenThrow(new GitlabServerException(404, "Not found")); assertThatExceptionOfType(IllegalStateException.class) .isThrownBy(() -> underTest.createProjectAndBindToDevOpsPlatform(mock(DbSession.class), CreationMethod.ALM_IMPORT_API, false, null, null)) .withMessage("Failed to fetch GitLab project with ID '1234' from 'http://api.com'"); @@ -158,11 +156,10 @@ class GitlabProjectCreatorTest { assertThat(createdProjectAlmSettingDto.getAlmRepo()).isEqualTo(REPOSITORY_ID); assertThat(createdProjectAlmSettingDto.getProjectUuid()).isEqualTo(PROJECT_UUID); assertThat(createdProjectAlmSettingDto.getMonorepo()).isTrue(); - } @Test - void createProjectAndBindToDevOpsPlatform_whenNoKeyAndNameSpecified_generatesOneKeyAndUsersGitlabProjectName() { + void createProjectAndBindToDevOpsPlatform_whenNoKeyAndNameSpecified_generatesKeyAndUsersGitlabProjectName() { mockPatForUser(); mockGitlabProject(); mockMainBranch(); @@ -196,12 +193,12 @@ class GitlabProjectCreatorTest { Project project = mock(Project.class); lenient().when(project.getPathWithNamespace()).thenReturn(REPOSITORY_PATH_WITH_NAMESPACE); when(project.getName()).thenReturn(GITLAB_PROJECT_NAME); - when(gitlabApplicationClient.getProject(DEVOPS_PROJECT_DESCRIPTOR.url(), USER_PAT, Long.valueOf(REPOSITORY_ID))).thenReturn(project); + when(gitlabApplicationClient.getProject(GITLAB_URL, USER_PAT, Long.valueOf(REPOSITORY_ID))).thenReturn(project); } private void mockMainBranch() { - when(gitlabApplicationClient.getBranches(DEVOPS_PROJECT_DESCRIPTOR.url(), USER_PAT, Long.valueOf(REPOSITORY_ID))) + when(gitlabApplicationClient.getBranches(GITLAB_URL, USER_PAT, Long.valueOf(REPOSITORY_ID))) .thenReturn(List.of(new GitLabBranch("notMain", false), new GitLabBranch(MAIN_BRANCH_NAME, true))); } diff --git a/server/sonar-webserver-webapi-v2/src/main/java/org/sonar/server/v2/api/projects/request/BoundProjectCreateRestRequest.java b/server/sonar-webserver-webapi-v2/src/main/java/org/sonar/server/v2/api/projects/request/BoundProjectCreateRestRequest.java index bee9a99fee0..340c1674449 100644 --- a/server/sonar-webserver-webapi-v2/src/main/java/org/sonar/server/v2/api/projects/request/BoundProjectCreateRestRequest.java +++ b/server/sonar-webserver-webapi-v2/src/main/java/org/sonar/server/v2/api/projects/request/BoundProjectCreateRestRequest.java @@ -39,7 +39,12 @@ public record BoundProjectCreateRestRequest( String devOpsPlatformSettingId, @NotEmpty - @Schema(description = "Identifier of the DevOps platform repository to import. Repository slug for GitHub, repository id for GitLab.") + @Schema( + description = """ + Identifier of the DevOps platform repository to import: + - repository slug for GitHub and Bitbucket Cloud + - repository id for GitLab + """) String repositoryIdentifier, @Nullable diff --git a/server/sonar-webserver-webapi/src/it/java/org/sonar/server/almintegration/ws/bitbucketcloud/ImportBitbucketCloudRepoActionIT.java b/server/sonar-webserver-webapi/src/it/java/org/sonar/server/almintegration/ws/bitbucketcloud/ImportBitbucketCloudRepoActionIT.java index 8dab56edddb..8003f0daa5b 100644 --- a/server/sonar-webserver-webapi/src/it/java/org/sonar/server/almintegration/ws/bitbucketcloud/ImportBitbucketCloudRepoActionIT.java +++ b/server/sonar-webserver-webapi/src/it/java/org/sonar/server/almintegration/ws/bitbucketcloud/ImportBitbucketCloudRepoActionIT.java @@ -42,7 +42,14 @@ import org.sonar.db.project.ProjectDto; import org.sonar.db.user.UserDto; import org.sonar.server.almintegration.ws.ImportHelper; import org.sonar.server.common.almintegration.ProjectKeyGenerator; +import org.sonar.server.common.almsettings.DevOpsProjectCreatorFactory; +import org.sonar.server.common.almsettings.bitbucketcloud.BitbucketCloudProjectCreatorFactory; import org.sonar.server.common.component.ComponentUpdater; +import org.sonar.server.common.newcodeperiod.NewCodeDefinitionResolver; +import org.sonar.server.common.permission.PermissionTemplateService; +import org.sonar.server.common.permission.PermissionUpdater; +import org.sonar.server.common.project.ImportProjectService; +import org.sonar.server.common.project.ProjectCreator; import org.sonar.server.es.TestIndexers; import org.sonar.server.exceptions.BadRequestException; import org.sonar.server.exceptions.ForbiddenException; @@ -50,10 +57,7 @@ import org.sonar.server.exceptions.NotFoundException; import org.sonar.server.exceptions.UnauthorizedException; import org.sonar.server.favorite.FavoriteUpdater; import org.sonar.server.l18n.I18nRule; -import org.sonar.server.common.newcodeperiod.NewCodeDefinitionResolver; import org.sonar.server.permission.PermissionService; -import org.sonar.server.common.permission.PermissionTemplateService; -import org.sonar.server.common.permission.PermissionUpdater; import org.sonar.server.project.DefaultBranchNameResolver; import org.sonar.server.project.ProjectDefaultVisibility; import org.sonar.server.project.Visibility; @@ -99,10 +103,15 @@ public class ImportBitbucketCloudRepoActionIT { private final ImportHelper importHelper = new ImportHelper(db.getDbClient(), userSession); private final ProjectKeyGenerator projectKeyGenerator = mock(ProjectKeyGenerator.class); - private PlatformEditionProvider editionProvider = mock(PlatformEditionProvider.class); - private NewCodeDefinitionResolver newCodeDefinitionResolver = new NewCodeDefinitionResolver(db.getDbClient(), editionProvider); - private final WsActionTester ws = new WsActionTester(new ImportBitbucketCloudRepoAction(db.getDbClient(), userSession, - bitbucketCloudRestClient, projectDefaultVisibility, componentUpdater, importHelper, projectKeyGenerator, newCodeDefinitionResolver, defaultBranchNameResolver)); + private final PlatformEditionProvider editionProvider = mock(PlatformEditionProvider.class); + private final NewCodeDefinitionResolver newCodeDefinitionResolver = new NewCodeDefinitionResolver(db.getDbClient(), editionProvider); + private final ProjectCreator projectCreator = new ProjectCreator(userSession, projectDefaultVisibility, componentUpdater); + + private final DevOpsProjectCreatorFactory devOpsProjectCreatorFactory = new BitbucketCloudProjectCreatorFactory(db.getDbClient(), userSession, bitbucketCloudRestClient, + projectCreator, projectKeyGenerator); + private final ImportProjectService importProjectService = new ImportProjectService(db.getDbClient(), devOpsProjectCreatorFactory, userSession, componentUpdater, + newCodeDefinitionResolver); + private final WsActionTester ws = new WsActionTester(new ImportBitbucketCloudRepoAction(importHelper, importProjectService)); @Before public void before() { @@ -294,7 +303,7 @@ public class ImportBitbucketCloudRepoActionIT { public void fail_project_already_exist() { UserDto user = db.users().insertUser(); userSession.logIn(user).addPermission(PROVISION_PROJECTS); - AlmSettingDto almSetting = db.almSettings().insertGitHubAlmSetting(); + AlmSettingDto almSetting = db.almSettings().insertBitbucketCloudAlmSetting(); db.almPats().insert(dto -> { dto.setAlmSettingUuid(almSetting.getUuid()); dto.setUserUuid(user.getUuid()); @@ -341,7 +350,7 @@ public class ImportBitbucketCloudRepoActionIT { public void check_pat_is_missing() { UserDto user = db.users().insertUser(); userSession.logIn(user).addPermission(PROVISION_PROJECTS); - AlmSettingDto almSetting = db.almSettings().insertGitHubAlmSetting(); + AlmSettingDto almSetting = db.almSettings().insertBitbucketCloudAlmSetting(); TestRequest request = ws.newRequest() .setParam("almSetting", almSetting.getKey()) @@ -349,7 +358,7 @@ public class ImportBitbucketCloudRepoActionIT { assertThatThrownBy(request::execute) .isInstanceOf(IllegalArgumentException.class) - .hasMessageContaining("Username and App Password for '" + almSetting.getKey() + "' is missing"); + .hasMessageContaining("personal access token for '" + almSetting.getKey() + "' is missing"); } @Test diff --git a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/almintegration/ws/bitbucketcloud/ImportBitbucketCloudRepoAction.java b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/almintegration/ws/bitbucketcloud/ImportBitbucketCloudRepoAction.java index 0f77f2fe2b6..956694ab8ae 100644 --- a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/almintegration/ws/bitbucketcloud/ImportBitbucketCloudRepoAction.java +++ b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/almintegration/ws/bitbucketcloud/ImportBitbucketCloudRepoAction.java @@ -19,46 +19,25 @@ */ package org.sonar.server.almintegration.ws.bitbucketcloud; -import java.util.Optional; import javax.annotation.Nullable; import javax.inject.Inject; -import org.sonar.alm.client.bitbucket.bitbucketcloud.BitbucketCloudRestClient; -import org.sonar.alm.client.bitbucket.bitbucketcloud.Repository; import org.sonar.api.server.ws.Change; import org.sonar.api.server.ws.Request; import org.sonar.api.server.ws.Response; import org.sonar.api.server.ws.WebService; -import org.sonar.db.DbClient; -import org.sonar.db.DbSession; -import org.sonar.db.alm.pat.AlmPatDto; import org.sonar.db.alm.setting.ALM; import org.sonar.db.alm.setting.AlmSettingDto; -import org.sonar.db.alm.setting.ProjectAlmSettingDto; -import org.sonar.db.component.BranchDto; -import org.sonar.db.project.ProjectDto; import org.sonar.server.almintegration.ws.AlmIntegrationsWsAction; import org.sonar.server.almintegration.ws.ImportHelper; -import org.sonar.server.common.almintegration.ProjectKeyGenerator; -import org.sonar.server.component.ComponentCreationData; -import org.sonar.server.common.component.ComponentCreationParameters; -import org.sonar.server.common.component.ComponentUpdater; -import org.sonar.server.common.component.NewComponent; -import org.sonar.server.common.newcodeperiod.NewCodeDefinitionResolver; -import org.sonar.server.project.DefaultBranchNameResolver; -import org.sonar.server.project.ProjectDefaultVisibility; -import org.sonar.server.user.UserSession; +import org.sonar.server.common.project.ImportProjectRequest; +import org.sonar.server.common.project.ImportProjectService; +import org.sonar.server.common.project.ImportedProject; import org.sonarqube.ws.Projects; -import static java.util.Optional.ofNullable; -import static org.sonar.api.resources.Qualifiers.PROJECT; -import static org.sonar.db.project.CreationMethod.Category.ALM_IMPORT; -import static org.sonar.db.project.CreationMethod.getCreationMethod; import static org.sonar.server.almintegration.ws.ImportHelper.PARAM_ALM_SETTING; import static org.sonar.server.almintegration.ws.ImportHelper.toCreateResponse; -import static org.sonar.server.common.component.NewComponent.newComponentBuilder; import static org.sonar.server.common.newcodeperiod.NewCodeDefinitionResolver.NEW_CODE_PERIOD_TYPE_DESCRIPTION_PROJECT_CREATION; import static org.sonar.server.common.newcodeperiod.NewCodeDefinitionResolver.NEW_CODE_PERIOD_VALUE_DESCRIPTION_PROJECT_CREATION; -import static org.sonar.server.common.newcodeperiod.NewCodeDefinitionResolver.checkNewCodeDefinitionParam; import static org.sonar.server.ws.WsUtils.writeProtobuf; import static org.sonarqube.ws.client.project.ProjectsWsParameters.PARAM_NEW_CODE_DEFINITION_TYPE; import static org.sonarqube.ws.client.project.ProjectsWsParameters.PARAM_NEW_CODE_DEFINITION_VALUE; @@ -67,30 +46,13 @@ public class ImportBitbucketCloudRepoAction implements AlmIntegrationsWsAction { private static final String PARAM_REPO_SLUG = "repositorySlug"; - private final DbClient dbClient; - private final UserSession userSession; - private final BitbucketCloudRestClient bitbucketCloudRestClient; - private final ProjectDefaultVisibility projectDefaultVisibility; - private final ComponentUpdater componentUpdater; private final ImportHelper importHelper; - private final ProjectKeyGenerator projectKeyGenerator; - private final NewCodeDefinitionResolver newCodeDefinitionResolver; - private final DefaultBranchNameResolver defaultBranchNameResolver; + private final ImportProjectService importProjectService; @Inject - public ImportBitbucketCloudRepoAction(DbClient dbClient, UserSession userSession, BitbucketCloudRestClient bitbucketCloudRestClient, - ProjectDefaultVisibility projectDefaultVisibility, ComponentUpdater componentUpdater, ImportHelper importHelper, - ProjectKeyGenerator projectKeyGenerator, NewCodeDefinitionResolver newCodeDefinitionResolver, - DefaultBranchNameResolver defaultBranchNameResolver) { - this.dbClient = dbClient; - this.userSession = userSession; - this.bitbucketCloudRestClient = bitbucketCloudRestClient; - this.projectDefaultVisibility = projectDefaultVisibility; - this.componentUpdater = componentUpdater; + public ImportBitbucketCloudRepoAction(ImportHelper importHelper, ImportProjectService importProjectService) { this.importHelper = importHelper; - this.projectKeyGenerator = projectKeyGenerator; - this.newCodeDefinitionResolver = newCodeDefinitionResolver; - this.defaultBranchNameResolver = defaultBranchNameResolver; + this.importProjectService = importProjectService; } @Override @@ -133,77 +95,17 @@ public class ImportBitbucketCloudRepoAction implements AlmIntegrationsWsAction { private Projects.CreateWsResponse doHandle(Request request) { importHelper.checkProvisionProjectPermission(); - + AlmSettingDto almSettingDto = importHelper.getAlmSettingDtoForAlm(request, ALM.BITBUCKET_CLOUD); String newCodeDefinitionType = request.param(PARAM_NEW_CODE_DEFINITION_TYPE); String newCodeDefinitionValue = request.param(PARAM_NEW_CODE_DEFINITION_VALUE); - String repoSlug = request.mandatoryParam(PARAM_REPO_SLUG); - AlmSettingDto almSettingDto = importHelper.getAlmSettingDtoForAlm(request, ALM.BITBUCKET_CLOUD); - String workspace = ofNullable(almSettingDto.getAppId()) - .orElseThrow(() -> new IllegalArgumentException(String.format("workspace for alm setting %s is missing", almSettingDto.getKey()))); - - try (DbSession dbSession = dbClient.openSession(false)) { - String pat = getPat(dbSession, almSettingDto); - - Repository repo = bitbucketCloudRestClient.getRepo(pat, workspace, repoSlug); - - ComponentCreationData componentCreationData = createProject(dbSession, workspace, repo, repo.getMainBranch().getName()); - ProjectDto projectDto = Optional.ofNullable(componentCreationData.projectDto()).orElseThrow(); - BranchDto mainBranchDto = Optional.ofNullable(componentCreationData.mainBranchDto()).orElseThrow(); - - populatePRSetting(dbSession, repo, projectDto, almSettingDto); - - checkNewCodeDefinitionParam(newCodeDefinitionType, newCodeDefinitionValue); - - if (newCodeDefinitionType != null) { - newCodeDefinitionResolver.createNewCodeDefinition(dbSession, projectDto.getUuid(), mainBranchDto.getUuid(), - Optional.ofNullable(repo.getMainBranch().getName()).orElse(defaultBranchNameResolver.getEffectiveMainBranchName()), - newCodeDefinitionType, newCodeDefinitionValue); - } - - componentUpdater.commitAndIndex(dbSession, componentCreationData); - - return toCreateResponse(projectDto); - } - } - - private String getPat(DbSession dbSession, AlmSettingDto almSettingDto) { - String userUuid = importHelper.getUserUuid(); - - return dbClient.almPatDao().selectByUserAndAlmSetting(dbSession, userUuid, almSettingDto) - .map(AlmPatDto::getPersonalAccessToken) - .orElseThrow(() -> new IllegalArgumentException(String.format("Username and App Password for '%s' is missing", - almSettingDto.getKey()))); - } - - private ComponentCreationData createProject(DbSession dbSession, String workspace, Repository repo, @Nullable String defaultBranchName) { - boolean visibility = projectDefaultVisibility.get(dbSession).isPrivate(); - String uniqueProjectKey = projectKeyGenerator.generateUniqueProjectKey(workspace, repo.getSlug()); - NewComponent newProject = newComponentBuilder() - .setKey(uniqueProjectKey) - .setName(repo.getName()) - .setPrivate(visibility) - .setQualifier(PROJECT) - .build(); - ComponentCreationParameters componentCreationParameters = ComponentCreationParameters.builder() - .newComponent(newProject) - .userUuid(userSession.getUuid()) - .userLogin(userSession.getLogin()) - .mainBranchName(defaultBranchName) - .creationMethod(getCreationMethod(ALM_IMPORT, userSession.isAuthenticatedBrowserSession())) - .build(); - return componentUpdater.createWithoutCommit(dbSession, componentCreationParameters); + ImportedProject importedProject = importProjectService.importProject(toServiceRequest(almSettingDto, repoSlug, newCodeDefinitionType, newCodeDefinitionValue)); + return toCreateResponse(importedProject.projectDto()); } - private void populatePRSetting(DbSession dbSession, Repository repo, ProjectDto projectDto, AlmSettingDto almSettingDto) { - ProjectAlmSettingDto projectAlmSettingDto = new ProjectAlmSettingDto() - .setAlmSettingUuid(almSettingDto.getUuid()) - // Bitbucket Cloud PR decoration reads almRepo - .setAlmRepo(repo.getSlug()) - .setProjectUuid(projectDto.getUuid()) - .setMonorepo(false); - dbClient.projectAlmSettingDao().insertOrUpdate(dbSession, projectAlmSettingDto, almSettingDto.getKey(), - projectDto.getName(), projectDto.getKey()); + private static ImportProjectRequest toServiceRequest(AlmSettingDto almSettingDto, String slug, @Nullable String newCodeDefinitionType, + @Nullable String newCodeDefinitionValue) { + return new ImportProjectRequest(null, null, almSettingDto.getUuid(), slug, newCodeDefinitionType, newCodeDefinitionValue, false); } } diff --git a/server/sonar-webserver/src/main/java/org/sonar/server/platform/platformlevel/PlatformLevel4.java b/server/sonar-webserver/src/main/java/org/sonar/server/platform/platformlevel/PlatformLevel4.java index a4056d43a92..e7a8b4c24f9 100644 --- a/server/sonar-webserver/src/main/java/org/sonar/server/platform/platformlevel/PlatformLevel4.java +++ b/server/sonar-webserver/src/main/java/org/sonar/server/platform/platformlevel/PlatformLevel4.java @@ -68,6 +68,7 @@ import org.sonar.server.almintegration.ws.github.GithubProvisioningWs; import org.sonar.server.almsettings.MultipleAlmFeature; import org.sonar.server.almsettings.ws.AlmSettingsWsModule; import org.sonar.server.common.almsettings.DelegatingDevOpsProjectCreatorFactory; +import org.sonar.server.common.almsettings.bitbucketcloud.BitbucketCloudProjectCreatorFactory; import org.sonar.server.common.almsettings.github.GithubProjectCreatorFactory; import org.sonar.server.common.almsettings.gitlab.GitlabProjectCreatorFactory; import org.sonar.server.authentication.AuthenticationModule; @@ -573,6 +574,7 @@ public class PlatformLevel4 extends PlatformLevel { BitbucketServerRestClient.class, AzureDevOpsHttpClient.class, new AlmIntegrationsWSModule(), + BitbucketCloudProjectCreatorFactory.class, BitbucketCloudValidator.class, BitbucketServerSettingsValidator.class, GithubGlobalSettingsValidator.class, -- 2.39.5