diff options
author | Wojtek Wajerowicz <115081248+wojciech-wajerowicz-sonarsource@users.noreply.github.com> | 2024-03-20 09:43:54 +0100 |
---|---|---|
committer | sonartech <sonartech@sonarsource.com> | 2024-03-28 20:02:50 +0000 |
commit | adfd2bc45b301cc8a895c7ba63c78cdd0b4df50b (patch) | |
tree | 2216182393631de464256f461cecb8c23f3085c2 | |
parent | 412f42cb112802614e541ca186d3e35006f28be9 (diff) | |
download | sonarqube-adfd2bc45b301cc8a895c7ba63c78cdd0b4df50b.tar.gz sonarqube-adfd2bc45b301cc8a895c7ba63c78cdd0b4df50b.zip |
SONAR-21819 Add POST /dop-translation/bound-projects endpoint.
22 files changed, 830 insertions, 125 deletions
diff --git a/server/sonar-db-dao/src/main/java/org/sonar/db/alm/setting/ProjectAlmSettingDto.java b/server/sonar-db-dao/src/main/java/org/sonar/db/alm/setting/ProjectAlmSettingDto.java index a05bcb5582b..b10d241fa18 100644 --- a/server/sonar-db-dao/src/main/java/org/sonar/db/alm/setting/ProjectAlmSettingDto.java +++ b/server/sonar-db-dao/src/main/java/org/sonar/db/alm/setting/ProjectAlmSettingDto.java @@ -70,7 +70,7 @@ public class ProjectAlmSettingDto { private long updatedAt; private long createdAt; - String getUuid() { + public String getUuid() { return uuid; } diff --git a/server/sonar-webserver-common/src/main/java/org/sonar/server/common/project/ImportProjectRequest.java b/server/sonar-webserver-common/src/main/java/org/sonar/server/common/project/ImportProjectRequest.java new file mode 100644 index 00000000000..017ed88fc6a --- /dev/null +++ b/server/sonar-webserver-common/src/main/java/org/sonar/server/common/project/ImportProjectRequest.java @@ -0,0 +1,38 @@ +/* + * 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.project; + +import javax.annotation.Nullable; + +public record ImportProjectRequest( + @Nullable + String projectKey, + + @Nullable + String projectName, + String almSettingId, + String repositoryIdentifier, + @Nullable + String newCodeDefinitionType, + @Nullable + String newCodeDefinitionValue, + Boolean monorepo) { + +} 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 new file mode 100644 index 00000000000..30150205d20 --- /dev/null +++ b/server/sonar-webserver-common/src/main/java/org/sonar/server/common/project/ImportProjectService.java @@ -0,0 +1,95 @@ +/* + * 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.project; + +import java.util.Optional; +import org.sonar.api.server.ServerSide; +import org.sonar.db.DbClient; +import org.sonar.db.DbSession; +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.CreationMethod; +import org.sonar.db.project.ProjectDto; +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.component.ComponentUpdater; +import org.sonar.server.common.newcodeperiod.NewCodeDefinitionResolver; +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; + +@ServerSide +public class ImportProjectService { + private final DbClient dbClient; + private final DevOpsProjectCreatorFactory devOpsProjectCreatorFactory; + private final UserSession userSession; + private final ComponentUpdater componentUpdater; + private final NewCodeDefinitionResolver newCodeDefinitionResolver; + + public ImportProjectService(DbClient dbClient, DevOpsProjectCreatorFactory devOpsProjectCreatorFactory, UserSession userSession, ComponentUpdater componentUpdater, + NewCodeDefinitionResolver newCodeDefinitionResolver) { + this.dbClient = dbClient; + this.devOpsProjectCreatorFactory = devOpsProjectCreatorFactory; + this.userSession = userSession; + this.componentUpdater = componentUpdater; + this.newCodeDefinitionResolver = newCodeDefinitionResolver; + } + + public ImportedProject importProject(ImportProjectRequest request) { + 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()); + + DevOpsProjectCreator projectCreator = devOpsProjectCreatorFactory.getDevOpsProjectCreator(almSetting, projectDescriptor) + .orElseThrow(() -> new IllegalArgumentException(format("Platform %s not supported", almSetting.getAlm().name()))); + + CreationMethod creationMethod = getCreationMethod(ALM_IMPORT, userSession.isAuthenticatedBrowserSession()); + ComponentCreationData componentCreationData = projectCreator.createProjectAndBindToDevOpsPlatform( + dbSession, + creationMethod, + request.monorepo(), + request.projectKey(), + request.projectName()); + + ProjectDto projectDto = Optional.ofNullable(componentCreationData.projectDto()).orElseThrow(); + BranchDto mainBranchDto = Optional.ofNullable(componentCreationData.mainBranchDto()).orElseThrow(); + + if (request.newCodeDefinitionType() != null) { + newCodeDefinitionResolver.createNewCodeDefinition(dbSession, projectDto.getUuid(), mainBranchDto.getUuid(), + mainBranchDto.getKey(), request.newCodeDefinitionType(), request.newCodeDefinitionValue()); + } + componentUpdater.commitAndIndex(dbSession, componentCreationData); + ProjectAlmSettingDto projectAlmSettingDto = dbClient.projectAlmSettingDao().selectByProject(dbSession, projectDto) + .orElseThrow(() -> new IllegalStateException("Project ALM setting was not created")); + dbSession.commit(); + return new ImportedProject(projectDto, projectAlmSettingDto); + } + } + +} diff --git a/server/sonar-webserver-common/src/main/java/org/sonar/server/common/project/ImportedProject.java b/server/sonar-webserver-common/src/main/java/org/sonar/server/common/project/ImportedProject.java new file mode 100644 index 00000000000..20f1dc556d9 --- /dev/null +++ b/server/sonar-webserver-common/src/main/java/org/sonar/server/common/project/ImportedProject.java @@ -0,0 +1,26 @@ +/* + * 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.project; + +import org.sonar.db.alm.setting.ProjectAlmSettingDto; +import org.sonar.db.project.ProjectDto; + +public record ImportedProject(ProjectDto projectDto, ProjectAlmSettingDto projectAlmSettingDto){ +} diff --git a/server/sonar-webserver-common/src/test/java/org/sonar/server/common/project/ImportProjectServiceTest.java b/server/sonar-webserver-common/src/test/java/org/sonar/server/common/project/ImportProjectServiceTest.java new file mode 100644 index 00000000000..6fe106801ae --- /dev/null +++ b/server/sonar-webserver-common/src/test/java/org/sonar/server/common/project/ImportProjectServiceTest.java @@ -0,0 +1,212 @@ +/* + * 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.project; + +import java.util.Optional; +import org.junit.Rule; +import org.junit.jupiter.api.Test; +import org.mockito.Answers; +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.db.alm.setting.ProjectAlmSettingDto; +import org.sonar.db.component.BranchDto; +import org.sonar.db.project.ProjectDto; +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.component.ComponentUpdater; +import org.sonar.server.common.newcodeperiod.NewCodeDefinitionResolver; +import org.sonar.server.component.ComponentCreationData; +import org.sonar.server.tester.UserSessionRule; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; +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; +import static org.sonar.db.permission.GlobalPermission.PROVISION_PROJECTS; + +class ImportProjectServiceTest { + + private static final String API_URL = "https://api.com"; + private static final String PROJECT_UUID = "project-uuid"; + private static final String REPOSITORY_ID = "repository-id"; + private static final String PROJECT_KEY = "project-key"; + private static final String PROJECT_NAME = "project-name"; + private static final String MAIN_BRANCH_UUID = "main-branch-uuid"; + private static final String MAIN_BRANCH_KEY = "main-branch-key"; + private static final String ALM_SETTING_ID = "alm-setting-id"; + + @Rule + public UserSessionRule userSession = UserSessionRule.standalone(); + + private final DevOpsProjectCreatorFactory devOpsProjectCreatorFactory = mock(); + + private final DbClient dbClient = mock(Answers.RETURNS_DEEP_STUBS); + private final NewCodeDefinitionResolver newCodeDefinitionResolver = mock(); + private final ComponentUpdater componentUpdater = mock(); + + private final ImportProjectService importProjectService = new ImportProjectService(dbClient, devOpsProjectCreatorFactory, userSession, componentUpdater, + newCodeDefinitionResolver);; + + @Test + void createdImportedProject_whenAlmSettingDoesntExist_throws() { + userSession.logIn().addPermission(PROVISION_PROJECTS); + DbSession dbSession = mockDbSession(); + when(dbClient.almSettingDao().selectByUuid(dbSession, ALM_SETTING_ID)).thenReturn(Optional.empty()); + + ImportProjectRequest request = new ImportProjectRequest(PROJECT_KEY, PROJECT_NAME, ALM_SETTING_ID, REPOSITORY_ID, null, null, true); + + assertThatThrownBy(() -> importProjectService.importProject(request)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("ALM setting not found"); + + } + + @Test + void createImportedProject_whenAlmIsNotSupported_throws() { + userSession.logIn().addPermission(PROVISION_PROJECTS); + + DbSession dbSession = mockDbSession(); + AlmSettingDto almSetting = mockAlmSetting(dbSession); + + when(devOpsProjectCreatorFactory.getDevOpsProjectCreator(eq(almSetting), any())) + .thenReturn(Optional.empty()); + + ImportProjectRequest request = new ImportProjectRequest(PROJECT_KEY, PROJECT_NAME, ALM_SETTING_ID, REPOSITORY_ID, null, null, true); + + assertThatThrownBy(() -> importProjectService.importProject(request)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("Platform GITHUB not supported"); + } + + @Test + void createImportedProject_whenAlmIsSupportedAndNoNewCodeDefinitionDefined_shouldCreateProject() { + userSession.logIn().addPermission(PROVISION_PROJECTS); + DbSession dbSession = mockDbSession(); + AlmSettingDto almSetting = mockAlmSetting(dbSession); + + DevOpsProjectCreator devOpsProjectCreator = mockDevOpsProjectCreator(almSetting); + + ComponentCreationData componentCreationData = mockProjectCreation(devOpsProjectCreator, dbSession); + + ProjectDto projectDto = mockProjectDto(componentCreationData); + when(componentCreationData.mainBranchDto()).thenReturn(mock(BranchDto.class)); + + ProjectAlmSettingDto projectAlmSettingDto = mockProjectAlmSetting(dbSession, projectDto); + + ImportProjectRequest request = new ImportProjectRequest(PROJECT_KEY, PROJECT_NAME, ALM_SETTING_ID, REPOSITORY_ID, null, null, true); + + ImportedProject importedProject = importProjectService.importProject(request); + + assertThat(importedProject.projectDto()).isEqualTo(projectDto); + assertThat(importedProject.projectAlmSettingDto()).isEqualTo(projectAlmSettingDto); + + verify(componentUpdater).commitAndIndex(dbSession, componentCreationData); + } + + @Test + void createImportedProject_whenAlmIsSupportedAndNewCodeDefinitionDefined_shouldCreateProjectAndNewCodeDefinition() { + userSession.logIn().addPermission(PROVISION_PROJECTS); + DbSession dbSession = mockDbSession(); + AlmSettingDto almSetting = mockAlmSetting(dbSession); + + DevOpsProjectCreator devOpsProjectCreator = mockDevOpsProjectCreator(almSetting); + + ComponentCreationData componentCreationData = mockProjectCreation(devOpsProjectCreator, dbSession); + + ProjectDto projectDto = mockProjectDto(componentCreationData); + mockBranchDto(componentCreationData); + + ProjectAlmSettingDto projectAlmSettingDto = mockProjectAlmSetting(dbSession, projectDto); + + ImportProjectRequest request = new ImportProjectRequest(PROJECT_KEY, PROJECT_NAME, ALM_SETTING_ID, REPOSITORY_ID, "NUMBER_OF_DAYS", "10", true); + + ImportedProject importedProject = importProjectService.importProject(request); + + assertThat(importedProject.projectDto()).isEqualTo(projectDto); + assertThat(importedProject.projectAlmSettingDto()).isEqualTo(projectAlmSettingDto); + + verify(newCodeDefinitionResolver).createNewCodeDefinition( + dbSession, + PROJECT_UUID, + MAIN_BRANCH_UUID, + MAIN_BRANCH_KEY, + "NUMBER_OF_DAYS", + "10"); + verify(componentUpdater).commitAndIndex(dbSession, componentCreationData); + } + + private DbSession mockDbSession() { + DbSession dbSession = mock(DbSession.class); + when(dbClient.openSession(false)).thenReturn(dbSession); + return dbSession; + } + + private AlmSettingDto mockAlmSetting(DbSession dbSession) { + AlmSettingDto almSetting = mock(AlmSettingDto.class); + when(almSetting.getAlm()).thenReturn(ALM.GITHUB); + when(almSetting.getUrl()).thenReturn(API_URL); + when(dbClient.almSettingDao().selectByUuid(dbSession, ALM_SETTING_ID)).thenReturn(Optional.of(almSetting)); + return almSetting; + } + + private DevOpsProjectCreator mockDevOpsProjectCreator(AlmSettingDto almSetting) { + DevOpsProjectCreator devOpsProjectCreator = mock(DevOpsProjectCreator.class); + DevOpsProjectDescriptor projectDescriptor = new DevOpsProjectDescriptor(ALM.GITHUB, API_URL, REPOSITORY_ID); + when(devOpsProjectCreatorFactory.getDevOpsProjectCreator(almSetting, projectDescriptor)) + .thenReturn(Optional.of(devOpsProjectCreator)); + return devOpsProjectCreator; + } + + private static ComponentCreationData mockProjectCreation(DevOpsProjectCreator devOpsProjectCreator, DbSession dbSession) { + ComponentCreationData componentCreationData = mock(ComponentCreationData.class); + when(devOpsProjectCreator.createProjectAndBindToDevOpsPlatform(eq(dbSession), any(), eq(true), eq(PROJECT_KEY), eq(PROJECT_NAME))) + .thenReturn(componentCreationData); + return componentCreationData; + } + + private static ProjectDto mockProjectDto(ComponentCreationData componentCreationData) { + ProjectDto projectDto = mock(ProjectDto.class); + lenient().when(projectDto.getUuid()).thenReturn(PROJECT_UUID); + when(componentCreationData.projectDto()).thenReturn(projectDto); + return projectDto; + } + + private static void mockBranchDto(ComponentCreationData componentCreationData) { + BranchDto mainBrainDto = mock(BranchDto.class); + when(mainBrainDto.getUuid()).thenReturn(MAIN_BRANCH_UUID); + when(mainBrainDto.getKey()).thenReturn(MAIN_BRANCH_KEY); + when(componentCreationData.mainBranchDto()).thenReturn(mainBrainDto); + } + + private ProjectAlmSettingDto mockProjectAlmSetting(DbSession dbSession, ProjectDto projectDto) { + ProjectAlmSettingDto projectAlmSetting = mock(ProjectAlmSettingDto.class); + when(dbClient.projectAlmSettingDao().selectByProject(dbSession, projectDto)) + .thenReturn(Optional.of(projectAlmSetting)); + return projectAlmSetting; + } + +} diff --git a/server/sonar-webserver-webapi-v2/build.gradle b/server/sonar-webserver-webapi-v2/build.gradle index 5fcc13f99a8..760f4a25198 100644 --- a/server/sonar-webserver-webapi-v2/build.gradle +++ b/server/sonar-webserver-webapi-v2/build.gradle @@ -18,6 +18,7 @@ dependencies { testImplementation 'javax.servlet:javax.servlet-api' testImplementation 'org.junit.jupiter:junit-jupiter-api' testImplementation 'org.mockito:mockito-core' + testImplementation 'org.mockito:mockito-junit-jupiter' testImplementation 'org.skyscreamer:jsonassert:1.5.1' testImplementation 'org.sonarsource.api.plugin:sonar-plugin-api-test-fixtures' testImplementation 'com.tngtech.java:junit-dataprovider' diff --git a/server/sonar-webserver-webapi-v2/src/main/java/org/sonar/server/v2/WebApiEndpoints.java b/server/sonar-webserver-webapi-v2/src/main/java/org/sonar/server/v2/WebApiEndpoints.java index 5e90478e87e..201c625b04a 100644 --- a/server/sonar-webserver-webapi-v2/src/main/java/org/sonar/server/v2/WebApiEndpoints.java +++ b/server/sonar-webserver-webapi-v2/src/main/java/org/sonar/server/v2/WebApiEndpoints.java @@ -38,6 +38,8 @@ public class WebApiEndpoints { public static final String GITLAB_CONFIGURATION_ENDPOINT = DOP_TRANSLATION_DOMAIN + "/gitlab-configurations"; + public static final String BOUND_PROJECTS_ENDPOINT = DOP_TRANSLATION_DOMAIN + "/bound-projects"; + public static final String INTERNAL = "internal"; private WebApiEndpoints() { diff --git a/server/sonar-webserver-webapi-v2/src/main/java/org/sonar/server/v2/api/projects/controller/BoundProjectsController.java b/server/sonar-webserver-webapi-v2/src/main/java/org/sonar/server/v2/api/projects/controller/BoundProjectsController.java new file mode 100644 index 00000000000..2a54c0289cc --- /dev/null +++ b/server/sonar-webserver-webapi-v2/src/main/java/org/sonar/server/v2/api/projects/controller/BoundProjectsController.java @@ -0,0 +1,49 @@ +/* + * 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.v2.api.projects.controller; + +import io.swagger.v3.oas.annotations.Operation; +import javax.validation.Valid; +import org.sonar.server.v2.api.projects.request.BoundProjectCreateRestRequest; +import org.sonar.server.v2.api.projects.response.BoundProjectCreateRestResponse; +import org.springframework.http.HttpStatus; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.ResponseStatus; +import org.springframework.web.bind.annotation.RestController; + +import static org.sonar.server.v2.WebApiEndpoints.BOUND_PROJECTS_ENDPOINT; + +@RequestMapping(BOUND_PROJECTS_ENDPOINT) +@RestController +public interface BoundProjectsController { + + + @PostMapping + @Operation(summary = "Create a SonarQube project with the information from the provided DevOps platform project.", description = """ + Create a SonarQube project with the information from the provided DevOps platform project. + Autoconfigure Pull-Request decoration mechanism. + Requires the 'Create Projects' permission + """) + @ResponseStatus(HttpStatus.CREATED) + BoundProjectCreateRestResponse createBoundProject(@Valid @RequestBody BoundProjectCreateRestRequest request); + +} diff --git a/server/sonar-webserver-webapi-v2/src/main/java/org/sonar/server/v2/api/projects/controller/DefaultBoundProjectsController.java b/server/sonar-webserver-webapi-v2/src/main/java/org/sonar/server/v2/api/projects/controller/DefaultBoundProjectsController.java new file mode 100644 index 00000000000..1ca3578199f --- /dev/null +++ b/server/sonar-webserver-webapi-v2/src/main/java/org/sonar/server/v2/api/projects/controller/DefaultBoundProjectsController.java @@ -0,0 +1,58 @@ +/* + * 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.v2.api.projects.controller; + +import org.sonar.server.common.project.ImportProjectRequest; +import org.sonar.server.common.project.ImportProjectService; +import org.sonar.server.common.project.ImportedProject; +import org.sonar.server.user.UserSession; +import org.sonar.server.v2.api.projects.request.BoundProjectCreateRestRequest; +import org.sonar.server.v2.api.projects.response.BoundProjectCreateRestResponse; + +import static org.sonar.db.permission.GlobalPermission.PROVISION_PROJECTS; + +public class DefaultBoundProjectsController implements BoundProjectsController { + + private final UserSession userSession; + + private final ImportProjectService importProjectService; + + public DefaultBoundProjectsController(UserSession userSession, ImportProjectService importProjectService) { + this.userSession = userSession; + this.importProjectService = importProjectService; + } + + @Override + public BoundProjectCreateRestResponse createBoundProject(BoundProjectCreateRestRequest request) { + userSession.checkLoggedIn().checkPermission(PROVISION_PROJECTS); + ImportedProject importedProject = importProjectService.importProject(restRequestToServiceRequest(request)); + return toRestResponse(importedProject); + + } + + private static ImportProjectRequest restRequestToServiceRequest(BoundProjectCreateRestRequest request) { + return new ImportProjectRequest(request.projectKey(), request.projectName(), request.devOpsPlatformSettingId(), request.repositoryIdentifier(), + request.newCodeDefinitionType(), request.newCodeDefinitionValue(), request.monorepo()); + } + + private static BoundProjectCreateRestResponse toRestResponse(ImportedProject importedProject) { + return new BoundProjectCreateRestResponse(importedProject.projectDto().getUuid(), importedProject.projectAlmSettingDto().getUuid()); + } +} diff --git a/server/sonar-webserver-webapi-v2/src/main/java/org/sonar/server/v2/api/projects/controller/package-info.java b/server/sonar-webserver-webapi-v2/src/main/java/org/sonar/server/v2/api/projects/controller/package-info.java new file mode 100644 index 00000000000..f125e37417e --- /dev/null +++ b/server/sonar-webserver-webapi-v2/src/main/java/org/sonar/server/v2/api/projects/controller/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.v2.api.projects.controller; + +import javax.annotation.ParametersAreNonnullByDefault; 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 new file mode 100644 index 00000000000..bee9a99fee0 --- /dev/null +++ b/server/sonar-webserver-webapi-v2/src/main/java/org/sonar/server/v2/api/projects/request/BoundProjectCreateRestRequest.java @@ -0,0 +1,68 @@ +/* + * 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.v2.api.projects.request; + +import io.swagger.v3.oas.annotations.media.Schema; +import javax.annotation.Nullable; +import javax.validation.constraints.NotEmpty; +import javax.validation.constraints.NotNull; + +public record BoundProjectCreateRestRequest( + + @NotEmpty + @Schema(description = "Key of the project to create") + String projectKey, + + @NotEmpty + @Schema(description = "Name of the project to create") + String projectName, + + @NotEmpty + @Schema(description = "Identifier of DevOps platform configuration to use.") + String devOpsPlatformSettingId, + + @NotEmpty + @Schema(description = "Identifier of the DevOps platform repository to import. Repository slug for GitHub, repository id for GitLab.") + String repositoryIdentifier, + + @Nullable + @Schema(description = """ + Project New Code Definition Type + New code definitions of the following types are allowed: + - PREVIOUS_VERSION + - NUMBER_OF_DAYS + - REFERENCE_BRANCH - will default to the main branch. + """) + String newCodeDefinitionType, + + @Nullable + @Schema(description = """ + Project New Code Definition Value + For each new code definition type, a different value is expected: + - no value, when the new code definition type is PREVIOUS_VERSION and REFERENCE_BRANCH + - a number between 1 and 90, when the new code definition type is NUMBER_OF_DAYS + """) + String newCodeDefinitionValue, + + @NotNull + @Schema(description = "True if project is part of a mono repo.") + Boolean monorepo +) { +} diff --git a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/newcodeperiod/package-info.java b/server/sonar-webserver-webapi-v2/src/main/java/org/sonar/server/v2/api/projects/request/package-info.java index 281ee26928d..8f659af2998 100644 --- a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/newcodeperiod/package-info.java +++ b/server/sonar-webserver-webapi-v2/src/main/java/org/sonar/server/v2/api/projects/request/package-info.java @@ -18,6 +18,6 @@ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ @ParametersAreNonnullByDefault -package org.sonar.server.newcodeperiod; +package org.sonar.server.v2.api.projects.request; import javax.annotation.ParametersAreNonnullByDefault; diff --git a/server/sonar-webserver-webapi-v2/src/main/java/org/sonar/server/v2/api/projects/response/BoundProjectCreateRestResponse.java b/server/sonar-webserver-webapi-v2/src/main/java/org/sonar/server/v2/api/projects/response/BoundProjectCreateRestResponse.java new file mode 100644 index 00000000000..2d423f2d169 --- /dev/null +++ b/server/sonar-webserver-webapi-v2/src/main/java/org/sonar/server/v2/api/projects/response/BoundProjectCreateRestResponse.java @@ -0,0 +1,31 @@ +/* + * 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.v2.api.projects.response; + +import io.swagger.v3.oas.annotations.media.Schema; + +public record BoundProjectCreateRestResponse( + + @Schema(description = "The identifier of the created project") + String projectId, + + @Schema(description = "The identifier of the binding between the created project and the DevOps platform project") + String bindingId) { +} diff --git a/server/sonar-webserver-webapi-v2/src/main/java/org/sonar/server/v2/api/projects/response/package-info.java b/server/sonar-webserver-webapi-v2/src/main/java/org/sonar/server/v2/api/projects/response/package-info.java new file mode 100644 index 00000000000..cc98c9d2480 --- /dev/null +++ b/server/sonar-webserver-webapi-v2/src/main/java/org/sonar/server/v2/api/projects/response/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.v2.api.projects.response; + +import javax.annotation.ParametersAreNonnullByDefault; diff --git a/server/sonar-webserver-webapi-v2/src/main/java/org/sonar/server/v2/config/PlatformLevel4WebConfig.java b/server/sonar-webserver-webapi-v2/src/main/java/org/sonar/server/v2/config/PlatformLevel4WebConfig.java index 9125aeb23ae..af59c51d9ed 100644 --- a/server/sonar-webserver-webapi-v2/src/main/java/org/sonar/server/v2/config/PlatformLevel4WebConfig.java +++ b/server/sonar-webserver-webapi-v2/src/main/java/org/sonar/server/v2/config/PlatformLevel4WebConfig.java @@ -32,6 +32,7 @@ import org.sonar.server.common.health.WebServerStatusNodeCheck; import org.sonar.server.common.management.ManagedInstanceChecker; import org.sonar.server.common.platform.LivenessChecker; import org.sonar.server.common.platform.LivenessCheckerImpl; +import org.sonar.server.common.project.ImportProjectService; import org.sonar.server.common.rule.service.RuleService; import org.sonar.server.common.text.MacroInterpreter; import org.sonar.server.common.user.service.UserService; @@ -46,6 +47,8 @@ import org.sonar.server.v2.api.group.controller.DefaultGroupController; import org.sonar.server.v2.api.group.controller.GroupController; import org.sonar.server.v2.api.membership.controller.DefaultGroupMembershipController; import org.sonar.server.v2.api.membership.controller.GroupMembershipController; +import org.sonar.server.v2.api.projects.controller.DefaultBoundProjectsController; +import org.sonar.server.v2.api.projects.controller.BoundProjectsController; import org.sonar.server.v2.api.rule.controller.DefaultRuleController; import org.sonar.server.v2.api.rule.controller.RuleController; import org.sonar.server.v2.api.rule.converter.RuleRestResponseGenerator; @@ -100,7 +103,6 @@ public class PlatformLevel4WebConfig { return new DefaultGroupController(userSession, dbClient, groupService, managedInstanceChecker); } - @Bean public GroupMembershipController groupMembershipsController(UserSession userSession, GroupMembershipService groupMembershipService, ManagedInstanceChecker managedInstanceChecker) { @@ -129,4 +131,10 @@ public class PlatformLevel4WebConfig { return new DefaultGitlabConfigurationController(userSession, gitlabConfigurationService); } + @Bean + public BoundProjectsController importedProjectsController(UserSession userSession, ImportProjectService importProjectService) { + return new DefaultBoundProjectsController( + userSession, importProjectService); + } + } diff --git a/server/sonar-webserver-webapi-v2/src/test/java/org/sonar/server/v2/api/projects/controller/DefaultBoundProjectsControllerTest.java b/server/sonar-webserver-webapi-v2/src/test/java/org/sonar/server/v2/api/projects/controller/DefaultBoundProjectsControllerTest.java new file mode 100644 index 00000000000..58af5d025fa --- /dev/null +++ b/server/sonar-webserver-webapi-v2/src/test/java/org/sonar/server/v2/api/projects/controller/DefaultBoundProjectsControllerTest.java @@ -0,0 +1,151 @@ +/* + * 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.v2.api.projects.controller; + +import org.junit.Rule; +import org.junit.jupiter.api.Test; +import org.sonar.db.alm.setting.ProjectAlmSettingDto; +import org.sonar.db.project.ProjectDto; +import org.sonar.server.common.project.ImportProjectRequest; +import org.sonar.server.common.project.ImportProjectService; +import org.sonar.server.common.project.ImportedProject; +import org.sonar.server.tester.UserSessionRule; +import org.sonar.server.v2.api.ControllerTester; +import org.springframework.http.MediaType; +import org.springframework.test.web.servlet.MockMvc; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; +import static org.sonar.db.permission.GlobalPermission.PROVISION_PROJECTS; +import static org.sonar.server.v2.WebApiEndpoints.BOUND_PROJECTS_ENDPOINT; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +class DefaultBoundProjectsControllerTest { + + private static final String PROJECT_UUID = "project-uuid"; + private static final String PROJECT_ALM_SETTING_UUID = "project-alm-setting-uuid"; + private static final String REPOSITORY_ID = "repository-id"; + private static final String PROJECT_KEY = "project-key"; + private static final String PROJECT_NAME = "project-name"; + private static final String ALM_SETTING_ID = "alm-setting-id"; + + @Rule + public UserSessionRule userSession = UserSessionRule.standalone(); + + private final ImportProjectService importProjectService = mock(); + private final MockMvc mockMvc = ControllerTester.getMockMvc( + new DefaultBoundProjectsController( + userSession, importProjectService)); + + @Test + void createBoundProject_whenUserDoesntHaveCreateProjectPermission_returnsForbidden() throws Exception { + userSession.logIn(); + mockMvc.perform( + post(BOUND_PROJECTS_ENDPOINT) + .contentType(MediaType.APPLICATION_JSON) + .content(""" + { + "projectKey": "project-key", + "projectName": "project-name", + "devOpsPlatformSettingId": "alm-setting-id", + "repositoryIdentifier": "repository-id", + "monorepo": true + } + """) + + ) + .andExpect(status().isForbidden()); + } + + @Test + void createdBoundProject_whenImportProjectServiceThrowsIllegalArgumentExceptions_returnsBadRequest() throws Exception { + userSession.logIn().addPermission(PROVISION_PROJECTS); + when(importProjectService.importProject(any())) + .thenThrow(new IllegalArgumentException("Error message")); + mockMvc.perform( + post(BOUND_PROJECTS_ENDPOINT) + .contentType(MediaType.APPLICATION_JSON) + .content(""" + { + "projectKey": "project-key", + "projectName": "project-name", + "devOpsPlatformSettingId": "alm-setting-id", + "repositoryIdentifier": "repository-id", + "monorepo": true + } + """)) + .andExpectAll( + status().isBadRequest(), + content().json(""" + { + "message": "Error message" + } + """)); + } + + @Test + void createBoundProject_whenProjectIsCreatedSuccessfully_returnResponse() throws Exception { + userSession.logIn().addPermission(PROVISION_PROJECTS); + + ProjectDto projectDto = mock(ProjectDto.class); + when(projectDto.getUuid()).thenReturn(PROJECT_UUID); + + ProjectAlmSettingDto projectAlmSettingDto = mock(ProjectAlmSettingDto.class); + when(projectAlmSettingDto.getUuid()).thenReturn(PROJECT_ALM_SETTING_UUID); + + when(importProjectService.importProject(new ImportProjectRequest( + PROJECT_KEY, + PROJECT_NAME, + ALM_SETTING_ID, + REPOSITORY_ID, + "NUMBER_OF_DAYS", + "10", + true))) + .thenReturn(new ImportedProject( + projectDto, + projectAlmSettingDto)); + + mockMvc.perform( + post(BOUND_PROJECTS_ENDPOINT) + .contentType(MediaType.APPLICATION_JSON) + .content(""" + { + "projectKey": "project-key", + "projectName": "project-name", + "devOpsPlatformSettingId": "alm-setting-id", + "repositoryIdentifier": "repository-id", + "newCodeDefinitionType": "NUMBER_OF_DAYS", + "newCodeDefinitionValue": "10", + "monorepo": true + } + """)) + .andExpectAll( + status().isCreated(), + content().json(""" + { + "projectId": "project-uuid", + "bindingId": "project-alm-setting-uuid" + } + """)); + } +} diff --git a/server/sonar-webserver-webapi/src/it/java/org/sonar/server/almintegration/ws/github/ImportGithubProjectActionIT.java b/server/sonar-webserver-webapi/src/it/java/org/sonar/server/almintegration/ws/github/ImportGithubProjectActionIT.java index 0bd1deaa6a1..a5a80858094 100644 --- a/server/sonar-webserver-webapi/src/it/java/org/sonar/server/almintegration/ws/github/ImportGithubProjectActionIT.java +++ b/server/sonar-webserver-webapi/src/it/java/org/sonar/server/almintegration/ws/github/ImportGithubProjectActionIT.java @@ -54,6 +54,7 @@ import org.sonar.server.almintegration.ws.ImportHelper; import org.sonar.server.common.almintegration.ProjectKeyGenerator; import org.sonar.server.common.almsettings.github.GithubProjectCreatorFactory; import org.sonar.server.common.component.ComponentUpdater; +import org.sonar.server.common.project.ImportProjectService; import org.sonar.server.es.EsTester; import org.sonar.server.es.IndexersImpl; import org.sonar.server.es.TestIndexers; @@ -144,8 +145,10 @@ public class ImportGithubProjectActionIT { private final GithubProjectCreatorFactory gitHubProjectCreatorFactory = new GithubProjectCreatorFactory(db.getDbClient(), null, appClient, projectKeyGenerator, userSession, projectCreator, gitHubSettings, githubPermissionConverter, userPermissionUpdater, permissionService, managedProjectService); - private final WsActionTester ws = new WsActionTester(new ImportGithubProjectAction(db.getDbClient(), userSession, - componentUpdater, importHelper, newCodeDefinitionResolver, defaultBranchNameResolver, gitHubProjectCreatorFactory)); + + private final ImportProjectService importProjectService = new ImportProjectService(db.getDbClient(), gitHubProjectCreatorFactory, userSession, componentUpdater, + newCodeDefinitionResolver); + private final WsActionTester ws = new WsActionTester(new ImportGithubProjectAction(importProjectService, importHelper)); @Before public void before() { diff --git a/server/sonar-webserver-webapi/src/it/java/org/sonar/server/almintegration/ws/gitlab/ImportGitLabProjectActionIT.java b/server/sonar-webserver-webapi/src/it/java/org/sonar/server/almintegration/ws/gitlab/ImportGitLabProjectActionIT.java index a9914d3c810..6133ac34d0b 100644 --- a/server/sonar-webserver-webapi/src/it/java/org/sonar/server/almintegration/ws/gitlab/ImportGitLabProjectActionIT.java +++ b/server/sonar-webserver-webapi/src/it/java/org/sonar/server/almintegration/ws/gitlab/ImportGitLabProjectActionIT.java @@ -44,6 +44,7 @@ import org.sonar.server.almintegration.ws.ImportHelper; import org.sonar.server.common.almintegration.ProjectKeyGenerator; import org.sonar.server.common.almsettings.gitlab.GitlabProjectCreatorFactory; import org.sonar.server.common.component.ComponentUpdater; +import org.sonar.server.common.project.ImportProjectService; import org.sonar.server.es.TestIndexers; import org.sonar.server.exceptions.NotFoundException; import org.sonar.server.favorite.FavoriteUpdater; @@ -105,8 +106,10 @@ public class ImportGitLabProjectActionIT { private final ProjectCreator projectCreator = new ProjectCreator(userSession, projectDefaultVisibility, componentUpdater); private final GitlabProjectCreatorFactory gitlabProjectCreatorFactory = new GitlabProjectCreatorFactory(db.getDbClient(), projectKeyGenerator, projectCreator, gitlabApplicationClient, userSession); - private final ImportGitLabProjectAction importGitLabProjectAction = new ImportGitLabProjectAction( - db.getDbClient(), userSession, componentUpdater, importHelper, newCodeDefinitionResolver, gitlabProjectCreatorFactory); + + private final ImportProjectService importProjectService = new ImportProjectService(db.getDbClient(), gitlabProjectCreatorFactory, userSession, componentUpdater, + newCodeDefinitionResolver); + private final ImportGitLabProjectAction importGitLabProjectAction = new ImportGitLabProjectAction(importProjectService, importHelper); private final WsActionTester ws = new WsActionTester(importGitLabProjectAction); @Before diff --git a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/almintegration/ws/github/ImportGithubProjectAction.java b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/almintegration/ws/github/ImportGithubProjectAction.java index 130078e2328..e5eb07f77d2 100644 --- a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/almintegration/ws/github/ImportGithubProjectAction.java +++ b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/almintegration/ws/github/ImportGithubProjectAction.java @@ -19,40 +19,24 @@ */ package org.sonar.server.almintegration.ws.github; -import java.util.Optional; +import javax.annotation.Nullable; import javax.inject.Inject; 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.setting.ALM; import org.sonar.db.alm.setting.AlmSettingDto; -import org.sonar.db.component.BranchDto; -import org.sonar.db.project.CreationMethod; -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.almsettings.DevOpsProjectCreator; -import org.sonar.server.common.almsettings.DevOpsProjectDescriptor; -import org.sonar.server.common.almsettings.github.GithubProjectCreatorFactory; -import org.sonar.server.component.ComponentCreationData; -import org.sonar.server.common.component.ComponentUpdater; -import org.sonar.server.exceptions.BadRequestException; -import org.sonar.server.common.newcodeperiod.NewCodeDefinitionResolver; -import org.sonar.server.project.DefaultBranchNameResolver; -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.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.almintegration.ws.ImportHelper.PARAM_ALM_SETTING; -import static org.sonar.server.almintegration.ws.ImportHelper.toCreateResponse; 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; @@ -60,30 +44,13 @@ import static org.sonarqube.ws.client.project.ProjectsWsParameters.PARAM_NEW_COD public class ImportGithubProjectAction implements AlmIntegrationsWsAction { public static final String PARAM_REPOSITORY_KEY = "repositoryKey"; - private final DbClient dbClient; - - private final UserSession userSession; - private final ComponentUpdater componentUpdater; + private final ImportProjectService importProjectService; private final ImportHelper importHelper; - private final NewCodeDefinitionResolver newCodeDefinitionResolver; - - private final DefaultBranchNameResolver defaultBranchNameResolver; - - private final GithubProjectCreatorFactory githubProjectCreatorFactory; - @Inject - public ImportGithubProjectAction(DbClient dbClient, UserSession userSession, - ComponentUpdater componentUpdater, ImportHelper importHelper, - NewCodeDefinitionResolver newCodeDefinitionResolver, - DefaultBranchNameResolver defaultBranchNameResolver, GithubProjectCreatorFactory githubProjectCreatorFactory) { - this.dbClient = dbClient; - this.userSession = userSession; - this.componentUpdater = componentUpdater; + public ImportGithubProjectAction(ImportProjectService importProjectService, ImportHelper importHelper) { + this.importProjectService = importProjectService; this.importHelper = importHelper; - this.newCodeDefinitionResolver = newCodeDefinitionResolver; - this.defaultBranchNameResolver = defaultBranchNameResolver; - this.githubProjectCreatorFactory = githubProjectCreatorFactory; } @Override @@ -128,34 +95,18 @@ public class ImportGithubProjectAction implements AlmIntegrationsWsAction { private Projects.CreateWsResponse doHandle(Request request) { importHelper.checkProvisionProjectPermission(); AlmSettingDto almSettingDto = importHelper.getAlmSettingDtoForAlm(request, ALM.GITHUB); - + String repositoryKey = request.mandatoryParam(PARAM_REPOSITORY_KEY); String newCodeDefinitionType = request.param(PARAM_NEW_CODE_DEFINITION_TYPE); String newCodeDefinitionValue = request.param(PARAM_NEW_CODE_DEFINITION_VALUE); - try (DbSession dbSession = dbClient.openSession(false)) { - String repositoryKey = request.mandatoryParam(PARAM_REPOSITORY_KEY); + ImportedProject importedProject = importProjectService.importProject(toServiceRequest(almSettingDto, repositoryKey, newCodeDefinitionType, newCodeDefinitionValue)); - String url = requireNonNull(almSettingDto.getUrl(), "DevOps Platform url cannot be null"); - DevOpsProjectDescriptor devOpsProjectDescriptor = new DevOpsProjectDescriptor(ALM.GITHUB, url, repositoryKey); + return ImportHelper.toCreateResponse(importedProject.projectDto()); - DevOpsProjectCreator devOpsProjectCreator = githubProjectCreatorFactory.getDevOpsProjectCreator(almSettingDto, devOpsProjectDescriptor) - .orElseThrow(() -> BadRequestException.create("GitHub DevOps platform configuration not found.")); - CreationMethod creationMethod = getCreationMethod(ALM_IMPORT, userSession.isAuthenticatedBrowserSession()); - ComponentCreationData componentCreationData = devOpsProjectCreator.createProjectAndBindToDevOpsPlatform(dbSession, creationMethod, false, null, null); - - checkNewCodeDefinitionParam(newCodeDefinitionType, newCodeDefinitionValue); - - ProjectDto projectDto = Optional.ofNullable(componentCreationData.projectDto()).orElseThrow(); - BranchDto mainBranchDto = Optional.ofNullable(componentCreationData.mainBranchDto()).orElseThrow(); - - if (newCodeDefinitionType != null) { - newCodeDefinitionResolver.createNewCodeDefinition(dbSession, projectDto.getUuid(), mainBranchDto.getUuid(), - Optional.ofNullable(mainBranchDto.getKey()).orElse(defaultBranchNameResolver.getEffectiveMainBranchName()), - newCodeDefinitionType, newCodeDefinitionValue); - } + } - componentUpdater.commitAndIndex(dbSession, componentCreationData); - return toCreateResponse(projectDto); - } + private static ImportProjectRequest toServiceRequest(AlmSettingDto almSettingDto, String githubRepositoryKey, @Nullable String newCodeDefinitionType, + @Nullable String newCodeDefinitionValue) { + return new ImportProjectRequest(null, null, almSettingDto.getUuid(), githubRepositoryKey, newCodeDefinitionType, newCodeDefinitionValue, false); } } diff --git a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/almintegration/ws/gitlab/ImportGitLabProjectAction.java b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/almintegration/ws/gitlab/ImportGitLabProjectAction.java index dc5e0ca7e0a..5bacb525b84 100644 --- a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/almintegration/ws/gitlab/ImportGitLabProjectAction.java +++ b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/almintegration/ws/gitlab/ImportGitLabProjectAction.java @@ -19,37 +19,24 @@ */ package org.sonar.server.almintegration.ws.gitlab; -import java.util.Optional; +import javax.annotation.Nullable; import javax.inject.Inject; 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.setting.ALM; import org.sonar.db.alm.setting.AlmSettingDto; -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.almsettings.DevOpsProjectCreator; -import org.sonar.server.common.almsettings.DevOpsProjectDescriptor; -import org.sonar.server.common.almsettings.gitlab.GitlabProjectCreatorFactory; -import org.sonar.server.component.ComponentCreationData; -import org.sonar.server.common.component.ComponentUpdater; -import org.sonar.server.exceptions.BadRequestException; -import org.sonar.server.common.newcodeperiod.NewCodeDefinitionResolver; -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.CreateWsResponse; -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.almintegration.ws.ImportHelper.PARAM_ALM_SETTING; 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; @@ -58,23 +45,13 @@ public class ImportGitLabProjectAction implements AlmIntegrationsWsAction { public static final String PARAM_GITLAB_PROJECT_ID = "gitlabProjectId"; - private final DbClient dbClient; - private final UserSession userSession; - private final ComponentUpdater componentUpdater; + private final ImportProjectService importProjectService; private final ImportHelper importHelper; - private final NewCodeDefinitionResolver newCodeDefinitionResolver; - private final GitlabProjectCreatorFactory projectCreatorFactory; @Inject - public ImportGitLabProjectAction(DbClient dbClient, UserSession userSession, - ComponentUpdater componentUpdater, ImportHelper importHelper, NewCodeDefinitionResolver newCodeDefinitionResolver, - GitlabProjectCreatorFactory projectCreatorFactory) { - this.dbClient = dbClient; - this.userSession = userSession; - this.componentUpdater = componentUpdater; + public ImportGitLabProjectAction(ImportProjectService importProjectService, ImportHelper importHelper) { + this.importProjectService = importProjectService; this.importHelper = importHelper; - this.newCodeDefinitionResolver = newCodeDefinitionResolver; - this.projectCreatorFactory = projectCreatorFactory; } @Override @@ -112,33 +89,17 @@ public class ImportGitLabProjectAction implements AlmIntegrationsWsAction { private CreateWsResponse doHandle(Request request) { importHelper.checkProvisionProjectPermission(); - + AlmSettingDto almSettingDto = importHelper.getAlmSettingDtoForAlm(request, ALM.GITLAB); + String gitlabProjectId = request.mandatoryParam(PARAM_GITLAB_PROJECT_ID); String newCodeDefinitionType = request.param(PARAM_NEW_CODE_DEFINITION_TYPE); String newCodeDefinitionValue = request.param(PARAM_NEW_CODE_DEFINITION_VALUE); + ImportedProject importedProject = importProjectService.importProject(toServiceRequest(almSettingDto, gitlabProjectId, newCodeDefinitionType, newCodeDefinitionValue)); - try (DbSession dbSession = dbClient.openSession(false)) { - AlmSettingDto almSettingDto = importHelper.getAlmSettingDtoForAlm(request, ALM.GITLAB); - - String gitlabProjectId = request.mandatoryParam(PARAM_GITLAB_PROJECT_ID); - String gitlabUrl = requireNonNull(almSettingDto.getUrl(), "DevOps Platform gitlabUrl cannot be null"); - - DevOpsProjectDescriptor projectDescriptor = new DevOpsProjectDescriptor(ALM.GITLAB, gitlabUrl, gitlabProjectId); - DevOpsProjectCreator projectCreator = projectCreatorFactory.getDevOpsProjectCreator(almSettingDto, projectDescriptor) - .orElseThrow(() -> BadRequestException.create("Gitlab DevOps platform configuration not found")); - ComponentCreationData componentCreationData = projectCreator.createProjectAndBindToDevOpsPlatform(dbSession, - getCreationMethod(ALM_IMPORT, userSession.isAuthenticatedBrowserSession()), false, null, null); - - ProjectDto projectDto = Optional.ofNullable(componentCreationData.projectDto()).orElseThrow(); - BranchDto mainBranchDto = Optional.ofNullable(componentCreationData.mainBranchDto()).orElseThrow(); - - checkNewCodeDefinitionParam(newCodeDefinitionType, newCodeDefinitionValue); - if (newCodeDefinitionType != null) { - newCodeDefinitionResolver.createNewCodeDefinition(dbSession, projectDto.getUuid(), mainBranchDto.getUuid(), - mainBranchDto.getKey(), newCodeDefinitionType, newCodeDefinitionValue); - } + return ImportHelper.toCreateResponse(importedProject.projectDto()); + } - componentUpdater.commitAndIndex(dbSession, componentCreationData); - return ImportHelper.toCreateResponse(projectDto); - } + private static ImportProjectRequest toServiceRequest(AlmSettingDto almSettingDto, String gitlabProjectId, @Nullable String newCodeDefinitionType, + @Nullable String newCodeDefinitionValue) { + return new ImportProjectRequest(null, null, almSettingDto.getUuid(), gitlabProjectId, newCodeDefinitionType, newCodeDefinitionValue, false); } } diff --git a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/project/ws/ProjectsWsModule.java b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/project/ws/ProjectsWsModule.java index 70ac999aee8..5dbd55f6f98 100644 --- a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/project/ws/ProjectsWsModule.java +++ b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/project/ws/ProjectsWsModule.java @@ -20,6 +20,7 @@ package org.sonar.server.project.ws; import org.sonar.core.platform.Module; +import org.sonar.server.common.project.ImportProjectService; import org.sonar.server.common.project.ProjectCreator; import org.sonar.server.project.ProjectDefaultVisibility; import org.sonar.server.project.ProjectLifeCycleListenersImpl; @@ -33,6 +34,7 @@ public class ProjectsWsModule extends Module { @Override protected void configureModule() { add( + ImportProjectService.class, ProjectDefaultVisibility.class, ProjectFinder.class, ProjectLifeCycleListenersImpl.class, diff --git a/server/sonar-webserver-webapi/src/test/java/org/sonar/server/project/ws/ProjectsWsModuleTest.java b/server/sonar-webserver-webapi/src/test/java/org/sonar/server/project/ws/ProjectsWsModuleTest.java index 7b9ad924335..f3eb02a0b35 100644 --- a/server/sonar-webserver-webapi/src/test/java/org/sonar/server/project/ws/ProjectsWsModuleTest.java +++ b/server/sonar-webserver-webapi/src/test/java/org/sonar/server/project/ws/ProjectsWsModuleTest.java @@ -29,6 +29,6 @@ public class ProjectsWsModuleTest { public void verify_count_of_added_components_on_SonarQube() { ListContainer container = new ListContainer(); new ProjectsWsModule().configure(container); - assertThat(container.getAddedObjects()).hasSize(14); + assertThat(container.getAddedObjects()).hasSize(15); } } |