]> source.dussan.org Git - sonarqube.git/blob
5b31a34239cb4f6dadccbce7a49ef9fb1d6ebfe9
[sonarqube.git] /
1 /*
2  * SonarQube
3  * Copyright (C) 2009-2020 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.qualitygate.ws;
21
22 import java.util.Arrays;
23 import java.util.Optional;
24 import java.util.stream.Collectors;
25 import javax.annotation.Nullable;
26 import javax.annotation.concurrent.Immutable;
27 import org.sonar.api.measures.CoreMetrics;
28 import org.sonar.api.server.ws.Change;
29 import org.sonar.api.server.ws.Request;
30 import org.sonar.api.server.ws.Response;
31 import org.sonar.api.server.ws.WebService;
32 import org.sonar.api.web.UserRole;
33 import org.sonar.core.util.Uuids;
34 import org.sonar.db.DbClient;
35 import org.sonar.db.DbSession;
36 import org.sonar.db.component.BranchDto;
37 import org.sonar.db.component.SnapshotDto;
38 import org.sonar.db.measure.LiveMeasureDto;
39 import org.sonar.db.measure.MeasureDto;
40 import org.sonar.db.project.ProjectDto;
41 import org.sonar.server.component.ComponentFinder;
42 import org.sonar.server.exceptions.BadRequestException;
43 import org.sonar.server.user.UserSession;
44 import org.sonar.server.ws.KeyExamples;
45 import org.sonarqube.ws.Qualitygates.ProjectStatusResponse;
46
47 import static com.google.common.base.Strings.isNullOrEmpty;
48 import static org.sonar.server.exceptions.BadRequestException.checkRequest;
49 import static org.sonar.server.exceptions.NotFoundException.checkFoundWithOptional;
50 import static org.sonar.server.qualitygate.ws.QualityGatesWsParameters.ACTION_PROJECT_STATUS;
51 import static org.sonar.server.qualitygate.ws.QualityGatesWsParameters.PARAM_ANALYSIS_ID;
52 import static org.sonar.server.qualitygate.ws.QualityGatesWsParameters.PARAM_BRANCH;
53 import static org.sonar.server.qualitygate.ws.QualityGatesWsParameters.PARAM_PROJECT_ID;
54 import static org.sonar.server.qualitygate.ws.QualityGatesWsParameters.PARAM_PROJECT_KEY;
55 import static org.sonar.server.qualitygate.ws.QualityGatesWsParameters.PARAM_PULL_REQUEST;
56 import static org.sonar.server.user.AbstractUserSession.insufficientPrivilegesException;
57 import static org.sonar.server.ws.WsUtils.writeProtobuf;
58
59 public class ProjectStatusAction implements QualityGatesWsAction {
60   private static final String QG_STATUSES_ONE_LINE = Arrays.stream(ProjectStatusResponse.Status.values())
61     .map(Enum::toString)
62     .collect(Collectors.joining(", "));
63   private static final String MSG_ONE_PROJECT_PARAMETER_ONLY = String.format("Either '%s', '%s' or '%s' must be provided", PARAM_ANALYSIS_ID, PARAM_PROJECT_ID, PARAM_PROJECT_KEY);
64   private static final String MSG_ONE_BRANCH_PARAMETER_ONLY = String.format("Either '%s' or '%s' can be provided, not both", PARAM_BRANCH, PARAM_PULL_REQUEST);
65
66   private final DbClient dbClient;
67   private final ComponentFinder componentFinder;
68   private final UserSession userSession;
69
70   public ProjectStatusAction(DbClient dbClient, ComponentFinder componentFinder, UserSession userSession) {
71     this.dbClient = dbClient;
72     this.componentFinder = componentFinder;
73     this.userSession = userSession;
74   }
75
76   @Override
77   public void define(WebService.NewController controller) {
78     WebService.NewAction action = controller.createAction(ACTION_PROJECT_STATUS)
79       .setDescription(String.format("Get the quality gate status of a project or a Compute Engine task.<br />" +
80         MSG_ONE_PROJECT_PARAMETER_ONLY + "<br />" +
81         "The different statuses returned are: %s. The %s status is returned when there is no quality gate associated with the analysis.<br />" +
82         "Returns an HTTP code 404 if the analysis associated with the task is not found or does not exist.<br />" +
83         "Requires one of the following permissions:" +
84         "<ul>" +
85         "<li>'Administer System'</li>" +
86         "<li>'Administer' rights on the specified project</li>" +
87         "<li>'Browse' on the specified project</li>" +
88         "</ul>", QG_STATUSES_ONE_LINE, ProjectStatusResponse.Status.NONE))
89       .setResponseExample(getClass().getResource("project_status-example.json"))
90       .setSince("5.3")
91       .setHandler(this)
92       .setChangelog(
93         new Change("7.7", "The parameters 'branch' and 'pullRequest' were added"),
94         new Change("7.6", "The field 'warning' is deprecated from the response"),
95         new Change("6.4", "The field 'ignoredConditions' is added to the response"));
96
97     action.createParam(PARAM_ANALYSIS_ID)
98       .setDescription("Analysis id")
99       .setExampleValue(Uuids.UUID_EXAMPLE_04);
100
101     action.createParam(PARAM_PROJECT_ID)
102       .setSince("5.4")
103       .setDescription("Project UUID. Doesn't work with branches or pull requests")
104       .setExampleValue(Uuids.UUID_EXAMPLE_01);
105
106     action.createParam(PARAM_PROJECT_KEY)
107       .setSince("5.4")
108       .setDescription("Project key")
109       .setExampleValue(KeyExamples.KEY_PROJECT_EXAMPLE_001);
110
111     action.createParam(PARAM_BRANCH)
112       .setSince("7.7")
113       .setDescription("Branch key")
114       .setExampleValue(KeyExamples.KEY_BRANCH_EXAMPLE_001);
115
116     action.createParam(PARAM_PULL_REQUEST)
117       .setSince("7.7")
118       .setDescription("Pull request id")
119       .setExampleValue(KeyExamples.KEY_PULL_REQUEST_EXAMPLE_001);
120   }
121
122   @Override
123   public void handle(Request request, Response response) throws Exception {
124     String analysisId = request.param(PARAM_ANALYSIS_ID);
125     String projectId = request.param(PARAM_PROJECT_ID);
126     String projectKey = request.param(PARAM_PROJECT_KEY);
127     String branchKey = request.param(PARAM_BRANCH);
128     String pullRequestId = request.param(PARAM_PULL_REQUEST);
129     checkRequest(
130       !isNullOrEmpty(analysisId)
131         ^ !isNullOrEmpty(projectId)
132         ^ !isNullOrEmpty(projectKey),
133       MSG_ONE_PROJECT_PARAMETER_ONLY);
134     checkRequest(isNullOrEmpty(branchKey) || isNullOrEmpty(pullRequestId), MSG_ONE_BRANCH_PARAMETER_ONLY);
135
136     try (DbSession dbSession = dbClient.openSession(false)) {
137       ProjectStatusResponse projectStatusResponse = doHandle(dbSession, analysisId, projectId, projectKey, branchKey, pullRequestId);
138       writeProtobuf(projectStatusResponse, request, response);
139     }
140   }
141
142   private ProjectStatusResponse doHandle(DbSession dbSession, @Nullable String analysisId, @Nullable String projectUuid,
143     @Nullable String projectKey, @Nullable String branchKey, @Nullable String pullRequestId) {
144     ProjectAndSnapshot projectAndSnapshot = getProjectAndSnapshot(dbSession, analysisId, projectUuid, projectKey, branchKey, pullRequestId);
145     checkPermission(projectAndSnapshot.project);
146     Optional<String> measureData = loadQualityGateDetails(dbSession, projectAndSnapshot, analysisId != null);
147
148     return ProjectStatusResponse.newBuilder()
149       .setProjectStatus(new QualityGateDetailsFormatter(measureData, projectAndSnapshot.snapshotDto).format())
150       .build();
151   }
152
153   private ProjectAndSnapshot getProjectAndSnapshot(DbSession dbSession, @Nullable String analysisId, @Nullable String projectUuid,
154     @Nullable String projectKey, @Nullable String branchKey, @Nullable String pullRequestId) {
155     if (!isNullOrEmpty(analysisId)) {
156       return getSnapshotThenProject(dbSession, analysisId);
157     }
158     if (!isNullOrEmpty(projectUuid) ^ !isNullOrEmpty(projectKey)) {
159       return getProjectThenSnapshot(dbSession, projectUuid, projectKey, branchKey, pullRequestId);
160     }
161
162     throw BadRequestException.create(MSG_ONE_PROJECT_PARAMETER_ONLY);
163   }
164
165   private ProjectAndSnapshot getProjectThenSnapshot(DbSession dbSession, @Nullable String projectUuid, @Nullable String projectKey,
166     @Nullable String branchKey, @Nullable String pullRequestId) {
167     ProjectDto projectDto;
168     BranchDto branchDto;
169
170     if (projectUuid != null) {
171       projectDto = componentFinder.getProjectByUuid(dbSession, projectUuid);
172       branchDto = componentFinder.getMainBranch(dbSession, projectDto);
173     } else {
174       projectDto = componentFinder.getProjectByKey(dbSession, projectKey);
175       branchDto = componentFinder.getBranchOrPullRequest(dbSession, projectDto, branchKey, pullRequestId);
176     }
177     Optional<SnapshotDto> snapshot = dbClient.snapshotDao().selectLastAnalysisByRootComponentUuid(dbSession, branchDto.getUuid());
178     return new ProjectAndSnapshot(projectDto, branchDto, snapshot.orElse(null));
179   }
180
181   private ProjectAndSnapshot getSnapshotThenProject(DbSession dbSession, String analysisUuid) {
182     SnapshotDto snapshotDto = getSnapshot(dbSession, analysisUuid);
183     BranchDto branchDto = dbClient.branchDao().selectByUuid(dbSession, snapshotDto.getComponentUuid())
184       .orElseThrow(() -> new IllegalStateException(String.format("Branch '%s' not found", snapshotDto.getUuid())));
185     ProjectDto projectDto = dbClient.projectDao().selectByUuid(dbSession, branchDto.getProjectUuid())
186       .orElseThrow(() -> new IllegalStateException(String.format("Project '%s' not found", branchDto.getProjectUuid())));
187
188     return new ProjectAndSnapshot(projectDto, branchDto, snapshotDto);
189   }
190
191   private SnapshotDto getSnapshot(DbSession dbSession, String analysisUuid) {
192     Optional<SnapshotDto> snapshotDto = dbClient.snapshotDao().selectByUuid(dbSession, analysisUuid);
193     return checkFoundWithOptional(snapshotDto, "Analysis with id '%s' is not found", analysisUuid);
194   }
195
196   private Optional<String> loadQualityGateDetails(DbSession dbSession, ProjectAndSnapshot projectAndSnapshot, boolean onAnalysis) {
197     if (onAnalysis) {
198       if (!projectAndSnapshot.snapshotDto.isPresent()) {
199         return Optional.empty();
200       }
201       // get the gate status as it was computed during the specified analysis
202       String analysisUuid = projectAndSnapshot.snapshotDto.get().getUuid();
203       return dbClient.measureDao().selectMeasure(dbSession, analysisUuid, projectAndSnapshot.branch.getUuid(), CoreMetrics.QUALITY_GATE_DETAILS_KEY)
204         .map(MeasureDto::getData);
205     }
206
207     // do not restrict to a specified analysis, use the live measure
208     Optional<LiveMeasureDto> measure = dbClient.liveMeasureDao().selectMeasure(dbSession, projectAndSnapshot.branch.getUuid(), CoreMetrics.QUALITY_GATE_DETAILS_KEY);
209     return measure.map(LiveMeasureDto::getDataAsString);
210   }
211
212   private void checkPermission(ProjectDto project) {
213     if (!userSession.hasProjectPermission(UserRole.ADMIN, project) &&
214       !userSession.hasProjectPermission(UserRole.USER, project)) {
215       throw insufficientPrivilegesException();
216     }
217   }
218
219   @Immutable
220   private static class ProjectAndSnapshot {
221     private final BranchDto branch;
222     private final Optional<SnapshotDto> snapshotDto;
223     private final ProjectDto project;
224
225     private ProjectAndSnapshot(ProjectDto project, BranchDto branch, @Nullable SnapshotDto snapshotDto) {
226       this.project = project;
227       this.branch = branch;
228       this.snapshotDto = Optional.ofNullable(snapshotDto);
229     }
230   }
231 }