]> source.dussan.org Git - sonarqube.git/blob
65553c26617f035aa9b95a2ec965ddd3b7708d42
[sonarqube.git] /
1 /*
2  * SonarQube
3  * Copyright (C) 2009-2024 SonarSource SA
4  * mailto:info AT sonarsource DOT com
5  *
6  * This program is free software; you can redistribute it and/or
7  * modify it under the terms of the GNU Lesser General Public
8  * License as published by the Free Software Foundation; either
9  * version 3 of the License, or (at your option) any later version.
10  *
11  * This program is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14  * Lesser General Public License for more details.
15  *
16  * You should have received a copy of the GNU Lesser General Public License
17  * along with this program; if not, write to the Free Software Foundation,
18  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
19  */
20 package org.sonar.server.almintegration.ws.gitlab;
21
22 import java.util.List;
23 import java.util.Map;
24 import java.util.Optional;
25 import java.util.Set;
26 import java.util.function.BinaryOperator;
27 import java.util.function.Function;
28 import java.util.stream.Collectors;
29 import org.sonar.alm.client.gitlab.GitlabApplicationClient;
30 import org.sonar.alm.client.gitlab.Project;
31 import org.sonar.alm.client.gitlab.ProjectList;
32 import org.sonar.api.server.ws.Request;
33 import org.sonar.api.server.ws.Response;
34 import org.sonar.api.server.ws.WebService;
35 import org.sonar.db.DbClient;
36 import org.sonar.db.DbSession;
37 import org.sonar.db.alm.pat.AlmPatDto;
38 import org.sonar.db.alm.setting.AlmSettingDto;
39 import org.sonar.db.alm.setting.ProjectAlmSettingDto;
40 import org.sonar.server.almintegration.ws.AlmIntegrationsWsAction;
41 import org.sonar.server.exceptions.NotFoundException;
42 import org.sonar.server.user.UserSession;
43 import org.sonarqube.ws.AlmIntegrations;
44 import org.sonarqube.ws.AlmIntegrations.GitlabRepository;
45 import org.sonarqube.ws.Common.Paging;
46
47 import static java.util.Objects.requireNonNull;
48 import static java.util.stream.Collectors.toSet;
49 import static org.sonar.db.permission.GlobalPermission.PROVISION_PROJECTS;
50 import static org.sonar.server.ws.WsUtils.writeProtobuf;
51
52 public class SearchGitlabReposAction implements AlmIntegrationsWsAction {
53
54   private static final String PARAM_ALM_SETTING = "almSetting";
55   private static final String PARAM_PROJECT_NAME = "projectName";
56   private static final int DEFAULT_PAGE_SIZE = 20;
57   private static final int MAX_PAGE_SIZE = 500;
58
59   private final DbClient dbClient;
60   private final UserSession userSession;
61   private final GitlabApplicationClient gitlabApplicationClient;
62
63   public SearchGitlabReposAction(DbClient dbClient, UserSession userSession, GitlabApplicationClient gitlabApplicationClient) {
64     this.dbClient = dbClient;
65     this.userSession = userSession;
66     this.gitlabApplicationClient = gitlabApplicationClient;
67   }
68
69   @Override
70   public void define(WebService.NewController context) {
71     WebService.NewAction action = context.createAction("search_gitlab_repos")
72       .setDescription("Search the GitLab projects.<br/>" +
73         "Requires the 'Create Projects' permission")
74       .setPost(false)
75       .setSince("8.5")
76       .setHandler(this);
77
78     action.createParam(PARAM_ALM_SETTING)
79       .setRequired(true)
80       .setMaximumLength(200)
81       .setDescription("DevOps Platform setting key");
82     action.createParam(PARAM_PROJECT_NAME)
83       .setRequired(false)
84       .setMaximumLength(200)
85       .setDescription("Project name filter");
86
87     action.addPagingParams(DEFAULT_PAGE_SIZE, MAX_PAGE_SIZE);
88
89     action.setResponseExample(getClass().getResource("search_gitlab_repos.json"));
90   }
91
92   @Override
93   public void handle(Request request, Response response) {
94     AlmIntegrations.SearchGitlabReposWsResponse wsResponse = doHandle(request);
95     writeProtobuf(wsResponse, request, response);
96   }
97
98   private AlmIntegrations.SearchGitlabReposWsResponse doHandle(Request request) {
99     String almSettingKey = request.mandatoryParam(PARAM_ALM_SETTING);
100     String projectName = request.param(PARAM_PROJECT_NAME);
101
102     int pageNumber = request.mandatoryParamAsInt("p");
103     int pageSize = request.mandatoryParamAsInt("ps");
104
105     try (DbSession dbSession = dbClient.openSession(false)) {
106       userSession.checkLoggedIn().checkPermission(PROVISION_PROJECTS);
107
108       String userUuid = requireNonNull(userSession.getUuid(), "User UUID cannot be null");
109       AlmSettingDto almSettingDto = dbClient.almSettingDao().selectByKey(dbSession, almSettingKey)
110         .orElseThrow(() -> new NotFoundException(String.format("DevOps Platform Setting '%s' not found", almSettingKey)));
111       Optional<AlmPatDto> almPatDto = dbClient.almPatDao().selectByUserAndAlmSetting(dbSession, userUuid, almSettingDto);
112
113       String personalAccessToken = almPatDto.map(AlmPatDto::getPersonalAccessToken).orElseThrow(() -> new IllegalArgumentException("No personal access token found"));
114       String gitlabUrl = requireNonNull(almSettingDto.getUrl(), "DevOps Platform url cannot be null");
115
116       ProjectList gitlabProjectList = gitlabApplicationClient
117         .searchProjects(gitlabUrl, personalAccessToken, projectName, pageNumber, pageSize);
118
119       Map<String, ProjectKeyName> sqProjectsKeyByGitlabProjectId = getSqProjectsKeyByGitlabProjectId(dbSession, almSettingDto, gitlabProjectList);
120
121       List<GitlabRepository> gitlabRepositories = gitlabProjectList.getProjects().stream()
122         .map(project -> toGitlabRepository(project, sqProjectsKeyByGitlabProjectId))
123         .toList();
124
125       Paging.Builder pagingBuilder = Paging.newBuilder()
126         .setPageIndex(gitlabProjectList.getPageNumber())
127         .setPageSize(gitlabProjectList.getPageSize());
128       Integer gitlabProjectListTotal = gitlabProjectList.getTotal();
129       if (gitlabProjectListTotal != null) {
130         pagingBuilder.setTotal(gitlabProjectListTotal);
131       }
132       return AlmIntegrations.SearchGitlabReposWsResponse.newBuilder()
133         .addAllRepositories(gitlabRepositories)
134         .setPaging(pagingBuilder.build())
135         .build();
136     }
137   }
138
139   private Map<String, ProjectKeyName> getSqProjectsKeyByGitlabProjectId(DbSession dbSession, AlmSettingDto almSettingDto,
140     ProjectList gitlabProjectList) {
141     Set<String> gitlabProjectIds = gitlabProjectList.getProjects().stream().map(Project::getId).map(String::valueOf)
142       .collect(toSet());
143     Map<String, ProjectAlmSettingDto> projectAlmSettingDtos = dbClient.projectAlmSettingDao()
144       .selectByAlmSettingAndRepos(dbSession, almSettingDto, gitlabProjectIds)
145       .stream().collect(Collectors.toMap(ProjectAlmSettingDto::getProjectUuid, Function.identity()));
146
147     return dbClient.projectDao().selectByUuids(dbSession, projectAlmSettingDtos.keySet())
148       .stream()
149       .collect(Collectors.toMap(projectDto -> projectAlmSettingDtos.get(projectDto.getUuid()).getAlmRepo(),
150         p -> new ProjectKeyName(p.getKey(), p.getName()), resolveNameCollisionOperatorByNaturalOrder()));
151   }
152
153   private static BinaryOperator<ProjectKeyName> resolveNameCollisionOperatorByNaturalOrder() {
154     return (a, b) -> b.key.compareTo(a.key) > 0 ? a : b;
155   }
156
157   private static GitlabRepository toGitlabRepository(Project project, Map<String, ProjectKeyName> sqProjectsKeyByGitlabProjectId) {
158     String name = project.getName();
159     String pathName = removeLastOccurrenceOfString(project.getNameWithNamespace(), " / " + name);
160
161     String slug = project.getPath();
162     String pathSlug = removeLastOccurrenceOfString(project.getPathWithNamespace(), "/" + slug);
163
164     GitlabRepository.Builder builder = GitlabRepository.newBuilder()
165       .setId(project.getId())
166       .setName(name)
167       .setPathName(pathName)
168       .setSlug(slug)
169       .setPathSlug(pathSlug)
170       .setUrl(project.getWebUrl());
171
172     String projectIdAsString = String.valueOf(project.getId());
173     Optional.ofNullable(sqProjectsKeyByGitlabProjectId.get(projectIdAsString))
174       .ifPresent(p -> builder
175         .setSqProjectKey(p.key)
176         .setSqProjectName(p.name));
177
178     return builder.build();
179   }
180
181   private static String removeLastOccurrenceOfString(String string, String stringToRemove) {
182     StringBuilder resultString = new StringBuilder(string);
183     int index = resultString.lastIndexOf(stringToRemove);
184     if (index > -1) {
185       resultString.delete(index, string.length() + index);
186     }
187     return resultString.toString();
188   }
189
190   static class ProjectKeyName {
191     String key;
192     String name;
193
194     ProjectKeyName(String key, String name) {
195       this.key = key;
196       this.name = name;
197     }
198   }
199 }