aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorZipeng WU <zipeng.wu@sonarsource.com>2021-05-11 22:11:39 +0200
committersonartech <sonartech@sonarsource.com>2021-05-21 20:03:36 +0000
commit97baa10facb3a4e6295fd7dfe93359ebd578c60f (patch)
treeff378b5065356d43d1e86ca2cb0e453ce485a9df
parent5333756be234c4bb50c9edf3c4b2cab5e0483559 (diff)
downloadsonarqube-97baa10facb3a4e6295fd7dfe93359ebd578c60f.tar.gz
sonarqube-97baa10facb3a4e6295fd7dfe93359ebd578c60f.zip
SONAR-14802 Search bitbucket cloud repository for onboarding
-rw-r--r--server/sonar-alm-client/src/main/java/org/sonar/alm/client/bitbucket/bitbucketcloud/BitbucketCloudRestClient.java6
-rw-r--r--server/sonar-alm-client/src/main/java/org/sonar/alm/client/bitbucket/bitbucketcloud/Project.java57
-rw-r--r--server/sonar-alm-client/src/main/java/org/sonar/alm/client/bitbucket/bitbucketcloud/Repository.java65
-rw-r--r--server/sonar-alm-client/src/main/java/org/sonar/alm/client/bitbucket/bitbucketcloud/RepositoryList.java66
-rw-r--r--server/sonar-alm-client/src/test/java/org/sonar/alm/client/bitbucket/bitbucketcloud/BitbucketCloudRestClientTest.java64
-rw-r--r--server/sonar-webserver-webapi/src/main/java/org/sonar/server/almintegration/ws/AlmIntegrationsWSModule.java2
-rw-r--r--server/sonar-webserver-webapi/src/main/java/org/sonar/server/almintegration/ws/bitbucketcloud/SearchBitbucketCloudReposAction.java140
-rw-r--r--server/sonar-webserver-webapi/src/main/java/org/sonar/server/almintegration/ws/bitbucketcloud/package-info.java23
-rw-r--r--server/sonar-webserver-webapi/src/main/java/org/sonar/server/almintegration/ws/bitbucketserver/SearchBitbucketServerReposAction.java2
-rw-r--r--server/sonar-webserver-webapi/src/test/java/org/sonar/server/almintegration/ws/bitbucketcloud/SearchBitbucketCloudReposActionTest.java181
-rw-r--r--sonar-ws/src/main/protobuf/ws-alm_integrations.proto16
11 files changed, 620 insertions, 2 deletions
diff --git a/server/sonar-alm-client/src/main/java/org/sonar/alm/client/bitbucket/bitbucketcloud/BitbucketCloudRestClient.java b/server/sonar-alm-client/src/main/java/org/sonar/alm/client/bitbucket/bitbucketcloud/BitbucketCloudRestClient.java
index 34c7ba2572c..1627f75869c 100644
--- a/server/sonar-alm-client/src/main/java/org/sonar/alm/client/bitbucket/bitbucketcloud/BitbucketCloudRestClient.java
+++ b/server/sonar-alm-client/src/main/java/org/sonar/alm/client/bitbucket/bitbucketcloud/BitbucketCloudRestClient.java
@@ -134,6 +134,12 @@ public class BitbucketCloudRestClient {
}
}
+ public RepositoryList searchRepos(String encodedCredentials, String workspace, @Nullable String repoName, Integer page, Integer pageSize) {
+ String filterQuery = String.format("q=name~\"%s\"", repoName != null ? repoName : "");
+ HttpUrl url = buildUrl(String.format("/repositories/%s?%s&page=%s&pagelen=%s", workspace, filterQuery, page, pageSize));
+ return doGetWithBasicAuth(encodedCredentials, url, r -> buildGson().fromJson(r.body().charStream(), RepositoryList.class));
+ }
+
public String createAccessToken(String clientId, String clientSecret) {
Request request = createAccessTokenRequest(clientId, clientSecret);
return doCall(request, r -> buildGson().fromJson(r.body().charStream(), Token.class)).getAccessToken();
diff --git a/server/sonar-alm-client/src/main/java/org/sonar/alm/client/bitbucket/bitbucketcloud/Project.java b/server/sonar-alm-client/src/main/java/org/sonar/alm/client/bitbucket/bitbucketcloud/Project.java
new file mode 100644
index 00000000000..9e32c6774b4
--- /dev/null
+++ b/server/sonar-alm-client/src/main/java/org/sonar/alm/client/bitbucket/bitbucketcloud/Project.java
@@ -0,0 +1,57 @@
+/*
+ * 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.bitbucket.bitbucketcloud;
+
+import com.google.gson.annotations.SerializedName;
+
+public class Project {
+
+ @SerializedName("key")
+ private String key;
+
+ @SerializedName("name")
+ private String name;
+
+ @SerializedName("uuid")
+ private String uuid;
+
+ public Project() {
+ // http://stackoverflow.com/a/18645370/229031
+ }
+
+ public Project(String uuid, String key, String name) {
+ this.uuid = uuid;
+ this.key = key;
+ this.name = name;
+ }
+
+ public String getKey() {
+ return key;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public String getUuid() {
+ return uuid;
+ }
+
+}
diff --git a/server/sonar-alm-client/src/main/java/org/sonar/alm/client/bitbucket/bitbucketcloud/Repository.java b/server/sonar-alm-client/src/main/java/org/sonar/alm/client/bitbucket/bitbucketcloud/Repository.java
new file mode 100644
index 00000000000..534a4773aff
--- /dev/null
+++ b/server/sonar-alm-client/src/main/java/org/sonar/alm/client/bitbucket/bitbucketcloud/Repository.java
@@ -0,0 +1,65 @@
+/*
+ * 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.bitbucket.bitbucketcloud;
+
+import com.google.gson.annotations.SerializedName;
+
+public class Repository {
+
+ @SerializedName("slug")
+ private String slug;
+
+ @SerializedName("name")
+ private String name;
+
+ @SerializedName("uuid")
+ private String uuid;
+
+ @SerializedName("project")
+ private Project project;
+
+ public Repository() {
+ // http://stackoverflow.com/a/18645370/229031
+ }
+
+ public Repository(String uuid, String slug, String name, Project project) {
+ this.uuid = uuid;
+ this.slug = slug;
+ this.name = name;
+ this.project = project;
+ }
+
+ public String getSlug() {
+ return slug;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public String getUuid() {
+ return uuid;
+ }
+
+ public Project getProject() {
+ return project;
+ }
+
+}
diff --git a/server/sonar-alm-client/src/main/java/org/sonar/alm/client/bitbucket/bitbucketcloud/RepositoryList.java b/server/sonar-alm-client/src/main/java/org/sonar/alm/client/bitbucket/bitbucketcloud/RepositoryList.java
new file mode 100644
index 00000000000..73cc74d6c9c
--- /dev/null
+++ b/server/sonar-alm-client/src/main/java/org/sonar/alm/client/bitbucket/bitbucketcloud/RepositoryList.java
@@ -0,0 +1,66 @@
+/*
+ * 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.bitbucket.bitbucketcloud;
+
+import com.google.gson.annotations.SerializedName;
+import java.util.List;
+
+public class RepositoryList {
+
+ @SerializedName("next")
+ private String next;
+
+ @SerializedName("page")
+ private Integer page;
+
+ @SerializedName("pagelen")
+ private Integer pagelen;
+
+ @SerializedName("values")
+ private List<Repository> values;
+
+ public RepositoryList() {
+ // http://stackoverflow.com/a/18645370/229031
+ }
+
+ public RepositoryList(String next, List<Repository> values, Integer page, Integer pagelen) {
+ this.next = next;
+ this.values = values;
+ this.page = page;
+ this.pagelen = pagelen;
+ }
+
+ public String getNext() {
+ return next;
+ }
+
+ public List<Repository> getValues() {
+ return values;
+ }
+
+ public Integer getPage() {
+ return page;
+ }
+
+ public Integer getPagelen() {
+ return pagelen;
+ }
+
+}
diff --git a/server/sonar-alm-client/src/test/java/org/sonar/alm/client/bitbucket/bitbucketcloud/BitbucketCloudRestClientTest.java b/server/sonar-alm-client/src/test/java/org/sonar/alm/client/bitbucket/bitbucketcloud/BitbucketCloudRestClientTest.java
index 387da13f89c..7a6d675a7c5 100644
--- a/server/sonar-alm-client/src/test/java/org/sonar/alm/client/bitbucket/bitbucketcloud/BitbucketCloudRestClientTest.java
+++ b/server/sonar-alm-client/src/test/java/org/sonar/alm/client/bitbucket/bitbucketcloud/BitbucketCloudRestClientTest.java
@@ -19,6 +19,7 @@
*/
package org.sonar.alm.client.bitbucket.bitbucketcloud;
+import com.google.gson.Gson;
import java.io.IOException;
import okhttp3.Call;
import okhttp3.OkHttpClient;
@@ -34,9 +35,11 @@ import org.junit.Before;
import org.junit.Test;
import org.sonarqube.ws.client.OkHttpClientBuilder;
+import static java.util.Arrays.asList;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
+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.when;
@@ -59,6 +62,67 @@ public class BitbucketCloudRestClientTest {
}
@Test
+ public void get_repos() {
+ server.enqueue(new MockResponse()
+ .setHeader("Content-Type", "application/json;charset=UTF-8")
+ .setBody("{\n" +
+ " \"values\": [\n" +
+ " {\n" +
+ " \"slug\": \"banana\",\n" +
+ " \"uuid\": \"BANANA-UUID\",\n" +
+ " \"name\": \"banana\",\n" +
+ " \"project\": {\n" +
+ " \"key\": \"HOY\",\n" +
+ " \"uuid\": \"BANANA-PROJECT-UUID\",\n" +
+ " \"name\": \"hoy\"\n" +
+ " }\n" +
+ " },\n" +
+ " {\n" +
+ " \"slug\": \"potato\",\n" +
+ " \"uuid\": \"POTATO-UUID\",\n" +
+ " \"name\": \"potato\",\n" +
+ " \"project\": {\n" +
+ " \"key\": \"HEY\",\n" +
+ " \"uuid\": \"POTATO-PROJECT-UUID\",\n" +
+ " \"name\": \"hey\"\n" +
+ " }\n" +
+ " }\n" +
+ " ]\n" +
+ "}"));
+
+ RepositoryList repositoryList = underTest.searchRepos("user:apppwd", "", null, 1, 100);
+ assertThat(repositoryList.getNext()).isNull();
+ assertThat(repositoryList.getValues())
+ .hasSize(2)
+ .extracting(Repository::getUuid, Repository::getName, Repository::getSlug,
+ g -> g.getProject().getUuid(), g -> g.getProject().getKey(), g -> g.getProject().getName())
+ .containsExactlyInAnyOrder(
+ tuple("BANANA-UUID", "banana", "banana", "BANANA-PROJECT-UUID", "HOY", "hoy"),
+ tuple("POTATO-UUID", "potato", "potato", "POTATO-PROJECT-UUID", "HEY", "hey"));
+ }
+
+ @Test
+ public void bbc_object_serialization_deserialization() {
+ Project project = new Project("PROJECT-UUID-ONE", "projectKey", "projectName");
+ Repository repository = new Repository("REPO-UUID-ONE", "repo-slug", "repoName", project);
+ RepositoryList repos = new RepositoryList(null, asList(repository), 1, 100);
+ server.enqueue(new MockResponse()
+ .setHeader("Content-Type", "application/json;charset=UTF-8")
+ .setBody(new Gson().toJson(repos)));
+
+ RepositoryList repositoryList = underTest.searchRepos("user:apppwd", "", null, 1, 100);
+ assertThat(repositoryList.getNext()).isNull();
+ assertThat(repositoryList.getPage()).isEqualTo(1);
+ assertThat(repositoryList.getPagelen()).isEqualTo(100);
+ assertThat(repositoryList.getValues())
+ .hasSize(1)
+ .extracting(Repository::getUuid, Repository::getName, Repository::getSlug,
+ g -> g.getProject().getUuid(), g -> g.getProject().getKey(), g -> g.getProject().getName())
+ .containsExactlyInAnyOrder(
+ tuple("REPO-UUID-ONE", "repoName", "repo-slug", "PROJECT-UUID-ONE", "projectKey", "projectName"));
+ }
+
+ @Test
public void failIfUnauthorized() {
server.enqueue(new MockResponse().setResponseCode(401).setBody("Unauthorized"));
diff --git a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/almintegration/ws/AlmIntegrationsWSModule.java b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/almintegration/ws/AlmIntegrationsWSModule.java
index 01e1fa92172..776327931cb 100644
--- a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/almintegration/ws/AlmIntegrationsWSModule.java
+++ b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/almintegration/ws/AlmIntegrationsWSModule.java
@@ -23,6 +23,7 @@ import org.sonar.core.platform.Module;
import org.sonar.server.almintegration.ws.azure.ImportAzureProjectAction;
import org.sonar.server.almintegration.ws.azure.ListAzureProjectsAction;
import org.sonar.server.almintegration.ws.azure.SearchAzureReposAction;
+import org.sonar.server.almintegration.ws.bitbucketcloud.SearchBitbucketCloudReposAction;
import org.sonar.server.almintegration.ws.bitbucketserver.ImportBitbucketServerProjectAction;
import org.sonar.server.almintegration.ws.bitbucketserver.ListBitbucketServerProjectsAction;
import org.sonar.server.almintegration.ws.bitbucketserver.SearchBitbucketServerReposAction;
@@ -42,6 +43,7 @@ public class AlmIntegrationsWSModule extends Module {
ImportBitbucketServerProjectAction.class,
ListBitbucketServerProjectsAction.class,
SearchBitbucketServerReposAction.class,
+ SearchBitbucketCloudReposAction.class,
GetGithubClientIdAction.class,
ImportGithubProjectAction.class,
ListGithubOrganizationsAction.class,
diff --git a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/almintegration/ws/bitbucketcloud/SearchBitbucketCloudReposAction.java b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/almintegration/ws/bitbucketcloud/SearchBitbucketCloudReposAction.java
new file mode 100644
index 00000000000..15267d12951
--- /dev/null
+++ b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/almintegration/ws/bitbucketcloud/SearchBitbucketCloudReposAction.java
@@ -0,0 +1,140 @@
+/*
+ * 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.server.almintegration.ws.bitbucketcloud;
+
+import java.util.List;
+import java.util.Optional;
+import org.sonar.alm.client.bitbucket.bitbucketcloud.BitbucketCloudRestClient;
+import org.sonar.alm.client.bitbucket.bitbucketcloud.Repository;
+import org.sonar.alm.client.bitbucket.bitbucketcloud.RepositoryList;
+import org.sonar.api.server.ws.Request;
+import org.sonar.api.server.ws.Response;
+import org.sonar.api.server.ws.WebService;
+import org.sonar.db.DbClient;
+import org.sonar.db.DbSession;
+import org.sonar.db.alm.pat.AlmPatDto;
+import org.sonar.db.alm.setting.AlmSettingDto;
+import org.sonar.server.almintegration.ws.AlmIntegrationsWsAction;
+import org.sonar.server.exceptions.NotFoundException;
+import org.sonar.server.user.UserSession;
+import org.sonarqube.ws.AlmIntegrations.BBCRepo;
+import org.sonarqube.ws.AlmIntegrations.SearchBitbucketcloudReposWsResponse;
+import org.sonarqube.ws.Common;
+
+import static java.util.Objects.requireNonNull;
+import static java.util.Optional.ofNullable;
+import static java.util.stream.Collectors.toList;
+import static org.sonar.api.server.ws.WebService.Param.PAGE;
+import static org.sonar.api.server.ws.WebService.Param.PAGE_SIZE;
+import static org.sonar.db.permission.GlobalPermission.PROVISION_PROJECTS;
+import static org.sonar.server.ws.WsUtils.writeProtobuf;
+
+public class SearchBitbucketCloudReposAction implements AlmIntegrationsWsAction {
+
+ private static final String PARAM_ALM_SETTING = "almSetting";
+ private static final String PARAM_REPO_NAME = "repositoryName";
+ private static final int DEFAULT_PAGE_SIZE = 20;
+ private static final int MAX_PAGE_SIZE = 100;
+
+ private final DbClient dbClient;
+ private final UserSession userSession;
+ private final BitbucketCloudRestClient bitbucketCloudRestClient;
+
+ public SearchBitbucketCloudReposAction(DbClient dbClient, UserSession userSession,
+ BitbucketCloudRestClient bitbucketCloudRestClient) {
+ this.dbClient = dbClient;
+ this.userSession = userSession;
+ this.bitbucketCloudRestClient = bitbucketCloudRestClient;
+ }
+
+ @Override
+ public void define(WebService.NewController context) {
+ WebService.NewAction action = context.createAction("search_bitbucketcloud_repos")
+ .setDescription("Search the Bitbucket Cloud repositories<br/>" +
+ "Requires the 'Create Projects' permission")
+ .setPost(false)
+ .setSince("9.0")
+ .setHandler(this);
+
+ action.createParam(PARAM_ALM_SETTING)
+ .setRequired(true)
+ .setMaximumLength(200)
+ .setDescription("ALM setting key");
+
+ action.createParam(PARAM_REPO_NAME)
+ .setRequired(false)
+ .setMaximumLength(200)
+ .setDescription("Repository name filter");
+
+ action.addPagingParams(DEFAULT_PAGE_SIZE, MAX_PAGE_SIZE);
+ }
+
+ @Override
+ public void handle(Request request, Response response) {
+ SearchBitbucketcloudReposWsResponse wsResponse = doHandle(request);
+ writeProtobuf(wsResponse, request, response);
+ }
+
+ private SearchBitbucketcloudReposWsResponse doHandle(Request request) {
+ userSession.checkLoggedIn().checkPermission(PROVISION_PROJECTS);
+ String almSettingKey = request.mandatoryParam(PARAM_ALM_SETTING);
+ String repoName = request.param(PARAM_REPO_NAME);
+ int page = request.mandatoryParamAsInt(PAGE);
+ int pageSize = request.mandatoryParamAsInt(PAGE_SIZE);
+
+ try (DbSession dbSession = dbClient.openSession(false)) {
+ AlmSettingDto almSettingDto = dbClient.almSettingDao().selectByKey(dbSession, almSettingKey)
+ .orElseThrow(() -> new NotFoundException(String.format("ALM Setting '%s' not found", almSettingKey)));
+
+ String workspace = ofNullable(almSettingDto.getAppId())
+ .orElseThrow(() -> new IllegalArgumentException(String.format("workspace for alm setting %s is missing", almSettingDto.getKey())));
+ String userUuid = requireNonNull(userSession.getUuid(), "User UUID cannot be null");
+ Optional<AlmPatDto> almPatDto = dbClient.almPatDao().selectByUserAndAlmSetting(dbSession, userUuid, almSettingDto);
+
+ String pat = almPatDto.map(AlmPatDto::getPersonalAccessToken).orElseThrow(() -> new IllegalArgumentException("No personal access token found"));
+
+ RepositoryList repositoryList = bitbucketCloudRestClient.searchRepos(pat, workspace, repoName, page, pageSize);
+
+ List<BBCRepo> bbcRepos = repositoryList.getValues().stream()
+ .map(repository -> toBBCRepo(repository, workspace))
+ .collect(toList());
+
+ SearchBitbucketcloudReposWsResponse.Builder builder = SearchBitbucketcloudReposWsResponse.newBuilder()
+ .setIsLastPage(repositoryList.getNext() == null)
+ .setPaging(Common.Paging.newBuilder()
+ .setPageIndex(page)
+ .setPageSize(pageSize)
+ .build())
+ .addAllRepositories(bbcRepos);
+ return builder.build();
+ }
+ }
+
+ private static BBCRepo toBBCRepo(Repository gsonBBCRepo, String workspace) {
+ return BBCRepo.newBuilder()
+ .setSlug(gsonBBCRepo.getSlug())
+ .setUuid(gsonBBCRepo.getUuid())
+ .setName(gsonBBCRepo.getName())
+ .setWorkspace(workspace)
+ .setProjectKey(gsonBBCRepo.getProject().getKey())
+ .build();
+ }
+
+}
diff --git a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/almintegration/ws/bitbucketcloud/package-info.java b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/almintegration/ws/bitbucketcloud/package-info.java
new file mode 100644
index 00000000000..f9398c22ced
--- /dev/null
+++ b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/almintegration/ws/bitbucketcloud/package-info.java
@@ -0,0 +1,23 @@
+/*
+ * 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.
+ */
+@ParametersAreNonnullByDefault
+package org.sonar.server.almintegration.ws.bitbucketcloud;
+
+import javax.annotation.ParametersAreNonnullByDefault;
diff --git a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/almintegration/ws/bitbucketserver/SearchBitbucketServerReposAction.java b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/almintegration/ws/bitbucketserver/SearchBitbucketServerReposAction.java
index aa009e10c3c..f9a295db803 100644
--- a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/almintegration/ws/bitbucketserver/SearchBitbucketServerReposAction.java
+++ b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/almintegration/ws/bitbucketserver/SearchBitbucketServerReposAction.java
@@ -125,8 +125,6 @@ public class SearchBitbucketServerReposAction implements AlmIntegrationsWsAction
String url = requireNonNull(almSettingDto.getUrl(), "ALM url cannot be null");
RepositoryList gsonBBSRepoList = bitbucketServerRestClient.getRepos(url, pat, projectKey, repoName);
- LOG.info(gsonBBSRepoList.toString());
-
Map<String, String> sqProjectsKeyByBBSKey = getSqProjectsKeyByBBSKey(dbSession, almSettingDto, gsonBBSRepoList);
List<BBSRepo> bbsRepos = gsonBBSRepoList.getValues().stream().map(gsonBBSRepo -> toBBSRepo(gsonBBSRepo, sqProjectsKeyByBBSKey))
.collect(toList());
diff --git a/server/sonar-webserver-webapi/src/test/java/org/sonar/server/almintegration/ws/bitbucketcloud/SearchBitbucketCloudReposActionTest.java b/server/sonar-webserver-webapi/src/test/java/org/sonar/server/almintegration/ws/bitbucketcloud/SearchBitbucketCloudReposActionTest.java
new file mode 100644
index 00000000000..be24d49f87a
--- /dev/null
+++ b/server/sonar-webserver-webapi/src/test/java/org/sonar/server/almintegration/ws/bitbucketcloud/SearchBitbucketCloudReposActionTest.java
@@ -0,0 +1,181 @@
+/*
+ * 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.server.almintegration.ws.bitbucketcloud;
+
+import org.jetbrains.annotations.NotNull;
+import org.junit.Rule;
+import org.junit.Test;
+import org.sonar.alm.client.bitbucket.bitbucketcloud.BitbucketCloudRestClient;
+import org.sonar.alm.client.bitbucket.bitbucketcloud.Project;
+import org.sonar.alm.client.bitbucket.bitbucketcloud.Repository;
+import org.sonar.alm.client.bitbucket.bitbucketcloud.RepositoryList;
+import org.sonar.api.server.ws.WebService;
+import org.sonar.db.DbTester;
+import org.sonar.db.alm.pat.AlmPatDto;
+import org.sonar.db.alm.setting.AlmSettingDto;
+import org.sonar.db.project.ProjectDto;
+import org.sonar.db.user.UserDto;
+import org.sonar.server.exceptions.ForbiddenException;
+import org.sonar.server.exceptions.NotFoundException;
+import org.sonar.server.exceptions.UnauthorizedException;
+import org.sonar.server.tester.UserSessionRule;
+import org.sonar.server.ws.TestRequest;
+import org.sonar.server.ws.WsActionTester;
+import org.sonarqube.ws.AlmIntegrations.BBCRepo;
+import org.sonarqube.ws.AlmIntegrations.SearchBitbucketcloudReposWsResponse;
+
+import static java.util.Arrays.asList;
+import static java.util.Collections.emptyList;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatThrownBy;
+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.when;
+import static org.sonar.api.server.ws.WebService.Param.PAGE;
+import static org.sonar.api.server.ws.WebService.Param.PAGE_SIZE;
+import static org.sonar.db.alm.integration.pat.AlmPatsTesting.newAlmPatDto;
+import static org.sonar.db.permission.GlobalPermission.PROVISION_PROJECTS;
+
+public class SearchBitbucketCloudReposActionTest {
+ @Rule
+ public UserSessionRule userSession = UserSessionRule.standalone();
+ @Rule
+ public DbTester db = DbTester.create();
+
+ private final BitbucketCloudRestClient bitbucketCloudRestClient = mock(BitbucketCloudRestClient.class);
+
+ private final WsActionTester ws = new WsActionTester(
+ new SearchBitbucketCloudReposAction(db.getDbClient(), userSession, bitbucketCloudRestClient));
+
+ @Test
+ public void list_repos() {
+ when(bitbucketCloudRestClient.searchRepos(any(), any(), any(), any(), any())).thenReturn(getRepositoryList());
+ 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.setPersonalAccessToken("abc:xyz");
+ dto.setUserUuid(user.getUuid());
+ });
+
+ SearchBitbucketcloudReposWsResponse response = ws.newRequest()
+ .setParam("almSetting", almSetting.getKey())
+ .setParam("p", "1")
+ .setParam("ps", "100")
+ .executeProtobuf(SearchBitbucketcloudReposWsResponse.class);
+
+ assertThat(response.getIsLastPage()).isFalse();
+ assertThat(response.getPaging().getPageIndex()).isEqualTo(1);
+ assertThat(response.getPaging().getPageSize()).isEqualTo(100);
+ assertThat(response.getRepositoriesList())
+ .extracting(BBCRepo::getUuid, BBCRepo::getName, BBCRepo::getSlug, BBCRepo::getProjectKey, BBCRepo::getWorkspace)
+ .containsExactlyInAnyOrder(
+ tuple("REPO-UUID-ONE", "repoName1", "repo-slug-1", "projectKey1", almSetting.getAppId()));
+ }
+
+ @Test
+ public void return_empty_list_when_no_bbs_repo() {
+ RepositoryList repositoryList = new RepositoryList(null, emptyList(), 1, 100);
+ when(bitbucketCloudRestClient.searchRepos(any(), any(), any(), any(), any())).thenReturn(repositoryList);
+ 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.setPersonalAccessToken("abc:xyz");
+ dto.setUserUuid(user.getUuid());
+ });
+ ProjectDto projectDto = db.components().insertPrivateProjectDto();
+ db.almSettings().insertBitbucketCloudProjectAlmSetting(almSetting, projectDto, s -> s.setAlmRepo("projectKey2"), s -> s.setAlmSlug("repo-slug-2"));
+
+ SearchBitbucketcloudReposWsResponse response = ws.newRequest()
+ .setParam("almSetting", almSetting.getKey())
+ .executeProtobuf(SearchBitbucketcloudReposWsResponse.class);
+
+ assertThat(response.getIsLastPage()).isTrue();
+ assertThat(response.getRepositoriesList()).isEmpty();
+ }
+
+ @Test
+ public void fail_check_pat_alm_setting_not_found() {
+ UserDto user = db.users().insertUser();
+ userSession.logIn(user).addPermission(PROVISION_PROJECTS);
+ AlmPatDto almPatDto = newAlmPatDto();
+ db.getDbClient().almPatDao().insert(db.getSession(), almPatDto);
+
+ TestRequest request = ws.newRequest().setParam("almSetting", "testKey");
+
+ assertThatThrownBy(request::execute)
+ .isInstanceOf(NotFoundException.class)
+ .hasMessageContaining("ALM Setting 'testKey' not found");
+ }
+
+ @Test
+ public void fail_when_not_logged_in() {
+ TestRequest request = ws.newRequest().setParam("almSetting", "anyvalue");
+
+ assertThatThrownBy(request::execute)
+ .isInstanceOf(UnauthorizedException.class);
+ }
+
+ @Test
+ public void fail_when_no_creation_project_permission() {
+ UserDto user = db.users().insertUser();
+ userSession.logIn(user);
+
+ TestRequest request = ws.newRequest().setParam("almSetting", "anyvalue");
+
+ assertThatThrownBy(request::execute)
+ .isInstanceOf(ForbiddenException.class)
+ .hasMessageContaining("Insufficient privileges");
+ }
+
+ @Test
+ public void definition() {
+ WebService.Action def = ws.getDef();
+
+ assertThat(def.since()).isEqualTo("9.0");
+ assertThat(def.isPost()).isFalse();
+ assertThat(def.params())
+ .extracting(WebService.Param::key, WebService.Param::isRequired)
+ .containsExactlyInAnyOrder(
+ tuple("almSetting", true),
+ tuple("repositoryName", false),
+ tuple(PAGE, false),
+ tuple(PAGE_SIZE, false));
+ }
+
+ @NotNull
+ private RepositoryList getRepositoryList() {
+ return new RepositoryList(
+ "http://next.url",
+ asList(getBBCRepo1()),
+ 1,
+ 100);
+ }
+
+ private Repository getBBCRepo1() {
+ Project project1 = new Project("PROJECT-UUID-ONE", "projectKey1", "projectName1");
+ return new Repository("REPO-UUID-ONE", "repo-slug-1", "repoName1", project1);
+ }
+
+}
diff --git a/sonar-ws/src/main/protobuf/ws-alm_integrations.proto b/sonar-ws/src/main/protobuf/ws-alm_integrations.proto
index 68ad17281da..465293d5492 100644
--- a/sonar-ws/src/main/protobuf/ws-alm_integrations.proto
+++ b/sonar-ws/src/main/protobuf/ws-alm_integrations.proto
@@ -32,6 +32,13 @@ message SearchBitbucketserverReposWsResponse {
repeated BBSRepo repositories = 2;
}
+// WS api/alm_integrations/search_bibucketcloud_repos
+message SearchBitbucketcloudReposWsResponse {
+ optional sonarqube.ws.commons.Paging paging = 1;
+ optional bool isLastPage = 2;
+ repeated BBCRepo repositories = 3;
+}
+
// WS api/alm_integrations/search_azure_repos
message SearchAzureReposWsResponse {
repeated AzureRepo repositories = 2;
@@ -55,6 +62,15 @@ message BBSRepo {
optional string projectKey = 5;
}
+message BBCRepo {
+ optional string slug = 1;
+ optional string uuid = 2;
+ optional string name = 3;
+ optional string sqProjectKey = 4;
+ optional string projectKey = 5;
+ optional string workspace = 6;
+}
+
message AzureRepo {
optional string name = 1;
optional string projectName = 2;