aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorWojtek Wajerowicz <115081248+wojciech-wajerowicz-sonarsource@users.noreply.github.com>2024-03-20 09:43:54 +0100
committersonartech <sonartech@sonarsource.com>2024-03-28 20:02:50 +0000
commitadfd2bc45b301cc8a895c7ba63c78cdd0b4df50b (patch)
tree2216182393631de464256f461cecb8c23f3085c2
parent412f42cb112802614e541ca186d3e35006f28be9 (diff)
downloadsonarqube-adfd2bc45b301cc8a895c7ba63c78cdd0b4df50b.tar.gz
sonarqube-adfd2bc45b301cc8a895c7ba63c78cdd0b4df50b.zip
SONAR-21819 Add POST /dop-translation/bound-projects endpoint.
-rw-r--r--server/sonar-db-dao/src/main/java/org/sonar/db/alm/setting/ProjectAlmSettingDto.java2
-rw-r--r--server/sonar-webserver-common/src/main/java/org/sonar/server/common/project/ImportProjectRequest.java38
-rw-r--r--server/sonar-webserver-common/src/main/java/org/sonar/server/common/project/ImportProjectService.java95
-rw-r--r--server/sonar-webserver-common/src/main/java/org/sonar/server/common/project/ImportedProject.java26
-rw-r--r--server/sonar-webserver-common/src/test/java/org/sonar/server/common/project/ImportProjectServiceTest.java212
-rw-r--r--server/sonar-webserver-webapi-v2/build.gradle1
-rw-r--r--server/sonar-webserver-webapi-v2/src/main/java/org/sonar/server/v2/WebApiEndpoints.java2
-rw-r--r--server/sonar-webserver-webapi-v2/src/main/java/org/sonar/server/v2/api/projects/controller/BoundProjectsController.java49
-rw-r--r--server/sonar-webserver-webapi-v2/src/main/java/org/sonar/server/v2/api/projects/controller/DefaultBoundProjectsController.java58
-rw-r--r--server/sonar-webserver-webapi-v2/src/main/java/org/sonar/server/v2/api/projects/controller/package-info.java23
-rw-r--r--server/sonar-webserver-webapi-v2/src/main/java/org/sonar/server/v2/api/projects/request/BoundProjectCreateRestRequest.java68
-rw-r--r--server/sonar-webserver-webapi-v2/src/main/java/org/sonar/server/v2/api/projects/request/package-info.java (renamed from server/sonar-webserver-webapi/src/main/java/org/sonar/server/newcodeperiod/package-info.java)2
-rw-r--r--server/sonar-webserver-webapi-v2/src/main/java/org/sonar/server/v2/api/projects/response/BoundProjectCreateRestResponse.java31
-rw-r--r--server/sonar-webserver-webapi-v2/src/main/java/org/sonar/server/v2/api/projects/response/package-info.java23
-rw-r--r--server/sonar-webserver-webapi-v2/src/main/java/org/sonar/server/v2/config/PlatformLevel4WebConfig.java10
-rw-r--r--server/sonar-webserver-webapi-v2/src/test/java/org/sonar/server/v2/api/projects/controller/DefaultBoundProjectsControllerTest.java151
-rw-r--r--server/sonar-webserver-webapi/src/it/java/org/sonar/server/almintegration/ws/github/ImportGithubProjectActionIT.java7
-rw-r--r--server/sonar-webserver-webapi/src/it/java/org/sonar/server/almintegration/ws/gitlab/ImportGitLabProjectActionIT.java7
-rw-r--r--server/sonar-webserver-webapi/src/main/java/org/sonar/server/almintegration/ws/github/ImportGithubProjectAction.java77
-rw-r--r--server/sonar-webserver-webapi/src/main/java/org/sonar/server/almintegration/ws/gitlab/ImportGitLabProjectAction.java69
-rw-r--r--server/sonar-webserver-webapi/src/main/java/org/sonar/server/project/ws/ProjectsWsModule.java2
-rw-r--r--server/sonar-webserver-webapi/src/test/java/org/sonar/server/project/ws/ProjectsWsModuleTest.java2
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);
}
}