diff options
author | Aurelien Poscia <aurelien.poscia@sonarsource.com> | 2023-08-07 17:12:45 +0200 |
---|---|---|
committer | sonartech <sonartech@sonarsource.com> | 2023-08-09 20:03:37 +0000 |
commit | 8a7995fb4e79c7fd5b0a22401fcaabb2db6c5a06 (patch) | |
tree | 3d11edcbe1c288a84c8478921bdd2929fe43f787 /server | |
parent | 2f2b64a42548a18b6fbbbb6549f98555424f5e5c (diff) | |
download | sonarqube-8a7995fb4e79c7fd5b0a22401fcaabb2db6c5a06.tar.gz sonarqube-8a7995fb4e79c7fd5b0a22401fcaabb2db6c5a06.zip |
SONAR-19789 schedule permissions sync task upon project onboarding
Diffstat (limited to 'server')
8 files changed, 173 insertions, 16 deletions
diff --git a/server/sonar-db-dao/src/main/java/org/sonar/db/ce/CeTaskTypes.java b/server/sonar-db-dao/src/main/java/org/sonar/db/ce/CeTaskTypes.java index 42c7f85ee96..356139f405b 100644 --- a/server/sonar-db-dao/src/main/java/org/sonar/db/ce/CeTaskTypes.java +++ b/server/sonar-db-dao/src/main/java/org/sonar/db/ce/CeTaskTypes.java @@ -26,6 +26,8 @@ public final class CeTaskTypes { public static final String REPORT = "REPORT"; public static final String PROJECT_EXPORT = "PROJECT_EXPORT"; + public static final String GITHUB_PROJECT_PERMISSIONS_PROVISIONING = "GITHUB_PROJECT_PERMISSIONS_PROVISIONING"; + private CeTaskTypes() { // only statics } diff --git a/server/sonar-server-common/src/main/java/org/sonar/server/management/DelegatingManagedServices.java b/server/sonar-server-common/src/main/java/org/sonar/server/management/DelegatingManagedServices.java index 2a76ad5779c..26d0f049341 100644 --- a/server/sonar-server-common/src/main/java/org/sonar/server/management/DelegatingManagedServices.java +++ b/server/sonar-server-common/src/main/java/org/sonar/server/management/DelegatingManagedServices.java @@ -124,6 +124,12 @@ public class DelegatingManagedServices implements ManagedInstanceService, Manage .orElse(false); } + @Override + public void queuePermissionSyncTask(String submitterUuid, String componentUuid, String projectUuid) { + findManagedProjectService() + .ifPresent(managedProjectService -> managedProjectService.queuePermissionSyncTask(submitterUuid, componentUuid, projectUuid)); + } + private Optional<ManagedProjectService> findManagedProjectService() { return findManagedInstanceService() .filter(ManagedProjectService.class::isInstance) diff --git a/server/sonar-server-common/src/main/java/org/sonar/server/management/ManagedProjectService.java b/server/sonar-server-common/src/main/java/org/sonar/server/management/ManagedProjectService.java index 4679195554e..9b4c3b88b80 100644 --- a/server/sonar-server-common/src/main/java/org/sonar/server/management/ManagedProjectService.java +++ b/server/sonar-server-common/src/main/java/org/sonar/server/management/ManagedProjectService.java @@ -29,4 +29,5 @@ public interface ManagedProjectService { boolean isProjectManaged(DbSession dbSession, String projectUuid); + void queuePermissionSyncTask(String submitterUuid, String componentUuid, String projectUuid); } diff --git a/server/sonar-server-common/src/main/java/org/sonar/server/project/VisibilityService.java b/server/sonar-server-common/src/main/java/org/sonar/server/project/VisibilityService.java index 8768b1d5625..2e4eccf5f87 100644 --- a/server/sonar-server-common/src/main/java/org/sonar/server/project/VisibilityService.java +++ b/server/sonar-server-common/src/main/java/org/sonar/server/project/VisibilityService.java @@ -19,6 +19,7 @@ */ package org.sonar.server.project; +import com.google.common.annotations.VisibleForTesting; import java.util.Optional; import org.sonar.api.ce.ComputeEngineSide; import org.sonar.api.server.ServerSide; @@ -38,6 +39,7 @@ import static java.util.Collections.singletonList; import static java.util.Optional.ofNullable; import static org.sonar.api.utils.Preconditions.checkState; import static org.sonar.api.web.UserRole.PUBLIC_PERMISSIONS; +import static org.sonar.db.ce.CeTaskTypes.GITHUB_PROJECT_PERMISSIONS_PROVISIONING; @ServerSide @ComputeEngineSide @@ -66,14 +68,19 @@ public class VisibilityService { } } } - private void checkNoPendingTasks(DbSession dbSession, EntityDto entityDto) { - //This check likely can be removed when we remove the column 'private' from components table - checkState(noPendingTask(dbSession, entityDto.getKey()), "Component visibility can't be changed as long as it has background task(s) pending or in progress"); + + @VisibleForTesting + void checkNoPendingTasks(DbSession dbSession, EntityDto entityDto) { + //This check likely can be removed when we remove the column 'private' from components table in SONAR-20126. + checkState(countPendingTask(dbSession, entityDto.getKey()) == 0, "Component visibility can't be changed as long as it has background task(s) pending or in progress"); } - private boolean noPendingTask(DbSession dbSession, String entityKey) { + private long countPendingTask(DbSession dbSession, String entityKey) { EntityDto entityDto = dbClient.entityDao().selectByKey(dbSession, entityKey).orElseThrow(() -> new IllegalStateException("Can't find entity " + entityKey)); - return dbClient.ceQueueDao().selectByEntityUuid(dbSession, entityDto.getUuid()).isEmpty(); + return dbClient.ceQueueDao().selectByEntityUuid(dbSession, entityDto.getUuid()) + .stream() + .filter(task -> !task.getTaskType().equals(GITHUB_PROJECT_PERMISSIONS_PROVISIONING)) + .count(); } private void setPrivateForRootComponentUuid(DbSession dbSession, EntityDto entity, boolean newIsPrivate) { diff --git a/server/sonar-server-common/src/test/java/org/sonar/server/management/DelegatingManagedServicesTest.java b/server/sonar-server-common/src/test/java/org/sonar/server/management/DelegatingManagedServicesTest.java index 44ee3c5c674..92e29cb833c 100644 --- a/server/sonar-server-common/src/test/java/org/sonar/server/management/DelegatingManagedServicesTest.java +++ b/server/sonar-server-common/src/test/java/org/sonar/server/management/DelegatingManagedServicesTest.java @@ -30,9 +30,14 @@ import org.sonar.db.DbSession; import static java.util.Collections.emptySet; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatIllegalStateException; +import static org.assertj.core.api.Assertions.assertThatNoException; import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import static org.mockito.Mockito.withSettings; @@ -46,9 +51,7 @@ public class DelegatingManagedServicesTest { @Test public void getProviderName_whenNotManaged_shouldThrow() { - DelegatingManagedServices managedInstanceService = NO_MANAGED_SERVICES; - - assertThatThrownBy(managedInstanceService::getProviderName) + assertThatThrownBy(NO_MANAGED_SERVICES::getProviderName) .isInstanceOf(IllegalStateException.class) .hasMessage("This instance is not managed."); } @@ -62,8 +65,7 @@ public class DelegatingManagedServicesTest { @Test public void isInstanceExternallyManaged_whenNoManagedInstanceService_returnsFalse() { - DelegatingManagedServices managedInstanceService = NO_MANAGED_SERVICES; - assertThat(managedInstanceService.isInstanceExternallyManaged()).isFalse(); + assertThat(NO_MANAGED_SERVICES.isInstanceExternallyManaged()).isFalse(); } @Test @@ -85,9 +87,8 @@ public class DelegatingManagedServicesTest { @Test public void getUserUuidToManaged_whenNoDelegates_setAllUsersAsNonManaged() { Set<String> userUuids = Set.of("a", "b"); - DelegatingManagedServices managedInstanceService = NO_MANAGED_SERVICES; - Map<String, Boolean> userUuidToManaged = managedInstanceService.getUserUuidToManaged(dbSession, userUuids); + Map<String, Boolean> userUuidToManaged = NO_MANAGED_SERVICES.getUserUuidToManaged(dbSession, userUuids); assertThat(userUuidToManaged).containsExactlyInAnyOrderEntriesOf(Map.of("a", false, "b", false)); } @@ -257,9 +258,24 @@ public class DelegatingManagedServicesTest { @Test public void isProjectManaged_whenManagedNoInstanceServices_returnsFalse() { - DelegatingManagedServices managedInstanceService = NO_MANAGED_SERVICES; + assertThat(NO_MANAGED_SERVICES.isProjectManaged(dbSession, "whatever")).isFalse(); + } + + @Test + public void queuePermissionSyncTask_whenManagedNoInstanceServices_doesNotFail() { + assertThatNoException().isThrownBy(() -> NO_MANAGED_SERVICES.queuePermissionSyncTask("userUuid", "componentUuid", "projectUuid")); + } - assertThat(managedInstanceService.isProjectManaged(dbSession, "whatever")).isFalse(); + @Test + public void queuePermissionSyncTask_whenManagedInstanceServices_shouldDelegatesToRightService() { + NeverManagedInstanceService neverManagedInstanceService = spy(new NeverManagedInstanceService()); + AlwaysManagedInstanceService alwaysManagedInstanceService = spy(new AlwaysManagedInstanceService()); + Set<ManagedInstanceService> delegates = Set.of(neverManagedInstanceService, alwaysManagedInstanceService); + DelegatingManagedServices managedInstanceService = new DelegatingManagedServices(delegates); + + managedInstanceService.queuePermissionSyncTask("userUuid", "componentUuid", "projectUuid"); + verify(neverManagedInstanceService, never()).queuePermissionSyncTask(anyString(), anyString(), anyString()); + verify(alwaysManagedInstanceService).queuePermissionSyncTask("userUuid", "componentUuid", "projectUuid"); } private static class NeverManagedInstanceService implements ManagedInstanceService, ManagedProjectService { @@ -313,6 +329,11 @@ public class DelegatingManagedServicesTest { public boolean isProjectManaged(DbSession dbSession, String projectUuid) { return false; } + + @Override + public void queuePermissionSyncTask(String submitterUuid, String componentUuid, String projectUuid) { + + } } private static class AlwaysManagedInstanceService implements ManagedInstanceService, ManagedProjectService { @@ -366,6 +387,11 @@ public class DelegatingManagedServicesTest { public boolean isProjectManaged(DbSession dbSession, String projectUuid) { return true; } + + @Override + public void queuePermissionSyncTask(String submitterUuid, String componentUuid, String projectUuid) { + + } } } diff --git a/server/sonar-server-common/src/test/java/org/sonar/server/project/VisibilityServiceTest.java b/server/sonar-server-common/src/test/java/org/sonar/server/project/VisibilityServiceTest.java new file mode 100644 index 00000000000..d31a8658038 --- /dev/null +++ b/server/sonar-server-common/src/test/java/org/sonar/server/project/VisibilityServiceTest.java @@ -0,0 +1,104 @@ +/* + * SonarQube + * Copyright (C) 2009-2023 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.project; + +import java.util.List; +import java.util.Optional; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Answers; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.MockitoJUnitRunner; +import org.sonar.db.DbClient; +import org.sonar.db.DbSession; +import org.sonar.db.ce.CeQueueDto; +import org.sonar.db.entity.EntityDto; + +import static org.assertj.core.api.Assertions.assertThatIllegalStateException; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; +import static org.sonar.db.ce.CeTaskTypes.GITHUB_PROJECT_PERMISSIONS_PROVISIONING; + +@RunWith(MockitoJUnitRunner.class) +public class VisibilityServiceTest { + + @Mock(answer = Answers.RETURNS_DEEP_STUBS) + private DbClient dbClient; + + @Mock + private DbSession dbSession; + + @InjectMocks + private VisibilityService visibilityService; + + @Test + public void checkNoPendingTasks_whenEntityNotFound_throwsIae() { + EntityDto entityDto = mockEntityDto(); + when(dbClient.entityDao().selectByKey(dbSession, entityDto.getKey())).thenReturn(Optional.empty()); + + assertThatIllegalStateException() + .isThrownBy(() -> visibilityService.checkNoPendingTasks(dbSession, entityDto)) + .withMessage("Can't find entity entityKey"); + } + + @Test + public void checkNoPendingTasks_whenEntityFoundAndNoTaskInQueue_doesNotThrow() { + EntityDto entityDto = mockEntityDto(); + when(dbClient.entityDao().selectByKey(dbSession, entityDto.getKey())).thenReturn(Optional.of(entityDto)); + + visibilityService.checkNoPendingTasks(dbSession, entityDto); + } + + @Test + public void checkNoPendingTasks_whenOneGithubSyncTaskInQueue_doesNotThrow() { + EntityDto entityDto = mockEntityDto(); + when(dbClient.entityDao().selectByKey(dbSession, entityDto.getKey())).thenReturn(Optional.of(entityDto)); + + mockCeQueueDto(GITHUB_PROJECT_PERMISSIONS_PROVISIONING, entityDto.getKey()); + + visibilityService.checkNoPendingTasks(dbSession, entityDto); + } + + @Test + public void checkNoPendingTasks_whenAnyOtherTaskInQueue_throws() { + EntityDto entityDto = mockEntityDto(); + when(dbClient.entityDao().selectByKey(dbSession, entityDto.getKey())).thenReturn(Optional.of(entityDto)); + + mockCeQueueDto("ANYTHING", entityDto.getUuid()); + + assertThatIllegalStateException() + .isThrownBy(() -> visibilityService.checkNoPendingTasks(dbSession, entityDto)) + .withMessage("Component visibility can't be changed as long as it has background task(s) pending or in progress"); + } + + private void mockCeQueueDto(String taskType, String entityDto) { + CeQueueDto ceQueueDto = mock(CeQueueDto.class); + when(ceQueueDto.getTaskType()).thenReturn(taskType); + when(dbClient.ceQueueDao().selectByEntityUuid(dbSession, entityDto)).thenReturn(List.of(ceQueueDto)); + } + + private static EntityDto mockEntityDto() { + EntityDto entityDto = mock(EntityDto.class); + when(entityDto.getKey()).thenReturn("entityKey"); + when(entityDto.getUuid()).thenReturn("entityUuid"); + return entityDto; + } +} diff --git a/server/sonar-webserver-webapi/src/it/java/org/sonar/server/almintegration/ws/github/ImportGithubProjectActionIT.java b/server/sonar-webserver-webapi/src/it/java/org/sonar/server/almintegration/ws/github/ImportGithubProjectActionIT.java index 140d53a9c91..5552328191c 100644 --- a/server/sonar-webserver-webapi/src/it/java/org/sonar/server/almintegration/ws/github/ImportGithubProjectActionIT.java +++ b/server/sonar-webserver-webapi/src/it/java/org/sonar/server/almintegration/ws/github/ImportGithubProjectActionIT.java @@ -54,6 +54,7 @@ import org.sonar.server.es.TestIndexers; import org.sonar.server.exceptions.NotFoundException; import org.sonar.server.exceptions.UnauthorizedException; import org.sonar.server.favorite.FavoriteUpdater; +import org.sonar.server.management.ManagedProjectService; import org.sonar.server.newcodeperiod.NewCodeDefinitionResolver; import org.sonar.server.permission.GroupPermissionChanger; import org.sonar.server.permission.PermissionService; @@ -123,7 +124,8 @@ public class ImportGithubProjectActionIT { private final GitHubSettings gitHubSettings = mock(GitHubSettings.class); private NewCodeDefinitionResolver newCodeDefinitionResolver = new NewCodeDefinitionResolver(db.getDbClient(), editionProvider); - private final WsActionTester ws = new WsActionTester(new ImportGithubProjectAction(db.getDbClient(), userSession, + private final ManagedProjectService managedProjectService = mock(ManagedProjectService.class); + private final WsActionTester ws = new WsActionTester(new ImportGithubProjectAction(db.getDbClient(), managedProjectService, userSession, projectDefaultVisibility, appClient, componentUpdater, importHelper, projectKeyGenerator, newCodeDefinitionResolver, defaultBranchNameResolver, gitHubSettings)); @@ -161,6 +163,7 @@ public class ImportGithubProjectActionIT { assertThat(mainBranch).isPresent(); assertThat(mainBranch.get().getKey()).isEqualTo("default-branch"); + verify(managedProjectService).queuePermissionSyncTask(userSession.getUuid(), mainBranch.get().getUuid() , projectDto.get().getUuid()); } @Test diff --git a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/almintegration/ws/github/ImportGithubProjectAction.java b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/almintegration/ws/github/ImportGithubProjectAction.java index f59b4cf84c6..41e77913588 100644 --- a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/almintegration/ws/github/ImportGithubProjectAction.java +++ b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/almintegration/ws/github/ImportGithubProjectAction.java @@ -19,6 +19,7 @@ */ package org.sonar.server.almintegration.ws.github; +import java.util.Objects; import java.util.Optional; import javax.inject.Inject; import org.sonar.alm.client.github.GithubApplicationClient; @@ -44,6 +45,7 @@ import org.sonar.server.component.ComponentCreationData; import org.sonar.server.component.ComponentUpdater; import org.sonar.server.component.NewComponent; import org.sonar.server.exceptions.NotFoundException; +import org.sonar.server.management.ManagedProjectService; import org.sonar.server.newcodeperiod.NewCodeDefinitionResolver; import org.sonar.server.project.DefaultBranchNameResolver; import org.sonar.server.project.ProjectDefaultVisibility; @@ -68,6 +70,8 @@ public class ImportGithubProjectAction implements AlmIntegrationsWsAction { public static final String PARAM_REPOSITORY_KEY = "repositoryKey"; private final DbClient dbClient; + + private final ManagedProjectService managedProjectService; private final UserSession userSession; private final ProjectDefaultVisibility projectDefaultVisibility; private final GithubApplicationClient githubApplicationClient; @@ -82,11 +86,12 @@ public class ImportGithubProjectAction implements AlmIntegrationsWsAction { private final GitHubSettings gitHubSettings; @Inject - public ImportGithubProjectAction(DbClient dbClient, UserSession userSession, ProjectDefaultVisibility projectDefaultVisibility, + public ImportGithubProjectAction(DbClient dbClient, ManagedProjectService managedProjectService, UserSession userSession, ProjectDefaultVisibility projectDefaultVisibility, GithubApplicationClientImpl githubApplicationClient, ComponentUpdater componentUpdater, ImportHelper importHelper, ProjectKeyGenerator projectKeyGenerator, NewCodeDefinitionResolver newCodeDefinitionResolver, DefaultBranchNameResolver defaultBranchNameResolver, GitHubSettings gitHubSettings) { this.dbClient = dbClient; + this.managedProjectService = managedProjectService; this.userSession = userSession; this.projectDefaultVisibility = projectDefaultVisibility; this.githubApplicationClient = githubApplicationClient; @@ -172,6 +177,9 @@ public class ImportGithubProjectAction implements AlmIntegrationsWsAction { componentUpdater.commitAndIndex(dbSession, componentCreationData); + String userUuid = Objects.requireNonNull(userSession.getUuid()); + managedProjectService.queuePermissionSyncTask(userUuid, mainBranchDto.getUuid(), projectDto.getUuid()); + return toCreateResponse(projectDto); } } |