diff options
author | Aurelien Poscia <aurelien.poscia@sonarsource.com> | 2024-07-19 13:42:04 +0200 |
---|---|---|
committer | sonartech <sonartech@sonarsource.com> | 2024-07-23 20:02:45 +0000 |
commit | 04bd7628152bc52451c9ac593a2cd1398b44f91b (patch) | |
tree | d8b8ce3bd598527342c954bb7226b93b761be7be /server/sonar-webserver-common | |
parent | 3e515e1d04424e962ef4d798c2820013e5facfae (diff) | |
download | sonarqube-04bd7628152bc52451c9ac593a2cd1398b44f91b.tar.gz sonarqube-04bd7628152bc52451c9ac593a2cd1398b44f91b.zip |
SONAR-22557 Synchronize projects visiblity upon GitLab onboarding
Diffstat (limited to 'server/sonar-webserver-common')
17 files changed, 1127 insertions, 793 deletions
diff --git a/server/sonar-webserver-common/build.gradle b/server/sonar-webserver-common/build.gradle index 4764b249bc0..38b5f22e1ab 100644 --- a/server/sonar-webserver-common/build.gradle +++ b/server/sonar-webserver-common/build.gradle @@ -25,6 +25,7 @@ dependencies { testImplementation 'junit:junit' testImplementation 'org.assertj:assertj-core' testImplementation 'org.junit.jupiter:junit-jupiter-api' + testImplementation 'org.junit.jupiter:junit-jupiter-params' testImplementation 'org.mockito:mockito-core' testImplementation 'org.mockito:mockito-junit-jupiter' diff --git a/server/sonar-webserver-common/src/main/java/org/sonar/server/common/almsettings/DefaultDevOpsProjectCreator.java b/server/sonar-webserver-common/src/main/java/org/sonar/server/common/almsettings/DefaultDevOpsProjectCreator.java new file mode 100644 index 00000000000..e6a8015677b --- /dev/null +++ b/server/sonar-webserver-common/src/main/java/org/sonar/server/common/almsettings/DefaultDevOpsProjectCreator.java @@ -0,0 +1,137 @@ +/* + * 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; + +import java.util.Optional; +import java.util.Set; +import javax.annotation.CheckForNull; +import org.jetbrains.annotations.Nullable; +import org.sonar.api.web.UserRole; +import org.sonar.auth.DevOpsPlatformSettings; +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.db.user.UserIdDto; +import org.sonar.server.common.almintegration.ProjectKeyGenerator; +import org.sonar.server.common.permission.Operation; +import org.sonar.server.common.permission.PermissionUpdater; +import org.sonar.server.common.permission.UserPermissionChange; +import org.sonar.server.common.project.ProjectCreator; +import org.sonar.server.component.ComponentCreationData; +import org.sonar.server.management.ManagedProjectService; +import org.sonar.server.permission.PermissionService; +import org.sonar.server.user.UserSession; + +import static java.util.Objects.requireNonNull; + +public class DefaultDevOpsProjectCreator implements DevOpsProjectCreator { + + protected final DbClient dbClient; + protected final ProjectKeyGenerator projectKeyGenerator; + protected final DevOpsPlatformSettings devOpsPlatformSettings; + protected final ProjectCreator projectCreator; + protected final PermissionService permissionService; + protected final PermissionUpdater<UserPermissionChange> permissionUpdater; + protected final ManagedProjectService managedProjectService; + protected final DevOpsProjectCreationContext devOpsProjectCreationContext; + + public DefaultDevOpsProjectCreator(DbClient dbClient, DevOpsProjectCreationContext devOpsProjectCreationContext, ProjectKeyGenerator projectKeyGenerator, + DevOpsPlatformSettings devOpsPlatformSettings, ProjectCreator projectCreator, PermissionService permissionService, PermissionUpdater<UserPermissionChange> permissionUpdater, + ManagedProjectService managedProjectService) { + this.dbClient = dbClient; + this.projectKeyGenerator = projectKeyGenerator; + this.devOpsPlatformSettings = devOpsPlatformSettings; + this.projectCreator = projectCreator; + this.permissionService = permissionService; + this.permissionUpdater = permissionUpdater; + this.managedProjectService = managedProjectService; + this.devOpsProjectCreationContext = devOpsProjectCreationContext; + } + + @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 key = Optional.ofNullable(projectKey).orElse(generateUniqueProjectKey()); + boolean isManaged = devOpsPlatformSettings.isProvisioningEnabled(); + Boolean shouldProjectBePrivate = shouldProjectBePrivate(devOpsProjectCreationContext.isPublic()); + + ComponentCreationData componentCreationData = projectCreator.createProject(dbSession, key, getProjectName(projectName), + devOpsProjectCreationContext.defaultBranchName(), creationMethod, shouldProjectBePrivate, isManaged); + ProjectDto projectDto = Optional.ofNullable(componentCreationData.projectDto()).orElseThrow(); + + createProjectAlmSettingDto(dbSession, projectDto, devOpsProjectCreationContext.almSettingDto(), monorepo); + addScanPermissionToCurrentUser(dbSession, projectDto); + + BranchDto mainBranchDto = Optional.ofNullable(componentCreationData.mainBranchDto()).orElseThrow(); + if (isManaged) { + syncProjectPermissionsWithDevOpsPlatform(projectDto, mainBranchDto); + } + return componentCreationData; + } + + @CheckForNull + private Boolean shouldProjectBePrivate(boolean isPublic) { + if (devOpsPlatformSettings.isProvisioningEnabled() && devOpsPlatformSettings.isProjectVisibilitySynchronizationActivated()) { + return !isPublic; + } else if (devOpsPlatformSettings.isProvisioningEnabled()) { + return true; + } + return null; + } + + private String getProjectName(@Nullable String projectName) { + return Optional.ofNullable(projectName).orElse(devOpsProjectCreationContext.name()); + } + + private String generateUniqueProjectKey() { + return projectKeyGenerator.generateUniqueProjectKey(devOpsProjectCreationContext.fullName()); + } + + private void createProjectAlmSettingDto(DbSession dbSession, ProjectDto projectDto, AlmSettingDto almSettingDto, Boolean monorepo) { + ProjectAlmSettingDto projectAlmSettingDto = new ProjectAlmSettingDto() + .setAlmSettingUuid(almSettingDto.getUuid()) + .setAlmRepo(devOpsProjectCreationContext.devOpsPlatformIdentifier()) + .setProjectUuid(projectDto.getUuid()) + .setSummaryCommentEnabled(true) + .setMonorepo(monorepo); + dbClient.projectAlmSettingDao().insertOrUpdate(dbSession, projectAlmSettingDto, almSettingDto.getKey(), projectDto.getName(), projectDto.getKey()); + } + + private void addScanPermissionToCurrentUser(DbSession dbSession, ProjectDto projectDto) { + UserSession userSession = devOpsProjectCreationContext.userSession(); + UserIdDto userId = new UserIdDto(requireNonNull(userSession.getUuid()), requireNonNull(userSession.getLogin())); + UserPermissionChange scanPermission = new UserPermissionChange(Operation.ADD, UserRole.SCAN, projectDto, userId, permissionService); + permissionUpdater.apply(dbSession, Set.of(scanPermission)); + } + + private void syncProjectPermissionsWithDevOpsPlatform(ProjectDto projectDto, BranchDto mainBranchDto) { + String userUuid = requireNonNull(devOpsProjectCreationContext.userSession().getUuid()); + managedProjectService.queuePermissionSyncTask(userUuid, mainBranchDto.getUuid(), projectDto.getUuid()); + } +} diff --git a/server/sonar-webserver-common/src/main/java/org/sonar/server/common/almsettings/github/GithubProjectCreationParameters.java b/server/sonar-webserver-common/src/main/java/org/sonar/server/common/almsettings/DevOpsProjectCreationContext.java index 72b2ca87602..1530eb1e7c0 100644 --- a/server/sonar-webserver-common/src/main/java/org/sonar/server/common/almsettings/github/GithubProjectCreationParameters.java +++ b/server/sonar-webserver-common/src/main/java/org/sonar/server/common/almsettings/DevOpsProjectCreationContext.java @@ -17,16 +17,12 @@ * 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.github; +package org.sonar.server.common.almsettings; import javax.annotation.Nullable; -import org.sonar.auth.github.AppInstallationToken; -import org.sonar.auth.github.security.AccessToken; import org.sonar.db.alm.setting.AlmSettingDto; -import org.sonar.server.common.almsettings.DevOpsProjectDescriptor; import org.sonar.server.user.UserSession; -public record GithubProjectCreationParameters(DevOpsProjectDescriptor devOpsProjectDescriptor, AlmSettingDto almSettingDto, UserSession userSession, - AccessToken devOpsAppInstallationToken, - @Nullable AppInstallationToken authAppInstallationToken) { +public record DevOpsProjectCreationContext(String name, String fullName, String devOpsPlatformIdentifier, boolean isPublic, @Nullable String defaultBranchName, + AlmSettingDto almSettingDto, UserSession userSession) { } diff --git a/server/sonar-webserver-common/src/main/java/org/sonar/server/common/almsettings/DevOpsProjectCreationContextService.java b/server/sonar-webserver-common/src/main/java/org/sonar/server/common/almsettings/DevOpsProjectCreationContextService.java new file mode 100644 index 00000000000..e86f91df853 --- /dev/null +++ b/server/sonar-webserver-common/src/main/java/org/sonar/server/common/almsettings/DevOpsProjectCreationContextService.java @@ -0,0 +1,28 @@ +/* + * 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; + +import org.sonar.db.alm.setting.AlmSettingDto; + +public interface DevOpsProjectCreationContextService { + + DevOpsProjectCreationContext create(AlmSettingDto almSettingDto, DevOpsProjectDescriptor devOpsProjectDescriptor); + +} diff --git a/server/sonar-webserver-common/src/main/java/org/sonar/server/common/almsettings/github/GithubDevOpsProjectCreationContextService.java b/server/sonar-webserver-common/src/main/java/org/sonar/server/common/almsettings/github/GithubDevOpsProjectCreationContextService.java new file mode 100644 index 00000000000..db83c9b7ae7 --- /dev/null +++ b/server/sonar-webserver-common/src/main/java/org/sonar/server/common/almsettings/github/GithubDevOpsProjectCreationContextService.java @@ -0,0 +1,80 @@ +/* + * SonarQube + * Copyright (C) 2009-2024 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.server.common.almsettings.github; + +import org.sonar.api.server.ServerSide; +import org.sonar.auth.github.client.GithubApplicationClient; +import org.sonar.auth.github.security.AccessToken; +import org.sonar.auth.github.security.UserAccessToken; +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.server.common.almsettings.DevOpsProjectCreationContext; +import org.sonar.server.common.almsettings.DevOpsProjectDescriptor; +import org.sonar.server.common.almsettings.DevOpsProjectCreationContextService; +import org.sonar.server.user.UserSession; + +import static java.util.Objects.requireNonNull; + +@ServerSide +public class GithubDevOpsProjectCreationContextService implements DevOpsProjectCreationContextService { + + private final DbClient dbClient; + private final UserSession userSession; + private final GithubApplicationClient githubApplicationClient; + + public GithubDevOpsProjectCreationContextService(DbClient dbClient, UserSession userSession, GithubApplicationClient githubApplicationClient) { + this.dbClient = dbClient; + this.userSession = userSession; + this.githubApplicationClient = githubApplicationClient; + } + + @Override + public DevOpsProjectCreationContext create(AlmSettingDto almSettingDto, DevOpsProjectDescriptor devOpsProjectDescriptor) { + AccessToken accessToken = getAccessToken(almSettingDto); + return createDevOpsProject(almSettingDto, devOpsProjectDescriptor, accessToken); + } + + private AccessToken getAccessToken(AlmSettingDto almSettingDto) { + try (DbSession dbSession = dbClient.openSession(false)) { + String userUuid = requireNonNull(userSession.getUuid(), "User UUID cannot be null."); + return dbClient.almPatDao().selectByUserAndAlmSetting(dbSession, userUuid, almSettingDto) + .map(AlmPatDto::getPersonalAccessToken) + .map(UserAccessToken::new) + .orElseThrow(() -> new IllegalArgumentException("No personal access token found")); + } + } + + public DevOpsProjectCreationContext createDevOpsProject(AlmSettingDto almSettingDto, DevOpsProjectDescriptor devOpsProjectDescriptor, AccessToken accessToken) { + GithubApplicationClient.Repository repository = fetchRepository(almSettingDto, devOpsProjectDescriptor, accessToken); + return new DevOpsProjectCreationContext(repository.getName(), repository.getFullName(), repository.getFullName(), + !repository.isPrivate(), repository.getDefaultBranch(), almSettingDto, userSession); + } + + private GithubApplicationClient.Repository fetchRepository(AlmSettingDto almSettingDto, DevOpsProjectDescriptor devOpsProjectDescriptor, AccessToken accessToken) { + String url = requireNonNull(almSettingDto.getUrl(), "DevOps Platform url cannot be null"); + return githubApplicationClient.getRepository(url, accessToken, devOpsProjectDescriptor.repositoryIdentifier()) + .orElseThrow(() -> new IllegalStateException( + String.format("Impossible to find the repository '%s' on GitHub, using the devops config %s", devOpsProjectDescriptor.repositoryIdentifier(), almSettingDto.getKey()))); + } + + +} diff --git a/server/sonar-webserver-common/src/main/java/org/sonar/server/common/almsettings/github/GithubProjectCreator.java b/server/sonar-webserver-common/src/main/java/org/sonar/server/common/almsettings/github/GithubProjectCreator.java index 3d946813b7f..90890b477fc 100644 --- a/server/sonar-webserver-common/src/main/java/org/sonar/server/common/almsettings/github/GithubProjectCreator.java +++ b/server/sonar-webserver-common/src/main/java/org/sonar/server/common/almsettings/github/GithubProjectCreator.java @@ -19,38 +19,25 @@ */ package org.sonar.server.common.almsettings.github; -import java.util.Optional; import java.util.Set; import javax.annotation.CheckForNull; -import javax.annotation.Nullable; import org.sonar.alm.client.github.GithubPermissionConverter; import org.sonar.api.web.UserRole; +import org.sonar.auth.DevOpsPlatformSettings; import org.sonar.auth.github.AppInstallationToken; -import org.sonar.auth.github.GitHubSettings; import org.sonar.auth.github.GsonRepositoryCollaborator; import org.sonar.auth.github.GsonRepositoryPermissions; import org.sonar.auth.github.GsonRepositoryTeam; import org.sonar.auth.github.client.GithubApplicationClient; -import org.sonar.auth.github.client.GithubApplicationClient.Repository; -import org.sonar.auth.github.security.AccessToken; 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.db.provisioning.GithubPermissionsMappingDto; import org.sonar.db.user.GroupDto; -import org.sonar.db.user.UserIdDto; 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.permission.Operation; +import org.sonar.server.common.almsettings.DefaultDevOpsProjectCreator; +import org.sonar.server.common.almsettings.DevOpsProjectCreationContext; import org.sonar.server.common.permission.PermissionUpdater; import org.sonar.server.common.permission.UserPermissionChange; import org.sonar.server.common.project.ProjectCreator; -import org.sonar.server.component.ComponentCreationData; import org.sonar.server.management.ManagedProjectService; import org.sonar.server.permission.PermissionService; import org.sonar.server.user.UserSession; @@ -59,52 +46,31 @@ import static java.util.Objects.requireNonNull; import static java.util.stream.Collectors.toSet; import static org.sonar.api.utils.Preconditions.checkState; -public class GithubProjectCreator implements DevOpsProjectCreator { +public class GithubProjectCreator extends DefaultDevOpsProjectCreator { - private final DbClient dbClient; private final GithubApplicationClient githubApplicationClient; private final GithubPermissionConverter githubPermissionConverter; - private final ProjectKeyGenerator projectKeyGenerator; - private final PermissionUpdater<UserPermissionChange> permissionUpdater; - private final PermissionService permissionService; - private final ManagedProjectService managedProjectService; - private final ProjectCreator projectCreator; - private final GithubProjectCreationParameters githubProjectCreationParameters; - private final DevOpsProjectDescriptor devOpsProjectDescriptor; - private final UserSession userSession; - private final AlmSettingDto almSettingDto; - private final AccessToken devOpsAppInstallationToken; - private final GitHubSettings gitHubSettings; @CheckForNull private final AppInstallationToken authAppInstallationToken; - public GithubProjectCreator(DbClient dbClient, GithubApplicationClient githubApplicationClient, GithubPermissionConverter githubPermissionConverter, - ProjectKeyGenerator projectKeyGenerator, PermissionUpdater<UserPermissionChange> permissionUpdater, PermissionService permissionService, - ManagedProjectService managedProjectService, ProjectCreator projectCreator, GithubProjectCreationParameters githubProjectCreationParameters, GitHubSettings gitHubSettings) { - - this.dbClient = dbClient; + public GithubProjectCreator(DbClient dbClient, DevOpsProjectCreationContext devOpsProjectCreationContext, + ProjectKeyGenerator projectKeyGenerator, + DevOpsPlatformSettings devOpsPlatformSettings, ProjectCreator projectCreator, PermissionService permissionService, PermissionUpdater<UserPermissionChange> permissionUpdater, + ManagedProjectService managedProjectService, GithubApplicationClient githubApplicationClient, GithubPermissionConverter githubPermissionConverter, + @CheckForNull AppInstallationToken authAppInstallationToken) { + super(dbClient, devOpsProjectCreationContext, projectKeyGenerator, devOpsPlatformSettings, projectCreator, permissionService, permissionUpdater, + managedProjectService); this.githubApplicationClient = githubApplicationClient; this.githubPermissionConverter = githubPermissionConverter; - this.projectKeyGenerator = projectKeyGenerator; - this.permissionUpdater = permissionUpdater; - this.permissionService = permissionService; - this.managedProjectService = managedProjectService; - this.projectCreator = projectCreator; - this.githubProjectCreationParameters = githubProjectCreationParameters; - userSession = githubProjectCreationParameters.userSession(); - almSettingDto = githubProjectCreationParameters.almSettingDto(); - devOpsProjectDescriptor = githubProjectCreationParameters.devOpsProjectDescriptor(); - devOpsAppInstallationToken = githubProjectCreationParameters.devOpsAppInstallationToken(); - authAppInstallationToken = githubProjectCreationParameters.authAppInstallationToken(); - this.gitHubSettings = gitHubSettings; + this.authAppInstallationToken = authAppInstallationToken; } @Override public boolean isScanAllowedUsingPermissionsFromDevopsPlatform() { - checkState(githubProjectCreationParameters.authAppInstallationToken() != null, "An auth app token is required in case repository permissions checking is necessary."); + checkState(authAppInstallationToken != null, "An auth app token is required in case repository permissions checking is necessary."); - String[] orgaAndRepoTokenified = devOpsProjectDescriptor.repositoryIdentifier().split("/"); + String[] orgaAndRepoTokenified = devOpsProjectCreationContext.fullName().split("/"); String organization = orgaAndRepoTokenified[0]; String repository = orgaAndRepoTokenified[1]; @@ -118,9 +84,10 @@ public class GithubProjectCreator implements DevOpsProjectCreator { } private boolean doesUserHaveScanPermission(String organization, String repository, Set<GithubPermissionsMappingDto> permissionsMappingDtos) { - Set<GsonRepositoryCollaborator> repositoryCollaborators = githubApplicationClient.getRepositoryCollaborators(devOpsProjectDescriptor.url(), authAppInstallationToken, - organization, repository); + String url = requireNonNull(devOpsProjectCreationContext.almSettingDto().getUrl(), "GitHub url not defined"); + Set<GsonRepositoryCollaborator> repositoryCollaborators = githubApplicationClient.getRepositoryCollaborators(url, authAppInstallationToken, organization, repository); + UserSession userSession = devOpsProjectCreationContext.userSession(); String externalLogin = userSession.getExternalIdentity().map(UserSession.ExternalIdentity::login).orElse(null); if (externalLogin == null) { return false; @@ -134,7 +101,8 @@ public class GithubProjectCreator implements DevOpsProjectCreator { private boolean doesUserBelongToAGroupWithScanPermission(String organization, String repository, Set<GithubPermissionsMappingDto> permissionsMappingDtos) { - Set<GsonRepositoryTeam> repositoryTeams = githubApplicationClient.getRepositoryTeams(devOpsProjectDescriptor.url(), authAppInstallationToken, organization, repository); + String url = requireNonNull(devOpsProjectCreationContext.almSettingDto().getUrl(), "GitHub url not defined"); + Set<GsonRepositoryTeam> repositoryTeams = githubApplicationClient.getRepositoryTeams(url, authAppInstallationToken, organization, repository); Set<String> groupsOfUser = findUserMembershipOnSonarQube(organization); return repositoryTeams.stream() @@ -144,7 +112,7 @@ public class GithubProjectCreator implements DevOpsProjectCreator { } private Set<String> findUserMembershipOnSonarQube(String organization) { - return userSession.getGroups().stream() + return devOpsProjectCreationContext.userSession().getGroups().stream() .map(GroupDto::getName) .filter(groupName -> groupName.contains("/")) .map(name -> name.replaceFirst(organization + "/", "")) @@ -157,73 +125,4 @@ public class GithubProjectCreator implements DevOpsProjectCreator { return sonarqubePermissions.contains(UserRole.SCAN); } - @Override - public ComponentCreationData createProjectAndBindToDevOpsPlatform(DbSession dbSession, CreationMethod creationMethod, Boolean monorepo, @Nullable String projectKey, - @Nullable String projectName) { - String url = requireNonNull(almSettingDto.getUrl(), "DevOps Platform url cannot be null"); - Repository repository = githubApplicationClient.getRepository(url, devOpsAppInstallationToken, devOpsProjectDescriptor.repositoryIdentifier()) - .orElseThrow(() -> new IllegalStateException( - String.format("Impossible to find the repository '%s' on GitHub, using the devops config %s", devOpsProjectDescriptor.repositoryIdentifier(), almSettingDto.getKey()))); - - return createProjectAndBindToDevOpsPlatform(dbSession, monorepo, projectKey, projectName, almSettingDto, repository, creationMethod); - } - - private ComponentCreationData createProjectAndBindToDevOpsPlatform(DbSession dbSession, Boolean monorepo, @Nullable String projectKey, @Nullable String projectName, - AlmSettingDto almSettingDto, - Repository repository, CreationMethod creationMethod) { - String key = Optional.ofNullable(projectKey).orElse(getUniqueProjectKey(repository)); - - boolean isManaged = gitHubSettings.isProvisioningEnabled(); - - ComponentCreationData componentCreationData = projectCreator.createProject(dbSession, key, Optional.ofNullable(projectName).orElse(repository.getName()), - repository.getDefaultBranch(), creationMethod, - shouldProjectBePrivate(repository), isManaged); - ProjectDto projectDto = Optional.ofNullable(componentCreationData.projectDto()).orElseThrow(); - createProjectAlmSettingDto(dbSession, repository, projectDto, almSettingDto, monorepo); - addScanPermissionToCurrentUser(dbSession, projectDto); - - BranchDto mainBranchDto = Optional.ofNullable(componentCreationData.mainBranchDto()).orElseThrow(); - if (gitHubSettings.isProvisioningEnabled()) { - syncProjectPermissionsWithGithub(projectDto, mainBranchDto); - } - return componentCreationData; - } - - @CheckForNull - private Boolean shouldProjectBePrivate(Repository repository) { - if (gitHubSettings.isProvisioningEnabled() && gitHubSettings.isProjectVisibilitySynchronizationActivated()) { - return repository.isPrivate(); - } else if (gitHubSettings.isProvisioningEnabled()) { - return true; - } else { - return null; - } - } - - private void addScanPermissionToCurrentUser(DbSession dbSession, ProjectDto projectDto) { - UserIdDto userId = new UserIdDto(requireNonNull(userSession.getUuid()), requireNonNull(userSession.getLogin())); - UserPermissionChange scanPermission = new UserPermissionChange(Operation.ADD, UserRole.SCAN, projectDto, userId, permissionService); - permissionUpdater.apply(dbSession, Set.of(scanPermission)); - } - - private void syncProjectPermissionsWithGithub(ProjectDto projectDto, BranchDto mainBranchDto) { - String userUuid = requireNonNull(userSession.getUuid()); - managedProjectService.queuePermissionSyncTask(userUuid, mainBranchDto.getUuid(), projectDto.getUuid()); - } - - private String getUniqueProjectKey(Repository repository) { - return projectKeyGenerator.generateUniqueProjectKey(repository.getFullName()); - } - - private void createProjectAlmSettingDto(DbSession dbSession, Repository repo, ProjectDto projectDto, AlmSettingDto almSettingDto, Boolean monorepo) { - ProjectAlmSettingDto projectAlmSettingDto = new ProjectAlmSettingDto() - .setAlmSettingUuid(almSettingDto.getUuid()) - .setAlmRepo(repo.getFullName()) - .setAlmSlug(null) - .setProjectUuid(projectDto.getUuid()) - .setSummaryCommentEnabled(true) - .setMonorepo(monorepo); - dbClient.projectAlmSettingDao().insertOrUpdate(dbSession, projectAlmSettingDto, almSettingDto.getKey(), projectDto.getName(), projectDto.getKey()); - } - } diff --git a/server/sonar-webserver-common/src/main/java/org/sonar/server/common/almsettings/github/GithubProjectCreatorFactory.java b/server/sonar-webserver-common/src/main/java/org/sonar/server/common/almsettings/github/GithubProjectCreatorFactory.java index 05fa562ed2e..59c4c54a8ae 100644 --- a/server/sonar-webserver-common/src/main/java/org/sonar/server/common/almsettings/github/GithubProjectCreatorFactory.java +++ b/server/sonar-webserver-common/src/main/java/org/sonar/server/common/almsettings/github/GithubProjectCreatorFactory.java @@ -30,14 +30,13 @@ import org.sonar.auth.github.AppInstallationToken; import org.sonar.auth.github.GitHubSettings; import org.sonar.auth.github.GithubAppConfiguration; import org.sonar.auth.github.client.GithubApplicationClient; -import org.sonar.auth.github.security.AccessToken; -import org.sonar.auth.github.security.UserAccessToken; 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.server.common.almintegration.ProjectKeyGenerator; +import org.sonar.server.common.almsettings.DefaultDevOpsProjectCreator; +import org.sonar.server.common.almsettings.DevOpsProjectCreationContext; import org.sonar.server.common.almsettings.DevOpsProjectCreator; import org.sonar.server.common.almsettings.DevOpsProjectCreatorFactory; import org.sonar.server.common.almsettings.DevOpsProjectDescriptor; @@ -47,10 +46,8 @@ import org.sonar.server.common.project.ProjectCreator; import org.sonar.server.exceptions.BadConfigurationException; import org.sonar.server.management.ManagedProjectService; import org.sonar.server.permission.PermissionService; -import org.sonar.server.user.UserSession; import static java.lang.String.format; -import static java.util.Objects.requireNonNull; import static org.sonar.core.ce.CeTaskCharacteristics.DEVOPS_PLATFORM_PROJECT_IDENTIFIER; import static org.sonar.core.ce.CeTaskCharacteristics.DEVOPS_PLATFORM_URL; @@ -63,28 +60,29 @@ public class GithubProjectCreatorFactory implements DevOpsProjectCreatorFactory private final GithubApplicationClient githubApplicationClient; private final ProjectKeyGenerator projectKeyGenerator; private final ProjectCreator projectCreator; - private final UserSession userSession; private final GitHubSettings gitHubSettings; private final GithubPermissionConverter githubPermissionConverter; private final PermissionUpdater<UserPermissionChange> permissionUpdater; private final PermissionService permissionService; private final ManagedProjectService managedProjectService; + private final GithubDevOpsProjectCreationContextService githubDevOpsProjectService; public GithubProjectCreatorFactory(DbClient dbClient, GithubGlobalSettingsValidator githubGlobalSettingsValidator, - GithubApplicationClient githubApplicationClient, ProjectKeyGenerator projectKeyGenerator, UserSession userSession, + GithubApplicationClient githubApplicationClient, ProjectKeyGenerator projectKeyGenerator, ProjectCreator projectCreator, GitHubSettings gitHubSettings, GithubPermissionConverter githubPermissionConverter, - PermissionUpdater<UserPermissionChange> permissionUpdater, PermissionService permissionService, ManagedProjectService managedProjectService) { + PermissionUpdater<UserPermissionChange> permissionUpdater, PermissionService permissionService, ManagedProjectService managedProjectService, + GithubDevOpsProjectCreationContextService githubDevOpsProjectService) { this.dbClient = dbClient; this.githubGlobalSettingsValidator = githubGlobalSettingsValidator; this.githubApplicationClient = githubApplicationClient; this.projectKeyGenerator = projectKeyGenerator; - this.userSession = userSession; this.projectCreator = projectCreator; this.gitHubSettings = gitHubSettings; this.githubPermissionConverter = githubPermissionConverter; this.permissionUpdater = permissionUpdater; this.permissionService = permissionService; this.managedProjectService = managedProjectService; + this.githubDevOpsProjectService = githubDevOpsProjectService; } @Override @@ -112,50 +110,39 @@ public class GithubProjectCreatorFactory implements DevOpsProjectCreatorFactory .map(appInstallationToken -> createGithubProjectCreator(devOpsProjectDescriptor, almSettingDto, appInstallationToken)); } - private GithubProjectCreator createGithubProjectCreator(DevOpsProjectDescriptor devOpsProjectDescriptor, AlmSettingDto almSettingDto, + private DevOpsProjectCreator createGithubProjectCreator(DevOpsProjectDescriptor devOpsProjectDescriptor, AlmSettingDto almSettingDto, AppInstallationToken appInstallationToken) { LOG.info("DevOps configuration {} auto-detected for project {}", almSettingDto.getKey(), devOpsProjectDescriptor.repositoryIdentifier()); - Optional<AppInstallationToken> authAppInstallationToken = getAuthAppInstallationTokenIfNecessary(devOpsProjectDescriptor); - GithubProjectCreationParameters githubProjectCreationParameters = new GithubProjectCreationParameters(devOpsProjectDescriptor, almSettingDto, userSession, appInstallationToken, - authAppInstallationToken.orElse(null)); - return new GithubProjectCreator(dbClient, githubApplicationClient, githubPermissionConverter, projectKeyGenerator, permissionUpdater, permissionService, - managedProjectService, projectCreator, githubProjectCreationParameters, gitHubSettings); + DevOpsProjectCreationContext devOpsProjectCreationContext = githubDevOpsProjectService.createDevOpsProject(almSettingDto, devOpsProjectDescriptor, appInstallationToken); + return createDefaultDevOpsProjectCreator(devOpsProjectCreationContext); } @Override - public Optional<DevOpsProjectCreator> getDevOpsProjectCreator(AlmSettingDto almSettingDto, - DevOpsProjectDescriptor devOpsProjectDescriptor) { + public Optional<DevOpsProjectCreator> getDevOpsProjectCreator(AlmSettingDto almSettingDto, DevOpsProjectDescriptor devOpsProjectDescriptor) { if (almSettingDto.getAlm() != ALM.GITHUB) { return Optional.empty(); } - try (DbSession dbSession = dbClient.openSession(false)) { - AccessToken accessToken = getAccessToken(dbSession, almSettingDto); - Optional<AppInstallationToken> authAppInstallationToken = getAuthAppInstallationTokenIfNecessary(devOpsProjectDescriptor); - GithubProjectCreationParameters githubProjectCreationParameters = new GithubProjectCreationParameters(devOpsProjectDescriptor, almSettingDto, userSession, accessToken, - authAppInstallationToken.orElse(null)); - GithubProjectCreator githubProjectCreator = new GithubProjectCreator(dbClient, githubApplicationClient, githubPermissionConverter, projectKeyGenerator, permissionUpdater, - permissionService, managedProjectService, this.projectCreator, githubProjectCreationParameters, gitHubSettings); - return Optional.of(githubProjectCreator); - } + DevOpsProjectCreationContext devOpsProjectCreationContext = githubDevOpsProjectService.create(almSettingDto, devOpsProjectDescriptor); + return Optional.of(createDefaultDevOpsProjectCreator(devOpsProjectCreationContext)); } - private AccessToken getAccessToken(DbSession dbSession, AlmSettingDto almSettingDto) { - String userUuid = requireNonNull(userSession.getUuid(), "User UUID cannot be null."); - return dbClient.almPatDao().selectByUserAndAlmSetting(dbSession, userUuid, almSettingDto) - .map(AlmPatDto::getPersonalAccessToken) - .map(UserAccessToken::new) - .orElseThrow(() -> new IllegalArgumentException("No personal access token found")); + private DefaultDevOpsProjectCreator createDefaultDevOpsProjectCreator(DevOpsProjectCreationContext devOpsProjectCreationContext) { + Optional<AppInstallationToken> authAppInstallationToken = getAuthAppInstallationTokenIfNecessary(devOpsProjectCreationContext); + + return new GithubProjectCreator(dbClient, devOpsProjectCreationContext, projectKeyGenerator, + gitHubSettings, projectCreator, permissionService, permissionUpdater, + managedProjectService, githubApplicationClient, githubPermissionConverter, authAppInstallationToken.orElse(null)); } - private Optional<AppInstallationToken> getAuthAppInstallationTokenIfNecessary(DevOpsProjectDescriptor devOpsProjectDescriptor) { + private Optional<AppInstallationToken> getAuthAppInstallationTokenIfNecessary(DevOpsProjectCreationContext devOpsProjectCreationContext) { if (gitHubSettings.isProvisioningEnabled()) { GithubAppConfiguration githubAppConfiguration = new GithubAppConfiguration(Long.parseLong(gitHubSettings.appId()), gitHubSettings.privateKey(), gitHubSettings.apiURL()); - long installationId = findInstallationIdToAccessRepo(githubAppConfiguration, devOpsProjectDescriptor.repositoryIdentifier()) + long installationId = findInstallationIdToAccessRepo(githubAppConfiguration, devOpsProjectCreationContext.devOpsPlatformIdentifier()) .orElseThrow(() -> new BadConfigurationException("PROJECT", format("GitHub auto-provisioning is activated. However the repo %s is not in the scope of the authentication application. " - + "The permissions can't be checked, and the project can not be created.", - devOpsProjectDescriptor.repositoryIdentifier()))); + + "The permissions can't be checked, and the project can not be created.", + devOpsProjectCreationContext.devOpsPlatformIdentifier()))); return Optional.of(generateAppInstallationToken(githubAppConfiguration, installationId)); } return Optional.empty(); diff --git a/server/sonar-webserver-common/src/main/java/org/sonar/server/common/almsettings/gitlab/GitlabDevOpsProjectCreationContextService.java b/server/sonar-webserver-common/src/main/java/org/sonar/server/common/almsettings/gitlab/GitlabDevOpsProjectCreationContextService.java new file mode 100644 index 00000000000..e1fa269cff1 --- /dev/null +++ b/server/sonar-webserver-common/src/main/java/org/sonar/server/common/almsettings/gitlab/GitlabDevOpsProjectCreationContextService.java @@ -0,0 +1,99 @@ +/* + * 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.gitlab; + +import java.util.Optional; +import org.sonar.alm.client.gitlab.GitLabBranch; +import org.sonar.alm.client.gitlab.GitlabApplicationClient; +import org.sonar.alm.client.gitlab.GitlabServerException; +import org.sonar.alm.client.gitlab.Project; +import org.sonar.api.server.ServerSide; +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.server.common.almsettings.DevOpsProjectCreationContext; +import org.sonar.server.common.almsettings.DevOpsProjectDescriptor; +import org.sonar.server.common.almsettings.DevOpsProjectCreationContextService; +import org.sonar.server.user.UserSession; + +import static java.lang.String.format; +import static java.util.Objects.requireNonNull; + +@ServerSide +public class GitlabDevOpsProjectCreationContextService implements DevOpsProjectCreationContextService { + + private final DbClient dbClient; + private final UserSession userSession; + private final GitlabApplicationClient gitlabApplicationClient; + + public GitlabDevOpsProjectCreationContextService(DbClient dbClient, UserSession userSession, GitlabApplicationClient gitlabApplicationClient) { + this.dbClient = dbClient; + this.userSession = userSession; + this.gitlabApplicationClient = gitlabApplicationClient; + } + + @Override + public DevOpsProjectCreationContext create(AlmSettingDto almSettingDto, DevOpsProjectDescriptor devOpsProjectDescriptor) { + String url = requireNonNull(almSettingDto.getUrl(), "DevOps Platform url cannot be null"); + Long gitlabProjectId = getGitlabProjectId(devOpsProjectDescriptor); + String pat = findPersonalAccessTokenOrThrow(almSettingDto); + Project gitlabProject = fetchGitlabProject(url, pat, gitlabProjectId); + String defaultBranchName = getDefaultBranchOnGitlab(url, pat, gitlabProjectId).orElse(null); + + return new DevOpsProjectCreationContext(gitlabProject.getName(), gitlabProject.getPathWithNamespace(), + String.valueOf(gitlabProjectId), gitlabProject.getVisibility().equals("public"), defaultBranchName, almSettingDto, userSession); + + } + + private static Long getGitlabProjectId(DevOpsProjectDescriptor devOpsProjectDescriptor) { + try { + return Long.parseLong(devOpsProjectDescriptor.repositoryIdentifier()); + } catch (NumberFormatException e) { + throw new IllegalArgumentException(format("GitLab project identifier must be a number, was '%s'", devOpsProjectDescriptor.repositoryIdentifier())); + } + } + + private String findPersonalAccessTokenOrThrow(AlmSettingDto almSettingDto) { + try (DbSession dbSession = dbClient.openSession(false)) { + 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(format("Personal access token for '%s' is missing", almSettingDto.getKey()))); + } + } + + private Project fetchGitlabProject(String gitlabUrl, String pat, Long gitlabProjectId) { + try { + return gitlabApplicationClient.getProject( + gitlabUrl, + pat, + gitlabProjectId); + } catch (GitlabServerException e) { + throw new IllegalStateException(format("Failed to fetch GitLab project with ID '%s' from '%s'", gitlabProjectId, gitlabUrl), e); + } + } + + private Optional<String> getDefaultBranchOnGitlab(String gitlabUrl, String pat, long gitlabProjectId) { + Optional<GitLabBranch> almMainBranch = gitlabApplicationClient.getBranches(gitlabUrl, pat, gitlabProjectId).stream().filter(GitLabBranch::isDefault).findFirst(); + return almMainBranch.map(GitLabBranch::getName); + } + +} diff --git a/server/sonar-webserver-common/src/main/java/org/sonar/server/common/almsettings/gitlab/GitlabProjectCreator.java b/server/sonar-webserver-common/src/main/java/org/sonar/server/common/almsettings/gitlab/GitlabProjectCreator.java deleted file mode 100644 index a5f2f695d51..00000000000 --- a/server/sonar-webserver-common/src/main/java/org/sonar/server/common/almsettings/gitlab/GitlabProjectCreator.java +++ /dev/null @@ -1,143 +0,0 @@ -/* - * 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.gitlab; - -import java.util.Optional; -import org.jetbrains.annotations.Nullable; -import org.sonar.alm.client.gitlab.GitLabBranch; -import org.sonar.alm.client.gitlab.GitlabApplicationClient; -import org.sonar.alm.client.gitlab.GitlabServerException; -import org.sonar.alm.client.gitlab.Project; -import org.sonar.db.DbClient; -import org.sonar.db.DbSession; -import org.sonar.db.alm.pat.AlmPatDto; -import org.sonar.db.alm.setting.AlmSettingDto; -import org.sonar.db.alm.setting.ProjectAlmSettingDto; -import org.sonar.db.project.CreationMethod; -import org.sonar.db.project.ProjectDto; -import org.sonar.server.common.almintegration.ProjectKeyGenerator; -import org.sonar.server.common.almsettings.DevOpsProjectCreator; -import org.sonar.server.common.almsettings.DevOpsProjectDescriptor; -import org.sonar.server.common.project.ProjectCreator; -import org.sonar.server.component.ComponentCreationData; -import org.sonar.server.user.UserSession; - -import static java.lang.String.format; -import static java.util.Objects.requireNonNull; - -public class GitlabProjectCreator implements DevOpsProjectCreator { - - private final DbClient dbClient; - private final ProjectKeyGenerator projectKeyGenerator; - private final ProjectCreator projectCreator; - private final AlmSettingDto almSettingDto; - private final DevOpsProjectDescriptor devOpsProjectDescriptor; - private final GitlabApplicationClient gitlabApplicationClient; - private final UserSession userSession; - - public GitlabProjectCreator(DbClient dbClient, ProjectKeyGenerator projectKeyGenerator, ProjectCreator projectCreator, AlmSettingDto almSettingDto, - DevOpsProjectDescriptor devOpsProjectDescriptor, GitlabApplicationClient gitlabApplicationClient, UserSession userSession) { - this.dbClient = dbClient; - this.projectKeyGenerator = projectKeyGenerator; - this.projectCreator = projectCreator; - this.almSettingDto = almSettingDto; - this.devOpsProjectDescriptor = devOpsProjectDescriptor; - this.gitlabApplicationClient = gitlabApplicationClient; - this.userSession = userSession; - } - - @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 gitlabUrl = requireNonNull(almSettingDto.getUrl(), "DevOps Platform gitlabUrl cannot be null"); - - Long gitlabProjectId = getGitlabProjectId(); - Project gitlabProject = fetchGitlabProject(gitlabUrl, pat, gitlabProjectId); - - Optional<String> almDefaultBranch = getDefaultBranchOnGitlab(gitlabUrl, pat, gitlabProjectId); - ComponentCreationData componentCreationData = projectCreator.createProject( - dbSession, - getProjectKey(projectKey, gitlabProject), - getProjectName(projectName, gitlabProject), - almDefaultBranch.orElse(null), - creationMethod); - ProjectDto projectDto = Optional.ofNullable(componentCreationData.projectDto()).orElseThrow(); - - createProjectAlmSettingDto(dbSession, gitlabProjectId.toString(), 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(format("personal access token for '%s' is missing", almSettingDto.getKey()))); - } - - private Long getGitlabProjectId() { - try { - return Long.parseLong(devOpsProjectDescriptor.repositoryIdentifier()); - } catch (NumberFormatException e) { - throw new IllegalArgumentException(format("GitLab project identifier must be a number, was '%s'", devOpsProjectDescriptor.repositoryIdentifier())); - } - } - - private Project fetchGitlabProject(String gitlabUrl, String pat, Long gitlabProjectId) { - try { - return gitlabApplicationClient.getProject( - gitlabUrl, - pat, - gitlabProjectId); - } catch (GitlabServerException e) { - throw new IllegalStateException(format("Failed to fetch GitLab project with ID '%s' from '%s'", gitlabProjectId, gitlabUrl), e); - } - } - - private Optional<String> getDefaultBranchOnGitlab(String gitlabUrl, String pat, long gitlabProjectId) { - Optional<GitLabBranch> almMainBranch = gitlabApplicationClient.getBranches(gitlabUrl, pat, gitlabProjectId).stream().filter(GitLabBranch::isDefault).findFirst(); - return almMainBranch.map(GitLabBranch::getName); - } - - private String getProjectKey(@Nullable String projectKey, Project gitlabProject) { - return Optional.ofNullable(projectKey).orElseGet(() -> projectKeyGenerator.generateUniqueProjectKey(gitlabProject.getPathWithNamespace())); - } - - private static String getProjectName(@Nullable String projectName, Project gitlabProject) { - return Optional.ofNullable(projectName).orElse(gitlabProject.getName()); - } - - private void createProjectAlmSettingDto(DbSession dbSession, String gitlabProjectId, ProjectDto projectDto, AlmSettingDto almSettingDto, Boolean monorepo) { - ProjectAlmSettingDto projectAlmSettingDto = new ProjectAlmSettingDto() - .setAlmSettingUuid(almSettingDto.getUuid()) - .setAlmRepo(gitlabProjectId) - .setProjectUuid(projectDto.getUuid()) - .setMonorepo(monorepo); - dbClient.projectAlmSettingDao().insertOrUpdate(dbSession, projectAlmSettingDto, almSettingDto.getKey(), projectDto.getName(), projectDto.getKey()); - } - -} diff --git a/server/sonar-webserver-common/src/main/java/org/sonar/server/common/almsettings/gitlab/GitlabProjectCreatorFactory.java b/server/sonar-webserver-common/src/main/java/org/sonar/server/common/almsettings/gitlab/GitlabProjectCreatorFactory.java index 19176e8c41a..8a13df6beb9 100644 --- a/server/sonar-webserver-common/src/main/java/org/sonar/server/common/almsettings/gitlab/GitlabProjectCreatorFactory.java +++ b/server/sonar-webserver-common/src/main/java/org/sonar/server/common/almsettings/gitlab/GitlabProjectCreatorFactory.java @@ -21,32 +21,44 @@ package org.sonar.server.common.almsettings.gitlab; import java.util.Map; import java.util.Optional; -import org.sonar.alm.client.gitlab.GitlabApplicationClient; +import org.sonar.auth.gitlab.GitLabSettings; 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.DefaultDevOpsProjectCreator; +import org.sonar.server.common.almsettings.DevOpsProjectCreationContext; 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.permission.PermissionUpdater; +import org.sonar.server.common.permission.UserPermissionChange; import org.sonar.server.common.project.ProjectCreator; -import org.sonar.server.user.UserSession; +import org.sonar.server.management.ManagedProjectService; +import org.sonar.server.permission.PermissionService; public class GitlabProjectCreatorFactory implements DevOpsProjectCreatorFactory { private final DbClient dbClient; private final ProjectKeyGenerator projectKeyGenerator; private final ProjectCreator projectCreator; - private final GitlabApplicationClient gitlabApplicationClient; - private final UserSession userSession; + private final GitLabSettings gitLabSettings; + private final PermissionService permissionService; + private final PermissionUpdater<UserPermissionChange> permissionUpdater; + private final ManagedProjectService managedProjectService; + private final GitlabDevOpsProjectCreationContextService gitlabDevOpsProjectService; - public GitlabProjectCreatorFactory(DbClient dbClient, ProjectKeyGenerator projectKeyGenerator, ProjectCreator projectCreator, GitlabApplicationClient gitlabApplicationClient, - UserSession userSession) { + public GitlabProjectCreatorFactory(DbClient dbClient, ProjectKeyGenerator projectKeyGenerator, ProjectCreator projectCreator, + GitLabSettings gitLabSettings, PermissionService permissionService, PermissionUpdater<UserPermissionChange> permissionUpdater, + ManagedProjectService managedProjectService, GitlabDevOpsProjectCreationContextService gitlabDevOpsProjectService) { this.dbClient = dbClient; this.projectKeyGenerator = projectKeyGenerator; this.projectCreator = projectCreator; - this.gitlabApplicationClient = gitlabApplicationClient; - this.userSession = userSession; + this.gitLabSettings = gitLabSettings; + this.permissionService = permissionService; + this.permissionUpdater = permissionUpdater; + this.managedProjectService = managedProjectService; + this.gitlabDevOpsProjectService = gitlabDevOpsProjectService; } @Override @@ -59,14 +71,21 @@ public class GitlabProjectCreatorFactory implements DevOpsProjectCreatorFactory if (almSettingDto.getAlm() != ALM.GITLAB) { return Optional.empty(); } + + DevOpsProjectCreationContext devOpsProjectCreationContext = gitlabDevOpsProjectService.create(almSettingDto, devOpsProjectDescriptor); + return Optional.of( - new GitlabProjectCreator( + new DefaultDevOpsProjectCreator( dbClient, + devOpsProjectCreationContext, projectKeyGenerator, + gitLabSettings, projectCreator, - almSettingDto, - devOpsProjectDescriptor, - gitlabApplicationClient, - userSession)); + permissionService, + permissionUpdater, + managedProjectService + ) + ); } + } diff --git a/server/sonar-webserver-common/src/test/java/org/sonar/server/common/almsettings/DefaultDevOpsProjectCreatorTest.java b/server/sonar-webserver-common/src/test/java/org/sonar/server/common/almsettings/DefaultDevOpsProjectCreatorTest.java new file mode 100644 index 00000000000..cc4fb7ec19b --- /dev/null +++ b/server/sonar-webserver-common/src/test/java/org/sonar/server/common/almsettings/DefaultDevOpsProjectCreatorTest.java @@ -0,0 +1,343 @@ +/* + * 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; + +import java.util.Collection; +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.Captor; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.sonar.api.resources.Qualifiers; +import org.sonar.api.web.UserRole; +import org.sonar.auth.DevOpsPlatformSettings; +import org.sonar.db.DbClient; +import org.sonar.db.alm.setting.ALM; +import org.sonar.db.alm.setting.AlmSettingDto; +import org.sonar.db.alm.setting.ProjectAlmSettingDao; +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.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.permission.PermissionUpdater; +import org.sonar.server.common.permission.UserPermissionChange; +import org.sonar.server.common.project.ProjectCreator; +import org.sonar.server.component.ComponentCreationData; +import org.sonar.server.management.ManagedProjectService; +import org.sonar.server.permission.PermissionService; +import org.sonar.server.permission.PermissionServiceImpl; +import org.sonar.server.project.ProjectDefaultVisibility; +import org.sonar.server.project.Visibility; +import org.sonar.server.user.UserSession; + +import static java.util.Objects.requireNonNull; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatException; +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.project.CreationMethod.ALM_IMPORT_API; +import static org.sonar.db.project.CreationMethod.SCANNER_API_DEVOPS_AUTO_CONFIG; + +@ExtendWith(MockitoExtension.class) +class DefaultDevOpsProjectCreatorTest { + + private static final String ORGANIZATION_NAME = "orga2"; + private static final String REPOSITORY_NAME = "repo1"; + + private static final String MAIN_BRANCH_NAME = "defaultBranch"; + private static final DevOpsProjectDescriptor DEVOPS_PROJECT_DESCRIPTOR = new DevOpsProjectDescriptor(ALM.GITHUB, "http://api.com", ORGANIZATION_NAME + "/" + REPOSITORY_NAME, + null); + private static final String ALM_SETTING_KEY = "github_config_1"; + private static final String USER_LOGIN = "userLogin"; + private static final String USER_UUID = "userUuid"; + + @Mock + private DbClient dbClient; + @Mock + private ProjectKeyGenerator projectKeyGenerator; + @Mock + private DevOpsPlatformSettings devOpsPlatformSettings; + @Mock + private PermissionUpdater<UserPermissionChange> permissionUpdater; + @Mock + private ManagedProjectService managedProjectService; + @Mock + private DevOpsProjectCreationContext devOpsProjectCreationContext; + + @InjectMocks + private DefaultDevOpsProjectCreator defaultDevOpsProjectCreator; + + @Captor + ArgumentCaptor<ComponentCreationParameters> componentCreationParametersCaptor; + @Captor + ArgumentCaptor<ProjectAlmSettingDto> projectAlmSettingDtoCaptor; + + @Mock + private AlmSettingDto almSettingDto; + @Mock + private UserSession userSession; + @Mock(answer = Answers.RETURNS_DEEP_STUBS) + private ProjectDefaultVisibility projectDefaultVisibility; + @Mock + private ComponentUpdater componentUpdater; + + private final PermissionService permissionService = new PermissionServiceImpl(mock()); + + @BeforeEach + void setup() { + lenient().when(userSession.getLogin()).thenReturn(USER_LOGIN); + lenient().when(userSession.getUuid()).thenReturn(USER_UUID); + lenient().when(devOpsProjectCreationContext.userSession()).thenReturn(userSession); + + lenient().when(almSettingDto.getUrl()).thenReturn(DEVOPS_PROJECT_DESCRIPTOR.url()); + lenient().when(almSettingDto.getKey()).thenReturn(ALM_SETTING_KEY); + lenient().when(devOpsProjectCreationContext.almSettingDto()).thenReturn(almSettingDto); + + lenient().when(devOpsProjectCreationContext.name()).thenReturn(REPOSITORY_NAME); + lenient().when(devOpsProjectCreationContext.devOpsPlatformIdentifier()).thenReturn(ORGANIZATION_NAME + "/" + REPOSITORY_NAME); + lenient().when(devOpsProjectCreationContext.fullName()).thenReturn(ORGANIZATION_NAME + "/" + REPOSITORY_NAME); + lenient().when(devOpsProjectCreationContext.defaultBranchName()).thenReturn(MAIN_BRANCH_NAME); + + ProjectCreator projectCreator = new ProjectCreator(userSession, projectDefaultVisibility, componentUpdater); + defaultDevOpsProjectCreator = new DefaultDevOpsProjectCreator(dbClient, devOpsProjectCreationContext, projectKeyGenerator, devOpsPlatformSettings, projectCreator, + permissionService, permissionUpdater, + managedProjectService); + + } + + @Test + void isScanAllowedUsingPermissionsFromDevopsPlatform_throws() { + assertThatException() + .isThrownBy(() -> defaultDevOpsProjectCreator.isScanAllowedUsingPermissionsFromDevopsPlatform()) + .isInstanceOf(UnsupportedOperationException.class) + .withMessage("Not Implemented"); + } + + @Test + void createProjectAndBindToDevOpsPlatformFromScanner_whenVisibilitySyncDeactivated_successfullyCreatesProjectAndUseDefaultProjectVisibility() { + // given + mockGeneratedProjectKey(); + + ComponentCreationData componentCreationData = mockProjectCreation("generated_orga2/repo1"); + ProjectAlmSettingDao projectAlmSettingDao = mock(); + when(dbClient.projectAlmSettingDao()).thenReturn(projectAlmSettingDao); + when(projectDefaultVisibility.get(any())).thenReturn(Visibility.PRIVATE); + + // when + ComponentCreationData actualComponentCreationData = defaultDevOpsProjectCreator.createProjectAndBindToDevOpsPlatform(dbClient.openSession(true), + SCANNER_API_DEVOPS_AUTO_CONFIG, false, null, null); + + // then + assertThat(actualComponentCreationData).isEqualTo(componentCreationData); + + ComponentCreationParameters componentCreationParameters = componentCreationParametersCaptor.getValue(); + assertComponentCreationParametersContainsCorrectInformation(componentCreationParameters, "generated_orga2/repo1", SCANNER_API_DEVOPS_AUTO_CONFIG); + assertThat(componentCreationParameters.isManaged()).isFalse(); + assertThat(componentCreationParameters.newComponent().isPrivate()).isTrue(); + + verify(projectAlmSettingDao).insertOrUpdate(any(), projectAlmSettingDtoCaptor.capture(), eq(ALM_SETTING_KEY), eq(REPOSITORY_NAME), eq("generated_orga2/repo1")); + ProjectAlmSettingDto projectAlmSettingDto = projectAlmSettingDtoCaptor.getValue(); + assertAlmSettingsDtoContainsCorrectInformation(almSettingDto, requireNonNull(componentCreationData.projectDto()), projectAlmSettingDto); + } + + @Test + void createProjectAndBindToDevOpsPlatformFromScanner_whenVisibilitySynchronizationEnabled_successfullyCreatesProjectAndSetsVisibility() { + // given + mockGeneratedProjectKey(); + when(devOpsProjectCreationContext.isPublic()).thenReturn(true); + + ComponentCreationData componentCreationData = mockProjectCreation("generated_orga2/repo1"); + ProjectAlmSettingDao projectAlmSettingDao = mock(); + when(dbClient.projectAlmSettingDao()).thenReturn(projectAlmSettingDao); + when(devOpsPlatformSettings.isProvisioningEnabled()).thenReturn(true); + when(devOpsPlatformSettings.isProjectVisibilitySynchronizationActivated()).thenReturn(true); + + // when + ComponentCreationData actualComponentCreationData = defaultDevOpsProjectCreator.createProjectAndBindToDevOpsPlatform(dbClient.openSession(true), + SCANNER_API_DEVOPS_AUTO_CONFIG, false, null, null); + + // then + assertThat(actualComponentCreationData).isEqualTo(componentCreationData); + + ComponentCreationParameters componentCreationParameters = componentCreationParametersCaptor.getValue(); + assertThat(componentCreationParameters.newComponent().isPrivate()).isFalse(); + } + + @Test + void createProjectAndBindToDevOpsPlatformFromScanner_whenVisibilitySynchronizationDisabled_successfullyCreatesProjectAndMakesProjectPrivate() { + // given + mockGeneratedProjectKey(); + + ComponentCreationData componentCreationData = mockProjectCreation("generated_orga2/repo1"); + ProjectAlmSettingDao projectAlmSettingDao = mock(); + when(dbClient.projectAlmSettingDao()).thenReturn(projectAlmSettingDao); + when(devOpsPlatformSettings.isProvisioningEnabled()).thenReturn(true); + when(devOpsPlatformSettings.isProjectVisibilitySynchronizationActivated()).thenReturn(false); + + // when + ComponentCreationData actualComponentCreationData = defaultDevOpsProjectCreator.createProjectAndBindToDevOpsPlatform(dbClient.openSession(true), + SCANNER_API_DEVOPS_AUTO_CONFIG, false, null, null); + + // then + assertThat(actualComponentCreationData).isEqualTo(componentCreationData); + + ComponentCreationParameters componentCreationParameters = componentCreationParametersCaptor.getValue(); + assertThat(componentCreationParameters.newComponent().isPrivate()).isTrue(); + } + + @Test + void createProjectAndBindToDevOpsPlatformFromApi_whenRepoFoundOnGitHub_successfullyCreatesProject() { + // given + String projectKey = "customProjectKey"; + mockGeneratedProjectKey(); + + ComponentCreationData componentCreationData = mockProjectCreation(projectKey); + ProjectAlmSettingDao projectAlmSettingDao = mock(); + when(dbClient.projectAlmSettingDao()).thenReturn(projectAlmSettingDao); + when(projectDefaultVisibility.get(any())).thenReturn(Visibility.PRIVATE); + + // when + ComponentCreationData actualComponentCreationData = defaultDevOpsProjectCreator.createProjectAndBindToDevOpsPlatform(dbClient.openSession(true), ALM_IMPORT_API, false, + projectKey, + null); + + // then + assertThat(actualComponentCreationData).isEqualTo(componentCreationData); + + ComponentCreationParameters componentCreationParameters = componentCreationParametersCaptor.getValue(); + assertComponentCreationParametersContainsCorrectInformation(componentCreationParameters, projectKey, ALM_IMPORT_API); + assertThat(componentCreationParameters.isManaged()).isFalse(); + assertThat(componentCreationParameters.newComponent().isPrivate()).isTrue(); + + verify(projectAlmSettingDao).insertOrUpdate(any(), projectAlmSettingDtoCaptor.capture(), eq(ALM_SETTING_KEY), eq(REPOSITORY_NAME), eq(projectKey)); + ProjectAlmSettingDto projectAlmSettingDto = projectAlmSettingDtoCaptor.getValue(); + assertAlmSettingsDtoContainsCorrectInformation(almSettingDto, requireNonNull(componentCreationData.projectDto()), projectAlmSettingDto); + } + + @Captor + private ArgumentCaptor<Collection<UserPermissionChange>> permissionChangesCaptor; + + @Test + void createProjectAndBindToDevOpsPlatformFromApi_whenAutoProvisioningOnAndRepoPrivate_successfullyCreatesProject() { + // given + String projectKey = "customProjectKey"; + mockGeneratedProjectKey(); + + ComponentCreationData componentCreationData = mockProjectCreation(projectKey); + ProjectAlmSettingDao projectAlmSettingDao = mock(); + when(dbClient.projectAlmSettingDao()).thenReturn(projectAlmSettingDao); + when(devOpsPlatformSettings.isProvisioningEnabled()).thenReturn(true); + + // when + ComponentCreationData actualComponentCreationData = defaultDevOpsProjectCreator.createProjectAndBindToDevOpsPlatform(dbClient.openSession(true), ALM_IMPORT_API, false, + projectKey, + null); + + // then + assertThat(actualComponentCreationData).isEqualTo(componentCreationData); + + ComponentCreationParameters componentCreationParameters = componentCreationParametersCaptor.getValue(); + assertComponentCreationParametersContainsCorrectInformation(componentCreationParameters, projectKey, ALM_IMPORT_API); + assertThat(componentCreationParameters.isManaged()).isTrue(); + assertThat(componentCreationParameters.newComponent().isPrivate()).isTrue(); + + verifyScanPermissionWasAddedToUser(actualComponentCreationData); + verifyProjectSyncTaskWasCreated(actualComponentCreationData); + + verify(projectAlmSettingDao).insertOrUpdate(any(), projectAlmSettingDtoCaptor.capture(), eq(ALM_SETTING_KEY), eq(REPOSITORY_NAME), eq(projectKey)); + ProjectAlmSettingDto projectAlmSettingDto = projectAlmSettingDtoCaptor.getValue(); + assertAlmSettingsDtoContainsCorrectInformation(almSettingDto, requireNonNull(componentCreationData.projectDto()), projectAlmSettingDto); + } + + private void verifyProjectSyncTaskWasCreated(ComponentCreationData componentCreationData) { + String projectUuid = requireNonNull(componentCreationData.projectDto()).getUuid(); + String mainBranchUuid = requireNonNull(componentCreationData.mainBranchDto()).getUuid(); + verify(managedProjectService).queuePermissionSyncTask(USER_UUID, mainBranchUuid, projectUuid); + } + + private void verifyScanPermissionWasAddedToUser(ComponentCreationData actualComponentCreationData) { + verify(permissionUpdater).apply(any(), permissionChangesCaptor.capture()); + UserPermissionChange permissionChange = permissionChangesCaptor.getValue().iterator().next(); + assertThat(permissionChange.getUserId().getUuid()).isEqualTo(userSession.getUuid()); + assertThat(permissionChange.getUserId().getLogin()).isEqualTo(userSession.getLogin()); + assertThat(permissionChange.getPermission()).isEqualTo(UserRole.SCAN); + assertThat(permissionChange.getProjectUuid()).isEqualTo(actualComponentCreationData.projectDto().getUuid()); + } + + private void mockGeneratedProjectKey() { + String generatedKey = "generated_" + devOpsProjectCreationContext.devOpsPlatformIdentifier(); + when(projectKeyGenerator.generateUniqueProjectKey(devOpsProjectCreationContext.fullName())).thenReturn(generatedKey); + } + + private ComponentCreationData mockProjectCreation(String projectKey) { + ComponentCreationData componentCreationData = mock(); + ProjectDto projectDto = mockProjectDto(projectKey); + when(componentCreationData.projectDto()).thenReturn(projectDto); + BranchDto branchDto = mock(); + when(componentCreationData.mainBranchDto()).thenReturn(branchDto); + when(componentUpdater.createWithoutCommit(any(), componentCreationParametersCaptor.capture())).thenReturn(componentCreationData); + return componentCreationData; + } + + private static ProjectDto mockProjectDto(String projectKey) { + ProjectDto projectDto = mock(); + when(projectDto.getName()).thenReturn(REPOSITORY_NAME); + when(projectDto.getKey()).thenReturn(projectKey); + when(projectDto.getUuid()).thenReturn("project-uuid-1"); + return projectDto; + } + + private static void assertComponentCreationParametersContainsCorrectInformation(ComponentCreationParameters componentCreationParameters, String expectedKey, + CreationMethod expectedCreationMethod) { + assertThat(componentCreationParameters.creationMethod()).isEqualTo(expectedCreationMethod); + assertThat(componentCreationParameters.mainBranchName()).isEqualTo(MAIN_BRANCH_NAME); + assertThat(componentCreationParameters.userLogin()).isEqualTo(USER_LOGIN); + assertThat(componentCreationParameters.userUuid()).isEqualTo(USER_UUID); + + NewComponent newComponent = componentCreationParameters.newComponent(); + assertThat(newComponent.isProject()).isTrue(); + assertThat(newComponent.qualifier()).isEqualTo(Qualifiers.PROJECT); + assertThat(newComponent.key()).isEqualTo(expectedKey); + assertThat(newComponent.name()).isEqualTo(REPOSITORY_NAME); + } + + private static void assertAlmSettingsDtoContainsCorrectInformation(AlmSettingDto almSettingDto, ProjectDto projectDto, ProjectAlmSettingDto projectAlmSettingDto) { + assertThat(projectAlmSettingDto.getAlmRepo()).isEqualTo(DEVOPS_PROJECT_DESCRIPTOR.repositoryIdentifier()); + assertThat(projectAlmSettingDto.getAlmSlug()).isNull(); + assertThat(projectAlmSettingDto.getAlmSettingUuid()).isEqualTo(almSettingDto.getUuid()); + assertThat(projectAlmSettingDto.getProjectUuid()).isEqualTo(projectDto.getUuid()); + assertThat(projectAlmSettingDto.getMonorepo()).isFalse(); + assertThat(projectAlmSettingDto.getSummaryCommentEnabled()).isTrue(); + } + +} + diff --git a/server/sonar-webserver-common/src/test/java/org/sonar/server/common/almsettings/github/GithubDevOpsProjectCreationContextServiceTest.java b/server/sonar-webserver-common/src/test/java/org/sonar/server/common/almsettings/github/GithubDevOpsProjectCreationContextServiceTest.java new file mode 100644 index 00000000000..b37660ae95f --- /dev/null +++ b/server/sonar-webserver-common/src/test/java/org/sonar/server/common/almsettings/github/GithubDevOpsProjectCreationContextServiceTest.java @@ -0,0 +1,139 @@ +/* + * 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.github; + +import java.util.Optional; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Answers; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.sonar.auth.github.client.GithubApplicationClient; +import org.sonar.auth.github.client.GithubApplicationClient.Repository; +import org.sonar.auth.github.security.UserAccessToken; +import org.sonar.db.DbClient; +import org.sonar.db.alm.pat.AlmPatDto; +import org.sonar.db.alm.setting.ALM; +import org.sonar.db.alm.setting.AlmSettingDto; +import org.sonar.server.common.almsettings.DevOpsProjectCreationContext; +import org.sonar.server.common.almsettings.DevOpsProjectDescriptor; +import org.sonar.server.user.UserSession; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; +import static org.assertj.core.api.Assertions.assertThatIllegalStateException; +import static org.assertj.core.api.Assertions.assertThatNullPointerException; +import static org.mockito.Mockito.lenient; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +@ExtendWith(MockitoExtension.class) +class GithubDevOpsProjectCreationContextServiceTest { + + private static final DevOpsProjectDescriptor DEV_OPS_PROJECT_DESCRIPTOR = new DevOpsProjectDescriptor(ALM.GITHUB, "project-key", "repository-identifier", null); + + @Mock(answer = Answers.RETURNS_DEEP_STUBS) + private DbClient dbClient; + @Mock + private UserSession userSession; + @Mock + private GithubApplicationClient githubApplicationClient; + + @InjectMocks + private GithubDevOpsProjectCreationContextService githubDevOpsProjectService; + + @Test + void create_whenUserUuidIsNull_shouldThrow() { + AlmSettingDto almSettingDto = mock(); + + assertThatNullPointerException() + .isThrownBy(() -> githubDevOpsProjectService.create(almSettingDto, DEV_OPS_PROJECT_DESCRIPTOR)) + .withMessage("User UUID cannot be null."); + } + + @Test + void create_whenNoPat_shouldThrow() { + AlmSettingDto almSettingDto = mock(); + + when(userSession.getUuid()).thenReturn("user-uuid"); + when(dbClient.almPatDao().selectByUserAndAlmSetting(dbClient.openSession(false), userSession.getUuid(), almSettingDto)).thenReturn(Optional.empty()); + + assertThatIllegalArgumentException() + .isThrownBy(() -> githubDevOpsProjectService.create(almSettingDto, DEV_OPS_PROJECT_DESCRIPTOR)) + .withMessage("No personal access token found"); + } + + @Test + void create_whenRepoNotFound_throws() { + AlmSettingDto almSettingDto = mockAlmSettingDto(); + + mockValidAccessToken(almSettingDto); + + assertThatIllegalStateException() + .isThrownBy(() -> githubDevOpsProjectService.create(almSettingDto, DEV_OPS_PROJECT_DESCRIPTOR)) + .withMessage("Impossible to find the repository 'repository-identifier' on GitHub, using the devops config alm-config-key"); + } + + @Test + void create_whenRepoFound_createsDevOpsProject() { + AlmSettingDto almSettingDto = mockAlmSettingDto(); + + AlmPatDto almPatDto = mockValidAccessToken(almSettingDto); + + Repository repository = mockGitHubRepository(almPatDto, almSettingDto); + + DevOpsProjectCreationContext devOpsProjectCreationContext = githubDevOpsProjectService.create(almSettingDto, DEV_OPS_PROJECT_DESCRIPTOR); + + assertThat(devOpsProjectCreationContext.name()).isEqualTo(repository.getName()); + assertThat(devOpsProjectCreationContext.fullName()).isEqualTo(repository.getFullName()); + assertThat(devOpsProjectCreationContext.devOpsPlatformIdentifier()).isEqualTo(repository.getFullName()); + assertThat(devOpsProjectCreationContext.isPublic()).isTrue(); + assertThat(devOpsProjectCreationContext.defaultBranchName()).isEqualTo(repository.getDefaultBranch()); + } + + private static AlmSettingDto mockAlmSettingDto() { + AlmSettingDto almSettingDto = mock(); + when(almSettingDto.getUrl()).thenReturn("http://www.url.com"); + lenient().when(almSettingDto.getKey()).thenReturn("alm-config-key"); + return almSettingDto; + } + + private AlmPatDto mockValidAccessToken(AlmSettingDto almSettingDto) { + when(userSession.getUuid()).thenReturn("user-uuid"); + AlmPatDto almPatDto = mock(); + when(dbClient.almPatDao().selectByUserAndAlmSetting(dbClient.openSession(false), userSession.getUuid(), almSettingDto)).thenReturn(Optional.of(almPatDto)); + when(almPatDto.getPersonalAccessToken()).thenReturn("token"); + return almPatDto; + } + + private Repository mockGitHubRepository(AlmPatDto almPatDto, AlmSettingDto almSettingDto) { + Repository repository = mock(); + when(repository.getName()).thenReturn("name"); + when(repository.getFullName()).thenReturn("full-name"); + when(repository.isPrivate()).thenReturn(false); + when(repository.getDefaultBranch()).thenReturn("default-branch"); + UserAccessToken accessToken = new UserAccessToken(almPatDto.getPersonalAccessToken()); + when(githubApplicationClient.getRepository(almSettingDto.getUrl(), accessToken, + DEV_OPS_PROJECT_DESCRIPTOR.repositoryIdentifier())).thenReturn(Optional.of(repository)); + return repository; + } + +} diff --git a/server/sonar-webserver-common/src/test/java/org/sonar/server/common/almsettings/github/GithubProjectCreatorFactoryTest.java b/server/sonar-webserver-common/src/test/java/org/sonar/server/common/almsettings/github/GithubProjectCreatorFactoryTest.java index 29c2ec7d57d..eebe1c535a0 100644 --- a/server/sonar-webserver-common/src/test/java/org/sonar/server/common/almsettings/github/GithubProjectCreatorFactoryTest.java +++ b/server/sonar-webserver-common/src/test/java/org/sonar/server/common/almsettings/github/GithubProjectCreatorFactoryTest.java @@ -41,6 +41,7 @@ import org.sonar.db.alm.pat.AlmPatDto; 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.DevOpsProjectCreationContext; import org.sonar.server.common.almsettings.DevOpsProjectCreator; import org.sonar.server.common.almsettings.DevOpsProjectDescriptor; import org.sonar.server.common.permission.PermissionUpdater; @@ -50,7 +51,6 @@ import org.sonar.server.exceptions.BadConfigurationException; import org.sonar.server.management.ManagedProjectService; import org.sonar.server.permission.PermissionService; import org.sonar.server.project.ProjectDefaultVisibility; -import org.sonar.server.user.UserSession; import static java.lang.String.format; import static org.assertj.core.api.Assertions.assertThat; @@ -77,6 +77,7 @@ public class GithubProjectCreatorFactoryTest { DEVOPS_PLATFORM_PROJECT_IDENTIFIER, GITHUB_PROJECT_DESCRIPTOR.repositoryIdentifier()); private static final long APP_INSTALLATION_ID = 534534534543L; private static final String USER_ACCESS_TOKEN = "userPat"; + private static final DevOpsProjectCreationContext DEV_OPS_PROJECT = mock(); @Mock private DbSession dbSession; @@ -86,8 +87,6 @@ public class GithubProjectCreatorFactoryTest { private GithubApplicationClient githubApplicationClient; @Mock(answer = Answers.RETURNS_DEEP_STUBS) private DbClient dbClient; - @Mock - private UserSession userSession; @Mock(answer = Answers.RETURNS_DEEP_STUBS) private ProjectDefaultVisibility projectDefaultVisibility; @Mock @@ -108,6 +107,8 @@ public class GithubProjectCreatorFactoryTest { private ManagedProjectService managedProjectService; @Mock private ProjectCreator projectCreator; + @Mock + private GithubDevOpsProjectCreationContextService devOpsProjectService; @InjectMocks private GithubProjectCreatorFactory githubProjectCreatorFactory; @@ -160,6 +161,7 @@ public class GithubProjectCreatorFactoryTest { AlmSettingDto almSettingDto = mockAlmSettingDto(true); mockSuccessfulGithubInteraction(); + when(devOpsProjectService.createDevOpsProject(almSettingDto, GITHUB_PROJECT_DESCRIPTOR, appInstallationToken)).thenReturn(DEV_OPS_PROJECT); DevOpsProjectCreator devOpsProjectCreator = githubProjectCreatorFactory.getDevOpsProjectCreator(dbSession, VALID_GITHUB_PROJECT_COORDINATES).orElseThrow(); GithubProjectCreator expectedGithubProjectCreator = getExpectedGithubProjectCreator(almSettingDto, false, appInstallationToken); @@ -177,7 +179,9 @@ public class GithubProjectCreatorFactoryTest { long authAppInstallationId = 32; when(githubApplicationClient.getInstallationId(any(), eq(GITHUB_REPO_FULL_NAME))).thenReturn(Optional.of(authAppInstallationId)); when(githubApplicationClient.createAppInstallationToken(any(), eq(authAppInstallationId))).thenReturn(Optional.of(authAppInstallationToken)); + when(DEV_OPS_PROJECT.devOpsPlatformIdentifier()).thenReturn(GITHUB_REPO_FULL_NAME); + when(devOpsProjectService.createDevOpsProject(almSettingDto, GITHUB_PROJECT_DESCRIPTOR, authAppInstallationToken)).thenReturn(DEV_OPS_PROJECT); DevOpsProjectCreator devOpsProjectCreator = githubProjectCreatorFactory.getDevOpsProjectCreator(dbSession, VALID_GITHUB_PROJECT_COORDINATES).orElseThrow(); GithubProjectCreator expectedGithubProjectCreator = getExpectedGithubProjectCreator(almSettingDto, true, appInstallationToken); @@ -192,6 +196,7 @@ public class GithubProjectCreatorFactoryTest { mockSuccessfulGithubInteraction(); + when(devOpsProjectService.createDevOpsProject(matchingAlmSettingDto, GITHUB_PROJECT_DESCRIPTOR, appInstallationToken)).thenReturn(DEV_OPS_PROJECT); DevOpsProjectCreator devOpsProjectCreator = githubProjectCreatorFactory.getDevOpsProjectCreator(dbSession, VALID_GITHUB_PROJECT_COORDINATES).orElseThrow(); GithubProjectCreator expectedGithubProjectCreator = getExpectedGithubProjectCreator(matchingAlmSettingDto, false, appInstallationToken); @@ -205,6 +210,7 @@ public class GithubProjectCreatorFactoryTest { mockSuccessfulGithubInteraction(); + when(devOpsProjectService.create(mockAlmSettingDto, GITHUB_PROJECT_DESCRIPTOR)).thenReturn(DEV_OPS_PROJECT); DevOpsProjectCreator devOpsProjectCreator = githubProjectCreatorFactory.getDevOpsProjectCreator(mockAlmSettingDto, GITHUB_PROJECT_DESCRIPTOR).orElseThrow(); GithubProjectCreator expectedGithubProjectCreator = getExpectedGithubProjectCreator(mockAlmSettingDto, false, new UserAccessToken(USER_ACCESS_TOKEN)); @@ -219,6 +225,8 @@ public class GithubProjectCreatorFactoryTest { mockValidGitHubSettings(); when(githubApplicationClient.getInstallationId(any(), eq(GITHUB_REPO_FULL_NAME))).thenReturn(Optional.empty()); + when(devOpsProjectService.create(mockAlmSettingDto, GITHUB_PROJECT_DESCRIPTOR)).thenReturn(DEV_OPS_PROJECT); + when(DEV_OPS_PROJECT.devOpsPlatformIdentifier()).thenReturn(GITHUB_PROJECT_DESCRIPTOR.repositoryIdentifier()); assertThatThrownBy(() -> githubProjectCreatorFactory.getDevOpsProjectCreator(mockAlmSettingDto, GITHUB_PROJECT_DESCRIPTOR)) .isInstanceOf(BadConfigurationException.class) @@ -240,12 +248,10 @@ public class GithubProjectCreatorFactoryTest { } private GithubProjectCreator getExpectedGithubProjectCreator(AlmSettingDto almSettingDto, boolean isInstanceManaged, AccessToken accessToken) { - DevOpsProjectDescriptor devOpsProjectDescriptor = new DevOpsProjectDescriptor(ALM.GITHUB, almSettingDto.getUrl(), GITHUB_REPO_FULL_NAME, null); AppInstallationToken authAppInstallToken = isInstanceManaged ? authAppInstallationToken : null; - GithubProjectCreationParameters githubProjectCreationParameters = new GithubProjectCreationParameters(devOpsProjectDescriptor, almSettingDto, userSession, accessToken, - authAppInstallToken); - return new GithubProjectCreator(dbClient, githubApplicationClient, githubPermissionConverter, projectKeyGenerator, permissionUpdater, permissionService, - managedProjectService, projectCreator, githubProjectCreationParameters, gitHubSettings); + return new GithubProjectCreator(dbClient, DEV_OPS_PROJECT, + projectKeyGenerator, gitHubSettings, projectCreator, permissionService, permissionUpdater, managedProjectService, githubApplicationClient, + githubPermissionConverter, authAppInstallToken); } private AlmSettingDto mockAlmSettingDto(boolean repoAccess) { @@ -258,7 +264,6 @@ public class GithubProjectCreatorFactoryTest { } private void mockAlmPatDto(AlmSettingDto almSettingDto) { - when(userSession.getUuid()).thenReturn("userUuid"); when(dbClient.almPatDao().selectByUserAndAlmSetting(any(), eq("userUuid"), eq(almSettingDto))) .thenReturn(Optional.of(new AlmPatDto().setPersonalAccessToken(USER_ACCESS_TOKEN))); } diff --git a/server/sonar-webserver-common/src/test/java/org/sonar/server/common/almsettings/github/GithubProjectCreatorTest.java b/server/sonar-webserver-common/src/test/java/org/sonar/server/common/almsettings/github/GithubProjectCreatorTest.java index 705e1468349..bdf3bf51c7e 100644 --- a/server/sonar-webserver-common/src/test/java/org/sonar/server/common/almsettings/github/GithubProjectCreatorTest.java +++ b/server/sonar-webserver-common/src/test/java/org/sonar/server/common/almsettings/github/GithubProjectCreatorTest.java @@ -20,19 +20,15 @@ package org.sonar.server.common.almsettings.github; import java.util.Arrays; -import java.util.Collection; import java.util.Optional; import java.util.Set; 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.Captor; import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; import org.sonar.alm.client.github.GithubPermissionConverter; -import org.sonar.api.resources.Qualifiers; import org.sonar.api.web.UserRole; import org.sonar.auth.github.AppInstallationToken; import org.sonar.auth.github.GitHubSettings; @@ -40,45 +36,31 @@ import org.sonar.auth.github.GsonRepositoryCollaborator; import org.sonar.auth.github.GsonRepositoryPermissions; import org.sonar.auth.github.GsonRepositoryTeam; import org.sonar.auth.github.client.GithubApplicationClient; -import org.sonar.auth.github.security.AccessToken; import org.sonar.db.DbClient; import org.sonar.db.alm.setting.ALM; import org.sonar.db.alm.setting.AlmSettingDto; -import org.sonar.db.alm.setting.ProjectAlmSettingDao; -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.db.provisioning.GithubPermissionsMappingDto; import org.sonar.db.user.GroupDto; import org.sonar.server.common.almintegration.ProjectKeyGenerator; +import org.sonar.server.common.almsettings.DevOpsProjectCreationContext; import org.sonar.server.common.almsettings.DevOpsProjectDescriptor; -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.permission.PermissionUpdater; import org.sonar.server.common.permission.UserPermissionChange; import org.sonar.server.common.project.ProjectCreator; -import org.sonar.server.component.ComponentCreationData; import org.sonar.server.management.ManagedProjectService; import org.sonar.server.permission.PermissionService; import org.sonar.server.permission.PermissionServiceImpl; import org.sonar.server.project.ProjectDefaultVisibility; -import org.sonar.server.project.Visibility; import org.sonar.server.user.UserSession; -import static java.util.Objects.requireNonNull; import static java.util.stream.Collectors.toSet; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatIllegalStateException; 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.project.CreationMethod.ALM_IMPORT_API; -import static org.sonar.db.project.CreationMethod.SCANNER_API_DEVOPS_AUTO_CONFIG; @ExtendWith(MockitoExtension.class) class GithubProjectCreatorTest { @@ -87,7 +69,8 @@ class GithubProjectCreatorTest { private static final String REPOSITORY_NAME = "repo1"; private static final String MAIN_BRANCH_NAME = "defaultBranch"; - private static final DevOpsProjectDescriptor DEVOPS_PROJECT_DESCRIPTOR = new DevOpsProjectDescriptor(ALM.GITHUB, "http://api.com", ORGANIZATION_NAME + "/" + REPOSITORY_NAME, null); + private static final DevOpsProjectDescriptor DEVOPS_PROJECT_DESCRIPTOR = new DevOpsProjectDescriptor(ALM.GITHUB, "http://api.com", ORGANIZATION_NAME + "/" + REPOSITORY_NAME, + null); private static final String ALM_SETTING_KEY = "github_config_1"; private static final String USER_LOGIN = "userLogin"; private static final String USER_UUID = "userUuid"; @@ -103,10 +86,6 @@ class GithubProjectCreatorTest { @Mock private ComponentUpdater componentUpdater; @Mock - private GithubProjectCreationParameters githubProjectCreationParameters; - @Mock - private AccessToken devOpsAppInstallationToken; - @Mock private AppInstallationToken authAppInstallationToken; @Mock private UserSession userSession; @@ -119,38 +98,38 @@ class GithubProjectCreatorTest { private ManagedProjectService managedProjectService; @Mock(answer = Answers.RETURNS_DEEP_STUBS) private ProjectDefaultVisibility projectDefaultVisibility; + @Mock + private DevOpsProjectCreationContext devOpsProjectCreationContext; + private final GitHubSettings gitHubSettings = mock(); private GithubProjectCreator githubProjectCreator; - @Captor - ArgumentCaptor<ComponentCreationParameters> componentCreationParametersCaptor; - @Captor - ArgumentCaptor<ProjectAlmSettingDto> projectAlmSettingDtoCaptor; - @BeforeEach void setup() { lenient().when(userSession.getLogin()).thenReturn(USER_LOGIN); lenient().when(userSession.getUuid()).thenReturn(USER_UUID); + lenient().when(devOpsProjectCreationContext.userSession()).thenReturn(userSession); lenient().when(almSettingDto.getUrl()).thenReturn(DEVOPS_PROJECT_DESCRIPTOR.url()); lenient().when(almSettingDto.getKey()).thenReturn(ALM_SETTING_KEY); + lenient().when(devOpsProjectCreationContext.almSettingDto()).thenReturn(almSettingDto); - when(githubProjectCreationParameters.devOpsProjectDescriptor()).thenReturn(DEVOPS_PROJECT_DESCRIPTOR); - when(githubProjectCreationParameters.userSession()).thenReturn(userSession); - when(githubProjectCreationParameters.devOpsAppInstallationToken()).thenReturn(devOpsAppInstallationToken); - when(githubProjectCreationParameters.authAppInstallationToken()).thenReturn(authAppInstallationToken); - when(githubProjectCreationParameters.almSettingDto()).thenReturn(almSettingDto); + lenient().when(devOpsProjectCreationContext.name()).thenReturn(REPOSITORY_NAME); + lenient().when(devOpsProjectCreationContext.devOpsPlatformIdentifier()).thenReturn(ORGANIZATION_NAME + "/" + REPOSITORY_NAME); + lenient().when(devOpsProjectCreationContext.fullName()).thenReturn(ORGANIZATION_NAME + "/" + REPOSITORY_NAME); + lenient().when(devOpsProjectCreationContext.defaultBranchName()).thenReturn(MAIN_BRANCH_NAME); ProjectCreator projectCreator = new ProjectCreator(userSession, projectDefaultVisibility, componentUpdater); - githubProjectCreator = new GithubProjectCreator(dbClient, githubApplicationClient, githubPermissionConverter, projectKeyGenerator, - permissionUpdater, permissionService, managedProjectService, projectCreator, githubProjectCreationParameters, gitHubSettings); + githubProjectCreator = new GithubProjectCreator(dbClient, devOpsProjectCreationContext, projectKeyGenerator, gitHubSettings, projectCreator, permissionService, permissionUpdater, + managedProjectService, githubApplicationClient, githubPermissionConverter, authAppInstallationToken); } @Test void isScanAllowedUsingPermissionsFromDevopsPlatform_whenNoAuthToken_throws() { - when(githubProjectCreationParameters.authAppInstallationToken()).thenReturn(null); + githubProjectCreator = new GithubProjectCreator(dbClient, devOpsProjectCreationContext, projectKeyGenerator, gitHubSettings, null, permissionService, permissionUpdater, + managedProjectService, githubApplicationClient, githubPermissionConverter, null); assertThatIllegalStateException().isThrownBy(() -> githubProjectCreator.isScanAllowedUsingPermissionsFromDevopsPlatform()) .withMessage("An auth app token is required in case repository permissions checking is necessary."); @@ -263,216 +242,4 @@ class GithubProjectCreatorTest { when(userSession.getGroups()).thenReturn(groupDtos); } - @Test - void createProjectAndBindToDevOpsPlatform_whenRepoNotFound_throws() { - assertThatIllegalStateException().isThrownBy( - () -> githubProjectCreator.createProjectAndBindToDevOpsPlatform(mock(), SCANNER_API_DEVOPS_AUTO_CONFIG, false, null, null)) - .withMessage("Impossible to find the repository 'orga2/repo1' on GitHub, using the devops config " + ALM_SETTING_KEY); - } - - @Test - void createProjectAndBindToDevOpsPlatformFromScanner_whenRepoFoundOnGitHub_successfullyCreatesProject() { - // given - mockGitHubRepository(); - - ComponentCreationData componentCreationData = mockProjectCreation("generated_orga2/repo1"); - ProjectAlmSettingDao projectAlmSettingDao = mock(); - when(dbClient.projectAlmSettingDao()).thenReturn(projectAlmSettingDao); - when(projectDefaultVisibility.get(any())).thenReturn(Visibility.PRIVATE); - - // when - ComponentCreationData actualComponentCreationData = githubProjectCreator.createProjectAndBindToDevOpsPlatform(dbClient.openSession(true), - SCANNER_API_DEVOPS_AUTO_CONFIG, false, null, null); - - // then - assertThat(actualComponentCreationData).isEqualTo(componentCreationData); - - ComponentCreationParameters componentCreationParameters = componentCreationParametersCaptor.getValue(); - assertComponentCreationParametersContainsCorrectInformation(componentCreationParameters, "generated_orga2/repo1", SCANNER_API_DEVOPS_AUTO_CONFIG); - assertThat(componentCreationParameters.isManaged()).isFalse(); - assertThat(componentCreationParameters.newComponent().isPrivate()).isTrue(); - - verify(projectAlmSettingDao).insertOrUpdate(any(), projectAlmSettingDtoCaptor.capture(), eq(ALM_SETTING_KEY), eq(REPOSITORY_NAME), eq("generated_orga2/repo1")); - ProjectAlmSettingDto projectAlmSettingDto = projectAlmSettingDtoCaptor.getValue(); - assertAlmSettingsDtoContainsCorrectInformation(almSettingDto, requireNonNull(componentCreationData.projectDto()), projectAlmSettingDto); - } - - @Test - void createProjectAndBindToDevOpsPlatformFromScanner_whenRepoFoundOnGitHubAndVisibilitySynchronizationEnabled_successfullyCreatesProjectAndSetsVisibility() { - // given - mockPublicGithubRepository(); - - ComponentCreationData componentCreationData = mockProjectCreation("generated_orga2/repo1"); - ProjectAlmSettingDao projectAlmSettingDao = mock(); - when(dbClient.projectAlmSettingDao()).thenReturn(projectAlmSettingDao); - when(gitHubSettings.isProvisioningEnabled()).thenReturn(true); - when(gitHubSettings.isProjectVisibilitySynchronizationActivated()).thenReturn(true); - - // when - ComponentCreationData actualComponentCreationData = githubProjectCreator.createProjectAndBindToDevOpsPlatform(dbClient.openSession(true), - SCANNER_API_DEVOPS_AUTO_CONFIG, false, null, null); - - // then - assertThat(actualComponentCreationData).isEqualTo(componentCreationData); - - ComponentCreationParameters componentCreationParameters = componentCreationParametersCaptor.getValue(); - assertThat(componentCreationParameters.newComponent().isPrivate()).isFalse(); - } - - @Test - void createProjectAndBindToDevOpsPlatformFromScanner_whenRepoFoundOnGitHubAndVisibilitySynchronizationDisabled_successfullyCreatesProjectAndMakesProjectPrivate() { - // given - mockGitHubRepository(); - - ComponentCreationData componentCreationData = mockProjectCreation("generated_orga2/repo1"); - ProjectAlmSettingDao projectAlmSettingDao = mock(); - when(dbClient.projectAlmSettingDao()).thenReturn(projectAlmSettingDao); - when(gitHubSettings.isProvisioningEnabled()).thenReturn(true); - when(gitHubSettings.isProjectVisibilitySynchronizationActivated()).thenReturn(false); - - // when - ComponentCreationData actualComponentCreationData = githubProjectCreator.createProjectAndBindToDevOpsPlatform(dbClient.openSession(true), - SCANNER_API_DEVOPS_AUTO_CONFIG, false, null, null); - - // then - assertThat(actualComponentCreationData).isEqualTo(componentCreationData); - - ComponentCreationParameters componentCreationParameters = componentCreationParametersCaptor.getValue(); - assertThat(componentCreationParameters.newComponent().isPrivate()).isTrue(); - } - - @Test - void createProjectAndBindToDevOpsPlatformFromApi_whenRepoFoundOnGitHub_successfullyCreatesProject() { - // given - String projectKey = "customProjectKey"; - mockGitHubRepository(); - - ComponentCreationData componentCreationData = mockProjectCreation(projectKey); - ProjectAlmSettingDao projectAlmSettingDao = mock(); - when(dbClient.projectAlmSettingDao()).thenReturn(projectAlmSettingDao); - when(projectDefaultVisibility.get(any())).thenReturn(Visibility.PRIVATE); - - // when - ComponentCreationData actualComponentCreationData = githubProjectCreator.createProjectAndBindToDevOpsPlatform(dbClient.openSession(true), ALM_IMPORT_API, false, projectKey, - null); - - // then - assertThat(actualComponentCreationData).isEqualTo(componentCreationData); - - ComponentCreationParameters componentCreationParameters = componentCreationParametersCaptor.getValue(); - assertComponentCreationParametersContainsCorrectInformation(componentCreationParameters, projectKey, ALM_IMPORT_API); - assertThat(componentCreationParameters.isManaged()).isFalse(); - assertThat(componentCreationParameters.newComponent().isPrivate()).isTrue(); - - verify(projectAlmSettingDao).insertOrUpdate(any(), projectAlmSettingDtoCaptor.capture(), eq(ALM_SETTING_KEY), eq(REPOSITORY_NAME), eq(projectKey)); - ProjectAlmSettingDto projectAlmSettingDto = projectAlmSettingDtoCaptor.getValue(); - assertAlmSettingsDtoContainsCorrectInformation(almSettingDto, requireNonNull(componentCreationData.projectDto()), projectAlmSettingDto); - } - - @Captor - private ArgumentCaptor<Collection<UserPermissionChange>> permissionChangesCaptor; - - @Test - void createProjectAndBindToDevOpsPlatformFromApi_whenRepoFoundOnGitHubAutoProvisioningOnAndRepoPrivate_successfullyCreatesProject() { - // given - String projectKey = "customProjectKey"; - mockGitHubRepository(); - - ComponentCreationData componentCreationData = mockProjectCreation(projectKey); - ProjectAlmSettingDao projectAlmSettingDao = mock(); - when(dbClient.projectAlmSettingDao()).thenReturn(projectAlmSettingDao); - when(gitHubSettings.isProvisioningEnabled()).thenReturn(true); - - // when - ComponentCreationData actualComponentCreationData = githubProjectCreator.createProjectAndBindToDevOpsPlatform(dbClient.openSession(true), ALM_IMPORT_API, false, projectKey, - null); - - // then - assertThat(actualComponentCreationData).isEqualTo(componentCreationData); - - ComponentCreationParameters componentCreationParameters = componentCreationParametersCaptor.getValue(); - assertComponentCreationParametersContainsCorrectInformation(componentCreationParameters, projectKey, ALM_IMPORT_API); - assertThat(componentCreationParameters.isManaged()).isTrue(); - assertThat(componentCreationParameters.newComponent().isPrivate()).isTrue(); - - verifyScanPermissionWasAddedToUser(actualComponentCreationData); - verifyProjectSyncTaskWasCreated(actualComponentCreationData); - - verify(projectAlmSettingDao).insertOrUpdate(any(), projectAlmSettingDtoCaptor.capture(), eq(ALM_SETTING_KEY), eq(REPOSITORY_NAME), eq(projectKey)); - ProjectAlmSettingDto projectAlmSettingDto = projectAlmSettingDtoCaptor.getValue(); - assertAlmSettingsDtoContainsCorrectInformation(almSettingDto, requireNonNull(componentCreationData.projectDto()), projectAlmSettingDto); - } - - private void verifyProjectSyncTaskWasCreated(ComponentCreationData componentCreationData) { - String projectUuid = requireNonNull(componentCreationData.projectDto()).getUuid(); - String mainBranchUuid = requireNonNull(componentCreationData.mainBranchDto()).getUuid(); - verify(managedProjectService).queuePermissionSyncTask(USER_UUID, mainBranchUuid, projectUuid); - } - - private void verifyScanPermissionWasAddedToUser(ComponentCreationData actualComponentCreationData) { - verify(permissionUpdater).apply(any(), permissionChangesCaptor.capture()); - UserPermissionChange permissionChange = permissionChangesCaptor.getValue().iterator().next(); - assertThat(permissionChange.getUserId().getUuid()).isEqualTo(userSession.getUuid()); - assertThat(permissionChange.getUserId().getLogin()).isEqualTo(userSession.getLogin()); - assertThat(permissionChange.getPermission()).isEqualTo(UserRole.SCAN); - assertThat(permissionChange.getProjectUuid()).isEqualTo(actualComponentCreationData.projectDto().getUuid()); - } - - private void mockPublicGithubRepository() { - GithubApplicationClient.Repository repository = mockGitHubRepository(); - when(repository.isPrivate()).thenReturn(false); - } - - private GithubApplicationClient.Repository mockGitHubRepository() { - GithubApplicationClient.Repository repository = mock(); - when(repository.getDefaultBranch()).thenReturn(MAIN_BRANCH_NAME); - when(repository.getName()).thenReturn(REPOSITORY_NAME); - when(repository.getFullName()).thenReturn(DEVOPS_PROJECT_DESCRIPTOR.repositoryIdentifier()); - lenient().when(repository.isPrivate()).thenReturn(true); - when(githubApplicationClient.getRepository(DEVOPS_PROJECT_DESCRIPTOR.url(), devOpsAppInstallationToken, DEVOPS_PROJECT_DESCRIPTOR.repositoryIdentifier())).thenReturn( - Optional.of(repository)); - when(projectKeyGenerator.generateUniqueProjectKey(repository.getFullName())).thenReturn("generated_" + DEVOPS_PROJECT_DESCRIPTOR.repositoryIdentifier()); - return repository; - } - - private ComponentCreationData mockProjectCreation(String projectKey) { - ComponentCreationData componentCreationData = mock(); - ProjectDto projectDto = mockProjectDto(projectKey); - when(componentCreationData.projectDto()).thenReturn(projectDto); - BranchDto branchDto = mock(); - when(componentCreationData.mainBranchDto()).thenReturn(branchDto); - when(componentUpdater.createWithoutCommit(any(), componentCreationParametersCaptor.capture())).thenReturn(componentCreationData); - return componentCreationData; - } - - private static ProjectDto mockProjectDto(String projectKey) { - ProjectDto projectDto = mock(); - when(projectDto.getName()).thenReturn(REPOSITORY_NAME); - when(projectDto.getKey()).thenReturn(projectKey); - when(projectDto.getUuid()).thenReturn("project-uuid-1"); - return projectDto; - } - - private static void assertComponentCreationParametersContainsCorrectInformation(ComponentCreationParameters componentCreationParameters, String expectedKey, - CreationMethod expectedCreationMethod) { - assertThat(componentCreationParameters.creationMethod()).isEqualTo(expectedCreationMethod); - assertThat(componentCreationParameters.mainBranchName()).isEqualTo(MAIN_BRANCH_NAME); - assertThat(componentCreationParameters.userLogin()).isEqualTo(USER_LOGIN); - assertThat(componentCreationParameters.userUuid()).isEqualTo(USER_UUID); - - NewComponent newComponent = componentCreationParameters.newComponent(); - assertThat(newComponent.isProject()).isTrue(); - assertThat(newComponent.qualifier()).isEqualTo(Qualifiers.PROJECT); - assertThat(newComponent.key()).isEqualTo(expectedKey); - assertThat(newComponent.name()).isEqualTo(REPOSITORY_NAME); - } - - private static void assertAlmSettingsDtoContainsCorrectInformation(AlmSettingDto almSettingDto, ProjectDto projectDto, ProjectAlmSettingDto projectAlmSettingDto) { - assertThat(projectAlmSettingDto.getAlmRepo()).isEqualTo(DEVOPS_PROJECT_DESCRIPTOR.repositoryIdentifier()); - assertThat(projectAlmSettingDto.getAlmSlug()).isNull(); - assertThat(projectAlmSettingDto.getAlmSettingUuid()).isEqualTo(almSettingDto.getUuid()); - assertThat(projectAlmSettingDto.getProjectUuid()).isEqualTo(projectDto.getUuid()); - assertThat(projectAlmSettingDto.getMonorepo()).isFalse(); - assertThat(projectAlmSettingDto.getSummaryCommentEnabled()).isTrue(); - } } diff --git a/server/sonar-webserver-common/src/test/java/org/sonar/server/common/almsettings/gitlab/GitlabDevOpsProjectCreationContextServiceTest.java b/server/sonar-webserver-common/src/test/java/org/sonar/server/common/almsettings/gitlab/GitlabDevOpsProjectCreationContextServiceTest.java new file mode 100644 index 00000000000..da6c57d8874 --- /dev/null +++ b/server/sonar-webserver-common/src/test/java/org/sonar/server/common/almsettings/gitlab/GitlabDevOpsProjectCreationContextServiceTest.java @@ -0,0 +1,167 @@ +/* + * 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.gitlab; + +import java.util.List; +import java.util.Optional; +import java.util.stream.Stream; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; +import org.mockito.Answers; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.sonar.alm.client.gitlab.GitLabBranch; +import org.sonar.alm.client.gitlab.GitlabApplicationClient; +import org.sonar.alm.client.gitlab.Project; +import org.sonar.db.DbClient; +import org.sonar.db.alm.pat.AlmPatDto; +import org.sonar.db.alm.setting.ALM; +import org.sonar.db.alm.setting.AlmSettingDto; +import org.sonar.server.common.almsettings.DevOpsProjectCreationContext; +import org.sonar.server.common.almsettings.DevOpsProjectDescriptor; +import org.sonar.server.user.UserSession; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; +import static org.assertj.core.api.Assertions.assertThatIllegalStateException; +import static org.assertj.core.api.Assertions.assertThatNullPointerException; +import static org.mockito.Mockito.lenient; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +@ExtendWith(MockitoExtension.class) +class GitlabDevOpsProjectCreationContextServiceTest { + + private static final AlmSettingDto ALM_SETTING_DTO = mock(); + private static final long GITLAB_PROJECT_ID = 123L; + + private static final DevOpsProjectDescriptor DEV_OPS_PROJECT_DESCRIPTOR = new DevOpsProjectDescriptor(ALM.GITHUB, "project-key", String.valueOf(GITLAB_PROJECT_ID), null); + private static final String GITLAB_COM = "https://gitlab.com"; + private static final String DEFAULT_BRANCH_NAME = "default-branch"; + + @Mock(answer = Answers.RETURNS_DEEP_STUBS) + private DbClient dbClient; + @Mock + private UserSession userSession; + @Mock + private GitlabApplicationClient gitlabApplicationClient; + + @InjectMocks + private GitlabDevOpsProjectCreationContextService gitlabDevOpsProjectService; + + @BeforeEach + public void setUp() { + when(ALM_SETTING_DTO.getUrl()).thenReturn(GITLAB_COM); + lenient().when(ALM_SETTING_DTO.getKey()).thenReturn("almKey"); + } + + @Test + void create_whenGitlabProjectIdIsInvalid_throws() { + DevOpsProjectDescriptor devOpsProjectDescriptor = mock(); + when(devOpsProjectDescriptor.repositoryIdentifier()).thenReturn("invalid"); + + assertThatIllegalArgumentException() + .isThrownBy(() -> gitlabDevOpsProjectService.create(ALM_SETTING_DTO, devOpsProjectDescriptor)) + .withMessage("GitLab project identifier must be a number, was 'invalid'"); + } + + @Test + void createDevOpsProject_whenUserUuidIsNull_shouldThrow() { + assertThatNullPointerException() + .isThrownBy(() -> gitlabDevOpsProjectService.create(ALM_SETTING_DTO, DEV_OPS_PROJECT_DESCRIPTOR)) + .withMessage("User UUID cannot be null."); + } + + @Test + void createDevOpsProject_whenNoPat_shouldThrow() { + when(userSession.getUuid()).thenReturn("user-uuid"); + when(dbClient.almPatDao().selectByUserAndAlmSetting(dbClient.openSession(false), userSession.getUuid(), ALM_SETTING_DTO)).thenReturn(Optional.empty()); + + assertThatIllegalArgumentException() + .isThrownBy(() -> gitlabDevOpsProjectService.create(ALM_SETTING_DTO, DEV_OPS_PROJECT_DESCRIPTOR)) + .withMessage("Personal access token for 'almKey' is missing"); + } + + @Test + void create_whenProjectNotFoundOnGitlab_throws() { + AlmPatDto almPatDto = mockPatExistence(); + + when(gitlabApplicationClient.getProject(GITLAB_COM, almPatDto.getPersonalAccessToken(), GITLAB_PROJECT_ID)).thenThrow(new IllegalStateException("error")); + + assertThatIllegalStateException() + .isThrownBy(() -> gitlabDevOpsProjectService.create(ALM_SETTING_DTO, DEV_OPS_PROJECT_DESCRIPTOR)) + .withMessage("error"); + } + + private static Stream<Arguments> visibilitiesAndExpectedResults() { + return Stream.of( + Arguments.of("public", true), + Arguments.of("private", false), + Arguments.of("internal", false), + Arguments.of("other", false) + ); + } + + @ParameterizedTest + @MethodSource("visibilitiesAndExpectedResults") + void create_whenProjectFoundOnGitLab_createCorrectDevOpsProject(String gitlabVisibility, boolean isPublic) { + AlmPatDto almPatDto = mockPatExistence(); + + Project project = mockGitlabProjectAndBranches(gitlabVisibility, almPatDto); + + DevOpsProjectCreationContext devOpsProjectCreationContext = gitlabDevOpsProjectService.create(ALM_SETTING_DTO, DEV_OPS_PROJECT_DESCRIPTOR); + assertThat(devOpsProjectCreationContext.name()).isEqualTo(project.getName()); + assertThat(devOpsProjectCreationContext.fullName()).isEqualTo(project.getPathWithNamespace()); + assertThat(devOpsProjectCreationContext.devOpsPlatformIdentifier()).isEqualTo(String.valueOf(project.getId())); + assertThat(devOpsProjectCreationContext.isPublic()).isEqualTo(isPublic); + assertThat(devOpsProjectCreationContext.defaultBranchName()).isEqualTo(DEFAULT_BRANCH_NAME); + } + + private AlmPatDto mockPatExistence() { + when(userSession.getUuid()).thenReturn("user-uuid"); + + AlmPatDto almPatDto = mock(AlmPatDto.class); + when(almPatDto.getPersonalAccessToken()).thenReturn("token"); + when(dbClient.almPatDao().selectByUserAndAlmSetting(dbClient.openSession(false), userSession.getUuid(), ALM_SETTING_DTO)).thenReturn(Optional.of(almPatDto)); + return almPatDto; + } + + private Project mockGitlabProjectAndBranches(String gitlabVisibility, AlmPatDto almPatDto) { + Project project = mock(Project.class); + when(project.getId()).thenReturn(GITLAB_PROJECT_ID); + when(project.getName()).thenReturn("project-name"); + when(project.getPathWithNamespace()).thenReturn("project-path"); + when(project.getVisibility()).thenReturn(gitlabVisibility); + when(gitlabApplicationClient.getProject(GITLAB_COM, almPatDto.getPersonalAccessToken(), GITLAB_PROJECT_ID)).thenReturn(project); + + GitLabBranch gitLabBranch = mock(); + GitLabBranch defaultGitlabBranch = mock(); + when(defaultGitlabBranch.getName()).thenReturn(DEFAULT_BRANCH_NAME); + when(defaultGitlabBranch.isDefault()).thenReturn(true); + when(gitlabApplicationClient.getBranches(GITLAB_COM, almPatDto.getPersonalAccessToken(), GITLAB_PROJECT_ID)).thenReturn(List.of(gitLabBranch, defaultGitlabBranch)); + return project; + } + +} diff --git a/server/sonar-webserver-common/src/test/java/org/sonar/server/common/almsettings/gitlab/GitlabProjectCreatorFactoryTest.java b/server/sonar-webserver-common/src/test/java/org/sonar/server/common/almsettings/gitlab/GitlabProjectCreatorFactoryTest.java index 2abdd276753..ed040708218 100644 --- a/server/sonar-webserver-common/src/test/java/org/sonar/server/common/almsettings/gitlab/GitlabProjectCreatorFactoryTest.java +++ b/server/sonar-webserver-common/src/test/java/org/sonar/server/common/almsettings/gitlab/GitlabProjectCreatorFactoryTest.java @@ -20,24 +20,32 @@ package org.sonar.server.common.almsettings.gitlab; import java.util.Map; +import java.util.Optional; import org.assertj.core.api.AssertionsForClassTypes; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.InjectMocks; +import org.mockito.Mock; import org.mockito.Mockito; import org.mockito.junit.jupiter.MockitoExtension; import org.sonar.db.DbSession; import org.sonar.db.alm.setting.ALM; import org.sonar.db.alm.setting.AlmSettingDto; +import org.sonar.server.common.almsettings.DevOpsProjectCreator; import org.sonar.server.common.almsettings.DevOpsProjectDescriptor; import static org.assertj.core.api.AssertionsForClassTypes.assertThat; +import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @ExtendWith(MockitoExtension.class) class GitlabProjectCreatorFactoryTest { + @Mock + private GitlabDevOpsProjectCreationContextService gitlabDevOpsProjectService; + @InjectMocks private GitlabProjectCreatorFactory underTest; @@ -50,17 +58,28 @@ class GitlabProjectCreatorFactoryTest { @Test void getDevOpsProjectCreator_whenDevOpsPlatformIsNotGitlab_returnsEmpty() { - AlmSettingDto almSetting = mock(); - when(almSetting.getAlm()).thenReturn(ALM.AZURE_DEVOPS); + AlmSettingDto almSetting = mockAlm(ALM.GITHUB); AssertionsForClassTypes.assertThat(underTest.getDevOpsProjectCreator(almSetting, Mockito.mock(DevOpsProjectDescriptor.class))).isEmpty(); } @Test - void getDevOpsProjectCreator_whenDevOpsPlatformIsNotGitlab_returnsProjectCreator() { - AlmSettingDto almSetting = mock(); - when(almSetting.getAlm()).thenReturn(ALM.GITLAB); - assertThat(underTest.getDevOpsProjectCreator(almSetting, mock(DevOpsProjectDescriptor.class))).isNotEmpty(); + void getDevOpsProjectCreator_whenDevOpsPlatformIsGitlab_returnsProjectCreator() { + when(gitlabDevOpsProjectService.create(any(), any())).thenReturn(mock()); + + AlmSettingDto almSetting = mockAlm(ALM.GITLAB); + DevOpsProjectDescriptor devOpsProjectDescriptor = mock(); + + Optional<DevOpsProjectCreator> devOpsProjectCreator = underTest.getDevOpsProjectCreator(almSetting, devOpsProjectDescriptor); + + assertThat(devOpsProjectCreator).isNotEmpty(); + verify(gitlabDevOpsProjectService).create(almSetting, devOpsProjectDescriptor); + } + + private static AlmSettingDto mockAlm(ALM alm) { + AlmSettingDto almSettingDto = mock(AlmSettingDto.class); + when(almSettingDto.getAlm()).thenReturn(alm); + return almSettingDto; } } diff --git a/server/sonar-webserver-common/src/test/java/org/sonar/server/common/almsettings/gitlab/GitlabProjectCreatorTest.java b/server/sonar-webserver-common/src/test/java/org/sonar/server/common/almsettings/gitlab/GitlabProjectCreatorTest.java deleted file mode 100644 index 1e67da2ff7c..00000000000 --- a/server/sonar-webserver-common/src/test/java/org/sonar/server/common/almsettings/gitlab/GitlabProjectCreatorTest.java +++ /dev/null @@ -1,209 +0,0 @@ -/* - * 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.gitlab; - -import java.util.List; -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.gitlab.GitLabBranch; -import org.sonar.alm.client.gitlab.GitlabApplicationClient; -import org.sonar.alm.client.gitlab.GitlabServerException; -import org.sonar.alm.client.gitlab.Project; -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 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; - - @Mock - private ProjectKeyGenerator projectKeyGenerator; - - @Mock - private ProjectCreator projectCreator; - - @Mock - private AlmSettingDto almSettingDto; - @Mock - private DevOpsProjectDescriptor devOpsProjectDescriptor; - @Mock - private GitlabApplicationClient gitlabApplicationClient; - @Mock - private UserSession userSession; - - @InjectMocks - private GitlabProjectCreator underTest; - - @BeforeEach - void setup() { - lenient().when(userSession.getLogin()).thenReturn(USER_LOGIN); - lenient().when(userSession.getUuid()).thenReturn(USER_UUID); - - lenient().when(almSettingDto.getUrl()).thenReturn(GITLAB_URL); - lenient().when(almSettingDto.getKey()).thenReturn(ALM_SETTING_KEY); - lenient().when(almSettingDto.getUuid()).thenReturn(ALM_SETTING_UUID); - - lenient().when(devOpsProjectDescriptor.repositoryIdentifier()).thenReturn(REPOSITORY_ID); - lenient().when(devOpsProjectDescriptor.url()).thenReturn(GITLAB_URL); - lenient().when(devOpsProjectDescriptor.alm()).thenReturn(ALM.GITLAB); - } - - @Test - void isScanAllowedUsingPermissionsFromDevopsPlatform_shouldThrowUnsupportedOperationException() { - assertThatExceptionOfType(UnsupportedOperationException.class) - .isThrownBy(() -> underTest.isScanAllowedUsingPermissionsFromDevopsPlatform()) - .withMessage("Not Implemented"); - } - - @Test - void createProjectAndBindToDevOpsPlatform_whenUserHasNoPat_throws() { - assertThatExceptionOfType(IllegalArgumentException.class) - .isThrownBy(() -> underTest.createProjectAndBindToDevOpsPlatform(mock(DbSession.class), CreationMethod.ALM_IMPORT_API, false, null, null)) - .withMessage("personal access token for 'gitlab_config_1' is missing"); - } - - @Test - void createProjectAndBindToDevOpsPlatform_whenRepoNotFound_throws() { - mockPatForUser(); - when(gitlabApplicationClient.getProject(GITLAB_URL, USER_PAT, Long.valueOf(REPOSITORY_ID))).thenThrow(new GitlabServerException(404, "Not found")); - assertThatExceptionOfType(IllegalStateException.class) - .isThrownBy(() -> underTest.createProjectAndBindToDevOpsPlatform(mock(DbSession.class), CreationMethod.ALM_IMPORT_API, false, null, null)) - .withMessage("Failed to fetch GitLab project with ID '1234' from 'http://api.com'"); - - } - - @Test - void createProjectAndBindToDevOpsPlatform_whenRepoFoundOnGitlab_successfullyCreatesProject() { - mockPatForUser(); - mockGitlabProject(); - mockMainBranch(); - 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_ID); - assertThat(createdProjectAlmSettingDto.getProjectUuid()).isEqualTo(PROJECT_UUID); - assertThat(createdProjectAlmSettingDto.getMonorepo()).isTrue(); - } - - @Test - void createProjectAndBindToDevOpsPlatform_whenNoKeyAndNameSpecified_generatesKeyAndUsesGitlabProjectName() { - mockPatForUser(); - mockGitlabProject(); - mockMainBranch(); - - String generatedProjectKey = "generatedProjectKey"; - when(projectKeyGenerator.generateUniqueProjectKey(REPOSITORY_PATH_WITH_NAMESPACE)).thenReturn(generatedProjectKey); - - mockProjectCreation(generatedProjectKey, GITLAB_PROJECT_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(GITLAB_PROJECT_NAME), eq(generatedProjectKey)); - - ProjectAlmSettingDto createdProjectAlmSettingDto = projectAlmSettingCaptor.getValue(); - - assertThat(createdProjectAlmSettingDto.getAlmSettingUuid()).isEqualTo(ALM_SETTING_UUID); - assertThat(createdProjectAlmSettingDto.getAlmRepo()).isEqualTo(REPOSITORY_ID); - 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 mockGitlabProject() { - Project project = mock(Project.class); - lenient().when(project.getPathWithNamespace()).thenReturn(REPOSITORY_PATH_WITH_NAMESPACE); - when(project.getName()).thenReturn(GITLAB_PROJECT_NAME); - when(gitlabApplicationClient.getProject(GITLAB_URL, USER_PAT, Long.valueOf(REPOSITORY_ID))).thenReturn(project); - - } - - private void mockMainBranch() { - when(gitlabApplicationClient.getBranches(GITLAB_URL, USER_PAT, Long.valueOf(REPOSITORY_ID))) - .thenReturn(List.of(new GitLabBranch("notMain", false), new GitLabBranch(MAIN_BRANCH_NAME, true))); - } - - 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); - } - -} |