Browse Source

SONAR-21819 Add POST /dop-translation/bound-projects endpoint.

tags/10.5.0.89998
Wojtek Wajerowicz 1 month ago
parent
commit
adfd2bc45b
22 changed files with 830 additions and 125 deletions
  1. 1
    1
      server/sonar-db-dao/src/main/java/org/sonar/db/alm/setting/ProjectAlmSettingDto.java
  2. 38
    0
      server/sonar-webserver-common/src/main/java/org/sonar/server/common/project/ImportProjectRequest.java
  3. 95
    0
      server/sonar-webserver-common/src/main/java/org/sonar/server/common/project/ImportProjectService.java
  4. 26
    0
      server/sonar-webserver-common/src/main/java/org/sonar/server/common/project/ImportedProject.java
  5. 212
    0
      server/sonar-webserver-common/src/test/java/org/sonar/server/common/project/ImportProjectServiceTest.java
  6. 1
    0
      server/sonar-webserver-webapi-v2/build.gradle
  7. 2
    0
      server/sonar-webserver-webapi-v2/src/main/java/org/sonar/server/v2/WebApiEndpoints.java
  8. 49
    0
      server/sonar-webserver-webapi-v2/src/main/java/org/sonar/server/v2/api/projects/controller/BoundProjectsController.java
  9. 58
    0
      server/sonar-webserver-webapi-v2/src/main/java/org/sonar/server/v2/api/projects/controller/DefaultBoundProjectsController.java
  10. 23
    0
      server/sonar-webserver-webapi-v2/src/main/java/org/sonar/server/v2/api/projects/controller/package-info.java
  11. 68
    0
      server/sonar-webserver-webapi-v2/src/main/java/org/sonar/server/v2/api/projects/request/BoundProjectCreateRestRequest.java
  12. 1
    1
      server/sonar-webserver-webapi-v2/src/main/java/org/sonar/server/v2/api/projects/request/package-info.java
  13. 31
    0
      server/sonar-webserver-webapi-v2/src/main/java/org/sonar/server/v2/api/projects/response/BoundProjectCreateRestResponse.java
  14. 23
    0
      server/sonar-webserver-webapi-v2/src/main/java/org/sonar/server/v2/api/projects/response/package-info.java
  15. 9
    1
      server/sonar-webserver-webapi-v2/src/main/java/org/sonar/server/v2/config/PlatformLevel4WebConfig.java
  16. 151
    0
      server/sonar-webserver-webapi-v2/src/test/java/org/sonar/server/v2/api/projects/controller/DefaultBoundProjectsControllerTest.java
  17. 5
    2
      server/sonar-webserver-webapi/src/it/java/org/sonar/server/almintegration/ws/github/ImportGithubProjectActionIT.java
  18. 5
    2
      server/sonar-webserver-webapi/src/it/java/org/sonar/server/almintegration/ws/gitlab/ImportGitLabProjectActionIT.java
  19. 14
    63
      server/sonar-webserver-webapi/src/main/java/org/sonar/server/almintegration/ws/github/ImportGithubProjectAction.java
  20. 15
    54
      server/sonar-webserver-webapi/src/main/java/org/sonar/server/almintegration/ws/gitlab/ImportGitLabProjectAction.java
  21. 2
    0
      server/sonar-webserver-webapi/src/main/java/org/sonar/server/project/ws/ProjectsWsModule.java
  22. 1
    1
      server/sonar-webserver-webapi/src/test/java/org/sonar/server/project/ws/ProjectsWsModuleTest.java

+ 1
- 1
server/sonar-db-dao/src/main/java/org/sonar/db/alm/setting/ProjectAlmSettingDto.java View File

@@ -70,7 +70,7 @@ public class ProjectAlmSettingDto {
private long updatedAt;
private long createdAt;

String getUuid() {
public String getUuid() {
return uuid;
}


+ 38
- 0
server/sonar-webserver-common/src/main/java/org/sonar/server/common/project/ImportProjectRequest.java View File

@@ -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) {

}

+ 95
- 0
server/sonar-webserver-common/src/main/java/org/sonar/server/common/project/ImportProjectService.java View File

@@ -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);
}
}

}

+ 26
- 0
server/sonar-webserver-common/src/main/java/org/sonar/server/common/project/ImportedProject.java View File

@@ -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){
}

+ 212
- 0
server/sonar-webserver-common/src/test/java/org/sonar/server/common/project/ImportProjectServiceTest.java View File

@@ -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;
}

}

+ 1
- 0
server/sonar-webserver-webapi-v2/build.gradle View File

@@ -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'

+ 2
- 0
server/sonar-webserver-webapi-v2/src/main/java/org/sonar/server/v2/WebApiEndpoints.java View File

@@ -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() {

+ 49
- 0
server/sonar-webserver-webapi-v2/src/main/java/org/sonar/server/v2/api/projects/controller/BoundProjectsController.java View File

@@ -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);

}

+ 58
- 0
server/sonar-webserver-webapi-v2/src/main/java/org/sonar/server/v2/api/projects/controller/DefaultBoundProjectsController.java View File

@@ -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());
}
}

+ 23
- 0
server/sonar-webserver-webapi-v2/src/main/java/org/sonar/server/v2/api/projects/controller/package-info.java View File

@@ -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;

+ 68
- 0
server/sonar-webserver-webapi-v2/src/main/java/org/sonar/server/v2/api/projects/request/BoundProjectCreateRestRequest.java View File

@@ -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
) {
}

server/sonar-webserver-webapi/src/main/java/org/sonar/server/newcodeperiod/package-info.java → server/sonar-webserver-webapi-v2/src/main/java/org/sonar/server/v2/api/projects/request/package-info.java View File

@@ -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;

+ 31
- 0
server/sonar-webserver-webapi-v2/src/main/java/org/sonar/server/v2/api/projects/response/BoundProjectCreateRestResponse.java View File

@@ -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) {
}

+ 23
- 0
server/sonar-webserver-webapi-v2/src/main/java/org/sonar/server/v2/api/projects/response/package-info.java View File

@@ -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;

+ 9
- 1
server/sonar-webserver-webapi-v2/src/main/java/org/sonar/server/v2/config/PlatformLevel4WebConfig.java View File

@@ -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);
}

}

+ 151
- 0
server/sonar-webserver-webapi-v2/src/test/java/org/sonar/server/v2/api/projects/controller/DefaultBoundProjectsControllerTest.java View File

@@ -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"
}
"""));
}
}

+ 5
- 2
server/sonar-webserver-webapi/src/it/java/org/sonar/server/almintegration/ws/github/ImportGithubProjectActionIT.java View File

@@ -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() {

+ 5
- 2
server/sonar-webserver-webapi/src/it/java/org/sonar/server/almintegration/ws/gitlab/ImportGitLabProjectActionIT.java View File

@@ -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

+ 14
- 63
server/sonar-webserver-webapi/src/main/java/org/sonar/server/almintegration/ws/github/ImportGithubProjectAction.java View File

@@ -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);
}
}

+ 15
- 54
server/sonar-webserver-webapi/src/main/java/org/sonar/server/almintegration/ws/gitlab/ImportGitLabProjectAction.java View File

@@ -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);
}
}

+ 2
- 0
server/sonar-webserver-webapi/src/main/java/org/sonar/server/project/ws/ProjectsWsModule.java View File

@@ -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,

+ 1
- 1
server/sonar-webserver-webapi/src/test/java/org/sonar/server/project/ws/ProjectsWsModuleTest.java View File

@@ -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);
}
}

Loading…
Cancel
Save