From 7f502f7890873c211b33a148d0a39961b5af1aef Mon Sep 17 00:00:00 2001 From: Nolwenn Cadic <98824442+Nolwenn-cadic-sonarsource@users.noreply.github.com> Date: Mon, 12 Jun 2023 13:53:18 +0200 Subject: [PATCH] SONAR-19457 Update the api/alm_integrations/import_bitbucketcloud_repo API(#8517) --- .../ImportBitbucketCloudRepoActionIT.java | 190 +++++++++++++++++- .../ImportBitbucketCloudRepoAction.java | 49 ++++- 2 files changed, 231 insertions(+), 8 deletions(-) diff --git a/server/sonar-webserver-webapi/src/it/java/org/sonar/server/almintegration/ws/bitbucketcloud/ImportBitbucketCloudRepoActionIT.java b/server/sonar-webserver-webapi/src/it/java/org/sonar/server/almintegration/ws/bitbucketcloud/ImportBitbucketCloudRepoActionIT.java index e4d4f1246e3..a25e53acd6d 100644 --- a/server/sonar-webserver-webapi/src/it/java/org/sonar/server/almintegration/ws/bitbucketcloud/ImportBitbucketCloudRepoActionIT.java +++ b/server/sonar-webserver-webapi/src/it/java/org/sonar/server/almintegration/ws/bitbucketcloud/ImportBitbucketCloudRepoActionIT.java @@ -29,12 +29,15 @@ import org.sonar.alm.client.bitbucket.bitbucketcloud.Project; import org.sonar.alm.client.bitbucket.bitbucketcloud.Repository; import org.sonar.api.server.ws.WebService; import org.sonar.api.utils.System2; +import org.sonar.core.platform.EditionProvider; +import org.sonar.core.platform.PlatformEditionProvider; import org.sonar.core.util.SequenceUuidFactory; import org.sonar.db.DbTester; 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.component.BranchDto; +import org.sonar.db.newcodeperiod.NewCodePeriodDto; import org.sonar.db.project.ProjectDto; import org.sonar.db.user.UserDto; import org.sonar.server.almintegration.ws.ImportHelper; @@ -47,6 +50,7 @@ import org.sonar.server.exceptions.NotFoundException; import org.sonar.server.exceptions.UnauthorizedException; import org.sonar.server.favorite.FavoriteUpdater; import org.sonar.server.l18n.I18nRule; +import org.sonar.server.newcodeperiod.NewCodeDefinitionResolver; import org.sonar.server.permission.PermissionTemplateService; import org.sonar.server.project.DefaultBranchNameResolver; import org.sonar.server.project.ProjectDefaultVisibility; @@ -65,8 +69,12 @@ import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import static org.sonar.db.alm.integration.pat.AlmPatsTesting.newAlmPatDto; +import static org.sonar.db.newcodeperiod.NewCodePeriodType.NUMBER_OF_DAYS; +import static org.sonar.db.newcodeperiod.NewCodePeriodType.REFERENCE_BRANCH; import static org.sonar.db.permission.GlobalPermission.PROVISION_PROJECTS; import static org.sonar.db.permission.GlobalPermission.SCAN; +import static org.sonarqube.ws.client.project.ProjectsWsParameters.PARAM_NEW_CODE_DEFINITION_TYPE; +import static org.sonarqube.ws.client.project.ProjectsWsParameters.PARAM_NEW_CODE_DEFINITION_VALUE; public class ImportBitbucketCloudRepoActionIT { @@ -82,14 +90,17 @@ public class ImportBitbucketCloudRepoActionIT { private final ProjectDefaultVisibility projectDefaultVisibility = mock(ProjectDefaultVisibility.class); private final BitbucketCloudRestClient bitbucketCloudRestClient = mock(BitbucketCloudRestClient.class); + DefaultBranchNameResolver defaultBranchNameResolver = mock(DefaultBranchNameResolver.class); private final ComponentUpdater componentUpdater = new ComponentUpdater(db.getDbClient(), i18n, System2.INSTANCE, mock(PermissionTemplateService.class), new FavoriteUpdater(db.getDbClient()), new TestProjectIndexers(), new SequenceUuidFactory(), - mock(DefaultBranchNameResolver.class), true); + defaultBranchNameResolver, true); private final ImportHelper importHelper = new ImportHelper(db.getDbClient(), userSession); private final ProjectKeyGenerator projectKeyGenerator = mock(ProjectKeyGenerator.class); + private PlatformEditionProvider editionProvider = mock(PlatformEditionProvider.class); + private NewCodeDefinitionResolver newCodeDefinitionResolver = new NewCodeDefinitionResolver(db.getDbClient(), editionProvider); private final WsActionTester ws = new WsActionTester(new ImportBitbucketCloudRepoAction(db.getDbClient(), userSession, - bitbucketCloudRestClient, projectDefaultVisibility, componentUpdater, importHelper, projectKeyGenerator)); + bitbucketCloudRestClient, projectDefaultVisibility, componentUpdater, importHelper, projectKeyGenerator, newCodeDefinitionResolver, defaultBranchNameResolver)); @Before public void before() { @@ -128,6 +139,171 @@ public class ImportBitbucketCloudRepoActionIT { assertThat(branchDto).isPresent(); assertThat(branchDto.get().isMain()).isTrue(); verify(projectKeyGenerator).generateUniqueProjectKey(requireNonNull(almSetting.getAppId()), repo.getSlug()); + + assertThat(db.getDbClient().newCodePeriodDao().selectAll(db.getSession())) + .isEmpty(); + } + + @Test + public void import_project_with_NCD_developer_edition() { + when(editionProvider.get()).thenReturn(Optional.of(EditionProvider.Edition.DEVELOPER)); + + UserDto user = db.users().insertUser(); + userSession.logIn(user).addPermission(PROVISION_PROJECTS); + AlmSettingDto almSetting = db.almSettings().insertBitbucketCloudAlmSetting(); + db.almPats().insert(dto -> { + dto.setAlmSettingUuid(almSetting.getUuid()); + dto.setUserUuid(user.getUuid()); + }); + Repository repo = getGsonBBCRepo(); + when(bitbucketCloudRestClient.getRepo(any(), any(), any())).thenReturn(repo); + + Projects.CreateWsResponse response = ws.newRequest() + .setParam("almSetting", almSetting.getKey()) + .setParam("repositorySlug", "repo-slug-1") + .setParam(PARAM_NEW_CODE_DEFINITION_TYPE, "NUMBER_OF_DAYS") + .setParam(PARAM_NEW_CODE_DEFINITION_VALUE, "30") + .executeProtobuf(Projects.CreateWsResponse.class); + + Projects.CreateWsResponse.Project result = response.getProject(); + assertThat(result.getKey()).isEqualTo(GENERATED_PROJECT_KEY); + assertThat(result.getName()).isEqualTo(repo.getName()); + + Optional projectDto = db.getDbClient().projectDao().selectProjectByKey(db.getSession(), result.getKey()); + assertThat(projectDto).isPresent(); + + assertThat(db.getDbClient().newCodePeriodDao().selectByProject(db.getSession(), projectDto.get().getUuid())) + .isPresent() + .get() + .extracting(NewCodePeriodDto::getType, NewCodePeriodDto::getValue, NewCodePeriodDto::getBranchUuid) + .containsExactly(NUMBER_OF_DAYS, "30", null); + } + + @Test + public void import_project_with_NCD_community_edition() { + when(editionProvider.get()).thenReturn(Optional.of(EditionProvider.Edition.COMMUNITY)); + + UserDto user = db.users().insertUser(); + userSession.logIn(user).addPermission(PROVISION_PROJECTS); + AlmSettingDto almSetting = db.almSettings().insertBitbucketCloudAlmSetting(); + db.almPats().insert(dto -> { + dto.setAlmSettingUuid(almSetting.getUuid()); + dto.setUserUuid(user.getUuid()); + }); + Repository repo = getGsonBBCRepo(); + when(bitbucketCloudRestClient.getRepo(any(), any(), any())).thenReturn(repo); + + Projects.CreateWsResponse response = ws.newRequest() + .setParam("almSetting", almSetting.getKey()) + .setParam("repositorySlug", "repo-slug-1") + .setParam(PARAM_NEW_CODE_DEFINITION_TYPE, "NUMBER_OF_DAYS") + .setParam(PARAM_NEW_CODE_DEFINITION_VALUE, "30") + .executeProtobuf(Projects.CreateWsResponse.class); + + Projects.CreateWsResponse.Project result = response.getProject(); + assertThat(result.getKey()).isEqualTo(GENERATED_PROJECT_KEY); + assertThat(result.getName()).isEqualTo(repo.getName()); + + Optional projectDto = db.getDbClient().projectDao().selectProjectByKey(db.getSession(), result.getKey()); + assertThat(projectDto).isPresent(); + + String projectUuid = projectDto.get().getUuid(); + assertThat(db.getDbClient().newCodePeriodDao().selectByBranch(db.getSession(), projectUuid, projectUuid)) + .isPresent() + .get() + .extracting(NewCodePeriodDto::getType, NewCodePeriodDto::getValue, NewCodePeriodDto::getBranchUuid) + .containsExactly(NUMBER_OF_DAYS, "30", projectUuid); + } + + @Test + public void import_project_reference_branch_ncd_no_default_branch_name() { + when(editionProvider.get()).thenReturn(Optional.of(EditionProvider.Edition.DEVELOPER)); + when(defaultBranchNameResolver.getEffectiveMainBranchName()).thenReturn("default-branch"); + + UserDto user = db.users().insertUser(); + userSession.logIn(user).addPermission(PROVISION_PROJECTS); + AlmSettingDto almSetting = db.almSettings().insertBitbucketCloudAlmSetting(); + db.almPats().insert(dto -> { + dto.setAlmSettingUuid(almSetting.getUuid()); + dto.setUserUuid(user.getUuid()); + }); + Repository repo = getGsonBBCRepoWithNoMainBranchName(); + when(bitbucketCloudRestClient.getRepo(any(), any(), any())).thenReturn(repo); + + Projects.CreateWsResponse response = ws.newRequest() + .setParam("almSetting", almSetting.getKey()) + .setParam("repositorySlug", "repo-slug-1") + .setParam(PARAM_NEW_CODE_DEFINITION_TYPE, "REFERENCE_BRANCH") + .executeProtobuf(Projects.CreateWsResponse.class); + + Projects.CreateWsResponse.Project result = response.getProject(); + assertThat(result.getKey()).isEqualTo(GENERATED_PROJECT_KEY); + assertThat(result.getName()).isEqualTo(repo.getName()); + + Optional projectDto = db.getDbClient().projectDao().selectProjectByKey(db.getSession(), result.getKey()); + assertThat(projectDto).isPresent(); + + assertThat(db.getDbClient().newCodePeriodDao().selectByProject(db.getSession(), projectDto.get().getUuid())) + .isPresent() + .get() + .extracting(NewCodePeriodDto::getType, NewCodePeriodDto::getValue) + .containsExactly(REFERENCE_BRANCH, "default-branch"); + } + + @Test + public void import_project_reference_branch_NCD() { + when(editionProvider.get()).thenReturn(Optional.of(EditionProvider.Edition.DEVELOPER)); + + UserDto user = db.users().insertUser(); + userSession.logIn(user).addPermission(PROVISION_PROJECTS); + AlmSettingDto almSetting = db.almSettings().insertBitbucketCloudAlmSetting(); + db.almPats().insert(dto -> { + dto.setAlmSettingUuid(almSetting.getUuid()); + dto.setUserUuid(user.getUuid()); + }); + Repository repo = getGsonBBCRepo(); + when(bitbucketCloudRestClient.getRepo(any(), any(), any())).thenReturn(repo); + + Projects.CreateWsResponse response = ws.newRequest() + .setParam("almSetting", almSetting.getKey()) + .setParam("repositorySlug", "repo-slug-1") + .setParam(PARAM_NEW_CODE_DEFINITION_TYPE, "REFERENCE_BRANCH") + .executeProtobuf(Projects.CreateWsResponse.class); + + Projects.CreateWsResponse.Project result = response.getProject(); + assertThat(result.getKey()).isEqualTo(GENERATED_PROJECT_KEY); + assertThat(result.getName()).isEqualTo(repo.getName()); + + Optional projectDto = db.getDbClient().projectDao().selectProjectByKey(db.getSession(), result.getKey()); + assertThat(projectDto).isPresent(); + + assertThat(db.getDbClient().newCodePeriodDao().selectByProject(db.getSession(), projectDto.get().getUuid())) + .isPresent() + .get() + .extracting(NewCodePeriodDto::getType, NewCodePeriodDto::getValue) + .containsExactly(REFERENCE_BRANCH, "develop"); + } + + @Test + public void import_project_throw_IAE_when_newCodeDefinitionValue_provided_and_no_newCodeDefinitionType() { + UserDto user = db.users().insertUser(); + userSession.logIn(user).addPermission(PROVISION_PROJECTS); + AlmSettingDto almSetting = db.almSettings().insertBitbucketCloudAlmSetting(); + db.almPats().insert(dto -> { + dto.setAlmSettingUuid(almSetting.getUuid()); + dto.setUserUuid(user.getUuid()); + }); + Repository repo = getGsonBBCRepo(); + when(bitbucketCloudRestClient.getRepo(any(), any(), any())).thenReturn(repo); + + TestRequest request = ws.newRequest() + .setParam("almSetting", almSetting.getKey()) + .setParam("repositorySlug", "repo-slug-1") + .setParam(PARAM_NEW_CODE_DEFINITION_VALUE, "30"); + + assertThatThrownBy(() -> request.executeProtobuf(Projects.CreateWsResponse.class)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("New code definition type is required when new code definition value is provided"); } @Test @@ -233,7 +409,9 @@ public class ImportBitbucketCloudRepoActionIT { .extracting(WebService.Param::key, WebService.Param::isRequired) .containsExactlyInAnyOrder( tuple("almSetting", true), - tuple("repositorySlug", true)); + tuple("repositorySlug", true), + tuple(PARAM_NEW_CODE_DEFINITION_TYPE, false), + tuple(PARAM_NEW_CODE_DEFINITION_VALUE, false)); } private Repository getGsonBBCRepo() { @@ -242,4 +420,10 @@ public class ImportBitbucketCloudRepoActionIT { return new Repository("REPO-UUID-ONE", "repo-slug-1", "repoName1", project1, mainBranch); } + private Repository getGsonBBCRepoWithNoMainBranchName() { + Project project1 = new Project("PROJECT-UUID-ONE", "projectKey1", "projectName1"); + MainBranch mainBranch = new MainBranch("branch", null); + return new Repository("REPO-UUID-ONE", "repo-slug-1", "repoName1", project1, mainBranch); + } + } diff --git a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/almintegration/ws/bitbucketcloud/ImportBitbucketCloudRepoAction.java b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/almintegration/ws/bitbucketcloud/ImportBitbucketCloudRepoAction.java index 051b4269f1a..69afb640854 100644 --- a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/almintegration/ws/bitbucketcloud/ImportBitbucketCloudRepoAction.java +++ b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/almintegration/ws/bitbucketcloud/ImportBitbucketCloudRepoAction.java @@ -21,6 +21,7 @@ package org.sonar.server.almintegration.ws.bitbucketcloud; import java.util.Optional; import javax.annotation.Nullable; +import javax.inject.Inject; import org.sonar.alm.client.bitbucket.bitbucketcloud.BitbucketCloudRestClient; import org.sonar.alm.client.bitbucket.bitbucketcloud.Repository; import org.sonar.api.server.ws.Request; @@ -38,6 +39,8 @@ import org.sonar.server.almintegration.ws.ProjectKeyGenerator; import org.sonar.server.component.ComponentCreationData; import org.sonar.server.component.ComponentUpdater; import org.sonar.server.component.NewComponent; +import org.sonar.server.newcodeperiod.NewCodeDefinitionResolver; +import org.sonar.server.project.DefaultBranchNameResolver; import org.sonar.server.project.ProjectDefaultVisibility; import org.sonar.server.user.UserSession; import org.sonarqube.ws.Projects; @@ -47,7 +50,12 @@ import static org.sonar.api.resources.Qualifiers.PROJECT; import static org.sonar.server.almintegration.ws.ImportHelper.PARAM_ALM_SETTING; import static org.sonar.server.almintegration.ws.ImportHelper.toCreateResponse; import static org.sonar.server.component.NewComponent.newComponentBuilder; +import static org.sonar.server.newcodeperiod.NewCodeDefinitionResolver.NEW_CODE_PERIOD_TYPE_DESCRIPTION_PROJECT_CREATION; +import static org.sonar.server.newcodeperiod.NewCodeDefinitionResolver.NEW_CODE_PERIOD_VALUE_DESCRIPTION_PROJECT_CREATION; +import static org.sonar.server.newcodeperiod.NewCodeDefinitionResolver.checkNewCodeDefinitionParam; import static org.sonar.server.ws.WsUtils.writeProtobuf; +import static org.sonarqube.ws.client.project.ProjectsWsParameters.PARAM_NEW_CODE_DEFINITION_TYPE; +import static org.sonarqube.ws.client.project.ProjectsWsParameters.PARAM_NEW_CODE_DEFINITION_VALUE; public class ImportBitbucketCloudRepoAction implements AlmIntegrationsWsAction { @@ -60,10 +68,14 @@ public class ImportBitbucketCloudRepoAction implements AlmIntegrationsWsAction { private final ComponentUpdater componentUpdater; private final ImportHelper importHelper; private final ProjectKeyGenerator projectKeyGenerator; + private final NewCodeDefinitionResolver newCodeDefinitionResolver; + private final DefaultBranchNameResolver defaultBranchNameResolver; + @Inject public ImportBitbucketCloudRepoAction(DbClient dbClient, UserSession userSession, BitbucketCloudRestClient bitbucketCloudRestClient, ProjectDefaultVisibility projectDefaultVisibility, ComponentUpdater componentUpdater, ImportHelper importHelper, - ProjectKeyGenerator projectKeyGenerator) { + ProjectKeyGenerator projectKeyGenerator, NewCodeDefinitionResolver newCodeDefinitionResolver, + DefaultBranchNameResolver defaultBranchNameResolver) { this.dbClient = dbClient; this.userSession = userSession; this.bitbucketCloudRestClient = bitbucketCloudRestClient; @@ -71,6 +83,8 @@ public class ImportBitbucketCloudRepoAction implements AlmIntegrationsWsAction { this.componentUpdater = componentUpdater; this.importHelper = importHelper; this.projectKeyGenerator = projectKeyGenerator; + this.newCodeDefinitionResolver = newCodeDefinitionResolver; + this.defaultBranchNameResolver = defaultBranchNameResolver; } @Override @@ -94,6 +108,14 @@ public class ImportBitbucketCloudRepoAction implements AlmIntegrationsWsAction { .setMaximumLength(200) .setDescription("DevOps Platform setting key"); + action.createParam(PARAM_NEW_CODE_DEFINITION_TYPE) + .setDescription(NEW_CODE_PERIOD_TYPE_DESCRIPTION_PROJECT_CREATION) + .setSince("10.1"); + + action.createParam(PARAM_NEW_CODE_DEFINITION_VALUE) + .setDescription(NEW_CODE_PERIOD_VALUE_DESCRIPTION_PROJECT_CREATION) + .setSince("10.1"); + } @Override @@ -105,16 +127,16 @@ public class ImportBitbucketCloudRepoAction implements AlmIntegrationsWsAction { private Projects.CreateWsResponse doHandle(Request request) { importHelper.checkProvisionProjectPermission(); - String userUuid = importHelper.getUserUuid(); + String newCodeDefinitionType = request.param(PARAM_NEW_CODE_DEFINITION_TYPE); + String newCodeDefinitionValue = request.param(PARAM_NEW_CODE_DEFINITION_VALUE); + String repoSlug = request.mandatoryParam(PARAM_REPO_SLUG); AlmSettingDto almSettingDto = importHelper.getAlmSetting(request); String workspace = ofNullable(almSettingDto.getAppId()) .orElseThrow(() -> new IllegalArgumentException(String.format("workspace for alm setting %s is missing", almSettingDto.getKey()))); try (DbSession dbSession = dbClient.openSession(false)) { - String pat = dbClient.almPatDao().selectByUserAndAlmSetting(dbSession, userUuid, almSettingDto) - .map(AlmPatDto::getPersonalAccessToken) - .orElseThrow(() -> new IllegalArgumentException(String.format("Username and App Password for '%s' is missing", almSettingDto.getKey()))); + String pat = getPat(dbSession, almSettingDto); Repository repo = bitbucketCloudRestClient.getRepo(pat, workspace, repoSlug); @@ -123,12 +145,29 @@ public class ImportBitbucketCloudRepoAction implements AlmIntegrationsWsAction { populatePRSetting(dbSession, repo, projectDto, almSettingDto); + checkNewCodeDefinitionParam(newCodeDefinitionType, newCodeDefinitionValue); + + if (newCodeDefinitionType != null) { + newCodeDefinitionResolver.createNewCodeDefinition(dbSession, projectDto.getUuid(), + Optional.ofNullable(repo.getMainBranch().getName()).orElse(defaultBranchNameResolver.getEffectiveMainBranchName()), + newCodeDefinitionType, newCodeDefinitionValue); + } + componentUpdater.commitAndIndex(dbSession, componentCreationData.mainBranchComponent()); return toCreateResponse(projectDto); } } + private String getPat(DbSession dbSession, AlmSettingDto almSettingDto) { + String userUuid = importHelper.getUserUuid(); + + return dbClient.almPatDao().selectByUserAndAlmSetting(dbSession, userUuid, almSettingDto) + .map(AlmPatDto::getPersonalAccessToken) + .orElseThrow(() -> new IllegalArgumentException(String.format("Username and App Password for '%s' is missing", + almSettingDto.getKey()))); + } + private ComponentCreationData createProject(DbSession dbSession, String workspace, Repository repo, @Nullable String defaultBranchName) { boolean visibility = projectDefaultVisibility.get(dbSession).isPrivate(); String uniqueProjectKey = projectKeyGenerator.generateUniqueProjectKey(workspace, repo.getSlug()); -- 2.39.5