@@ -0,0 +1,124 @@ | |||
/* | |||
* SonarQube | |||
* Copyright (C) 2009-2024 SonarSource SA | |||
* mailto:info AT sonarsource DOT com | |||
* | |||
* This program is free software; you can redistribute it and/or | |||
* modify it under the terms of the GNU Lesser General Public | |||
* License as published by the Free Software Foundation; either | |||
* version 3 of the License, or (at your option) any later version. | |||
* | |||
* This program is distributed in the hope that it will be useful, | |||
* but WITHOUT ANY WARRANTY; without even the implied warranty of | |||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |||
* Lesser General Public License for more details. | |||
* | |||
* You should have received a copy of the GNU Lesser General Public License | |||
* along with this program; if not, write to the Free Software Foundation, | |||
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | |||
*/ | |||
package org.sonar.server.common.almsettings.azuredevops; | |||
import java.util.Optional; | |||
import org.jetbrains.annotations.Nullable; | |||
import org.sonar.alm.client.azure.AzureDevOpsHttpClient; | |||
import org.sonar.alm.client.azure.AzureDevopsServerException; | |||
import org.sonar.alm.client.azure.GsonAzureRepo; | |||
import org.sonar.db.DbClient; | |||
import org.sonar.db.DbSession; | |||
import org.sonar.db.alm.pat.AlmPatDto; | |||
import org.sonar.db.alm.setting.AlmSettingDto; | |||
import org.sonar.db.alm.setting.ProjectAlmSettingDto; | |||
import org.sonar.db.project.CreationMethod; | |||
import org.sonar.db.project.ProjectDto; | |||
import org.sonar.server.common.almintegration.ProjectKeyGenerator; | |||
import org.sonar.server.common.almsettings.DevOpsProjectCreator; | |||
import org.sonar.server.common.almsettings.DevOpsProjectDescriptor; | |||
import org.sonar.server.common.project.ProjectCreator; | |||
import org.sonar.server.component.ComponentCreationData; | |||
import org.sonar.server.user.UserSession; | |||
import static com.google.common.base.Preconditions.checkArgument; | |||
import static java.lang.String.format; | |||
import static java.util.Objects.requireNonNull; | |||
public class AzureDevOpsProjectCreator implements DevOpsProjectCreator { | |||
private final DbClient dbClient; | |||
private final AlmSettingDto almSettingDto; | |||
private final DevOpsProjectDescriptor devOpsProjectDescriptor; | |||
private final UserSession userSession; | |||
private final AzureDevOpsHttpClient azureDevOpsHttpClient; | |||
private final ProjectCreator projectCreator; | |||
private final ProjectKeyGenerator projectKeyGenerator; | |||
public AzureDevOpsProjectCreator(DbClient dbClient, AlmSettingDto almSettingDto, DevOpsProjectDescriptor devOpsProjectDescriptor, UserSession userSession, | |||
AzureDevOpsHttpClient azureDevOpsHttpClient, ProjectCreator projectCreator, ProjectKeyGenerator projectKeyGenerator) { | |||
this.dbClient = dbClient; | |||
this.almSettingDto = almSettingDto; | |||
this.devOpsProjectDescriptor = devOpsProjectDescriptor; | |||
this.userSession = userSession; | |||
this.azureDevOpsHttpClient = azureDevOpsHttpClient; | |||
this.projectCreator = projectCreator; | |||
this.projectKeyGenerator = projectKeyGenerator; | |||
} | |||
@Override | |||
public boolean isScanAllowedUsingPermissionsFromDevopsPlatform() { | |||
throw new UnsupportedOperationException("Not Implemented"); | |||
} | |||
@Override | |||
public ComponentCreationData createProjectAndBindToDevOpsPlatform(DbSession dbSession, CreationMethod creationMethod, Boolean monorepo, @Nullable String projectKey, | |||
@Nullable String projectName) { | |||
String pat = findPersonalAccessTokenOrThrow(dbSession, almSettingDto); | |||
String url = requireNonNull(almSettingDto.getUrl(), "DevOps Platform url cannot be null"); | |||
checkArgument(devOpsProjectDescriptor.projectIdentifier() != null, "DevOps Project Identifier cannot be null for Azure DevOps"); | |||
GsonAzureRepo repo = fetchAzureDevOpsProject(url, pat, devOpsProjectDescriptor.projectIdentifier(), devOpsProjectDescriptor.repositoryIdentifier()); | |||
ComponentCreationData componentCreationData = projectCreator.createProject( | |||
dbSession, | |||
getProjectKey(projectKey, repo), | |||
getProjectName(projectName, repo), | |||
repo.getDefaultBranchName(), | |||
creationMethod); | |||
ProjectDto projectDto = Optional.ofNullable(componentCreationData.projectDto()).orElseThrow(); | |||
createProjectAlmSettingDto(dbSession, repo, projectDto, almSettingDto, monorepo); | |||
return componentCreationData; | |||
} | |||
private String findPersonalAccessTokenOrThrow(DbSession dbSession, AlmSettingDto almSettingDto) { | |||
String userUuid = requireNonNull(userSession.getUuid(), "User UUID cannot be null."); | |||
Optional<AlmPatDto> almPatDto = dbClient.almPatDao().selectByUserAndAlmSetting(dbSession, userUuid, almSettingDto); | |||
return almPatDto.map(AlmPatDto::getPersonalAccessToken) | |||
.orElseThrow(() -> new IllegalArgumentException(String.format("personal access token for '%s' is missing", almSettingDto.getKey()))); | |||
} | |||
private GsonAzureRepo fetchAzureDevOpsProject(String azureDevOpsUrl, String pat, String projectIdentifier, String repositoryIdentifier) { | |||
try { | |||
return azureDevOpsHttpClient.getRepo(azureDevOpsUrl, pat, projectIdentifier, repositoryIdentifier); | |||
} catch (AzureDevopsServerException e) { | |||
throw new IllegalStateException(format("Failed to fetch AzureDevOps repository '%s' from project '%s' from '%s'", repositoryIdentifier, projectIdentifier, azureDevOpsUrl), | |||
e); | |||
} | |||
} | |||
private String getProjectKey(@Nullable String projectKey, GsonAzureRepo repository) { | |||
return Optional.ofNullable(projectKey).orElseGet(() -> projectKeyGenerator.generateUniqueProjectKey(repository.getProject().getName(), repository.getName())); | |||
} | |||
private static String getProjectName(@Nullable String projectName, GsonAzureRepo repository) { | |||
return Optional.ofNullable(projectName).orElse(repository.getName()); | |||
} | |||
private void createProjectAlmSettingDto(DbSession dbSession, GsonAzureRepo repository, ProjectDto projectDto, AlmSettingDto almSettingDto, Boolean monorepo) { | |||
ProjectAlmSettingDto projectAlmSettingDto = new ProjectAlmSettingDto() | |||
.setAlmSettingUuid(almSettingDto.getUuid()) | |||
.setAlmRepo(repository.getName()) | |||
.setAlmSlug(repository.getProject().getName()) | |||
.setProjectUuid(projectDto.getUuid()) | |||
.setSummaryCommentEnabled(true) | |||
.setMonorepo(monorepo); | |||
dbClient.projectAlmSettingDao().insertOrUpdate(dbSession, projectAlmSettingDto, almSettingDto.getKey(), projectDto.getName(), projectDto.getKey()); | |||
} | |||
} |
@@ -0,0 +1,65 @@ | |||
/* | |||
* SonarQube | |||
* Copyright (C) 2009-2024 SonarSource SA | |||
* mailto:info AT sonarsource DOT com | |||
* | |||
* This program is free software; you can redistribute it and/or | |||
* modify it under the terms of the GNU Lesser General Public | |||
* License as published by the Free Software Foundation; either | |||
* version 3 of the License, or (at your option) any later version. | |||
* | |||
* This program is distributed in the hope that it will be useful, | |||
* but WITHOUT ANY WARRANTY; without even the implied warranty of | |||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |||
* Lesser General Public License for more details. | |||
* | |||
* You should have received a copy of the GNU Lesser General Public License | |||
* along with this program; if not, write to the Free Software Foundation, | |||
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | |||
*/ | |||
package org.sonar.server.common.almsettings.azuredevops; | |||
import java.util.Map; | |||
import java.util.Optional; | |||
import org.sonar.alm.client.azure.AzureDevOpsHttpClient; | |||
import org.sonar.db.DbClient; | |||
import org.sonar.db.DbSession; | |||
import org.sonar.db.alm.setting.ALM; | |||
import org.sonar.db.alm.setting.AlmSettingDto; | |||
import org.sonar.server.common.almintegration.ProjectKeyGenerator; | |||
import org.sonar.server.common.almsettings.DevOpsProjectCreator; | |||
import org.sonar.server.common.almsettings.DevOpsProjectCreatorFactory; | |||
import org.sonar.server.common.almsettings.DevOpsProjectDescriptor; | |||
import org.sonar.server.common.project.ProjectCreator; | |||
import org.sonar.server.user.UserSession; | |||
public class AzureDevOpsProjectCreatorFactory implements DevOpsProjectCreatorFactory { | |||
private final DbClient dbClient; | |||
private final UserSession userSession; | |||
private final AzureDevOpsHttpClient azureDevOpsHttpClient; | |||
private final ProjectCreator projectCreator; | |||
private final ProjectKeyGenerator projectKeyGenerator; | |||
public AzureDevOpsProjectCreatorFactory(DbClient dbClient, UserSession userSession, AzureDevOpsHttpClient azureDevOpsHttpClient, ProjectCreator projectCreator, | |||
ProjectKeyGenerator projectKeyGenerator) { | |||
this.dbClient = dbClient; | |||
this.userSession = userSession; | |||
this.azureDevOpsHttpClient = azureDevOpsHttpClient; | |||
this.projectCreator = projectCreator; | |||
this.projectKeyGenerator = projectKeyGenerator; | |||
} | |||
@Override | |||
public Optional<DevOpsProjectCreator> getDevOpsProjectCreator(DbSession dbSession, Map<String, String> characteristics) { | |||
return Optional.empty(); | |||
} | |||
@Override | |||
public Optional<DevOpsProjectCreator> getDevOpsProjectCreator(AlmSettingDto almSettingDto, DevOpsProjectDescriptor devOpsProjectDescriptor) { | |||
if (almSettingDto.getAlm() != ALM.AZURE_DEVOPS) { | |||
return Optional.empty(); | |||
} | |||
return Optional.of(new AzureDevOpsProjectCreator(dbClient, almSettingDto, devOpsProjectDescriptor, userSession, azureDevOpsHttpClient, projectCreator, projectKeyGenerator)); | |||
} | |||
} |
@@ -0,0 +1,23 @@ | |||
/* | |||
* SonarQube | |||
* Copyright (C) 2009-2024 SonarSource SA | |||
* mailto:info AT sonarsource DOT com | |||
* | |||
* This program is free software; you can redistribute it and/or | |||
* modify it under the terms of the GNU Lesser General Public | |||
* License as published by the Free Software Foundation; either | |||
* version 3 of the License, or (at your option) any later version. | |||
* | |||
* This program is distributed in the hope that it will be useful, | |||
* but WITHOUT ANY WARRANTY; without even the implied warranty of | |||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |||
* Lesser General Public License for more details. | |||
* | |||
* You should have received a copy of the GNU Lesser General Public License | |||
* along with this program; if not, write to the Free Software Foundation, | |||
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | |||
*/ | |||
@ParametersAreNonnullByDefault | |||
package org.sonar.server.common.almsettings.azuredevops; | |||
import javax.annotation.ParametersAreNonnullByDefault; |
@@ -0,0 +1,80 @@ | |||
/* | |||
* SonarQube | |||
* Copyright (C) 2009-2024 SonarSource SA | |||
* mailto:info AT sonarsource DOT com | |||
* | |||
* This program is free software; you can redistribute it and/or | |||
* modify it under the terms of the GNU Lesser General Public | |||
* License as published by the Free Software Foundation; either | |||
* version 3 of the License, or (at your option) any later version. | |||
* | |||
* This program is distributed in the hope that it will be useful, | |||
* but WITHOUT ANY WARRANTY; without even the implied warranty of | |||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |||
* Lesser General Public License for more details. | |||
* | |||
* You should have received a copy of the GNU Lesser General Public License | |||
* along with this program; if not, write to the Free Software Foundation, | |||
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | |||
*/ | |||
package org.sonar.server.common.almsettings.azuredevops; | |||
import org.junit.jupiter.api.Test; | |||
import org.junit.jupiter.api.extension.ExtendWith; | |||
import org.mockito.InjectMocks; | |||
import org.mockito.Mock; | |||
import org.mockito.junit.jupiter.MockitoExtension; | |||
import org.sonar.alm.client.azure.AzureDevOpsHttpClient; | |||
import org.sonar.db.DbClient; | |||
import org.sonar.db.alm.setting.ALM; | |||
import org.sonar.db.alm.setting.AlmSettingDto; | |||
import org.sonar.server.common.almintegration.ProjectKeyGenerator; | |||
import org.sonar.server.common.almsettings.DevOpsProjectCreator; | |||
import org.sonar.server.common.almsettings.DevOpsProjectDescriptor; | |||
import org.sonar.server.common.project.ProjectCreator; | |||
import org.sonar.server.user.UserSession; | |||
import static org.assertj.core.api.Assertions.assertThat; | |||
import static org.mockito.Mockito.mock; | |||
import static org.mockito.Mockito.when; | |||
@ExtendWith(MockitoExtension.class) | |||
class AzureDevOpsProjectCreatorFactoryTest { | |||
@Mock() | |||
private DbClient dbClient; | |||
@Mock | |||
private UserSession userSession; | |||
@Mock | |||
private AzureDevOpsHttpClient azureDevOpsHttpClient; | |||
@Mock | |||
private ProjectCreator projectCreator; | |||
@Mock | |||
private ProjectKeyGenerator projectKeyGenerator; | |||
@InjectMocks | |||
private AzureDevOpsProjectCreatorFactory underTest; | |||
@Test | |||
void getDevOpsProjectCreator_whenAlmIsNotAzureDevOps_shouldReturnEmpty() { | |||
AlmSettingDto almSettingDto = mock(AlmSettingDto.class); | |||
when(almSettingDto.getAlm()).thenReturn(ALM.GITLAB); | |||
DevOpsProjectDescriptor devOpsProjectDescriptor = new DevOpsProjectDescriptor(ALM.GITLAB, null, null, "bitbucket_project"); | |||
assertThat(underTest.getDevOpsProjectCreator(almSettingDto, devOpsProjectDescriptor)).isEmpty(); | |||
} | |||
@Test | |||
void getDevOpsProjectCreator_whenAlmIsAzureDevOps_shouldReturnProjectCreator() { | |||
AlmSettingDto almSettingDto = mock(AlmSettingDto.class); | |||
when(almSettingDto.getAlm()).thenReturn(ALM.AZURE_DEVOPS); | |||
DevOpsProjectDescriptor devOpsProjectDescriptor = new DevOpsProjectDescriptor(ALM.AZURE_DEVOPS, null, "project-identifier", "bitbucket_project"); | |||
DevOpsProjectCreator expectedProjectCreator = new AzureDevOpsProjectCreator(dbClient, almSettingDto, devOpsProjectDescriptor, userSession, azureDevOpsHttpClient, | |||
projectCreator, projectKeyGenerator); | |||
DevOpsProjectCreator devOpsProjectCreator = underTest.getDevOpsProjectCreator(almSettingDto, devOpsProjectDescriptor).orElseThrow(); | |||
assertThat(devOpsProjectCreator).usingRecursiveComparison().isEqualTo(expectedProjectCreator); | |||
} | |||
} |
@@ -0,0 +1,211 @@ | |||
/* | |||
* SonarQube | |||
* Copyright (C) 2009-2024 SonarSource SA | |||
* mailto:info AT sonarsource DOT com | |||
* | |||
* This program is free software; you can redistribute it and/or | |||
* modify it under the terms of the GNU Lesser General Public | |||
* License as published by the Free Software Foundation; either | |||
* version 3 of the License, or (at your option) any later version. | |||
* | |||
* This program is distributed in the hope that it will be useful, | |||
* but WITHOUT ANY WARRANTY; without even the implied warranty of | |||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |||
* Lesser General Public License for more details. | |||
* | |||
* You should have received a copy of the GNU Lesser General Public License | |||
* along with this program; if not, write to the Free Software Foundation, | |||
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | |||
*/ | |||
package org.sonar.server.common.almsettings.azuredevops; | |||
import java.util.Optional; | |||
import org.junit.jupiter.api.BeforeEach; | |||
import org.junit.jupiter.api.Test; | |||
import org.junit.jupiter.api.extension.ExtendWith; | |||
import org.mockito.Answers; | |||
import org.mockito.ArgumentCaptor; | |||
import org.mockito.InjectMocks; | |||
import org.mockito.Mock; | |||
import org.mockito.junit.jupiter.MockitoExtension; | |||
import org.sonar.alm.client.azure.AzureDevOpsHttpClient; | |||
import org.sonar.alm.client.azure.AzureDevopsServerException; | |||
import org.sonar.alm.client.azure.GsonAzureRepo; | |||
import org.sonar.db.DbClient; | |||
import org.sonar.db.DbSession; | |||
import org.sonar.db.alm.pat.AlmPatDto; | |||
import org.sonar.db.alm.setting.ALM; | |||
import org.sonar.db.alm.setting.AlmSettingDto; | |||
import org.sonar.db.alm.setting.ProjectAlmSettingDto; | |||
import org.sonar.db.project.CreationMethod; | |||
import org.sonar.db.project.ProjectDto; | |||
import org.sonar.server.common.almintegration.ProjectKeyGenerator; | |||
import org.sonar.server.common.almsettings.DevOpsProjectDescriptor; | |||
import org.sonar.server.common.project.ProjectCreator; | |||
import org.sonar.server.component.ComponentCreationData; | |||
import org.sonar.server.user.UserSession; | |||
import static org.assertj.core.api.AssertionsForClassTypes.assertThat; | |||
import static org.assertj.core.api.AssertionsForClassTypes.assertThatExceptionOfType; | |||
import static org.mockito.ArgumentMatchers.any; | |||
import static org.mockito.ArgumentMatchers.eq; | |||
import static org.mockito.Mockito.lenient; | |||
import static org.mockito.Mockito.mock; | |||
import static org.mockito.Mockito.verify; | |||
import static org.mockito.Mockito.when; | |||
@ExtendWith(MockitoExtension.class) | |||
class AzureDevOpsProjectCreatorTest { | |||
private static final String USER_LOGIN = "userLogin"; | |||
private static final String USER_UUID = "userUuid"; | |||
private static final String REPOSITORY_NAME = "repositoryName"; | |||
private static final String DEVOPS_PROJECT_ID = "project-identifier"; | |||
private static final String DEVOPS_PROJECT_NAME = "devops-project-name"; | |||
private static final String ALM_SETTING_KEY = "azuredevops_config_1"; | |||
private static final String ALM_SETTING_UUID = "almSettingUuid"; | |||
private static final String USER_PAT = "1234"; | |||
public static final String AZURE_DEVOPS_URL = "http://api.com"; | |||
private static final String MAIN_BRANCH_NAME = "defaultBranch"; | |||
private static final String PROJECT_UUID = "projectUuid"; | |||
@Mock(answer = Answers.RETURNS_DEEP_STUBS) | |||
private DbClient dbClient; | |||
@Mock | |||
private AlmSettingDto almSettingDto; | |||
@Mock | |||
private DevOpsProjectDescriptor devOpsProjectDescriptor; | |||
@Mock | |||
private UserSession userSession; | |||
@Mock | |||
private AzureDevOpsHttpClient azureDevOpsHttpClient; | |||
@Mock | |||
private ProjectCreator projectCreator; | |||
@Mock | |||
private ProjectKeyGenerator projectKeyGenerator; | |||
@InjectMocks | |||
private AzureDevOpsProjectCreator underTest; | |||
@BeforeEach | |||
void setup() { | |||
lenient().when(userSession.getLogin()).thenReturn(USER_LOGIN); | |||
lenient().when(userSession.getUuid()).thenReturn(USER_UUID); | |||
lenient().when(almSettingDto.getKey()).thenReturn(ALM_SETTING_KEY); | |||
lenient().when(almSettingDto.getUuid()).thenReturn(ALM_SETTING_UUID); | |||
lenient().when(almSettingDto.getUrl()).thenReturn(AZURE_DEVOPS_URL); | |||
lenient().when(devOpsProjectDescriptor.repositoryIdentifier()).thenReturn(REPOSITORY_NAME); | |||
lenient().when(devOpsProjectDescriptor.projectIdentifier()).thenReturn(DEVOPS_PROJECT_ID); | |||
lenient().when(devOpsProjectDescriptor.alm()).thenReturn(ALM.BITBUCKET_CLOUD); | |||
} | |||
@Test | |||
void isScanAllowedUsingPermissionsFromDevopsPlatform_shouldThrowUnsupportedOperationException() { | |||
assertThatExceptionOfType(UnsupportedOperationException.class) | |||
.isThrownBy(() -> underTest.isScanAllowedUsingPermissionsFromDevopsPlatform()) | |||
.withMessage("Not Implemented"); | |||
} | |||
@Test | |||
void createProjectAndBindToDevOpsPlatform_whenPatIsMissing_shouldThrow() { | |||
assertThatExceptionOfType(IllegalArgumentException.class) | |||
.isThrownBy(() -> underTest.createProjectAndBindToDevOpsPlatform(mock(DbSession.class), CreationMethod.ALM_IMPORT_API, false, null, null)) | |||
.withMessage("personal access token for 'azuredevops_config_1' is missing"); | |||
} | |||
@Test | |||
void createProjectAndBindToDevOpsPlatform_whenRepositoryNotFound_shouldThrow() { | |||
mockPatForUser(); | |||
when(azureDevOpsHttpClient.getRepo(AZURE_DEVOPS_URL, USER_PAT, DEVOPS_PROJECT_ID, REPOSITORY_NAME)) | |||
.thenThrow(new AzureDevopsServerException(404, "Problem fetching repository from AzureDevOps")); | |||
assertThatExceptionOfType(IllegalStateException.class) | |||
.isThrownBy(() -> underTest.createProjectAndBindToDevOpsPlatform(mock(DbSession.class), CreationMethod.ALM_IMPORT_API, false, null, null)) | |||
.withMessage("Failed to fetch AzureDevOps repository 'repositoryName' from project 'project-identifier' from 'http://api.com'"); | |||
} | |||
@Test | |||
void createProjectAndBindToDevOpsPlatform_projectIdentifierIsNull_shouldThrow() { | |||
mockPatForUser(); | |||
lenient().when(devOpsProjectDescriptor.projectIdentifier()).thenReturn(null); | |||
assertThatExceptionOfType(IllegalArgumentException.class) | |||
.isThrownBy(() -> underTest.createProjectAndBindToDevOpsPlatform(mock(DbSession.class), CreationMethod.ALM_IMPORT_API, false, null, null)) | |||
.withMessage("DevOps Project Identifier cannot be null for Azure DevOps"); | |||
} | |||
@Test | |||
void createProjectAndBindToDevOpsPlatform_whenRepoFoundOnAzureDevOps_successfullyCreatesProject() { | |||
mockPatForUser(); | |||
mockAzureDevOpsProject(); | |||
mockProjectCreation("projectKey", "projectName"); | |||
underTest.createProjectAndBindToDevOpsPlatform(mock(DbSession.class), CreationMethod.ALM_IMPORT_API, true, "projectKey", "projectName"); | |||
ArgumentCaptor<ProjectAlmSettingDto> projectAlmSettingCaptor = ArgumentCaptor.forClass(ProjectAlmSettingDto.class); | |||
verify(dbClient.projectAlmSettingDao()).insertOrUpdate(any(), projectAlmSettingCaptor.capture(), eq(ALM_SETTING_KEY), eq("projectName"), eq("projectKey")); | |||
ProjectAlmSettingDto createdProjectAlmSettingDto = projectAlmSettingCaptor.getValue(); | |||
assertThat(createdProjectAlmSettingDto.getAlmSettingUuid()).isEqualTo(ALM_SETTING_UUID); | |||
assertThat(createdProjectAlmSettingDto.getAlmRepo()).isEqualTo(REPOSITORY_NAME); | |||
assertThat(createdProjectAlmSettingDto.getAlmSlug()).isEqualTo(DEVOPS_PROJECT_NAME); | |||
assertThat(createdProjectAlmSettingDto.getProjectUuid()).isEqualTo(PROJECT_UUID); | |||
assertThat(createdProjectAlmSettingDto.getMonorepo()).isTrue(); | |||
} | |||
@Test | |||
void createProjectAndBindToDevOpsPlatform_whenNoKeyAndNameSpecified_generatesKeyAndUsesAzureRepositoryName() { | |||
mockPatForUser(); | |||
mockAzureDevOpsProject(); | |||
String generatedProjectKey = "generatedProjectKey"; | |||
when(projectKeyGenerator.generateUniqueProjectKey(DEVOPS_PROJECT_NAME, REPOSITORY_NAME)).thenReturn(generatedProjectKey); | |||
mockProjectCreation(generatedProjectKey, REPOSITORY_NAME); | |||
underTest.createProjectAndBindToDevOpsPlatform(mock(DbSession.class), CreationMethod.ALM_IMPORT_API, true, null, null); | |||
ArgumentCaptor<ProjectAlmSettingDto> projectAlmSettingCaptor = ArgumentCaptor.forClass(ProjectAlmSettingDto.class); | |||
verify(dbClient.projectAlmSettingDao()).insertOrUpdate(any(), projectAlmSettingCaptor.capture(), eq(ALM_SETTING_KEY), eq(REPOSITORY_NAME), eq(generatedProjectKey)); | |||
ProjectAlmSettingDto createdProjectAlmSettingDto = projectAlmSettingCaptor.getValue(); | |||
assertThat(createdProjectAlmSettingDto.getAlmSettingUuid()).isEqualTo(ALM_SETTING_UUID); | |||
assertThat(createdProjectAlmSettingDto.getAlmRepo()).isEqualTo(REPOSITORY_NAME); | |||
assertThat(createdProjectAlmSettingDto.getAlmSlug()).isEqualTo(DEVOPS_PROJECT_NAME); | |||
assertThat(createdProjectAlmSettingDto.getProjectUuid()).isEqualTo(PROJECT_UUID); | |||
assertThat(createdProjectAlmSettingDto.getMonorepo()).isTrue(); | |||
} | |||
private void mockPatForUser() { | |||
AlmPatDto almPatDto = mock(); | |||
when(almPatDto.getPersonalAccessToken()).thenReturn(USER_PAT); | |||
when(dbClient.almPatDao().selectByUserAndAlmSetting(any(), eq(USER_UUID), eq(almSettingDto))).thenReturn(Optional.of(almPatDto)); | |||
} | |||
private void mockAzureDevOpsProject() { | |||
GsonAzureRepo repository = mock(GsonAzureRepo.class, Answers.RETURNS_DEEP_STUBS); | |||
when(repository.getName()).thenReturn(REPOSITORY_NAME); | |||
when(repository.getDefaultBranchName()).thenReturn(MAIN_BRANCH_NAME); | |||
when(repository.getProject().getName()).thenReturn(DEVOPS_PROJECT_NAME); | |||
when(azureDevOpsHttpClient.getRepo(AZURE_DEVOPS_URL, USER_PAT, DEVOPS_PROJECT_ID, REPOSITORY_NAME)).thenReturn(repository); | |||
} | |||
private void mockProjectCreation(String projectKey, String projectName) { | |||
ComponentCreationData componentCreationData = mock(); | |||
ProjectDto projectDto = mock(); | |||
when(componentCreationData.projectDto()).thenReturn(projectDto); | |||
when(projectDto.getUuid()).thenReturn(PROJECT_UUID); | |||
when(projectDto.getKey()).thenReturn(projectKey); | |||
when(projectDto.getName()).thenReturn(projectName); | |||
when(projectCreator.createProject(any(), eq(projectKey), eq(projectName), eq(MAIN_BRANCH_NAME), eq(CreationMethod.ALM_IMPORT_API))) | |||
.thenReturn(componentCreationData); | |||
} | |||
} |
@@ -60,24 +60,17 @@ import static org.mockito.Mockito.when; | |||
class GitlabProjectCreatorTest { | |||
private static final String PROJECT_UUID = "projectUuid"; | |||
private static final String USER_LOGIN = "userLogin"; | |||
private static final String USER_UUID = "userUuid"; | |||
private static final String REPOSITORY_PATH_WITH_NAMESPACE = "pathWith/namespace"; | |||
private static final String GITLAB_PROJECT_NAME = "gitlabProjectName"; | |||
private static final String REPOSITORY_ID = "1234"; | |||
private static final String MAIN_BRANCH_NAME = "defaultBranch"; | |||
private static final String ALM_SETTING_KEY = "gitlab_config_1"; | |||
private static final String ALM_SETTING_UUID = "almSettingUuid"; | |||
private static final String USER_PAT = "1234"; | |||
public static final String GITLAB_URL = "http://api.com"; | |||
@Mock(answer = Answers.RETURNS_DEEP_STUBS) | |||
private DbClient dbClient; | |||
@@ -159,7 +152,7 @@ class GitlabProjectCreatorTest { | |||
} | |||
@Test | |||
void createProjectAndBindToDevOpsPlatform_whenNoKeyAndNameSpecified_generatesKeyAndUsersGitlabProjectName() { | |||
void createProjectAndBindToDevOpsPlatform_whenNoKeyAndNameSpecified_generatesKeyAndUsesGitlabProjectName() { | |||
mockPatForUser(); | |||
mockGitlabProject(); | |||
mockMainBranch(); |
@@ -44,6 +44,7 @@ public record BoundProjectCreateRestRequest( | |||
Identifier of the DevOps platform repository to import: | |||
- repository slug for GitHub and Bitbucket (Cloud and Server) | |||
- repository id for GitLab | |||
- repository name for Azure DevOps | |||
""") | |||
String repositoryIdentifier, | |||
@@ -42,10 +42,14 @@ import org.sonar.db.project.ProjectDto; | |||
import org.sonar.db.user.UserDto; | |||
import org.sonar.server.almintegration.ws.ImportHelper; | |||
import org.sonar.server.common.almintegration.ProjectKeyGenerator; | |||
import org.sonar.server.common.almsettings.DevOpsProjectCreatorFactory; | |||
import org.sonar.server.common.almsettings.azuredevops.AzureDevOpsProjectCreatorFactory; | |||
import org.sonar.server.common.component.ComponentUpdater; | |||
import org.sonar.server.common.newcodeperiod.NewCodeDefinitionResolver; | |||
import org.sonar.server.common.permission.PermissionTemplateService; | |||
import org.sonar.server.common.permission.PermissionUpdater; | |||
import org.sonar.server.common.project.ImportProjectService; | |||
import org.sonar.server.common.project.ProjectCreator; | |||
import org.sonar.server.es.TestIndexers; | |||
import org.sonar.server.exceptions.BadRequestException; | |||
import org.sonar.server.exceptions.ForbiddenException; | |||
@@ -103,11 +107,16 @@ public class ImportAzureProjectActionIT { | |||
private final ProjectDefaultVisibility projectDefaultVisibility = mock(ProjectDefaultVisibility.class); | |||
private final ProjectKeyGenerator projectKeyGenerator = mock(ProjectKeyGenerator.class); | |||
private PlatformEditionProvider editionProvider = mock(PlatformEditionProvider.class); | |||
private NewCodeDefinitionResolver newCodeDefinitionResolver = new NewCodeDefinitionResolver(db.getDbClient(), editionProvider); | |||
private final ImportAzureProjectAction importAzureProjectAction = new ImportAzureProjectAction(db.getDbClient(), userSession, | |||
azureDevOpsHttpClient, projectDefaultVisibility, componentUpdater, importHelper, projectKeyGenerator, newCodeDefinitionResolver, | |||
defaultBranchNameResolver); | |||
private final PlatformEditionProvider editionProvider = mock(PlatformEditionProvider.class); | |||
private final NewCodeDefinitionResolver newCodeDefinitionResolver = new NewCodeDefinitionResolver(db.getDbClient(), editionProvider); | |||
private final ProjectCreator projectCreator = new ProjectCreator(userSession, projectDefaultVisibility, componentUpdater); | |||
private final DevOpsProjectCreatorFactory devOpsProjectCreatorFactory = new AzureDevOpsProjectCreatorFactory(db.getDbClient(), userSession, azureDevOpsHttpClient, projectCreator, | |||
projectKeyGenerator); | |||
private final ImportProjectService importProjectService = new ImportProjectService(db.getDbClient(), devOpsProjectCreatorFactory, userSession, componentUpdater, | |||
newCodeDefinitionResolver); | |||
private final ImportAzureProjectAction importAzureProjectAction = new ImportAzureProjectAction( | |||
importHelper, importProjectService); | |||
private final WsActionTester ws = new WsActionTester(importAzureProjectAction); | |||
@Before | |||
@@ -126,7 +135,7 @@ public class ImportAzureProjectActionIT { | |||
ProjectDto projectDto = getProjectDto(result); | |||
Optional<ProjectAlmSettingDto> projectAlmSettingDto = db.getDbClient().projectAlmSettingDao().selectByProject(db.getSession(), projectDto); | |||
Optional<ProjectAlmSettingDto> projectAlmSettingDto = db.getDbClient().projectAlmSettingDao().selectByProject(db.getSession(), projectDto); | |||
assertThat(projectAlmSettingDto.get().getAlmRepo()).isEqualTo("repo-name"); | |||
assertThat(projectAlmSettingDto.get().getAlmSettingUuid()).isEqualTo(almSetting.getUuid()); | |||
@@ -447,7 +456,6 @@ public class ImportAzureProjectActionIT { | |||
assertThatNoException().isThrownBy(request::execute); | |||
} | |||
@Test | |||
public void define() { | |||
WebService.Action def = ws.getDef(); |
@@ -19,45 +19,25 @@ | |||
*/ | |||
package org.sonar.server.almintegration.ws.azure; | |||
import java.util.Optional; | |||
import javax.annotation.Nullable; | |||
import javax.inject.Inject; | |||
import org.sonar.alm.client.azure.AzureDevOpsHttpClient; | |||
import org.sonar.alm.client.azure.GsonAzureRepo; | |||
import org.sonar.api.server.ws.Change; | |||
import org.sonar.api.server.ws.Request; | |||
import org.sonar.api.server.ws.Response; | |||
import org.sonar.api.server.ws.WebService; | |||
import org.sonar.db.DbClient; | |||
import org.sonar.db.DbSession; | |||
import org.sonar.db.alm.pat.AlmPatDto; | |||
import org.sonar.db.alm.setting.ALM; | |||
import org.sonar.db.alm.setting.AlmSettingDto; | |||
import org.sonar.db.alm.setting.ProjectAlmSettingDto; | |||
import org.sonar.db.component.BranchDto; | |||
import org.sonar.db.project.ProjectDto; | |||
import org.sonar.server.almintegration.ws.AlmIntegrationsWsAction; | |||
import org.sonar.server.almintegration.ws.ImportHelper; | |||
import org.sonar.server.common.almintegration.ProjectKeyGenerator; | |||
import org.sonar.server.common.component.ComponentCreationParameters; | |||
import org.sonar.server.common.component.ComponentUpdater; | |||
import org.sonar.server.common.component.NewComponent; | |||
import org.sonar.server.common.newcodeperiod.NewCodeDefinitionResolver; | |||
import org.sonar.server.component.ComponentCreationData; | |||
import org.sonar.server.project.DefaultBranchNameResolver; | |||
import org.sonar.server.project.ProjectDefaultVisibility; | |||
import org.sonar.server.user.UserSession; | |||
import org.sonar.server.common.project.ImportProjectRequest; | |||
import org.sonar.server.common.project.ImportProjectService; | |||
import org.sonar.server.common.project.ImportedProject; | |||
import org.sonarqube.ws.Projects.CreateWsResponse; | |||
import static java.util.Objects.requireNonNull; | |||
import static org.sonar.api.resources.Qualifiers.PROJECT; | |||
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.component.NewComponent.newComponentBuilder; | |||
import static org.sonar.server.common.newcodeperiod.NewCodeDefinitionResolver.NEW_CODE_PERIOD_TYPE_DESCRIPTION_PROJECT_CREATION; | |||
import static org.sonar.server.common.newcodeperiod.NewCodeDefinitionResolver.NEW_CODE_PERIOD_VALUE_DESCRIPTION_PROJECT_CREATION; | |||
import static org.sonar.server.common.newcodeperiod.NewCodeDefinitionResolver.checkNewCodeDefinitionParam; | |||
import static org.sonar.server.ws.WsUtils.writeProtobuf; | |||
import static org.sonarqube.ws.client.project.ProjectsWsParameters.PARAM_NEW_CODE_DEFINITION_TYPE; | |||
import static org.sonarqube.ws.client.project.ProjectsWsParameters.PARAM_NEW_CODE_DEFINITION_VALUE; | |||
@@ -67,30 +47,14 @@ public class ImportAzureProjectAction implements AlmIntegrationsWsAction { | |||
private static final String PARAM_REPOSITORY_NAME = "repositoryName"; | |||
private static final String PARAM_PROJECT_NAME = "projectName"; | |||
private final DbClient dbClient; | |||
private final UserSession userSession; | |||
private final AzureDevOpsHttpClient azureDevOpsHttpClient; | |||
private final ProjectDefaultVisibility projectDefaultVisibility; | |||
private final ComponentUpdater componentUpdater; | |||
private final ImportHelper importHelper; | |||
private final ProjectKeyGenerator projectKeyGenerator; | |||
private final NewCodeDefinitionResolver newCodeDefinitionResolver; | |||
private final DefaultBranchNameResolver defaultBranchNameResolver; | |||
private final ImportProjectService importProjectService; | |||
@Inject | |||
public ImportAzureProjectAction(DbClient dbClient, UserSession userSession, AzureDevOpsHttpClient azureDevOpsHttpClient, | |||
ProjectDefaultVisibility projectDefaultVisibility, ComponentUpdater componentUpdater, | |||
ImportHelper importHelper, ProjectKeyGenerator projectKeyGenerator, NewCodeDefinitionResolver newCodeDefinitionResolver, | |||
DefaultBranchNameResolver defaultBranchNameResolver) { | |||
this.dbClient = dbClient; | |||
this.userSession = userSession; | |||
this.azureDevOpsHttpClient = azureDevOpsHttpClient; | |||
this.projectDefaultVisibility = projectDefaultVisibility; | |||
this.componentUpdater = componentUpdater; | |||
public ImportAzureProjectAction(ImportHelper importHelper, ImportProjectService importProjectService) { | |||
this.importHelper = importHelper; | |||
this.projectKeyGenerator = projectKeyGenerator; | |||
this.newCodeDefinitionResolver = newCodeDefinitionResolver; | |||
this.defaultBranchNameResolver = defaultBranchNameResolver; | |||
this.importProjectService = importProjectService; | |||
} | |||
@Override | |||
@@ -140,74 +104,20 @@ public class ImportAzureProjectAction implements AlmIntegrationsWsAction { | |||
private CreateWsResponse doHandle(Request request) { | |||
importHelper.checkProvisionProjectPermission(); | |||
AlmSettingDto almSettingDto = importHelper.getAlmSettingDtoForAlm(request, ALM.AZURE_DEVOPS); | |||
String newCodeDefinitionType = request.param(PARAM_NEW_CODE_DEFINITION_TYPE); | |||
String newCodeDefinitionValue = request.param(PARAM_NEW_CODE_DEFINITION_VALUE); | |||
String projectName = request.mandatoryParam(PARAM_PROJECT_NAME); | |||
String repositoryName = request.mandatoryParam(PARAM_REPOSITORY_NAME); | |||
try (DbSession dbSession = dbClient.openSession(false)) { | |||
String pat = getPat(dbSession, almSettingDto); | |||
String projectName = request.mandatoryParam(PARAM_PROJECT_NAME); | |||
String repositoryName = request.mandatoryParam(PARAM_REPOSITORY_NAME); | |||
String url = requireNonNull(almSettingDto.getUrl(), "DevOps Platform url cannot be null"); | |||
GsonAzureRepo repo = azureDevOpsHttpClient.getRepo(url, pat, projectName, repositoryName); | |||
ComponentCreationData componentCreationData = createProject(dbSession, repo); | |||
ProjectDto projectDto = Optional.ofNullable(componentCreationData.projectDto()).orElseThrow(); | |||
BranchDto mainBranchDto = Optional.ofNullable(componentCreationData.mainBranchDto()).orElseThrow(); | |||
populatePRSetting(dbSession, repo, projectDto, almSettingDto); | |||
checkNewCodeDefinitionParam(newCodeDefinitionType, newCodeDefinitionValue); | |||
if (newCodeDefinitionType != null) { | |||
newCodeDefinitionResolver.createNewCodeDefinition(dbSession, projectDto.getUuid(), mainBranchDto.getUuid(), | |||
Optional.ofNullable(repo.getDefaultBranchName()).orElse(defaultBranchNameResolver.getEffectiveMainBranchName()), | |||
newCodeDefinitionType, newCodeDefinitionValue); | |||
} | |||
componentUpdater.commitAndIndex(dbSession, componentCreationData); | |||
return toCreateResponse(projectDto); | |||
} | |||
} | |||
private String getPat(DbSession dbSession, AlmSettingDto almSettingDto) { | |||
String userUuid = importHelper.getUserUuid(); | |||
Optional<AlmPatDto> almPatDto = dbClient.almPatDao().selectByUserAndAlmSetting(dbSession, userUuid, almSettingDto); | |||
return almPatDto.map(AlmPatDto::getPersonalAccessToken) | |||
.orElseThrow(() -> new IllegalArgumentException(String.format("personal access token for '%s' is missing", almSettingDto.getKey()))); | |||
} | |||
ImportedProject importedProject = importProjectService | |||
.importProject(toServiceRequest(almSettingDto, projectName, repositoryName, newCodeDefinitionType, newCodeDefinitionValue)); | |||
private ComponentCreationData createProject(DbSession dbSession, GsonAzureRepo repo) { | |||
boolean visibility = projectDefaultVisibility.get(dbSession).isPrivate(); | |||
String uniqueProjectKey = projectKeyGenerator.generateUniqueProjectKey(repo.getProject().getName(), repo.getName()); | |||
NewComponent newProject = newComponentBuilder() | |||
.setKey(uniqueProjectKey) | |||
.setName(repo.getName()) | |||
.setPrivate(visibility) | |||
.setQualifier(PROJECT) | |||
.build(); | |||
ComponentCreationParameters componentCreationParameters = ComponentCreationParameters.builder() | |||
.newComponent(newProject) | |||
.userUuid(userSession.getUuid()) | |||
.userLogin(userSession.getLogin()) | |||
.mainBranchName(repo.getDefaultBranchName()) | |||
.creationMethod(getCreationMethod(ALM_IMPORT, userSession.isAuthenticatedBrowserSession())) | |||
.build(); | |||
return componentUpdater.createWithoutCommit(dbSession, componentCreationParameters); | |||
return toCreateResponse(importedProject.projectDto()); | |||
} | |||
private void populatePRSetting(DbSession dbSession, GsonAzureRepo repo, ProjectDto projectDto, AlmSettingDto almSettingDto) { | |||
ProjectAlmSettingDto projectAlmSettingDto = new ProjectAlmSettingDto() | |||
.setAlmSettingUuid(almSettingDto.getUuid()) | |||
.setAlmRepo(repo.getName()) | |||
.setAlmSlug(repo.getProject().getName()) | |||
.setProjectUuid(projectDto.getUuid()) | |||
.setMonorepo(false); | |||
dbClient.projectAlmSettingDao().insertOrUpdate(dbSession, projectAlmSettingDto, almSettingDto.getKey(), | |||
projectDto.getName(), projectDto.getKey()); | |||
private static ImportProjectRequest toServiceRequest(AlmSettingDto almSettingDto, String projectIdentifier, String repositoryIdentifier, @Nullable String newCodeDefinitionType, | |||
@Nullable String newCodeDefinitionValue) { | |||
return new ImportProjectRequest(null, null, almSettingDto.getUuid(), repositoryIdentifier, projectIdentifier, newCodeDefinitionType, newCodeDefinitionValue, false); | |||
} | |||
} |
@@ -81,6 +81,7 @@ import org.sonar.server.ce.projectdump.ProjectExportWsModule; | |||
import org.sonar.server.ce.ws.CeWsModule; | |||
import org.sonar.server.common.almintegration.ProjectKeyGenerator; | |||
import org.sonar.server.common.almsettings.DelegatingDevOpsProjectCreatorFactory; | |||
import org.sonar.server.common.almsettings.azuredevops.AzureDevOpsProjectCreatorFactory; | |||
import org.sonar.server.common.almsettings.bitbucketcloud.BitbucketCloudProjectCreatorFactory; | |||
import org.sonar.server.common.almsettings.bitbucketserver.BitbucketServerProjectCreatorFactory; | |||
import org.sonar.server.common.almsettings.github.GithubProjectCreatorFactory; | |||
@@ -574,6 +575,7 @@ public class PlatformLevel4 extends PlatformLevel { | |||
BitbucketCloudRestClientConfiguration.class, | |||
BitbucketServerRestClient.class, | |||
AzureDevOpsHttpClient.class, | |||
AzureDevOpsProjectCreatorFactory.class, | |||
new AlmIntegrationsWSModule(), | |||
BitbucketCloudValidator.class, | |||
BitbucketCloudProjectCreatorFactory.class, |