aboutsummaryrefslogtreecommitdiffstats
path: root/sonar-scanner-engine/src/main/java/org/sonar/scanner/qualitygate/QualityGateCheck.java
blob: 94a074818521fa77b73b2fc3976826d9d653f863 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
/*
 * SonarQube
 * Copyright (C) 2009-2023 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.scanner.qualitygate;

import java.io.InputStream;
import java.time.Duration;
import java.time.temporal.ChronoUnit;
import java.util.EnumSet;
import org.sonar.api.Startable;
import org.sonar.api.utils.MessageException;
import org.sonar.api.utils.log.Logger;
import org.sonar.api.utils.log.Loggers;
import org.sonar.scanner.bootstrap.DefaultScannerWsClient;
import org.sonar.scanner.bootstrap.GlobalAnalysisMode;
import org.sonar.scanner.report.CeTaskReportDataHolder;
import org.sonar.scanner.scan.ScanProperties;
import org.sonarqube.ws.Ce;
import org.sonarqube.ws.Ce.TaskStatus;
import org.sonarqube.ws.MediaTypes;
import org.sonarqube.ws.Qualitygates;
import org.sonarqube.ws.Qualitygates.ProjectStatusResponse.Status;
import org.sonarqube.ws.client.GetRequest;
import org.sonarqube.ws.client.HttpException;
import org.sonarqube.ws.client.WsResponse;

public class QualityGateCheck implements Startable {

  private static final Logger LOG = Loggers.get(QualityGateCheck.class);
  private static final EnumSet<TaskStatus> TASK_TERMINAL_STATUSES = EnumSet.of(TaskStatus.SUCCESS, TaskStatus.FAILED, TaskStatus.CANCELED);
  private static final int POLLING_INTERVAL_IN_MS = 5000;

  private final DefaultScannerWsClient wsClient;
  private final GlobalAnalysisMode analysisMode;
  private final CeTaskReportDataHolder ceTaskReportDataHolder;
  private final ScanProperties properties;

  private long qualityGateTimeoutInMs;
  private boolean enabled;

  public QualityGateCheck(DefaultScannerWsClient wsClient, GlobalAnalysisMode analysisMode, CeTaskReportDataHolder ceTaskReportDataHolder,
    ScanProperties properties) {
    this.wsClient = wsClient;
    this.properties = properties;
    this.ceTaskReportDataHolder = ceTaskReportDataHolder;
    this.analysisMode = analysisMode;
  }

  @Override
  public void start() {
    this.enabled = properties.shouldWaitForQualityGate();
    this.qualityGateTimeoutInMs = Duration.of(properties.qualityGateWaitTimeout(), ChronoUnit.SECONDS).toMillis();
  }

  @Override
  public void stop() {
    // nothing to do
  }

  public void await() {
    if (!enabled) {
      LOG.debug("Quality Gate check disabled - skipping");
      return;
    }

    if (analysisMode.isMediumTest()) {
      throw new IllegalStateException("Quality Gate check not available in medium test mode");
    }

    LOG.info("Waiting for the analysis report to be processed (max {}s)", properties.qualityGateWaitTimeout());
    String taskId = ceTaskReportDataHolder.getCeTaskId();

    Ce.Task task = waitForCeTaskToFinish(taskId);

    if (!TaskStatus.SUCCESS.equals(task.getStatus())) {
      throw MessageException.of(String.format("CE Task finished abnormally with status: %s, you can check details here: %s",
        task.getStatus().name(), ceTaskReportDataHolder.getCeTaskUrl()));
    }

    Status qualityGateStatus = getQualityGateStatus(task.getAnalysisId());

    if (Status.OK.equals(qualityGateStatus)) {
      LOG.info("QUALITY GATE STATUS: PASSED - View details on " + ceTaskReportDataHolder.getDashboardUrl());
    } else {
      throw MessageException.of("QUALITY GATE STATUS: FAILED - View details on " + ceTaskReportDataHolder.getDashboardUrl());
    }
  }

  private Ce.Task waitForCeTaskToFinish(String taskId) {
    GetRequest getTaskResultReq = new GetRequest("api/ce/task")
      .setMediaType(MediaTypes.PROTOBUF)
      .setParam("id", taskId);

    long currentTime = 0;
    while (qualityGateTimeoutInMs > currentTime) {
      try {
        WsResponse getTaskResultResponse = wsClient.call(getTaskResultReq).failIfNotSuccessful();
        Ce.Task task = parseCeTaskResponse(getTaskResultResponse);
        if (TASK_TERMINAL_STATUSES.contains(task.getStatus())) {
          return task;
        }

        Thread.sleep(POLLING_INTERVAL_IN_MS);
        currentTime += POLLING_INTERVAL_IN_MS;
      } catch (HttpException e) {
        throw MessageException.of(String.format("Failed to get CE Task status - %s", DefaultScannerWsClient.createErrorMessage(e)));
      } catch (InterruptedException e) {
        Thread.currentThread().interrupt();
        throw new IllegalStateException("Quality Gate check has been interrupted", e);
      }
    }
    throw MessageException.of("Quality Gate check timeout exceeded - View details on " + ceTaskReportDataHolder.getDashboardUrl());
  }

  private static Ce.Task parseCeTaskResponse(WsResponse response) {
    try (InputStream protobuf = response.contentStream()) {
      return Ce.TaskResponse.parser().parseFrom(protobuf).getTask();
    } catch (Exception e) {
      throw new IllegalStateException("Failed to parse response from " + response.requestUrl(), e);
    }
  }

  private Status getQualityGateStatus(String analysisId) {
    GetRequest getQualityGateReq = new GetRequest("api/qualitygates/project_status")
      .setMediaType(MediaTypes.PROTOBUF)
      .setParam("analysisId", analysisId);
    try {
      WsResponse getTaskResultResponse = wsClient.call(getQualityGateReq).failIfNotSuccessful();
      Qualitygates.ProjectStatusResponse.ProjectStatus status = parseQualityGateResponse(getTaskResultResponse);
      return status.getStatus();
    } catch (HttpException e) {
      throw MessageException.of(String.format("Failed to get Quality Gate status - %s", DefaultScannerWsClient.createErrorMessage(e)));
    }
  }

  private static Qualitygates.ProjectStatusResponse.ProjectStatus parseQualityGateResponse(WsResponse response) {
    try (InputStream protobuf = response.contentStream()) {
      return Qualitygates.ProjectStatusResponse.parser().parseFrom(protobuf).getProjectStatus();
    } catch (Exception e) {
      throw new IllegalStateException("Failed to parse response from " + response.requestUrl(), e);
    }
  }
}