You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

QualityGateCheck.java 6.4KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159
  1. /*
  2. * SonarQube
  3. * Copyright (C) 2009-2022 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.scanner.qualitygate;
  21. import java.io.InputStream;
  22. import java.time.Duration;
  23. import java.time.temporal.ChronoUnit;
  24. import java.util.EnumSet;
  25. import org.sonar.api.Startable;
  26. import org.sonar.api.utils.MessageException;
  27. import org.sonar.api.utils.log.Logger;
  28. import org.sonar.api.utils.log.Loggers;
  29. import org.sonar.scanner.bootstrap.DefaultScannerWsClient;
  30. import org.sonar.scanner.bootstrap.GlobalAnalysisMode;
  31. import org.sonar.scanner.report.CeTaskReportDataHolder;
  32. import org.sonar.scanner.scan.ScanProperties;
  33. import org.sonarqube.ws.Ce;
  34. import org.sonarqube.ws.Ce.TaskStatus;
  35. import org.sonarqube.ws.MediaTypes;
  36. import org.sonarqube.ws.Qualitygates;
  37. import org.sonarqube.ws.Qualitygates.ProjectStatusResponse.Status;
  38. import org.sonarqube.ws.client.GetRequest;
  39. import org.sonarqube.ws.client.HttpException;
  40. import org.sonarqube.ws.client.WsResponse;
  41. public class QualityGateCheck implements Startable {
  42. private static final Logger LOG = Loggers.get(QualityGateCheck.class);
  43. private static final EnumSet<TaskStatus> TASK_TERMINAL_STATUSES = EnumSet.of(TaskStatus.SUCCESS, TaskStatus.FAILED, TaskStatus.CANCELED);
  44. private static final int POLLING_INTERVAL_IN_MS = 5000;
  45. private final DefaultScannerWsClient wsClient;
  46. private final GlobalAnalysisMode analysisMode;
  47. private final CeTaskReportDataHolder ceTaskReportDataHolder;
  48. private final ScanProperties properties;
  49. private long qualityGateTimeoutInMs;
  50. private boolean enabled;
  51. public QualityGateCheck(DefaultScannerWsClient wsClient, GlobalAnalysisMode analysisMode, CeTaskReportDataHolder ceTaskReportDataHolder,
  52. ScanProperties properties) {
  53. this.wsClient = wsClient;
  54. this.properties = properties;
  55. this.ceTaskReportDataHolder = ceTaskReportDataHolder;
  56. this.analysisMode = analysisMode;
  57. }
  58. @Override
  59. public void start() {
  60. this.enabled = properties.shouldWaitForQualityGate();
  61. this.qualityGateTimeoutInMs = Duration.of(properties.qualityGateWaitTimeout(), ChronoUnit.SECONDS).toMillis();
  62. }
  63. @Override
  64. public void stop() {
  65. // nothing to do
  66. }
  67. public void await() {
  68. if (!enabled) {
  69. LOG.debug("Quality Gate check disabled - skipping");
  70. return;
  71. }
  72. if (analysisMode.isMediumTest()) {
  73. throw new IllegalStateException("Quality Gate check not available in medium test mode");
  74. }
  75. LOG.info("Waiting for the analysis report to be processed (max {}s)", properties.qualityGateWaitTimeout());
  76. String taskId = ceTaskReportDataHolder.getCeTaskId();
  77. Ce.Task task = waitForCeTaskToFinish(taskId);
  78. if (!TaskStatus.SUCCESS.equals(task.getStatus())) {
  79. throw MessageException.of(String.format("CE Task finished abnormally with status: %s, you can check details here: %s",
  80. task.getStatus().name(), ceTaskReportDataHolder.getCeTaskUrl()));
  81. }
  82. Status qualityGateStatus = getQualityGateStatus(task.getAnalysisId());
  83. if (Status.OK.equals(qualityGateStatus)) {
  84. LOG.info("QUALITY GATE STATUS: PASSED - View details on " + ceTaskReportDataHolder.getDashboardUrl());
  85. } else {
  86. throw MessageException.of("QUALITY GATE STATUS: FAILED - View details on " + ceTaskReportDataHolder.getDashboardUrl());
  87. }
  88. }
  89. private Ce.Task waitForCeTaskToFinish(String taskId) {
  90. GetRequest getTaskResultReq = new GetRequest("api/ce/task")
  91. .setMediaType(MediaTypes.PROTOBUF)
  92. .setParam("id", taskId);
  93. long currentTime = 0;
  94. while (qualityGateTimeoutInMs > currentTime) {
  95. try {
  96. WsResponse getTaskResultResponse = wsClient.call(getTaskResultReq).failIfNotSuccessful();
  97. Ce.Task task = parseCeTaskResponse(getTaskResultResponse);
  98. if (TASK_TERMINAL_STATUSES.contains(task.getStatus())) {
  99. return task;
  100. }
  101. Thread.sleep(POLLING_INTERVAL_IN_MS);
  102. currentTime += POLLING_INTERVAL_IN_MS;
  103. } catch (HttpException e) {
  104. throw MessageException.of(String.format("Failed to get CE Task status - %s", DefaultScannerWsClient.createErrorMessage(e)));
  105. } catch (InterruptedException e) {
  106. Thread.currentThread().interrupt();
  107. throw new IllegalStateException("Quality Gate check has been interrupted", e);
  108. }
  109. }
  110. throw MessageException.of("Quality Gate check timeout exceeded - View details on " + ceTaskReportDataHolder.getDashboardUrl());
  111. }
  112. private static Ce.Task parseCeTaskResponse(WsResponse response) {
  113. try (InputStream protobuf = response.contentStream()) {
  114. return Ce.TaskResponse.parser().parseFrom(protobuf).getTask();
  115. } catch (Exception e) {
  116. throw new IllegalStateException("Failed to parse response from " + response.requestUrl(), e);
  117. }
  118. }
  119. private Status getQualityGateStatus(String analysisId) {
  120. GetRequest getQualityGateReq = new GetRequest("api/qualitygates/project_status")
  121. .setMediaType(MediaTypes.PROTOBUF)
  122. .setParam("analysisId", analysisId);
  123. try {
  124. WsResponse getTaskResultResponse = wsClient.call(getQualityGateReq).failIfNotSuccessful();
  125. Qualitygates.ProjectStatusResponse.ProjectStatus status = parseQualityGateResponse(getTaskResultResponse);
  126. return status.getStatus();
  127. } catch (HttpException e) {
  128. throw MessageException.of(String.format("Failed to get Quality Gate status - %s", DefaultScannerWsClient.createErrorMessage(e)));
  129. }
  130. }
  131. private static Qualitygates.ProjectStatusResponse.ProjectStatus parseQualityGateResponse(WsResponse response) {
  132. try (InputStream protobuf = response.contentStream()) {
  133. return Qualitygates.ProjectStatusResponse.parser().parseFrom(protobuf).getProjectStatus();
  134. } catch (Exception e) {
  135. throw new IllegalStateException("Failed to parse response from " + response.requestUrl(), e);
  136. }
  137. }
  138. }