]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-14558 Update SonarQube main branch name during Gitlab project onboarding
authorPierre <pierre.guillot@sonarsource.com>
Mon, 8 Mar 2021 13:47:36 +0000 (14:47 +0100)
committersonartech <sonartech@sonarsource.com>
Tue, 16 Mar 2021 20:08:15 +0000 (20:08 +0000)
server/sonar-alm-client/src/main/java/org/sonar/alm/client/gitlab/GitLabBranch.java [new file with mode: 0644]
server/sonar-alm-client/src/main/java/org/sonar/alm/client/gitlab/GitlabHttpClient.java
server/sonar-alm-client/src/test/java/org/sonar/alm/client/gitlab/GitlabHttpClientTest.java
server/sonar-webserver-webapi/src/main/java/org/sonar/server/almintegration/ws/gitlab/ImportGitLabProjectAction.java
server/sonar-webserver-webapi/src/test/java/org/sonar/server/almintegration/ws/gitlab/ImportGitLabProjectActionTest.java

diff --git a/server/sonar-alm-client/src/main/java/org/sonar/alm/client/gitlab/GitLabBranch.java b/server/sonar-alm-client/src/main/java/org/sonar/alm/client/gitlab/GitLabBranch.java
new file mode 100644 (file)
index 0000000..d6519aa
--- /dev/null
@@ -0,0 +1,50 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2021 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.alm.client.gitlab;
+
+import com.google.gson.annotations.SerializedName;
+import javax.annotation.Nullable;
+
+public class GitLabBranch {
+
+  @SerializedName("name")
+  private final String name;
+
+  @SerializedName("default")
+  private final boolean isDefault;
+
+  public GitLabBranch() {
+    // http://stackoverflow.com/a/18645370/229031
+    this(null, false);
+  }
+
+  public GitLabBranch(@Nullable String name, boolean isDefault) {
+    this.name = name;
+    this.isDefault = isDefault;
+  }
+
+  public String getName() {
+    return name;
+  }
+
+  public boolean isDefault() {
+    return isDefault;
+  }
+}
index f621e3d5a1d2c0f45364bdcec06144dbc13073d3..a2d6b982f504784b457df3912a805a3b728b3036 100644 (file)
@@ -25,6 +25,7 @@ import com.google.gson.JsonSyntaxException;
 import java.io.IOException;
 import java.io.UnsupportedEncodingException;
 import java.net.URLEncoder;
+import java.util.Arrays;
 import java.util.List;
 import java.util.Optional;
 import javax.annotation.Nullable;
@@ -57,7 +58,6 @@ public class GitlabHttpClient {
       .build();
   }
 
-
   public void checkReadPermission(@Nullable String gitlabUrl, @Nullable String personalAccessToken) {
     checkProjectAccess(gitlabUrl, personalAccessToken, "Could not validate GitLab read permission. Got an unexpected answer.");
   }
@@ -223,6 +223,27 @@ public class GitlabHttpClient {
     }
   }
 
+  public List<GitLabBranch> getBranches(String gitlabUrl, String pat, Long gitlabProjectId) {
+    String url = String.format("%s/projects/%s/repository/branches", gitlabUrl, gitlabProjectId);
+    LOG.debug(String.format("get branches : [%s]", url));
+    Request request = new Request.Builder()
+      .addHeader(PRIVATE_TOKEN, pat)
+      .get()
+      .url(url)
+      .build();
+
+    try (Response response = client.newCall(request).execute()) {
+      checkResponseIsSuccessful(response);
+      String body = response.body().string();
+      LOG.trace(String.format("loading branches payload result : [%s]", body));
+      return Arrays.asList(new GsonBuilder().create().fromJson(body, GitLabBranch[].class));
+    } catch (JsonSyntaxException e) {
+      throw new IllegalArgumentException("Could not parse GitLab answer to retrieve project branches. Got a non-json payload as result.");
+    } catch (IOException e) {
+      throw new IllegalStateException(e.getMessage(), e);
+    }
+  }
+
   public ProjectList searchProjects(String gitlabUrl, String personalAccessToken, @Nullable String projectName,
     int pageNumber, int pageSize) {
     String url = String.format("%s/projects?archived=false&simple=true&membership=true&order_by=name&sort=asc&search=%s&page=%d&per_page=%d",
index a19ee3343206b0c1baae5d8e120b0e414c5057bd..38745899f06169261a165c260d43566345cd56c9 100644 (file)
@@ -62,7 +62,6 @@ public class GitlabHttpClientTest {
       .setBody("{\"error\":\"invalid_token\",\"error_description\":\"Token was revoked. You have to re-authorize from the user.\"}");
     server.enqueue(response);
 
-    String gitlabUrl = this.gitlabUrl;
     assertThatThrownBy(() -> underTest.searchProjects(gitlabUrl, "pat", "example", 1, 2))
       .isInstanceOf(IllegalArgumentException.class)
       .hasMessage("Your GitLab token was revoked");
@@ -77,7 +76,6 @@ public class GitlabHttpClientTest {
         "\"scope\":\"api read_api\"}");
     server.enqueue(response);
 
-    String gitlabUrl = this.gitlabUrl;
     assertThatThrownBy(() -> underTest.searchProjects(gitlabUrl, "pat", "example", 1, 2))
       .isInstanceOf(IllegalArgumentException.class)
       .hasMessage("Your GitLab token has insufficient scope");
@@ -90,7 +88,6 @@ public class GitlabHttpClientTest {
       .setBody("error in pat");
     server.enqueue(response);
 
-    String gitlabUrl = this.gitlabUrl;
     assertThatThrownBy(() -> underTest.searchProjects(gitlabUrl, "pat", "example", 1, 2))
       .isInstanceOf(IllegalArgumentException.class)
       .hasMessage("Invalid personal access token");
@@ -122,12 +119,55 @@ public class GitlabHttpClientTest {
       .setBody("non json payload");
     server.enqueue(response);
 
-    String instanceUrl = gitlabUrl;
-    assertThatThrownBy(() -> underTest.getProject(instanceUrl, "pat", 12345L))
+    assertThatThrownBy(() -> underTest.getProject(gitlabUrl, "pat", 12345L))
       .isInstanceOf(IllegalArgumentException.class)
       .hasMessage("Could not parse GitLab answer to retrieve a project. Got a non-json payload as result.");
   }
 
+  @Test
+  public void get_branches(){
+    MockResponse response = new MockResponse()
+      .setResponseCode(200)
+      .setBody("[{\n"
+        + "    \"name\": \"main\",\n"
+        + "    \"default\": true\n"
+        + "},{\n"
+        + "    \"name\": \"other\",\n"
+        + "    \"default\": false\n"
+        + "}]");
+    server.enqueue(response);
+
+    assertThat(underTest.getBranches(gitlabUrl, "pat", 12345L))
+      .extracting(GitLabBranch::getName, GitLabBranch::isDefault)
+      .containsExactly(
+        tuple("main", true),
+        tuple("other", false)
+      );
+  }
+
+  @Test
+  public void get_branches_fail_if_non_json_payload() {
+    MockResponse response = new MockResponse()
+      .setResponseCode(200)
+      .setBody("non json payload");
+    server.enqueue(response);
+
+    String instanceUrl = gitlabUrl;
+    assertThatThrownBy(() -> underTest.getBranches(instanceUrl, "pat", 12345L))
+      .isInstanceOf(IllegalArgumentException.class)
+      .hasMessage("Could not parse GitLab answer to retrieve project branches. Got a non-json payload as result.");
+  }
+
+  @Test
+  public void get_branches_fail_if_exception() throws IOException {
+    server.shutdown();
+
+    String instanceUrl = gitlabUrl;
+    assertThatThrownBy(() -> underTest.getBranches(instanceUrl, "pat", 12345L))
+      .isInstanceOf(IllegalStateException.class)
+      .hasMessageContaining("Failed to connect to");
+  }
+
   @Test
   public void search_projects() throws InterruptedException {
     MockResponse projects = new MockResponse()
@@ -236,8 +276,7 @@ public class GitlabHttpClientTest {
     projects.addHeader("X-Total", "bad-total-number");
     server.enqueue(projects);
 
-    String gitlabInstanceUrl = gitlabUrl;
-    assertThatThrownBy(() -> underTest.searchProjects(gitlabInstanceUrl, "pat", "example", 1, 10))
+    assertThatThrownBy(() -> underTest.searchProjects(gitlabUrl, "pat", "example", 1, 10))
       .isInstanceOf(IllegalArgumentException.class)
       .hasMessage("Could not parse pagination number");
   }
@@ -249,8 +288,7 @@ public class GitlabHttpClientTest {
       .setBody("[ ]");
     server.enqueue(projects);
 
-    String gitlabInstanceUrl = gitlabUrl;
-    assertThatThrownBy(() -> underTest.searchProjects(gitlabInstanceUrl, "pat", "example", 1, 10))
+    assertThatThrownBy(() -> underTest.searchProjects(gitlabUrl, "pat", "example", 1, 10))
       .isInstanceOf(IllegalArgumentException.class)
       .hasMessage("Pagination data from GitLab response is missing");
   }
@@ -262,7 +300,6 @@ public class GitlabHttpClientTest {
       .setBody("test");
     server.enqueue(projects);
 
-    String gitlabUrl = this.gitlabUrl;
     assertThatThrownBy(() -> underTest.searchProjects(gitlabUrl, "pat", "example", 1, 2))
       .isInstanceOf(IllegalArgumentException.class)
       .hasMessage("Could not get projects from GitLab instance");
index b116ab2e222d7efc30fb0efc1963066127c3ce45..ae3629e87cd1581e800bd47f4c56ff0a8e058de6 100644 (file)
@@ -21,6 +21,8 @@ package org.sonar.server.almintegration.ws.gitlab;
 
 import com.google.common.annotations.VisibleForTesting;
 import java.util.Optional;
+import javax.annotation.Nullable;
+import org.sonar.alm.client.gitlab.GitLabBranch;
 import org.sonar.alm.client.gitlab.GitlabHttpClient;
 import org.sonar.alm.client.gitlab.Project;
 import org.sonar.api.server.ws.Request;
@@ -103,16 +105,23 @@ public class ImportGitLabProjectAction implements AlmIntegrationsWsAction {
 
       long gitlabProjectId = request.mandatoryParamAsLong(PARAM_GITLAB_PROJECT_ID);
 
-      String url = requireNonNull(almSettingDto.getUrl(), "ALM url cannot be null");
-      Project gitlabProject = gitlabHttpClient.getProject(url, pat, gitlabProjectId);
+      String gitlabUrl = requireNonNull(almSettingDto.getUrl(), "ALM gitlabUrl cannot be null");
+      Project gitlabProject = gitlabHttpClient.getProject(gitlabUrl, pat, gitlabProjectId);
 
-      ComponentDto componentDto = createProject(dbSession, gitlabProject);
+      Optional<String> almMainBranchName = getAlmDefaultBranch(pat, gitlabProjectId, gitlabUrl);
+      ComponentDto componentDto = createProject(dbSession, gitlabProject, almMainBranchName.orElse(null));
       populateMRSetting(dbSession, gitlabProjectId, componentDto, almSettingDto);
+      componentUpdater.commitAndIndex(dbSession, componentDto);
 
       return ImportHelper.toCreateResponse(componentDto);
     }
   }
 
+  private Optional<String> getAlmDefaultBranch(String pat, long gitlabProjectId, String gitlabUrl) {
+    Optional<GitLabBranch> almMainBranch = gitlabHttpClient.getBranches(gitlabUrl, pat, gitlabProjectId).stream().filter(GitLabBranch::isDefault).findFirst();
+    return almMainBranch.map(GitLabBranch::getName);
+  }
+
   private void populateMRSetting(DbSession dbSession, Long gitlabProjectId, ComponentDto componentDto, AlmSettingDto almSettingDto) {
     dbClient.projectAlmSettingDao().insertOrUpdate(dbSession, new ProjectAlmSettingDto()
       .setProjectUuid(componentDto.projectUuid())
@@ -120,20 +129,20 @@ public class ImportGitLabProjectAction implements AlmIntegrationsWsAction {
       .setAlmRepo(gitlabProjectId.toString())
       .setAlmSlug(null)
       .setMonorepo(false));
-    dbSession.commit();
   }
 
-  private ComponentDto createProject(DbSession dbSession, Project gitlabProject) {
+  private ComponentDto createProject(DbSession dbSession, Project gitlabProject, @Nullable String mainBranchName) {
     boolean visibility = projectDefaultVisibility.get(dbSession).isPrivate();
     String sqProjectKey = generateProjectKey(gitlabProject.getPathWithNamespace(), uuidFactory.create());
 
-    return componentUpdater.create(dbSession, newComponentBuilder()
+    return componentUpdater.createWithoutCommit(dbSession, newComponentBuilder()
       .setKey(sqProjectKey)
       .setName(gitlabProject.getName())
       .setPrivate(visibility)
       .setQualifier(PROJECT)
       .build(),
-      userSession.getUuid());
+      userSession.getUuid(), mainBranchName, s -> {
+      });
   }
 
   @VisibleForTesting
index 55c0d0e6ea6e3cfa656aab7acee4ce89f224b68e..9ad81f6beda36dd389979300e19c76e4bfc56cea 100644 (file)
@@ -21,9 +21,11 @@ package org.sonar.server.almintegration.ws.gitlab;
 
 import java.util.Optional;
 import java.util.stream.IntStream;
+import org.assertj.core.api.Assertions;
 import org.junit.Before;
 import org.junit.Rule;
 import org.junit.Test;
+import org.sonar.alm.client.gitlab.GitLabBranch;
 import org.sonar.alm.client.gitlab.GitlabHttpClient;
 import org.sonar.alm.client.gitlab.Project;
 import org.sonar.api.utils.System2;
@@ -32,6 +34,7 @@ import org.sonar.core.util.SequenceUuidFactory;
 import org.sonar.core.util.UuidFactory;
 import org.sonar.db.DbTester;
 import org.sonar.db.alm.setting.AlmSettingDto;
+import org.sonar.db.component.BranchDto;
 import org.sonar.db.project.ProjectDto;
 import org.sonar.db.user.UserDto;
 import org.sonar.server.almintegration.ws.ImportHelper;
@@ -45,9 +48,12 @@ import org.sonar.server.tester.UserSessionRule;
 import org.sonar.server.ws.WsActionTester;
 import org.sonarqube.ws.Projects;
 
+import static java.util.Collections.emptyList;
+import static java.util.Collections.singletonList;
 import static java.util.stream.Collectors.joining;
 import static org.apache.commons.lang.RandomStringUtils.randomAlphanumeric;
 import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.tuple;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.verify;
@@ -93,6 +99,7 @@ public class ImportGitLabProjectActionTest {
     });
     Project project = getGitlabProject();
     when(gitlabHttpClient.getProject(any(), any(), any())).thenReturn(project);
+    when(gitlabHttpClient.getBranches(any(), any(), any())).thenReturn(singletonList(new GitLabBranch("master", true)));
     when(uuidFactory.create()).thenReturn("uuid");
 
     Projects.CreateWsResponse response = ws.newRequest()
@@ -111,6 +118,78 @@ public class ImportGitLabProjectActionTest {
     assertThat(db.getDbClient().projectAlmSettingDao().selectByProject(db.getSession(), projectDto.get())).isPresent();
   }
 
+  @Test
+  public void import_project_with_specific_different_default_branch() {
+    UserDto user = db.users().insertUser();
+    userSession.logIn(user).addPermission(PROVISION_PROJECTS);
+    AlmSettingDto almSetting = db.almSettings().insertGitlabAlmSetting();
+    db.almPats().insert(dto -> {
+      dto.setAlmSettingUuid(almSetting.getUuid());
+      dto.setUserUuid(user.getUuid());
+      dto.setPersonalAccessToken("PAT");
+    });
+    Project project = getGitlabProject();
+    when(gitlabHttpClient.getProject(any(), any(), any())).thenReturn(project);
+    when(gitlabHttpClient.getBranches(any(), any(), any())).thenReturn(singletonList(new GitLabBranch("main", true)));
+    when(uuidFactory.create()).thenReturn("uuid");
+
+    Projects.CreateWsResponse response = ws.newRequest()
+      .setParam("almSetting", almSetting.getKey())
+      .setParam("gitlabProjectId", "12345")
+      .executeProtobuf(Projects.CreateWsResponse.class);
+
+    verify(gitlabHttpClient).getProject(almSetting.getUrl(), "PAT", 12345L);
+    verify(gitlabHttpClient).getBranches(almSetting.getUrl(), "PAT", 12345L);
+
+    Projects.CreateWsResponse.Project result = response.getProject();
+    assertThat(result.getKey()).isEqualTo(project.getPathWithNamespace() + "_uuid");
+    assertThat(result.getName()).isEqualTo(project.getName());
+
+    Optional<ProjectDto> projectDto = db.getDbClient().projectDao().selectProjectByKey(db.getSession(), result.getKey());
+    assertThat(projectDto).isPresent();
+    assertThat(db.getDbClient().projectAlmSettingDao().selectByProject(db.getSession(), projectDto.get())).isPresent();
+
+    Assertions.assertThat(db.getDbClient().branchDao().selectByProject(db.getSession(), projectDto.get()))
+      .extracting(BranchDto::getKey, BranchDto::isMain)
+      .containsExactlyInAnyOrder(tuple("main", true));
+  }
+
+  @Test
+  public void import_project_no_gitlab_default_branch() {
+    UserDto user = db.users().insertUser();
+    userSession.logIn(user).addPermission(PROVISION_PROJECTS);
+    AlmSettingDto almSetting = db.almSettings().insertGitlabAlmSetting();
+    db.almPats().insert(dto -> {
+      dto.setAlmSettingUuid(almSetting.getUuid());
+      dto.setUserUuid(user.getUuid());
+      dto.setPersonalAccessToken("PAT");
+    });
+    Project project = getGitlabProject();
+    when(gitlabHttpClient.getProject(any(), any(), any())).thenReturn(project);
+    when(gitlabHttpClient.getBranches(any(), any(), any())).thenReturn(emptyList());
+    when(uuidFactory.create()).thenReturn("uuid");
+
+    Projects.CreateWsResponse response = ws.newRequest()
+      .setParam("almSetting", almSetting.getKey())
+      .setParam("gitlabProjectId", "12345")
+      .executeProtobuf(Projects.CreateWsResponse.class);
+
+    verify(gitlabHttpClient).getProject(almSetting.getUrl(), "PAT", 12345L);
+    verify(gitlabHttpClient).getBranches(almSetting.getUrl(), "PAT", 12345L);
+
+    Projects.CreateWsResponse.Project result = response.getProject();
+    assertThat(result.getKey()).isEqualTo(project.getPathWithNamespace() + "_uuid");
+    assertThat(result.getName()).isEqualTo(project.getName());
+
+    Optional<ProjectDto> projectDto = db.getDbClient().projectDao().selectProjectByKey(db.getSession(), result.getKey());
+    assertThat(projectDto).isPresent();
+    assertThat(db.getDbClient().projectAlmSettingDao().selectByProject(db.getSession(), projectDto.get())).isPresent();
+
+    Assertions.assertThat(db.getDbClient().branchDao().selectByProject(db.getSession(), projectDto.get()))
+      .extracting(BranchDto::getKey, BranchDto::isMain)
+      .containsExactlyInAnyOrder(tuple("master", true));
+  }
+
   @Test
   public void generate_project_key_less_than_250() {
     String name = "abcdeert";